1

所以我有一些未分阶段的更改和一些分阶段的更改。我发出

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

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

$ 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 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 pop我下令开始取回我的藏匿处并一一提交。

$ 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)

灾难 !

我很高兴有备份(保管箱万岁)

2个问题:

  • 我做错了什么 ?
  • 应该如何从这种情况中恢复过来?

编辑:我恢复更改后的 gitk(修改后的提交是 SETUP ALARMS 之一)

在此处输入图像描述

编辑

找到了一种重现问题的方法 - 将对其进行修改(可能只需要最后一部分 - 试图准确重现我的原始场景)但对于初学者:

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 &

现在在 gui 中点击 amend commit 。不确定它对应于哪个命令,但git commit --amend不能解决问题。

在此处输入图像描述

在修改状态下再次在 gui中取消暂存文件 f3 (单击它以便它在未暂存区域上移动 - 会git reset HEAD f3,但这也不起作用)然后

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

得到:

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

期待:f3修改出现

4

1 回答 1

7

/我在下面保留第一个长回复(稍后可能会尝试将其移至新问题),但现在有一个“重现示例”,我将通过它。不过,让我在这里列出要点。

  1. git stash总是隐藏索引工作目录。有人可能会认为--keep-index让它存储更多,或者改变存储值在pop. 它没有!默认情况下,两者都git stash apply混合git stash pop在一起分离的索引更改。添加--keep-index 不会改变这一点。只有--index论点applypop试图避免混合它们。

  2. 保存金额的“工作目录”,git stash实际上是当前HEAD. 这意味着,如果索引从 更改HEAD,但当前工作目录没有,则“WIP on branch ...”提交中实际上没有保存更改。(我认为这是git stash.稍后状态git stash branch,它会丢弃工作目录状态。这会导致您的问题。)

  3. 应用 stash 会尝试对当前 state进行更改,以反映stashed state中的更改。这可能很复杂,因为当前状态不一定与您保存存储时的状态相同。

这就是git-gui正在做的事情。在你启动它的时候,你有这个(实际的提交数量当然会有所不同)。未标记的“WIP on master”是“第一个”存储,现在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

现在git gui,当您选择“修改上次提交”时,它会找到 HEAD 提交的 ref(c93c8fe在我的情况下)。它实际上并没有对它任何事情(还)。但是,一旦您单击f3取消暂存它,它就会执行一些操作:它会抓取以前版本的f3(我不确定 gui 在下面使用什么,我猜应该是HEAD^' 副本)并将其塞入 index。如果您检查f3它仍然有额外的行,但如果您git show :0:f3在索引中查看版本,它不再有该行。

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

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

$ git stash save --keep-index

这产生了第三对提交,一个带有索引,一个带有当前目录。索引版本在f1f2中有额外的行,在f3. 当前目录版本应该(有人会认为)在所有三个文件中都有额外的行 - 但是,唉,它没有,因为git stash save比较当前目录与HEAD提交,并且额外的行在HEAD提交中,所以它不在隐藏版本。

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

从这里开始,问题仍然存在(更改消失了,--keep-index扔掉了)。您当然可以从原始提交(“tobeamended123”)中恢复它。但这就是在这种情况下出现问题的地方:命令行stash保存了索引,然后将工作目录与HEAD未更改的f3.


我没有看到灾难,但我看到了一些令人困惑的东西,我敢打赌这会让你感到困惑。我不知道你为什么在--keep-index上面使用。(事实上​​,我不确定1--keep-index的用例可能是什么,在我看来,并且可能应该默认为,但这完全是另一回事....)而且,您总共进行了 4 次存储“推送” ”,只“弹出”一个,剩下三个。applypop--index

[ 1我在文档中找到了预期的用例:用于在提交之前测试索引中当前的内容。但是等等,嗯? ,在裁判上--keep-index 确实提交了。stash无论如何,您最好还是提交,git checkout -b test-stash用于将其安全隔离,直到您满意为止。如果您对其进行测试并且它失败并且您需要对其进行修改,那么该存储将会发生冲突。如果您测试并且它有效,您可以将有效的提交拉/快进合并到您之前的分支中。]

“tl; dr”简短回答

运行git stash list。您将看到以下列表:

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

项目。使用(可选)尝试按名称和编号应用每个已保存的存储,而不弹出任何存储。这是一个堆栈,最近推送和(此时)第一个(最久之前)推送。git stash apply --index 'stash@{n}'--indexstash@{0}stash@{3}

apply-without-pop 意味着您可以git reset --hard返回master并准备好git stash apply使用不同的存储区。(确保你从一个干净的工作目录开始整个序列,也许通过添加另一个git stash,尽管这可能会再次令人困惑。:-))

