Proper Git Rebasing

October 21, 2015
on
git

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:

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.