I understand the scenario presented in Pro Git about The Perils of Rebasing. The author basically tells you how to avoid duplicated commits:
Do not rebase commits that you have pushed to a public repository.
I am going to tell you my particular situation because I think it does not exactly fit the Pro Git scenario and I still end up with duplicated commits.
Let’s say I have two remote branches with their local counterparts:
origin/master origin/dev
| |
master dev
All four branches contains the same commits and I am going to start development in dev:
origin/master : C1 C2 C3 C4
master : C1 C2 C3 C4
origin/dev : C1 C2 C3 C4
dev : C1 C2 C3 C4
After a couple of commits I push the changes to origin/dev:
origin/master : C1 C2 C3 C4
master : C1 C2 C3 C4
origin/dev : C1 C2 C3 C4 C5 C6 # (2) git push
dev : C1 C2 C3 C4 C5 C6 # (1) git checkout dev, git commit
I have to go back to master to make a quick fix:
origin/master : C1 C2 C3 C4 C7 # (2) git push
master : C1 C2 C3 C4 C7 # (1) git checkout master, git commit
origin/dev : C1 C2 C3 C4 C5 C6
dev : C1 C2 C3 C4 C5 C6
And back to dev I rebase the changes to include the quick fix in my actual development:
origin/master : C1 C2 C3 C4 C7
master : C1 C2 C3 C4 C7
origin/dev : C1 C2 C3 C4 C5 C6
dev : C1 C2 C3 C4 C7 C5' C6' # git checkout dev, git rebase master
If I display the history of commits with GitX/gitk I notice that origin/dev now contains two identical commits C5' and C6' which are different to Git. Now if I push the changes to origin/dev this is the result:
origin/master : C1 C2 C3 C4 C7
master : C1 C2 C3 C4 C7
origin/dev : C1 C2 C3 C4 C5 C6 C7 C5' C6' # git push
dev : C1 C2 C3 C4 C7 C5' C6'
Maybe I don’t fully understand the explanation in Pro Git, so I would like to know two things:
- Why does Git duplicate these commits while rebasing? Is there a particular reason to do that instead of just applying
C5andC6afterC7? - How can I avoid that? Would it be wise to do it?
You should not be using rebase here, a simple merge will suffice. The Pro Git book that you linked basically explains this exact situation. The inner workings might be slightly different, but here’s how I visualize it:
C5andC6are temporarily pulled out ofdevC7is applied todevC5andC6are played back on top ofC7, creating new diffs and therefore new commitsSo, in your
devbranch,C5andC6effectively no longer exist: they are nowC5'andC6'. When you push toorigin/dev, git seesC5'andC6'as new commits and tacks them on to the end of the history. Indeed, if you look at the differences betweenC5andC5'inorigin/dev, you’ll notice that though the content is the same, the line numbers are probably different — which makes the hash of the commit different.I’ll restate the Pro Git rule: never rebase commits that have ever existed anywhere but your local repository. Use merge instead.