如何在 `git stash -p` 中忽略已添加的补丁

16

想象这种情况:

# edit two files
git add -p // add hunks from one file

现在当你运行git stash -p命令时,它会再次询问你是否想通过git add -p选择刚刚选择的文件块进行存储。有没有办法配置git默认忽略这些已经添加的文件块呢?大多数情况下,我不想把已经添加过的东西暂存起来。


4
在执行 git stash 命令之前,你考虑过使用 git commit -m WIP 吗?这将把已暂存的内容转换成一个完整的提交,从而解决问题。在执行 git stash 后,使用命令 git reset --soft HEAD^ 可以让你回到之前的状态。 - user4815162342
@user4815162342 是的,但这对于一个非常常见的用例来说需要大量的打字工作。看起来我得在某个地方添加一个功能请求... - milianw
1
你不能为此创建一个 git alias 有什么原因吗?这必须是本地的根本原因吗? - merlin2011
我不明白你在什么情况下尝试这样做。你能用具体的例子来解释一下你正在做什么吗?请考虑一下,无论你处于什么情况,都可能有比你尝试的方法更好的处理方式。如果你能解释清楚这里真正发生了什么,那么帮助你就会容易得多。 - jthill
1
我认为只需要执行 git stash -k 即可实现你想要的功能,它会储藏所有内容并重置工作区到已索引状态,也就是你使用 git add [-p] 命令添加的所有更改。之后你可以对这些更改进行 git-clang-format,提交结果,然后执行 git stash pop 命令来恢复。所以说,你只需要执行 git stash -k; git clang-format; git stash pop 就好了。 - jthill
显示剩余4条评论
3个回答

11

在man手册中有一个类似的示例:

man git stash:

"Testing partial commits
You can use git stash save --keep-index when you want 
to make two or more commits out of the changes in the 
work tree, and you want to test each change before 
committing:

# ... hack hack hack ...
$ git add --patch foo            # add just first part to the index
$ git stash save --keep-index    # save all other changes to the stash"*

我可以确认:

如果您使用git stash -p(它意味着--keep-index),您仍然会被询问是否应该保存已经在索引中的更改(就像您所描述的那样)。

因此,似乎手册有些混淆,这也在其他地方提到:https://github.com/progit/progit2/issues/822

总结一下:

--keep-index(或暗示--keep-index-p)只是保留索引不变。已经暂存的更改仍然会被插入stash。根据我的理解,没有办法做到您所描述的事情。

或者,更精确地讲(再次来自手册):

With --patch, you can interactively select hunks from 
the diff between HEAD and the working tree to be stashed. 

The stash entry is constructed such that its index state 
is the same as the index state of your repository, and its 
worktree contains only the changes you selected interactively.

替代方案:

至少有三种方法可以实现你想要的(或多或少):

  • 不要在git stash中使用-p。将所有内容都存储起来(使用--keep-index和可能的--all,以确保您已经安全地隐藏了所有内容)。
  • 在藏之前提交您的暂存更改。这样,你就不会有一个与HEAD和工作树之间的差异,对于这些你想从stash中省略的更改。但是,如果您不确定是否要提交此更改怎么办?您随时可以稍后进行更改,并使用--amend更改现有提交。
  • 取消暂存您的更改(从索引中删除),然后进行藏。

2

好的,从评论中可以看出,需要一个存储所有尚未添加的更改(无论是在git add -p期间被排除还是尚未添加)的隐藏区。

这样做的原因是为了对暂存状态应用一些测试/潜在调整,然后再提交它。

这就是一个简单的git stash -k,像往常一样将所有内容都存储到隐藏区,但保留索引状态和相应的工作树,即从工作树清除我即将提交的所有内容。

所以:

git stash -k
git clang-format
git commit

现在,代码库有四个有趣的快照:原始内容(即stash base)、被截图的索引(snapshotted index)、被截图的工作目录(snapshotted worktree)以及当前索引快照(包括提交和工作目录),它是在stash^2处应用了清理之后的索引快照。需要注意的是,这三个新的快照(即提交)都以stash base为父级。

