Chapters

Hide chapters

Advanced Git

Second Edition · Git 2.32 · Console

Section I: Advanced Git

Section 1: 7 chapters
Show chapters Hide chapters

4. Demystifying Rebasing
Written by Jawwad Ahmad & Chris Belanger

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Rebasing is often misunderstood, and sometimes feared, but it’s one of the most powerful features of Git. Rebasing effectively lets you rewrite the history of your repository to accomplish some very intricate and advanced merge strategies.

Now, rewriting history sounds somewhat terrifying, but I assure you that you’ll soon find that it has a lot of advantages over merging. You just have to be sure to rebase responsibly.

Why would you rebase?

Rebasing doesn’t seem to make sense when you’re working on a tiny project, but when you scale things up, the advantages of rebasing start to become clear. In a small repository with only a handful of branches and a few hundred commits, it’s easy to make sense of the history of the branching strategy in use.

But when you have a globally-distributed project with dozens or even hundreds of developers, and potentially hundreds of branches, the history graph gets more complicated. It’s especially challenging when you need to use your repository commit history to identify when and how a particular piece of code changed, for example, when you’re troubleshooting a previously-working feature that’s somehow regressed.

Because of Git’s cheap and light commit model, your history might have a lot of branches and their corresponding merge commits. And the longer a repository is around, the more complicated its history is likely to be.

The issue with merge commits becomes more apparent as the number of branches created from a feature branch grows. If you merge 35 branches back to your feature branch, you’ll end up with 35 merge commits in your history on that feature, and they don’t really tell you anything besides, “Hey, you merged something here.”

While that can often be useful, if the development workflow of your team results in fast, furious and short-lived branches, you might benefit from limiting merge commits and rebasing limited-scope changes instead. Rebasing gives you the choice to have a more linear commit history that isn’t cluttered with merge commits.

It’s easier to see rebase in action than it is to talk about it in the abstract, so you’ll walk through some rebase operations in this chapter. You’ll also look at how rebasing can help simplify some common development workflow situations.

What is rebasing?

Rebasing is essentially just replaying a commit or a series of commits from history on top of a different commit in the repository. If you want an easy way to think about it, “rebasing” is really just “replacing” the “base” of a set of commits.

A simple branch (b) off of feature (f).
U wergsi gtuswc (s) ixm az naorefu (p).

A simple branch (b) off of feature (f), merged back to feature with merge commit mc5.
E jusgni krewkg (k) imm aq zuegiri (y), kurtac yisp ju woohupu yotm nuncu bobbor zm5.

A more complex set of branches and merge commits, with merge commits mc5, mc6 and mc7.
O tiya hubrtuk jux ep lzuykqow anp lihse hugronl, rirp bizne fobxoqv tv2, bk0 abz fp0.

A simple branch (b) off of feature (f).
A gocydo gsafqj (h) itm eg vuuzivu (m).

Rewinding HEAD to the common ancestor of the feature and bugfix branches
Sehuktecl PUIM ge zre mozgez ilgurjog az kxa ciukipu enr bovwoc byeqfjoj

Applying b1, b2 and b3 patches on top of the common ancestor and moving labels along.
Ezmxzadv w0, v0 uyp s1 hoxjvub oj qur un kya jecfir uhyipcuj oqf januxx lebofh isadz.

Applying f1, and f2 patches on top of the new base branch and moving labels along
Ibstloln l7, atg b9 jukfwab um kew ad qwo goq yaho fgatlt ely suqawr tugifc ujelj

Creating your first rebase operation

To start, you can continue using the your project from the previous chapter or use the project in the starter folder for this chapter.

git checkout wValidator
git checkout -b cValidator
# Maintainers

This project is maintained by teamWYXZ:
- Will
- Yasmin
- Xanthe
- Zack
- Chris
git add .
git commit -m "Added Chris as a new maintainer to README.md"
git checkout wValidator
# Maintainers

This project is maintained by teamWYXZC:
git commit -am "Updated team acronym to teamWYXZC"
git log --oneline --graph --all
* 9f5e491 (HEAD -> wValidator) Updated team acronym to teamWYXZC
| * 7f754d4 (cValidator) Added Chris as a new maintainer to README.md
|/
* 3574ab3 (origin/wValidator) Whoops—didn't need to call that one twice
git rebase cValidator
Successfully rebased and updated refs/heads/wValidator.
git log --oneline --graph --all
* 0e84d2b (HEAD -> wValidator) Updated team acronym to teamWYXZC
* 7f754d4 (cValidator) Added Chris as a new maintainer to README.md
* 3574ab3 (origin/wValidator) Whoops—didn't need to call that one twice
git branch -d cValidator

A more complex rebase

Let’s go back to our Magic Square development team. Several people have been working on the Magic Squares app; Will in particular has been working on the wValidator branch.

git log --oneline --graph wValidator origin/xValidator
* 0e84d2b (HEAD -> wValidator) Updated team acronym to teamWYXZC
* 7f754d4 Added Chris as a new maintainer to README.md
* 3574ab3 (origin/wValidator) Whoops—didn't need to call that one twice
* 43d6f24 check05: Finally, we can return true
| * 8ef01ac (origin/xValidator) Refactoring the main check function
| * 5fea71e Removing TODO
|/
* bf3753e check04: Checking diagonal sums
.
.
.
git rebase origin/xValidator
Auto-merging js/magic_square/validator.js
CONFLICT (content): Merge conflict in js/magic_square/validator.js
error: could not apply 43d6f24... check05: Finally, we can return true
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 43d6f24... check05: Finally, we can return true

