父级被压扁并提交后,如何根据母版重新设置分支?
How to rebase branch against master after parent is squashed and committed?
我在 Gitlab 中使用了一个乐观的工作流程,它假设我的大部分合并请求都将被接受而无需更改。流程如下所示:
- 提交分支的合并请求
cool-feature-A
- 基于
cool-feature-A
创建一个名为 cool-feature-B
的新分支。开始在此分支上开发。
- 一位同事批准了我对
cool-feature-A
的合并请求。
- 我将
cool-feature-B
重新设置为 master
(无痛)并继续开发。
如果同事在第 3 步进行挤压合并,就会出现问题。这会重写 cool-feature-B
中存在的大量历史记录。当我到达第 4 步时,我面前出现了合并痛苦的世界。
如何避免这种情况发生?
本质上,你必须告诉Git:我想根据master
变基cool-feature-B
,但我想要复制一组不同于您通常在此处计算的提交集。 最简单的方法是使用 --onto
。也就是说,通常你运行,如你所说:
git checkout cool-feature-B
git rebase master
但你需要做:
git rebase --onto master cool-feature-A
之前你删除自己的branch-name/标签cool-feature-A
.
你总是可以这样做,即使他们使用正常的合并。这样做不会有什么坏处,除了你必须输入更多并记住(无论你喜欢什么)你会想要这个 --onto
,稍后需要正确的名称或哈希 ID。
(如果你可以获取 cool-feature-A
指向的提交的哈希 ID 到 cool-feature-B
上游的 reflog 中,你可以使用 --fork-point
功能使 Git 稍后自动为您计算,前提是相关的 reflog 条目尚未过期。但一般来说,这可能比手动执行此操作更难。另外,它具有整个 "provided" 部分。 )
为什么会这样
像往常一样,让我们从图表开始。最初,您在自己的存储库中有此设置:
...--A <-- master, origin/master
\
B--C <-- cool-feature-A
\
D <-- cool-feature-B
一旦您拥有 运行 git push origin cool-feature-A cool-feature-B
,您将拥有:
...--A <-- master, origin/master
\
B--C <-- cool-feature-A, origin/cool-feature-A
\
D <-- cool-feature-B, origin/cool-feature-B
请注意,我们在这里所做的只是添加两个 origin/
名称(两个 remote-tracking 名称):在 他们的 存储库中,在 origin
,他们获取了提交 B
、C
和 D
,并将 他们的 cool-feature-A
和 cool-feature-B
名称设置为记住分别提交 C
和 D
,所以你自己的 Git 添加了 你的 origin/
这两个名字的变体。
如果他们(无论 "they" 是谁——在 origin
上控制存储库的人)进行 fast-forward 合并,他们将滑动 他们的 master up to point to commit C
。 (请注意,GitHub 网络界面 没有用于进行 fast-forward 合并的按钮。 我没有使用 GitLab 的网络界面;它可能在很多方面有所不同。)如果他们强制进行真正的合并——这是 GitHub 网页 "merge this now" 默认情况下点击按钮所做的;再一次,我不知道 GitLab 在这里做了什么——他们将进行新的合并提交 E
:
...--A------E
\ /
B--C
\
D
(这里我特意去掉了所有的名字,因为他们的名字和你的不太匹配)。他们可能会删除(或者甚至可能从未真正创建过)他们的 cool-feature-A
名字。无论哪种方式,您都可以拥有自己的 Git fast-forward 您的 master
名称,同时更新您的 origin/*
名称:
...--A------E <-- master, origin/master
\ /
B--C <-- cool-feature-A [, origin/cool-feature-A if it exists]
\
D <-- cool-feature-B, origin/cool-feature-B
或:
...--A
\
B--C <-- cool-feature-A, master, origin/master [, origin/cool-feature-A]
\
D <-- cool-feature-B, origin/cool-feature-B
现在是否删除你的名字cool-feature-A
——为了以后画图方便,假设你删除了——如果你运行:
git checkout cool-feature-B
git rebase master
Git 现在将枚举可从 D
—D
、C
、B
等到达的提交列表从 master
中减去可访问的提交列表:E
(如果存在),然后 A
(如果 E
存在)和 C
(无论是否存在) E
存在),然后是 B
,依此类推。减法的结果就是 commit D
.
您的 Git 现在复制此列表中的提交,以便新副本出现在 master
的提示之后:即,如果它们进行了真正的合并,则在 E
之后,或者在 C
之后,如果他们进行了 fast-forward 合并。所以 Git 要么将 D
复制到 E
之后的新提交 D'
:
D' <-- cool-feature-B
/
...--A------E <-- master, origin/master
\ /
B--C
\
D <-- origin/cool-feature-B
或者它单独留下 D
因为它已经在 C
之后(所以没有什么新东西可以绘制)。
棘手的部分发生在他们使用 GitLab 的 GitHub 的 "squash and merge" 或 "rebase and merge" 可点击按钮的等效项时。我将跳过 "rebase and merge" 案例(这通常不会导致问题,因为 Git 的 rebase 也会检查 patch-IDs)并直接进入困难的案例,即 "squash and merge"。正如您正确指出的那样,这使得 新的和不同的 ,单一的,提交。当您将新提交放入您自己的存储库时——例如,在 git fetch
之后——你有:
...--A--X <-- master, origin/master
\
B--C <-- cool-feature-A [, origin/cool-feature-A]
\
D <-- cool-feature-B, origin/cool-feature-B
其中 X
是进行新提交的结果,其 snapshot 将匹配合并 E
(如果他们要进行合并 E
),但其(单个)父项是现有提交 A
。所以历史——通过向后工作枚举的提交列表——从 X
只是 X
,然后是 A
,然后是 A
.
之前的任何提交
如果你 运行 一个普通的 git rebase master
而在 cool-feature-B
, Git:
- 枚举可从
D
访问的提交:D
、C
、B
、A
、...;
- 枚举可从
X
访问的提交:X
、A
、...;
- 从第1步的集合中减去第2步的集合,剩下
D
、C
、B
;
- 复制那些提交(按 un-backwards-ized 顺序)以便它们在
X
. 之后
请注意,第 2 步和第 3 步都使用词 master
来查找提交:要复制的提交,对于第 2 步,是那些 aren' 可从 master
到达。对于步骤 3,放置副本的位置是 after master
.
的提示
但是如果你运行:
git rebase --onto master cool-feature-A
您 Git 在第 2 步和第 3 步中使用 不同的 项目:
- 要复制的提交列表,来自第 2 步,来自
cool-feature-A..cool-feature-B
:从 D-C-B-A-...
中减去 C-B-A-...
。那只剩下提交 D
.
- 第3步中放置副本的位置来自
--onto master
:将它们放在X
之后。
所以现在 Git 只将 D
复制到 D'
,然后 git rebase
将名称 cool-feature-B
拉到指向 D'
:
D' <-- cool-feature-B
/
...--A--X <-- master, origin/master
\
B--C <-- cool-feature-A [, origin/cool-feature-A]
\
D <-- origin/cool-feature-B
这就是你想要的。
如果他们——控制 GitLab 存储库的人——使用真正的合并或 fast-forward not-really-a-merge-at-all,这一切仍然有效:你将拥有你的 Git 枚举 D
-on-backwards 但从列表中删除 C
-on-backwards,只留下 D
进行复制;然后 Git 将复制 D
以便它出现在 E
(真正的合并案例)或 C
(fast-forward 案例)之后。 fast-forward 案例 "copy D so that it comes where it already is" 巧妙地根本不去复制,只是将所有内容留在原地。
我在 Gitlab 中使用了一个乐观的工作流程,它假设我的大部分合并请求都将被接受而无需更改。流程如下所示:
- 提交分支的合并请求
cool-feature-A
- 基于
cool-feature-A
创建一个名为cool-feature-B
的新分支。开始在此分支上开发。 - 一位同事批准了我对
cool-feature-A
的合并请求。 - 我将
cool-feature-B
重新设置为master
(无痛)并继续开发。
如果同事在第 3 步进行挤压合并,就会出现问题。这会重写 cool-feature-B
中存在的大量历史记录。当我到达第 4 步时,我面前出现了合并痛苦的世界。
如何避免这种情况发生?
本质上,你必须告诉Git:我想根据master
变基cool-feature-B
,但我想要复制一组不同于您通常在此处计算的提交集。 最简单的方法是使用 --onto
。也就是说,通常你运行,如你所说:
git checkout cool-feature-B
git rebase master
但你需要做:
git rebase --onto master cool-feature-A
之前你删除自己的branch-name/标签cool-feature-A
.
你总是可以这样做,即使他们使用正常的合并。这样做不会有什么坏处,除了你必须输入更多并记住(无论你喜欢什么)你会想要这个 --onto
,稍后需要正确的名称或哈希 ID。
(如果你可以获取 cool-feature-A
指向的提交的哈希 ID 到 cool-feature-B
上游的 reflog 中,你可以使用 --fork-point
功能使 Git 稍后自动为您计算,前提是相关的 reflog 条目尚未过期。但一般来说,这可能比手动执行此操作更难。另外,它具有整个 "provided" 部分。 )
为什么会这样
像往常一样,让我们从图表开始。最初,您在自己的存储库中有此设置:
...--A <-- master, origin/master
\
B--C <-- cool-feature-A
\
D <-- cool-feature-B
一旦您拥有 运行 git push origin cool-feature-A cool-feature-B
,您将拥有:
...--A <-- master, origin/master
\
B--C <-- cool-feature-A, origin/cool-feature-A
\
D <-- cool-feature-B, origin/cool-feature-B
请注意,我们在这里所做的只是添加两个 origin/
名称(两个 remote-tracking 名称):在 他们的 存储库中,在 origin
,他们获取了提交 B
、C
和 D
,并将 他们的 cool-feature-A
和 cool-feature-B
名称设置为记住分别提交 C
和 D
,所以你自己的 Git 添加了 你的 origin/
这两个名字的变体。
如果他们(无论 "they" 是谁——在 origin
上控制存储库的人)进行 fast-forward 合并,他们将滑动 他们的 master up to point to commit C
。 (请注意,GitHub 网络界面 没有用于进行 fast-forward 合并的按钮。 我没有使用 GitLab 的网络界面;它可能在很多方面有所不同。)如果他们强制进行真正的合并——这是 GitHub 网页 "merge this now" 默认情况下点击按钮所做的;再一次,我不知道 GitLab 在这里做了什么——他们将进行新的合并提交 E
:
...--A------E
\ /
B--C
\
D
(这里我特意去掉了所有的名字,因为他们的名字和你的不太匹配)。他们可能会删除(或者甚至可能从未真正创建过)他们的 cool-feature-A
名字。无论哪种方式,您都可以拥有自己的 Git fast-forward 您的 master
名称,同时更新您的 origin/*
名称:
...--A------E <-- master, origin/master
\ /
B--C <-- cool-feature-A [, origin/cool-feature-A if it exists]
\
D <-- cool-feature-B, origin/cool-feature-B
或:
...--A
\
B--C <-- cool-feature-A, master, origin/master [, origin/cool-feature-A]
\
D <-- cool-feature-B, origin/cool-feature-B
现在是否删除你的名字cool-feature-A
——为了以后画图方便,假设你删除了——如果你运行:
git checkout cool-feature-B
git rebase master
Git 现在将枚举可从 D
—D
、C
、B
等到达的提交列表从 master
中减去可访问的提交列表:E
(如果存在),然后 A
(如果 E
存在)和 C
(无论是否存在) E
存在),然后是 B
,依此类推。减法的结果就是 commit D
.
您的 Git 现在复制此列表中的提交,以便新副本出现在 master
的提示之后:即,如果它们进行了真正的合并,则在 E
之后,或者在 C
之后,如果他们进行了 fast-forward 合并。所以 Git 要么将 D
复制到 E
之后的新提交 D'
:
D' <-- cool-feature-B
/
...--A------E <-- master, origin/master
\ /
B--C
\
D <-- origin/cool-feature-B
或者它单独留下 D
因为它已经在 C
之后(所以没有什么新东西可以绘制)。
棘手的部分发生在他们使用 GitLab 的 GitHub 的 "squash and merge" 或 "rebase and merge" 可点击按钮的等效项时。我将跳过 "rebase and merge" 案例(这通常不会导致问题,因为 Git 的 rebase 也会检查 patch-IDs)并直接进入困难的案例,即 "squash and merge"。正如您正确指出的那样,这使得 新的和不同的 ,单一的,提交。当您将新提交放入您自己的存储库时——例如,在 git fetch
之后——你有:
...--A--X <-- master, origin/master
\
B--C <-- cool-feature-A [, origin/cool-feature-A]
\
D <-- cool-feature-B, origin/cool-feature-B
其中 X
是进行新提交的结果,其 snapshot 将匹配合并 E
(如果他们要进行合并 E
),但其(单个)父项是现有提交 A
。所以历史——通过向后工作枚举的提交列表——从 X
只是 X
,然后是 A
,然后是 A
.
如果你 运行 一个普通的 git rebase master
而在 cool-feature-B
, Git:
- 枚举可从
D
访问的提交:D
、C
、B
、A
、...; - 枚举可从
X
访问的提交:X
、A
、...; - 从第1步的集合中减去第2步的集合,剩下
D
、C
、B
; - 复制那些提交(按 un-backwards-ized 顺序)以便它们在
X
. 之后
请注意,第 2 步和第 3 步都使用词 master
来查找提交:要复制的提交,对于第 2 步,是那些 aren' 可从 master
到达。对于步骤 3,放置副本的位置是 after master
.
但是如果你运行:
git rebase --onto master cool-feature-A
您 Git 在第 2 步和第 3 步中使用 不同的 项目:
- 要复制的提交列表,来自第 2 步,来自
cool-feature-A..cool-feature-B
:从D-C-B-A-...
中减去C-B-A-...
。那只剩下提交D
. - 第3步中放置副本的位置来自
--onto master
:将它们放在X
之后。
所以现在 Git 只将 D
复制到 D'
,然后 git rebase
将名称 cool-feature-B
拉到指向 D'
:
D' <-- cool-feature-B
/
...--A--X <-- master, origin/master
\
B--C <-- cool-feature-A [, origin/cool-feature-A]
\
D <-- origin/cool-feature-B
这就是你想要的。
如果他们——控制 GitLab 存储库的人——使用真正的合并或 fast-forward not-really-a-merge-at-all,这一切仍然有效:你将拥有你的 Git 枚举 D
-on-backwards 但从列表中删除 C
-on-backwards,只留下 D
进行复制;然后 Git 将复制 D
以便它出现在 E
(真正的合并案例)或 C
(fast-forward 案例)之后。 fast-forward 案例 "copy D so that it comes where it already is" 巧妙地根本不去复制,只是将所有内容留在原地。