如何 git 重置——硬子目录

UPDATE2 : 对于 Git 2.23(2019年8月) ,有一个新的命令 git restore可以完成这项工作,请参阅 接受的答案

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

设想下面的用例: 我想要去掉 Git 工作树的特定子目录中的所有更改,而保留所有其他子目录的完整性。

  • 我可以做 git checkout .,但是 < a href = “ https://stackoverflow./questions/15251638/git-checkout-add-directory-) > git checkout.  增加稀疏 checkout 排除的目錄

  • git reset --hard,但是它不允许我为子目录这样做:

      > git reset --hard .
    fatal: Cannot do hard reset with paths.
    

    再说一遍: 为什么 git 不能通过路径进行硬/软重置?

  • 我可以使用 git diff subdir | patch -p1 -R反向修补当前状态,但这是一种相当奇怪的方法。

这个操作的正确 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
221130 次浏览

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

注意(如评论 by 丹Fabulich):

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

作为回答Ajedi32,两个结帐表单都是不要删除目标修订中已删除的文件
如果在工作树中有HEAD中不存在的额外文件,git checkout HEAD -- <path>将不会删除它们

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

但是签出可以尊重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

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

从1.7.0版本开始,Git的ls-files 尊重跳过工作树标志. exe

运行测试脚本(通过一些小调整改变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

Git开发人员Duy Nguyen好心地实现了该特性和兼容性开关,根据他的说法,下面的从Git 1.8.3开始工作正常:

git checkout -- a

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

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

对于简单地丢弃更改的情况,其他答案建议的git checkout -- path/git checkout HEAD -- path/命令非常有效。然而,当您希望将目录重置为HEAD以外的修订时,该解决方案存在一个重大问题:它不能删除在目标修订中删除的文件。

因此,我开始使用以下命令:

__abc8 __abc1 __abc9 __abc3 __abc10 __abc5 __abc11 __abc7

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

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

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

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

  1. 切换到主分支,复制要重置的子目录。
  2. 现在切换回你的特性分支,用你在第1步中创建的副本替换子目录。
  3. 提交更改。

欢呼。您只需手动重置您的特性分支中的子目录,使其与主分支的子目录相同!

我将在这里提供一个糟糕的选择,因为我不知道如何用Git做任何事情,除了add commitpush。以下是我“回复”的方式。子目录:

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

使用:

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

对于我来说,我将使用checkout:

首先,我删除我想重置的文件夹。

然后我将简单地使用checkout:

git checkout <branch> <path>

文件夹将被正确重置为我想要的分支。