git rebase
Rebasing allows you to re-write commit history. This only really works when:
- You are changing commits that haven't been merged into master yet.
- No-one else is collaborating on your branch at the same time as you.
- Your repository is set up to accept "force" pushes (i.e. your repository allows you to re-write history).
I mostly use rebasing when changing commits locally (before pushing) or rebasing my own remote branch (that only I am working on) before raising a pull request.
ABORT!
Before you start experimenting with git rebase. Make sure you are comfortable with git rebase --abort
. This command abandons any changes done through a rebase and takes you back to the state you were in before you executed the rebase command. This is super handy when experimenting with git rebase
!
git rebase main
Quite often, you'll be working on a feature branch and someone will merge their feature into main before you. How rude!! To make sure your branch includes these latest changes (and is compatible) you will need to pull the latest remote main branch to your local main branch, then your local main branch onto your feature branch. There are a couple of ways of doing this:
Chronologically:
git checkout main
git pull --rebase
git checkout -
(the hypen just means your previous branch, which in this case would be the feature you were just working on)
git rebase main
Without moving anywhere!:
git pull --rebase origin master
or git rebase origin master
Don't use git merge main
:
Some people will merge main onto their branch to include the latest changes, but this leaves a horrible merge commit. Merge commits have their benefits at times, but there is no benefit. No-one cares about WHEN you started building your feature. If you started your feature a month ago you might have 19 merge commits where you updated your branch with changes on master, but if you started your feature yesterday, you might not have any.
interactive
If you want to control the behaviour of individual commits you will want to start an interactive rebase. To do this, you can just add the interactive
flag (-i
for short) like so: git rebase --interactive main
. This will present all of the commits since main
for you to play with.
There are lots of options when rebasing a branch. Helpfully, these are usually listed at the bottom of the rebase window:
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
# commit's log message, unless -C is used, in which case
# keep only this commit's message; -c is same as -C but
# opens the editor
# x, exec <command> = run command (the rest of the line) using shell
...
The most useful actions that don't involve using any of these commands are:
- You can remove commits by deleting the line from this "rebase" view.
- You can re-arrange these commits into your preferred order (but be careful, as you may cause merge conflicts if commits edit the same line in a file)
All other actions will involve using these key words, described with their own sections below.
edit
Remember the git commit --amend
super powers (see blog for more info). Well... marking a commit with edit
(or just e
) means that mid-way through the rebase, git will stop after this commit (and all others marked with edit
). This means you can then make changes to the code, stage them and "amend" the most recent commit (the one we marked with edit
). This is a really nice way to make a simple change to a commit that isn't the latest.
fixup
Fixup is useful for combining multiple commits into one. This is quite common if you have been using git to arbritrarily save your progress as you develop a feature but you then want to organise your commits neatly into logical topics. When marking a commit as f
or fixup
git will "smush" those changes into the commit above (the previous commit). This effectively converts two commits into one (using the "top"/oldest commit's message as the commit message).
squash
Squash is exactly the same as fixup
except it preserves the commit messages of all commits as they get smushed together. I don't find this one nearly as helpful as it often leads to commit messages with several "title" lines. If I use squash within a rebase I normally end up later tidying the commit message anyway with reword
.
reword
If there is one or more commit messages that you want to re-write, you can mark them with reword
. It opens the commit message in its current state (using your default editor), allows you to edit it and stores the commit message when the editor is closed. Simples!
autostash
Sometimes you will start rebasing a branch while you still have changes in your repository. For example, regardless of the feature I'm working on I may want to reword that dodgy commit message before I forget what it's for! For this, you can use git rebase --interactive --autostash main
(which will automatically stash your changes, do the rebasing magic, then "pop" your stash back into place). Sweet!
autosquash
This is a super handy bit of functionality, but only if you know about "manual" fixup commits. We discussed that you can "smush" changes into a previous commit during an interactive rebase. If you have changes that haven't yet been committed, you can stage the changes with git add
and create a special commit that can automagically be fixed up into any previous commit! You can refer to either the full hash, the abbreviated hash or the title:
git commit --fixup <commit-hash>
such as git commit --fixup a0b1c2d3e4f5g6h7i8j9k0l1m2n3o4p5
git commit --fixup <commit-hash-abbrev>
such as git commit --fixup a0b1c2d
git commit --fixup <title>
such as git commit --fixup add super sweet feature
After doing this, use git log
to see your commit history. You'll notice that your changes are in the most recent commit with the fixup
notation. Now you can simply run git rebase --autosquash
to tidy your changes into your chosen commit. Tidy!