“git stash”是否在内部“提交”到我的本地存储库?

hip*_*ail 3 git git-stash git-commit

我只在访问远程存储库commit之前手动访问本地存储库。push

但更多时候我是pull为了让我的编码伙伴做出改变。

有时我们都处理同一个文件并且存在冲突。git stash在这些情况下,他告诉我在我之前做一个git pull,然后在git stash pop之后做一个。

但有时这会导致 git 下次告诉我不能,pull因为我有未合并的文件。这些通常是我不希望commit或对我的本地树进行的实验性更改push

有几次我需要发送我的工作,结果是远程存储库中的中间修订,包括我的本地实验、调试代码等,而我从来不想发送这些。我想避免造成这样的混乱。

这是由于stash修改了我的本地存储库吗?如果是这样,我怎样才能避免这种情况?如果不是,还有什么原因造成的?我是 git 的菜鸟,只使用这几个命令。

tor*_*rek 5

我想首先在这里提到术语“索引”与暂存区域的含义相同,并且您应该记住文件在任何时候“活动”都有三个HEAD版本:版本、索引或“暂存”版本,和工作树版本。当您第一次运行时,git checkout <branch>通常所有三个版本都会匹配。任何提交的版本都是永久的\xe2\x80\x94,与提交\xe2\x80\x94 一样永久且不可更改:您无法触及当前提交中存储的版本。您可以随时覆盖索引和工作树版本,但正常模式是:

\n\n
    \n
  1. 查看提交的版本:将提交复制到索引,然后将索引复制到工作树。
  2. \n
  3. 使用工作树版本。
  4. \n
  5. 用于git add将工作树版本复制回索引。
  6. \n
\n\n

重复步骤2、3直至满意;或用于git add --patch构建类似于工作树版本但不同的索引版本。(通常这样做是为了在运行调试文件时制作一些文件的可提交版本,其中没有额外的调试内容。)这确实意味着索引和工作树可以彼此不同,也可以HEAD犯罪。

\n\n

如果您确实运行,则会立即git commit从索引/暂存区域中的任何内容进行提交。这就是为什么你必须一直保持ing,从工作树复制到索引中。git add

\n\n
\n\n

正如萨吉布汗回答的那样,git stash save确实做出了承诺。更准确地说,如果git stash save执行任何操作(有时,如果没有更改,则不执行任何操作),它至少会进行两次提交。如果您使用--untrackedor--all标志,则会进行三次提交。 git stash文档的讨论部分下有一个小图表。 与文档一样,我们基本上会忽略第三次提交。1

\n\n

这些提交的不寻常之处在于它们不在任何分支上。特殊引用名称refs/stash指向新创建的w(工作树)提交。它至少有两个父级,一个是HEAD提交,另一个是i(索引)提交。有--untracked--all有第三个父级(我称之为u)持有额外的、未跟踪的文件。

\n\n

除了一种情况2i之外,我们在这里也将忽略,在保存并提交中每个文件的索引和工作树版本之后wgit stash save然后运行git reset --hard HEAD以将这些文件的索引和工作树版本替换为版本存储在HEAD提交中。因此,您的工作现在已保存,并且可以稍后恢复,但不再存在于索引(也称为暂存区域)或工作树中。

\n\n
\n\n

1如果(且仅当)您使用--all--untracked选项创建第三次提交,Git 还会git clean使用适当的选项运行来删除存储在该第三个父级中的文件。请记住这一点:任何现有的未跟踪文件(无论是否被忽略)都不会包含在 或iw。它们根本不会被保存,因此也不会被清除,除非您使用这些额外的选项。请注意,未跟踪文件的定义就是当前不在索引中的任何文件。最后两个词也很重要,以防您尚未遇到但最终可能遇到的情况。

\n\n

2使用该--keep-index选项时会出现一种情况。在这种情况下,git stash save代码做了一些相当棘手的事情:在进行iw提交之后,它不是将索引和工作树重置为HEAD,而是将它们重置为i提交中的内容。这样做的目的是安排工作树来保存建议的新提交,以便测试工作树文件的程序可以测试文件的“待提交”版本。然而,对于粗心的人来说,这里有几个陷阱:请参阅如何在预提交挂钩中正确地 git stash/pop 以获得用于测试的干净工作树?

\n\n
\n\n

你哪里出错了

\n\n

一旦你有了 stash\xe2\x80\x94i.e.,iw保存了 commits\xe2\x80\x94,你就可以安全地运行git pull, 3或更好的,git fetch。这将从您的 Git 记住的其他 Git 获取新提交origin,通过origin/masterorigin/develop等等来记住它们origin/feature/tall。然后 的第二步pull,即rebasemerge,将 rebase\xe2\x80\x94,即复制\xe2\x80\x94您现有的提交(如果有),或者合并您现有的提交(如果有)您引入的最新提交,并调整您自己的当前分支以指向结果。

\n\n

到目前为止,一切都很顺利,这就是你正在做的事情。但现在我们到达了棘手的部分。

\n\n

git stash pop现在,您按照同事/编码伙伴的建议运行。git stash apply我建议从而不是开始git stash pop,但是当它失败\xe2\x80\x94并且它失败时,考虑到你提到的其他内容\xe2\x80\x94这并不重要。4 无论哪种方式,Git 都会尝试应用已保存的提交,以便恢复您在索引和/或工作树中保存的更改。但这正如我刚才所说,很棘手,因为提交是快照,而不是更改。(另外,默认情况下,git stash apply/git stash pop会丢弃i提交。使用 时--index,它也会尝试将i提交恢复到索引中。)

