Using Git rebase
- Sunday, June 10th 2018
- 4 minutes read
Back to when I started using Git, I was faced with this challenge. Someone has updated the upstream - how do I incorporate the latest changes into my working branch?
For the sake of simplicity, the upstream will be
master and the working branch
The obvious route is to pull
master and merge it into the working branch. So something like:
git pull origin master
Unfortunately, this way we're inevitably creating a new commit.
Working with a team that provides frequent updates while we're slowly working on our feature, we may end up with quite a lot of noise in the git graph.
What we really want in this scenario is the following:
- Isolate the local commits
- Get the latest upstream updates
- Apply the changes on top
- Resolve any conflicts
So we don't have to merge our branch in the updated upstream but rebase it.
First, let's fast-forward the local master branch for good measure.
git checkout master git pull
Now let's head back to our working branch and do a rebase. All of the local commits will be applied on top of the updated master
git checkout working_branch git rebase origin master
Note that you can completely ignore updating your local master branch and simply run
git rebase origin/master
I like having my local master up to date so I opt for the first case.
At this point, everything will either run smoothly or some conflicts will arise.
git rebase --abortwill completely stop the rebase. The branch's initial state will be restored.
git rebase --skipwill ignore the local commit that causes the problem - never used it, to be honest
- Fix the conflicts, run
git add/rm <files_to_fix>to mark them as resolved and keep the rebase going with
git rebase --continue
Having finished the work, it's always a nice idea to 'squash' commits that are meant to fix previous commits. This way we can automatically generate a Changelog out of the commits and have a tidy git log.
For this reason, doing an 'Interactive' rebase is really helpful
git rebase -i master
Here's an example of an interactive rebase prompt. The 'formm' typo is intentional.
pick 07c6add Include password strength indicator in registration formm pick d29b1fb Fix the regex pick 317tt36 Minor typo pick fa2fff3 CSS tweaks # Rebase 44a7ee6..fa2fff3 onto 44a7ee6 # # 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. #
Now you can do any of the following
- Pick - Keep the commit
- Reword - Keep the commit, get a prompt to update the message
- Edit - Keep the commit, allows to modify files
- Squash - Merge into the commit above, get a prompt to update the message
- Fixup - Same as
Squashbut won't prompt for a new message
- Exec - Allows running shell commands against a commit
Note that you can reorder these commits around if needed.
For this case, we'll
- Reword and keep the first commit
- Squash the rest and discard the commit message by doing a
r 07c6add Include password strength indicator in registration formm f d29b1fb Fix the regex f 317tt36 Minor typo f fa2fff3 CSS tweaks
Consequently, our branch will contain a single meaningful commit on top of all the latest upstream changes.
We push, open a pull request, get some code reviews and when everything is said and done, merge
We only use
rebase to keep our local branch up-to-date. Don't rebase branches where others are collaborating with you.
It's always helpful to keep a backup of your working branch if you don't feel confident about the rebase.
In the case of not having pushed your branch in the remote you can't do a
git reset --hard origin/working_branch if you mess up, so keep a backup:
git checkout -b backup/working_branch git checkout working_branch
Do the rebase process, and if you mess up reset like this:
git reset --hard backup/working_branch
Say we have opened a pull request but some people have requested changes. We implement them, do another rebase to incorporate the latest changes and do a
git push origin working_branch
Unfortunately our changes will be rejected! By doing a rebase, each commit is considered new, and have different hashes. The remote informs you that there are commits (the ones with the old hashes) that are not present in your local branch, so you have to do an update first. That's not the case. We have rewritten history and would rather override everything that is on the remote. Essentially we have to do a
There is a little caveat with that though. If someone has pushed a commit into the branch before us, and we do a force push - it will forever be gone.
It's best to do a
--force-with-lease, that will essentially prevent the remote from being updated if someone else has done some work.
git push --force-with-lease origin working_branch