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

当前回答

Use:

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

其他回答

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

错误:不能将二进制补丁应用到'path/to/directory'没有完整的索引行

可能是因为目录中有些文件是二进制文件。在git diff命令中添加'——binary'选项可以解决这个问题:

git diff --binary --cached commit -- path/to/directory | git apply -R --index

试着改变

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 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没有重新创建)。

这种方法具有非常灵活的额外好处;您可以在目录中添加特定的文件,但不能添加其他文件。

Use:

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

Git开发人员Duy Nguyen好心地实现了该功能并进行了兼容性切换,根据他的说法,在Git 1.8.3中,以下工作正常进行:

git checkout -- a

(其中a是要硬重置的目录)。原始行为可以通过

git checkout --ignore-skip-worktree-bits -- a