Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why reordering commits using git rebase -i doesn't screw up the history?

Tags:

git

As I understand it, commits are snapshots of files so if I perform change A and then change B, change B's commit file already includes the changes in change A, wouldn't reordering make change A redundant?

like image 957
Aspiring Dev Avatar asked Apr 29 '16 22:04

Aspiring Dev


People also ask

When should you avoid rebasing a branch?

If you use pull requests as part of your code review process, you need to avoid using git rebase after creating the pull request. As soon as you make the pull request, other developers will be looking at your commits, which means that it's a public branch.

What does rebasing a commit do?

From a content perspective, rebasing is changing the base of your branch from one commit to another making it appear as if you'd created your branch from a different commit. Internally, Git accomplishes this by creating new commits and applying them to the specified base.


1 Answers

That is, in fact, a very good question, because commits are snapshots.

The reason rebase works is because rebase is actually repeated git cherry-pick (with a bit of wrapping at the front to figure out what to pick, and more at the end to move the branch labels), and git cherry-pick works by turning a commit into a change-set.

Suppose for instance that you have this sequence of commits:

          A--B--C   <-- topic
         /
...--o--*--o--o     <-- mainline

To rebase topic onto mainline we need to (1) find the commits that are on topic but not on mainline (which are C, B, and A along the top row, ending at the commit marked *), and then (2) copy them to new commits we will add on below the tip of mainline.

Rebase first finds the three post-* commits and puts them into a (reverse ordered: A, B, C) list (it also omits merge commits by default but there are no merges here). It then does a cherry pick for each commit.

To cherry-pick A, Git diffs A against *. This turns the two snapshots into changesets. Git then applies the changes to the tip-most commit of mainline and makes a new commit, let's call it A', on an anonymous branch:

          A--B--C   <-- topic
         /
...--o--*--o--o     <-- mainline
               \
                A'  <-- HEAD

To cherry-pick B, Git diffs B against A. Applying these changes to A' produces another commit B'. Repeat for C to obtain C':

          A--B--C         <-- topic
         /
...--o--*--o--o           <-- mainline
               \
                A'-B'-C'  <-- HEAD

Last, Git peels the topic label away from C and points it to C' instead. The old chain is abandoned (though you can still find it through reflogs, and rebase copies the ID of C to the special name ORIG_HEAD as well):

          A--B--C         [abandoned]
         /
...--o--*--o--o           <-- mainline
               \
                A'-B'-C'  <-- topic

and now the rebase is complete.

Note that each copy is done using git's merge mechanisms if needed (if the diffs do not apply cleanly right away). Each one can result in a merge conflict, requiring rebase to stop and get help from you. (Or, worse, you can get a mis-merge, although these are rare in practice.)

And of course, if you re-order the commits (by moving pick lines around in an interactive rebase), we just change the order we pick and apply each commit. The cherry-pick operations still work the same way: compare the picked commit against its parent (C vs B, A vs *, B vs A).

like image 165
torek Avatar answered Sep 29 '22 11:09

torek