Git 101: Surgical precision: git reset --soft and cherry-pick
This post is one of the Git 101: Conquer the GIT POWER series.
Summary:
1 - Intro
This combination is helpful when merging two branches with some very distant relation; in other words, the dev forgot about updating his branch, and he can't merge anymore because the branch has so many changes that it is impossible to do without facing countless conflicts.
The repository https://github.com/ribas89/git-101-examples contains a simple example, but you can do this in any situation, especially on very old/merged branches. A reminder that operation will modify the commit history, so like all other operations of the same kind, DO NOT do these in public branches.
2 - Find common ground
The first step is to find the commit that originates all other branches. Before that commit, the commit history shared by the branches is the same; after that, they diverge. Let's examine the "organize-commits-3" branch:
We can see that all the branches diverge from the commit feat: add essential component for feature A (hash ef6bbf0f0def1622c52178192fc4eb41f309a86b). We will use that commit as a reference for our next operations.
Create a temporary branch to start the merge process:
git checkout -B temp
git reset --hard origin/organize-commits-3
3 - Reset soft
Execute the reset soft operation using the common ground commit as a reference:
git reset --soft ef6bbf0f0def1622c52178192fc4eb41f309a86b
Using the gitk interface, we should see this:
What happened? The rebase --soft command removed all the commits but preserved the files. That is super useful when you don't want to deal with all the conflicts and commit merges but still want the files of these commits.
Now we add the changes and create another commit with them.
git add .
git commit -m "feat: add new features"
Using the gitk interface, we should see this:
Congratulations! You saved a bunch of time and created a single commit with the changes, making it easier to spot and manage the commit history. Of course, this should not be the standard approach to merging branches, but it is convenient in some hairy situations. Since you changed the commit history, you need to force push your private branch to update it with the new commit history:
git push origin +organize-commits-3
4 - Cherry pick
You cleaned the mess, but now you must put your commit inside another branch. You will create a neat and clean commit history using the cherry pick operation.
With this command, you select only your commit and put it on top of the branch you are trying to merge. You don't mess with commits, preserve the commit history and remove the pain of dealing with future merges.
To start, you must copy the commit feat: add new features hash we create earlier.
In my case, it is ac6e56dc82501ad513669e8d566ebe87b3e9475a, but because this commit was the result of a reset --soft operation, it will be a different hash on your local repo.
Save this hash for later and reset the temporary branch with our target branch. In this example, we will use the branch organize-commits-2
git checkout -B temp
git reset --hard origin/organize-commits-2
With the temporary branch in position, execute the git cherry-pick command with the reference we noted:
git cherry-pick ac6e56dc82501ad513669e8d566ebe87b3e9475a
Check the results with the gitk interface:
And like magic, all the files are in place, your changes are intact, and the updates inside other files are preserved.
Sometimes the cherry-pick will create conflicts. But it is easier to fix them if you compare them with a merge or rebase operation.
5 - Outro
These commands are for specific situations, like when you merge an ancient branch or deal with a merge ladder. If you follow some rules about rebasing and updating the private branch, you will rarely use them. But when things get hot, you know something that can help you fix the situation much faster and easier than just merging.