如何Git select rebase 起点?

How Git select the rebase starting point?

如何 git 变基 select 来自源(通常是功能)分支的开始提交?

我猜 git 可以追溯到 src 和 dst 分支的共同祖先。

如果两个分支没有共同的提交怎么办?

需要知道的一件有用的事情——你可能已经知道了——rebase 是通过复制 提交来工作的。它只复制正确的提交,使新副本在新基础结束后立即进行。

selection of commits to rebase (to copy) 实际上使用了关于 Git 及其提交 selection 的最重要的事情之一。当你理解了这一点,你也就理解了git loggit rev-list是如何工作的。1

首先,请记住 Git 的提交形成了一个 graph(特别是 D 直接 Acyclic Graph or DAG,但你不用担心很长时间)。每个提交都会记住它的 parent,或者对于合并提交,会记住它的所有 parent。当我们绘制的图形部分没有合并提交时,我们将得到一个 tree 结构而不是任意 DAG。当您没有合并时,变基效果最好,因为变基通常会丢弃合并。

我们可以——而且你应该——绘制这些图表。你可以让 Git 为你做这件事,例如 git log --graph。它是垂直绘制的,这对我们这里的目的来说占用了太多空间,所以我们将水平绘制它们,右边是较新的提交,左边是较旧的提交。

这是一个示例图:

...<- o <- o <- o <- o
            \
             o <- o <- o <- o

每个 o 代表图中的一些 commit。在正式的图论中,每个提交都是图中的一个 vertex,但有时这些被称为 nodes,我倾向于使用 nodes =315=] 和 "commit nodes" 来描述它们。

每个提交节点的 "true name" 是一个 Git 散列,其中一个丑陋的 40 个字符 a234567... 东西。给定一个 Git 散列,Git 可以在存储库中查找任何 object(当然包括提交)。但是不知何故我们必须记住这些 "true names",它们完全是 not-memorable.

由于每个提交都会记住它的 parent,不过,我们可以从任何提交开始并在历史中向后工作(但不能向前!)。我们需要的是记住分支的最近tip-most提交。我们让 Git 为我们做这件事,让 Git 将丑陋的大哈希保存在 分支名称 中,例如 masterdevelop.

你可以用git rev-parse把这样的名字变成哈希:

$ git rev-parse master
08bb3500a2a718c3c78b0547c68601cafa7a8fd9

这意味着 master 指向真实名称为 08bb350... 的提交。该提交在其中包含先前提交的真实名称,等等。

让我们再次绘制示例图,但这次添加分支名称。我也会让它更紧凑:我们知道提交总是指向 "backwards"(到它们的 parents)所以没有必要把它们画成箭头,我们可以只使用连接线。这次我将用 * 标记两个提交:

...--*--*--o--o        <-- master
         \
          o--o--o--o   <-- develop

请注意 name master selects,特别是 tip master 分支。同样,名称 develop select 只是 develop 分支的尖端。但是 Git 通常不只是 select 一个 提交。通常,当我们告诉 Git 特别查看一个提交时,我们实际上是在要求 Git 考虑该提交 其所有 parents.

当我们从 master 开始并向后工作时,我们得到两个完全在 master 上的提交(提示,以及提示之前的那个)然后我们得到第二个 * 提交,第一个 *,依此类推。

当我们从 develop 开始并向后工作时,我们得到了独占 develop 的四个提交,然后是第二个 * 提交,然后是第一个 *,依此类推。

也就是说,两个 * 提交实际上是在 两个 分支上。

请注意,我们可以像这样轻松地绘制图形:

          o--o          <-- master
         /
...--*--*--o--o--o--o   <-- develop

或者像这样:

          o--o        <-- master
         /
...--*--*
         \
          o--o--o--o   <-- develop

这些图都是同一个图,master没有什么特别的地方。

这是rebase必须解决的核心问题

如果我们想在 master 上变基 developgit rebase 必须以某种方式选择 在 [=30 上的四个提交=],同时排除 alsomaster.

上的所有提交

这就是 Git 的 X..Y 语法的用武之地。奇怪的是,rebase 不使用它!这是有原因的,但我们现在只看语法。使用这种语法——在本例中,使用 master..develop——我们要求 Git 从一个提示提交开始,develop 的提示,并且 select 每个提交及时返回,一直到开始,它可以从那里开始;但也从 master 的尖端开始,并且 un-select 每次提交都会及时返回。

我喜欢将此视为临时绘画提交绿色(去)和红色(停止)。我们可以先涂绿色,在 develop 上涂上四个 o 再加上两个*s 加上它们之前的所有内容,然后从 master 上的两个 os 开始在顶部涂上红色油漆,然后继续到两个 *s 以及它们之前的所有内容。或者,我们可以先画红色,然后画绿色,但一旦找到红色节点就停止绘制。无论哪种方式,我们最终都会得到四个 exclusive-to-develop 提交 "painted green".

