【发布时间】:2016-12-05 17:58:12
【问题描述】:
git rebase 如何从源(通常是功能)分支中选择开始提交?
我猜 git 可以追溯到 src 和 dst 分支的共同祖先。
如果两个分支没有共同提交怎么办?
【问题讨论】:
-
这取决于你传递给rebase的参数的
refspec。
git rebase 如何从源(通常是功能)分支中选择开始提交?
我猜 git 可以追溯到 src 和 dst 分支的共同祖先。
如果两个分支没有共同提交怎么办?
【问题讨论】:
refspec。
要知道的一件有用的事情——你可能已经知道了——是 rebase 通过 复制 提交来工作。它只复制正确的提交,使新副本在新基础结束后立即生效。
选择要变基(复制)的提交实际上使用了了解 Git 及其提交选择的最重要的事情之一。了解了这一点,也就了解了git log 和git rev-list 的工作原理。1
首先,请记住 Git 的提交形成一个 图表(特别是 D 直接 A循环 G 图表或DAG,但您不必担心很长时间)。每个提交都会记住它的父级,或者对于合并提交,它的所有父级。当我们绘制的图形部分没有合并提交时,我们将得到一个 tree 结构而不是任意 DAG。当你没有合并时,rebase 效果最好,因为 rebase 通常会丢弃合并。
我们可以——而且你应该——画出这些图表。您可以让 Git 为您执行此操作,例如 git log --graph。它垂直绘制它们,这对我们的目的来说占用了太多空间,所以我们将它们水平绘制,右侧是较新的提交,左侧是较旧的提交。
这是一个示例图表:
...<- o <- o <- o <- o
\
o <- o <- o <- o
每个o 代表图中的一些提交。在正式的图论中,每个提交都是图中的一个 vertex,但有时这些被称为 nodes,我倾向于使用“nodes”和“commit nodes”这两个词"来描述它们。
每个提交节点的“真实名称”是一个 Git 哈希,是那些丑陋的 40 个字符 a234567... 的东西之一。给定一个 Git 哈希,Git 可以在存储库中查找任何对象(当然包括提交)。但不知何故,我们必须记住这些“真名”,而这些“真名”是完全无法记住的。
由于每个提交都会记住其父级,因此我们可以从任何提交开始并在历史中向后工作(但不能向前!)。我们需要记住一个分支的最近或tip-most提交。我们让 Git 为我们执行此操作,让 Git 将丑陋的大哈希保存在 分支名称 中,例如 master 或 develop。
您可以使用git rev-parse 将这样的名称转换为哈希:
$ git rev-parse master
08bb3500a2a718c3c78b0547c68601cafa7a8fd9
这意味着master 指向真实名称为08bb350... 的提交。该提交在其中包含先前提交的真实名称,等等。
让我们再次绘制该示例图,但这次添加分支名称。我也会让它更紧凑:我们知道提交总是指向“向后”(指向他们的父母)所以没有必要把它们画成箭头,我们可以使用连接线。这次我要用* 标记其中的两个提交:
...--*--*--o--o <-- master
\
o--o--o--o <-- develop
请注意,name master 特别选择了master 分支的tip。同样,名称develop 仅选择develop 分支的尖端。但是 Git 通常不只是选择 one 提交。通常,当我们告诉 Git 特别查看一个提交时,我们实际上是在要求 Git 考虑该提交和它的所有父提交。
当我们从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 必须以某种方式选择 only 在develop 上的四个提交,同时排除所有 的提交也上master。
这就是 Git 的 X..Y 语法的用武之地。奇怪的是,rebase 不使用它!这是有原因的,但让我们暂时看一下语法。使用这种语法——在这种情况下,使用master..develop——我们要求 Git 从一个提示提交开始,develop 的提示,并选择每个可以追溯到过去的提交,一直到开始,它可以从那里;但也可以从master 的提示开始,un-选择每个返回时间的提交。
我喜欢将其视为临时绘画提交绿色(开始)和红色(停止)。我们可以先做绿色油漆,在develop 上绘制四个os 加上两个*s 加上它们之前的任何东西,然后从master 上的两个os 开始在顶部涂上红色油漆并继续到两个*s 以及之前的所有内容。或者,我们可以先画红色,然后再画绿色,但一旦找到红色节点就停止绘画。无论哪种方式,我们都将只得到四个独占develop 的提交“涂成绿色”。
这就是git rebase 知道复制这四个提交而不是任何其他提交的方式。
rebase 开始从的地方通常是你当前的分支:
$ git branch
diff-merge-base
master
precious
* stash-exp
(所以在这种情况下,我目前在stash-exp)。
rebase 复制到的地方——或者更确切地说,“在之后复制”——来自git rebase 的参数:
$ git rebase master
事实证明,这也是git rebase 获得“红色提交”概念的地方(不要复制的内容)。
Rebase 有效地接受您的论点,例如 master,以及您当前的分支名称(在我的例子中为 stash-exp,但假设为 develop)并使用 git rev-list2获取要复制的提交的 ID:
$ git rev-list master..develop
(当然,您必须在 rebase 之前运行此命令)。
当您运行 git rebase 时,它会尝试检查 other 分支(您正在变基的分支)是否具有您拥有的提交的副本。也就是说,假设我们看我们画的版本图是这样的:
o--o
/
...--*--*
\
o--o--o--o
在这张图中,有两个来自最终共同 * 提交的分叉。我们可以轻松地将其中一个重新定位到另一个上。但是,如果顶线o 提交之一或多或少匹配底线o 提交之一怎么办?省略额外的会很好。让我们将底线重新定位到顶部,但让我们将这些提交标记为 A、B、C 和 D,并注意在顶部,os 之一是 Just Like @987654396 @:
o--B'
/
...--*--*
\
A--B--C--D
(例如,当您使用cherry-pick 时,您会得到这种图表)。提交B 和B' 基本上是彼此的副本。因此,当我们对较低的四个提交进行 rebase 时,我们真的应该只复制 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 链会发生什么变化?我们在这里将它标记为“已放弃”,但实际上,Git 会使用 reflog 机制在它上面挂起一段时间——例如,我们可以让 Git 找到 develop@{1},它会找到原始提交D——还有特殊名称ORIG_HEAD,rebase 设置为指向D。默认情况下,reflog 条目会保留 30 天,3 而名称 ORIG_HEAD 会一直保留,直到有东西(通常是另一个变基)覆盖它。
有时,这一点 Git 魔法——使用像 master 这样的名称来“将提交涂成红色”,然后使用 相同的 名称来决定将副本放在哪里——是不够的。在某些情况下,您需要告诉git rebase 在某个特定点停止 复制,但将新 副本放在其他地方。在这种情况下,您可以使用git rebase --onto:
git rebase --onto <em>target</em> <em>upstream</em>
(the rebase documentation 调用 red-paint “停止” 参数 upstream)。默认情况下 upstream 既是 --onto 目标 和 是停止复制红色油漆指示器,它在停止点在右侧时起作用放置在要复制到的点的历史记录中。这通常是正确的——而且经常(但并非总是),作为 upstream 为某些分支 foo 提供的东西是您将设置的 origin/foo 远程跟踪分支4 作为foo 的upstream,我认为这就是为什么rebase 调用这个参数upstream。
如果两个分支没有共同提交怎么办?
在这种情况下,“绘制提交节点红色”步骤对“绘制提交绿色”步骤没有影响:
o--o--o--o <-- master
o--o--o <-- unrelated
如果您在分支unrelated 上运行git rebase master,Git 有效地将三个unrelated-branch 提交绘制为绿色,将四个master-branch 提交绘制为红色,然后采用绿色绘制的提交,这是从unrelated 的提示提交中可以访问的三个提交。然后,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 chery-pick,有时它使用git format-patch | git am。两种方式的效果都是一样的,我喜欢define rebase 在cherry-pick方面。