如果你弄得特别大,可以使用 use 。这是一个大、快、有效的锤子,它的主要缺点是你必须发明一个分支名称。(您可以在 stashes 中查看其中的内容,以帮助您想出名称。)不要让这吓到您,因为您可以随时重命名分支,甚至稍后将其删除。请参阅详细说明以了解其工作原理。git stash branch name 'stash@{n}'git stash show

当你用完所有的藏匿处时,用它git stash clear来清除它们。

关于git commit --amendvsgit stash

这些实际上是有些独立的。commit --amend基于您所在的任何分支的提交链上的工作。假设您正在运行master并且链看起来像这样(在git log --graph --oneline --decorate或中gitk):

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

你编辑git add一些东西——我会改变文件f3并添加它——然后运行git commit --amend​​. 这将获取索引并进行新的提交,但新提交的父级是从原来的位置返回的master,即previous commit上面的。现在日志输出如下所示:

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

你看不到的(因为上面没有分支标签)67dec43仍然在那里(直到它过期并被垃圾收集),但如果你告诉git log看那里它会:

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

您有一个从“先前提交”中脱落的分支,master标签位于新的替换提交处,而“​​amendme”提交位于未标记的分支上。

让我们再做一次,这次有一个藏匿处。我从f3“amendme”提交中的“已知错误”文件开始。然后我投入一秒钟(但仍然不对)f3并运行git stash. 最后,我修复f3“真实”并使用--amend. stash 保留对现在未标记的分支的引用,因为 stash一个新的提交(实际上是两个)。以下是最后几个步骤:

$ 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

如果您尝试应用 stash,将会发生冲突(因为 stash 更改了 file f3,而我的中间版本是“不完全坏,但也不是更好”的版本):

$ 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

这些与引入提交时的任何其他冲突相同,例如cherry-pickor merge,并且您以相同的方式解决它们。

如果您愿意,您可以在“amendme”提交上粘贴分支或标签标签:

$ 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

现在它很容易供参考。然后你可以检查它和git stash pop --index那个特定的藏匿处;这可以保证工作(因此是安全的,尽管在完成其中几个之前pop你可能想要这样做)。apply另请参阅git stash branch下面的“使用”,它可以自动执行此操作。

stash 的工作原理,长版本

让我们退后一点。我想展示一个简化的示例,只有三个文件。

让我们创建一个临时目录和 git repo 并提交一个起点,其中包含三个单行长的文件:

$ 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

现在,让我们进行更改:

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

至此,f2变化并上演:

$ 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

并且f3已更改但未上演:

$ 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

这里diff --cached显示了暂存的东西(在索引中),diff没有--cached显示未暂存的东西。

现在,让我们git stash(默认操作是 to save)。stash将向 repo 添加两个提交。第一个只是到目前为止上演的东西(如果没有上演,则stash强制执行无更改提交),第二个是合并提交,即 that-plus-work-dir。所以:

$ 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

第一个,index on master,有我的变化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

第二个有两个变化(f2f3),但是是一个合并提交,所以git show显示一个组合的差异,只显示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

(旁白:如果您想将任何合并M与每个父级进行比较,请使用。例如,首先diffs -vs- ,然后^2-vs- 。)git show -m Mgit show -m 753a6c8753a6c8^1753a6c8753a6c8753a6c8

--keep-index东西是怎么回事?

通常,在您执行 a 之后git stash,您将拥有一个干净的目录,因此无需“重新存储”任何内容,因为它是:

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

但是你要求 stash 到--keep-index. 这仍然是通常的 stash 条目,但随后它会提取索引 commit 的内容,并将其放入工作目录和索引中。让我们弹出当前存储,查看状态(git status-<code>popstatus自动执行-然后git log --graph --oneline --decorate --all),然后看到我们回到了工作进行中状态,但这次没有任何内容:

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

现在让我们重新上演f2并重新制作stash save,但这一次,使用--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

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

$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   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 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

乍一看,前后看起来一样。但请仔细查看SHA-1 ID。他们变了!在第二个之前git stash saverefs/stash命名为 commit 7efe9a6。现在它的名字eb383e0

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

好的,还不错但是现在您必须了解“reflog”。另一个stash去哪儿了?答案是,它已被“推送”并消失在 reflog 中。还有一点额外的皱纹:“stash”不是常规的分支或标签。相反,它在refs/stash. 因此,这是查看它的一种方式,使用git log -g,这意味着“查看 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

