git存储修改上次提交(在gui中) - 弹出窗口没有任何结果 [英] git stash while amending last commit (in gui) - pop pops nothing

查看:1496
本文介绍了git存储修改上次提交(在gui中) - 弹出窗口没有任何结果的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以我有一些未分阶段的变化和一些上演的变化。我发行了

$ p $ 欢迎使用Git(版本1.8.3-preview20130601)
$ git stash save --keep-index
保存的工作目录和索引状态主站上WIP:ab0d18d设置报警f
或网络服务+记录异常+长算术
HEAD现在在ab0d18d为网络服务设置报警+记录异常
+ long算术
$ git存储
保存的工作目录和索引状态在主WIP:ab0d18d设置报警f
或网络服务+记录异常+长算术
HEAD现在位于ab0d18d为网络服务设置报警+记录异常
+长算术

然后,我在gui中修改了上次提交 以将ab0d18d提交拆分为更小的提交。我取消了一些文件,然后我点击

$ p $ git stash save --keep-index
保存的工作目录和索引状态主站上WIP:ab0d18d设置报警f
或网络服务+记录异常+长算术
HEAD现在位于ab0d18d为网络服务设置报警+记录异常
+长算术

重复上述步骤:

  $ git stash保存--keep-index 
保存工作目录和索引状态在主WIP:ab0d18d设置警报f
或网络服务+记录异常+长算术
HEAD现在在ab0d18d为网络服务设置报警+记录异常
+长算术



<然后我编辑了提交信息,然后我承诺。然后,我发出了 git stash pop 开始取回我的藏品并逐一提交。

  $ git stash pop 
#在分支大师
#未经记录的文件:
#(使用git add< file> ...来包含什么已提交)

#TODO.txt
没有添加提交但未跟踪的文件存在(使用git add跟踪)
已删除的引用/存储{0}(43facd88ea3548071b196324523bd017d680d6dd )

灾难!



快乐(长寿保险箱)



2问题:


  • 我做了什么错误?

  • 如何从这种情况恢复?



编辑:gitk after I恢复了更改(修改的提交是SETUP ALARMS之一)



编辑

找到了一个方法重现这个问题 - 将会对它进行修正(可能只有最后一部分是需要的 - 试图重现我的原始情况),但对于初学者:

  mkdir test_stash; cd test_stash; git init 
echo f1>> f1;回波f2>> f2; echo f3>> f3;回声f4>> f4;回声f5>> f5
git add f1 f2 f3 f4 f5
git commit -m base123
echo f1 to be revised>> f1
要修改的回波改变f2>> f2
要修改的回波变化f3>> f3
git add。
git commit -m tobeamended123
echo change f4>> F4; git add f4
echo change f5>> f5
git存储--keep-index
git存储
git gui&

现在点击gui中的修改提交 。不知道它对应的是哪个命令,但是 git commit --amend 并不能解决问题。





在gui中修改状态为unstree的文件f3 再次 (点击它以便它在未分区的区域移动) git reset HEAD f3 但这不起作用)然后

  git stash保存--keep-index 
git commit -m修改#没有真正修改 - 一次新的提交
git stash pop

获取:

 #分支master 
没有提交,工作目录clean
已删除的参考文献/隐藏文章@ {0}(898687d73b65ccc9e10cd826bc12fda1a4759651)

期待: f3修改显示

解决方案

/我保留下面的第一个长答复(可能会尝试将其移至新问题后来),但现在有一个重现例子,我会通过它。不过,让我列出一些要点。


  1. git存储始终保存索引和工作目录。有人可能会认为 - keep-index 会让它藏起来更多,或者改变在 pop 。它不!默认情况下, git stash apply git stash pop 会混合分离的索引更改。添加 - keep-index 不会改变这个。只有 - index 参数应用 pop 尝试以避免混合它们。

  2. 到当前 HEAD 更改。这意味着如果 index HEAD 有一个变化,但是当前工作目录不是,那么确实没有更改保存在WIP on 分支 ...提交中。 (这是我认为的 git stash 中的一个错误。我已经发送了一个测试用例并可能修复git邮件列表。对于正常情况,这很好,但如果你已经拆分了一些部分,然后想用 git stash branch 来恢复你的确切状态,那么它会丢失工作目录状态,并且它在这里引起你的问题。)


  3. 应用存储器会尝试更改当前状态,以反映存储状态中的更改 。这可能会很复杂,因为当前状态不一定就像您保存存储时那样。


什么 git-gui 正在做。在你启动它的时候你有这个(实际的提交数字当然会有所不同)。未标记的WIP on master是第一藏匿处,现在存放@ {1}

  $ git存储列表
存储@ {0}:在主WIP上:c93c8fe tobeamended123
存储@ {1}:在主上WIP:c93c8fe tobeamended123
$ git log --decorate --oneline --graph --all'stash @ {1}'
* 3d01942(refs / stash)WIP on master:c93c8fe tobeamended123
| \
| * 6be9135指数在主:c93c8fe tobeamended123
| /
| * de8038c在主人的WIP上:c93c8fe tobeamended123
| | \
| / /
| * 3db6cfc index master:c93c8fe tobeamended123
| /
* c93c8fe(HEAD,master)tobeamended123
* 828d5cf base123

现在在 git gui 中,当您选择修改上次提交时,它会找到HEAD提交的引用( c93c8fe ,在我的情况下)。它实际上并没有对它做任何事情(但)。但是,只要您点击 f3 来取消它,它就会执行某些操作:它会抓取 f3 HEAD ^ 的副本)并将其填充到索引中。如果您检查 f3 ,它仍然有额外的行,但是如果您< git show:0:f3 在索引中的版本,它不再有这条线。



请注意,由于gui-mouse-clicks没有修改,并且没有新的提交。所有的操作都发生在索引内。



接下来,您回到命令行并运行:

  $ git stash save --keep-index 

提交了第三对提交,其中一个提交索引,另一个提交当前目录。索引版本在 f1 f2 中有额外的行,并且缺少 f3中的额外行。当前目录版本应该可以在所有三个文件中都有额外的行,但是,唉,它不会,因为 git stash save 比较当前目录和 HEAD 提交,额外的行在 HEAD 提交中,所以它不在

不幸的是,您使用了 - keep-index 参数,所以现在工作目录版本与隐藏的索引版本相同。文件 f3 不再有额外的行。



从这里开始,问题依然存在(更改不见了, - keep-index 抛出它)。你当然可以从原始提交中恢复它(tobeamended123)。但是在这种情况下,出错的地方在于:命令行 stash 保存了索引,然后将工作目录与 HEAD ,它没有改变,所以没有保存(不变)到 f3






我没有看到灾难,但我看到一些令人困惑的事情,我敢打赌你会感到困惑。我不知道你为什么使用上面的 - keep-index 。 (事实上​​,我不确定什么用例 - keep-index 可能用于 1 ,并且在我看来 apply pop 应该可以默认为 - index ,但这完全是另一回事......)而且,你总共做了四次推,并且只弹出了一次,剩下三次。

[< sup> 1 我找到了预期的用例,就在文档中:用于在提交之前测试索引中的当前内容。但是,等等,huhwha?, - keep-index 确实存储 ref。不管怎么样,只要使用 git checkout -b test -stash 就可以保证安全地分离,直到你满意为止。如果你测试它并且它失败,并且你需要修改它,那么这个存储会有冲突。如果你测试了它并且工作,你可以将你工作的提交拉到/快进 - 合并到你早期的分支中。] tl; dr简答

运行 git存储列表。您会看到一个列表:

 存储@ {0}:在主设备上执行:ab0d18d设置报警... 
stash @ {1}:主人WIP:...

项目。使用 git stash apply - index 'stash @ { n }' - -index 是可选的),以尝试通过名称和号码应用每个保存的存储,而不弹出它们中的任何一个。这是一个堆栈,最近推送了 stash @ {0} ,并且(到此为止) stash @ {3} 第一个(最长前)推送。



apply-without-pop意味着您可以 git reset --hard 回到 master 并准备好 git stash apply 一个不同的藏匿处。 (确保你用一个干净的工作目录开始整个序列,也许通过添加另外一个 git stash ,尽管这可能会让人感到困惑。:-))



如果你弄得特别大,你可以使用 git stash branch name 'stash @ { n }。这是一个大的,快速的,有效的锤子,其主要缺点是你必须发明一个分支名称。 (你可以 git stash show 看看里面有什么,以帮助你想出名字。)不要让这吓倒你,因为你总是可以重命名分支甚至以后删除它。



当您完成所有存档时,使用 git stash clear $ b $ h2>关于 git commit --amend vs git stash

这些实际上有点独立。 提交--amend 可以基于您所在的任何分支进行提交链。假设你在 master 上,并且链看起来像这样(在 git log --graph --oneline --decorate gitk ):

  * 67dec43(HEAD,master )changesmecommit 
* 9c37840 previous commit

您编辑并 git add 有些东西 - 我将更改文件 f3 并添加它 - 然后运行 git commit --amend 。这需要索引并进行一次新的提交,但是新提交的父节点是从 master 所在的位置,即 previous commit 以上。现在日志输出如下所示:

  * 68c51f3(HEAD,master)替换为modificationsmecommit 
* 9c37840以前的提交

你看不到(因为它没有分支标签) code> 67dec43 仍然存在(直到它过期并被垃圾回收),但是如果你告诉 git log

  $ git log --graph --decorate --oneline master 67dec43 
* 68c51f3(HEAD,master)替换为修改提交
| * 67dec43changesme提交
| /
* 9c37840先前提交

您有一个分支从先前的提交中脱颖而出,新的替换提交中的 master 标签和未标记的分支上的修改提交。



让我们再次这样做,这次隐藏了。我从修改提交中的 f3 中的已知错误文件开始。然后,我放入第二个(但仍然不正确) f3 并运行 git stash 。最后,我修正了 f3 真实,并使用 - 修改。存储保持对现在未标记的分支的引用,因为存储新提交(实际上是两个)。以下是最后几步:

  $ git log --graph --decorate --oneline 
