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 checkoutThere 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
For the case of simply discarding changes, the
git checkout -- path/
orgit checkout HEAD -- path/
commands suggested by other answers work great. However, when you wish to reset a directory to a revision other than HEAD, that solution has a significant problem: it doesn't remove files which were deleted in the target revision.So instead, I have begun using the following command:
This works by finding the diff between the target commit and the index, then applying that diff in reverse to the working directory and index. Basically, this means that it makes the contents of the index match the contents of the revision you specified. The fact that
git diff
takes a path argument allows you to limit this effect to a specific file or directory.Since this command fairly long and I plan on using it frequently, I have set up an alias for it which I named
reset-checkout
:You can use it like this:
Or just:
A reset will normally change everything, but you can use
git stash
to pick what you want to keep. As you mentioned,stash
doesn't accept a path directly, but it can still be used to keep a specific path with the--keep-index
flag. In your example, you would stash the b directory, then reset everything else.This gets you to a point where the last part of your script will output this:
I believe this was the target result (b remains modified, a/* files are back, a/c is not recreated).
This approach has the added benefit of being very flexible; you can get as fine-grained as you want adding specific files, but not other ones, in a directory.
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 theHEAD
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.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?".If the size of the subdirectory is not particularly huge, AND you wish to stay away from the CLI, here's a quick solution to manually reset the sub-directory:
Cheers. You just manually reset a sub-directory in your feature branch to be same as that of master branch !!
I'm going to offer a terrible option here, since I have no idea how to do anything with git except
add
commit
andpush
, here's how I "reverted" a subdirectory:I started a new repository on my local pc, reverted the whole thing to the commit I wanted to copy code from and then copied those files over to my working directory,
add
commit
push
et voila. Don't hate the player, hate Mr Torvalds for being smarter than us all.Try changing
to
Since version 1.7.0, Git's
ls-files
honors the skip-worktree flag.Running your test script (with some minor tweaks changing
git commit
... togit commit -q
andgit status
togit status --short
) outputs:Running your test script with the proposed
checkout
change outputs: