git存储和应用 [英] git stash and apply
问题描述
假设我正在使用分支管理器并尝试使用 git pull
,并收到我的本地更改将被覆盖并需要隐藏或提交的错误。如果我没有执行任何更改并运行 git stash
,则执行 git pull
并更新成功,当我 git stash apply
?
时会发生什么一般情况下,如果其他人修改了文件并运行 git pull
,当我运行git stash apply
时会发生什么?它是否会覆盖刚更新的文件,而不管它们是否在我隐藏它们时进行了演示?它是否覆盖了每一个我刚刚用 git pull
更新的文件,以及被隐藏的文件?
快速TL; DR外卖版本,所以人们可以稍后再回来学习更多
git在当前
挂起了一个存储包 - 这是一个不属于任何分支的合并提交的特殊形式。后面的 HEAD
提交中,stash git存储应用
,当你在任何提交时 - 可能是一个不同的提交 - 然后尝试恢复 em> git通过查看挂起的存储包和从它挂起的提交来计算。
完成更改后,应该使用 git stash drop
从放置它的提交中放开存储包。 (并且, git stash pop
只是apply,then automatically drop的缩写。我建议保持两步分开,但如果你不喜欢结果)
长版
git stash
实际上相当复杂。 据说一旦你理解了X,git就更有意义了,对于许多不同的值的X,它泛化为git一旦你理解git就变得更有意义。 : - )
在这种情况下,要真正理解 stash
,您需要理解提交,分支,索引/暂存区域,git的引用名称空间以及合并所有工作,因为 git stash
会创建一个非常特殊的归并提交通过名称空间之外的名称 - 一种奇怪的合并方式,它根本不是在分支上 - 并且 git stash apply
使用git的合并机制来尝试重新应用特定合并提交时保存的更改,可选地保留分阶段和未分离更改之间的区别。
幸运地,您实际上并不需要了解所有这些以便使用 git存储
。
在这里,你正在研究一些分支( master
),并且你有一些尚未准备好的更改,所以你不想在分支上提交它们。 1 与此同时,其他人把一些好东西放在一边st,你希望它很好 - 进入远程仓库的 origin / master
,所以你想选择这些。
假设你和他们都以提交以 - A - B - C
结尾的提交,即 C
是您开始使用分支 master
时在回购库中的最终提交。新的好东西提交后,我们将调用 D
和 E
。
在你的情况下,你正在运行 git pull
,并且失败时出现工作目录不干净的问题。所以,你运行 git stash
。这为你的东西提供了特殊的怪异存储方式,这样你的工作目录就变得干净了。现在您可以 git pull
。
在提交绘图方面(您使用 gitk
或 git log --graph
),你现在有这样的东西。隐藏在 master
分支中的 iw
,当你运行 git stash
。 (名称 i
和 w
的原因是这些是index / staging-area和 )
- A - B - C - D - E < - HEAD = master,origin / master
| \
iw < - stash
如果您开始处理 master
并且从未执行任何提交,那么此图就是您所得到的。最近的提交是 C
。在完成存储之后, git pull
可以添加提交 D
和 E
到你的本地分支 master
。如果你提交了一些你自己的提交,我们可以将这些提交的内容放在 C
。
Y
,为你的提交,并且 Z
只是有两个提交 - 隐藏然后拉看起来像这样:$ $ p $ $ $ $ $。$原产地/主
- A - B - C - D - E - M - - HEAD = master
\\ /
Y - Z
| \
iw < - 隐藏
这次, stash
关闭 Z
,拉
- 这只是获取
然后 merge
-had做一个真正的合并,而不只是一个快进。所以它使提交 M
,合并提交。 origin / master
标签仍然指向commit E
,而不是 M
。您现在位于 master
at commit M
,它是 E
和
Z
。你是一前一后的 origin / master
。
在任何一种情况下,如果你现在运行
git stash apply
,存储脚本(这是一个使用大量低级gitplumbing命令的shell脚本)有效地执行此操作: p> git diff stash ^ stash> / tmp / patch
git apply / tmp / patch
这个差异 stash
,其中 w
- 隐藏在正确的 3 父项中的工作树部分。换句话说,它会根据情况找到正确的父提交( C
或 Z
)之间的你改变了什么 )和隐藏的工作树。然后将更改应用于当前签出的版本,该版本是 E
或 M
,同样取决于
$ b
顺便提一下, git储存展示-p
真的只是运行相同的 git diff
命令(当然没有> / tmp / patch
部分)。如果没有 -p
,它会使用 - stat
运行diff。因此,如果您想详细了解 git stash apply
将合并到的位置,请使用 git stash show -p
。 (这不会告诉你什么 git stash apply
可以尝试从存储器的索引部分应用,但这是我对存储脚本的一个小小抱怨。 )
在任何情况下,一旦隐藏应用,您可以使用 git stash drop
删除引用到存储袋,以便它可以被垃圾收集。在你删除它之前,它有一个名称( refs / stash
,aka stash @ {0}
),围绕永远......除了如果你创建一个新的存储器,存储
脚本将当前存储器推入存储reflog(使其名称变成 stash @ {1}
),并使新存储使用 refs / stash
名称。大多数reflog条目会持续90天(您可以将其配置为不同),然后过期。默认情况下,存储过期不会过期,但如果您另行配置,则推入存储可能会丢失,因此如果您开始配置git以满足您的喜好,请注意取决于永久保存。
请注意 git stash drop
在这里弹出存储栈,重新编号 stash @ {2}
到 stash @ {1}
并使藏匿
。使用 git stash list
查看存储堆栈。
1 不管怎样,继续提交它们,然后稍后执行 git rebase -i
来压缩或修复第二,第三,第四,...,第n次提交,和/或重写临时检查点提交。但这与此无关。
2 这是一个更为复杂的因为您可以使用 - index code>试图保持阶段性的变化,但事实上,如果你看看脚本,你会看到实际的命令序列
git diff ... | git apply --index
。在这种情况下,它确实只是应用差异!最终,它直接调用 git merge-recursive
来合并工作树,允许从别处引入相同的更改。如果你的补丁做了好东西提交 D
和,那么一个普通的
也可以。 git apply
> E
3 这个使用git的父命名魔法语法, 隐藏
脚本。由于存储是这种时髦的合并提交,因此 w
有两个或者甚至三个父项,但是存储脚本将它设置为使得第一个父项是原始提交, code> C 或 Z
,如适用。 第二父母 stash ^ 2
是提交时的索引状态,显示为 i
如果存在的话,第三个父母是未分开的和可能被忽略的文件,从 git stash save -u
或 git stash save -a
。
请注意,在这个答案中,我假设您有 仔细地分阶段部署工作树,并且使用 git stash apply --index
来不是来恢复暂存索引。通过不执行任何操作,可以使 i
提交非常多余,以便我们在 apply $ c期间不必担心它$ c>步骤。如果您 使用
apply --index
或同等效果,并且有分段项目,则可以进入更多
这些相同的注意事项适用于更多的角落案例,使用 -u保存的存储空间
或 -a
,它们具有第三次提交。
例如, git stash
提供了一种方法将一个存储转化为一个完整的分支 - 但我会把所有这些留给另一个答案。 / p>
I'm new to git and not quite clear on how stashing works.
Let's say I'm working on branch master and try to git pull
and receive the error that my local changes would be overwritten and need to be stashed or committed. If I haven't staged any of my changes and run git stash
, then do a git pull
and and update successfully, what happens when I git stash apply
?
In general, If someone else modifies files and I run git pull
, what happens when I run git stash apply
? does it overwrite the files that were just updated, regardless of whether or not they were staged when I stashed them? Does it overwrite every file that I just updated with git pull
, with the files that were stashed?
Quick "TL;DR" take-away version, so one can come back later and study more
git stash
hangs a stash-bag—this is a peculiar form of a merge commit that is not on any branch—on the current HEAD
commit. A later git stash apply
, when you're at any commit—probably a different commit—then tries to restore the changes git computes by looking at both the hanging stash-bag and the commit it hangs from.
When you're done with the changes, you should use git stash drop
to let go of the stash-bag from the commit it was "stashed on". (And, git stash pop
is just shorthand for "apply, then automatically drop". I recommend keeping the two steps separate, though, in case you don't like the result of "apply" and you want to try again later.)
The long version
git stash
is actually fairly complex.
It's been said that "git makes much more sense once you understand X", for many different values of "X", which generalizes to "git makes much more sense once you understand git". :-)
In this case, to really understand stash
, you need to understand how commits, branches, the index/staging-area, git's reference name space, and merges all work, because git stash
creates a very peculiar merge commit that is referred-to by a name outside the usual name-spaces—a weird kind of merge that is not "on a branch" at all—and git stash apply
uses git's merge machinery to attempt to "re-apply" the changes saved when the peculiar merge commit was made, optionally preserving the distinction between staged and unstaged changes.
Fortunately, you don't actually need to understand all of that to use git stash
.
Here, you're working on some branch (master
) and you have some changes that aren't ready yet, so you don't want to commit them on the branch.1 Meanwhile someone else put something good—or at least, you hope it's good—into the origin/master
over on the remote repo, so you want to pick those up.
Let's say that you and they both started with commits that end in - A - B - C
, i.e., C
is the final commit that you had in your repo when you started working on branch master
. The new "something good" commits, we'll call D
and E
.
In your case you're running git pull
and it fails with the "working directory not clean" problem. So, you run git stash
. This commits your stuff for you, in its special weird stash-y fashion, so that your working directory is now clean. Now you can git pull
.
In terms of drawing of commits (a graph like you get with gitk
or git log --graph
), you now have something like this. The stash is the little bag-of-i-w
dangling off the commit you were "on", in your master
branch, when you ran git stash
. (The reason for the names i
and w
is that these are the "i"ndex / staging-area and "w"ork-tree parts of the stash.)
- A - B - C - D - E <-- HEAD=master, origin/master
|\
i-w <-- the "stash"
This drawing is what you get if you started working on master
and never did any commits. The most recent commit you had was thus C
. After making the stash, git pull
was able to add commits D
and E
to your local branch master
. The stashed bag of work is still hanging off C
.
If you made a few commits of your own—we'll call them Y
, for your commit, and Z
just to have two commits—the result of the "stash then pull" looks like this:
.-------- origin/master
- A - B - C - D - E - M <-- HEAD=master
\ /
Y - Z
|\
i-w <-- the "stash"
This time, after stash
hung its stash-bag off Z
, the pull
—which is just fetch
then merge
—had to do a real merge, instead of just a "fast forward". So it makes commit M
, the merge commit. The origin/master
label still refers to commit E
, not M
. You're now on master
at commit M
, which is a merge of E
and Z
. You're "one ahead" of origin/master
.
In either case, if you now run git stash apply
, the stash script (it's a shell script that uses a lot of low level git "plumbing" commands) effectively2 does this:
git diff stash^ stash > /tmp/patch
git apply /tmp/patch
This diffs stash
, which names w
—the "work tree" part of the stash—against the correct3 parent. In other words, it finds out "what you changed" between the proper parent commit (C
or Z
, as appropriate) and the stashed work-tree. It then applies the changes to the currently-checked-out version, which is either E
or M
, again depending on where you started.
Incidentally, git stash show -p
really just runs that same git diff
command (with no > /tmp/patch
part of course). Without -p
, it runs the diff with --stat
. So if you want to see in detail what git stash apply
will merge in, use git stash show -p
. (This won't show you what git stash apply
can attempt to apply from the index part of the stash, though; this is a minor gripe I have with the stash script.)
In any case, once the stash applies cleanly, you can use git stash drop
to remove the reference to the stash-bag, so that it can be garbage-collected. Until you drop it, it has a name (refs/stash
, aka stash@{0}
) so it sticks around "forever" ... except for the fact that if you make a new stash, the stash
script "pushes" the current stash into the stash reflog (so that its name becomes stash@{1}
) and makes the new stash use the refs/stash
name. Most reflog entries stick around for 90 days (you can configure this to be different) and then expire. Stashes don't expire by default, but if you configure this otherwise, a "pushed" stash can get lost, so be careful about depending on "save forever" if you start configuring git to your liking.
Note that git stash drop
"pops" the stash stack here, renumbering stash@{2}
to stash@{1}
and making stash@{1}
become plain stash
. Use git stash list
to see the stash-stack.
1It's not bad to go ahead and commit them anyway, and then do a later git rebase -i
to squash or fixup further second, third, fourth, ..., nth commits, and/or rewrite the temporary "checkpoint" commit. But that's independent of this.
2It's a fair bit more complex because you can use --index
to try to keep staged changes staged, but in fact, if you look in the script, you'll see the actual command sequence git diff ... | git apply --index
. It really does just apply a diff, in this case! Eventually it invokes git merge-recursive
directly, though, to merge in the work tree, allowing the same changes to have been brought in from elsewhere. A plain git apply
would fail if your patch does something the "good stuff" commits D
and E
also does.
3This uses git's parent-naming magic syntax, with a little advance planning inside the stash
script. Because the stash is this funky merge commit, w
has two or even three parents, but the stash script sets it up so that the "first parent" is the original commit, C
or Z
, as appropriate. The "second parent" stash^2
is the index state at the time of the commit, shown as i
in the little hanging stash-bag, and the "third parent", if it exists, is unstaged-and-maybe-ignored files, from git stash save -u
or git stash save -a
.
Note that I assume, in this answer, that you have not carefully staged part of your work-tree and that you are not using git stash apply --index
to restore the staged index. By not doing any of this, you render the i
commit pretty much redundant, so that we need not worry about it during the apply
step. If you are using apply --index
or equivalent, and have staged items, you can get into a lot more corner cases, where the stash won't apply cleanly.
These same caveats apply, with yet more corner cases, to stashes saved with -u
or -a
, that have that third commit.
For these extra-hard cases, git stash
provides a way to turn a stash into a full-fledged branch—but I'll leave all that to another answer.
这篇关于git存储和应用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!