"git stash" 是否在内部 "commit" 到我的本地存储库?

Does "git stash" internally "commit" to my local repo?

我只是在 push 到远程仓库之前手动 commit 到我的本地仓库。

但更多时候我 pull 获取编码合作伙伴的更改。

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

但有时这会导致 git 下次告诉我不能 pull 因为我有未合并的文件。这些通常是我不希望 commitpush.

在本地树中进行的实验性更改

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

这是因为 stash 修改了我的本地存储库吗?如果是这样,我该如何避免呢?如果不是,还有什么可能导致它?我是 git 的菜鸟,只会使用这几个命令。

Stash实际上commit/save你在一个临时框中的本地更改,not in your working tree

$ git stash

您可以看到隐藏列表 -

$ git stash --list

在提取更改之前,请确保您完美地存储了所有 unnecessary/experimental 更改。

$ git stash save 'provide stash message'       # better give a stash message
$ git stash     # see if all the changes are stashed

您也可以 apply 存储而不是 pop(如果不想删除它)。您还可以通过 git stash drop 删除存储而不应用它(删除 #1 存储)。 基本上,pop = apply + drop

$ git stash apply stash@{0}     # get back the last (#1) stash changes
$ git stash apply stash@{1}     # get back the #2 stash changes

这里我想先提一下,index这个词和staging area是同一个意思,大家要记住任何时候一个文件 "active" 有 三个 版本:HEAD 版本、索引或 "staged" 版本和 work-tree 版本。当您第一次 运行 git checkout <branch> 时,您通常会匹配所有三个版本。任何提交的版本都是永久的——好吧,和提交一样永久——并且不可更改:你不能触及存储在当前提交中的版本。您 可以 随时覆盖索引和 work-tree 版本,但正常模式是:

  1. 检查已提交的版本:将提交复制到索引,然后将索引复制到 work-tree。
  2. 在 work-tree 版本上工作。
  3. 使用git add将work-tree版本复制回索引。

重复步骤2和3直到满意为止;或使用 git add --patch 建立一个有点像 work-tree 版本但不同的索引版本。 (通常这样做是为了制作一些文件的可提交版本,其中没有额外的调试内容,而 运行ning 调试文件。)这确实意味着索引和 work-tree 可以不同来自彼此 来自 HEAD 提交。

如果并且当您执行 运行 git commit 时,这会从索引 / staging-area 中的任何内容进行提交 。这就是为什么你必须一直保持 git adding,从 work-tree 复制到 index.


因为 , git stash save does make commits. More precisely, if git stash save does anything (sometimes it does nothing, if there are no changes), it makes at least two commits. If you use the --untracked or --all flag, it makes three commits. The git stash documentation has a small diagram of this under its DISCUSSION section. 与文档一样,我们将主要忽略第三次提交。1

这些提交的不寻常之处在于它们在 nob运行ch 上。特殊引用名称 refs/stash 指向新创建的 w (work-tree) 提交。它至少有两个 parents,一个是 HEAD 提交,另一个是 i (索引)提交。使用 --untracked--all 有第三个 parent(我称之为 u)保存额外的、未跟踪的文件。

在除一种情况外的所有情况下2 我们也将在此处忽略,在保存 [=26= 中每个文件的索引和 work-tree 版本之后] 和 w 提交,git stash save 然后 运行s git reset --hard HEAD 将这些文件的索引和 work-tree 版本替换为存储在 [=11= 中的版本] 犯罪。所以您的工作现在已保存,以后可以恢复,但不再存在于索引(又名暂存区)或 work-tree.


1如果(且仅当)您使用 --all--untracked 选项创建第三次提交,Git 也 运行s git clean 使用适当的选项删除存储在这第三个 parent 中的文件。请记住这一点:任何现有的未跟踪文件(无论是否忽略)都 从不 包含在 iw 中。它们根本没有保存,因此也没有被清除,除非你使用这些额外的选项。请注意,未跟踪文件 的定义只是任何不在索引中的文件现在 .最后两个词也很关键,如果你还没有 运行 进入,但最终可能会。

2一种情况发生在您使用 --keep-index 选项时。在这种情况下,git stash save 代码做了一些相当棘手的事情:在进行 iw 提交之后,而不是将索引和 work-tree 重置为 HEAD,它将它们重置为 i 提交中的内容。这样做的目的是 运行ge work-tree 保存建议的新提交,以便测试 work-tree 文件的程序可以测试文件的 "to be committed" 版本。然而,对于粗心的人来说,这里有几个陷阱:请参阅 How do I properly git stash/pop in pre-commit hooks to get a clean working tree for tests?


你哪里错了

一旦你有一个存储 - 即 iw 提交 - 保存下来,你可以 mostly-safely 运行 git pull,3 或更好,git fetch。这将从其他 Git 获得新的提交,您将 Git 记住为 origin,通过 origin/masterorigin/develop 以及 origin/feature/tall 和很快。然后 pull 的第二步,即 rebasemerge,将变基——即复制——你现有的提交(如果有的话),或者合并你现有的提交(如果有的话), on/with 你带来的最新提交,并调整你自己当前的 b运行ch 指向结果。

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

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

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

git merge-recursive $b_tree -- $c_tree $w_tree

效果就好像你有 运行 一个 git merge——或者更接近,git cherry-pick——命令。 Git 将你隐藏的工作树 $w_tree(提交 w)与 HEAD$b_tree)的提交进行比较以查看 "what you changed",并进行比较您当前的索引变成了针对同一个 $b_tree 的部分提交 ($c_tree) 以查看 "what they changed" 并合并它们。

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

