git restore
Let's review the different use cases of the git restore command
The git restore
command is used to revert files in the working directory to the state they were in at a specific commit.
It was added to Git in version 2.23.0
and is intended to replace the git checkout
command for the use cases described below.
This article explains how the git restore
command helps to simplify the git checkout
command that has way too many usages.
The git restore
command does not modify any commits, so it can be safely used in a multi-user environment.
Please note that, as stated in the documentation, this command is experimental and its behavior may change.
initial state
Let’s begin with a file file.txt
in a clean repository:
alina
alina git:( main ) echo "A" > file.txt
alina git:( main ) git add file.txt
alina git:( main ) git commit -m 'A'
[main bc57535] A
1 file changed, 1 insertion(+)
create mode 100644 file.txt
alina git:( main ) █
Both the working directory and the index are clean and synchronized.
alina
alina git:( main ) git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
alina git:( main ) cat file.txt
A
alina git:( main ) █
restore before git add
Now, let’s make some changes to that file.
alina
alina git:( main ) echo "B" >> file.txt
alina git:( main ) cat file.txt
A
B
alina git:( main ) █
Running git status
will indicate that we are out of sync.
alina
alina git:( main ) git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
(use "git push" to publish your local commits)
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: file.txt
no changes added to commit (use "git add" and/or "git commit -a")
alina git:( main ) █
As shown by the output of git status
, the git restore
command will revert the file to the state it was in at the last commit.
alina
alina git:( main ) git restore file.txt
alina git:( main ) cat file.txt
A
alina git:( main ) █
The git status
command will confirm that we are once again in sync.
alina
alina git:( main ) git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
alina git:( main ) █
git restore after a git add and a git commit
Let’s modify the file again, but this time we will stage the changes and create a commit.
alina
alina git:( main ) echo "B" >> file.txt
alina git:( main ) git add file.txt
alina git:( main ) git commit -m "B" file.txt
[main ec94472] B
1 file changed, 1 insertion(+)
alina git:( main ) cat file.txt
A
B
alina git:( main ) █
Running git status
will show that we are in sync.
alina
alina git:( main ) git status
On branch main
Your branch is ahead of 'origin/main' by 2 commits.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
alina git:( main ) █
Now, let’s try using the git restore
command again:
alina
alina git:( main ) git restore file.txt
alina git:( main ) █
Nothing happens because the file is already in sync with the index.
alina
alina git:( main ) cat file.txt
A
B
alina git:( main ) █
The content of the index is as follows:
alina
alina git:( main ) git ls-files --stage
100644 0c50cb1098bda2e5c32fe2667cb64453434edc8f 0 README.md
100644 35d242ba79ae89ac695e26b3d4c27a8e6f028f9e 0 file.txt
alina git:( main ) █
And we can see that the hash of file.txt
matches the one in the index.
alina
alina git:( main ) git hash-object file.txt
35d242ba79ae89ac695e26b3d4c27a8e6f028f9e
alina git:( main ) █
git restore —staged
The git restore --staged
command will revert the file in the index to the state it was in at the last commit.
alina
alina git:( main ) git restore --staged file.txt
alina git:( main ) █
Our file in the working directory remains unchanged.
alina
alina git:( main ) cat file.txt
A
B
alina git:( main ) █
Since our changes were already committed, the index is also left unchanged.
alina
alina git:( main ) git status
On branch main
Your branch is ahead of 'origin/main' by 2 commits.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
alina git:( main ) █
alina
alina git:( main ) git ls-files --stage
100644 0c50cb1098bda2e5c32fe2667cb64453434edc8f 0 README.md
100644 35d242ba79ae89ac695e26b3d4c27a8e6f028f9e 0 file.txt
alina git:( main ) █
git restore with a source
The git restore
command can also be used with the --source
option to revert the file to a specific commit.
Let’s use HEAD~1
as the source, which refers to the commit before the last one. In that commit, file.txt
contained only one line of text.
The git restore --staged --source
command will revert the file in the index to the state it was in at the specified commit, leaving the working directory unaffected.
alina
alina git:( main ) git restore --staged --source HEAD~1 file.txt
alina git:( main ) █
As a result, git status
will now report that the working directory is out of sync with the index.
alina
alina git:( main ) cat file.txt
A
B
alina git:( main ) git status
On branch main
Your branch is ahead of 'origin/main' by 2 commits.
(use "git push" to publish your local commits)
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: file.txt
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: file.txt
alina git:( main ) █
And git diff
confirms that the file in the index has only one line of text:
alina
alina git:( main ) git diff
diff --git a/file.txt b/file.txt
index f70f10e..35d242b 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1,2 @@
A
+B
alina git:( main ) █
git restore —staged —worktree
Let’s re-add that file and commit it.
alina
alina git:( main ) git add file.txt
alina git:( main ) git commit -m "B2"
On branch main
Your branch is ahead of 'origin/main' by 2 commits.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
alina git:( main ) █
Returning to a clean index.
alina
alina git:( main ) git status
On branch main
Your branch is ahead of 'origin/main' by 2 commits.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
alina git:( main ) cat file.txt
A
B
alina git:( main ) █
Running the command git restore --staged --worktree --source HEAD~1 file.txt
will revert file.txt
in both the working directory and the index to the state it was in at the specified commit.
alina
alina git:( main ) git restore --staged --worktree --source HEAD~1 file.txt
alina git:( main ) █
However, the git status
command shows that the file is modified.
alina
alina git:( main ) git status
On branch main
Your branch is ahead of 'origin/main' by 2 commits.
(use "git push" to publish your local commits)
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: file.txt
alina git:( main ) █
Even though the file is in sync with the index.
alina
alina git:( main ) cat file.txt
A
alina git:( main ) git diff
alina git:( main ) █
To investigate further, let’s examine the content of the index:
alina
alina git:( main ) git ls-files --stage
100644 0c50cb1098bda2e5c32fe2667cb64453434edc8f 0 README.md
100644 f70f10e4db19068f79bc43844b49f3eece45c4e8 0 file.txt
alina git:( main ) █
And display the content of file.txt
in the index:
alina
alina git:( main ) git cat-file blob f70f10e4db19068f79bc43844b49f3eece45c4e8f70f10e4db19068f79bc43844b49f3eece45c4e8
A
alina git:( main ) █
We can observe that the content of the index is the same as the content of the file in the working directory.
alina
alina git:( main ) cat file.txt
A
alina git:( main ) █
My suspicion is that Git first compares the “modification time” on the file system, which is also stored in the index but not displayed. If the modification time differs between the index and the working directory, then the file is considered modified.
Hence, the file appears as modified even though its content matches the index.
alina
alina git:( main ) git add file.txt
alina git:( main ) █
git restore can be destructive
Suppose we commit the file file.txt
with the two lines A
and B
, and then we make local changes to the file.
alina
alina git:( main ) cat file.txt
1
2
alina git:( main ) █
If we use the git restore
command to revert it, it will be restored to the state it was in at the last commit, containing only A
and B
.
alina
alina git:( main ) git restore file.txt
alina git:( main ) cat file.txt
A
B
alina git:( main ) █
Be cautious as the git restore
command will delete any changes in your working directory!