/我在下面保留第一个长回复(稍后可能会尝试将其移至新问题),但现在有一个“重现示例”,我将通过它。不过,让我在这里列出要点。
git stash
总是隐藏索引和工作目录。有人可能会认为--keep-index
让它存储更多,或者改变存储值在pop
. 它没有!默认情况下,两者都git stash apply
混合git stash pop
在一起分离的索引更改。添加--keep-index
不会改变这一点。只有--index
论点apply
和pop
试图避免混合它们。
保存金额的“工作目录”,git stash
实际上是从当前HEAD
. 这意味着,如果索引从 更改HEAD
,但当前工作目录没有,则“WIP on branch ...”提交中实际上没有保存更改。(我认为这是git stash
.稍后状态git stash branch
,它会丢弃工作目录状态。这会导致您的问题。)
应用 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
这产生了第三对提交,一个带有索引,一个带有当前目录。索引版本在f1
和f2
中有额外的行,在f3
. 当前目录版本应该(有人会认为)在所有三个文件中都有额外的行 - 但是,唉,它没有,因为git stash save
比较当前目录与HEAD
提交,并且额外的行在HEAD
提交中,所以它不在隐藏版本。
不幸的是,您使用了该--keep-index
参数,因此现在工作目录版本与隐藏索引版本相同。文件f3
不再有多余的行。
从这里开始,问题仍然存在(更改消失了,--keep-index
扔掉了)。您当然可以从原始提交(“tobeamended123”)中恢复它。但这就是在这种情况下出现问题的地方:命令行stash
保存了索引,然后将工作目录与HEAD
未更改的f3
.
我没有看到灾难,但我看到了一些令人困惑的东西,我敢打赌这会让你感到困惑。我不知道你为什么在--keep-index
上面使用。(事实上,我不确定1--keep-index
的用例可能是什么,在我看来,并且可能应该默认为,但这完全是另一回事....)而且,您总共进行了 4 次存储“推送” ”,只“弹出”一个,剩下三个。apply
pop
--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}'
--index
stash@{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 --amend
vsgit 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-pick
or 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
第二个有两个变化(f2
和f3
),但是是一个合并提交,所以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 M
git show -m 753a6c8
753a6c8^1
753a6c8
753a6c8
753a6c8
这--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 save
,refs/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
啊哈,他们在那里,两者都7efe9a6
和eb383e0
。他们有“用户表单全名”(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/stash
。reflog 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 list
,git 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)
我们一切都回来了。(或者,没有--index
,stash
只会将所有更改应用到工作目录,而不是恢复索引和工作目录。)
使用git stash apply
好处git stash pop
是它适用,然后删除最顶层的存储条目。烦人的是,它应用然后删除条目。如果你改用git stash apply
它,它会挂在它上面。
除其他事项外,如果您拼错--index
了--keep-index
(我不止一次,在键入此内容时),或者将其忽略并稍后决定使用它会很好,这将非常方便。你可以git reset --hard
重新做apply
。
如果您完成了存储条目,将从 reflog 中删除它。例如,假设您这样做然后决定一切都很好并且想要和/或它然后忘记那个藏匿处。然后就可以了。缺点是这会重新编号其余部分:什么变成了,等等。我发现有时将它们全部保留并在最后一次摆脱所有它们会更容易。git stash drop entry
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
生效。通常这很好,但如果你已经小心地上演了一些位而让其他人不上演,你可能希望所有这些都回来。(and )的论点试图做到这一点。有时事实证明“太难了”。在这种情况下,您有两个选择:省略或使用。--index
apply
pop
--index
git stash branch
使用git stash branch
我在上面提到过,在修改提交与存储的部分中,您可以将新的分支标签添加到具有存储的提交中,然后apply
甚至pop
是相应的存储,使用 --index
, 并且它将始终有效。原因很简单:stash 是索引和 WIP 的合并提交,对应于它们所在的提交。如果您检查该提交(作为“分离的 HEAD”),索引和 WIP将干净地应用。
因此,假设您在有问题的提交中添加了一个新分支名称,然后进入新分支 ( )。现在应用(并弹出)存储,使用: 您现在处于与第一次运行时完全相同的状态,除了分支具有不同的名称。这就是它的作用:你给它一个新的分支名称并告诉它使用哪个存储(默认是,AKA )。它使用该存储条目来查找父提交,在那里附加分支名称,然后执行.git checkout -b newname
--index
git stash save
git stash branch
refs/stash
stash@{0}
git stash pop --index
此时,您可以使用git status
、git diff --cached
、git diff
等来查看索引中的内容和不存在的内容,确定要添加的其他内容,然后git commit
将新内容添加到您创建的新分支中。