Git Rebase
What is a rebase?
Assume the following git history where you are working on the my-feature
branch.
A---B---C my-feature
/
D---E---F---G master
After git rebase master
this would become:
A--B--C my-feature
/
D---E---F---G master
git rebase origin/master
rebases your current branch against mastergit rebase -i origin/master
is the interactive version which allows you to do things such as squashing commits.
Workflow for Single Remote
This is assuming:
- Everyone on your team is working from one repository.
- You are currently on a feat/chore/fix branch and not on
master
.
git fetch origin
git rebase -i origin/master
At this point the editor will launch and will allow you to pick commits for squashing:
We are going to squash all commits into one. Leave the first commit untouched and convert all the other pick
into squash
or s
. Save and close the editor window.
If no conflicts were encountered the rebase will complete and you move to the final step. Your editor will open up again. This time it shows you the messages from the various commits that are going to be squashed. Here you get a chance to compose the message for the new squashed commit.
- Use a descriptive commit message.
- If you have a Pivotal Tracker, JIRA, or Github issue number for the feature, reference it in the commit.
Save and close the editor window once you have composed a new git message.
At this point the rebase is complete 🎉 Last step, push your changes to remote.
$ git push origin my-feature -f
Yes, that’s a force push there. Because you have successfully re-written git history. Where you previously had multiple commits you now only have one commit.
Resolving Conflicts
During the rebase process you might encounter conflicts.
Relax. Take a deep to breath. We can deal with this 💪
Start by running git status
this will show you a list of files that have conflicts. Go through them one by one and resolve conflicts.
The conflicts show up something like this (there can be more than one in a file):
This is the first line
This is the second line
This is the third line I added
<<<<<<< HEAD
Someone else added this line
Someone else added this line too
=======
This is the fourth line I added
This is the fifth line I added
>>>>>>> f78d8ae... <commit message>
Here everything between <<<<<<< HEAD
and =======
is stuff that is in the master
branch. And everything between =======
and >>>>>>> f78d8ae... <commit message>
is your on the my-feature
branch. Git could not automatically merge these so it wants you to decide how these lines should be merged.
You can pick master
version or your version depending on what you were implementing. You are the best judge here. Sometimes the correct answer will be a combination of the two.
Pick the appropriate combination and delete the conflict markers (<<<<<<< HEAD
, =======
, etc).
This is the first line
This is the second line
This is the third line I added
Someone else added this line
This is the fourth line I added
This is the fifth line I added
Do the same for all the other files with conflicts. Then:
- Stage the changes by executing
git add <file-name>
- Continue the rebase
git rebase --contine
If you messed something up you can always git rebase --abort
. Remember to rebase early and often. This will prevent messy merge conflicts.
Workflow for Multiple Remotes
This is assuming:
- There is a main repository that you can only update through pull requests. We will call this remote
upstream
. - You create a fork of this repository and clone that locally. We will call your forked remote
origin
.
The setup process will be something like this:
$ git clone git@github.com:[your user name]/[your repo].git
$ cd [your repo]
$ git remote add upstream git@github.com:rangle/[your repo].git
The only difference between this setup and previous is that you will be fetching upstream
and rebasing against upstream/master
. Then push to your forked origin
. The actual rebase & conflict resolution process is the same.
Let us break this down step-by-step:
# (1) Start by updating your fork from the main repo.
# At this point you are on the master branch.
$ git fetch upstream
$ git rebase upstream/master
# (2) Create a new branch for your feature
$ git checkout -b my-feature
# (3) Implement the feature and commit your changes as usual.
# This might be single or multiple commits.
# (4) Push your changes to origin
$ git push origin my-feature
# (5) Before we rebase we must get the latest upstream
$ git fetch upstream
# (6) Next, do the actual rebase. This point onwards it is the
# same process as single remote workflow above.
$ git rebase upstream/master
# (7) Push your changes to origin not upstream!
$ git push origin my-feature -f
Github
If you are using github then a lot of this stuff can be done through their GUI. Read more about it here.
Undo
Scenario: I messed up conflict resolution during a rebase, pushed changes to remote and lost hours of work 😭
Use git reflog
to undo your rebase. Running the command will show you the reflog for your local repository. For example:
adc164e HEAD@{0}: rebase -i (finish): returning to refs/heads/my-feature
adc164e HEAD@{1}: rebase -i (pick): Bugfix: SVG icons on android
8ab621c HEAD@{2}: rebase -i (start): checkout master
067be75 HEAD@{3}: checkout: moving from master to my-feature
8ab621c HEAD@{4}: pull: Fast-forward
In this scenario to undo my rebase I need to run git reset --hard HEAD@{3}
because that was the last state before I started the rebase.