* 3c97241( WPS on master:67dec43changesmecommit
| \
| * f3a50e9在master上的索引:67dec43修改提交
| /
* 67dec43(HEAD,master)修改提交
* 9c37840前一提交
* 84408ef基
$ echo'f3'的更好变化>> f3
$ git add f3
$ git commit --amend -m'replacementmecommitme'commit'
$ git log --graph --decorate --oneline --all
* c1f1042(HEAD,master)替换为修改提交
| * 3c97241(refs / stash)WIP on master:67dec43amendmecommit
| | \
| | * master3上的f3a50e9索引:67dec43修改提交
| | /
| * 67dec43changesme提交
| /
* 9c37840先前提交
* 84408ef基准

如果您尝试应用存储空间,将会有冲突(因为存储空间更改文件 f3 ,使用我的中间存储空间,不完全不好,但是b
$ b

  $ git stash apply 
git stash apply
自动合并f3
CONFLICT(内容):在f3中合并冲突
$ git reset --hard master
HEAD现在位于c1f1042替换为modificationsme提交
$ git stash apply --index
自动合并f3
CONFLICT(内容):合并f3中的冲突
索引不是未分离的。
$ git reset --hard master
HEAD现在位于c1f1042替换为修改提交

这些与其他任何冲突一样,例如 cherry-pick merge ,你可以用同样的方法解决它们。



如果你喜欢,你可以在修改提交上粘贴一个分支或标记标签:

  $ git branch master-old 67dec43 
$ git log --graph --oneline --decorate --all
* c1f1042 (HEAD,master)替换为修改提交
| * 3c97241(refs / stash)WIP on master:67dec43amendmecommit
| | \
| | * master3上的f3a50e9索引:67dec43修改提交
| | /
| * 67dec43(master-old)changesmecommit
| /
* 9c37840 previous commit
* 84408ef base

现在可以很方便地参考。然后你可以检查出来,并且 git stash pop --index 那个特定的藏匿处;这是保证工作(因此 pop 是安全的,尽管您可能希望应用,直到您完成其中几个)。另请参阅下面的使用 git存储分支,它自动执行此操作。

/ h2>

让我们稍微退一步。我想展示一个简单的例子,只有三个文件。



让我们制作一个临时目录和git repo并提交一个起点,包含三行一行的文件:

  $ mkdir / tmp / tt; cd / tmp / tt; git init 
...#创建文件f1,f2,f3; git add ...
$ git commit -m base
[master 84408ef] base
3个文件已更改,3个插入(+)
创建模式100644 f1
创建模式100644 f2
创建模式100644 f3
$ ls
f1 f2 f3
$ cat f1 f2 f3
此文件保持不变
此文件更改为指数
此文件在WIP
中变化

现在让我们进行更改:

  $ echo for f2>> F2; git add f2 
$ echo for f3>> f3

此时, f2 被更改并演示:

  $ git diff --cached 
diff --git a / f2 b / f2
索引78991d3..3a2f199 100644
--- a / f2
+++ b / f2
@@ -1 +1,2 @@
此文件在索引
+ f2以上

f3

  $ git diff 
diff --git a / f3 b / f3
index d5943ba..188fe9b 100644
--- a / f3
+++ b / f3
@@ -1 +1,2 @@
此文件在WIP中的变化
+ f3

这里 diff - 缓存显示暂存的内容(位于索引中), diff 不存在 - 缓存显示暂停的东西。



现在,让我们 git stash (默认op为保存)。 隐藏会向repo添加两个提交。第一个是 到目前为止所展示的东西(如果没有任何变化, stash 在无变化的提交中强制执行),第二个是合并提交,即加工作目录。所以:

pre $ code $ git stash
保存的工作目录和索引状态master上的WIP:84408ef base
HEAD现在在84408ef基地
$ git log --graph --oneline --decorate --all
* 753a6c8(refs / stash)在主人在制品上:84408ef base
| \
| * 36b23f2 index master:84408ef base
| /
* 84408ef(HEAD,master)base

第一个,在master 上的索引,将我改为 f2

  $ git show 36b23f2 
[snip]
diff --git a / f2 b / f2
index 78991d3 .3a2f199 100644
--- a / f2
+++ b / f2
@@ -1 +1,2 @@
此文件在索引$ b中变化$ b + more for f2

第二个有两个变化( f2 f3 ),但是是合并提交,因此 git show 仅显示组合diff显示 f3

  $ git show 753a6c8 
[snip ]
diff --cc f3
index d5943ba,d5943ba..188fe9b
--- a / f3
+++ b / f3
@@@ -1 ,1 -1,1 +1,2 @@@
此文件在WIP中更改
++更多用于f3

(另外:如果你想比较任何合并 M 对每个父对象,使用 git show -m M 。例如, git show -m 753a6c8 第一差异 753a6c8 ^ 1 -vs - 753a6c8 ,然后 753a6c8 ^ 2-vs - 753a6c8 。)



什么是 - keep-index 的东西?



通常情况下,做一个 git存储,你有一个干净的目录,所以没有什么可以重新存储,因为它是:

  $ git status 
#分支master
没有提交,工作目录干净
$ git存储
没有本地更改以保存

但是您要求存入 - keep-index 。这仍然使通常的存储条目,但然后它提取索引提交的内容,将其放入工作目录和索引。让我们从当前存储中弹出,查看状态( git status - pop 是否存在状态自动 - 然后 git log --graph --oneline --decorate --all ),然后看到我们回到了工作正在进行状态,但这次没有任何进展:

  $ git stash pop 
...
$ git log --graph --oneline --decorate --all
* 84408ef(HEAD,master)base

现在我们重新执行 f2 并重新执行存储,但是这次, - keep-index

  $ git add f2 
$ git stash save --keep-index
保存的工作目录和索引状态在主设备上WIP:84408ef base
HEAD现在是84408ef base

看起来和以前一样......但不完全:

  $ git status 
#在分支主机上
#要提交的更改:
#(使用git reset HEAD< file> ...取消)

#修改:f2

使用 git log --graph --oneline --decorate --all ,你将会看到基本上和以前一样的东西(使用不同的提交哈希): stash 提交索引,然后提交工作树的合并提交。但是这一次它也会重新提取索引,所以现在你需要改变被提交。这只是 f2 ,而不是 f3



这意味着你可以(稍微毫无意义地) git stash save 。你做到了!让我们在前后使用这个log-graph-one-line-decorate(我使用它很多):

  $ git log --graph --oneline --decorate --all 
* 7efe9a6(refs / stash)WIP on master:84408ef base
| \
| * 76c840e在master上的索引:84408ef base
| /
* 84408ef(HEAD,master)base
$ git stash save
保存的工作目录和索引状态master上的WIP:84408ef base
HEAD现在在84408ef基础
$ git log --graph --oneline --decorate --all
$ git lola
* eb383e0(参考/隐藏)WIP on master :84408ef base
| \
| * master上的aba15e6索引:84408ef base
| /
* 84408ef(HEAD,master)base

乍一看,前后看起来一样。但仔细观察在SHA-1 ID处。他们改变了!在第二个 git stash save 之前, refs / stash 命名为commit 7efe9a6 。现在它名为 eb383e0



reflog(或者,注意,它变得复杂)



好吧,这不是 坏,但现在你必须了解reflog。其他隐藏去哪了?答案是,它被推送并且已经消失在reflog中。还有一点额外的皱纹:藏匿不是普通的分支或标签。相反,它在 refs / stash 中。所以这里有一种方法可以看到它,使用 git log -g ,这意味着查看reflogs:

  $ git log -g --oneline refs / stash 
eb383e0 refs / stash @ {0}:在主人的WIP上:84408ef base
7efe9a6 refs / stash @ {1 }:在主人的WIP上:84408ef base



Aha,它们都是 7efe9a6 eb383e0 。他们有用户表单全名(例如 refs / stash @ {1} ),这些都很麻烦。幸运的是, stash 的作用(除非你命名一个分支 stash )来获得最顶层 {0} one,并且可以为另一个写 stash @ {1} 。或者我们可以进行全面的自动化:

  $ git log -g --pretty = format:%H refs / stash 

这会抛出它们的完整散列,我们可以用它作为 git的参数为了得到这个:

  $ git log --graph --decorate  -oneline --decorate $(git log -g --pretty = format:%H refs / stash)
* eb383e0(refs / stash)WIP on master:84408ef base
| \
| * master上的aba15e6索引:84408ef base
| /
| * 7efe9a6在主人的在制品:84408ef基础
| | \
| / /
| * 76c840e master:84408ef base
| /
* 84408ef(HEAD,master)base

这只是为了看看还有什么在那里,在回购中。



(或者,您可以使用 gitk ,就像你一样,看看它们。 gitk 足够聪明,可以查找存储引用日志。)



(另外:您还可以使用 git reflog show refs / stash reflog show 子命令只运行 git log -g --oneline 。)



再回到我们的问题, / h3>

现在我们已经完成了第一个 git stash save --keep-index ,然后是一个毫无意义的 git stash保存,现在是什么?



那么,我们可以 git stash pop

  $ git stash pop 
#在分支大师
#没有为commit提交更改:
#(使用git add< file> ...来更新将提交的内容)
#(使用git checkout - < file> ...放弃工作目录中的更改)

#修改:f2

no添加到提交的更改(使用git add和/或git commit -a)
丢弃的refs / stash @ {0}(eb383e050d150a8ce5b69a3662849ffdd7070c89)

f3 发生了什么?正如我们前面提到的,第二个 git stash只保存了保存了 的保留索引,即只改变了 f2 。我们需要的是回到第一藏匿处。

  $ git stash pop 
错误:对以下文件的本地更改将被合并覆盖:
f2
请在提交更改或隐藏它们之前进行合并。
正在取消

这没什么帮助,是吗? : - )



如果您不确定自己在做什么,现在是制作保存东西分支的好时机(您可以稍后再删除它)。只需 git checkout -b help-me-spock 或其他,添加和提交。这些东西现在在一个分支上,更容易跟踪。但是我们知道我们在做什么,并且在其他存储区中有 f2 。所以我们可以把它擦掉:

  $ git reset --hard 

现在我们回到了原来的状态,如果我们只做了一个 git stash save ,没有 - keep-index :我们在 master 上,工作目录干净,保存单个存储。我们可以 git存储列表它, git存储显示它等等。所以现在:

  $ git stash pop --index 
#分支主
#更改为提交:
#(使用git reset HEAD< file> ...停用)

#修改:f2

#对于提交:
#(使用git add< file> ...来更新将提交的内容)
#(使用git checkout - < file> ...工作目录中的变化)

#修改:f3

丢弃的refs / stash @ {0}(7efe9a65c44156921bbbcb6a3df4edc5cb44492b)

,我们已经把所有东西都回来了。 (或者,如果没有 - index stash 将只将所有更改应用于工作目录,而不是恢复索引)



使用 git stash apply



关于 git stash pop 的好处是它适用,然后删除最顶端的存储条目。令人讨厌的是,它适用,然后下降条目。如果您使用 git stash apply ,它会挂起它。



除此之外,这非常方便您拼错 - index - keep-index (我在输入时多次输入),或把它留下来,稍后再决定它会很好用。您可以 git reset --hard 并重新执行 apply



如果您完成了一个存储条目, git stash drop entry 会将其从reflog中删除。例如,假设你做了 git stash apply --index'stash @ {1}',然后决定它是否好,并且想要 add 和/或 commit 它,然后忘掉那个藏匿处。然后你可以 git stash drop'stash @ {1}'。缺点是这会重新编号: stash @ {2} 变为 stash @ {1} ,以及等等。我发现将它们全部放在一起并且使用 git stash clear 可以一次性清除所有这些内容。



等一下,这些是什么 - index -es?



默认, git stash apply git stash pop 取已保存的索引(更改已提交进行提交)和work-in -progress(更改不会提交进行提交),并将它们同时作为工作进行中。通常情况下,这很好,但如果你仔细地进行了一些比赛并将其他人暂时搁置,那么你很可能需要这些。对应用(和 pop )的 - index 参数试图做到这一点。有时会变得太难。在这种情况下,你有两个选择:省略 - index ,或者使用 git stash branch



使用 git存储分支



我在上面提到过,在关于修改后的提交与stashes的部分,你可以添加一个新的分支标签到一个存放它的提交,然后 apply 甚至 pop 相应的存储空间, - index ,它会始终工作。原因很简单:存储是索引和WIP的合并提交,对应于它们的提交。如果你检查了这个提交(作为一个分离的HEAD),索引和WIP 干净地应用。

所以,假设你添加在提交时出现一个新的分支名称,并获取新分支( git checkout -b newname )。现在,使用 - index 应用(并弹出)藏匿处:您现在处于与您第一次使用时相同的状态运行 git stash save ,除了分支名称不同。 And that’s what git stash branch does: you give it a new branch name and tell it which stash to use (the default is refs/stash, A.K.A. stash@{0}). It uses that stash entry to find the parent commit, attaches the branch name there, and then does a git stash pop --index.



At this point you can use git status, git diff --cached, git diff, etc., to see what’s in the index and what’s not, decide what else if anything to add, then git commit to add new stuff to the new branch you’ve created.


So I had some unstaged changes and some staged ones. I issued

Welcome to Git (version 1.8.3-preview20130601)
$ git stash save --keep-index
Saved working directory and index state WIP on master: ab0d18d Setup of alarms f
or network service + Logging exceptions + long arithmetic
HEAD is now at ab0d18d Setup of alarms for network service + Logging exceptions
+ long arithmetic
$ git stash
Saved working directory and index state WIP on master: ab0d18d Setup of alarms f
or network service + Logging exceptions + long arithmetic
HEAD is now at ab0d18d Setup of alarms for network service + Logging exceptions
+ long arithmetic

Then I hit amend last commit in the gui to split the ab0d18d commit into smaller ones. I unstaged some of the files and I hit

$ git stash save --keep-index
Saved working directory and index state WIP on master: ab0d18d Setup of alarms f
or network service + Logging exceptions + long arithmetic
HEAD is now at ab0d18d Setup of alarms for network service + Logging exceptions
+ long arithmetic

Repeated the above procedure :

$ git stash save --keep-index
Saved working directory and index state WIP on master: ab0d18d Setup of alarms f
or network service + Logging exceptions + long arithmetic
HEAD is now at ab0d18d Setup of alarms for network service + Logging exceptions
+ long arithmetic

Then I edited the commit message and I committed. Then I issued git stash pop to start getting back my stashes and committing them one by one.

$ git stash pop
# On branch master
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       TODO.txt
nothing added to commit but untracked files present (use "git add" to track)
Dropped refs/stash@{0} (43facd88ea3548071b196324523bd017d680d6dd)

Disaster !

I had backups happily (long live dropbox)

2 questions :

  • what did I do wrong ?
  • how should one recover from such a scenario ?

EDIT : gitk after I restored the changes (the amended commit is the SETUP ALARMS one)

EDIT

Found a way to reproduce the problem - will be amending it (probably only the last part is needed - was trying to reproduce my original scenario exactly) but for starters :

mkdir test_stash; cd  test_stash;git init
echo f1 >> f1 ; echo f2 >> f2 ; echo f3 >> f3 ; echo f4 >> f4 ; echo f5 >> f5
git add f1 f2 f3 f4 f5
git commit -m base123
echo change f1 to be amended >> f1
echo change f2 to be amended >> f2
echo change f3 to be amended >> f3
git add .
git commit -m tobeamended123
echo change f4 >> f4; git add f4
echo change f5 >> f5
git stash save --keep-index
git stash
git gui &

Now hit amend commit in the gui. Not sure to which command it corresponds to but git commit --amend does not do the trick.

While in the amend state unstage file f3 in the gui again (click on it so it moves on the unstaged area - would be git reset HEAD f3 but this does not work either) then

git stash save --keep-index
git commit -m amended # not really amended - a new commit altogether
git stash pop

Getting :

# # On branch master
nothing to commit, working directory clean
Dropped refs/stash@{0} (898687d73b65ccc9e10cd826bc12fda1a4759651)

Expecting : the f3 modifications to show up

解决方案

/I'm keeping the first long reply below (might try to move it to a new question later), but now that there's a "reproduce example" I'll go through that. Let me make a list of points here though.

  1. git stash always stashes both the index and the work dir. One might think --keep-index makes it stash more, or changes the way the stashed value is handled on a pop. It doesn't! Both git stash apply and git stash pop mix together the separated index change by default. Adding --keep-index does not change this. Only the --index argument to apply and pop tries to avoid mixing them.

  2. The "work directory" that git stash saves amounts, in effect, to the change from the current HEAD. This means that if the index has a change from HEAD, but the current work directory does not, there's really no change saved in the "WIP on branch..." commit. (This is, I think, a bug in git stash. I have sent a test case and possible fix to the git mailing list. For "normal" cases it's fine, but if you've split out some parts and then want to recover your exact state later with git stash branch, it drops working directory state. And it's causing your problem here.)

  3. Applying a stash tries to make changes to the current state that mirror the changes in the stashed state. This can be complicated, because the current state is not necessarily anything like it was when you saved the stash.

