我的工作流程目标通常是避免无意义的合并。要做到这一点,我经常做两件事之一:
git pull --rebase
以防止不必要的合并。如果我以后希望更改现有提交,我会使用git rebase --interactive
git stash
我的更改,然后git pull
正常而简单地git stash pop
稍后当我希望修改和/或提交这些更改时。{/ li>
醇>
有些同事提醒我git stash save
/ git stash pop
不安全。
我想知道使用提交和git pull --rebase
与存储列表是否有任何微妙的优势。
答案 0 :(得分:12)
使用git stash
可以避免"无意义的合并"只有你没有自己的承诺。 git pull --rebase
方法更为通用。
使用git stash
只进行一些提交, 1 所以对于第一次近似,存储和提交基本相同。存储提交采取我喜欢称之为"存储包的形式,当你执行stash
时,它会挂在你最常提交的任何分支上。
第一个区别是藏匿袋提交不会移动分支的尖端。也就是说,假设分支(让我们称之为devel
)如下所示:
... <- E <- F <-- devel
然后在收起后,它看起来像这样:
... <- E <- F <-- devel
|\
i-w <-- "the stash"
(这是对[&34;提交图表]的[部分]的ASCII艺术描述&#34 ;:提交F
是分支的提示。提交F
具有,作为其父提交,提交E
:F
&#34;指向&#34; E
。同时E
指回其父级,依此类推。名称{ {1}}只是指向提示最多的提交,即devel
。)
如果您进行了常规提交,则会获得新提交F
,并指向G
,F
将更改为指向devel
},&#34;向前移动尖端&#34;。
与G
(vs git stash
)的第二个区别发生在&#34;另一端&#34;,就像它一样。当您运行git commit
, 2 时,git的作用与以下内容非常相似。 (有许多实现细节使得准确的描述变得困难,但我认为这是考虑它的方法。如果你git stash apply
它更复杂;请记住,这个简化的图片只是& #34;足够接近&#34;非apply --keep-index
案例。)
要了解这种情况如何适用于您的情况,我们必须查看有--keep-index
的{{1}}做什么,无论有无git pull
。
--rebase
命令最好描述为pull
- 然后 - fetch
。 (它甚至在手册页中也是如此。)对于merge
,最好将其描述为--rebase
- 然后 - fetch
。所以我们有两个非常不同的案例,有两种不同的方式来调用rebase
。
pull
步骤很容易描述。来自某些&#34; remote&#34;的fetch
,告诉git通过Internet电话调用远程存储库:-) 3 并询问它是什么分支和提交这样的它有。然后你的 git将他们的 git移交给你的git存储在你的存储库中的所有新东西,以便你拥有他们所做的一切,当然还有你自己的任何东西 4
再说一次,让我们说你在分支机构fetch
上,然后运行这种devel
步骤。让我们进一步说,当你这样做时,分支git fetch
看起来像这样:
devel
当你的git联系他们的git时,它可能会发现你之前没有的新提交,例如commit ... E - F - G <-- devel
,它指向一些早期提交作为其父级。
也许H
的父提交是提交H
。但是,要实现这一点,您的同事必须已经提交G
。因此,假设您创建G
,您需要发布(推送)它,以便她获得它,并根据G
创建H
。
但可能你还没有推动G
,她的提交G
指向H
。这是更普遍的情况,所以让我们画出来:
F
由于提交... - E - F - G <-- devel
\
H <-- (her/their idea of what "devel" looks like)
对您的存储库是私有的,因此她和其他人一样认为链是G
。你现在必须做一些事情来整合&#34;你的承诺&#34;和#34;她的承诺&#34;。
对所发生工作的最准确描述的方式是您进行新的合并提交E - F - H
:
M
这是... - E - F - G - M <-- devel
\ /
H
将要执行的操作,因此普通git merge
会发生什么。
令人讨厌的事情是,历史上完全准确,它会让你得到这些&#34;无意义的合并&#34;。 5 所以你能做的就是复制你的旧提交git pull
到一个新的,略有不同的提交,G
。在G'
与G
之间的G'
和G'
之间会有两处更改:(1)与H
相关联的工作树中,您将首先包含她对G'
的更改; (2)在H
中,您要说父提交是F
而不是G
。这将是这样的 - 让我们移动您的旧E - F - H
以使该行转为 G [no longer needed, hence abandoned]
/
... - E - F - H - G' <-- devel
:
H
这是一个&#34; rebase&#34;操作:复制现有的提交,根据需要更改其工作目录内容,将新提交添加到适当的位置(G1
),然后使您的分支提示指向新副本中的最后一次提交。 / p>
即使您提交了大量提交,G5
到git pull --rebase
或其他任何提交,这都会有效,只需要进行更多复制。
当您使用fetch
时,git会为您执行此操作。首先,它使用git stash
来引入任何新的提交,然后 - 如果有一些新的提交 - 将您以前的提交重新绑定到新的提交。 6
现在我们可以回到devel
了。如果您尚未在git stash
上进行任何新的提交,但有一些正在进行的工作,并且您使用... - E - F <-- devel
|\
i-w
进行保存,则可以获得:
git pull
现在使用--rebase
而不是H
,它会带来提交... - E - F - H <-- devel
|\
i-w
(&#34;她&#34; -we&#39;完全跳过字母G,现在保留它并进行合并。 Git将此视为快速合并&#34;因为你没有自己的提交,所以你得到了这个:
git stash apply
然后你F
,让git查看提交w
和H
之间的更改,并将它们合并到您的工作目录中。也就是说,它将您的更改应用于提交drop
的工作目录。一旦您git add
藏匿(或者我们只是不打算绘制它),git commit
您的更改以及G'
,您将获得新的提交。出于某种原因:-)让我们称之为G
而不是... - E - F - H - G' <-- devel
。所以你现在有:
git pull --rebase
看起来与完全相同,就像您先提交一样,然后运行G
。事实上,&#34;放弃了&#34;前面提到的G
提交实际上是相同的提交与(删除,即被放弃)的stash-bag提交! 7
但是,如果你做了已经做了一些提交(或几个,但我们只是使用了一个)提交git stash
,那么... - E - F - G <-- devel
|\
i-w <-- stash
之前更多变化?然后你有这个:
git pull
现在你--rebase
(没有H
)并拿起她的提交 H
/ \
... - E - F - G - M <-- devel
|\
i-w <-- stash
并合并它:
apply
最后,您drop
存储,确保它一切正常,git add
它,N
,然后进行新的提交 H
/ \
... - E - F - G - M - N <-- devel
:
git pull
你有一个令人讨厌的&#34;毫无意义的合并&#34;。当你--rebase
没有git stash
时,它会进来。
简短的版本是git pull --rebase
只保存您的培根(避免恼人的合并),如果您没有自己的提交。 git fetch
方法更为通用。 (虽然,有问题的&#34;上游rebase&#34;尽管如此,我更喜欢做一个单独的git commit
步骤。然后我查看进来的内容,并选择是否进行rebase或merge。但是那个&#39由你决定。)
1 具体来说,它至少提交两次。首先,它为当前索引创建一个,即,如果您git add
没有任何git rm
,--allow-empty
等,并且强制提交存在,您将得到什么(a la git stash apply
)即使树没有变化。然后它以当前工作目录作为其内容进行多父提交,即合并提交。所有这些提交都是以不移动分支尖端的方式完成的。有关其他详细信息,请参阅this answer。
2 我建议您使用git stash drop
,检查结果,然后使用apply
,如果您对pop
的效果感到满意。 apply
命令仅表示drop
- 然后 - git stash
,即它假定您满意。但是如果你经常使用apply
,你可能会有多个藏匿处,并且你可能会意外地应用错误的或者过多的东西,或者某些东西。如果你首先养成了drop
的习惯,那就把所有事情都设置好,然后file://whatever
&#34;,我认为你可能会减少错误。当然,人们不同。 : - )
3 除非&#34;远程&#34;是真正的本地,例如git pull
,或本地路径;或者可以想象,未来可能会有一些非互联网&#34;网址。 Git并不真正关心如何它从遥控器获取新内容,只是它可以找出遥控器的内容,并将其结束以使其现在是本地的。
4 当您使用fetch
时,它会调用git pull --rebase
并启用一些特殊限制,以便您只获取要合并的内容(或rebase-onto )。在1.8.4之前版本的git中,这也禁止更新本地&#34;远程分支&#34;条目,即提取无法保存一些有用的信息。正如git 1.8.4的发行说明所说:
这是一个早期的 设计决定保持远程跟踪分支的更新 可预测,但实际上,人们发现它更多 每当我们有一个时,便于机会性地更新它们 机会,当我们运行&#34; git push&#34;时,我们一直在更新它们。哪一个 已经破坏了原有的可预测性&#34;反正。
5 他们确实有一些的含义(他们意味着你和她并行工作),但是如果不是更早的话,下周就没有人关心。这只是噪音。这通常适用于所有类型的提交:如果你尝试了一些东西,然后做了一些提交,并且必须退出早期的&#34;尝试一些事情&#34;作为完全失败,尝试加上退出可能只是噪音。如果这些提交都是私有(未发布),您可以使用交互式rebase来编辑历史记录&#34;使它看起来像你从未打扰做失败的实验。与此同时,失败的实验可能实际上是有用的信息:&#34;不要这样尝试,这不起作用&#34;。由你来决定什么是好信息,什么是无意义的噪音。
6 值得注意的是...-o-x-x-Y <-- branch
`------- origin/branch
在&#34;上游历史重写&#34;的情况下是非常聪明的。假设,在你拉之前,你有这个:
o
其中x
和Y
代表&#34;他们的&#34;提交和Y1-Y2-Y3
是&#34;您的&#34;提交(它们可能是git fetch
等;它最终都是相同的)。假设当你运行branch
步骤时,结果是&#34;他们&#34;自己重新定位o-x-x
,而不是origin/branch
而不是&#34;&#34; on&#34; o-*-*-*
,您得到...-o-x-x-Y <-- branch
\ `------- old origin/branch
*-*-* <-- FETCH_HEAD, to become new origin/branch
:
*
很明显(好吧,这张图应该让它显得显而易见......)哪些提交在上游重新定位:它们是拼写为x
的拼写Y
。因此,如*
指出的那样,git可以将FETCH_HEAD
链重新绑定到提示...-o-x-x-Y [abandoned]
\
*-*-*-Y' <-- tip
`------- new origin/tip
提交,这也很明显(嘿):
git pull --rebase
如果您使用&#34;常规&#34; fetch,而不是origin/tip
中的那个,它会更新远程分支origin/tip
,它会遮挡&#34; fork point&#34;这里很容易识别,至少在git fetch
移动到指向新提示之前。幸运的是,在git的reflogs中有足够的信息来重构它,而在git 1.9 / 2.0中,现在w
总是更新远程分支,有一种方法可以让git稍后找到fork-point ,这样你就可以更容易地从上游的rebase中恢复。
7 更准确地说,它与存储袋中的提交{{1}}具有相同的树。