如何git reset --hard一个子目录?

时间:2013-03-14 08:40:36

标签: git reset git-reset sparse-checkout

  

更新:从Git 1.8.3开始,这将更加直观,请参阅my own answer

想象一下以下用例:我想摆脱我的Git工作树的特定子目录中的所有更改,保留所有其他子目录。

此操作的Git命令是什么?

下面的脚本说明了这个问题。在How to make files注释下面插入适当的命令 - 当前命令将恢复应该由稀疏结账排除的文件a/c/ac。请注意,我想要明确还原a/aa/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

9 个答案:

答案 0 :(得分:112)

注意(commentedDan Fabulich):

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

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?”中所述。

答案 1 :(得分:100)

根据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

答案 2 :(得分:27)

尝试更改

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 -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

答案 3 :(得分:17)

对于简单地放弃更改的情况,其他答案建议的git checkout -- path/git checkout HEAD -- path/命令效果很好。但是,当您希望将目录重置为HEAD以外的修订时,该解决方案存在严重问题:它不会删除目标修订中删除的文件。

相反,我开始使用以下命令:

  

git diff --cached commit -- subdir | {{3 }} -R --index

这可以通过查找目标提交和索引之间的差异,然后将该diff反向应用于工作目录和索引来实现。基本上,这意味着它使索引的内容与您指定的修订的内容相匹配。 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

答案 4 :(得分: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 / c没有重新创建)。

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

答案 5 :(得分:3)

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

  1. 切换到主分支并复制要重置的子目录。
  2. 现在切换回您的功能分支,并将子目录替换为您在步骤1中创建的副本。
  3. 提交更改。
  4. 干杯。您只需手动重置功能分支中的子目录,使其与主分支的子目录相同!!

答案 6 :(得分:3)

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

我在本地PC上启动了一个新的存储库,将整个内容还原为我要从中复制代码的提交,然后将这些文件复制到了我的工作目录add commit {{1 }}等等。不要恨球员,不要恨托瓦尔兹先生比我们所有人都聪明。

答案 7 :(得分:1)

Ajedi32answer是我正在寻找的,但是对于某些提交,我遇到了这个错误:

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

可能是因为该目录的某些文件是二进制文件。在git diff命令中添加'--binary'选项修复了它:

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

答案 8 :(得分: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