这就是 git rebase 知道复制这四个提交而不是任何其他提交的方式。

rebase开始的地方通常是您当前的分支:

$ git branch
  diff-merge-base
  master
  precious
* stash-exp

(所以在这种情况下,我目前在 stash-exp)。

rebase 复制到 的地方——或者更确切地说,"copies after"——来自 git rebase 的参数:

$ git rebase master

事实证明,这也是 git rebase 得到 "red commits" 想法的地方(不是复制)。

Rebase 有效地接受了您的论点,例如 master,以及您当前的分支名称——在我的例子中,stash-exp,但假设是 develop——并使用 git rev-list2 获取要复制的提交的 ID:

$ git rev-list master..develop

(当然,你必须 运行 这个 变基之前。

额外的皱纹 #1

当您 运行 git rebase 时,它会尝试检查 other 分支(您要重新定位的分支)是否具有承诺你有。也就是说,假设我们看一下我们画的版本图是这样的:

          o--o
         /
...--*--*
         \
          o--o--o--o

在此图中,有两个来自最终共同 * 提交的分支。我们可以很容易地将其中一个变基到另一个上。但是,如果 top-line o 之一的提交或多或少匹配 bottom-line o 之一的提交怎么办?省略额外的部分会很好。让我们将底线重新定位到顶部,但让我们将这些提交标记为 ABCD 并注意,在顶部,os 就像 B:

          o--B'
         /
...--*--*
         \
          A--B--C--D

(这是您使用 cherry-pick 时得到的那种图表)。提交 BB' 基本上是彼此的副本。所以当我们变基较低的四个提交时,我们真的应该只复制 ACD,给出:

          o--B'
         /    \
...--*--*      A'-C'-D'
         \
          A--B--C--D

最后,让我们重新贴上标签。我们希望master指向B'develop指向D',像这样:

          o--B'          <-- master
         /    \
...--*--*      A'-C'-D'  <-- develop
         \
          A--B--C--D     [abandoned]

原来的 A--B--C--D 链发生了什么变化?我们在这里将其标记为 "abandoned",但实际上,Git 会保留一段时间,同时使用 reflog 机制——例如,我们可以询问Git 找到 develop@{1} 找到原始提交 D——以及特殊名称 ORIG_HEADrebase 设置为指向 D .默认情况下,reflog 条目会保留 30 天,3 而名称 ORIG_HEAD 会保留直到某些东西(通常是另一个 rebase)覆盖它。

额外的皱纹 #2

有时,这 Git 魔法——使用一个名字,例如 master 到 "paint commits red",然后使用 相同的 名字决定把副本放在哪里——是不够的。对于某些情况,您需要告诉 git rebase 停止 在某个特定点复制,但将 new 副本放在其他地方。在这种情况下,您可以使用 git rebase --onto:

git rebase --onto <em>target</em> <em>upstream</em>

the rebase documentation 调用 red-paint "stop" 参数 upstream)。默认是 upstream 既是 --onto 目标 又是 stop-copy red-paint指示器,当停止点位于指向 copy-onto 的历史记录中的正确位置时,它会起作用。这通常是正确的——而且经常(但不总是),作为 upstream 对于某些分支 foo 给出的东西是 origin/foo remote-tracking 分支,你将设置 4 作为 fooupstream,我认为这就是为什么 rebase 调用这个参数 upstream.

如果两个分支没有共同的提交怎么办?

What if the two branches have no common commits?

在这种情况下,"paint commit nodes red"步骤对"paint commits green"步骤没有影响:

o--o--o--o   <-- master

o--o--o      <-- unrelated

如果你在分支 unrelated 并且你 运行 git rebase master,Git 有效地绘制了三个 unrelated-分支提交绿色和四个master-branch 提交红色,然后采用 green-painted 提交,这是从 unrelated 的 tip 提交可以到达的三个提交。然后 rebase 代码复制这些提交:

o--o--o--o           <-- master
          \
           o--o--o   <-- unrelated

o--o--o              [abandoned]

1好吧,git rev-list 有大约一百万个标志,所以这有点夸大其词,因为它对所有旗帜。 :-)

2这里有一些复杂的方面:有时 git rebase 实际上直接使用 git rev-list,有时则不然。不过效果差不多。

3这是可配置的:gc.reflogExpiregc.reflogExpireUnreachable 控制默认值,您可以为特定模式设置其他名称。

4你可以使用 git branch --set-upstream-to 显式设置,但对于这些类型的分支,通常会在您最初使用 git checkout 创建分支时自动设置。一旦它被设置,git rebase,没有额外的参数,也会自动找到它。