Here's what git-gui is doing. At the time you fire it up you have this (actual commit numbers will of course vary). The unlabeled "WIP on master" is the "first" stash, now stash@{1}.

$ git stash list
stash@{0}: WIP on master: c93c8fe tobeamended123
stash@{1}: WIP on master: c93c8fe tobeamended123
$ git log --decorate --oneline --graph --all 'stash@{1}'
*   3d01942 (refs/stash) WIP on master: c93c8fe tobeamended123
|\  
| * 6be9135 index on master: c93c8fe tobeamended123
|/  
| *   de8038c WIP on master: c93c8fe tobeamended123
| |\  
|/ /  
| * 3db6cfc index on master: c93c8fe tobeamended123
|/  
* c93c8fe (HEAD, master) tobeamended123
* 828d5cf base123

Now in git gui, when you select "amend last commit", it finds the ref for the HEAD commit (c93c8fe, in my case). It does not actually do anything to it (yet). But as soon as you click on f3 to unstage it, it does something: it grabs the previous version of f3 (I'm not sure what the gui uses underneath, my guess would be HEAD^'s copy) and stuffs it into the index. If you examine f3 it still has the extra line in it, but if you git show :0:f3 to see the version in the index, it no longer has that line.

Note that no refs have changed due to gui-mouse-clicks, and there are no new commits. All the action has taken place inside the index.