啊哈,他们在那里,两者都7efe9a6eb383e0。他们有“用户表单全名”(refs/stash@{1}例如),使用起来有点麻烦。幸运的是stash(除非您命名一个分支stash)以获得“最顶层”的{0}一个,并且您可以为另一个编写stash@{1}。或者我们可以进行全面的自动化:

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

这会转储出它们的完整哈希值,我们可以将其用作 的参数git log --graph --decorate,以获得以下信息:

$ 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

这只是为了看看回购中仍然“在那里”的东西。

(或者,当然,您可以gitk像以前那样使用 来查看它们。 gitk足够聪明,可以查找存储引用日志。)

(旁白:您也可以使用git reflog show refs/stashreflog show子命令只是运行git log -g --oneline。)

再次回到我们的问题

既然我们已经做了一个 firstgit stash save --keep-index然后是一个毫无意义的git stash save,现在呢?

好吧,我们可以git stash pop取回最近的(堆栈的最顶部)存储:

$ 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)

怎么了f3?正如我们前面提到的,第二个git stash save保存了“保留索引”,即只保存了更改的. 我们需要的是回到第一个存储区。f2

$ 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

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

如果您不确定自己在做什么,现在是创建“保存东西”分支的好时机(您以后可以随时删除它)。随便git checkout -b help-me-spock什么,添加并提交。这些东西现在在一个分支上,更容易跟踪。但是我们知道我们在做什么,并且知道我们f2另一个藏匿处。所以我们可以把它清除掉:

$ git reset --hard

现在我们回到了我们本来应该拥有的状态,如果我们只做了一个git stash save,没有--keep-index: 我们在 on master,工作目录干净,并且保存了一个存储。我们可以git stash listgit stash show它,等等。所以现在:

$ 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)

我们一切都回来了。(或者,没有--indexstash只会将所有更改应用到工作目录,而不是恢复索引和工作目录。)

使用git stash apply

好处git stash pop是它适用,然后删除最顶层的存储条目。烦人的是,它应用然后删除条目。如果你改用git stash apply它,它会挂在它上面。

除其他事项外,如果您拼错--index--keep-index(我不止一次,在键入此内容时),或者将其忽略并稍后决定使用它会很好,这将非常方便。你可以git reset --hard重新做apply

如果您完成了存储条目,将从 reflog 中删除它。例如,假设您这样做然后决定一切都很好并且想要和/或它然后忘记那个藏匿处。然后就可以了。缺点是这会重新编号其余部分:什么变成了,等等。我发现有时将它们全部保留并在最后一次摆脱所有它们会更容易。git stash drop entrygit stash apply --index 'stash@{1}'addcommitgit stash drop 'stash@{1}'stash@{2}stash@{1}git stash clear

等一下,这些--index-es 是怎么回事?

默认情况下,git stash apply获取保存的索引(“为提交暂存的更改”)和正在进行的工作(“未为提交暂存的更改”)并将它们都作为工作进行中git stash pop生效。通常这很好,但如果你已经小心地上演了一些位而让其他人不上演,你可能希望所有这些都回来。(and )的论点试图做到这一点。有时事实证明“太难了”。在这种情况下,您有两个选择:省略或使用。--indexapplypop--indexgit stash branch

使用git stash branch

我在上面提到过,在修改提交与存储的部分中,您可以将新的分支标签添加到具有存储的提交中,然后apply甚至pop是相应的存储,使用 --index, 并且它将始终有效。原因很简单:stash 是索引和 WIP 的合并提交,对应于它们所在的提交。如果您检查该提交(作为“分离的 HEAD”),索引和 WIP干净地应用。

因此,假设您在有问题的提交中添加了一个新分支名称,然后进入新分支 ( )。现在应用(并弹出)存储,使用: 您现在处于与第一次运行时完全相同的状态,除了分支具有不同的名称。这就是它的作用:你给它一个新的分支名称并告诉它使用哪个存储(默认是,AKA )。它使用该存储条目来查找父提交,在那里附加分支名称,然后执行.git checkout -b newname--indexgit stash savegit stash branchrefs/stashstash@{0}git stash pop --index

此时,您可以使用git statusgit diff --cachedgit diff等来查看索引中的内容和不存在的内容,确定要添加的其他内容,然后git commit将新内容添加到您创建的新分支中。

于 2013-09-06T20:49:25.310 回答