如何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 log
和git 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 将丑陋的大哈希保存在 分支名称 中,例如 master
或 develop
.
你可以用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
上变基 develop
,git rebase
必须以某种方式选择 仅 在 [=30 上的四个提交=],同时排除 also 在 master
.
上的所有提交
这就是 Git 的 X..Y
语法的用武之地。奇怪的是,rebase 不使用它!这是有原因的,但我们现在只看语法。使用这种语法——在本例中,使用 master..develop
——我们要求 Git 从一个提示提交开始,develop
的提示,并且 select 每个提交及时返回,一直到开始,它可以从那里开始;但也从 master
的尖端开始,并且 un-select 每次提交都会及时返回。
我喜欢将此视为临时绘画提交绿色(去)和红色(停止)。我们可以先涂绿色,在 develop
上涂上四个 o
再加上两个*
s 加上它们之前的所有内容,然后从 master 上的两个 o
s 开始在顶部涂上红色油漆,然后继续到两个 *
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-list
2 获取要复制的提交的 ID:
$ git rev-list master..develop
(当然,你必须 运行 这个 在 变基之前。
额外的皱纹 #1
当您 运行 git rebase
时,它会尝试检查 other 分支(您要重新定位的分支)是否具有承诺你有。也就是说,假设我们看一下我们画的版本图是这样的:
o--o
/
...--*--*
\
o--o--o--o
在此图中,有两个来自最终共同 *
提交的分支。我们可以很容易地将其中一个变基到另一个上。但是,如果 top-line o
之一的提交或多或少匹配 bottom-line o
之一的提交怎么办?省略额外的部分会很好。让我们将底线重新定位到顶部,但让我们将这些提交标记为 A
、B
、C
和 D
并注意,在顶部,o
s 就像 B
:
o--B'
/
...--*--*
\
A--B--C--D
(这是您使用 cherry-pick
时得到的那种图表)。提交 B
和 B'
基本上是彼此的副本。所以当我们变基较低的四个提交时,我们真的应该只复制 A
、C
和 D
,给出:
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_HEAD
,rebase
设置为指向 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 作为 foo
的 upstream,我认为这就是为什么 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.reflogExpire
和 gc.reflogExpireUnreachable
控制默认值,您可以为特定模式设置其他名称。
4你可以使用 git branch --set-upstream-to
显式设置,但对于这些类型的分支,通常会在您最初使用 git checkout
创建分支时自动设置。一旦它被设置,git rebase
,没有额外的参数,也会自动找到它。
如何 git 变基 select 来自源(通常是功能)分支的开始提交?
我猜 git 可以追溯到 src 和 dst 分支的共同祖先。
如果两个分支没有共同的提交怎么办?
需要知道的一件有用的事情——你可能已经知道了——rebase 是通过复制 提交来工作的。它只复制正确的提交,使新副本在新基础结束后立即进行。
selection of commits to rebase (to copy) 实际上使用了关于 Git 及其提交 selection 的最重要的事情之一。当你理解了这一点,你也就理解了git log
和git 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 将丑陋的大哈希保存在 分支名称 中,例如 master
或 develop
.
你可以用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
上变基 develop
,git rebase
必须以某种方式选择 仅 在 [=30 上的四个提交=],同时排除 also 在 master
.
这就是 Git 的 X..Y
语法的用武之地。奇怪的是,rebase 不使用它!这是有原因的,但我们现在只看语法。使用这种语法——在本例中,使用 master..develop
——我们要求 Git 从一个提示提交开始,develop
的提示,并且 select 每个提交及时返回,一直到开始,它可以从那里开始;但也从 master
的尖端开始,并且 un-select 每次提交都会及时返回。
我喜欢将此视为临时绘画提交绿色(去)和红色(停止)。我们可以先涂绿色,在 develop
上涂上四个 o
再加上两个*
s 加上它们之前的所有内容,然后从 master 上的两个 o
s 开始在顶部涂上红色油漆,然后继续到两个 *
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-list
2 获取要复制的提交的 ID:
$ git rev-list master..develop
(当然,你必须 运行 这个 在 变基之前。
额外的皱纹 #1
当您 运行 git rebase
时,它会尝试检查 other 分支(您要重新定位的分支)是否具有承诺你有。也就是说,假设我们看一下我们画的版本图是这样的:
o--o
/
...--*--*
\
o--o--o--o
在此图中,有两个来自最终共同 *
提交的分支。我们可以很容易地将其中一个变基到另一个上。但是,如果 top-line o
之一的提交或多或少匹配 bottom-line o
之一的提交怎么办?省略额外的部分会很好。让我们将底线重新定位到顶部,但让我们将这些提交标记为 A
、B
、C
和 D
并注意,在顶部,o
s 就像 B
:
o--B'
/
...--*--*
\
A--B--C--D
(这是您使用 cherry-pick
时得到的那种图表)。提交 B
和 B'
基本上是彼此的副本。所以当我们变基较低的四个提交时,我们真的应该只复制 A
、C
和 D
,给出:
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_HEAD
,rebase
设置为指向 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 作为 foo
的 upstream,我认为这就是为什么 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.reflogExpire
和 gc.reflogExpireUnreachable
控制默认值,您可以为特定模式设置其他名称。
4你可以使用 git branch --set-upstream-to
显式设置,但对于这些类型的分支,通常会在您最初使用 git checkout
创建分支时自动设置。一旦它被设置,git rebase
,没有额外的参数,也会自动找到它。