\n\n

为了将w提交恢复为更改而不是快照,Git 使用其合并机制。隐藏脚本中有这样的实际行:

\n\n
git merge-recursive $b_tree -- $c_tree $w_tree\n
Run Code Online (Sandbox Code Playgroud)\n\n

效果就好像您运行了git merge\xe2\x80\x94 或更接近的git cherry-pick\xe2\x80\x94 命令。Git 将您隐藏的工作树$w_tree(提交w)与之前的提交HEAD$b_tree)进行比较,以查看“您更改了什么”,并将当前索引转换为部分提交($c_tree)与相同的提交进行比较$b_tree,以查看“它们更改了什么”,然后合并他们。

\n\n

与任何合并一样,此合并可能会因合并​​冲突而失败。这就是您所描述的:

\n\n
\n

...无法拉动,因为我有未合并的文件...

\n
\n\n

当合并失败时,它会将部分合并的结果保留在工作树中,并将原始文件集保留在索引中。例如,假设该文件foo.txt存在合并冲突。现在,您拥有五个版本,而不是foo.txt\xe2\x80\x94 HEAD(当前提交)、索引和工作树\xe2\x80\x94 的三个版本!这些都是:

\n\n
    \n
  • HEAD, 一如既往;
  • \n
  • 索引阶段 1,合并基础版本:这是取自 的版本,它是与您运行时返回$b_tree的提交相关的树;HEADgit stash save
  • \n
  • 索引阶段 2 或:这是当您启动失败的/--ours时索引中的内容。(这可能与版本匹配。)git stash applygit stash popHEAD
  • \n
  • 索引阶段 3 或--theirs:这是 中的内容$w_tree,即您隐藏的更改;和
  • \n
  • 留在工作树中的版本,带有合并冲突标记。
  • \n
\n\n

请注意,就像 和git rebase一样git cherry-pick,我们的/他们的git checkout标志在这里有点颠倒。

\n\n

一旦处于这种烦人的“未合并索引条目”状态,除了完成它或完全中止操作之外,您几乎无能为力。

\n\n

在这种特殊情况下,git stash apply已经在应用过程中停止了,因此已经中止了后续的git stash drop。因此,您仍然拥有您的存储库,并且可以运行git reset --hard HEAD以中止应用存储库的尝试。或者,您可以编辑工作树文件来完成 Git 无法完成的合并,以及git add将它们复制到索引中的文件,以便索引具有从工作树中获取的(单个)合并条目,替换三个较高阶段的条目。

\n\n

对于任何失败的合并,您都必须执行相同的过程:您要么中止它,然后弄清楚稍后要做什么;要么中止它,然后弄清楚稍后要做什么;或者你现在就完成它。

\n\n

请注意,一般来说,您不应该只是git add“按原样”使用包含冲突标记的工作树文件。虽然这解决了“不能 X,您有未合并的索引条目”的问题,但它会给您留下充满冲突标记的文件,这些文件并不是真正有用。

\n\n

git pull如果 Git 需要将您的某些提交与其他人的提交合并,那么当您运行 时,也可能会发生此类合并失败(合并冲突) 。或者,Git 可能会成功地自行进行合并(或者至少认为它成功了),然后进行新的合并提交。在这种情况下,系统将提示您输入提交消息:

\n\n
\n

我注意到pullafter的副作用stash有时会打开 vi 让我输入提交消息。

\n
\n\n

这表明您已在分支上进行了正常提交,并且您git pull已运行git merge,它认为已成功合并,现在需要此新合并提交的提交消息。

\n\n

您可能想使用git rebase而不是git merge在这里。git fetch如果您使用后跟第二个 Git 命令,而不是使用 . ,这会更容易git pull

\n\n

请注意,如果您确实使用git rebase(并学习了它,尤其是非常方便的git rebase -i),您可以随意进行各种临时提交,包括:

\n\n
\n

...本地实验、调试代码等...

\n
\n\n

当你变基时,你将把这些提交复制到其他人的提交之上,将你的工作保留为你自己的;您最终可以用来git rebase -i“压缩”临时提交,以支持一个大的最终“真正”提交。你必须小心,不要意外地得到git push它们(一旦你这样做了,副本就会到处都是,而且很难让其他人放弃它们)。

\n\n
\n\n

3我建议不要在这里使用git pull:相反,将其分成几个组成部分。首先,运行git fetch以从另一个 Git 获取新的提交。一旦你有了提交,你可以根据需要查看它们。然后使用git rebasegit merge将这些提交合并到您的分支中。您需要一个git rebasegit merge每个分支,但git fetch在所有分支之前只需要一个。

\n\n

一旦您非常熟悉它的两个组成部分如何为您工作,您就可以安全地运行git pull,因为知道当出现问题时\xe2\x80\x94并且最终会\xe2\x80\x94您会认识到发生了什么以及哪一步失败了,并且知道要注意什么以找出解决方法。

\n\n

4不过,为了完整起见,请注意,它git stash pop的意思是git stash apply && git stash drop。也就是说,尝试应用隐藏的东西;然后,如果Git认为一切顺利,请立即删除该存储。但有时 Git 认为一切顺利,但实际上并非如此。在这种特殊情况下,最好还是把藏匿的东西放在手边,但git stash drop要拿回来却非常困难。

\n\n
\n