现在您想要回到您的工作目录更改,但显然,从base到存储索引和工作目录中的更改与当前索引和工作目录中的不匹配(新的提交除外,这些都匹配),因此当git尝试弹出存储时,将会发现冲突:从stashed base到stashed index的更改与从stash基本到当前索引的更改不匹配。

如果git直接提供了您想要的“存储所有工作目录更改除了索引中的更改”,那么您可以使用该选项,那么stash pop将不会遇到任何问题,只需执行简单的git stash pop命令即可。幸运的是,如果说Git擅长什么,那就是合并、组合、分离和整体处理差异。

git cherry-pick -nm2 stash
# cherry-pick updated the index, too. maybe git reset here to unstage that stuff.
git stash drop

Stash pop 是将存储状态(stash)的更改与 stash 基础版本到当前版本之间的更改进行合并。你需要将存储状态中的工作树更改还原到工作树中,但仅包括你尚未添加的更改,因为你已经添加的更改仍然存在,只是有一些不同。

所以 cherry-pick 使用 -n 选项表示不提交,-m2 选项表示主线更改是第二个父级,也就是你在存储时所做的所有差异但尚未添加的更改。

以下是一个例子:

 cd `mktemp -d`
 git init
 printf >file %s\\n 1 2 3 4 5
 git add .;git commit -m1
 printf >file %s\\n 1 2a 3 4 5
 git add .
 printf >file %s\\n 1 2a 3 4a 5

现在你已经有效地执行了git add -p命令来添加第二个变更,而第四个变更仅存在于你的工作目录中。

 $ git stash -k
 $ cat file
 1
 2a
 3
 4
 5
 $ sed -i '2s,^,_,' file   # indent the 2a line
 $ git commit -am2

现在,初始提交 :/11 2 3 4 5,您当前的提交、索引和工作树都是 1 _2a 3 4 5,您隐藏的索引是 1 2a 3 4 5,隐藏的工作树是 1 2a 3 4a 5

您需要的更改是隐藏的索引与隐藏的工作树之间的差异,即存储提交与其第二个父提交的差异。因此,需要使用 cherry-pick。


cherry-pick 的其他拼写方式包括:

git cherry-pick -nm1 -Xours stash

该命令应用所有存储的工作树更改,但在冲突的情况下采用本地版本(基本上是查找并丢弃冲突差异,而不仅仅是像-m2一样避免它们),并且

git diff stash^2..stash|git apply -3

让一切变得更加容易的方法是使用脚本,关于如何设置它,最简单的方式是通过Git别名来讨论。
git config --global alias.poptree '!git cherry-pick -nm2 stash; git reset; git stash pop'

现在你可以使用git poptree命令来执行你想要的操作。


编辑:更进一步,假设在记起你的存储的工作树更改之前,你已经进行了一些额外的工作,那么樱桃拣选将正确更新工作树和索引,但重置将撤销任何你已经添加到新索引中的更改。现在你处于核心命令领域,

( index=`git write-tree` &&
  git cherry-pick -nm2 stash &&
  git read-tree $index &&
  git stash drop
)

这是我实际实现的方式。

非常有趣。+1。git cherry-pick的运用非常妙。 - VonC

2

谈到git stash push(因为不带参数调用git stash等效于git stash push),请考虑添加--keep-index选项。

这意味着所有已添加到索引的更改都会保持不变。

因此,-p (patch)选项不应查询那些(已缓存)的块。

注意: --patch选项意味着--keep-index,所以(为测试)请确保您正在使用最新的Git版本(2.17),并尝试git stash push -p

如果问题仍然存在,则可以像评论中提到的那样先进行提交,以便stash -p在干净的索引上操作。
git reset --soft @~将恢复已提交的索引。


1
这不是 OP 请求的内容,OP 请求的是忽略已经暂存的差异。虽然 --keep-index 看起来会有所帮助,因为它不会清除已经暂存的补丁,但它会将它们包含在存储区中,从而在后面引起冲突。 - user4815162342
@user4815162342,是的,这就是我在我的回答中提到的内容。其中包括你自己的评论。 - VonC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接