Amending Commits
Sometimes you commit too soon — a typo in the message, a forgotten file, or a stray debug line you meant to remove. git commit --amend lets you fix your most recent commit instead of piling on a new “oops” commit. It doesn’t edit the old commit in place; it replaces it with a brand-new commit object and moves your branch pointer to match. Understanding that distinction is the key to using --amend safely, especially once a commit has been shared with others.
Overview: What “Amending” Really Means
Git’s history is built from three kinds of objects, all identified by a SHA-1 hash of their content: blobs (the raw contents of a file), trees (a snapshot of a directory, mapping file names to blob or tree hashes), and commits (a pointer to one tree, plus a pointer to the parent commit, an author, a committer, a timestamp, and a message). A branch like main is nothing more than a small file containing the hash of its latest commit — a movable pointer. HEAD usually points at the branch, which in turn points at the commit.
Because a commit’s hash is derived from its content (tree, parent, message, author, timestamp), you cannot edit a commit’s message or contents without changing its hash. git commit --amend takes whatever is currently staged in the index, combines it with the contents of the commit you’re amending, builds a new tree object, and writes a brand-new commit object that uses the same parent as the commit being replaced. Git then moves the current branch pointer to this new commit. The old commit object still exists in the repository (nothing is deleted immediately), but it is no longer reachable from any branch — it becomes an orphaned commit, retrievable for a while through the reflog (git reflog) until Git eventually garbage-collects it.
In short: amending doesn’t rewrite a commit — it replaces it with a new one and forgets the old one existed. This matters enormously once a commit has been pushed: anyone who already fetched the old commit now has history that has “diverged” from yours, because from Git’s perspective they are two entirely different commits that happen to look similar.
Syntax
git commit --amend [--no-edit] [-m "<message>"] [--author "<name> <email>"] [-a]
| Flag | Meaning |
|---|---|
--amend |
Replace the most recent commit (HEAD) with a new commit built from the current index plus the old commit’s tree. |
-m "<message>" |
Supply a brand-new commit message directly, skipping the editor. |
--no-edit |
Keep the original commit message unchanged — useful when you only want to add staged changes, not reword. |
--author "<name> <email>" |
Override the author identity recorded on the new commit (the committer identity still comes from your current git config). |
-a |
Automatically stage modifications to already-tracked files before amending (does not add new, untracked files). |
If you run git commit --amend with no message flag, Git opens your configured editor pre-filled with the old commit message so you can edit it and save.
Examples
Example 1: Fixing a commit message
git commit --amend -m "Fix header alignment on mobile viewport"
Output:
[main a1b2c3d] Fix header alignment on mobile viewport
Date: Mon Aug 3 10:15:22 2026 -0700
1 file changed, 3 insertions(+), 1 deletion(-)
No files were staged before this command, so the tree stays exactly the same as the previous commit — only the message and the commit’s hash change. Notice the new hash, a1b2c3d: this is a genuinely different commit object from the one it replaced, even though the file contents are identical.
Example 2: Adding a forgotten file
git add styles/nav.css
git commit --amend --no-edit
Output:
[main d4e5f6a] Add responsive navigation bar
Date: Mon Aug 3 10:20:05 2026 -0700
2 files changed, 15 insertions(+), 1 deletion(-)
Here styles/nav.css was staged first, then --amend --no-edit folded it into the previous commit and kept the original message (“Add responsive navigation bar”). The resulting commit now shows two changed files instead of one, but it’s still a single commit in the log — not two.
Example 3: Correcting the wrong author
git commit --amend --author "Jordan Lee <jordan.lee@example.com>" --no-edit
Output:
[main 3c2b1a0] refactor(auth): use async/await in middleware
Author: Jordan Lee <jordan.lee@example.com>
Date: Mon Aug 3 10:32:40 2026 -0700
1 file changed, 9 insertions(+), 9 deletions(-)
This is handy right after realizing a commit was made using the wrong global user.email — for example, a work commit made with a personal Git identity. The commit’s content and message stay the same, but the recorded author identity changes.
How Git Amends a Commit, Step by Step
- Git looks at whatever is currently in the index (the staging area) — anything you ran
git addon since the last commit. - It builds a new tree object representing the full snapshot: the old commit’s tree, overlaid with any newly staged changes.
- It writes a new commit object pointing at that tree. This new commit’s parent is set to the same parent the old commit had — the new commit slots into exactly the same place in history, it just replaces the tip.
- Git updates the current branch ref (e.g.
refs/heads/main) to point at this new commit’s hash.HEAD, which points at the branch, now resolves to the new commit automatically. - The previous commit object is not deleted. It becomes unreachable from any branch, but its hash is recorded in the reflog (
git reflog) for about 90 days by default, sogit reset --hard HEAD@{1}can still recover it if the amend was a mistake.
Amending only ever touches the single most recent commit. To edit an older commit further back in history, you need git rebase -i and mark that commit as edit:
git rebase -i HEAD~3
That technique is covered in the interactive rebase lesson — it works by essentially replaying an amend at each commit you flag, one at a time.
Common Mistakes
Mistake 1: Amending a commit that’s already been pushed and pulled by others
git commit --amend -m "Fix typo in README"
git push
Output:
To github.com:octocat/project-tracker.git
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to 'github.com:octocat/project-tracker.git'
The push is rejected because the remote still has the original commit, and your amended commit is a completely different object with the same parent — from the remote’s point of view your branch has diverged, not simply moved forward. If nobody else has fetched the old commit yet, it’s safe to push with --force-with-lease, which refuses to overwrite the remote if someone else has pushed in the meantime:
git commit --amend -m "Fix typo in README"
git push --force-with-lease
But if teammates have already pulled the original commit, amending and force-pushing rewrites history out from under them — this is the same golden rule that applies to rebasing: don’t rewrite commits that other people may already have based work on. Prefer a new commit instead once a commit is shared.
Mistake 2: Forgetting to stage the fix before amending
# Edited src/app.js to fix a null-pointer bug, but forgot to stage it
git commit --amend --no-edit
Because nothing new was staged, this amend only rewrites the commit’s metadata (a new hash, same tree) — the bug fix in the working tree is left out of the commit entirely, staying as an uncommitted change. Always run git status before amending to confirm your fix is staged:
git add src/app.js
git commit --amend --no-edit
Best Practices
- Only amend commits that exist solely in your local repository, or that you are certain no one else has fetched yet.
- After amending a pushed commit, use
git push --force-with-leaserather than plain--force, so the push fails safely if the remote moved unexpectedly. - Run
git statusbefore--amendto double-check exactly what’s staged — it’s easy to amend with the wrong changes half-staged. - Use
--no-editwhen you only want to fold in a forgotten file or fix, not reword the message. - Follow a consistent commit message style, such as Conventional Commits (
feat:,fix:,refactor:,docs:), so amended messages stay consistent with the rest of your history. - If you’re unsure whether a commit has been shared, check
git log origin/main..HEAD— if your commit doesn’t show up there, it hasn’t been pushed. - Keep
git reflogin mind as a safety net: an accidental amend can almost always be undone by resetting back to the reflog entry from just before it.
Practice Exercises
- Make a commit with an intentionally misspelled message (for example
"Fx login button styling"), then use--amendto correct just the message without touching any files. Confirm withgit log -1that the hash changed even though the file contents didn’t. - Commit a change to one file, then realize you forgot to include a second, related file. Stage the second file and fold it into the previous commit using
--no-edit. Verify withgit show --stat HEADthat both files now appear in the single commit. - Push a commit to a remote branch, then amend it locally and try a plain
git push. Observe the rejection, then push again with--force-with-leaseand confirm it succeeds. Do this on a throwaway repo or branch, never on a shared branch with real collaborators.
Summary
git commit --amendreplaces the most recent commit with a new commit object — it does not edit history in place.- The new commit reuses the old commit’s parent and combines the old tree with anything currently staged in the index.
- The branch pointer and
HEADmove to the new commit; the old commit becomes unreachable but survives briefly in the reflog. - Use
-mto reword,--no-editto keep the message while adding staged changes, and--authorto correct identity. - Never amend (or rebase) a commit that others may already have pulled without coordinating — use
git push --force-with-lease, never a bare--force, when you must update a shared branch. - To edit a commit further back than the tip, use
git rebase -iinstead of--amend.
