编辑:从 Git 版本 1.8.4 开始,但在 Git 版本 2.0.1 中修复了一个重要的错误,git rebase 现在有--autostash。您也可以将git rebase 配置为默认使用--autostash,使用git config --global rebase.autoStash true。请注意the documentation的以下句子:
但是,请谨慎使用:最后的存储
成功变基后的应用程序可能会导致不平凡
冲突。
(我还是更喜欢只提交。)
TL;DR 回答:只需提交(稍后取消提交)
这可能会帮助您意识到git stash 实际上只是git commit(以更复杂的形式,首先提交索引,然后是工作树——当您应用存储时,您可以保持分离索引和工作树,或者将它们组合成一个工作树更改)。
stash 的特别之处在于它所做的两次提交,或者-u 或 -a,甚至是三个提交,都是以一种不寻常的形式进行的(作为不是真正合并的合并提交),并且没有放在任何分支上(相反,特殊的refs/stash 引用用于保留和查找它们)。
由于它们不在分支上,rebase 不会触及它们,在您的工作流程中,是 git stash pop 将工作树更改带入您的新工作树。但是,如果您在分支上进行自己的(正常)提交,并重新设置并包含该提交,则此正常提交将与任何其他提交一起重新设置。我们稍后会解决最后一个问题;现在,让我们将其绘制为一系列可以(或不)重新定位的提交:
... do some work ...
... make some commits ...
... more work ...
... do something that causes upstream/master to update, such as git fetch upstream
$ git stash
此时,您拥有的是:
... - o - * - A - B - C <-- HEAD=master
\ |\
\ i-w <-- stash
\
@-@-@ <-- upstream/master
这里,A、B 和 C 是您的提交(我假设您已经提交了 3 个),都在分支 master 上。 i-w 挂起提交 C 是您的存储,它不在分支上,但仍然是两个提交 "git stash bag" 并且实际上附加到您的最新提交 (C)。 @ 提交(可能只有一个)是新的上游提交。
(如果您进行了 no 提交,您的 stash-bag 将挂起提交 *,并且您当前的分支指向提交 *,因此 git rebase 没有工作要做除了向前移动当前的分支指针。在这种情况下,一切都一样,但我假设有一些提交。)
现在你运行git rebase upstream/master。这会将您的提交复制到具有新 ID 和新父 ID 的新提交,以便它们位于最后一个 @ 之上。 stash-bag 没有移动,所以结果如下所示:
... - o - * - A - B - C [abandoned, except for the stash]
\ |\
\ i-w <-- stash
\
@-@-@ <-- upstream/master
\
A'-B'-C' <-- HEAD=master
您现在使用git stash pop,它会在工作树更改时恢复 i/w 内容,删除stash 标签(更准确地说,弹出它以便stash@{1},如果它存在,现在是stash , 等等)。这释放了对原始A - B - C 链的最后引用,这意味着我们也不需要i-w 位,这让我们可以更简单地重新绘制它:
... - @ <-- upstream/master
\
A'-B'-C' <-- HEAD=master plus work tree changes
现在让我们画出如果您只执行git commit -a(或不带-a 的git add 和git commit)而不是git stash save 来创建实际提交D 会发生什么。你开始:
... - o-*-A-B-C-D <-- HEAD=master
\
@-@-@ <-- upstream/master
现在你 git rebase upstream/master,将 A 复制到 D 以将它们放在最后一个 @ 的末尾,你有这个:
... - o-*-@-@-@ <-- upstream/master
\
A'-B'-C'-D' <-- HEAD=master
唯一的问题是你有这个不需要的额外提交D(嗯,现在是D'),而不是未提交的工作树更改。但这可以通过git reset 轻松撤消以退回一次提交。我们可以使用--mixed 重置(默认设置)来重新设置索引(暂存区),以便“取消添加”所有文件,或者如果您希望它们保留git add-ed, --soft 重置。 (不影响生成的提交图,只是索引状态不同。)
git reset --mixed HEAD^ # or leave out `--mixed` since it's the default
看起来是这样的:
... - o-*-@-@-@ <-- upstream/master
\
A'-B'-C' <-- HEAD=master
\
D' [abandoned]
您可能认为这是低效的,但是当您使用 git stash 时,您实际上至少进行了 两次 提交,然后当您 git stash pop 时您会放弃这些提交。真正的区别在于,通过进行临时的、非发布的提交,您会自动获得这些提交。
不要害怕临时提交
git 有一个通用规则:进行 lots 的临时提交,以便随时保存您的工作。您以后可以随时重新调整它们。也就是说,而不是这个:
... - * - A - B - C <-- mybranch
如果A、B 和C 是提交* 之上的完美和最终提交(来自其他人或更早发布的内容),请执行以下操作:
... - * - a1 - a2 - b1 - a3 - b2 - a4 - b3 - c1 - b4 - c2 - c3
其中a1 是A 的初始刺,a2 修复了a1 中的错误,b1 是使b 工作的初步尝试,a3 是从意识到@987654392 @毕竟需要A不同,b2修复了b1中的错误,a4修复了a3更改为a2的错误,b3是b1应该的完成了;那么c1 是C 的初始尝试,b4 是b1 的另一个修复,c2 是一种改进,等等。
假设在c3 之后,您认为它基本上已经准备好了。现在你运行git rebase -i origin/master 或其他任何东西,将pick 行打乱以使a1 到a4 有序,b1 到b4 有序,c1 到c3 有序,然后让变基运行。然后你修复所有冲突并确保一切仍然正确,然后运行另一个git rebase -i 将所有四个a 版本折叠成A,等等。
当你全部完成后,看起来就像你第一次创建了一个完美的 A(或者可能使用 a4 或其他一些,这取决于你保留哪些提交以及哪些提交你放弃以及你是否重新设置了任何时间戳)。其他人可能不想或不需要看到你的中间工作——尽管你可以保留它,不组合提交,如果这有用的话。同时,您永远不需要必须重新设置未提交的内容,因为您只提交了部分内容。
在单行提交文本中为这些提交命名确实有帮助,这将指导您以后的 rebase 工作:
git commit -m 'temp commit: work to enable frabulator, incomplete'
等等。