• Skip to primary navigation
  • Skip to main content
Sal Ferrarello
  • About Sal Ferrarello
  • Speaking
  • Connect
    Mastodon GitHub Twitter (deprecated)
You are here: Home / Computing / Git Rebase and the Multiverse

Git Rebase and the Multiverse

Last updated on September 17, 2023 by Sal Ferrarello

In Git Merge and the Multiverse I looked at merging branches and how sometimes this results in a fast-forward merge and sometimes in the creation of a merge commit. Now let’s look at Git rebase (a.k.a. the power to rewrite history).

If you haven’t read Git Merge and the Multiverse yet, I strongly suggest you start there first and then return to this article.

Progress in Both Universes (no conflict)

Revisiting a scenario from Git Merge and the Multiverse, our main universe branched off into a second universe (feature-a). In feature-a, I placed flowers on the coffee table, while in main I placed a tablecloth on the kitchen table.

(main)            (feature-a)
Add tablecloth    Add flowers to coffee table
Add kitchen.md    Add kitchen.md

When we originally addressed this situation we created a merge commit, a commit that stitches the two separate universes together. Looking at the parents of this merge commit, we see two separate histories (one where flowers are added to the coffee table and one were a tablecloth is added to the kitchen table).

While stitching together this two universes with a merge commit is a perfectly legitimate technique, it doesn’t create the “cleanest” Git history.

Rewriting History

We have an alternative to creating this merge commit, we can instead modify the history of our feature-a branch. As numerous time travel movies have pointed out, messing with time and modifying history can cause significant problems.

Only rewrite history on branches you have full control over, where full control means either the branch only exists on your local machine (not on a Git hosting platform like GitHub) or the branch does exist on a Git hosting platform but you’ve made it clear to others that the history on this branch may change (I’m a fan of starting a branch name with my initials, to make it clear this is my branch and I’ll rewrite history if I want to (e.g. sf/feature-a)).

How does Rebase Work

When we rebase feature-a with main, the following happens:

  • the feature-a timeline is rewound back to the last common commit with main (i.e. rewinds to before Add flowers to coffee table in this case)
  • does a fast-forward merge of the main branch timeline into feature-a (now main and feature-a are identical)
  • replays the time we previously rewound on feature-a (i.e. reapply Add flowers to coffee table)

We end up with the following

(main)            (feature-a)
                  Add flowers to coffee table
Add tablecloth    Add tablecloth
Add kitchen.md    Add kitchen.md

Why Rebase

After rebasing feature-a with main, the feature-a branch is up to date with main meaning that if we merged feature-a into main it would be a fast-forward merge (we like fast-forward merges).

Technical Details of Rebase without Conflict

Two rebase feature-a with main, we checkout feature-a

git checkout feature-a

and then run the rebase command, telling Git what branch we want to use

git rebase main

The following is the output of git log --oneline --graph after the rebase.

* 12b853b (feature-a) Add flowers to coffee table
* 032193f (main) Add tablecloth
* ea72b65 Add kitchen.md

Notice:

  1. When we reapply the time we rewound (“Add flowers to coffee table”), the commit gets a new commit hash (12b853b).
  2. We are now in a position that we could do a fast-forward merge of feature-a into main, since main is an ancestor of feature-a

Git repo (before the rebase) available at salcode/git-merge-multiverse-scenario-2

Progress in Both Universes that Overlap

Revisiting our scenario from Git Merge and the Multiverse, our main branch and feature-b branch have both moved forward in time but in this case the changes conflict. Flowers were placed on the kitchen table in feature-b and in main a tablecloth was placed on the kitchen table.

(main)            (feature-b)
Add tablecloth    Add flowers to kitchen table
Add kitchen.md    Add kitchen.md

In this case, we rewind time on feature-b (removing the flowers from the kitchen table), merge main into this rewound feature-b, and re-apply Add flowers to the kitchen table.

Originally, when we did Add flowers to the kitchen table there was no tablecloth on the table. When re-applying Add flowers to the kitchen table in the rebase, the table does have a table cloth – Git can’t handle this. Git doesn’t know how to handle this.

Auto-merging kitchen.md
CONFLICT (content): Merge conflict in kitchen.md
error: could not apply 9d59323... Add flowers to kitchen table

When we open kitchen.md we see the conflict

<<<<<<< HEAD
## Kitchen table with table cloth
=======
## Kitchen table withh flowers on top
>>>>>>> 9d59323 (Add flowers to kitchen table)

Using our knowledge and understanding of the situation we resolve this merge conflict (and fix the typo)

## Kitchen table with tablecloth on top with flowers on top
Technical Details of Rebase with Conflict

These are the commands you can run to rebase these branches

git checkout feature-b
git rebase main

at this point, you have a merge conflict as described above. Open kitchen.md and resolve the merge conflict, then run

git add kitchen.md
git rebase --continue

Now feature-b has been brought up to date with main, the two branches now match except feature-b has additional work on top (so feature-b can now be fast-forward merged into main).

Git repo (before the merge) available at salcode/git-merge-multiverse-scenario-3

Git Push Now Fails

When we break the rules of time and rewrite history, by default Git tries to prevent us from messing up the timeline for others. For this reason git push fails with a message like

error: failed to push some refs
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: ‘git pull …’) before pushing again.
hint: See the ‘Note about fast-forwards’ in ‘git push –help’ for details.

However, we can tell Git that we know what we’re doing and to make this update even though it may seem like a bad idea. The --force-with-lease flag lets this work.

git push --force-with-lease

(Some might suggest using --force instead of --force-with-lease, however in my experience --force-with-lease is always better. See Never use git push force).

To Rebase or Not to Rebase

The opinions on this topic vary from “never rebase” to “always rebase. My opinion falls in between these two.

When I’m working on my own branch and I haven’t pushed it to the remote Git repo yet, I definitely rebase my branch.

When someone else creates a branch and I load it in my local environment, I definitely do NOT rebase their branch.

If I’ve named the branch starting with my initials (e.g. sf/feature-a) and I haven’t asked anyone to review the branch yet, I’ll rebase.

If someone has pulled down my branch to their local computer to review a pull request, I don’t rebase (even if the branch name starts with my initials).

Related Articles

  • Git Rebase with Blocks (article & video)
  • Resolving Git Rebase Merge Conflicts
  • See gitgifs.com for block visualizations
Sal Ferrarello
Sal Ferrarello (@salcode)
Sal is a PHP developer with a focus on the WordPress platform. He is a conference speaker with a background including Piano Player, Radio DJ, Magician/Juggler, Beach Photographer, and High School Math Teacher. Sal can be found professionally at WebDevStudios, where he works as a senior backend engineer.

Filed Under: Computing, Dev Tips, Programming Tagged With: Git, rebase

Reader Interactions

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Copyright © 2023 · Bootstrap4 Genesis on Genesis Framework · WordPress · Log in