From 709842180f3eb742d68984fb6b1725bcab10b55b Mon Sep 17 00:00:00 2001 From: Mario Lubenka Date: Thu, 25 Mar 2021 19:52:54 +0100 Subject: [PATCH] feat: configure backmerge strategy Allows configuring the strategy to be used for backmerge. Default is "rebase", i.e. develop is rebased onto master. Another option is "merge", i.e. the master is merged into develop. --- README.md | 6 +++++ src/definitions/config.ts | 3 +++ src/helpers/git.ts | 15 +++++++++++ src/helpers/resolve-config.ts | 3 ++- src/index.ts | 1 + src/perform-backmerge.ts | 6 ++++- test/helpers/git.test.ts | 9 +++++++ test/perform-backmerge.test.ts | 47 ++++++++++++++++++++++++++++------ 8 files changed, 80 insertions(+), 10 deletions(-) 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(); + }); });