07. Rebasing - ivomac/GitBasics GitHub Wiki
๐ Cherry-picking: Copying commits
- Cherry-picking is applying the changes in a specific commit on top of HEAD in the repository.
- ๐ข If the changes apply with no conflicts, a new commit is created.
- ๐ก If the changes do not apply, conflict markers are left in the workdir to resolve.
- ๐ You can apply several commits in a sequence. Cherry-picking stops at each conflict.
def cherry_pick(commits: list[Commit]) -> list[Commit]:
- Cherry-picking creates a new commit with the same changes but a different parent.
๐ Cherry-pick Scenario
A โโ B โโ C โ main โ HEAD
โฒ
D โโ E โโ F โ feature
- We try to cherry-pick commits D, E, and F (the feature branch):
A โโ B โโ C โโ G โ main โ HEAD
โฒ
D โโ E โโ F โ feature
- ๐ข D applied successfully.
diff(C, G)
is (mostly) the same as diff(B, D)
.
- ๐ก
diff(E, D)
does not apply properly on G. Conflicts need to be resolved. Example:
<<<<<<< HEAD # HEAD is G
(ours content)
=======
(theirs content)
>>>>>>> 674d107 # (part of) E's hash
- โ We could decide to abort:
- ๐งน G is removed and we go back to the repo state before cherry-picking.
- โ๏ธ If we resolve all conflicts and eventually complete cherry-picking, the state will be:
A โโ B โโ C โโ G โโ H โโ J โ main โ HEAD
โฒ
D โโ E โโ F โ feature
- ๐ G, H, J, correspond to D, E, F in that order.
- ๐ง J contains changes from main and feature and effectively merged both branches.
โ๏ธ Rebasing
def rebase(new_base: Commit | Branch):
- Rebasing moves a series of commits forward so that the new_base commit is in their history.
- Rebasing is effectively a cherry-pick wrapper and a common merge alternative.
- It is a destructive operation (when successful), creating/deleting commits and rewriting history.
โ๏ธ Rebase Scenario
- ๐ข We replay the previous scenario, starting with HEAD on feature.
A โโ B โโ C โ main
โฒ
D โโ E โโ F โ feature โ HEAD
- Currently, C/main is not in the history of feature.
- If feature is rebased successfully onto main, the new state will be:
A โโ B โโ C โ main
โฒ
G โโ H โโ J โ feature โ HEAD
- ๐ Again: G, H, J, correspond to D, E, F, the same commits as in the cherry-pick scenario.
- ๐ The original commits D, E, F, still exist in the repository but are no longer referenced by any branch.
๐ Rebase: Cherry-pick wrapper
- โ Rebasing is a sort of wrapper for cherry-picking, with extra branch handling.
- In the scenario above, rebasing feature onto main is the same as:
- โ Creating a new feature-tmp branch in main and switching to it:
main
โผ
A โโ B โโ C โ feature-tmp โ HEAD
โฒ
D โโ E โโ F โ feature
- ๐ญ Identify the commits of feature not in main's history: D, E, F.
- ๐ Cherry-picking the feature's commits on top of feature-tmp:
main
โผ
A โโ B โโ C โโ G โโ H โโ J โ feature-tmp โ HEAD
โฒ
D โโ E โโ F โ feature
- ๐งฝ Deleting feature and renaming feature-tmp to feature:
main
โผ
A โโ B โโ C โโ G โโ H โโ J โ feature โ HEAD
โ๏ธ Rebase Conflict Markers
- During rebasing, HEAD first changes to the new base branch.
- HEAD then moves forward as you cherry-pick commits one-by-one on top of the base branch.
- If there are conflicts during cherry-picking, conflict markers are added:
- โ ๏ธ The top version in the conflict markers will refer to the moving HEAD version.
- โ ๏ธ The bottom version will refer to the commits being cherry-picked.
<<<<<<< HEAD # will go through C/G/H
(ours content)
=======
(theirs content)
>>>>>>> 674d107 # will go through D/E/F
- โ๏ธ This can be unintuitive since HEAD will start and end in the branch being rebased (feature/F).
๐ Rebase vs Merge
- Merge: Creates a new merge commit that combines changes from both branches.
- Rebase: Rewrites history by creating new commits on top of the target branch.
๐ Comparison
Merge |
Rebase |
Preserves full history |
Creates a linear history |
Non-destructive |
Rewrites commit history |
Shows when branches were integrated |
Makes it look like work happened sequentially |
Creates merge commits |
No extra merge commits |
๐ง Interactive Rebasing
- Interactive rebasing allows you to modify commits as you rebase them:
- During interactive rebasing, you can:
- ๐ Pick: Keep the commit as is
- โ๏ธ Reword: Change the commit message
- ๐ฑ Edit: Pause to amend the commit in the workdir
- ๐ Squash/Fixup: Combine with previous commit, keeping/dropping its messages
- ๐ฅ Drop: Remove the commit entirely
- ๐ Reorder: Change the order of commits
- Interactive rebasing in-place (without changing base) let's you rewrite a branch's commit history.
๐ Squash/Fixup
- Squash/Fixup combines multiple commits into one:
A โโ B โโ C โ main
โฒ
D โโ E โโ F โ feature โ HEAD
- After squashing commits E and F during in-place rebasing:
A โโ B โโ C โ main
โฒ
D โโ G โ feature โ HEAD
- ๐ค G is a new commit with the combined changes of E and F.
- ๐ฌ G's message will be a combination of E and F's messages.
- ๐งน Fixup would discard F's message.
๐ฒ Interactive Rebase in SourceTree
- Right-click on the parent of the first commit that you want to modify.
- Select "Rebase children of [commit] interactively..."
- In the dialog that appears:
- Reorder commits by dragging
- Double-click a commit to edit its message
- Right-click for more options (squash, edit, etc.)
- Click "OK" to start the rebase