What do you do when you have two commits in the past that you want to re-arrange? or combine? or completely obliterate? What about after you’ve created patches, emailed them to a maintainer, and that maintainer has accepted some of them into the official repo? What if you realize that your topic branch actually has two topics in it? These are all jobs for
Before we can talk about these use cases, we need a way to refer to commits or ranges of commits. There are two main ways that I use when using
git rebase <branch> and
git rebase --onto <newbase> <beginning> <ending>. The former says, “take all the commits in my branch that aren’t currently in <branch> and put them on top of <branch>” and the latter says, “take all the commits between <beginning> and <ending> (excluding <beginning> itself) and put them on top of <newbase>”.
Now, let’s go through these use cases one at a time, starting from the simplest.
If you’ve submitted patches to a maintainer, and all or some of them have been accepted to a project, your topic branch will no longer be the same as theirs (they could have applied them at a different point, added a signed-off tag, or many other things). To reconcile a topic branch with what’s in the current (remote) master branch, you could run
git rebase origin/master from your topic branch. All the patches that match up exactly with patches in
origin/master will disappear and all patches that were edited or not included will be created on top of
origin/master. The topic branch you were in will then point to the newly created commits and the old commits will be relegated to the reflog.
If you realize that your topic branch actually has two totally separate topics in it, you can use the –onto option. A diagram helps to describe this better (diagram blatantly taken from
git rebase man page):
H---I---J topicB / E---F---G topicA / A---B---C---D master
If you want to make topicA and topicB separate branches that both branch off of master, you would run
git rebase --onto master topicA topicB. After running it, your repository would look like this:
H'--I'--J' topicB / | E---F---G topicA |/ A---B---C---D master
H’, I’, and J’ have the same contents as H, I, and J respectively but have different commit hashes because they have different parents.
If you want to change the order or contents of any commits in the past,
git rebase -i is your friend. If you run something like
git rebase -i HEAD~3 you will be dropped into an editor with lines something like this:
pick 428c9da Fix bugaboo pick 503ffbe Some other patch pick de6b9c5 Even another one # Rebase fc08684..742098a onto fc08684 # # Commands: # pick = use commit # edit = use commit, but stop for amending # squash = use commit, but meld into previous commit # # If you remove a line here THAT COMMIT WILL BE LOST. # However, if you remove everything, the rebase will be aborted. #
By changing the first element of a line, you can choose to include the commit, stop rebasing when it gets to that commit (so you can change things), or combine two commits into one. You can also choose to throw away a commit (by deleting the line) and change the order of commits. This is the main way to modify git history in a more-than-just-the-most-recent-commit sense.
git rebase -i can also use the –onto option, so you could re-order, squash, and modify patches as you change their base from one point in history to another.
Because of how
git rebase works, merges can be tricky. If you have to rebase over a merge (and don’t want the merge to just be included in one of the patches) be sure to use the -p (–preserve-merges) option with -i.
git rebase modifies history, it’s not a very nice command to run against commits that are publicly available. Other people don’t like it when they have commits that depend on your commits and you rewrite the history of your commits.