Git 分支名称实际上没有任何嵌套意义:它们只是指向特定提交的指针。
首先,画出提交 DAG(一部分)
通常情况下,我们需要做的是绘制一些提交有向无环图(DAG)片段,并考虑重新基于的情况。因此,我们从您的示例开始:
master
develop
project
<sprint_number>
<task_number>
并添加一些节点(并将它们的“真实名称”哈希值,例如
a1cf93a...
替换为单个大写字母,因为这些名称过于庞大和笨重):
并添加一些节点(并用单个大写字母代替它们的“真实名称”哈希值,如a1cf93a...
,因为这些名称太大且难以使用):
A <- B <- C <
\
D <- E <
\
F <- G <
\
H <
\
I <
(这里的反斜杠应该是向上和向左箭头,但是在普通文本中很难画出来)。
也就是说,在这种情况下,我们在 master
分支上有(至少)三个提交记录(在提交 A
之前可能有任意数量的提交记录,我们只是没有画出来)。master
的尖端是提交记录 C
,它指向回提交记录 B
,而 B
又指向回 A
。
我们在 develop
分支上有两个提交记录,它们不在 master
分支上:提交记录 E
是 develop
分支的尖端,它指向回 D
,而 D
又指向回 B
。提交记录 B
和它的所有祖先(包括 A
和之前的任何提交记录)都在 两个 分支 master
和 develop
上。
同时,提交记录 G
是 project
分支的尖端;G
指向回 F
,F
指向回 E
,以此类推。这意味着提交记录 A
和 B
实际上存在于所有 三个 分支中。但是等等,还有更多!H
是 <sprint_number>
分支的尖端,H
指向回 G
,以此类推;I
是 <task_number>
分支的尖端,I
指向回 H
。
最终,这意味着提交记录 A
和 B
存在于(至少)五个分支(如图所示),而提交记录 D
和 E
存在于至少四个分支,以此类推。
决定是否需要并允许变基
在 git 中,变基实际上是指将提交记录复制到新的、稍微不同/修改的提交记录中。(可能不是正确的方法。稍后我们将讨论这个问题,因为在你了解更多之前,它没有意义。)
现在,master
的尖端是提交记录 C
,而不是提交记录 B
。可以想象,在早些时候,master
的尖端是B
,那时我们做了提交记录 D
(也可能是 E
)。但现在你正在考虑将 develop
分支变基到 master
的新尖端上。
要实现这一点,您必须将提交
D
和
E
复制到新的不同提交中。 我们将称这些副本为
D'
和
E'
。即使没有其他变化 - 实际上可能会有其他变化,特别是在
B
和
C
之间有所不同的任何内容都会进入新的
D'
中 - 原始提交
D
的副本
D'
必须指向提交
C
而不是提交
B
。
仅绘制此复制阶段(略去原始
E
的所有内容),我们得到:
A - B - C <-- master
\ \
\ D' - E' <-- develop (after rebase)
\
D - E [abandoned]
这次我也简化了向左箭头,现在我们知道提交指向左边了。但是,虽然原始的 D
和 E
不再被分支名称 develop
指向,但是一旦我们填写绘图的其余部分,它们仍然是可以到达的:
A - B - C <
\ \
\ D' - E' <
\
D-E
\
F-G <
\
H <
\
I <
此时特别重要的是,原始提交 D
和 E
已经*不再在 develop
上。
如何使用 rebase 命令
忽略 --fork-point
(这里可以是一个解决方案),git rebase
命令实际上需要三个参数,其中一个通常只取自于 HEAD
:
- 要复制的最顶层提交(这通常只是“你当前的分支”,即
HEAD
);
- 限制要复制的提交的指示符,即指定-间接地-不复制的提交;以及
- 将添加第一个复制提交的提交的身份。
后两者通常合并为一个 <upstream>
参数。与此同时,您首先需要使用 git checkout
命令切换到要重新定位的分支来设置第一个参数。例如,如果我们决定将 develop
重新定位到 master
上:
git checkout develop
git rebase master
在这里,要复制的顶部提交是像往常一样的HEAD
提交,因为由于git checkout
,它是develop
的最顶端提交,并且新副本将生长的起始位置是master
的顶部。 Git会考虑复制每个在develop
上的提交(这将是A
、B
、D
和E
),但在此之前被告知避免复制master
上的每个提交(这意味着A
、B
和C
)。
(等等,什么?我们不应该复制C
吗?但我们一开始就不打算复制C
!那么没问题了,我们就不复制它!)这就是我们如何将两件事合并成一个<upstream>
参数。我们希望在C
之后添加新副本,并同时避免复制C
和从C
向后导致的所有路径。
所以如果我们选择继续进行这个git rebase
,我们将复制D
和E
到D'
和E'
,最终得到我们绘制的新图形片段。
这对于develop
来说很好,但如果我们现在执行以下操作会发生什么:
git checkout project
git rebase develop
这一次,我们会要求git将project
的尖端可达的所有内容(即G
、F
、E
、D
、B
和A
,以及可能还有其他东西)复制到已经变基的develop
的尖端,即提交E'
。
这是一个问题。如果我们运气好的话,它可能是一个自解决的问题,因为rebase会检测到某些被复制的提交并避免重新复制它们。也就是说,当git试图将D
复制到一个(另一个)新的副本D''
中时,它可能会检测到D
已经存在于E'
中。如果它确实检测到了,它就会跳过复制。当它试图将E
复制到E''
时,同样的事情也可能发生:它可能会检测到不需要复制,然后跳过复制。
另一方面,git的检测器可能会被愚弄,它可能会复制
D
和/或
E
。我们绝对不想要那样,所以最好完全避免要求git复制它们。
有很多种方法可以提出要求,包括交互式变基(在这里我们可以编辑
pick
指令,因此可以删除提交
D
和
E
的两个
pick
行),或者更聪明地使用
git rebase
的参数:
git checkout project
git rebase
这第二个命令使用reflog历史记录告诉git要复制的提交是那些在
project
(当前分支)上但不在
project
的
之前的尖端包含的提交。也就是说,
'project@{1}'
解析为未复制的原始提交
E
的提交ID。因此,这将只复制提交
F
和
G
,到
F'
和
G'
。
(顺便说一下,如果你用彩色记号笔在白板上画你的DAGs,你可以用颜色来代表原始提交和它们的副本。我觉得这比所有的
D'
和
D''
符号更容易阅读。我只是无法在StackOverflow上画它。)
我们可以使用reflog重复这个过程来处理sprint和task,以识别要留下的提交。
自从git 1.9,git rebase现在有了--fork-point,它本质上自动化了我们在这里使用reflogs所做的事情。(在git 2.1中有一个修复bug的git rebase --fork-point,无法发现不需要复制的提交,因此明智的做法是将此选项限制为2.1或更高版本。)因此,这可能是一种做法。
最后,在回到是否这是一个好主意的问题之前,我要再做一点说明。我们可以先重新定位任务,而不是将develop重新定位到master,将project重新定位到develop等等。这会告诉git将提交
D
复制到
D'
,将
E
复制到
E'
,将
F
复制到
F'
,一直到将
I
复制到
I'
。任务分支将指向新提交
I'
,其历史链回溯到
C
。现在,我们只需要通过找到正确的副本来重新定位sprint分支、project分支和develop分支的副本提交。更新后的develop应该指向
E'
;更新后的project应该指向
G'
;更新后的sprint分支应该指向
H'
。
如果还有其他的sprint分支和/或task分支,它们可能需要复制一些提交,这些提交不会被上述方法复制,因此必须小心使用此技巧。像往常一样,画DAG会有所帮助。
重定位是否正确?
如果你的分支结构如此复杂,那么变基可能不是正确的方法。即使不是这样,也可能不是正确的做法。
要记住,正如我们刚刚看到的那样,变基涉及将提交复制一份,然后将分支标签移动到新的副本上,而不是原始版本。当你在仅 你 使用的存储库中执行此操作时,通常不会太混乱,因为你移动了所有分支标签,并且现在已经完成:你可以选择旧的、预复制状态或者新的、复制后的状态,你可以忽略所有中间(中途变基)状态,除非你需要进行所有这些变基的短暂期���。
但是,如果有其他人分享这个存储库,请考虑你将会给他们带来的影响。在进行大规模的变基之前,他们认为自己拥有正确的develop
、project
、sprint和task分支指针。他们正在使用原始(尚未复制)提交并创建依赖于这些原始提交的新提交。
现在,你来告诉他们:“哦,嘿,忘掉那些旧的提交吧!使用这些全新的、闪亮的提交代替它们!” 现在,他们必须去找到所有依赖旧提交的工作,并将其更新为依赖于新提交。
换句话说,他们必须应对一个“上游变基”,或者实际上是很多次的上游变基。这通常不是一件有趣的事情(尽管使自动化这个过程成为可能的--fork-point
代码也使他们能够自动恢复从上游变基中)。
--fork-point
有一个时间限制,因为它使用reflog条目,并且reflog条目会过期。如果你没有重新配置这些内容,git会在30天后默认过期关键reflog条目,所以,如果你要做这个操作,其他人有大约一个月的时间来恢复。
--fork-point
的明确示例就更好了。 - ijoseph--fork-point
。如果使用不当,我记得你可能会失去自己的提交记录:我记得我曾经想过这个问题并创建了一个测试案例,结果让我很烦恼。因此,我建议使用没有--fork-point
的git rebase -i
,并从那里开始工作。 - torek