【问题标题】:git stash and applygit stash 并应用
【发布时间】:2013-12-22 23:24:18
【问题描述】:

我是 git 新手,不太清楚 stash 的工作原理。

假设我正在处理分支 master 并尝试 git pull 并收到我的本地更改将被覆盖并需要隐藏或提交的错误。如果我没有暂存任何更改并运行git stash,然后执行git pull 并成功更新,当我git stash apply 时会发生什么?

一般来说,如果其他人修改文件并且我运行git pull,当我run git stash apply 时会发生什么?它是否会覆盖刚刚更新的文件,无论它们在我存储它们时是否已上演?它会用隐藏的文件覆盖我刚刚使用git pull 更新的每个文件吗?

【问题讨论】:

标签: git git-pull git-stash


【解决方案1】:

快速“TL;DR”外卖版,以后可以回来学习更多

git stash 在当前的HEAD 提交上挂起一个存储袋——这是一种特殊的合并提交形式,它不在任何分支上。稍后的git stash apply,当您进行任何提交时(可能是 不同的 提交),然后尝试通过查看悬挂的 stash-bag 来恢复 git 计算的 更改以及它挂起的提交。

当您完成更改后,您应该使用git stash drop 从“隐藏”的提交中释放隐藏包。 (而且,git stash pop 只是“应用,然后自动删除”的简写。不过,我建议将这两个步骤分开,以防您不喜欢“应用”的结果并且想稍后再试。)

加长版

git stash 实际上相当复杂。

据说"git makes much more sense once you understand X",对于许多不同的“X”值,概括为“一旦你理解了 git,git 就更有意义了”。 :-)

在这种情况下,真正了解stash,您需要了解提交、分支、索引/暂存区、git 的引用名称空间和合并所有工作,因为@987654330 @ 创建了一个非常特殊的合并提交,它由通常的名称空间之外的名称引用——一种根本不在“分支上”的奇怪合并——并且 git stash apply 使用 git 的合并机制来尝试“重新应用”在进行特殊合并提交时保存的更改,可选择保留分阶段和非分阶段更改之间的区别。

幸运的是,您实际上不需要了解所有这些使用git stash

在这里,您正在处理某个分支 (master),并且您有一些尚未准备好的更改,因此您不想在分支上提交它们。1与此同时,其他人在远程仓库的origin/master 上放了一些好的东西——或者至少,你希望它是好的东西,所以你想把它们捡起来。

假设您和他们都从以- A - B - C 结尾的提交开始,即C 是您开始在分支master 上工作时在您的repo 中的最终提交。新的“好东西”提交,我们将调用 DE

在您的情况下,您正在运行 git pull,但由于“工作目录不干净”问题而失败。所以,你运行git stash。这会以其特殊的怪异 stash-y 方式为您提交您的东西,因此您的工作目录现在是干净的。现在你可以git pull

就提交的绘制而言(像您使用gitkgit log --graph 获得的图表),您现在有了这样的东西。当您运行git stash 时,存储区是i-w 的小袋子,悬挂在您“开启”的提交中,在您的master 分支中。 (名称iw 的原因是它们是存储的“i”索引/暂存区域和“w”ork-tree 部分。)

- A - B - C - D - E      <-- HEAD=master, origin/master
          |\
          i-w            <-- the "stash"

如果您开始处理master 并且从未进行过任何 次提交,您会得到这张图。因此,您最近的提交是C。在进行存储之后,git pull 能够将提交 DE 添加到您的本地分支 master。隐藏的工作包仍然挂在C

如果您自己进行了一些提交——我们将它们称为Y,用于您的提交,Z 只是为了进行两次提交——“stash then pull”的结果如下所示:

                   .-------- origin/master
- A - B - C - D - E - M  <-- HEAD=master
            \       /
              Y - Z
                  |\
                  i-w    <-- the "stash"

这一次,在 stash 将其隐藏包挂在 Z 之后,pull(即 fetch 然后 merge)必须进行真正的合并,而不仅仅是“快进” ”。所以它提交M,合并提交。 origin/master 标签仍然指的是提交E,而不是M。你现在在master 上提交M,这是EZ 的合并。你比origin/master“领先”。

在任何一种情况下,如果您现在运行 git stash apply,stash 脚本(它是一个使用大量低级 git“管道”命令的 shell 脚本)会有效地2执行以下操作:

git diff stash^ stash > /tmp/patch
git apply /tmp/patch

这将 stash 命名为 w(存储的“工作树”部分)与正确的3 父级不同。换句话说,它会找出正确的父提交(CZ,视情况而定)和隐藏的工作树之间的“你改变了什么”。然后它将更改应用到当前签出的版本,即EM,同样取决于您从哪里开始。

顺便说一句,git stash show -p 实际上只是运行相同的git diff 命令(当然没有&gt; /tmp/patch 部分)。如果没有-p,它将使用--stat 运行差异。因此,如果您想详细了解git stash apply 将合并的内容,请使用git stash show -p。 (不过,这不会告诉您 git stash apply 可以尝试从存储的索引部分应用什么;这是我对存储脚本的一个小抱怨。)


无论如何,一旦 stash 应用干净,您可以使用 git stash drop 删除对 stash-bag 的引用,以便对其进行垃圾回收。在你放下它之前,它有一个名字(refs/stash,又名stash@{0})所以它会“永远”存在......除了如果你制作一个 new stash,@ 987654394@ 脚本将当前存储“推送”到存储引用日志中(使其名称变为stash@{1})并使新存储使用refs/stash 名称。大多数 reflog 条目会保留 90 天(您可以将其配置为不同)然后过期。默认情况下,存储不会过期,但如果您以其他方式配置,“推送”存储可能会丢失,因此如果您开始根据自己的喜好配置 git,请小心依赖“永久保存”。

