Team training · Block 3 of 6

Rewriting local history

Still yours alone. Still safe.

Nothing here edits a commit

Commits are named by their contents. Change anything and you have a different commit.

Every command in this block creates new commits and moves a branch label onto them. The originals are orphaned, not modified.

Which means "rewriting history" is a misleading phrase — and also why the old commits are still in your reflog.

3.1

amend

Fixing the commit you just made

$ git commit -m "Add tax calclation"      # typo
[main c584509] Add tax calclation

$ git commit --amend -m "Add tax calculation"
[main 9f2ab41] Add tax calculation

Different hash. c584509 still exists, unreferenced — 9f2ab41 is a new commit that replaced it on the branch.

kata · amend
3.2

rebase

Replaying commits onto a new base

main feature copies — new hashes originals, orphaned feature

Rebase doesn't move your commits. It copies them onto a new starting point.

Doing it

$ git switch feature
$ git rebase main

Successfully rebased and updated refs/heads/feature.

Read it as: "take my commits, and replay them as if I'd started from main instead."

Conflicts can surface per replayed commit. Resolve, then git rebase --continue. Or --abort to put everything back.

Merge or rebase?

merge main into your branch

Adds a merge commit. Nothing is rewritten. Safe even if the branch is shared.

rebase onto main

Linear history, no merge commit. Rewrites — so only while the branch is yours alone.

On our setup, trunk only ever receives squashed commits — so merge commits on a feature branch never reach main. Both options are fine.

3.3

Interactive rebase

A todo list you can edit

$ git rebase -i HEAD~4

pick 91ac5d3 Add discount helper
pick e91e334 wip
pick c584509 wip2
pick f6c6987 fix typo

# p, pick   = use commit
# r, reword = use commit, edit the message
# s, squash = meld into previous, combine messages
# f, fixup  = meld into previous, discard message
# d, drop   = remove commit

Squashing everything back into one? Skip the todo list entirely: git reset --soft <ancestor> moves the branch back but leaves every change staged — then a single commit rebuilds it. <ancestor> is where you branched from: main, or HEAD~4.

Collapsing the mess

before

pick 91ac5d3 Add discount helper
pick e91e334 wip
pick c584509 wip2
pick f6c6987 fix typo

after editing

pick 91ac5d3 Add discount helper
squash e91e334 wip
squash c584509 wip2
fixup f6c6987 fix typo

Four commits become one. squash lets you edit the combined message; fixup silently discards its own.

Why bother, given squash merge?

Trunk gets one commit either way. So this is not about keeping main tidy.

It's about the reviewer. A branch of four coherent commits can be reviewed step by step. A branch of eleven "wip" commits can only be reviewed as one enormous diff.

Polish, not hygiene. Worth doing on a large PR; skip it on a two-line fix.

Where we got to

Want toUse
Fix the message or content of the last commitcommit --amend
Move your branch on top of current mainrebase main
Combine, reorder, or drop commitsrebase -i
Undo any of the aboverebase --abort, or reflog

All of it is safe while the branch is yours alone. Block 4 adds the one constraint that matters.