... can't pull because I have unmerged files ...

当合并失败时,它会将 partially-merged 结果留在 work-tree 和索引中的原始文件集中。例如,假设文件 foo.txt 存在合并冲突。现在 three 版本的 foo.txt——HEAD(当前提交)、index 和 work-tree——你有 five版本!它们是:

  • HEAD,一如既往;
  • index 阶段 1,merge base 版本:这是取自 $b_tree 的版本,它是与 [=11= 的任何提交一起使用的树] 当你 运行 git stash save;
  • index stage 2 或 --ours:这是当您启动失败的 git stash apply / git stash pop 时索引中的任何内容。 (这可能与 HEAD 版本匹配。)
  • 索引阶段 3 或 --theirs:这是 $w_tree 中的任何内容,即您隐藏的更改;和
  • 留在 work-tree 中的版本,带有合并冲突标记。

请注意,就像 git rebasegit cherry-pick 一样,ours/theirs git checkout 标志在这里有点相反。

一旦进入这种烦人的 "unmerged index entries" 状态,除了完成它或完全中止操作之外,您几乎无能为力。

在这种特殊情况下,git stash apply 已经在申请中途停止,因此已经中止了后续的 git stash drop。因此,您仍然拥有您的藏匿处,并且可以 运行 git reset --hard HEAD 中止应用藏匿处的尝试。或者,您可以编辑 work-tree 文件以完成 Git 无法完成的合并,并编辑 git add 文件以将它们复制到索引中,以便索引具有(单个) 合并了来自 work-tree 的条目,替换了三个 higher-staged 条目。

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

请注意,通常您不应该 只是 git add work-tree 文件 "as is",并带有冲突标记。虽然这解决了 "cannot X, you have unmerged index entries",但它给您留下了充满冲突标记的文件,这些标记并没有多大用处。

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

I've noticed that a side effect of the pull after the stash sometimes opens up vi for me to enter a commit message.

这表明您已经在您的 b运行ch 上进行了正常提交,并且您的 git pull 具有 运行 git merge,这相信它已经成功合并,现在需要此新合并提交的提交消息。

您可能希望在此处使用 git rebase 而不是 git merge。如果您使用 git fetch 后跟第二个 Git 命令,而不是使用 git pull.

,这会更容易

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

... local experiments, debug code, etc ...

当你 rebase 时,你会将这些提交复制到其他人的提交之上,将你的工作保留为你自己的;你最终可以使用 git rebase -i 到 "squash away" 临时提交来支持一个大的最终 "real" 提交。你必须小心不要不小心 git push 他们(一旦你这样做,副本就会到处都是,很难让其他人放弃它们)。


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

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

4为了完整起见,请注意git stash pop 就意味着 git stash apply && git stash drop。也就是说,尝试应用存储;然后如果 Git 认为进展顺利,立即放下藏匿处。但有时 Git 认为一切顺利,但事实并非如此。在这种特殊情况下,仍然可以随身携带藏品很好,而且 git stash drop 很难取回它。