Skip to main content
Git & GitHub · lesson 8 of 12
Git & GitHub intermediate Lesson 8 of 12

Rewriting History: Amend and Interactive Rebase

Fix a commit message and fold in a forgotten file with git commit --amend, then squash three sloppy WIP commits into one clean commit with interactive rebase.

By SkillByExample · Published

git rebase amend

commit --amend rewrites the single most recent commit. Interactive rebase (rebase -i) rewrites any range of recent commits — reordering, combining, or dropping them. Both are safe on commits nobody else has pulled yet.

1) Fix a typo’d commit message

$ git commit -m "add cofnig"
$ git log --oneline -1
cdc8dd8 add cofnig
$ git commit --amend -m "Add config module"
$ git log --oneline -1
82464c3 Add config module

--amend doesn’t edit the old commit in place — it creates a brand-new commit with the corrected message and the same parent, then moves main to point at it. cdc8dd8 no longer exists on any branch.

2) Fold a forgotten change into the same commit

$ # ...realize config.py is missing a line, edit it...
$ git add config.py
$ git commit --amend --no-edit
$ git show --stat HEAD
[main 069e689] Add config module
 1 file changed, 2 insertions(+)
 create mode 100644 config.py

--no-edit keeps the existing message and just replaces the snapshot with whatever is now staged — the missing line joins the same commit instead of becoming its own.

3) Three sloppy commits, squashed into one

$ git log --oneline -4
cb34b76 WIP finish feature
0b0c40d WIP more
bb6ae01 WIP
069e689 Add config module

An interactive rebase against the commit three back (HEAD~3) opens an editor with a todo list like:

pick bb6ae01 WIP
pick 0b0c40d WIP more
pick cb34b76 WIP finish feature

Changing the last two lines’ pick to squash tells Git to fold each one into the commit above it, in order:

pick bb6ae01 WIP
squash 0b0c40d WIP more
squash cb34b76 WIP finish feature

4) The result: one commit, all three diffs combined

$ git log --oneline -2
26b7ef1 WIP
069e689 Add config module

Three commits became one — but Git kept the first commit’s message (WIP) as the default for the combined result, since that line was still pick, not squash. scratch.py’s content is now the union of all three WIP edits.

5) Rename it with amend, the same tool from step 2

$ git commit --amend -m "Add scratch calculation feature"
$ git log --oneline -2
73f7789 Add scratch calculation feature
069e689 Add config module

The squash produced 26b7ef1; amending it for a real message produced 73f7789 — a second new hash, same content, better message. This two-step shape (squash first, rename after) is exactly what the reflog shows in the next lesson.

Practice

  1. Run git commit --amend with no -m flag at all and watch it open your editor pre-filled with the old message — confirm cancelling the edit (saving unchanged) still keeps the amend (new hash, same text).

  2. In an interactive rebase todo list, change a pick to drop (or just delete the line) for a commit in the middle of the range. Confirm that commit’s changes disappear from history entirely, not just its message.

    Solution

    Dropping a middle commit still replays the commits above it, but without that commit’s diff — if a later commit depended on it, you’ll get a conflict during the rebase, exactly like a merge conflict.

  3. Use reword instead of pick on one line of a todo list to change only that commit’s message, leaving its content and every other commit untouched.

  4. Reorder two pick lines in a todo list (swap their order) and confirm Git replays them in the new order — watch for a conflict if the second commit (now first) doesn’t build on the first’s changes.

  5. Run git reflog immediately after a squash and find the pre-rebase commits (bb6ae01, 0b0c40d, cb34b76) still present — rebase doesn’t delete objects immediately, it just stops pointing branches at them.

Frequently Asked Questions

Is it safe to amend a commit that's already been pushed?
Technically yes, but it changes the commit's hash, which means anyone who already pulled the old version now has diverged history. Amend freely on commits only you have; for pushed commits, coordinate with your team or avoid it (git revert instead).
What does the interactive rebase todo list actually contain?
One line per commit being rebased, oldest first, each starting with an action word (pick, squash, reword, drop, ...) followed by the short hash and subject line. Editing the word changes what happens to that commit; reordering lines reorders the commits.
What's the difference between squash and fixup in an interactive rebase?
Both combine a commit into the one above it. squash keeps both commit messages and lets you edit the combined message; fixup silently discards the message of the commit being folded in, keeping only the target's message. Use fixup for pure 'oops, forgot a line' commits.
Why does interactive rebase create new commit hashes even for commits I didn't change?
A commit's hash is derived from its content, parent, and metadata. Rebase replays commits onto a new base one at a time; even an untouched commit gets a new parent pointer, which changes its hash — this is the same reason rebasing shared history is disruptive.