diff --git a/README.md b/README.md index d476cca..7d47b65 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ If you do, you may set the [clearWorkspace](#clearWorkspace) option to stash the | Options | Description | Default | |-----------|---------------------------------------------------------------------------------|-----------| | `branchName` | The branch where the release is merged into. See [branchName](#branchName). | develop | +| `backmergeStrategy` | How to perform the backmerge. See [backmergeStrategy](#backmergeStrategy). | rebase | | `plugins` | Plugins defined here may stage files to be included in a back-merge commit. See [plugins](#plugins). | [] | | `message` | The message for the back-merge commit (if files were changed by plugins. See [message](#message). | `chore(release): Preparations for next release [skip ci]` | | `forcePush` | If set the back-merge will be force-pushed. See [forcePush](#forcePush). | false | @@ -121,3 +122,8 @@ Setting this option will stash all uncommitted changes from Git workspace before #### `restoreWorkspace` Setting this option will restore the stashed changes after the backmerge completed. + +#### `backmergeStrategy` + +This setting will determine whether the _develop_ branch should be rebased onto _master_ or _master_ should be merged into _develop_. +Allowed values: rebase (default), merge diff --git a/src/definitions/config.ts b/src/definitions/config.ts index 9e1d4b4..7693149 100644 --- a/src/definitions/config.ts +++ b/src/definitions/config.ts @@ -1,5 +1,8 @@ +export type BackmergeStrategy = "rebase" | "merge"; + export interface Config { branchName: string; + backmergeStrategy: BackmergeStrategy; plugins: any; forcePush: boolean; message: string; diff --git a/src/helpers/git.ts b/src/helpers/git.ts index 90946cf..2b2486a 100644 --- a/src/helpers/git.ts +++ b/src/helpers/git.ts @@ -130,4 +130,19 @@ export default class Git { async rebase(branch: string) { await execa('git', ['rebase', `origin/${branch}`], this.execaOpts); } + + /** + * Rebases the currently checked out branch onto another branch. + * + * @param {String} branch The branch to rebase onto. + * @param {boolean} fromOrigin + * + * @throws {Error} if the rebase failed. + */ + async merge(branch: string, fromOrigin = true) { + if (fromOrigin) { + branch = 'origin/' + branch; + } + await execa('git', ['merge', branch], this.execaOpts); + } } diff --git a/src/helpers/resolve-config.ts b/src/helpers/resolve-config.ts index 007aabb..f1d9249 100644 --- a/src/helpers/resolve-config.ts +++ b/src/helpers/resolve-config.ts @@ -2,9 +2,10 @@ import {isNil} from 'lodash'; import {Config} from "../definitions/config"; export function resolveConfig(config: Partial): Config { - const {branchName, plugins, message, forcePush, allowSameBranchMerge, clearWorkspace, restoreWorkspace} = config + const {branchName, backmergeStrategy, plugins, message, forcePush, allowSameBranchMerge, clearWorkspace, restoreWorkspace} = config return { branchName: isNil(branchName) ? 'develop' : branchName, + backmergeStrategy: isNil(backmergeStrategy) ? 'rebase' : backmergeStrategy, plugins: isNil(plugins) ? [] : plugins, message: message, forcePush: isNil(forcePush) ? false : forcePush, diff --git a/src/index.ts b/src/index.ts index 9e698a2..df41c11 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,6 +19,7 @@ export async function verifyConditions(pluginConfig: Config, context: Context) { const preparePlugin = castArray(options.prepare) .find(config => config.path && config.path === '@saithodev/semantic-release-backmerge') || {}; pluginConfig.branchName = defaultTo(pluginConfig.branchName, preparePlugin.branchName); + pluginConfig.backmergeStrategy = defaultTo(pluginConfig.backmergeStrategy, preparePlugin.backmergeStrategy); pluginConfig.plugins = defaultTo(pluginConfig.plugins, preparePlugin.plugins); pluginConfig.forcePush = defaultTo(pluginConfig.forcePush, preparePlugin.forcePush); pluginConfig.message = defaultTo(pluginConfig.message, preparePlugin.message); diff --git a/src/perform-backmerge.ts b/src/perform-backmerge.ts index d208f0c..16b565d 100644 --- a/src/perform-backmerge.ts +++ b/src/perform-backmerge.ts @@ -44,7 +44,11 @@ export async function performBackmerge(git: Git, pluginConfig: Partial, // Branch is detached. Checkout master first to be able to check out other branches await git.checkout(masterBranchName); await git.checkout(developBranchName); - await git.rebase(masterBranchName); + if (options.backmergeStrategy === 'merge') { + await git.merge(masterBranchName); + } else { + await git.rebase(masterBranchName); + } } else { await git.checkout(developBranchName); } diff --git a/test/helpers/git.test.ts b/test/helpers/git.test.ts index d85fdf3..c29ba5d 100644 --- a/test/helpers/git.test.ts +++ b/test/helpers/git.test.ts @@ -104,6 +104,15 @@ describe("git", () => { ); }); + it("merge", async () => { + await subject.merge('master'); + expect(execa).toHaveBeenCalledWith( + 'git', + ['merge', 'origin/master'], + expect.objectContaining(execaOpts) + ); + }); + it("getStagedFiles", async () => { const execaMock: any = execa; execaMock.mockResolvedValueOnce({ diff --git a/test/perform-backmerge.test.ts b/test/perform-backmerge.test.ts index f9d9b56..fcb2d48 100644 --- a/test/perform-backmerge.test.ts +++ b/test/perform-backmerge.test.ts @@ -19,7 +19,7 @@ describe("perform-backmerge", () => { when(mockedGit.push(anyString(), anyString(), anything())).thenResolve(); const context = {logger: instance(mockedLogger), branch: {name: 'master'}, options: {repositoryUrl: 'my-repo'}}; - performBackmerge(instance(mockedGit), {branchName: 'develop', plugins: []}, context) + performBackmerge(instance(mockedGit), {branchName: 'develop'}, context) .then(() => { verify(mockedLogger.log('Release succeeded. Performing back-merge into branch "develop".')).once(); verify(mockedGit.checkout('master')).once(); @@ -44,7 +44,7 @@ describe("perform-backmerge", () => { when(mockedGit.push(anyString(), anyString(), anything())).thenResolve(); const context = {logger: instance(mockedLogger), branch: {name: 'master'}, options: {repositoryUrl: 'my-repo'}}; - performBackmerge(instance(mockedGit), {branchName: 'master', plugins: [], allowSameBranchMerge: true}, context) + performBackmerge(instance(mockedGit), {branchName: 'master', allowSameBranchMerge: true}, context) .then(() => { verify(mockedLogger.log('Release succeeded. Performing back-merge into branch "master".')).once(); verify(mockedGit.configFetchAllRemotes()).once(); @@ -76,7 +76,7 @@ describe("perform-backmerge", () => { when(mockedGit.push(anyString(), anyString(), anything())).thenResolve(); const context = {logger: instance(mockedLogger), branch: {name: 'master'}, options: {repositoryUrl: 'my-repo'}}; - performBackmerge(instance(mockedGit), {branchName: '${branch.name}', plugins: [], allowSameBranchMerge: true}, context) + performBackmerge(instance(mockedGit), {branchName: '${branch.name}', allowSameBranchMerge: true}, context) .then(() => { verify(mockedLogger.log('Release succeeded. Performing back-merge into branch "master".')).once(); verify(mockedGit.configFetchAllRemotes()).once(); @@ -125,7 +125,7 @@ describe("perform-backmerge", () => { when(mockedGit.push(anyString(), anyString(), anything())).thenResolve(); const context = {logger: instance(mockedLogger), branch: {name: 'master'}, options: {repositoryUrl: 'my-repo'}}; - performBackmerge(instance(mockedGit), {branchName: 'develop', plugins: [], forcePush: true}, context) + performBackmerge(instance(mockedGit), {branchName: 'develop', forcePush: true}, context) .then(() => { verify(mockedLogger.log('Release succeeded. Performing back-merge into branch "develop".')).once(); verify(mockedGit.checkout('master')).once(); @@ -157,8 +157,7 @@ describe("perform-backmerge", () => { instance(mockedGit), { branchName: 'develop', - message: 'my-commit-message', - plugins: [] // plugin interactions are tested in helpers/plugins test + message: 'my-commit-message' }, context ); @@ -191,8 +190,7 @@ describe("perform-backmerge", () => { { branchName: 'develop', clearWorkspace: true, - restoreWorkspace: true, - plugins: [] // plugin interactions are tested in helpers/plugins test + restoreWorkspace: true }, context ); @@ -210,4 +208,37 @@ describe("perform-backmerge", () => { verify(pushAction).once(); verify(mockedGit.unstash()).calledAfter(pushAction); }); + + it("merge as backmerge strategy", async () => { + const mockedGit = mock(Git); + const mockedLogger = mock(NullLogger); + when(mockedGit.checkout(anyString())).thenResolve(); + when(mockedGit.configFetchAllRemotes()).thenResolve(); + when(mockedGit.getStagedFiles()) + .thenReturn(new Promise(resolve => resolve([]))); + when(mockedGit.fetch()).thenResolve(); + when(mockedGit.commit(anyString())).thenResolve(); + when(mockedGit.merge(anyString())).thenResolve(); + when(mockedGit.push(anyString(), anyString(), anything())).thenResolve(); + + const context = {logger: instance(mockedLogger), branch: {name: 'master'}, options: {repositoryUrl: 'my-repo'}}; + + await performBackmerge( + instance(mockedGit), + { + branchName: 'develop', + backmergeStrategy: 'merge' + }, + context + ); + verify(mockedLogger.log('Release succeeded. Performing back-merge into branch "develop".')).once(); + verify(mockedGit.checkout('master')).once(); + verify(mockedGit.configFetchAllRemotes()).once(); + verify(mockedGit.fetch()).once(); + + verify(mockedGit.checkout('develop')).once(); + verify(mockedGit.merge('master')).once(); + + verify(mockedGit.push('my-repo', 'develop', false)).once(); + }); });