如何git重置 – 哈哈一个子目录?

更新 :这将工作更直观的Git 1.8.3,看到我自己的答案 。

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

  • 我可以做git checkout . ,但git结帐。 添加稀疏结帐排除的目录

  • git reset --hard ,但不会让我做一个子目录:

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

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

  • 我可以使用git diff subdir | patch -p1 -R来修改当前状态 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 ab; do for g in abc; 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 

注意( Dan Fabulich 评论 )说:

  • git checkout -- <path>不会执行硬重置:它使用暂存的内容replace正在工作的树内容。
  • git checkout HEAD -- <path>git checkout HEAD -- <path>进行硬重置,用HEAD提交的版本replace索引和工作树。

正如Ajedi32所回答的,两个结帐表格都不会删除目标版本中已删除的文件
如果在HEAD中不存在额外的文件, git checkout HEAD -- <path>将不会删除它们。

但是,结帐可以尊重一个git update-index --skip-worktree (对于那些你想忽略的目录),如“ 为什么排除的文件不断重新出现在我的git稀疏结账中? ”中提到的那样。

根据Git开发者Duy Nguyen的说法,他善意地实现了这个特性和一个兼容性开关 ,下面的Git 1.8.3就像预期的那样工作 :

 git checkout -- a 

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

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

尝试改变

 git checkout -- a 

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

从1.7.0版本开始,Git的ls-files 将会使用skip-worktree标志 。

运行你的testing脚本(通过一些小的调整,改变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 

使用build议的checkout更改输出运行testing脚本:

 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 

对于简单地放弃更改的情况,其他答案所build议的git checkout -- path/git checkout HEAD -- path/ commands的效果很好。 但是,如果希望将目录重置为除HEAD之外的修订版本,则该解决scheme存在一个严重的问题:它不会删除目标修订中已删除的文件。

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

git diff --cached commit -- subdir | git apply -R --index

这是通过find目标提交和索引之间的差异,然后将该差异反向应用于工作目录和索引。 基本上,这意味着它使索引的内容与您指定的修订版本的内容匹配。 git diff接受一个path参数的事实允许你限制这个效果到一个特定的文件或目录。

由于这个命令相当长,我打算经常使用它,我已经设置了一个别名,我命名为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 

重置通常会改变一切,但是你可以使用git stash来select你想要保留的东西。 正如你所提到的, stash不直接接受path,但仍然可以使用--keep-index标志保持特定的path。 在你的例子中,你会隐藏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不重新创build)。

这种方法具有非常灵活的附加好处。 你可以得到如同你想要添加特定的文件,但不是其他目录中的细粒度。

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

  1. 切换到主分支并复制要重置的子目录。
  2. 现在切换回您的function分支,并用您在第1步中创build的副本replace子目录。
  3. 提交更改。

干杯。 您只需手动重置您的function分支中的子目录,以便与主分支相同!

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