【问题标题】:Does "git stash" internally "commit" to my local repo?“git stash”是否在内部“提交”到我的本地仓库?
【发布时间】:2018-04-10 23:10:21
【问题描述】:

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

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

有时我们都在处理同一个文件,但会发生冲突。在这些情况下,他告诉我在git pull 之前执行git stash,然后在之后执行git stash pop

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

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

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

【问题讨论】:

  • git stash 确实做了一种提交,但它不太可能是你偶然推动的。你使用的 Git GUI 可能会提交比你想要的更多的文件吗?或者你可能在做git add ./git commit -a
  • 不,在我了解 git 在原始级别做什么之前,我会避免使用 GUI (-:

标签: git git-commit git-stash


【解决方案1】:

这里我想先提一下,index这个词和staging area的意思是一样的,你应该记住有三个 任何时候“活动”文件的版本:HEAD 版本、索引或“暂存”版本以及工作树版本。当您第一次运行git checkout <branch> 时,您通常会匹配所有三个版本。任何提交的版本都是永久的——嗯,和提交一样永久——并且不可更改:你不能触及存储在当前提交中的版本。您可以随时覆盖索引和工作树版本,但正常模式是:

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

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

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


作为Sajib Khan answeredgit stash save 确实会提交。更准确地说,如果git stash save 做了anything(有时它什么也不做,如果没有更改),它至少会进行两次提交。如果您使用 --untracked--all 标志,它会进行三个提交。 The git stash documentation has a small diagram of this under its DISCUSSION section. 和文档一样,我们大多会忽略第三次提交。1

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

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


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

2当您使用--keep-index 选项时会出现一种情况。在这种情况下,git stash save 代码做了一些相当棘手的事情:在提交iw 之后,它没有将索引和工作树重置为HEAD,而是将它们重置为i 中的内容犯罪。这样做的目的是安排工作树保存提议的新提交,以便测试工作树文件的程序可以测试文件的“待提交”版本。但是,这里有几个陷阱供粗心的人使用:请参阅How do I properly git stash/pop in pre-commit hooks to get a clean working tree for tests?


你哪里出错了

一旦你有一个 stash(即 iw 提交)被保存,你就可以安全地运行 git pull,3 或更好的 git fetch。这将从您的 Git 记忆为 origin 的另一个 Git 获得新的提交,通过 origin/masterorigin/developorigin/feature/tall 等方式记住它们。然后pull 的第二步,即rebasemerge,将变基——即,复制——如果你有任何现有的提交,或者如果你有任何现有的提交,在最新的提交上/与最新的提交合并你已经引入,并调整你自己的当前分支以指向结果。

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

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

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

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

效果就像您运行了git merge——或者更接近,git cherry-pick——命令。 Git 将您隐藏的工作树 $w_tree (commit w) 与 HEAD ($b_tree) 的提交进行比较,以查看“您更改了什么”,并将您当前的索引与部分提交 (@987654386) 进行比较@) 针对相同的$b_tree 来查看“他们改变了什么”,然后合并它们。

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

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

当合并失败时,它将部分合并的结果保留在工作树中,并将原始文件集保留在索引中。例如,假设该文件foo.txt 存在合并冲突。现在,foo.txt——HEAD(当前提交)、索引和工作树的三个版本——你有五个版本!它们是:

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

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

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

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

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

请注意,通常您不应该只是git add“按原样”工作树文件,并带有冲突标记。虽然这解决了“不能 X,你有未合并的索引条目”,但它会给你留下充满冲突标记的文件,这并不是真正有用的。

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

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

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

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

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

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

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


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

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

4为了完整起见,请注意git stash pop 仅表示git stash apply && git stash drop。也就是说,尝试应用存储;然后如果 Git 认为进展顺利,请立即删除存储。但有时 Git 认为它运行良好,但实际上并非如此。在这种特殊情况下,仍然可以方便地保存存储空间,而 git stash drop 很难找回它。


【讨论】:

  • 感谢您的超深回答。! (+1)。这里所有的案例都被精确地讨论了(虽然有点长:))
  • 哇,这是我在 Stack Overflow 上收到的关于问题的最佳答案。干得好!
【解决方案2】:

Stash 实际上在一个临时框 not in your working tree 中提交/保存您的本地更改。

$ git stash

你可以看到存储列表 -

$ git stash --list

在拉取更改之前,请确保您完美地隐藏了所有不必要的/实验性更改。

$ 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

【讨论】:

  • 我注意到pullstash 之后的副作用有时会打开vi 让我输入提交消息。这让我很困惑,让我觉得我要搞砸了。
  • Sometime stash 消息指示此存储中的实际更改。并且 Pull 不应该影响 stash 的消息。你能重新生成问题并附加命令吗?
  • 一个问题是我只有一个项目,我想避免对其进行试验,因为这是我不想添加混乱的真实工作。
  • 所以复制它并进行实验。
  • @hippietrail, "git pull 有时在git stash 之后打开vi" 与git stash 完全无关。后者只是将 未提交 更改保存到与当前签出分支无关的区域中。 git pull 向您弹出 vi 的事实是因为 git pull 看到您的本地分支和您从中提取的分支已经分歧,因此命令 merged 将其拉入本地分支,请您描述合并操作。
猜你喜欢
  • 2021-09-03
  • 1970-01-01
  • 2011-09-03
  • 1970-01-01
  • 2019-02-22
  • 2015-12-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多