UPDATE²: With Git 2.23 (August 2019), there's a new command
git restore
that does this, see the accepted answer.UPDATE: This will work more intuitively as of Git 1.8.3, see my own answer.
Imagine the following use case: I want to get rid of all changes in a specific subdirectory of my Git working tree, leaving all other subdirectories intact.
I can do git checkout .
, but git checkout . adds directories excluded by sparse checkout
There is git reset --hard
, but it won't let me do it for a subdirectory:
> git reset --hard .
fatal: Cannot do hard reset with paths.
I can reverse-patch the current state using git diff subdir | patch -p1 -R
, but this is a rather weird way of doing this.
What is the proper Git command for this operation?
The script below illustrates the problem. Insert the proper command below the How to make files
comment -- the current command will restore the file a/c/ac
which is supposed to be excluded by the sparse checkout. Note that I do not want to explicitly restore a/a
and a/b
, I only "know" a
and want to restore everything below. EDIT: And I also don't "know" b
, or which other directories reside on the same level as a
.
#!/bin/sh
rm -rf repo; git init repo; cd repo
for f in a b; do
for g in a b c; do
mkdir -p $f/$g
touch $f/$g/$f$g
git add $f/$g
git commit -m "added $f/$g"
done
done
git config core.sparsecheckout true
echo a/a > .git/info/sparse-checkout
echo a/b >> .git/info/sparse-checkout
echo b/a >> .git/info/sparse-checkout
git read-tree -m -u HEAD
echo "After read-tree:"
find * -type f
rm a/a/aa
rm a/b/ab
echo >> b/a/ba
echo "After modifying:"
find * -type f
git status
# How to make files a/* reappear without changing b and without recreating a/c?
git checkout -- a
echo "After checkout:"
git status
find * -type f
With Git 2.23 (August 2019), you have the new command git restore
git restore --source=HEAD --staged --worktree -- aDirectory
# or, shorter
git restore -s@ -SW -- aDirectory
That would replace both the index and working tree with HEAD
content, like an reset --hard
would, but for a specific path.
Original answer (2013)
Note (as commented by Dan Fabulich) that:
git checkout -- <path>
doesn't do a hard reset: it replaces the working tree contents with the staged contents. git checkout HEAD -- <path>
does a hard reset for a path, replacing both the index and the working tree with the version from the HEAD
commit.As answered by Ajedi32, both checkout forms don't remove files which were deleted in the target revision.
If you have extra files in the working tree which don't exist in HEAD, a git checkout HEAD -- <path>
won't remove them.
Note: With git checkout --overlay HEAD -- <path>
(Git 2.22, Q1 2019), files that appear in the index and working tree, but not in <tree-ish>
are removed, to make them match <tree-ish>
exactly.
But that checkout can respect a git update-index --skip-worktree
(for those directories you want to ignore), as mentioned in "Why do excluded files keep reappearing in my git sparse checkout?".