Everyone likes to keep the source control history linear, preferring fast-forward merges to merge commits. It’s really not that hard. Promise.
Git’s commit structure is a linked list of snapshots whose commits point to one or several ancestors. This means that you must “base” your work on a previous commit.
Merge commits can be useful when merging topic/feature branches together to indicate, in the history, where branches were combined.
When working on a single branch, however, they are ugly, unnecessary, clutter the history, and look like this:
This is the result of doing work — not necessarily on the same file just in the same branch — in two places at once. Here, eada9ae and eac49bb are both children of 7036724, but they contain different snapshots which were merged at 53ad270.
Let’s start with a history like this:
Now, let’s work on the same file in two places at once (and also simulate a merge conflict). To do this, I’ll edit the file locally in the working copy, and I’ll edit it in the BitBucket editor (which gets committed and pushed immediately):
Now, edit and commit the file locally:
msanford@Tenjin:/tmp/rebase-example $ git commit -m “Fourth commit — working copy” [master 25dd301] Fourth commit — working copy 1 file changed, 1 insertion(+)
Our branches are now out of sync: HEAD in origin/master points to 4ff5d2 whereas it points to 25dd301 in the working copy:
msanford@Tenjin:/tmp/rebase-example $ git log --oneline 25dd301 Fourth commit — working copy e8a2bb8 Third commit — working copy 988eb03 Second commit — working copy 4168ee0 Initial commit — working copy
At this point, if you git push from the working copy, you’ll see this familiar message, suggesting that you git pull:
msanford@Tenjin:/tmp/rebase-example $ git push To [firstname.lastname@example.org](mailto:email@example.com):michaelsanford/rebase-example.git ! [rejected] master -> master (fetch first) error: failed to push some refs to ‘[firstname.lastname@example.org](mailto:email@example.com):michaelsanford/rebase-example.git’ hint: **Updates were rejected because the remote contains work that you do** hint: **not have locally**. This is usually caused by another repository pushing hint: to the same ref. You may want to first integrate the remote changes hint: (e.g., ‘ **git pull** …’) before pushing again. hint: See the ‘Note about fast-forwards’ in ‘ **git push --help** ’ for details.
Note that the hint also suggests consulting “the fast-forward section of the manpage” which, of course, nobody has ever done. It suggests a more elegant course of action.
msanford@Tenjin:/tmp/rebase-example $ **git pull --rebase** remote: Counting objects: 5, done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From bitbucket.org:michaelsanford/rebase-example e8a2bb8..4ff45d2 master -> origin/master **First, rewinding head to replay your work on top of it…** Applying: Fourth commit — working copy **Using index info to reconstruct a base tree…** M README.md Falling back to patching base and 3-way merge… Auto-merging README.md CONFLICT (content): Merge conflict in README.md Failed to merge in the changes. Patch failed at 0001 Fourth commit — working copy The copy of the patch that failed is found in: /private/tmp/rebase-example/.git/rebase-apply/patch When you have resolved this problem, run “git rebase —continue”.
Rebase succeeded (no error messages)? No merge conflicts? You can push right away!
Oh no, a merge conflict in the file we were working on!
Edited locally in the working copy. Second edit locally from the working copy. Third commit locally from the working copy. <<<<<<< HEAD Fourth commit from BitBucket. ======= Fourth commit locally from the working copy. >>>>>>> Fourth commit — working copy
No problem: we can edit the file in the working copy, resolve the conflict, and rebase continue.
msanford@Tenjin:/tmp/rebase-example $ vi README.md msanford@Tenjin:/tmp/rebase-example $ git add README.md msanford@Tenjin:/tmp/rebase-example $ git rebase —continue Applying: Fourth commit — working copy msanford@Tenjin:/tmp/rebase-example $ git status On branch master Your branch is ahead of ‘origin/master’ by 1 commit. (use “git push” to publish your local commits)
Git rebase has, as the name suggests, changed the base of our working copy’s commit history to match origin/master’s base. Since both the local working copy and the remote have the same base commit (after fixing the merge conflict) it has allowed us to fast-forward our work as a patch on top of the remote’s commit and maintain a linear history.
This results in a nice, straight line that contains all of the work done in a semantically-sensible way (because the merged changes are topical):
In the first example (with the merge commit) if our working copy held the blue line with eac49bb, git pull rebase would pull eada9ae, then create a patch and replay the work contained in eac49bb on top of that commit (_instead of using _7036724 as the base_)_, fast-forwarding it and preserving a linear topical history.
In this example I have used only one developer, but that is not relevant: you can do this in teams exactly the same way!