Next, you went back to the command line and ran:

$ git stash save --keep-index

This made a third pair of commits, one with the index and one with the current directory. The index version has the extra line in f1 and f2 and lacks the extra line in f3. The current-directory version should (one would think) have the extra line in all three files—but, alas, it does not, because git stash save compares current dir vs HEAD commit, and the extra line is there in the HEAD commit, so it's not in the stashed version.

Unforunately, you used that --keep-index argument, so now the working directory version is the same as the stashed index version. File f3 no longer has the extra line.

From here on, the problem persists (the change is gone, --keep-index tossed it). You can of course recover it from the original commit ("tobeamended123"). But that's where things went wrong in this case: the command-line stash saved the index, and then compared the work directory against HEAD, which had not changed, so did not save the (non-change) to f3.


I don't see disaster, but I see something confusing, which I bet confused you. I don't know why you used --keep-index above. (In fact, I'm not sure what use-case --keep-index might be intended for1, and it seems to me that apply and pop should probably default to --index, but that's another matter entirely....) And, you made four total stash "pushes", and only "popped" one, leaving three to go.

[1I found the intended use-case, right there in the documentation: for testing what is currently in the index, before committing it. But wait, huhwha?, --keep-index does commit it, on the stash ref. You might as well just commit anyway, using git checkout -b test-stash to keep it safely segregated until you're happy with it. If you test it and it fails and you need to modify it, that stash is going to have conflicts. If you test and it works you can just pull / fast-forward-merge the commit that worked, into your earlier branch.]

The "tl;dr" short answer

Run git stash list. You'll see a list of:

stash@{0}: WIP on master: ab0d18d Setup of alarms ...
stash@{1}: WIP on master: ...

items. Use git stash apply --index 'stash@{n}' (the --index is optional) to try to apply each saved stash by name-and-number, without popping any of them. It's a stack, with stash@{0} the most recently pushed and (by this point) stash@{3} the first (longest-ago) pushed.

The apply-without-pop means you can git reset --hard to get back to master and ready to git stash apply a different stash. (Be sure you start the whole sequence with a clean work directory, perhaps by adding another git stash, although that could get confusing again. :-) )

