Reputation: 455
What is the difference between a fast-forwarded git merge
and a git rebase
? Don't both accomplish keeping history linear and no merge commits? If so, why would one use one over the other? If not, which is what I think is true, what is the whole story that I'm not seeing?
Thanks!
Upvotes: 33
Views: 43280
Reputation: 880
Yes, fast-forwarding is a way of maintaining linear history without merge commits.
What is the difference between a fast-forwarded
git merge
and agit rebase
When git performs a fast-forward, it is the same thing whether you are using git merge
or git rebase
.
However, merging 2 branches using git merge
will not always be a fast-forward. In this case, you use git rebase
to prepare commit history in the merging branch so that you get to achieve a fast-forward merge.
git merge
Let us have a scenario: You are working on your own project. Somewhere along, you want to add a feature you have thought of. It is somewhat challenging and you need to do it in a separate branch to see if you can build it.
Your project has a main branch called develop
. And to build this feature, you create a branch called feature
and switch to it. You work on this feature and make several commits. After some while of hacking, you have managed to build it.
At this moment, you project history is like this:
Actually, develop
branch has not had a new commit since you shifted to building and committing in the feature
branch. Therefore, there is no divergent work, i.e feature
branch is directly ahead of develop
. When we merge feature
into develop
using:
git checkout develop
git merge feature
We will get a fast-forward merge. Meaning, Git will not generate a merge commit but just point develop
branch to the tip of feature
branch:
git rebase
to make Git perform fast-forward mergeIn the second scenario: You have more features to build for your project. And you bring in more developers and give them a feature to build in their respective feature/topic branches. Yourself, you are building yet another feature in feature-2
branch.
At some point, you have finished your feature. But at this time, other developers you assigned them features to build, have done so and already merged their work to the develop
branch:
Right now if you merge like:
git checkout develop
git merge feature-2
You won't have a fast-forward merge because develop
has evolved with newer commits since you last branched off. To make a fast-forward merge possible, you have to change the base of feature-2
branch onto the latest commit on develop
branch, like:
git rebase develop feature-2 #checkout feature-2 then rebase
And this is now the history:
Something about git rebase
is that it will change the base of a branch and reapply its commits with new IDs in the order they occured. Hence the commits illustrated as c3'...c5'
in the image.
Once we have rebased feature-2
branch, we can now merge it into develop
:
git checkout develop
git merge feature-2
And the result should be a fast-forward. The commits of feature-2
branch are appended to the top of everything else in develop
branch:
If so, why would one use one over the other?
When we are certain that git will fast forward changes when merging branches, the merge command: git checkout develop; git merge feature
,will yield the same project history as the rebase command: git rebase feature develop
. So there is no reason to use one over the other(If the two branches can be fast-forwaded). Just your preference.
Upvotes: 14
Reputation: 115037
When you are ahead of main, both do the same thing.
If you're ahead and behind main, then a fast-forward merge isn't possible, since there are newer commits on master. But in that case, you can rebase, creating new commits based on the commits that are ahead of main.
When you're ahead of main:
*E-*F-*G-*H-*I BRANCH
/
*A-*B-*C-*D MAIN
When you do a fast forward merge the main pointer is moved forward to the tip of the branch. When you rebase each commit of the branch is moved after the head of MAIN. The end result is the same:
*A-*B-*C-*D-*E-*F-*G-*H-*I MAIN | BRANCH
When you are ahead as well as behind of MAIN:
*E-*F-*G-*H-*I BRANCH
/
*A-*B-*C-*D-*J-*K MAIN
Then you can't fast-forward merge E..I into main, since J..K are in the way. So fast-forward isn't possible in this case.
But you can rebase E..I onto K:
*A-*B-*C-*D-*J-*K-*E'-*F'-*G'-*H'-*I' MAIN | BRANCH
But what happens is that a new commit is made containing the changes of E and appended to main, then a new commit is made with the changes of F and appended to main... etc etc until all commits from the branch have been "moved"/"replayed" on the other branch. The result is again a single line of history with no branches and merges.
Because the commits had to be re-applied and conflicts potentially resolved, the actual commits will change, a new commit-id generated etc.
Upvotes: 66