Git暂存区(stash)是否将已暂存的文件转换为未暂存状态?

23
我正在对一份代码库进行一系列大型修改。其中一些更改已经被暂存,而其他一些则没有。我需要切换到另一个分支,但还没有准备好提交,所以我使用了git stash命令将我的当前状态保存起来。
后来,我使用git stash apply命令来应用我的保存状态。然后我运行了git status命令,注意到我的暂存更改不再显示为“已暂存”,而是出现为“更改未暂存以备提交”。请问我的理解是否正确,也就是说实际上没有丢失任何数据,而只是将“已暂存”的数据转换为“未暂存”的数据?
补充说明:在进行保存时,涉及到的一些文件同时存在已暂存和未暂存的版本。例如,文件A有些更改被暂存了。然后对文件A进行了一些其他更改,这些更改尚未被暂存。接着进行了保存。
3个回答

38

对于提出的问题(“stash是否将暂存文件转换为未暂存文件”),答案既是肯定的,也是否定的。

如果您使用git stash apply(而不是git stash pop)提交了申请,那么您就处于很好的状态,因为储藏仍然存在。但让我们退后一步,看看底层机制,因为它在这里很重要。

当您运行git stash push(新动词)或git stash save(旧动词)(或纯粹的git stash执行push/save)时,Git会创建两个1未在任何分支上的提交。一个提交保存索引的状态,即您已经暂存的内容。第二个提交保存工作树的状态,即其他所有内容。

稍后,当您使用git stash apply时,Git将更改压缩在一起,以便没有内容被暂存,除非您在apply操作中添加了--index2,在这种情况下,它会恢复(如果可以)您之前排列的已暂存与未暂存内容。

当您使用apply时,储藏脚本也会保留储藏提交,因此如果应用程序没有按照您想要的方式进行-包括如果您忘记了--index或拼错了它(请参见注释2)-您可以git reset --hard(假设您开始时所有设置都干净)并重新执行apply

但如果您使用了pop,并且Git认为申请成功,那么它就会放弃储藏。出于这个原因,我通常建议仅使用单独的apply和drop操作。

(顺便说一句:我实际上建议尽可能避免使用git stash。它对于不谨慎的人来说有太多的陷阱,而且历史上还存在许多bug。但是如果你已经使用了它并且现在后悔了,可以考虑使用git stash branch,它可以将保存的存储库转换成一个分支。请参见下面ADTC的评论。您可以使用已经弹出的存储库的原始哈希ID进行此操作,但通常需要处于“干净”的状态。新分支从您制作存储库时所在的提交分支出。)


1使用-u-a选项,它不仅保存已暂存和未暂存的文件,还保存被忽略的和/或所有文件,因此存储库脚本会生成三个提交。如果没有这些标志,则这些文件都不会进入存储库的任何部分。

2令人困惑的是,存储库脚本还有一个--keep-index标志,它允许您为apply操作指定此选项,但在那里没有任何意义。相反,--keep-index会影响stash在生成其特殊的存储库提交后所做的操作。偶尔我会不小心使用了git stash apply --keep-index而不是git stash apply --index,把这两个选项搞混了。


2
感谢您详细的回复,torek。现在我对正在发生的事情有了更好的理解。 - Jeff Fohl
所以,如果你想要应用 stash@{2} 并保留暂存/未暂存状态,请使用以下命令:git stash apply --index 2 - Neurotransmitter
@神经递质:是的。注意,使用裸数值(例如apply ... 2而不是更长的apply ... stash@{2})的能力在某种程度上是相对较新的。如果你的 Git 版本比较古老,而你的 git push 命令只有 save 动词,那么你可能需要手动输入 stash@{} 部分。 - torek
1
@ADTC:这一切都是正确的;请注意,如果您有哈希 ID,您也可以使用 git stash branch 命令并将哈希 ID 作为参数,这可能会更简单和更容易。 - torek
1
@ADTC:完成了 - 它在脚注之前的底部,但现在已经在里面了... - torek
显示剩余2条评论

3

是的,你的想法是正确的。在 man git-stash 的以下部分中有描述:

Use git stash when you want to record the current state of the working
directory and the index, but want to go back to a clean working
directory. The command saves your local modifications away and reverts
the working directory to match the HEAD commit.

(...)

pop [--index] [-q|--quiet] [<stash>] Remove a single stashed
           state from the stash list and apply it on top of the
           current working tree state, i.e., do the inverse operation
           of git stash save. The working directory must match the
           index.

           (...)

           If the --index option is used, then tries to reinstate not
           only the working tree’s changes, but also the index’s
           ones.

(...)

apply [--index] [-q|--quiet] [<stash>]
           Like pop, but do not remove the state from the stash list.

3

不,没有任何更改被丢失。

根据文档

Git re-modifies the files you uncommitted when you saved the stash. In this case, you had a clean working directory when you tried to apply the stash, and you tried to apply it on the same branch you saved it from; but having a clean working directory and applying it on the same branch aren’t necessary to successfully apply a stash. You can save a stash on one branch, switch to another branch later, and try to reapply the changes. You can also have modified and uncommitted files in your working directory when you apply a stash — Git gives you merge conflicts if anything no longer applies cleanly.

The changes to your files were reapplied, but the file you staged before wasn’t restaged. To do that, you must run the git stash apply command with a --index option to tell the command to try to reapply the staged changes. If you had run that instead, you’d have gotten back to your original position:

$ git stash apply --index
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#      modified:   index.html
#
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#
#      modified:   lib/simplegit.rb
#

谢谢Pankaj。我确实读过那个,但我不太确定我完全理解了它。感谢确认。 - Jeff Fohl

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