The git doc states Show archive.org snapshot on the difference of these two commands:
- git-restore[1] is about restoring files in the working tree from either the index or another commit. This command does not update your branch. The command can also be used to restore files in the index from another commit.
- git-reset[1] is about updating your branch, moving the tip in order to add or remove commits from the branch. This operation changes the commit history.
git reset can also be used to restore the index, overlapping with git restore.
Background
git restore
Regarding the
git doc
Show archive.org snapshot
restore
does by default:
The command can also be used to restore the content in the index with --staged, or restore both the working tree and the index with --staged --worktree.
By default, if --staged is given, the contents are restored from HEAD, otherwise from the index.
What does this mean?
-
git restore --staged <path>
reverts changes from the staging area to match the content of HEAD. -
git restore --worktree <path>
reverts changes from the working tree to match the index from the current revision of HEAD including all files added to the index in previous commits. -
git restore --staged --worktree <path>
discards local changes and overwrites both to match the current HEAD.
But what about --source
?
As the doc states
Use --source to restore from a different commit.
it is a hint that it acts as if it would restore changes from the checked out state of that commit.
The git doc Show archive.org snapshot also states on the source option:
If not specified, the contents are restored from HEAD if --staged is given, otherwise from the index.
This means it will use the index and the HEAD as if that commit was checked out, such that git restore --staged --worktree --source <commit hash>
uses the index and the HEAD of the checked out state:
-
worktree
: For the working directory it will simply restore the working directory to the given commit. -
staged:
For the staging area it will restore the changes within the staging area and apply the diff to working directory to keep the changes of the source commit.The combination of
--staged --source
is especially unique about this command, because it allows you to compare the index (or the staging area) of two commits, inspect the differences and apply or revert them selectively for your current need of change.For a more detailed example on this behavior compared to the
--worktree
option you can have a look on the example within the addendum of this card.
git reset
git reset <commit hash>
moves HEAD to a specified commit and resets the state of the repository to that commit. The different options let you decide how to deal with changed differences compared to the command, i.e. reverting the index/working directory or not.
--mixed (default)
Resets to the state of that commit by setting the working tree to the state of the given commit and discarding changes in the staging area. It keeps changes in the working directory untouched.
--soft
Resets to the state of that commit by setting the staging area to the state of that commit and keeping changes in the working and in the index untouched.
--hard
Resets to the state of the given commit by resetting all introduced changes in the working tree and the staging area.
Difference
So as we've seen both can be used to revert revisions within the index and the working directory. Here is a short overview of the main differences:
- It is especially unique about
git restore
that you can revert the working directory compared to a given index. -
git restore
does not move theHEAD
, this is especially useful if changing theHEAD
may lead to side effects, since commits may be lost after resetting and pushing.- If you use
git reset
with a path it also does not move theHEAD
, so in this case you can use these commands quite similar.
- If you use
Also see
- Card on git restore
- Card Git basics: checkout vs. reset
- Manage git work tree and index using git restore command Show archive.org snapshot
- What is the git restore command and is the difference to git reset Show archive.org snapshot
Addendum
Example on git restore
with --source
for the usage --staged
compared to --worktree
To get a better idea, why this behavior might be useful I will add an example for both commands.
Let's say we refactored the code to match a simple default controller implementation and we lost some controller logic due to refactoring. We might want to revert some of the changes or at least compare them with a previous revision.
Let's say we have the following staged changes in our repository:
git status
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: app/controllers/actors_controller.rb
modified: app/controllers/movies/merges_controller.rb
modified: app/controllers/movies_controller.rb
modified: app/controllers/roles_controller.rb
modified: app/controllers/sessions_controller.rb
modified: app/controllers/users_controller.rb
git diff
diff --git a/app/controllers/movies/merges_controller.rb b/app/controllers/movies/merges_controller.rb
def merge_params
- params.require(:movie_merge).permit(:source_movie_id, :target_movie_id)
-
- rescue ActionController::ParameterMissing
- {}
+ merge_params = params[:movie_merge]
+ merge_params ? merge_params.permit(:source_movie_id, :target_movie_id) : {}
end
end
diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb
def create
- build_movie
- save_movie or render :new
+ if @movie.save
+ redirect_to @movie
+ else
+ build_showtimes
+ render :new
+ end
end
.... # more diffs
git restore --staged
Now when we want to revert HEAD to a commit before the changes were introduced to overwrite the staged changes:
git restore --staged --source HEAD~1 app/controllers
git status
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: app/controllers/application_controller.rb
modified: app/controllers/movies/merges_controller.rb
modified: app/controllers/movies_controller.rb
modified: app/controllers/roles_controller.rb
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: app/controllers/application_controller.rb
modified: app/controllers/movies_controller.rb
So the first difference we can see, is that some changes are within the index and some are within the working directory. This seems strange it first, because we expected the command to restore the staging area. Let's dig a little bit deeper here.
Now the staging area contains all staged changes without conflicts and applies introduced changes from HEAD~1 by overwriting the staging area:
git diff --cached
diff --git a/app/controllers/movies/merges_controller.rb b/app/controllers/movies/merges_controller.rb
def merge_params
- params.require(:movie_merge).permit(:source_movie_id, :target_movie_id)
-
- rescue ActionController::ParameterMissing
- {}
+ merge_params = params[:movie_merge]
+ merge_params ? merge_params.permit(:source_movie_id, :target_movie_id) : {}
end
end
diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb
+ def create
+ build_movie
+ save_movie or render :new
+ end
.... # more diffs
But what about the working directory?
git diff
diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb
- def create
- build_movie
- save_movie or render :new
- end
+ def create
+ build_movie
+ if @movie.save
+ redirect_to @movie
+ else
+ build_showtimes
+ render :new
+ end
+ end
.... # more diffs
So it contains all changes which had a conflict in applying HEAD~1
, but keeps the state of HEAD
.
git restore --worktree
Let's say we have the same state, but all changes now are within the working directory and not within the staging area:
git status
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: app/controllers/movies/merges_controller.rb
modified: app/controllers/movies_controller.rb
modified: app/controllers/roles_controller.rb
Now we apply git restore
:
git restore --worktree --source HEAD~1 app/controllers
git status
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: app/controllers/application_controller.rb
modified: app/controllers/movies/merges_controller.rb
modified: app/controllers/movies_controller.rb
modified: app/controllers/roles_controller.rb
So the first difference we see that all files are now within the working directory.
If we inspect the actual differences:
git diff
diff --git a/app/controllers/movies/merges_controller.rb b/app/controllers/movies/merges_controller.rb
def merge_params
- params.require(:movie_merge).permit(:source_movie_id, :target_movie_id)
-
- rescue ActionController::ParameterMissing
- {}
+ merge_params = params[:movie_merge]
+ merge_params ? merge_params.permit(:source_movie_id, :target_movie_id) : {}
end
end
diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb
+ def create
+ build_movie
+ save_movie or render :new
+ end
- def create
- build_movie
- if @movie.save
- redirect_to @movie
- else
- render :new
- end
- end
.... # more diffs
We can see that it directly restores all changes made within the working directory to the state of HEAD~1
.