If you've made a particularly big mess, you can use use git stash branch name 'stash@{n}'. This is a big, fast, effective hammer whose main drawback is that you have to invent a branch name. (You can git stash show the stashes to see what's in them, to help you come up with names.) Don't let this scare you, as you can always rename the branch or even delete it later. See the long description for exactly how this works.

When you're all done with all your stashes, use git stash clear to wipe them all out.

Regarding git commit --amend vs git stash

These are actually somewhat independent. The commit --amend works on a commit-chain based on whatever branch you're on. Let's say you're on master and the chain looks like this (in git log --graph --oneline --decorate, or gitk):

* 67dec43 (HEAD, master) "amendme" commit
* 9c37840 previous commit

You edit and git add some things—I will change file f3 and add it—and then run git commit --amend. This takes the index and makes a new commit, but the new commit's parent is one back from where master was, i.e., the previous commit above. Now the log output looks like this:

* 68c51f3 (HEAD, master) replacement for "amendme" commit
* 9c37840 previous commit

What you can't see (because there's no branch label on it) is that 67dec43 is still in there (until it expires and gets garbage collected), but if you tell git log to look there it will:

$ git log --graph --decorate --oneline master 67dec43
* 68c51f3 (HEAD, master) replacement for "amendme" commit
| * 67dec43 "amendme" commit
|/  
* 9c37840 previous commit

You have a branch coming off "previous commit", with the master label at the new replacement commit and the "amendme" commit on an unlabeled branch.

Let's do this again, with a stash in place this time. I start with a "known bad" file in f3 in the "amendme" commit. I then put in a second (but still not right) f3 and run git stash. Finally, I fix f3 "for real" and use --amend. The stash keeps a reference to the now-unlabeled branch, because a stash is a new commit (really, two). Here are the last few steps:

$ git log --graph --decorate --oneline
*   3c97241 (refs/stash) WIP on master: 67dec43 "amendme" commit
|\  
| * f3a50e9 index on master: 67dec43 "amendme" commit
|/  
* 67dec43 (HEAD, master) "amendme" commit
* 9c37840 previous commit
* 84408ef base
$ echo 'better changes for f3' > f3
$ git add f3
$ git commit --amend -m 'replacement for "amendme" commit'
$ git log --graph --decorate --oneline --all
* c1f1042 (HEAD, master) replacement for "amendme" commit
| *   3c97241 (refs/stash) WIP on master: 67dec43 "amendme" commit
| |\  
| | * f3a50e9 index on master: 67dec43 "amendme" commit
| |/  
| * 67dec43 "amendme" commit
|/  
* 9c37840 previous commit
* 84408ef base

If you try to apply the stash, there will be a conflict (because the stash changes file f3, with my intermediate, "not completely bad, but not better either" version):

