UPDATE²:在Git 2.23(2019年8月)中,有一个新的命令Git restore可以执行此操作,请参阅已接受的答案。
更新:从Git 1.8.3开始,这将更加直观地工作,请参阅我自己的答案。
想象一下下面的用例:我想去除Git工作树的特定子目录中的所有更改,而保持所有其他子目录不变。
我可以做git结帐。,但git结帐。添加被稀疏签出排除的目录
有git重置-很难,但它不让我做一个子目录:
> git重置——很难。
致命:不能对路径进行硬复位。
再次:为什么git不能按路径进行硬/软重置?
我可以使用git diff subdir | patch -p1 -R对当前状态进行反向修补,但这是一种相当奇怪的方式。
这个操作应该使用什么Git命令?
下面的脚本说明了这个问题。在How to make files注释下面插入适当的命令——当前命令将恢复稀疏签出应该排除的文件a/c/ac。注意,我不想显式地恢复a/a和a/b,我只“知道”a,并想恢复下面的所有内容。编辑:我也不“知道”b,或者哪些其他目录与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
在Git 2.23(2019年8月)中,你有了新的命令Git restore(也在这里介绍)
git restore --source=HEAD --staged --worktree -- aDirectory
# or, shorter
git restore -s@ -SW -- aDirectory
这将用HEAD内容替换索引和工作树,就像重置一样——很难,但针对的是特定的路径。
原答案(2013)
请注意(正如Dan Fabulich所评论的):
Git checkout——<path>不做硬重置:它将工作树内容替换为阶段性内容。
>对一个路径进行了硬重置,将索引和工作树替换为HEAD提交的版本。
正如Ajedi32所回答的,这两个签出表单都不会删除在目标修订中删除的文件。
如果在工作树中有HEAD中不存在的额外文件,git签出HEAD—<path>将不会删除它们。
注意:使用git checkout——overlay HEAD——<path> (git 2.22, Q1 2019),出现在索引和工作树中,但不在<树状>中的文件将被删除,以使它们与<树状>完全匹配。
但是签出可以尊重git update-index -skip-worktree(用于那些你想忽略的目录),正如“为什么排除的文件不断出现在我的git稀疏签出中?”中提到的那样。
重置通常会改变一切,但你可以使用git stash来选择你想保留的东西。正如您所提到的,stash不直接接受路径,但仍然可以使用——keep-index标记来保存特定的路径。在您的示例中,您将保存b目录,然后重置其他所有内容。
# How to make files a/* reappear without changing b and without recreating a/c?
git add b #add the directory you want to keep
git stash --keep-index #stash anything that isn't added
git reset #unstage the b directory
git stash drop #clean up the stash (optional)
这让你到了一个点,你的脚本的最后一部分将输出:
After checkout:
# On branch master
# Changes not staged for commit:
#
# modified: b/a/ba
#
no changes added to commit (use "git add" and/or "git commit -a")
a/a/aa
a/b/ab
b/a/ba
我相信这是目标结果(b仍然被修改,a/*文件返回,a/c没有重新创建)。
这种方法具有非常灵活的额外好处;您可以在目录中添加特定的文件,但不能添加其他文件。
试着改变
git checkout -- a
to
git checkout -- `git ls-files -m -- a`
从1.7.0版本开始,Git的ls-files支持跳跃式工作树标志。
运行你的测试脚本(用一些小的调整改变git提交…git commit -q和git status到git status——short)输出:
Initialized empty Git repository in /home/user/repo/.git/
After read-tree:
a/a/aa
a/b/ab
b/a/ba
After modifying:
b/a/ba
D a/a/aa
D a/b/ab
M b/a/ba
After checkout:
M b/a/ba
a/a/aa
a/c/ac
a/b/ab
b/a/ba
使用建议的签出更改输出运行测试脚本:
Initialized empty Git repository in /home/user/repo/.git/
After read-tree:
a/a/aa
a/b/ab
b/a/ba
After modifying:
b/a/ba
D a/a/aa
D a/b/ab
M b/a/ba
After checkout:
M b/a/ba
a/a/aa
a/b/ab
b/a/ba
对于简单地丢弃更改的情况,其他答案建议的git checkout——path/或git checkout HEAD——path/命令效果很好。然而,当您希望将目录重置为HEAD以外的修订时,该解决方案存在一个重大问题:它不能删除在目标修订中删除的文件。
因此,我开始使用以下命令:
git diff——cached commit——subdir | git apply -R——index
这是通过查找目标提交和索引之间的差异,然后将该差异反向应用到工作目录和索引来实现的。基本上,这意味着它使索引的内容与您指定的修订的内容相匹配。git diff接受路径参数的事实允许您将此效果限制到特定的文件或目录。
因为这个命令相当长,我计划经常使用它,我已经为它设置了一个别名,我命名为reset-checkout:
git config --global alias.reset-checkout '!f() { git diff --cached "$@" | git apply -R --index; }; f'
你可以这样使用它:
git reset-checkout 451a9a4 -- path/to/directory
或者是:
git reset-checkout 451a9a4