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 aHEADreference (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 revertto delete a commit. It adds a new commit that cancels the old one; both stay in the history. - Using
git revertto fix one file. It reverts every file of the commit. Usegit checkout <commit> -- <file>instead. - Forgetting to commit after
git checkout <commit> -- <file>orgit 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:
- The latest commit added a malformed line to
survey.csv. Undo it with a new commit, without opening an editor. - Someone decides the funding reminder should not be in
report.md. Restore onlyreport.mdto its version in the first commit, check the status, and commit with the messageRestore initial report. - Create a file
notes.txt, modifysurvey.csv(add the line19,M,Yes), and stage both. Then unstage onlynotes.txt, and commit the survey change with the messageAdd second participant. - Stage
notes.txtagain, then unstage all files with a single command.
Expected result and verification:
git log --onelineshows, 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.mdno longer containsTODO: cite funding sources.cat survey.csvends with19,M,Yesand does not containF,56,Yes.- At the end,
git statusshowsnotes.txtas 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 revertaddsRevert "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 fromgit log --onelineis often safer. git checkout <commit> -- <file>stages the restored file but does not commit it.git restore --stagedonly changes the staging area:notes.txtkeeps 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.