What is a fast-forward merge?
Lets say we create a new, empty Git repository. We do an initial commit on the
master branch, then checkout a new branch,
new_branch, and make a couple commits on it.
We might have a Git history that looks something like this:
new_branch is a couple of commits in front of
master. If we checkout
master and merge with
new_branch, we get this:
master was “fast-forwarded” to get caught up with
new_branch. No new commits were made. This happens by default when you merge a branch with another branch that can be reached by following the first branch’s commit history. There will never be conflicts in a fast-forward merge. To force a new commit you can use
Lets say we push up our repo and another person begins to work on our project while we continue to work on it as well. Lets say we add an about page on our local
new_branch and our co-worker adds a footer on her local
new_branch and then pushes her changes up. We might see a Git history like the following:
We can see that
origin/new_branch have diverged.
new_branch is our local
new_branch that we are working from.
origin/new_branch is also a local branch, but it tracks our remote server’s
git fetch will update our local
origin/new_branch to bring in any changes that have been pushed to the remote repo. Then, on
new_branch we can do a
git merge origin/new_branch to actually update our
We have updated our local
new_branch with all the latest changes but our local
origin/new_branch is not fully up to date which means neither is the remote repo’s
new_branch. We can push up our local
new_branch, to update the remote repo.
When we merged
origin/new_branch into our
new_branch, we created an entirely new commit that was just for the merge. “Merge remote-tracking branch ‘origin/new_branch’ into new_branch” is the default message. Sometimes people change the message to something shorter like “merge fix”. It can be argued that this is a rather ugly commit that doesn’t really add much to our git history. Others may have no complaints and instead argue that being able to see the exact git history with merges included is ideal.
Lets look at an alternative to merge: rebase.
We need to add a products page and decide to create a new branch for it,
products_page. We make some commits and our most recent Git history looks like this:
In the meantime, our co-worker has made some changes on the remote’s
new_branch. After a
git fetch, we can see these changes reflected in our
new_branch by checking it out and merging it with
This will result in a fast-forward merge – no new merge commits. This is because
new_branch could be reached directly by following
origin/new_branch‘s commit history.
We could merge
new_branch but this would create a new merge commit while keeping the branching structure of our commit history. This is where the rebase option really starts to shine. Rebase lets us change the commit that a branch is based on.
products_page branch is based on the merge commit. We created the
products_page branch at the merge commit. Rebase allows us to take our
products_page and plop it onto a different commit. We can do this by checking out
products_page and running
git rebase new_branch. The command
git rebase <base> tells Git to place the branch that we are currently on, onto our
Rebasing is a great way to maintain a linear project history. It is also a great way to integrate remote changes into your local repo. Rebasing is like saying, “I want to base my changes on what everybody has already done”.
We can update
new_branch by checking it out and merging
products_page into it. This will result in a fast-forward merge and no new merge commits. We can then delete our
products_page branch. Finally, we can push
new_branch to our remote to update
This is my favorite and most common use of rebase.
Rebase gets even cooler with its interactive option:
git rebase -i <base>. This will open an editor where you have full control over how individual commits from your branch will be transferred to the new base. You can discard certain commits, change commit messages, split commits, combine commits, etc.
Lets take a look at a basic example and say that we did not yet rebase our deleted
products_page branch onto
new_branch. At that point, on
products_page, we have a commit for “add products” and then a separate commit for “add more products”. It might be nice to combine those into one commit. We can do this by running:
git rebase -i HEAD~2
Our “base” here is the commit that is two back from the tip of our current branch. The “add products page skeleton” commit. We can edit all the commits that come after the base and then plop them all back onto our base. Here are the options we have when rebasing:
pick fa8238b add products pick fedff0b add more products # Rebase aae2cd4..fedff0b onto aae2cd4 # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # # These lines can be re-ordered; they are executed from top to bottom. # # If you remove a line here THAT COMMIT WILL BE LOST. # # However, if you remove everything, the rebase will be aborted. # # Note that empty commits are commented out
Note that the commits are listed from earliest to most recent.
squash fedff0b add more products
This will combine the two commits into one brand new commit, remove the two old commits and open a new editor that allows us to edit the commit message for the new commit. If you start an interactive rebase and realize that you shouldn’t be or are unsure of what to do next, you can always run
git rebase --abort at any time within the process to cancel it.
We could have also used
new_branch as our base and edited the commits on
products_page right when we plopped it onto
It is important to note that you should not rebase commits that you have pushed to a public repo. “Treat rebasing as a way to clean up and work with commits before you push them”.
Leave a comment below and follow me on twitter: @QuintonAiken