When a merge request is merged into master, it may need to be backported to maintenance branches. As each merge request consists of multiple commits, backporting it to another branch without squashing boils down to:
cherry-picking the original commits comprising the merge request one-by-one on top of the target branch (i.e. the branch to which the merge request is being backported),
creating a merge commit linking the previous HEAD of the target branch with the last cherry-picked commit, preferably retaining the commit log message from the original merge commit (e.g. to leave the merge request identifier intact for future reference).
git-replay-merge.sh to the rescue
A script called git-replay-merge.sh was written to make backporting merge requests more convenient by automating the process as much as possible.
How it works
The script can be thought of as a wrapper for git cherry-pick and git merge which cowardly interrupts its actions as soon as things go south. In short, the script:
Detects the range of commits that needs to be cherry-picked.
Initiates the cherry-picking process on a throwaway local branch, called the replay branch below.
If conflicts arise while cherry-picking, you need to fix them yourself and let the script know when you are done.
Once cherry-picking succeeds, the replay branch is merged into a local branch with the same name as the target branch. Original merge commit log message with a modified subject line is used for the merge commit in the target branch.
The replay branch is removed.
Summing up, given:
the ID of the merge commit in the "original" branch (i.e. the branch the merge request was merged into),
the name of the git remote to ask about the target branch,
the name of the target branch,
the script prepares a local branch with the relevant merge request "replayed" for pushing.
The script only prepares a branch for pushing; it is not pushed automatically.
If at any point in the process the user decides the script should not continue its work, --abort can be used to restore the working copy to the state it was in before the script was called.
To use the script:
copy util/git-replay-merge.sh somewhere into your $PATH,
rename the script to git-replay-merge,
make sure the script is executable.
Running git replay-merge (without the first hyphen) should then reward you with a usage message:
$ git log --no-decorate--graph--oneline gitlab/master...* 3abc7bf264 Merge branch 'fix-loadpending-handling' into 'master'|\ | * 801dfe8f5d Add CHANGES entry| * f5079bb877 Do not recheck DNS_ZONEFLG_LOADPENDING in zone_asyncload()| * b9e9361c7b Asynchronous zone load events have no way of getting canceled| * 29b7efdd9f Only clear DNS_ZONEFLG_LOADPENDING in zone_asyncload()if zone loading is completed immediately| * 0e4fba2ced Lock zone before checking whether its asynchronous load is already pending* | 883a9485e9 [master] copyrights|/ * 3548061d03 Merge branch 'gitlab-ci-keep-artifacts' into 'master'...
$ git replay-merge 3abc7bf264 gitlab v9_12Attempting to replay 883a9485e95916a686e56d81fff5130ac3102953..801dfe8f5d69ccb1252fbc57e2feeed34a9a438c on top of gitlab/v9_12 in fix-loadpending-handling-v9_12...Switched to a new branch 'fix-loadpending-handling-v9_12'[fix-loadpending-handling-v9_12 6b5b0dcb94] Lock zone before checking whether its asynchronous load is already pending Date: Thu Feb 15 20:31:49 2018 +0100 1 file changed, 5 insertions(+), 2 deletions(-)[fix-loadpending-handling-v9_12 8c9de139df] Only clear DNS_ZONEFLG_LOADPENDING in zone_asyncload()if zone loading is completed immediately Date: Thu Feb 15 20:31:51 2018 +0100 1 file changed, 4 insertions(+), 2 deletions(-)[fix-loadpending-handling-v9_12 eb8de80fcd] Asynchronous zone load events have no way of getting canceled Date: Thu Feb 15 20:31:53 2018 +0100 1 file changed, 1 insertion(+), 6 deletions(-)[fix-loadpending-handling-v9_12 87ac04b893] Do not recheck DNS_ZONEFLG_LOADPENDING in zone_asyncload() Date: Thu Feb 15 20:31:54 2018 +0100 1 file changed, 11 deletions(-)error: could not apply 801dfe8f5d... Add CHANGES entryhint: after resolving the conflicts, mark the corrected pathshint: with 'git add <paths>' or 'git rm <paths>'hint: and commit the result with 'git commit'Replay interrupted. Conflicts need to be fixed manually.When done, run "git replay-merge --continue".Use "git replay-merge --abort" to abort the replay.
Oops, there is a conflict in CHANGES:
$ git statusOn branch fix-loadpending-handling-v9_12Your branch is ahead of 'gitlab/v9_12' by 4 commits.(use "git push" to publish your local commits)You are currently cherry-picking commit 801dfe8f5d.(fix conflicts and run "git cherry-pick --continue")(use "git cherry-pick --abort" to cancel the cherry-pick operation)Unmerged paths:(use "git add <file>..." to mark resolution) both modified: CHANGESno changes added to commit (use "git add" and/or "git commit -a")
Let's fix the conflict using an editor of choice. Once that is done,
cherry-picking can continue:
$ $VISUAL CHANGES$ git add CHANGES$ git replay-merge --continue[fix-loadpending-handling-v9_12 b7a6e602e9] Add CHANGES entry Date: Thu Feb 15 20:31:55 2018 +0100 1 file changed, 6 insertions(+)Attempting to merge fix-loadpending-handling-v9_12 into v9_12...Switched to a new branch 'v9_12'Merge made by the 'recursive' strategy. CHANGES | 6 ++++++ lib/dns/zone.c | 29 +++++++++-------------------- 2 files changed, 15 insertions(+), 20 deletions(-)Replayed fix-loadpending-handling onto v9_12.To push the replay, use: git push gitlab v9_12:v9_12
That's it, the replay is complete:
$ git log --graph--oneline v9_12* 6801ed2c4f (v9_12) Merge branch 'fix-loadpending-handling-v9_12' into 'v9_12'|\ | * b7a6e602e9 Add CHANGES entry| * 87ac04b893 Do not recheck DNS_ZONEFLG_LOADPENDING in zone_asyncload()| * eb8de80fcd Asynchronous zone load events have no way of getting canceled| * 8c9de139df Only clear DNS_ZONEFLG_LOADPENDING in zone_asyncload()if zone loading is completed immediately| * 6b5b0dcb94 Lock zone before checking whether its asynchronous load is already pending|/ * 44d995992a (gitlab/v9_12) Merge branch 'fix-cpp-check-errors' into 'v9_12'...
The branch is now ready to be pushed using the command suggested by git replay-merge.
Example: the easy case (no conflicts)
With the pessimistic scenario sorted out, let's now try backporting !21 (merged) to v9_12.