Git & Collaboration Workflows

Rebase, Cherry-Pick & History Surgery

18 min Lesson 3 of 28

Rebase, Cherry-Pick & History Surgery

Every senior engineer eventually faces the same decision: should I merge this branch or rebase it? The choice shapes not just the graph you see in git log — it shapes how your team debugs incidents, bisects regressions, and reviews history six months from now. This lesson moves beyond the basics and teaches you to wield Git's history-manipulation tools the way staff engineers and DevOps leads do at scale.

Merge vs Rebase: The Mental Model

A merge records the fact that two lines of development converged at a specific moment. It preserves causality: you can always reconstruct exactly what was on main when your feature landed. A merge commit has two parents and is the permanent record of that join.

A rebase takes your commits and replays them on top of a different base. The commit content is identical, but the SHA changes because the parent pointer changes. The result looks as though you started your work after the latest upstream commit — the branch becomes linear.

Merge vs Rebase comparison git merge A B C (main) F1 F2 M git rebase A B C (main) F1\' new SHA Linear history — no merge commit Preserves full context
Merge preserves the full join context; rebase replays commits for a linear history.
The golden rule of rebase: never rebase commits that have been pushed to a shared remote branch. Rebase rewrites SHAs — anyone who has checked out those commits will have a diverged history that is painful to reconcile.

Everyday Rebase: Keeping a Feature Branch Current

The most common use of rebase is simple: you want your feature branch to sit on top of the latest main before you open a pull request.

# Fetch latest upstream changes git fetch origin # Replay your feature commits on top of origin/main git rebase origin/main # If there is a conflict, Git pauses and tells you which file to fix. # After resolving: git add <conflicted-file> git rebase --continue # If you need to abort and return to the original state: git rebase --abort

Interactive Rebase: The Surgeon's Scalpel

Interactive rebase (git rebase -i) lets you rewrite history before it reaches the shared remote. At big-tech companies, squashing WIP commits into atomic, reviewable commits before pushing is standard practice. The commands available per commit are:

  • pick — keep the commit as-is
  • reword — keep the commit but edit its message
  • edit — pause after applying so you can amend content
  • squash — merge into the previous commit, combining messages
  • fixup — like squash but discard this commit's message
  • drop — remove the commit entirely
# Rewrite the last 4 commits interactively git rebase -i HEAD~4 # The editor opens with a list like: # pick a1b2c3d Add feature skeleton # pick d4e5f6a WIP: half-done # pick 7g8h9i0 Fix typo # pick 1j2k3l4 Add tests and docs # # Change it to: # pick a1b2c3d Add feature skeleton # squash d4e5f6a WIP: half-done # fixup 7g8h9i0 Fix typo # reword 1j2k3l4 Add tests and docs # # Git will prompt you to edit the combined commit message for the squash, # and the message for the reworded commit. Result: 2 clean commits.
Pro practice: configure your editor for interactive rebase with git config --global core.editor "code --wait" (VS Code) or vim. Many teams also use git rebase -i --autosquash together with git commit --fixup=<SHA> to automatically order fixup commits during the rebase.

Cherry-Pick: Transplanting Individual Commits

Sometimes you need a specific fix from one branch on another — without merging the entire branch. git cherry-pick applies the diff of one or more commits onto your current HEAD.

# Apply a single commit from another branch git cherry-pick 4f5a6b7 # Apply a range of commits (exclusive of start, inclusive of end) git cherry-pick a1b2c3d..f6g7h8i # Cherry-pick without committing (stage only, review before committing) git cherry-pick --no-commit 4f5a6b7 # Common scenario: backporting a security fix to a release branch git checkout release/2.4 git cherry-pick 9d1e2f3 # the security fix commit from main git push origin release/2.4
Cherry-pick creates a duplicate commit with a new SHA. If both branches eventually merge, Git will try to apply the change twice — usually a no-op if the content is identical, but it can cause confusing conflicts. Document cherry-picks in your commit message (e.g., Cherry-picked from main: 9d1e2f3) and track them in your release notes.

Reflog: The Emergency Undo

The reflog is Git's hidden safety net. Every time HEAD moves — commit, rebase, reset, checkout — Git records the old position in .git/logs/HEAD. This log persists for 90 days by default and is local only (not pushed). It means almost nothing is truly lost on your machine.

# Show the full reflog for HEAD git reflog # Output looks like: # 4a5b6c7 HEAD@{0}: rebase finished: returning to refs/heads/feature/auth # 1d2e3f4 HEAD@{1}: rebase: Apply rate limiting middleware # 9g8h7i6 HEAD@{2}: rebase: Apply JWT validation # a3b4c5d HEAD@{3}: checkout: moving from main to feature/auth # ... # Scenario: you ran an interactive rebase and something went wrong. # Find the SHA of the state BEFORE the rebase: git reflog | grep "checkout:" # Restore to that exact state git checkout -b recovery/before-rebase a3b4c5d # Or hard-reset your current branch (DESTRUCTIVE — use with care) git reset --hard HEAD@{3}

Other History Surgery Tools

git commit --amend rewrites the most recent commit — useful to fix a typo in a commit message or add a forgotten file. Like rebase, it changes the SHA, so only use it before pushing.

git reset moves the branch pointer backward. --soft keeps your changes staged; --mixed (default) unstages them; --hard discards them entirely. Use --hard only when you are certain you want to throw away work.

git bisect is the partner tool to clean history: a linear, atomic commit history makes binary search for regressions dramatically faster. This is why teams enforce "one logical change per commit" — not aesthetics, but incident response efficiency.

When to merge, when to rebase: most big-tech teams rebase feature branches locally before PR review (clean history for reviewers), then use a squash merge or merge commit into main depending on their branching strategy. The exact policy matters less than applying it consistently and never rewriting public commits.