Disclaimer: Many readers have noted that this is a straw man argument and git was not designed to use this workflow. I wrote this blog post because I had seen people at two different companies working this way so it's only addressed to people who find themselves working this way accidentally. When you have conflicts in the cherry-picking workflow you can lose track of what has been merged and what has not been merged into particular branches. I've seen teams lose emergency hotfixes and not be aware of it because of this. Cherry-picking has its uses and I don't mean to discourage it when necessary but it shouldn't be something you need to do more frequently than merging.
Cherry-picking Workflow
Changes are made in a maintenance branch off of the release where the bug was found. The commitid from this change is then cherry-picked into the current integration branch.
% git checkout -b maintenance-branch <release tag or commitid> # (if the maintenance branch doesn't yet exist)
% git checkout -t origin/maintenance-branch # (if the branch already exists)
% git commit -am "Made a bug fix" # note the commitid
% git push origin maintenance-branch
% git checkout integration-branch # (e.g. master)
% git cherry-pick <commitid>
# resolve conflicts
% git push origin integration-branch
Problems with the cherry-picking workflow
- When a change is cherry-picked into a branch and there is a conflict a new commitid is created (meaning that there are two commitids for the same change).
- All of git's diagnostic tools which determine differences between branches stop working when there are multiple commitids for the same change.
Because git's diagnostic tools don't work it is hard to determine if all the changes that were made in the maintenance branch made their way to the integration branch.
% git cherry -v <upstream> <head> # list of commit differences between branches % git branch --contains <commitid> # which branches contain the commitid % git branch --no-merge # list which branches haven't been merged in
For example:
Under the cherry-pick workflow, even though a bugfix was cherry-picked into the integration branch and there was a conflict git cherry -v reports that the integration branch is missing this commit from the maintenance branch.
% git cherry -v maintenance-branch integration-branch
+ 33de19776f4446d92b45e1fdfb2d9c37b3a867a7 Made a bug fix
Merge Workflow
Changes are made in a maintenance branch off of the release where the bug was found (same as in the cherry-picking workflow). The maintenance branch is then merged into the current integration branch.
% git checkout -b maintenance-branch <commitid> # (if the branch doesn't yet exist)
% git checkout -t origin/maintenance-branch # (if the branch already exists)
% git commit -am "Made a bug fix"
% git push origin maintenance-branch
% git checkout integration-branch # (e.g. master)
% git merge origin/maintenance
# resolve conflicts
% git push origin integration-branch
Benefits
- The history of the changes made in the maintenance branch is preserved and there are no duplicate commitids for the same changes - just an extra merge commit.
- git's diagnostic tools work.
- It is much harder to lose work because all the commits from the maintenance branch make their way from the maintenance branch to the integration branch with each merge.
- You can have git automatically verify that the changes have all made their way from the maintenance branch to the integration branch.
Confusingly enough one of the most useful tools in the merge workflow to check that the state is correct is named "cherry". It shows the commits that were made in one branch and not the other. It should show no missing changes when following the merge workflow because all the changes from the maintenance should make their way into the integration branch:
% git cherry -v maintenance-branch integration-branch
[nothing]
Draws
- Same number of commands for both cherry-picking (neither is more complicated)
- You need to resolve conflicts either way (this might actually be a win for the merge workflow because there are some scenarios where having git understand the merge history will make multiple merges easier than multiple cherry-picks)