$ git stash apply
git stash apply 
Auto-merging f3
CONFLICT (content): Merge conflict in f3
$ git reset --hard master
HEAD is now at c1f1042 replacement for "amendme" commit
$ git stash apply --index
Auto-merging f3
CONFLICT (content): Merge conflict in f3
Index was not unstashed.
$ git reset --hard master
HEAD is now at c1f1042 replacement for "amendme" commit

These are the same as any other conflict when bringing commits in, such as cherry-pick or merge, and you resolve them the same way.

If you like, you can stick a branch or tag label on the "amendme" commit:

$ git branch master-old 67dec43
$ git log --graph --oneline --decorate --all
* c1f1042 (HEAD, master) replacement for "amendme" commit
| *   3c97241 (refs/stash) WIP on master: 67dec43 "amendme" commit
| |\  
| | * f3a50e9 index on master: 67dec43 "amendme" commit
| |/  
| * 67dec43 (master-old) "amendme" commit
|/  
* 9c37840 previous commit
* 84408ef base

and now it's easily available for reference. You can then check it out and git stash pop --index that particular stash; this is guaranteed to work (hence the pop is safe, although you might want to apply anyway until you've done several of these). See also "Using git stash branch" below, which automates this.

How stash works, the long version

Let's step back a bit. I want to show a simplified example, with just three files.

Let's make a temp dir and git repo and commit a starting point, with three one-line-long files:

$ mkdir /tmp/tt; cd /tmp/tt; git init
... # create files f1, f2, f3; git add ...
$ git commit -m base
[master 84408ef] base
 3 files changed, 3 insertions(+)
 create mode 100644 f1
 create mode 100644 f2
 create mode 100644 f3
$ ls
f1      f2      f3
$ cat f1 f2 f3
this file stays the same
this file changes in the index
this file changes in the WIP

Now, let's make the changes happen:

$ echo more for f2 >> f2; git add f2
$ echo more for f3 >> f3

At this point, f2 is changed and staged:

$ git diff --cached
diff --git a/f2 b/f2
index 78991d3..3a2f199 100644
--- a/f2
+++ b/f2
@@ -1 +1,2 @@
 this file changes in the index
+more for f2

and f3 is changed but not staged:

$ git diff
diff --git a/f3 b/f3
index d5943ba..188fe9b 100644
--- a/f3
+++ b/f3
@@ -1 +1,2 @@
 this file changes in the WIP
+more for f3

Here diff --cached shows the staged stuff (in the index) and diff without --cached shows the unstaged stuff.

Now, let's git stash (the default op is to save). The stash will add two commits to the repo. The first one is just the stuff staged so far (if there's nothing staged, stash forces in a no-changes commit) and the second is a merge commit, of that-plus-work-dir. So:

$ git stash
Saved working directory and index state WIP on master: 84408ef base
HEAD is now at 84408ef base
$ git log --graph --oneline --decorate --all
*   753a6c8 (refs/stash) WIP on master: 84408ef base
|\  
| * 36b23f2 index on master: 84408ef base
|/  
* 84408ef (HEAD, master) base

That first one, index on master, has my change to f2:

$ git show 36b23f2
[snip]
diff --git a/f2 b/f2
index 78991d3..3a2f199 100644
--- a/f2
+++ b/f2
@@ -1 +1,2 @@
 this file changes in the index
+more for f2

The second has both changes (f2 and f3), but is a merge commit, so git show shows a combined diff, only showing f3:

$ git show 753a6c8
[snip]
diff --cc f3
index d5943ba,d5943ba..188fe9b
--- a/f3
+++ b/f3
@@@ -1,1 -1,1 +1,2 @@@
  this file changes in the WIP
++more for f3

(Aside: if you want to compare any merge M against each parent, use git show -m M. For instance, git show -m 753a6c8 first diffs 753a6c8^1-vs-753a6c8, then 753a6c8^2-vs-753a6c8.)

What's with this --keep-index thing?

Normally, after you do a git stash, you have a clean directory so there's nothing to "re-stash", as it were:

$ git status
# On branch master
nothing to commit, working directory clean
$ git stash
No local changes to save

But you asked stash to --keep-index. That still makes the usual stash entry, but then it extracts the contents of the index commit, putting that into both the working directory and the index. Let's pop off the current stash, look at the state (git statuspop does the status automatically—and then git log --graph --oneline --decorate --all), and see that we're back to the work in progress state but there's nothing staged this time:

$ git stash pop
...
$ git log --graph --oneline --decorate --all
* 84408ef (HEAD, master) base

Now let's re-stage f2 and re-do the stash save, but this time, with --keep-index:

$ git add f2
$ git stash save --keep-index
Saved working directory and index state WIP on master: 84408ef base
HEAD is now at 84408ef base

Looks the same as before ... but not quite:

$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   f2
#

Use git log --graph --oneline --decorate --all and you'll see basically the same thing (with different commit hashes) as before: stash committed the index, then committed a merge-commit of the work tree. But this time it also re-extracted the index, so now you have "changes to be committed". This is just f2, not f3.

This means you can (somewhat pointlessly) git stash save again. And you did! Let's use that log-graph-one-line-decorate thing (I use it a lot), both before and after:

$ git log --graph --oneline --decorate --all
*   7efe9a6 (refs/stash) WIP on master: 84408ef base
|\  
| * 76c840e index on master: 84408ef base
|/  
* 84408ef (HEAD, master) base
$ git stash save
Saved working directory and index state WIP on master: 84408ef base
HEAD is now at 84408ef base
$ git log --graph --oneline --decorate --all
$ git lola
*   eb383e0 (refs/stash) WIP on master: 84408ef base
|\  
| * aba15e6 index on master: 84408ef base
|/  
* 84408ef (HEAD, master) base

