【问题标题】:How to git reset --hard a subdirectory?如何 git reset --hard 一个子目录?
【发布时间】:2013-03-02 12:32:16
【问题描述】:

更新²:在 Git 2.23(2019 年 8 月)中,有一个新命令 git restore 可以执行此操作,请参阅 accepted answer

更新:这将从 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

【问题讨论】:

  • git stash && git stash drop 怎么样?
  • git checkout -- /path/to/subdir/ 呢?
  • @CharlesB: git stash 不接受路径参数...
  • @iberbeu:不。还将添加被稀疏结帐排除的文件。
  • @CharlesBailey:那为什么会有一个单选按钮显示“从可靠和/或官方来源寻找答案”。在赏金对话框中?不是我自己写的!还可以尝试谷歌搜索“git reset subdirectory”(不带引号)并查看前 3 个位置的内容。当然,发往 kernel.org 邮件列表的消息会更难找到。 -- 另外,我还不清楚这种行为是错误还是功能。

标签: git reset git-reset sparse-checkout


【解决方案1】:

使用 Git 2.23(2019 年 8 月),您拥有 new command git restore(也称为 presented here

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

这将用HEAD 内容替换索引和工作树,就像reset --hard 那样,但是对于特定路径。


原始答案(2013 年)

请注意(commentedDan Fabulich):

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

作为Ajedi32answered,两种结帐表单不删除在目标修订中删除的文件
如果工作树中有 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 checkout HEAD -- . 之后,被稀疏检出排除的文件重新出现。 git update-index --skip-worktree 应该做什么?
  • @krlmlr skip-worktree 或 assume-unchanged 是尝试在索引中为 git 设置“不可见”条目的两种方式:fallengamer.livejournal.com/93321.htmlstackoverflow.com/q/13630849/6309stackoverflow.com/a/6139470/6309
  • @krlmlr 这些链接只是供您尝试查看结帐是否仍会恢复这些条目(一旦它们被标记为“跳过工作树”)的指针。
  • 抱歉,这对于手头的任务来说太复杂了。我想要一个包容性的重置,而不是排他性的。在 Git 中真的没有很好的方法吗?
  • git checkout HEAD -- <path> 的行为似乎与硬重置不同。硬重置会删除路径中不再存在于给定修订版中的文件。根据我的经验(git 1.8.3.3),git checkout HEAD -- <path> 没有。
【解决方案2】:

根据好心实现the feature and a compatibility switch 的Git 开发人员Duy Nguyen 所说,以下工作正常as of Git 1.8.3

git checkout -- a

(其中a 是您要硬重置的目录)。可以通过

访问原始行为
git checkout --ignore-skip-worktree-bits -- a

【讨论】:

  • 感谢您跟进 Git 开发团队的努力,促成了 Git 的这一变化。
  • 请注意,在这种情况下,“a”表示您要还原的目录,因此如果您在要还原的目录中,则命令应为git checkout -- .,其中. 表示当前目录。
  • 我这边的一条评论是,您必须先使用 git reset -- a 取消暂存文件夹(其中 a 是您要重置的目录)
  • 如果你想 git reset --hard 整个 repo 不也是这样吗?
  • 如果您向该目录添加了任何新文件,请在之前执行rm -rf a
【解决方案3】:

尝试改变

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 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 不应该首先尊重“skip-worktree”位吗?
  • 快速查看checkout.ctree.c 并没有发现使用了skip-worktree 标志。
  • 这很简单,在实践中很有用,即使我必须为此命令设置一个 bash 别名。 Duy Nguyen 有 replied 给我的 Git 邮件列表,让我们看看是否会很快弹出一个更用户友好的替代方案。
【解决方案4】:

对于简单地丢弃更改的情况,其他答案建议的 git checkout -- path/git 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

【讨论】:

  • 我昨天看到了你的评论,今天做了实验。您的别名很有帮助。 +1
  • 这个选项与@​​VonC 在他的回答中提到的git checkout --overlay HEAD -- <path> 命令相比如何?
  • @EhteshChoudhury 请注意,git checkout --overlay HEAD -- <path> 尚未发布(Git 2.22 将于 2019 年第二季度发布)
【解决方案5】:

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

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

【讨论】:

    【解决方案6】:

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

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

    【讨论】:

    • 这很好,但除了a 之外,我必须要git add,对吗?在实践中听起来很困难。
    • @krlmlr 不是真的。您可以git add . 然后git reset a 添加除a 之外的所有内容。
    • @krlmlr 另外,值得注意的是git add 不会添加已删除的文件。因此,如果您只是恢复已删除的文件,git add . 将添加所有已修改的文件,但不会添加已删除的文件。
    【解决方案7】:

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

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

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

    【讨论】:

    • 我没有看到任何对此答案的投票,但有时这是保证成功的最简单途径。
    【解决方案8】:

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

    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
    

    【讨论】:

      【解决方案9】:

      怎么样

      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 
      

      【讨论】:

        猜你喜欢
        • 2018-01-13
        • 2019-09-02
        • 2010-09-05
        • 2011-02-09
        • 1970-01-01
        相关资源
        最近更新 更多