我有这个场景:
o---o---o---o---o master
\
o---o---o---o---o next
\
o---o---o topic
如上所述,我在 master 上做了一些提交,我想更新其他两个分支并有:
o---o master
\
o---o---o---o---o next
\
o---o---o topic
我已经做了
git rebase master
在 next
分支,不用考虑 topic
现在还没有 next
的基础提交分支。我现在能做什么?立即重新设置中间分支是错误的吗?我该怎么办?
最佳答案
这个问题有两种相对简单的解决方案。一种是尝试 rebase,这可能会奏效。但如果没有,它仍然很容易。这是第二种更强大的方法,其次是为什么:
git checkout topic; git rebase --onto next next@{1}
发生了什么:rebase 是如何工作的
我认为,如果您以不同的方式绘制图形,则 View 会更清晰。这是原始的,略有重绘:
A---B---C---D---E <-- master
\
F---G---H---I---J <-- next
\
K---L---M <-- topic
这是您在
git checkout next; git rebase master
之后所拥有的:A---B---C---D---E <-- master
\ \
| F'-G'-H'-I'-J' <-- next
\
F---G---H---I---J [was "next", but not anymore]
\
K---L---M <-- topic
这是因为 rebase 通过复制提交来工作,并进行一些轻微(或重大)更改。新提交
F'
是原件F
的副本, G'
是 G
的副本, 等等。如果您现在运行
git checkout topic; git rebase next
,它通常只是有效 - 但并非总是如此。原因是,默认情况下,rebase
从单个参数中选择要复制的提交和复制的位置。这有点复杂,所以让我们分两步进行。当你说
git rebase next
在这里,单个参数是 next
: 副本将在 next
的提示提交之后进行(现在是 J'
)。这部分非常简单:一个标识符,next
, 找到提交 --onto
附加副本。 (我拼写这个 --onto
而不仅仅是“onto”是有原因的。:-))被复制的部分更棘手,有两种方式。一、
next
指定不复制的内容,而当前分支( topic
)指定复制的内容。 Git 获取可从 topic
访问的所有提交的列表——这是提交 A
, B
, F
, G
, H
,等等一直到 L
.然后它排除所有从 next
可达的提交,这是提交 A
, B
, C
, D
, E
, F'
(但不是 F
),G'
(但不是 G
),依此类推至 J'
.一些排除的提交不在包含列表中并不重要:我们只是排除那些在包含列表中的提交。不幸的是,这会导致提交
F
, G
, H
, I
, 和 J
在一堆要复制的提交中。正如您所注意到的,这就是事情出错的地方。但不知何故,大多数时候,这无论如何都有效。它工作时发生了什么?为什么它不总是有效?答案是 rebase 也排除了“补丁等效提交”。这里的一般想法是,当您重新设置基础时,Git 会扫描“排除”列表以查看其中是否有任何提交与“包含”列表中的提交执行相同的操作。自
F'
是 F
的副本,很可能与 F
“相同” ,至少在补丁等效意义上。 G
也是如此对比 G'
, H
对比 H'
, 等等。只要提交与其重新调整的版本补丁相同,它们就会被排除在外。
如果这是真的——而且通常是这样——你可以简单地
git checkout topic; git rebase next
它只是有效。当它不是真的时——当一些 F
通过 H
被复制时被修改了“太多”,他们的副本不再是补丁等效的——它失败了。作为一种经验法则,1 如果 Git 能够自己完成第一个 rebase ,那么它也能够自己完成第二个 rebase 。主要是当它因冲突而停止并让您手动修复它时,补丁会变成“非补丁等效”。但是处理这个很容易,因为
git rebase
有一个更长的形式。代替:
git rebase next
我们可以写:
git rebase --onto next <something>
根据我们绘制的图形,
<something>
在这里使用的是任何指定提交 J
的内容。 .也就是说,我们需要找到哪里next
曾是。做了什么提交 next
指点一下,在我们rebase之前呢?它现在指向 J'
但它曾经指向J
.我们需要 Git 排除的提交是那些 next
用于到达:A
, B
, F
, G
, H
, I
, 和 J
.如果 Git 不能自己找到这些,通过补丁等效性,我们可以告诉它。这仍然留下了如何拼写标识提交
J
的名称的问题。 .一种方法是通过原始哈希 ID,它总是有效,但有点痛苦(查找和复制,尽管剪切和粘贴对于复制哈希非常有效)。但是再看看重绘的图,上面写着 was "next", but not anymore
.如果我们能告诉 Git 查找 next
的先前值就好了.可是等等! Git 有 reflog!我们可以告诉它这样做!
next
的先前值拼写为 next@{1}
. (每次我们更改标签 next
时,数字都会增加,例如通过向其添加新提交,但我们刚才所做的只是 rebase
,所以它仍然是 next@{1}
。)所以在这里我们可以这样做:git checkout topic; git rebase --onto next next@{1}
(根据您的 shell,您可能需要在带有大括号的内容周围使用引号,例如
'next@{1}'
或 next@\{1\}
或类似的内容)。1 有一些异常(exception)。例如,在这种情况下,如果
git diff E F'
不会产生与 git diff B F
相同的变更集由于 diff 选择不同的半无关匹配,例如空白行或大括号行,这会导致问题。但是,处理这些问题的答案与处理由于冲突而手动应用的 rebase 的答案相同。
关于git - 从另一个分支重新建立一个分支,该分支是从 master 重新建立的,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39594021/