Aller au contenu

Restoring and reverting files

Mistakes happen: a commit that contains wrong data, or a file staged by accident. This page covers three situations:

Situation Command
A whole commit must be undone git revert
One file must go back to an earlier version git checkout <commit> -- <file> (or git restore --source)
A file was staged by mistake git restore --staged <file>

Making an error

In the course scenario, the repository contains report.md, mental_health_survey.csv, summary_statistics.csv, and funding_sources.txt. The survey file was modified, staged, and committed, and the commit turned out to be wrong:

 Repository                 Staging area               Commit
 report.md
 mental_health_survey.csv ─────▶ mental_health_survey.csv ─────▶ mental_health_survey.csv  (wrong data!)
 summary_statistics.csv
 funding_sources.txt

The commit cannot simply be deleted from a shared history. Instead, Git can record a new commit that cancels it.

Reverting a commit: git revert

git revert restores the repository to the state it was in before a given commit, by creating a new commit that reverses that commit's changes:

  • It reinstates the previous versions of the files and makes a commit.
  • It restores all files updated in the given commit.
  • The commit to revert is given by its hash (a845edcb, ebe93178, ...) or a HEAD reference (HEAD, HEAD~1, ...).
git revert HEAD   # undo the most recent commit

The original commit stays in the history, followed by the revert commit. Nothing is erased, so the history still shows what happened and when.

The commit message editor

By default, git revert opens a text editor with a prepared commit message:

Revert "Adding fresh data for the survey."

This reverts commit 7f71eadea60bf38f53c8696d23f8314d85342aaf.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch main
# Changes to be committed:
#   modified:   data/mental_health_survey.csv
#

In the course environment, the editor is nano:

Action nano keys
Save Ctrl + O, then Enter
Exit Ctrl + X

After you exit, Git creates the commit:

[main 7d11f79] Revert "Adding fresh data for the survey."
 Date: Tue Jul 30 14:17:56 2024 +0000
 1 file changed, 3 deletions(-)

Which editor opens on your system?

The editor is not always nano. Git uses the editor configured in core.editor, or the GIT_EDITOR, VISUAL, or EDITOR environment variables, with a system default as fallback (often vi). In vi/vim, save and quit with Esc then :wq and Enter. To use nano for Git: git config --global core.editor nano. Source: git var, GIT_EDITOR.

git revert options

Command Effect
git revert --no-edit HEAD Reverts and commits with the default message, without opening the editor
git revert -n HEAD Reverts the changes in the files and the staging area, without making a commit (-n = --no-commit)

With -n, the reverted files are left staged: you can check them, adjust them, and then commit yourself with git commit -m "...". Options can also be written after the commit (git revert HEAD --no-edit), as in the course summary.

Reverting a single file

git revert works on commits, not on individual files: it undoes every file changed by the commit. To bring back one file from an earlier commit, use git checkout with a commit reference, --, and the file name:

git checkout HEAD~1 -- report.md
Part Meaning
HEAD~1 The commit to take the file from (a hash also works)
-- Separates the commit from the file paths, so that a file name is never mistaken for a commit or branch name
report.md The file to restore

Checking the checkout

The file is restored in the working directory and staged:

$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
    modified:   report.md

Commit it to record the restored version:

$ git commit -m "Checkout previous version of report.md"
[main daa6c87] Checkout previous version of report.md
 1 file changed, 1 deletion(-)

This overwrites your local changes

git checkout <commit> -- <file> replaces the file in the working directory without asking. Any uncommitted edits to that file are lost. Commit or copy them first.

Modern equivalent: git restore --source

Since Git 2.23, the same operation can be written with git restore, which was introduced for restoring files:

git restore --source=HEAD~1 --staged --worktree report.md

--source selects the commit, and --staged --worktree restores both the staging area and the working directory, like the checkout form above. Source: git restore documentation. The git revert documentation also recommends git restore --source for extracting files as they were in another commit.

Unstaging files

Staging a file does not commit it: you can still take it out of the staging area. In the course scenario, report.md, mental_health_survey.csv, and summary_statistics.csv were staged, but summary_statistics.csv was not ready:

 Repository                    Staging area
 report.md               ───▶  report.md
 mental_health_survey.csv ───▶  mental_health_survey.csv
 summary_statistics.csv  ◀──▶  summary_statistics.csv   (taken back out)
 funding_sources.txt

Unstaging a single file

git restore --staged summary_statistics.csv

The file leaves the staging area, but your edits stay in the working directory: nothing is lost. You can keep editing it, then stage and commit it when it is ready:

git restore --staged summary_statistics.csv   # unstage
# ...edit the file...
git add summary_statistics.csv                # stage again
git commit -m "Adding age summary statistics"

git status reminds you of this command in its hint: (use "git restore --staged <file>..." to unstage).

