247

更新²:使用 Git 2.23(2019 年 8 月),有一个新命令git restore可以执行此操作,请参阅接受的答案

更新:从 Git 1.8.3 开始,这将更直观地工作,请参阅我自己的答案

想象以下用例:我想删除 Git 工作树的特定子目录中的所有更改,而保留所有其他子目录不变。

此操作的正确 Git 命令是什么?

下面的脚本说明了这个问题。在注释下方插入正确的命令How to make files——当前命令将恢复a/c/ac应该被稀疏检出排除的文件。请注意,我不想显式恢复a/aand 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
4

9 回答 9

248

使用 Git 2.23(2019 年 8 月),您将拥有新命令git restore(也在此处介绍

git restore --source=HEAD --staged --worktree -- aDirectory
# or, shorter
git restore -s@ -SW  -- aDirectory

这将用内容替换索引和工作树HEAD,就像reset --hard一个特定的路径一样。


原始答案(2013)

请注意(如Dan Fabulich评论):

  • git checkout -- <path>不进行硬重置:它将工作树内容替换为暂存内容。
  • git checkout HEAD -- <path>对路径进行硬重置,用HEAD提交中的版本替换索引和工作树。

正如Ajedi32回答的那样,两种结帐表格都不会删除在目标修订中删除的文件。 如果工作树中有 HEAD 中不存在的额外文件,则不会删除它们。
git checkout HEAD -- <path>

注意:使用git checkout --overlay HEAD -- <path>(Git 2.22, Q1 2019),会删除出现在索引和工作树中但不在其中的<tree-ish>文件,以使它们<tree-ish>完全匹配。

但是该结帐可以尊重 a git update-index --skip-worktree(对于您想要忽略的那些目录),如“为什么排除的文件不断重新出现在我的 git sparse 结帐? ”中所述。

于 2013-03-14T08:51:14.530 回答
138

根据 Git 开发人员 Duy Nguyen 的说法,他善意地实现了该功能和兼容性开关,从Git 1.8.3 开始,以下工作按预期工作:

git checkout -- a

a您要硬重置的目录在哪里)。可以通过以下方式访问原始行为

git checkout --ignore-skip-worktree-bits -- a
于 2013-05-16T14:00:27.297 回答
32

尝试改变

git checkout -- a

git checkout -- `git ls-files -m -- a`

从 1.7.0 版本开始,Git采用了 skip-worktree 标志ls-files

运行你的测试脚本(有一些小的调整改变git commit... 到git commit -qgit statusgit 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
于 2013-03-16T22:26:45.180 回答
18

对于简单地丢弃更改的情况,其他答案建议的git checkout -- path/orgit 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
于 2015-01-16T16:45:17.727 回答
6

我将在这里提供一个糟糕的选择,因为我不知道如何用 git 做任何事情,除了add commitand push,这就是我“恢复”子目录的方式:

我在本地 pc 上启动了一个新的存储库,将整个内容还原为我想要从中复制代码的提交,然后将这些文件复制到我的工作目录中,add commit push等等。不要恨玩家,恨 Torvalds 先生比我们都聪明。

于 2018-09-13T23:21:27.123 回答
4

重置通常会改变一切,但您可以使用它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 未重新创建)。

这种方法的另一个好处是非常灵活。您可以根据需要在目录中添加特定文件,但不能添加其他文件。

于 2013-03-16T22:26:00.547 回答
4

如果子目录的大小不是特别大,并且您希望远离 CLI,这里有一个手动重置子目录的快速解决方案:

  1. 切换到 master 分支并复制要重置的子目录。
  2. 现在切换回您的功能分支并将子目录替换为您刚刚在步骤 1 中创建的副本。
  3. 提交更改。

干杯。您只需手动将功能分支中的子目录重置为与主分支相同!

于 2017-07-07T18:37:32.170 回答
1

Ajedi32答案是我一直在寻找的,但是对于某些提交,我遇到了这个错误:

error: cannot apply binary patch to 'path/to/directory' without full index line

可能是因为该目录的某些文件是二进制文件。将“--binary”选项添加到 git diff 命令修复它:

git diff --binary --cached commit -- path/to/directory | git apply -R --index
于 2017-01-03T14:55:34.947 回答
0

关于什么

subdir=thesubdir
for fn in $(find $subdir); do
  git ls-files --error-unmatch $fn 2>/dev/null >/dev/null;
  if [ "$?" = "1" ]; then
    continue;
  fi
  echo "Restoring $fn";
  git show HEAD:$fn > $fn;
done 
于 2020-03-19T14:23:18.567 回答