Looks the same before-and-after, at first blush. But look closely at the SHA-1 IDs. They changed! Before the second git stash save, refs/stash named commit 7efe9a6. Now it names eb383e0!

The reflog (or, pay attention, it's getting complicated)

OK, it's not that bad, but now you have to learn about the "reflog". Where did the other stash go? The answer is, it's been "pushed" and has disappeared into the reflog. There's a minor extra wrinkle, too: the "stash" is not a regular branch or tag. Instead, it's in refs/stash. So here's one way to see it, using git log -g, which means "look at reflogs":

$ git log -g --oneline refs/stash
eb383e0 refs/stash@{0}: WIP on master: 84408ef base
7efe9a6 refs/stash@{1}: WIP on master: 84408ef base

Aha, there they are, both 7efe9a6 and eb383e0. They have "user form full names" (refs/stash@{1} for instance) that are a bit of a pain to use. Fortunately stash works (unless you name a branch stash) to get the "top-most" {0} one, and you can write stash@{1} for the other. Or we can go for full-blown automation:

$ git log -g --pretty=format:%H refs/stash

This dumps out their full hashes, which we can use as arguments to git log --graph --decorate, to get this:

$ git log --graph --oneline --decorate $(git log -g --pretty=format:%H refs/stash)
*   eb383e0 (refs/stash) WIP on master: 84408ef base
|\  
| * aba15e6 index on master: 84408ef base
|/  
| *   7efe9a6 WIP on master: 84408ef base
| |\  
|/ /  
| * 76c840e index on master: 84408ef base
|/  
* 84408ef (HEAD, master) base

That's all just to see what's still "in there", in the repo.

(Or, of course, you can use gitk, as you did, to see them. gitk is smart enough to look for the stash reflogs.)

(Aside: you can also use git reflog show refs/stash. The reflog show sub-command just runs git log -g --oneline.)

Back to our problem, again

Now that we've done a first git stash save --keep-index and then a pointless git stash save, now what?

Well, we can git stash pop to get the most recent (top-most of stack) stash back:

$ git stash pop
# On branch master
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   f2
#
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (eb383e050d150a8ce5b69a3662849ffdd7070c89)

What happened to f3? As we noted earlier, the second git stash save saved only the "kept index", i.e., just the changed f2. What we need is to get back to the first stash.

$ git stash pop
error: Your local changes to the following files would be overwritten by merge:
    f2
Please, commit your changes or stash them before you can merge.
Aborting

That's not much help, is it? :-)

If you're not sure what you're doing, now is a good time to make a "save stuff" branch (you can always delete it later). Just git checkout -b help-me-spock or whatever, add, and commit. This stuff is now on a branch and easier to keep track of. But we know what we are doing, and that we have f2 in the other stash. So we can just wipe this out:

$ git reset --hard

Now we're back to the state we would have had, if we had done just one git stash save, without --keep-index: we're on master, with the working directory clean, and a single stash saved. We can git stash list it, git stash show it, and so on. So now:

$ git stash pop --index
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   f2
#
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   f3
#
Dropped refs/stash@{0} (7efe9a65c44156921bbbcb6a3df4edc5cb44492b)

and we have everything back. (Or, without --index, stash will just apply all the changes to the working directory, rather than restoring index and work-dir.)

Using git stash apply

The nice thing about git stash pop is that it applies and then drops the top-most stash entry. The annoying thing is, it applies and then drops the entry. If you use git stash apply instead, it hangs on to it.

Among other things, that's quite handy if you misspell --index as --keep-index (I did more than once, while typing this), or leave it out and later decide it would have been nice to use it. You can git reset --hard and re-do the apply.

If you're done with a stash entry, git stash drop entry will remove it from the reflog. For instance, suppose you do git stash apply --index 'stash@{1}' and then decide it's all good and want to add and/or commit it and then forget about that stash. You can then git stash drop 'stash@{1}'. The drawback is that this renumbers the rest: what was stash@{2} becomes stash@{1}, and so on. I find it's sometimes easier to keep them all around and use git stash clear to get rid of all of them at once, at the end.

Wait a minute, what's with these --index-es?

By default, git stash apply and git stash pop take the saved index ("changes staged for commit") and work-in-progress ("changes not staged for commit") and put them both into effect as work-in-progress only. Often that's fine, but if you've carefully staged some bits and left others unstaged, you might well want all that back. The --index argument to apply (and pop) tries to do that. Sometimes it turns out to be "too hard". In that case, you have two options: leave out --index, or use git stash branch.

Using git stash branch

I mentioned above, in the section on amended commits vs stashes, that you can add a new branch label to a commit that has a stash on it, and then apply or even pop the corresponding stash, with --index, and it will always work. The reason is simple: the stash is a merge commit of the index and WIP, corresponding to the commit they're on. If you check that commit out (as a "detached HEAD"), the index and WIP will apply cleanly.

So, suppose you add a new branch name at the commit in question, and get on the new branch (git checkout -b newname). Now apply (and pop-off) the stash, using --index: you're now in exactly the same state you were when you first ran git stash save, except that the branch has a different name. And that's what git stash branch does: you give it a new branch name and tell it which stash to use (the default is refs/stash, A.K.A. stash@{0}). It uses that stash entry to find the parent commit, attaches the branch name there, and then does a git stash pop --index.

At this point you can use git status, git diff --cached, git diff, etc., to see what's in the index and what's not, decide what else if anything to add, then git commit to add new stuff to the new branch you've created.

这篇关于git存储修改上次提交(在gui中) - 弹出窗口没有任何结果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