请注意,git stash drop 在此处“弹出”存储堆栈,将stash@{2} 重新编号为stash@{1},并使stash@{1} 变为普通stash。使用git stash list 查看存储堆栈。


1不管怎样继续提交它们也不错,然后再执行git rebase -i 以进一步压缩或修复第二、第三、第四、...、第 n 次提交,和/或重写临时“检查点”提交。但这与此无关。

2这有点复杂,因为您可以使用--index 来尝试保持分阶段的更改分阶段进行,但实际上,如果您查看脚本,您会看到实际的命令序列git diff ... | git apply --index。在这种情况下,它确实只是应用了差异!但最终它直接调用git merge-recursive 以合并到工作树中,从而允许从其他地方引入相同的更改。如果你的补丁做了一些“好东西”提交DE 也做的事情,那么一个普通的git apply 将会失败。

3这使用了 git 的父命名魔法语法,并在 stash 脚本中进行了一些预先规划。因为存储是这个时髦的合并提交,w 有两个甚至三个父级,但是存储脚本设置它以便“第一个父级”是原始提交,CZ,视情况而定。 “第二父”stash^2 是提交时的索引状态,在悬挂的小存储袋中显示为i,而“第三父”(如果存在)是未分级的-并且可能-忽略的文件,来自 git stash save -ugit stash save -a

请注意,在此答案中,我假设您没有仔细上演了工作树的一部分,并且您没有使用git stash apply --index 来恢复阶段性索引。通过不执行任何这些操作,您会使i 提交变得非常多余,因此我们在apply 步骤期间不必担心它。如果您正在使用apply --index 或等效项,并且暂存项目,则您可能会遇到更多的极端情况,即无法完全应用存储。 p>

这些相同的警告适用于使用-u-a 保存的具有第三次提交的存储。

对于这些特别困难的情况,git stash 提供了一种将存储变为成熟的分支的方法——但我将把这一切留给另一个答案。

【讨论】:

  • 这是我在 SO 上见过的最好的答案之一,您的其他答案似乎同样完整。谢谢你。但是有一个问题:在应用存储时,git 会通知您冲突吗? (也就是说,在 D 或 E 中所做的更改被您隐藏的更改覆盖?)
  • @AmadeusDrZaius:“应用”步骤(实际上,所有这些在 git 中的东西)都使用了我所说的“合并机制”。只有一些命令公开选项(--strategy-X),其他命令使用默认设置;默认值因冲突错误而停止。当然,git 只能告诉你 看到的冲突,所以一般来说,即使 git 对结果感到满意,你也总是需要检查结果。
  • 如果stash 回到我获取的最新HEAD,为什么我要使用pull --rebase,如stackoverflow.com/a/30209767/577052 之类的帖子所示?应该没有任何更改要重新设置,因为它们也被隐藏了,或者不是吗?
  • @BernhardDöbler:我不明白问题的前提(“回到最新获取的 HEAD”部分)。 Stash 本身与git fetch 无关; git stash save 只是创建了几个根本不在分支上的提交,然后重置(使用选项,所以这里不是那么简单)索引和工作树。 Rebase 也与此无关:git rebase copies 提交。使用当前分支选择要复制的提交。新副本的目的地和限制器来自git rebase 的参数,或来自当前分支的上游设置。
【解决方案2】:

stash git 命令会记住 stash 的来源:

   git stash list

输出

   stash@{0}: WIP on master.color-rules.0: 35669fb [NEW] another step toward initial cube

您可以在哪里看到它是在哪个 SHA1 上制作的。因此,如果您 git stash、git pull、git stash apply 并且发生冲突,则不会删除存储(仅当您删除或应用成功时才会删除)。所以你总是可以从 git stash list 和

   git checkout 35669fb
   git stash apply

并且保证可以正常工作。我建议使用 -b 选项并为该恢复提供一个分支名称。

话虽如此,我最喜欢的工作流程是始终以新的“个人”名称结帐以避免此类问题

【讨论】:

  • git stash branch &lt;newbranch&gt; 结合了所有三个步骤(检查存储应用的版本,创建新分支,并使用--index 应用存储,然后在成功应用后删除存储)。
【解决方案3】:

通常未提交的更改总是不好的。您的更改要么是好的,然后提交它们,要么它们比丢弃它们不好。在有未提交的更改时执行任何 git 操作往往会导致麻烦,git 将无法帮助您,因为 git 不知道您未提交的任何内容。

话虽如此,回到你的问题。 ;)

Git 通常非常聪明。当您应用存储时,它会尝试将您的更改与其他更改合并。大多数情况下,这很有效。

如果更改确实发生冲突,因为您以不同的方式更改了相同的行,git 会告诉您,您必须自己解决冲突。 - 即使在这种情况下,git 也会通过git mergetool 为您提供帮助,它会启动一个合适的命令来向您显示冲突并允许您一一解决。

【讨论】:

  • 讨论(这个答案的第一段)可能更适合 cmets,而不是答案。
猜你喜欢
  • 2010-12-26
  • 1970-01-01
  • 2014-03-11
  • 2013-03-26
  • 2017-11-25
  • 2013-02-23
  • 1970-01-01
  • 2012-09-10
相关资源
最近更新 更多