存储,拉动和弹出与提交和拉动 - 基础之间是否存在重大差异?

pep*_*dip 5 git git-stash git-rebase

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

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

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

tor*_*rek 12

TL; DR版本

git stash只有当你没有自己的提交时,使用才能避免"无意义的合并".该git pull --rebase方法更为通用.

很长,教程版本:发生了什么,为什么等等.

使用git stash只是进行一些提交,1对于第一次近似,存储和提交基本相同.隐藏提交采取我喜欢称之为"隐藏包"的形式,这是一个挂在最尖端 - 当你做的时候你正在进行的任何分支stash.

第一个区别是隐藏袋提交不会移动分支的尖端.也就是说,假设分支(让我们称之为devel)看起来像这样:

... <- E <- F     <-- devel
Run Code Online (Sandbox Code Playgroud)

在收起后,它看起来像这样:

... <- E <- F     <-- devel
            |\
            i-w   <-- "the stash"
Run Code Online (Sandbox Code Playgroud)

(这是对"提交图"的[部分]的ASCII艺术描述:提交F是分支的提示.提交F有,作为其父提交,提交E:F"指回" E.同时E指回其父,这个名字devel只是指向最尖端的提交,即F.)

如果你做了一个常规的提交,你会得到一个新的提交G,指向回来F,devel并将被改为指向G"向前移动提示".

git stash(vs git commit)的第二个区别发生在"另一端",就像它一样.当你运行时git stash apply,2 git所做的就像下面这样.(有许多实现细节使得准确的描述变得困难,但我认为这是考虑它的方法.如果你apply --keep-index更复杂;请记住,这个简化的图片仅对非--keep-index案例"足够接近" . )

  1. 将存储袋与它挂起的提交区分开来.
  2. 然后,将diff作为补丁应用到你现在的任何地方,使用git的合并机制来做一个比简单的简单补丁更好的工作.(即,git可以判断补丁的某些部分是否已经完成,如果已经完成,请跳过它们.)

要了解这是如何适用于您的情况,我们必须看看有git pull没有做什么--rebase.


pull命令最好描述为fetch-then- merge.(它甚至在手册页中也是这样说的.)--rebase最好将其描述为fetch-then- rebase.所以我们有两种截然不同的情况,有两种不同的调用方式pull.

这个fetch步骤很容易描述.你fetch从一些"远程",它告诉git的调通过互联网,电话远程仓库:-) 3,并要求它什么分支机构,并承诺和这样了.然后你的 Git有自己的git交出任何新的东西,这在你的仓库了你的git的商店,让你有他们所做的一切,再加上自己的课程什么.4

再说一遍,让我们说你在分支机构devel,你就是这样做的git fetch.让我们进一步说,当你这样做时,分支devel看起来像这样:

... E - F - G   <-- devel
Run Code Online (Sandbox Code Playgroud)

当你的git联系他们的git时,它可能会发现你之前没有的新提交,例如commit H,它指向一些早期提交作为其父级.

也许H父提交是提交G.但是,要实现这一点,你的同事必须已经做出承诺G.因此,假设你做了G,你需要发布(推)它,以便她得到它,并使她成为H基础G.

但据推测,你还没有推G,并且她的承诺H指向了回归F.这是更一般的情况,所以让我们画出来:

... - E - F - G   <-- devel
            \
              H   <-- (her/their idea of what "devel" looks like)
Run Code Online (Sandbox Code Playgroud)

由于提交G对您的存储库是私有的,因此她和所有其他人一样认为链接已经存在E - F - H.您现在必须做一些事情,将"您的提交"与"她的提交"结合在一起.

对所发生工作的最准确描述的方式是您进行新的合并提交M:

... - E - F - G - M   <-- devel
            \   /
              H
Run Code Online (Sandbox Code Playgroud)

这就是git merge将要做的事情,所以这将是一个简单的事情git pull.

令人讨厌的事情是,历史上完全准确,它会让你获得这些"毫无意义的合并".5 所以你可以做的是,将你的旧提交复制G到一个新的,稍微不同的提交,G'.在G和之间会有两个变化G':(1)在与之相关的工作树中G',你将从H第一个变化中包含她的变化; 和(2)in G',你会说父提交H而不是F.这将看起来像这样 - 让我们移动您的旧G的线路E - F - H:

              G       [no longer needed, hence abandoned]
            /
... - E - F - H - G'  <-- devel
Run Code Online (Sandbox Code Playgroud)

这是一个"rebase"操作:复制现有的提交,根据需要更改其工作目录内容,将新提交添加到适当的位置(H),然后使您的branch-tip指向新副本中的最后一次提交.

即使你做了大量的提交,G1通过G5或其他什么,它只需要更多的复制.

当你使用时git pull --rebase,git会为你做这件事.首先它用于fetch引入任何新的提交,然后 - 如果有一些新的提交 - 将你以前的提交重新绑定到新的提交上.6


所以现在我们可以回过头来git stash.如果你没有自己做任何新的提交devel,但有一些正在进行的工作,并且你用git stash它来保存它,你得到这个:

... - E - F       <-- devel
          |\
          i-w
Run Code Online (Sandbox Code Playgroud)

你现在使用git pull没有--rebase,它带来了提交H("她的" - 我们完全跳过字母G,现在保留它)并进行合并.Git将此作为"快进合并",因为您没有自己的提交,并且您得到了这个:

... - E - F - H   <-- devel
          |\
          i-w
Run Code Online (Sandbox Code Playgroud)

然后你git stash apply,这使得混帐看变化之间的承诺F,并w在您的工作目录合并.也就是说,它将您的更改应用于提交的工作目录H.一旦你也drop藏匿了(或者我们只是不打扰它),git add你的改变git commit,你得到一个新的提交.由于某种原因:-)让我们称之为G'而不是G.所以你现在有:

... - E - F - H - G'  <-- devel
Run Code Online (Sandbox Code Playgroud)

看起来你先提交的一样,然后运行git pull --rebase.实际上,G早期案例中的"废弃" 提交实际上(丢弃的,即被放弃的)存储袋提交完全相同!7


但是,如果你什么已经做出了一些承诺(或几个,但我们只用一个)提交,G也可以先git stash多一些变化?然后你有这个:

... - E - F - G     <-- devel
              |\
              i-w   <-- stash
Run Code Online (Sandbox Code Playgroud)

现在你git pull(没有--rebase)并拿起她的提交H并合并它:

              H
            /   \
... - E - F - G - M    <-- devel
              |\
              i-w   <-- stash
Run Code Online (Sandbox Code Playgroud)

最后,你apply藏匿,确保它是好的,dropgit add,并做一个新的提交N:

              H
            /   \
... - E - F - G - M - N   <-- devel
Run Code Online (Sandbox Code Playgroud)

你有一个令人讨厌的"毫无意义的合并".当你做了git pull没有时,它进来了--rebase.


那么简短的版本就是git stash只保存您的培根(避免烦人的合并),如果您没有自己的提交.该git pull --rebase方法更为通用.(虽然,尽管有问题的"上游反叛"案例,但我更喜欢做一个单独的git fetch步骤.然后我查看进来的内容,并选择是否进行折扣或合并.但这取决于你.)


1具体来说,它至少提交了两次提交.首先,它使一个当前指数,即,如果你这样做,你会得到什么git commit,没有任何git add,git rm等等,并迫使承诺存在(LA --allow-empty),即使树是不变的.然后它以当前工作目录作为其内容进行多父提交,即合并提交.所有这些提交都是以不移动分支尖端的方式完成的.有关更多详细信息,请参阅这个答案.

2我建议使用git stash apply,检查结果,然后使用,git stash drop如果你对效果感到满意apply.该pop命令只是意味着apply-then- drop,即,它假设你满意.但是,如果你git stash经常使用,你可能会有多个藏匿处,并且你可能会意外地使用错误的或者过多的东西,或者某些东西.如果你习惯于" apply先把所有事情都搞定,只有这样drop",我认为你可能会减少错误.当然,人们不同.:-)

3除非"远程"确实是本地的,例如file://whatever,或本地路径; 或者可以想象,将来可能会有一些非"互联网"URL.Git并不真正关心它是如何从遥控器获取新东西的,只是它可以找出遥控器的内容,然后把它带到现在本地.

4当您使用时git pull,它会fetch在启用某些特殊限制时调用,以便您只获取要合并(或重新绑定)的内容.在1.8.4之前版本的git中,这也禁止更新本地"远程分支"条目,即获取无法保存一些有用的信息.正如git 1.8.4的发行说明所说:

这是一个早期的设计决策,以保持远程跟踪分支的更新可预测,但实际上,人们发现每当我们有机会时机会性地更新它们会更方便,并且当我们运行"git push时我们一直在更新它们"无论如何,这已经打破了原来的"可预测性".

5他们确实有一些意义(他们的意思是你和她并行工作),但到下周如果不早,没有人关心.那只是噪音.这通常适用于所有类型的提交:如果你尝试了一些东西,然后做了一些提交,并且必须将早期的"尝试某些东西"作为完全失败退出,那么尝试加上退出可能只是噪音.如果这些提交都是私有的(未发布),您可以使用交互式rebase来"编辑历史记录",使其看起来像是从未打扰过做失败的实验.与此同时,失败的实验可能实际上是有用的信息:"不要这样尝试,这不起作用".由你决定什么是好信息,什么是无意义噪音.

6值得注意的是,git pull --rebase在"上游历史重写"的情况下,这是非常聪明的.假设,在你拉之前,你有这个:

...-o-x-x-Y   <-- branch
         `------- origin/branch
Run Code Online (Sandbox Code Playgroud)

where ox代表"他们的"提交,并且Y是"你的"提交(它们可能是Y1-Y2-Y3等等;它最终会有相同的效果).假设当你运行git fetch步骤,原来"他们"重订branch自己,让而不是o-x-x作为什么是"上" origin/branch,你会得到o-*-*-*:

...-o-x-x-Y   <-- branch
     \   `------- old origin/branch
      *-*-*   <-- FETCH_HEAD, to become new origin/branch
Run Code Online (Sandbox Code Playgroud)

很明显(好吧,这张图应该让它看起来显而易见......)哪些提交在上游重新定位:它们是拼写*而不是拼写的x.因此,很明显(嘿)git可以将Y链重新绑定到提示*提交上,如下所示FETCH_HEAD:

...-o-x-x-Y     [abandoned]
     \
      *-*-*-Y'  <-- tip
           `------- new origin/tip
Run Code Online (Sandbox Code Playgroud)

如果你使用"常规"提取而不是输入git pull --rebase,这将更新远程分支,origin/tip它会模糊这里容易识别的"分叉点",至少直到origin/tip移动到指向新提示.幸运的是,在git的reflogs中有足够的信息来重构它,而在git 1.9/2.0中,现在git fetch总是更新远程分支,有一种方法可以让git稍后找到fork-point,这样你就可以从上游的rebases中恢复更容易.

7更确切地说,它w与存储袋中的提交具有相同的树.