Skip to content

07. Rebasing

ivomac edited this page Mar 16, 2025 · 1 revision

πŸ’ 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:
  1. βž• Creating a new feature-tmp branch in main and switching to it:
         main
          β–Ό
A ◄─ B ◄─ C β—„ feature-tmp β—„ HEAD
     β–²
     D ◄─ E ◄─ F β—„ feature
  1. πŸ”­ Identify the commits of feature not in main's history: D, E, F.
  2. πŸ’ 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
  1. 🧽 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

  1. Right-click on the parent of the first commit that you want to modify.
  2. Select "Rebase children of [commit] interactively..."
  3. 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.)
  4. Click "OK" to start the rebase

Clone this wiki locally