存储,拉动和拉伸之间是否存在重大差异? popping vs. commiting&拉 - 基地?

时间:2014-02-23 18:04:38

标签: git git-rebase git-stash

我的工作流程目标通常是避免无意义的合并。要做到这一点,我经常做两件事之一:

  1. 我提交了更改git pull --rebase以防止不必要的合并。如果我以后希望更改现有提交,我会使用git rebase --interactive
  2. 进行更改,提交并合并它们
  3. 我只是git stash我的更改,然后git pull正常而简单地git stash pop稍后当我希望修改和/或提交这些更改时。{/ li>

    有些同事提醒我git stash save / git stash pop不安全。 我想知道使用提交和git pull --rebase与存储列表是否有任何微妙的优势。

1 个答案:

答案 0 :(得分:12)

TL; DR版本

使用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具有,作为其父提交,提交EF&#34;指向&#34; E。同时E指回其父级,依此类推。名称{ {1}}只是指向提示最多的提交,即devel。)

如果您进行了常规提交,则会获得新提交F,并指向GF将更改为指向devel },&#34;向前移动尖端&#34;。

G(vs git stash)的第二个区别发生在&#34;另一端&#34;,就像它一样。当您运行git commit 2 时,git的作用与以下内容非常相似。 (有许多实现细节使得准确的描述变得困难,但我认为这是考虑它的方法。如果你git stash apply它更复杂;请记住,这个简化的图片只是& #34;足够接近&#34;非apply --keep-index案例。)

  1. 将存储袋与其挂起的提交区分开来。
  2. 然后,将这个差异作为补丁应用到你现在的任何地方,使用git的合并机制来做一个比简单的简单补丁更好的工作。 (即,git可以判断补丁的某些部分是否已经完成,如果已经完成,请跳过它们。)
  3. 要了解这种情况如何适用于您的情况,我们必须查看有--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>

    即使您提交了大量提交,G5git 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查看提交wH之间的更改,并将它们合并到您的工作目录中。也就是说,它将您的更改应用于提交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

    其中xY代表&#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}}具有相同的树。