Skip to content

Instantly share code, notes, and snippets.

@hugodahl
Last active August 18, 2021 13:18
Show Gist options
  • Save hugodahl/b1adc686540451cd95b345d111cb70c3 to your computer and use it in GitHub Desktop.
Save hugodahl/b1adc686540451cd95b345d111cb70c3 to your computer and use it in GitHub Desktop.
Why folks fear the `git rebase` or `git merge --rebase`

This is a response to a question asked on the Adafruit discord server, where someone asked

"Hugo what is exactly rebasing, and why people always has nightmare stories about that?"

Rebasing has become a bit overloaded as a term, but in essence, it means taking a given branch and putting its changes "on top of" other changes. That's quite confusing, so I'll explains...

Imagine you're working on a repository with other people. Commits up to now were merged and linear in the "main" branch... c0 - c1 - c2 - c3 - c4. You start working on a feature, so you create a feature branch off the "main" branch, at commit "c4". You do work on that branch, and create your local commits. So the tree looks like this:

c0 - c1 - c2 - c3 - c4 - c5 - c6
                     \  f0 - f1 - f2

Note that "c5" and "c6" appeared while you did your works. Turns out, I was doing work too, and either committed to the "main" branch (tsk tsk tsk), or did my work in other branches "hugo-feature-1" and "hugo-feature-2", and merged them into "main". So my changes from "hugo-feature-1" became "c5" and "hugo-feature-2" became "c6" after merging PRs.

Typically, what most people would do to get their branch lined up with the main branch, is pull in the changes from main, which creates the "merge commit" on their branch. In our example, if you did git pull origin main while your on your branch at "f2", you'd end up with "f3" which is a merge of changes in "c5" and "c6".

However, some people, or some organizations, it's a preference (in my opinion), don't want to merge a merge into the main branch. So they'd want ONLY the changes from your branch that YOU made to be in the PR. But you couldn't do that easily because you changed files file1.py, file2.py and file3.py which overlaps with something the files which I changed in "c5" and "c6" - file1.py and file3.py. This is typically where the merge commits come from - pulling in those changes, making sure your changes don't stomp on mine, and vice-versa. But we can't have that, because reasons.

So what you would typically do is when you run git pull origin main, is run git pull --rebase origin main. That means that locally, git would roll back to where your branch and "main" split apart, "c4", pull in the commits "c5" And "c6", then replay your commits "f0", "f1" and "f2" on top of that. So the tree should look like this:

c0 - c1 - c2 - c3 - c4 - c5 - c6
                               \  f0' - f1' - f2'

where "fx'" is the commit previously known as "fx" since the hash will change, and the contents may change as well.

But as git replays your commits, one at a time - sort of like a string of cherry-pick operations - it will then ask you to resolve any conflicts from your commit that have appeared. So you will see both the changes from my commits, as well as your commit as "f0" is replayed. You then need to pick and choose and edit the files so that both your changes and mine are valid after that merge is committed. And this is repeated for all other commits in your branch - "f1" and "f2". If you're lucky, the changes can be replayed in an obvious manner by git, and that's that. However, a conflict might appear in "f1" instead, or in all commits. So each commit replayed is another "opportunity" to cause a break. Or lose/overwrite changes that were made in "c5" and "c6".

So imagine, after all this, you're happy, code compiles, "works on my machine"(tm). You create a PR, its approved, and you merge. 🎉. But, hold on. I come back the next day, adding a new feature that builds upon my commit in "~c6", and some of my code I KNOW was merged in is gone. That's "easy" to fix, since I can go dig out what was in "c6" that's missing now in "c7", which is where your branch was merged in. Yeah, it's a pain, it shouldn't happen, but it did, and we can recover. HOWEVER, imagine instead that you make a mistake while merging "f1" into "f1'", but don't noticed until you create your pull request. For all intents and purposes, your original "f1" no longer exists - it became "f1'" and is now gone. You can't recover those changes (not easily, and not always guaranteed). THAT is (in part) what causes cold sweats for devs using rebase.

@jposada202020
Copy link

@hugodahl Thanks. Very informative. I understand now. Normally I was doing merge from main as you mentioned most people do. Thanks again!

@arjunpunnam
Copy link

@hugodahl this is the least complex explanation of all the ones I heard about rebase. Thank you.

@hugodahl
Copy link
Author

@hugodahl this is the least complex explanation of all the ones I heard about rebase. Thank you.

Thank you for the feedback @arjunpunnam. I'm glad it helped clarify things!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment