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?
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.
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.
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
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With