Why this matters
Most people make small mistakes right after they save a change. The commit message has a typo. A file was left out. A line used for debugging was saved by accident. Git has two commands for fixing these mistakes without a long cleanup: git commit --amend changes the newest commit, and git restore moves a file back to an earlier state.
Both commands are easy to use. The risk is in what they do to history. Changing a commit that only you have seen is harmless. Changing a commit that others have already copied can break their work. This lesson shows both commands with real examples, then gives a simple test for when a fix is safe.
This lesson assumes you can already make a commit with git add and git commit -m. The branch lessons explain how commits sit on a branch, and the next lesson covers git reset and git revert, which undo larger amounts of work.
Terms used in this lesson
A commit is a saved snapshot of your project. It has a message, a date, an author, and a long ID. The ID is a string of 40 characters, made of the digits 0 to 9 and the letters a to f. Git accepts a short form, usually the first 7 characters, as long as no other commit starts the same way.
The working directory is the folder on disk where you edit files. This is the place you see in your file explorer or editor.
The staging area is a holding list. It holds the changes that the next commit will include. Some people call it the index. You put a change on the list with git add, and you remove it from the list with git restore --staged.
HEAD is Git's name for the commit you are on right now. Most of the time it points at the tip of your current branch, which is the newest commit on that branch. HEAD~1 means "the commit before HEAD", also called the parent of HEAD.
Three places a change can be
A change moves through three places. Knowing which place a change is in tells you which command can fix it.
- The working directory. You edited
notes.txtand saved it. Git knows the file changed, but the change is not on the list yet. - The staging area. You ran
git add notes.txt. The change is on the list for the next commit. - The repository. You ran
git commit. The change is part of a saved commit.
Here is one full trip for a file called notes.txt:
echo "Git stores snapshots." >> notes.txt
git add notes.txt
git commit -m "Describe snapshots"The first line changes the working directory. The second moves the change to the staging area. The third saves it as a commit. Each step can be undone, but a different command is needed for each one. git commit --amend works on the third place. git restore works on the first two places, and on files that a commit already holds.
Fixing the message with amend
Suppose you ran this command and then noticed the typo:
git commit -m "Add intro paragrah"The message says "paragrah". To fix it, run:
git commit --amend -m "Add intro paragraph"The --amend flag tells Git to replace the newest commit instead of adding a new one. Git does not edit the old commit in place. It creates a new commit with the same files and the same parent, but with the new message. Then it moves the branch to point at the new commit. The old commit is no longer on the branch.
Two things follow from this. The commit ID changes, because the message is part of what Git hashes to make the ID. And the old commit is not destroyed at once. Git keeps a record of where HEAD has been, called the reflog, and the old commit stays there for a while. You can see it with git reflog. The next lesson uses the reflog to find commits that a reset removed.
If you leave off -m, Git opens a text editor with the old message. Change the text, save, and close the editor to finish. To keep the old message exactly, use --no-edit:
git commit --amend --no-editAdding a forgotten file with amend
Amend can also change what the commit contains. Suppose you committed a page, then noticed that the matching stylesheet was not included. You can add the file to the commit like this:
git add styles.css
git commit --amend --no-editThe first command puts styles.css on the staging area. The second replaces the newest commit with one that holds the page and the stylesheet. Because the message is kept with --no-edit, the history still looks like one commit.
Amend always takes the whole staging area into account. If you have other files staged for a different purpose, they will be folded into the same commit. Run git status before you amend, so you know what the new commit will contain.
A worked example: removing a debugging line
Here is a small Python file. It prints a value on the screen. A developer added a debugging line and committed it by mistake.
# greet.py
name = "Anisha"
print("DEBUG name is", name)
print("Hello,", name)The line that starts with DEBUG should not be in the commit. The fix has three steps. First, edit greet.py in your editor so that only the first, second and last lines remain:
# greet.py
name = "Anisha"
print("Hello,", name)Second, stage the change:
git add greet.pyThird, replace the last commit with the cleaned file:
git commit --amend --no-editRead the result with git log --oneline. The top line still shows the same message as before, and the file in that commit no longer has the debugging line. The commit ID is new. The old commit still exists in the reflog, but nothing on your branch points at it.
A harder example: when the commit was already pushed
A push sends your commits to a shared copy of the project, usually on GitHub. The shared copy is called the remote, and its default name is origin. Once a commit is pushed, other people may copy it to their own computers.
Suppose you pushed a commit that adds a password to config.py. You remove the password from the file, amend the commit, and try to push again:
git add config.py
git commit --amend --no-edit
git pushGit refuses. The push fails with a message like this:
To ...
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to '...'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. If you want to integrate the remote changes,
hint: use 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.The message means that the remote's copy of main holds a commit that your local copy does not have. The hint suggests git pull, but do not follow it here, because the remote's commit is the old one you replaced. Your amended commit replaced the old one, so the two histories no longer line up. Git blocks the push so that it does not silently throw away the old commit on the remote.
You could force the push with git push --force, which overwrites the remote branch. That is risky. If a teammate pulled the old commit and added their own work on top of it, their work would no longer match the branch, and they would get confusing errors. The safer form is:
git push --force-with-leaseThis works like a force push, but Git first checks that the remote branch is still where you last saw it. If someone else pushed in the meantime, the push is refused and nothing is overwritten.
In this case, the password is also a problem on its own. Once a secret has been pushed, treat it as leaked. Change the password. Removing it from a commit does not remove it from the older commit on the remote, and other people may have copies.
Fixing the author of the last commit
Each commit records a name and an email address. Git takes them from the settings in user.name and user.email. If those settings were wrong when you made the commit, the commit shows the wrong author. The author is part of the commit, so it can only be changed by rewriting the commit.
Suppose you set your email to a work address by mistake, and then made a commit with it. First fix the setting, so that later commits are correct:
git config user.email "you@example.com"Then replace the newest commit, and tell Git to use the current settings for the author:
git commit --amend --reset-author --no-editThe --reset-author flag sets the author to the current settings. Without it, --amend keeps the original author. The message and the files stay the same, as --no-edit asks.
The same rules apply as for any amend. The commit ID changes, so a branch that was already pushed needs the careful push described above. The example address you@example.com is a placeholder. Use the address that your hosting service expects.
Amend changes only the newest commit. To change the author or message of an older commit, you need a rebase, which the next lessons cover. Do not try to copy files around by hand to get the same result, because that is slow and easy to get wrong.
Restoring files with git restore
git restore changes files in the working directory or the staging area. It has three common uses. The first is to throw away edits you have not committed yet.
Suppose you changed notes.txt by mistake and want the last committed version back:
git restore notes.txtGit copies the committed version of the file over your edited version. The edits are gone. This is the one use of git restore that cannot be undone, because uncommitted edits to a file are not stored anywhere in Git. Check git status and git diff notes.txt before you run it, so that you know you do not want the edits.
The second use is to take a file off the staging area while keeping your edits. Suppose you ran git add on a file by mistake:
git restore --staged notes.txtThe file is no longer on the list for the next commit. Your edits stay on disk. This command is safe to run, because nothing is lost.
The third use is to bring back a file from an older commit. The --source option names the commit to copy from:
git restore --source=HEAD~1 notes.txtThis copies notes.txt as it was in the parent of the newest commit. The change appears in the working directory, not yet in a new commit. If you want to keep it, run git add and git commit. If you do not, run git restore notes.txt to go back to the newest commit's version.
git restore only works on files that Git already knows about. A new file that was never added cannot be restored, because Git has no copy of it. The next section shows the error this produces.
When a fix is safe
The test that matters is simple. Ask whether anyone else may have a copy of the commit you want to change. If nobody else has it, a rewrite of the last commit is safe. If somebody may have it, do not rewrite it. Make a new commit instead.
| Situation | Is it safe? | What to use |
|---|---|---|
| Last commit is only on your computer | Yes | git commit --amend |
| Last commit is pushed, and only you use the branch | Yes, with care | git commit --amend, then git push --force-with-lease |
| Last commit is pushed to a branch others use | No, not without agreement | A new commit that fixes the mistake |
| Edits in files were never saved in a commit | Only if you want them gone | git restore discards them for good |
A rewrite is safe for you alone, but it is not always safe for a team. Ask the people who share the branch before you force a push.
Common mistakes
Running git restore on a file Git does not know. If you create a new file and then run git restore on it, Git replies:
error: pathspec 'notes.txt' did not match any file(s) known to gitGit cannot restore a file it has never committed or added. Delete the file yourself if you do not need it, or run git add first if you want to keep it.
Running git restore with no file name. The command needs to know which files to restore. Without a name, Git replies:
fatal: you must specify path(s) to restoreAdd the file name after the command. Pass . only if you really want every file in the folder reset.
Amending a commit and then forgetting the push. After git commit --amend, your local branch and the remote branch no longer match. A plain git push will be rejected, as shown earlier. Do not respond with git push --force on reflex. Look at who else uses the branch first.
Amending without checking the staging area. Staged files join the commit, even if you meant them for a later commit. Run git status first. If an unwanted file is staged, run git restore --staged file-name to take it off the list before you amend.
Editing a file after the amend and expecting it to be in the commit. The amend took a snapshot of the staging area at the moment you ran it. Changes made later stay in the working directory until you git add them and run another commit or amend.
Practice
Exercise 1. Your newest commit has the message Fix typo in intro. The commit has not been pushed. Change the message to Fix typo in the introduction. Write the command.
Show the answer
Run git commit --amend -m "Fix typo in the introduction". The commit is replaced, so it gets a new ID. This is safe because nothing has been pushed.
Exercise 2. You committed lesson.md. Afterwards you edited quiz.json and forgot to add it. You want both files in the newest commit, with the same message. Write the commands.
Show the answer
Run git add quiz.json, then git commit --amend --no-edit. The first command puts quiz.json on the staging area. The second replaces the newest commit with one that holds both files and keeps the message. Check git status before the amend so you know nothing else is staged.
Exercise 3. You changed config.txt after the last commit, and the changes are wrong. You do not want any of them. Write the command and say what it erases.
Show the answer
Run git restore config.txt. It copies the committed version of the file over your edits. The edits are erased and cannot be recovered from Git, so check git diff config.txt first.
Exercise 4. You ran git add notes.txt by accident. You want the file off the staging area, and you want to keep the edits you made to it. Write the command.
Show the answer
Run git restore --staged notes.txt. The file leaves the list for the next commit. The edits stay in the file on disk, so nothing is lost.
Exercise 5. Last week you pushed a commit that contains a secret key. A teammate has already pulled it. You delete the key and amend the commit. Should you force push the amended commit? Explain.
Show the answer
No. The teammate's copy still has the old commit, so a forced push would leave their work out of step with the branch. A new commit that deletes the key is the usual choice on a shared branch. The key is leaked either way, so change the key as well. Removing it from a new commit does not remove it from the older commit.
Summary
git commit --amendreplaces the newest commit with one that has a new message or new staged files, and the commit ID changes.- Use
--no-editto keep the old message when you only add files. git restore filethrows away unsaved edits for good, andgit restore --staged filetakes a file off the staging area without losing edits.- A rewrite of a commit that others may have copied needs agreement first, and a plain push after an amend is rejected for that reason.
- Use
git push --force-with-leaserather thangit push --force, because it refuses to overwrite a branch that changed since you last looked.