Resolving errors

In the second line of the output from git rebase you’ll see that there’s a merge conflict in js/magic_square/validator.js.

<<<<<< HEAD
    Section 1: Xanthe's changes from origin/xValidator
|||||| parent of 43d6f24
    Section 2: Code before changes in the common ancestor
======
    Section 3: Will's changes on wValidator
>>>>>> 43d6f24

Section 1: Xanthe’s changes

The lines in the first section between <<<<<<< HEAD and ||||||| parent of 43d6f24 are Xanthe’s refactored changes from the origin/xValidator branch.

Section 2: Code before changes

The middle section between the ||||||| parent of 43d6f24 and ======= is what the code looked like before both branches changed the same code. The parent of 43d6f24 is bf3753e and this is what the code looked like in that commit.

Section 3: Will’s changes

Will’s changes are in the third section between the ======= and >>>>>>> conflict markers. If you take a closer look at the final conflict marker you’ll see that it also tells you in which commit the conflict occurred:

>>>>>>> 43d6f24 (check05: Finally, we can return true)

Resolving the conflict

In this case you want to keep the changes on Xanthe’s branch. Keep the code in the first section, i.e. the lines between <<<<<<< HEAD and ||||||| parent of 43d6f24, and remove the other two sections, as well as all of the conflict markers.

git rebase --continue
js/magic_square/validator.js: needs merge
You must edit all merge conflicts and then
mark them as resolved using git add
git add .
git rebase --continue
Auto-merging js/magic_square/validator.js
CONFLICT (content): Merge conflict in js/magic_square/validator.js
error: could not apply 3574ab3... Whoops—didn't need to call that one twice
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 3574ab3... Whoops—didn't need to call that one twice
git rebase --skip
Successfully rebased and updated refs/heads/wValidator.
git log --oneline --graph wValidator origin/xValidator
* ed0c808 (HEAD -> wValidator) Updated team acronym to teamWYXZC
* d1ff29f Added Chris as a new maintainer to README.md
* 8ef01ac (origin/xValidator) Refactoring the main check function
* 5fea71e Removing TODO
* bf3753e check04: Checking diagonal sums
*   44194fa (HEAD -> wValidator) Merge remote-tracking branch 'origin/xValidator' into wValidator
|\
| * 8ef01ac (origin/xValidator) Refactoring the main check function
| * 5fea71e Removing TODO
* | 0e84d2b Updated team acronym to teamWYXZC
* | 7f754d4 Added Chris as a new maintainer to README.md
* | 3574ab3 (origin/wValidator) Whoops—didn't need to call that one twice
* | 43d6f24 check05: Finally, we can return true
|/
* bf3753e check04: Checking diagonal sums
*   96f42e3 (HEAD -> wValidator) Merge remote-tracking branch 'origin/xValidator' into wValidator
|\
| * 8ef01ac (xValidator) Refactoring the main check function
| * 5fea71e Removing TODO
* |   b567a15 Merge branch 'cValidator' into wValidator
|\ \
| * | 9443e8d (cValidator) Added Chris as a new maintainer to README.md
* | | 76bacc5 Updated team acronym to teamWYXZC
|/ /
* | 3574ab3 Whoops—didn't need to call that one twice
* | 43d6f24 check05: Finally, we can return true
|/
* bf3753e check04: Checking diagonal sums

Orphaned commits

Even though the skipped commits are no longer shown in the log, you can still find these commits if you still have the original commit hash for them.

git log --oneline -3 3574ab3
3574ab3 (origin/wValidator) Whoops—didn't need to call that one twice
43d6f24 check05: Finally, we can return true
bf3753e check04: Checking diagonal sums
git cat-file -p 3574ab3
tree 1b4c07023270ed26167d322c6e7d9b63125320ef
parent 43d6f24d140fa63721bd67fb3ad3aafa8232ca97
author Will <will@example.com> 1499074126 +0700
committer Sam Davies <sam@razeware.com> 1499074126 +0700

Whoops—didn't need to call that one twice

Merging vs rebasing

Although the politics and goals of your development team will dictate your approach to merging and rebasing, here are some pragmatic tips on when rebasing might be more appropriate over merging, and vice versa:

Challenge: Rebase on top of another branch

You’ve discovered that Zach has also been doing a bit of refactoring on the zValidator branch with the range checking function:

git log --oneline --graph wValidator origin/zValidator
...
* 64b1b51 check02: Checking the array contains the correct values
| * 136dc26 (origin/zValidator) Refactoring the range checking function
|/
* 665575c util02: Adding function to check the range of values

Key points

  • Rebasing “replays” commits from one branch on top of another.
  • Rebasing is a great technique over merging when you want to keep the repository history linear and as free from merge commits as possible.
  • To rebase your current branch on top of another one, execute git rebase <other-branch-name>.
  • You can resolve rebase conflicts just as you do merge conflicts.
  • To resume a rebase operation after resolving conflicts and staging your changes, execute git rebase --continue.
  • To skip rebasing a commit on top of the current branch, execute git rebase --skip.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now