Unstaging all files

To unstage everything, pass the current directory as the path:

git restore --staged .

Correction: a path is required

The course writes git restore --staged without any path to unstage all files. This does not work. git restore requires at least one path, and Git stops with:

fatal: you must specify path(s) to restore

Use git restore --staged . (the . means every file in the current directory and below). Run it from the repository root to unstage everything. Source: git restore synopsis, checked with Git 2.55.

Before the first commit

In a repository with no commits yet, git status suggests git rm --cached <file>... to unstage, because there is no previous commit to restore from.

git restore --staged versus git restore

Command Effect on the staging area Effect on your file
git restore --staged <file> Removes the staged change Kept: your edits stay
git restore <file> None Discards unstaged edits: the file returns to its staged (or committed) version

The second command is not part of the course, but it is easy to confuse with the first. It permanently discards uncommitted edits.

Summary

Command Result
git revert HEAD Revert all files from a given commit, with a new commit
git revert --no-edit HEAD Revert without opening a text editor
git revert -n HEAD Revert without making a new commit
git checkout HEAD~1 -- report.md Revert a single file to its version in the previous commit
git restore --staged report.md Remove a single file from the staging area
git restore --staged . Remove all files from the staging area

Common mistakes

  • Expecting git revert to delete a commit. It adds a new commit that cancels the old one; both stay in the history.
  • Using git revert to fix one file. It reverts every file of the commit. Use git checkout <commit> -- <file> instead.
  • Forgetting to commit after git checkout <commit> -- <file> or git revert -n. The changes are only staged until you commit.
  • Forgetting the path in git restore --staged. Use . to unstage everything.

Challenge: recover from mistakes

Objective: undo a bad commit, restore a single file from an earlier version, and fix a staging mistake.

Prerequisites and initial state: Git installed. The setup creates ~/git-practice/undo with three commits; the last one adds wrong data.

Setup:

mkdir -p ~/git-practice/undo && cd ~/git-practice/undo
git init -q -b main
git config user.name "Practice User"
git config user.email "[email protected]"
printf '# Mental Health in Tech Survey\nTODO: write executive summary.\n' > report.md
printf 'age,gender,treatment\n31,M,No\n' > survey.csv
git add . && git commit -q -m "Initial report and survey"
echo "TODO: cite funding sources." >> report.md
git commit -q -am "Add funding reminder"
echo "F,56,Yes" >> survey.csv
git commit -q -am "Add new participant"

Tasks:

  1. The latest commit added a malformed line to survey.csv. Undo it with a new commit, without opening an editor.
  2. Someone decides the funding reminder should not be in report.md. Restore only report.md to its version in the first commit, check the status, and commit with the message Restore initial report.
  3. Create a file notes.txt, modify survey.csv (add the line 19,M,Yes), and stage both. Then unstage only notes.txt, and commit the survey change with the message Add second participant.
  4. Stage notes.txt again, then unstage all files with a single command.

Expected result and verification:

  • git log --oneline shows, from newest to oldest: Add second participant, Restore initial report, Revert "Add new participant", Add new participant, Add funding reminder, Initial report and survey. The original bad commit is still there.
  • cat report.md no longer contains TODO: cite funding sources.
  • cat survey.csv ends with 19,M,Yes and does not contain F,56,Yes.
  • At the end, git status shows notes.txt as untracked and nothing staged. The file still exists.
Solution
# 1. Undo the latest commit with a new commit, no editor
git revert --no-edit HEAD
tail -1 survey.csv                 # 31,M,No: the bad line is gone

# 2. Restore one file from the first commit
git log --oneline                  # the first commit is now HEAD~3
git checkout HEAD~3 -- report.md   # or use the first commit's hash
git status                         # report.md under "Changes to be committed"
git commit -m "Restore initial report"

# 3. Unstage a single file
echo "draft" > notes.txt
echo "19,M,Yes" >> survey.csv
git add notes.txt survey.csv
git restore --staged notes.txt     # notes.txt is untracked again; its content is kept
git commit -m "Add second participant"

# 4. Unstage everything
git add notes.txt
git restore --staged .             # a path is required: "." means everything
git status                         # notes.txt untracked, nothing to commit
  • git revert adds Revert "Add new participant" on top of the history instead of deleting the bad commit.
  • After the revert there are four commits, so the first one is HEAD~3. Counting is error-prone: copying the hash from git log --oneline is often safer.
  • git checkout <commit> -- <file> stages the restored file but does not commit it.
  • git restore --staged only changes the staging area: notes.txt keeps its content in the working directory.
  • Equivalent for task 2 with the modern syntax: git restore --source=HEAD~3 --staged --worktree report.md.

Clean up when you are done: rm -rf ~/git-practice/undo.