Rebasing is an other way of merging in changes from an other branch into your
own. It’s similar to git merge
but the big difference is it keeps a clean
history between commits by avoiding the useless merge commits.
Messy history
Clean history, only merge pull merges should be present
git merge
is still a useful command for putting commits into master
or dev
. git
rebase
should only be used for grabbing commits from dev
and putting them into a feature branch,
these kinds of Merge branch 'something'
commits are just noise.
So Why Rebase?
- Keeping a clean history is better for everyones sanity
- Removes the useless merge branch commits
- Reverting commits is easier
- It makes squashing–combining commits–much much easier
Have you ever tried
squashing a merge commit? It’ll bring you through hell and back. Although it can still be
done easily through git reset
lets not go there right now.
You Might Lose Commits
Yes, git rebase
is a more advanced git command and if used incorrectly can
lose commits but if you have a deeper knowledge of git rebase
and
practice a bit, it’ll quickly become a valuable tool in your arsenal.
I wrote lose commits because although commits can disappear,
if you know how to use git reflog
,
another advanced command, you can get back any lost commit. Remember git never
deletes commits, every commit is always kept.
Simple Rebasing Example
You just got into work and like a good dev, the first thing you do is update
your feature branch with dev
.
So you do a git pull
and see some new commits. The difference between the two
branches ends up looking like this:
+-----+ +-----+
Dev +-------+ B +----+ C |
| +-----+ +-----+
+--+--+
| A |
+--+--+
| +-----+ Feature +-------+ D |
+-----+
You decide to do a git merge dev
and although your feature branch is up to
speed, it ended up with this nasty new merge commit.
+-----+ +-----+
Dev +-------+ B +----+ C |
| +-----+ +--+--+
+--+--+ |
| A | |
+--+--+ v
| +-----+ +-----+ Feature +-------+ D +----+Merge|
+-----+ +-----+
There must be a better way and that is rebasing. git rebase dev
.
+-----+ +-----+
Dev +-------+ B +----+ C +--+
| +-----+ +-----+ |
+--+--+ |
| A | |
+-----+ |
| +-----+ Feature +-+ 'D |
+-----+
git rebase
works a bit differently, instead of creating a merge commit it will checkout dev
in a
temporary branch and cherry pick all
the commits in the feature branch (only D
in this case) into the temp branch. Once every thing is
done, the original feature branch is overwritten and you end up with a linear
history.
So now it appears as if you wrote your new feature on top of B
and C
.
Remember because D
was cherry picked, it is now a new commit, with a new hash,
and it’s authored date would be now.
Git Status Is Messed Up
Now running git status --short
, will give you this weird output.
## feature...origin/feature [ahead 2, behind 1]
This is the most confusing part of rebasing. When
you do a git status
it compares your local branch changes to what is on the remote branch,
obivously remote doesn’t have the rebase you just did because you never pushed, so a difference is
expected.
+-----+ Remote | A |
+--+--+
| +-----+
Feature +-------+ D |
+-----+
+———————————————————-+
+-----+ +-----+
Dev +-------+ B +----+ C +--+
| +-----+ +-----+ | Local +--+--+ |
| A | |
+-----+ |
| +-----+
Feature +-+ 'D |
+-----+
This is the current state of your local branch and the same branch on remote.
When a git status
is done, it actually counts the differences in commits, your
local branch has two commits remote does not–ahead by 2–and you do not have
one commit from remote–behind by 1. Remember 'D
and D
these are actually different commits now but contain the same changes.
Force Pushing
Hopefully we understand what’s going on with git status
. So we can now push our
changes, but because we significantly change it by removing D
we have to force
push it.
This is the part where you could lose data, by force pushing you are telling remote to accept whatever you are giving it and it could be anything.
Before you run the command ensure you have this setting in your
~/.gitconfig
.
[push]
default = simple
This will stop git from force pushing all your branches to remote and potentially destroying other peoples work.
Safely push it with, git push --force
.
+ 1d88fab...6612ec4 feature -> feature (forced update)
At this point you’re done and remote has your changes but there are a couple caveats to look out for.
Pulling a Rebased Branch
Pulling from remote and seeing either merge conflicts or a merge prompt window. That means you just pulled a rebased branch and should stop everything, but do close the merge prompt window.
By default git will try to make things right by merging remote changes into your branch but don’t do it. You’ll end up with this hydra merge and will make squashing a nightmare. It’s much better to just reset your working directory.
git reset --hard @{u}
This says, throw away everything I have on my local and match the remote branch exactly. I do this so often I made it a git alias.
If you happened to have a commit you were trying to push you’ll now have to do a cherry pick.
Handling Merge Conflicts
An other ceveat is rebasing and encountering merge conflicts. This is handled
differently from git merge
but it’s still simple enough.
Resolve the conficts as normal and when finished just add the files to staging.
git add .
git rebase --continue
Or if you were not brave enough and want to stop rebasing.
git rebase --abort
Rebasing brings you to a temporary branch and you can make whatever changes you want. This makes it easy to abort at any time.
For more information on rebasing checkout the git docs.