Skip to main content
CodeOath
← All posts

Git95 min total · 17 parts

Git Internals and Workflows: Branching, Merging, and Rebasing

Part 9 of 17 · ~4 min

Interactive Rebase: Squash, Reword, Reorder

Thursday morning. Shift swapping works, tests pass, Nadia's fix is in. Here is the branch you are about to ask Theo to review:

git log --oneline main..feature/shift-swap
3c81e06 typo in changelog
a7d92b4 Add swap offer + claim endpoints
6b3e0f1 fix the test for real
d20f5c8 fix the test
8e4b1a7 wip
1f0c9d3 Block swaps inside the deadline window

Those are not the hashes from Monday and Tuesday, and that is worth a second's attention rather than a squint: Wednesday's rebase replaced every commit on this branch. Same changes, same messages, same author, new objects. This is the apostrophe from chapter three, showing up in real output.

Which leaves the actual problem: that is an honest record of your Tuesday and it is a terrible thing to ask a colleague to read. Four of those six commits are noise. Worse, if this history lands on main, then a year from now somebody bisecting a payroll bug has to check out a commit called wip and find out whether the app even starts.

git rebase -i fixes that. It is the same replay mechanism from the last chapter, with one addition: before replaying anything, it hands you an editable list of what it is about to do.

git rebase -i main     # or HEAD~6 — "the last six commits" — which is the same range here

Your editor opens on this:

pick 1f0c9d3 Block swaps inside the deadline window
pick 8e4b1a7 wip
pick d20f5c8 fix the test
pick 6b3e0f1 fix the test for real
pick a7d92b4 Add swap offer + claim endpoints
pick 3c81e06 typo in changelog

Note the order: oldest at the top, which is the reverse of git log and is the single most common thing people get wrong here. The list reads top to bottom in the order Git will replay the commits.

You edit the verbs:

pick   1f0c9d3 Block swaps inside the deadline window
fixup  8e4b1a7 wip
fixup  d20f5c8 fix the test
fixup  6b3e0f1 fix the test for real
reword a7d92b4 Add swap offer + claim endpoints
fixup  3c81e06 typo in changelog

Save, close, and Git replays the list. Six commits become two, one of which you are prompted to rename.

The verbs you will actually use:

  • pick — keep the commit as it is. The default for every line.
  • squash (s) — fold this commit into the one above it, and open an editor with both messages so you can write a combined one.
  • fixup (f) — fold this commit into the one above it and discard its message entirely. This is the right verb for wip and fix the test, whose messages are worth nothing. Reaching for squash out of habit means editing away four junk messages by hand.
  • reword (r) — keep the changes exactly, edit only the message.
  • edit (e) — stop the rebase at this commit with it checked out, so you can amend the actual content, then git rebase --continue.
  • drop (d) — discard the commit and its changes. Deleting the line does the same thing, which is worth knowing mostly so you do not do it by accident.
  • Reordering lines replays the commits in the new order. Useful, and the most reliable way to generate conflicts, because a commit moved earlier may depend on something that used to exist before it.

There is a way to avoid the whole editing session, and once you have it you will use it constantly. When you write a commit that only exists to fix an earlier one, say so at commit time:

git commit --fixup 1f0c9d3      # message becomes: fixup! Block swaps inside the deadline window

Then let Git arrange the list for you:

git rebase -i --autosquash main
pick  1f0c9d3 Block swaps inside the deadline window
fixup 8e4b1a7 fixup! Block swaps inside the deadline window
pick  a7d92b4 Add swap offer + claim endpoints

Every fixup! commit has been moved directly beneath its target and marked fixup, with no manual reordering and no chance of putting a line in the wrong place. git config rebase.autoSquash true makes it the default for every interactive rebase.

(A cosmetic note, so nothing surprises you: newer Git versions print the todo list as pick 1f0c9d3 # Block swaps…, with a # before the subject. Older versions omit it. It changes nothing you do.)

Now the part that should make you slightly uncomfortable. Every one of those six commits was already pushed on Tuesday, and Theo has them. You have just rewritten all six. Your local branch and origin/feature/shift-swap now disagree about the entire history of this feature, and a normal git push is about to be rejected.

That is not a mistake — this is the legitimate case for rewriting a pushed branch, and it is why you and Theo agreed he would not pull. But it does mean you now need to force-push, which is the most destructive routine operation in Git and gets its own chapter. Leave the branch as it is; we come back to it.