编辑:从Git版本1.8.4开始,但在Git版本2.0.1中修复了一个重要的副作用错误,
git rebase
现在具有
--autostash
。 您也可以通过
git config --global rebase.autoStash true
将
git rebase
配置为默认使用
--autostash
。 请注意
文档中的以下句子:
然而,需要小心使用:成功合并后最终stash应用可能会导致非微不足道的冲突。
(我仍然更喜欢直接提交。)
TL;DR回答:只需进行提交(稍后再撤销)
您可能会发现
git stash
实际上只是
git commit
(以更复杂的形式,首先提交索引,然后提交工作树 - 当您应用存储时,可以保持索引和工作树的分离,或将它们组合成仅是工作树更改)。
一个存储区的特殊之处在于它所产生的提交——通过一种不寻常的方式(作为不真正合并的合并提交)进行两个或者甚至三个提交,而这些提交并没有放置在任何分支上(相反,使用了特殊的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
上。挂在提交
C
上的
i-w
是您的stash,它不在分支上,但仍然是一个由两个提交组成的
“git stash bag”,实际上连接到您的最新提交(
C
)。
@
提交(可能只有一个)是新的上游提交。
(如果您没有进行任何提交,则您的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
命令,它将把暂存区和工作区的更改都恢复,同时删除
stash
标签(准确地说是弹出,这样如果存在
stash@{1}
,那么它现在就变成了
stash
,以此类推)。这会释放对原始的
A - B - C
链的最后引用,并意味着我们也不再需要
i-w
位,这样我们可以将其简化为以下形式:
... - @ <
\
A'-B'-C' <-- HEAD=master plus work tree changes
现在让我们画出如果不使用
git stash save
,而是直接使用
git commit -a
(或者使用
git add
和
git commit
,但不加 -a 参数)创建一个实际的提交
D
会发生什么。你从以下状态开始:
... - o-*-A-B-C-D <
\
@-@-@ <
现在你执行
git rebase upstream/master
,这会将
A
到
D
复制到最后一个
@
的末尾,得到如下结果:
... - o-*-@-@-@ <-- upstream/master
\
A'-B'-C'-D' <-- HEAD=master
唯一的问题是,您有一个不需要的额外提交D
(现在是D'
),而不是未提交的工作树更改。但是这可以通过使用git reset
来轻松撤消,以回退一个提交。我们可以使用默认的--mixed
重置来重新设置索引(暂存区),以便“取消添加”所有文件,或者如果您希望它们保持git add
状态,则使用--soft
重置。(两者都不会影响最终的提交图,只有索引状态不同。)
git reset --mixed HEAD^
这是它的样子:
... - o-*-@-@-@ <-- upstream/master
\
A'-B'-C' <-- HEAD=master
\
D' [abandoned]
您可能认为这样效率低下,但是当您使用
git stash
时,实际上至少做了
两次提交,然后在您
git stash pop
它们时放弃。真正的区别在于通过进行临时提交而不是发布提交,您可以自动重新应用它们。
不要害怕临时提交
Git有一个通用规则:进行大量的临时提交以便在工作过程中保存您的工作。您始终可以稍后对它们进行变基。也就是说,不要这样:
... - * - 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
是因为意识到b1
需要不同于A
的变化,而做出的变化,b2
修复了b1
中的错误,a4
修复了a3
对a2
的更改中的错误,b3
是b1
本应该完成的任务;然后c1
是对C
的初始尝试,b4
是对b1
的另一个修复,c2
是细化过程中的一步。
假设在后,您认为其基本准备好了。现在运行
git rebase -i origin/master
或其他命令,重排
pick
行,按顺序排列
a1
到
a4
,
b1
到
b4
,
c1
到
c3
,然后运行rebase。然后您修复任何冲突,并确保一切正常,然后再次运行
git rebase -i
将所有四个
版本合并成,以此类推。
完成后,看起来您第一次就创建了完美的(或者可能是与或其他提交有关,具体取决于您保留和删除哪些提交以及是否重新设置了任何时间戳)。其他人可能不想要或不需要看到您的中间工作 - 虽然如果有用的话,可以保留它而不合并提交。同时,您永远不需要拥有必须进行rebase的未提交的内容,因为您只有部分内容的提交。
在一行提交文本中为这些提交命名确实有助于您后续的变基工作:
git commit -m 'temp commit: work to enable frabulator, incomplete'
等等。