更新:从Git 1.8.3开始,这将更直观地工作,请参阅my own answer。
想象一下以下用例:我想摆脱我的Git工作树的特定子目录中的所有更改,使所有其他子目录保持不变。
git checkout .
,但git checkout . adds directories excluded by sparse checkoutgit reset --hard
,但它不会让我为子目录这样做:
> git reset --hard .
fatal: Cannot do hard reset with paths.
再次:Why git can't do hard/soft resets by path?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
注意(由commented作为Dan Fabulich):
git checkout -- <path>
不进行硬重置:它用分段内容替换工作树内容。git checkout HEAD -- <path>
对路径进行硬重置,用HEAD
commit中的版本替换索引和工作树。作为answered的Ajedi32,两个结账表单都不会删除目标修订版中删除的文件。
如果工作树中有额外的文件在HEAD中不存在,则git checkout HEAD -- <path>
不会删除它们。
注意:使用git checkout --overlay HEAD -- <path>
(Git 2.22, Q1 2019),删除出现在索引和工作树中但不在<tree-ish>
中的文件,以使它们与<tree-ish>
完全匹配。
但是,结帐可以尊重git update-index --skip-worktree
(对于那些你想忽略的目录),如“Why do excluded files keep reappearing in my git sparse checkout?”中所述。
根据Git开发人员Duy Nguyen(善于实施the feature and a compatibility switch),以下工作如预期as of Git 1.8.3:
git checkout -- a
(其中a
是您要硬重置的目录)。可以通过访问原始行为
git checkout --ignore-skip-worktree-bits -- a
尝试改变
git checkout -- a
至
git checkout -- `git ls-files -m -- a`
从版本1.7.0开始,Git的ls-files
honors the skip-worktree flag。
运行你的测试脚本(通过一些小的调整改变git commit
...到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
使用建议的checkout
更改输出运行测试脚本:
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
采用路径参数这一事实允许您将此效果限制为特定文件或目录。
由于这个命令相当长,我打算经常使用它,我已经为它设置了一个别名,我命名为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
重置通常会改变一切,但您可以使用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 / c不重新创建)。
这种方法具有非常灵活的额外好处;您可以在目录中添加特定文件,而不是其他文件。
我将在这里提供一个可怕的选项,因为我不知道如何用git做任何事情,除了add
commit
和push
,这里是我如何“恢复”一个子目录:
我在我的本地电脑上开始了一个新的存储库,将整个事情还原到我想要复制代码的提交,然后将这些文件复制到我的工作目录,add
commit
push
et voila。不要讨厌玩家,讨厌Torvalds先生比我们所有人更聪明。
如果子目录的大小不是特别大,并且您希望远离CLI,这里是手动重置子目录的快速解决方案:
干杯。您只需手动将功能分支中的子目录重置为与主分支相同的子目录!