Behind the scene: git rebase
In the previous post, we saw that a git pull
command could lead to a merge commit.
A lot of developers prefer to have a linear git history and deeply despise those merge commits.
First. A git pull
doesn’t always lead to a merge commit:
The important word here is Fast-forward: all the commits on the local machine were already on the remote, so git was able to bring back the new commit without having to create a merge commit.
But, if someone else pushed a commit to the same branch before you had time to push yours, then the git pull
will create that pesky merge commit. That’s what happened in the previous post.
By default, git pull
will try to perform a fast forward, but if it is not always possible.
To anticipate what will happen, you can either do a git fetch
to get all the remote commits and to then assess the situation.
You can also tell git that you only want to do the git pull
if a fast forward can be performed.
To prevent a merge commit during a git pull, you can perform the command: git pull --ff-only
This time the fast forward is not possible so the git pull
aborts without creating any merge commit.
A git pull
always performs a git fetch
first, meaning that all the commits are now on your local machine for you to assess.
Before the git fetch
:
After the git fetch:
This new tree shows that:
- your working directory has not changed:
HEAD
still points to your last commit - the
refs/remotes
references shows you what is happening on the server: the commit26d0
is the commit which prevents the fast forward.
To solve that connundrum, the solution is to perform a git rebase
.
The rebase will bring back the remote commits and then try to reapply your local changes on top of those commits.
In our current test scenario, the rebase is not smooth : both our local commit and the remote commit changed the same file README.txt
file.
Here, the conflict is easy to fix:
Once you have resolved the conflict(s), you follow the instructions given by git:
- you
git add
the files you have changed during the conflicts resolution - you complete the rebase with
git rebase --continue
Now your commit tree looks like that:
You are back to a situation where you can push your commit tree to the remote — unless someone beat you to the punch and already pushed a new commit to the remote.
The 847a
commit is still there. The git rebase
did not destroy it.
There is no references to that dangling commit though, and it will be garbage collected (aka deleted) by git at some point in the future. That also means that you still have the opportunity to revive it if needed using reflog
(more on that another day)
Time for a final git push
:
As expected, your commit tree is now:
If you have a sneak peek on the server commit tree:
Mission accomplished. A nice linear history of commits. End of the civilized world averted.