Computing and the Command Line

Undoing Changes

The everyday undos, matched to the situation: git restore to throw away an edit, git restore --staged to unstage, git restore --source to bring back an old version of a file, git commit --amend to fix the last commit before you push it, git revert to undo a commit that's already shared, and git rm --cached to stop tracking a file. Which of these can lose work, and why committed work is hard to lose.

  • 5 min
  • 9 steps
  • 2 questions
  • Lesson 30 of 80

In this lesson

  1. Throwing away an edit
  2. Unstaging
  3. An older version of a file
  4. Fixing the last commit
  5. Undoing a shared commit
  6. Stop tracking a file
  7. Committed work is hard to lose
  8. Your turn
  9. So

Throwing away an edit

You changed a file and want it back the way it was. git restore replaces your working copy with the staged version, or the last committed one if nothing’s staged 1:

me@linuxbox:~/garden$ echo oops >> beds.txt
me@linuxbox:~/garden$ git restore beds.txt
me@linuxbox:~/garden$ git status -s
me@linuxbox:~/garden$

This is one of the few git commands that can destroy work. An edit you never committed or staged isn’t stored anywhere else, so once it’s overwritten it’s gone 2. Run git diff first to see what you’d lose. git status even prints this command as a hint, so take a second before pasting it. git restore . does every file at once.

(Older guides use git checkout -- file for the same job. Git 2.23 split git checkout’s two jobs into git switch, for changing branches, and git restore, for restoring files 3.)

Six situations and their commands. Changed a file and want it back: git restore beds.txt, which throws your edit away. Staged something by mistake: git restore --staged beds.txt; the edit stays, just unstaged. Want an older version of a file: git restore --source=HEAD~3 beds.txt, from any commit. Last commit wrong and not pushed: git commit --amend, which replaces that commit. A shared commit was a mistake: git revert a1b2c3d, a new commit that undoes it. Shouldn't be tracked: git rm --cached secret.txt, then add it to .gitignore. Bigger rewinds, git reset and the reflog, come in module 5.
Find your situation, run that command. Credit: StudyCorner diagram · CC BY 4.0 · Source

Quick check

Which of these can permanently lose work?

Unstaging

You git added something that doesn’t belong in the next commit. git restore --staged takes it back out of the staging area and leaves your edit alone 1:

me@linuxbox:~/garden$ git add beds.txt README.md
me@linuxbox:~/garden$ git restore --staged README.md
me@linuxbox:~/garden$ git status -s
M  beds.txt
 M README.md

Nothing is lost; README.md is just unstaged again.

An older version of a file

To bring back a file as it was in any earlier commit, add --source 1:

me@linuxbox:~/garden$ git restore --source=HEAD~3 beds.txt     # as it was three commits ago
me@linuxbox:~/garden$ git restore --source=adc6083 README.md   # as it was in that commit

It changes only your working copy; commit it if you want to keep the old version. (HEAD~3 means “three commits before the current one” 2.)

Fixing the last commit

Forgot a file, or made a typo in the message? git commit --amend replaces the last commit with a new one made from the staging area 2:

me@linuxbox:~/garden$ git commit -m "Plan the fence"
me@linuxbox:~/garden$ git add fence-posts.txt                # forgot this one
me@linuxbox:~/garden$ git commit --amend --no-edit           # same message, now with the file
me@linuxbox:~/garden$ git commit --amend -m "Plan the fence and posts"   # or reword it

Amending doesn’t edit the old commit; it makes a new commit with a new ID and drops the old one from your branch 2. That’s harmless for commits only you have. Only amend commits you haven’t pushed: if someone else already has the old commit, amending and force-pushing causes them real trouble 2.

Undoing a shared commit

To undo a commit that’s already been shared, don’t rewrite history; add to it. git revert creates a new commit that does the opposite of an earlier one 4:

me@linuxbox:~/garden$ git revert --no-edit HEAD
[main d758bab] Revert "Add lettuce"
 1 file changed, 1 deletion(-)
me@linuxbox:~/garden$ git log --oneline -3
d758bab Revert "Add lettuce"
11bf533 Add lettuce
3ce82ab Describe the beds in the README

The mistake and its reversal both stay in history, with a message saying so, and everyone’s copies stay consistent. Revert any commit by ID, not just the last one 4.

Quick check

Yesterday you pushed a commit that broke something. How do you undo it?

Stop tracking a file

To remove a file from the repository but keep it on disk, git rm --cached, then add it to .gitignore so it isn’t added again (last lesson) 2.

Committed work is hard to lose

Notice the pattern: the commands that can lose work act on uncommitted changes. Once something is committed, git keeps it, even after amending, and module 5 shows how to get back commits you thought you’d thrown away 2. So commit early and often, even messy work in progress; you can tidy it up later.

Your turn

Exercises

  1. Make a change to beds.txt, look at it with git diff, then throw it away with git restore.
  2. Stage two files, then unstage one with git restore --staged. Check with git status -s.
  3. Bring back beds.txt as it was in your very first commit, look at it, then return it to normal with git restore beds.txt. (Why does that work?)
  4. Make a commit, then amend it to add a forgotten file. Compare git log --oneline before and after: did the ID change?
  5. Revert one of your earlier commits, and read the message git writes.
Answers
  1. git restore --source=<first-commit-id> beds.txt changes only the working copy; the staging area still holds the current version, so a plain git restore beds.txt copies that back.
  2. Yes: amending makes a new commit with a new ID.
  3. Revert "<original summary>", with a body saying This reverts commit <full id>.

So

git restore throws away uncommitted edits (carefully), --staged unstages, and --source brings back any old version. git commit --amend fixes the last commit before you push it; git revert undoes a shared commit by adding a new one; git rm --cached stops tracking a file. Commit often: committed work is hard to lose.

Lesson complete

Nice work.

1day streak
0/1today's goal
–correct

Up next · 5 min

What a Branch Is

Next lesson
Sources for this lesson
  1. 1
    git-restore documentation. Git project (git-scm.com). verifiedRestores working-tree files from the index by default, or from HEAD with --staged (which restores the index, i.e. unstages); --staged --worktree does both; --source=<tree> restores from any commit.
  2. 2
    Scott Chacon, Ben Straub. Pro Git, 2nd edition. Apress; free online at git-scm.com. 2014. verifiedFree CC BY-NC-SA 3.0 book, maintained online. Ch. 1: version control; Git's 2005 origin when the Linux kernel lost free use of BitKeeper; snapshots, not differences (unchanged files stored once); nearly every operation local; integrity through 40-character SHA-1 checksums; the three states (modified, staged, committed) and three areas (working tree, staging area or index, .git directory); first-time setup with system/global/local config levels, user.name and user.email baked into commits, core.editor, git config --list --show-origin. Ch. 2: git init, status (and -s), add, diff and diff --staged, commit (-m, -a), .gitignore, log options, amending, undoing, remotes, tags, aliases. Ch. 3: branches as movable pointers, HEAD, merging and conflicts, remote branches, rebasing and its rule. Ch. 7: reset demystified, stashing, revision selection. Ch. 8: core.autocrlf true on Windows, input on Linux and macOS. Ch. 10: objects (blob, tree, commit) and references.
  3. 3
    Git 2.23 Release Notes. Git project. verifiedTwo new commands, git switch and git restore, split checking out a branch to work on from checking out paths out of the index or a tree, the two jobs git checkout had done.
  4. 4
    git-revert documentation. Git project (git-scm.com). verifiedGiven existing commits, revert the changes they introduce by recording new commits; requires a clean working tree; --no-edit skips the message editor. Distinct from reset (which moves a branch) and restore (which changes files).