如何使分支B与分支A具有相同的代码?
How to make branch B have the same code as branch A?
分支 A 的代码比分支 B 少。
我想将分支 A 合并到 B 中,这样 B 将以更少的代码结束,并且基本上具有与 A 完全相同的代码。类似于撤消多次提交。问题是我必须通过 Pull Request 合并来做到这一点。我不能直接推送到B,它必须通过A(功能分支)。
Pull Request 应该是什么样子的?当我尝试将 A 合并到 B 中时,它没有检测到任何差异 - 这是为什么?
如果我翻转 Pull Request(B 到 A),它会显示 B 有但 A 没有的所有更改。
测试变基
功能分支(包括清理过的代码)
B 你的开发者
第一次保存你的开发
git checkout B
git add
git commit -am "blabla my dev"
然后更新 A
git checkout A
git pull A
然后将 B 变基到 A 之上
git checkout B
git rebase A
此时您可能需要处理一些冲突
TL;DR
你想要一个新提交,其快照来自旧提交。然后你可以从这个做一个公关。使用普通的 Git 工具进行这个新提交是很棘手的,但是使用旁路进行它很容易。不过,我会把它留到长篇。
长
我们需要在这里区分 pull request——一个东西GitHub add,1 over and above what Git 做的——以及 Git 自己做的。一旦我们这样做,事情就会变得更清楚一些,尽管因为这是 Git,他们可能仍然很不清楚。
Git 实际上就是 提交 。 Git 与文件无关,但提交 包含 文件。 Git 也与 branches 无关,尽管我们(和 Git)使用分支名称来查找提交。所以 Git 就是关于 提交 。这意味着我们需要确切地知道提交是什么以及为我们做了什么:
每个提交都有 编号。然而,这些数字又大又丑,random-looking,用hexadecimal表示,例如e9e5ba39a78c8f5057262d49e261b42a8660d5b9
。我们称这些为 哈希 ID(有时更正式地说,object ID 或 OID)。不知道将来的提交会有什么哈希 ID。但是,一旦提交,that 哈希 ID 指的是 that 提交,并且没有其他提交,任何地方,永远。 2 这允许两个不同的 Git 存储库通过比较提交编号来查看它们是否具有相同的提交。 (我们不会在这里使用 属性,但它很重要。)
每个提交存储两件事:
一个提交有每个文件的完整快照(虽然这些是压缩的——有时压缩得非常厉害——并且,通过用于制作提交编号,de-duplicated).
一个提交也有一些关于提交本身的元数据:信息,比如谁做的,什么时候做的。在此提交数据中,每个提交都存储一个 previous 提交哈希 ID 的列表,通常只有一个元素长。单个 previous-commit 哈希 ID 是此提交的 parent。
这个 my-parent-is-Frank,Frank's-is-Barb 的东西将提交粘在一起到他们的祖先链中。当我们使用普通的 git merge
时,Git 使用祖先链来确定要合并的内容。我们不想要一个正常合并在这里。同时,同样的 parent 东西是 Git 如何将提交——一个 快照 ——变成一个“改变”:找出“我”发生了什么变化,如果我的 parent 是提交 feedcab
(不能是 frank
,那个 non-hexadecimal 字母太多)我提交 ee1f00d
,Git 比较 这两个提交中的快照。什么都一样,没变。不同的文件 确实 改变了,并且 Git 通过玩某种 Spot the Difference 游戏弄清楚 在 [=372= 中改变了什么] 他们并生成一个配方:对该文件的 feedcab
版本执行此操作,您将获得 ee1f00d
版本。
现在,实际上没有人使用原始提交编号来查找提交。您最近一次提交的提交编号是多少?你知道吗?你 关心吗? 可能不关心:你只需使用 main
或 master
或 develop
或一些 name找到它。
这是它的工作原理。假设我们有一个很小的存储库,其中只有三个提交。我们称它们为 A
、B
和 C
(而不是使用它们的真实哈希 ID,它们又大又丑,而且我们也不知道它们)。这三个提交看起来像这样:
A <-B <-C <--main
提交 C
是我们最新的。它有一个快照(所有文件的完整副本)和元数据。它的元数据列出了早期提交 B
的原始哈希 ID:我们说 C
指向 B
。与此同时,提交 B
有一个快照和一些元数据,并且 B
的元数据指向 A
。 A
有一个快照和元数据,并且由于 A
是 第一次 提交,它的元数据根本没有列出 parent。这是一个孤儿,有点(所有的提交都是处女出生,有点——好吧,让我们不要再沿着这条路走下去了)。所以这就是操作停止的地方,这就是我们知道只有三个提交的方式。
但是我们找到 commit C
by name: the name main
points to C
(保存 C
的原始哈希 ID),就像 C
指向 B
.
要进行新的提交,我们检查 main
,这样 C
就是我们的 当前 提交。我们更改内容、添加新文件、删除旧文件等等,然后使用 git add
然后 git commit
制作新快照。新快照获得一个新的 random-looking 哈希 ID,但我们将其称为 D
。D
指向 C
:
A <-B <-C <--main
\
D
现在 git commit
开始使用它的巧妙技巧:它将 D
的哈希 ID 写入 name main
:
A--B--C--D <-- main
现在 main
指向 D
而不是 C
,现在有四个提交。
因为人们使用名称,而不是数字来查找提交,我们可以通过丢弃我们对较新提交的访问权来返回到一些旧提交。我们强制使用一个名称,如 main
,指向一些较旧的提交,如 C
或 B
,并忘记 D
存在。这就是 git reset
的意义所在。不过,这可能不是您想要的,尤其是因为 Git 和 GitHub 喜欢 添加新提交 ,而不是将它们带走。特别是拉取请求不会让您取消提交。
不,您想要的是创建一个 new 提交,其 snapshot 匹配一些旧提交。
1如果您没有使用 GitHub,也许您正在使用其他一些也添加了 Pull Requests 的站点。这有点棘手,因为每个添加它们的站点都以自己的方式进行。例如,GitLab 有类似的东西,但称它们为 Merge Requests(我认为这是一个更好的名字)。
2这取决于一些密码技巧,最终会失败。哈希 ID 的大小(big-and-ugly-ness)会根据我们的需要推迟失败,尽管现在它有点太小了,它们很快就会变得更大更丑。
正常合并
在正常的日常 Git 用法中,我们创建分支名称,并使用这些分支名称来添加提交。我已经展示了一个非常简单的例子。让我们变得更复杂一点。和以前一样,我们将从一个小的存储库开始:
...--G--H <-- br1 (HEAD)
我在这里添加了 HEAD
符号来表示这是我们签出 的分支的名称。现在让我们添加另一个分支名称 br2
, 现在也选择提交 H
:
...--G--H <-- br1 (HEAD), br2
由于我们通过名称 br1
使用提交 H
,因此我们现在所做的任何 new 提交仅更新名称 br1
.让我们做两个新的提交:
I--J <-- br1 (HEAD)
/
...--G--H <-- br2
现在让我们再次检查提交 H
,git switch br2
:
I--J <-- br1
/
...--G--H <-- br2 (HEAD)
并再提交两次:
I--J <-- br1
/
...--G--H
\
K--L <-- br2 (HEAD)
我们现在可以 运行 git checkout br1
然后 git merge br2
,或者现在 运行 git merge br1
。先做前者吧:最后得到的snapshot两种方式都是一样的,只是其他的东西有点变化,所以我们只好挑一个。
无论哪种方式,Git 现在必须执行 真正的合并 (不是 fast-forward 假合并,而是真正的合并)。要执行合并,Git 需要弄清楚 we 在 br1
上发生了什么变化,以及 they 发生了什么(好吧,我们,但暂时没有)在 br2
上发生了变化。这意味着 Git 必须弄清楚我们俩 从哪里开始 — 如果我们只看图,就很清楚:我们都从提交 H
开始。我们进行了“我们的”更改并提交(多次)并获得了 J
.
中的快照
从H
到J
的区别:
git diff --find-renames <hash-of-H> <hash-of-J>
告诉 Git 我们 在 br1
.
上改变了什么
相似的区别:
git diff --find-renames <hash-of-H> <hash-of-L>
告诉 Git 他们 在 br2
上发生了什么变化。 (请注意 Git 在这里使用 commits:分支名称 br1
和 br2
,只是用于 find 提交。Git 然后使用历史记录(记录在每个提交的 parent 中)来找到 最佳共享 starting-point 提交 H
.)
为了执行合并本身,Git 现在 合并 两个差异列表。我们更改了一些文件而他们没有更改的地方,Git 使用我们的更改。他们更改了文件而我们没有更改的地方,Git 使用他们的更改。我们都更改了 相同的 文件,Git 必须合并这些更改。
如果我们都进行了完全相同的更改,那很好。如果我们触及 不同的行 ,那也很好——尽管这里有一个边缘情况:如果我们的更改邻接,Git 声明一个 合并冲突; 但如果它们完全重叠,并进行相同的更改,那没关系)。如果一切顺利,合并更改时不会发生合并冲突,Git 可以将合并的更改应用到来自 H
的快照。这会保留我们的更改并添加他们的 - 或者,等效地保留他们的更改并添加我们的更改。在我们的更改完全重叠的地方,Git 只保留一份更改。
生成的快照——H
加上两组更改——进入我们新的合并提交。不过,这个新的合并提交有一点特别之处。而不是只有 一个正常的 parent,在这种情况下——在分支 br1
上——将是 J
,它得到两个 parents:
I--J
/ \
...--G--H M <-- br1 (HEAD)
\ /
K--L <-- br2
一如既往,Git 更新 当前分支名称 以指向新的 merge commit M
.合并现已完成。
git merge -s ours
让我们画你想要的。你是从这个开始的:
o--o--...--R <-- br-A
/
...--o--*
\
o--o--...--L <-- br-B (HEAD)
您想 git merge br-A
,但 保留 br-B
末尾提交 L
的快照。
要在原始 Git 中完成您想要的 ,您需要 运行:
git switch br-B
git merge -s ours br-A
Git 现在会找到合并基础 *
(或者真的不麻烦),然后......完全忽略 他们的 更改,并进行当前分支上的新合并提交 M
:
o--o--...--R <-- br-A
/ \
...--o--* \
\ \
o--o--...--L---M <-- br-B (HEAD)
其中合并提交 M
有 L
和 R
作为它的两个 parent,但是使用提交 L
作为 快照.
这很简单,原始 Git。但是 GitHub 不会这样做!我们如何让 GitHub 提供这种结果?
我们得对 GitHub 耍点小花招
为了论证,假设我们要 git switch br-A
——即检查提交 R
——然后创建一个新提交,其 快照 是来自提交 L
吗?也就是说,我们制作:
o--...--R--L' <-- br-A (HEAD)
/
...--o--*
\
o--o--...--L <-- br-B
提交 L'
与提交 L
具有不同的 哈希 ID ,并且具有不同的 元数据 — 我们刚刚完成,带有我们的姓名和电子邮件以及日期和时间等等,它的 parent 是 R
——但具有与提交相同的 snapshot L
.
如果我们 Git 在此处执行 正常合并 ,Git 将:
git diff --find-renames <hash-of-*> <hash-of-L>
git diff --find-renames <hash-of-*> <hash-of-L'>
获取 Git 需要合并的两个差异。 这些差异将显示完全相同的变化。
正常合并将合并这些更改,方法是获取所有更改的一个副本。这就是我们想要的!最终的合并结果将是:
o--...--R--L' <-- br-A
/ \
...--o--* M <-- br-B (HEAD)
\ /
o--o--...--L
我没有特别的原因用另一种风格绘制它(中间有 M
)。 M
中的快照将匹配提交 L
和 L'
,分支 br-B
将在新提交处结束,没有 更改 到任何 文件,但最后有一个新的提交。
我们可以轻松地在 Git 中提交 L'
,然后通过 [=127] 上的 L'
向上发送提交,从而在 GitHub 上提出合并请求=] 分支。 PR 将顺利合并,通过在 br-B
中“改变”任何内容,只需添加新的合并提交 M
。所以——除了额外的 L'
提交——我们在分支 br-B
.[= 上得到与 git merge -s ours
运行 相同的 effect 154=]
困难重重
将快照 L'
添加到分支 br-A
的困难方法是:
git switch br-A
git rm -r . # from the top level
git restore -SW --source br-B -- .
git commit -C br-B
例如。第一步将我们置于 br-A
并检出提交 R
。第二个——git rm -r .
——从 Git 的索引 / staging-area 中删除所有文件,并从我们的工作树中删除相应的文件。 git restore
将所有文件 放回 但从 --source br-B
或提交 L
中取出它们,最后一步 git commit -C br-B
创建一个新文件使用来自提交 L
的消息提交。 (使用 -C
你可以编辑它。)
这很好用,就是有点慢。为了走得更快,我们可以使用两种技巧中的任何一种。这是第一个,可能是我实际使用的那个:
git switch br-A
git read-tree -u --reset br-B
git commit -C br-B
这消除了remove-and-restore,取而代之的是git read-tree
,可以一举完成。 (您可以使用 -m
而不是 --reset
但两个标志之一是必需的,并且 git read-tree
是一个棘手的命令,我不喜欢使用太多,所以我从来不记得随手使用哪一个:幸运的是,在这里并不重要。)
或者,我们可以这样做:
git switch br-B # so that we are not on br-A
git branch -f br-A $(git log --no-walk --format=%B br-B | git commit-tree -F - -p br-A br-B^{tree})
如果我没有打错字的话。但是,这使您没有机会编辑提交消息。你不需要直接签出 br-B
,你只需要确保你不是 on br-A
,或者你使用 git merge --ff-only
提交后继续前进。
如果 GitHub 可以做一个 git merge -s ours
就好了
但不能,就这样。
分支 A 的代码比分支 B 少。 我想将分支 A 合并到 B 中,这样 B 将以更少的代码结束,并且基本上具有与 A 完全相同的代码。类似于撤消多次提交。问题是我必须通过 Pull Request 合并来做到这一点。我不能直接推送到B,它必须通过A(功能分支)。
Pull Request 应该是什么样子的?当我尝试将 A 合并到 B 中时,它没有检测到任何差异 - 这是为什么? 如果我翻转 Pull Request(B 到 A),它会显示 B 有但 A 没有的所有更改。
测试变基 功能分支(包括清理过的代码) B 你的开发者
第一次保存你的开发
git checkout B git add git commit -am "blabla my dev"
然后更新 A
git checkout A git pull A
然后将 B 变基到 A 之上
git checkout B git rebase A
此时您可能需要处理一些冲突
TL;DR
你想要一个新提交,其快照来自旧提交。然后你可以从这个做一个公关。使用普通的 Git 工具进行这个新提交是很棘手的,但是使用旁路进行它很容易。不过,我会把它留到长篇。
长
我们需要在这里区分 pull request——一个东西GitHub add,1 over and above what Git 做的——以及 Git 自己做的。一旦我们这样做,事情就会变得更清楚一些,尽管因为这是 Git,他们可能仍然很不清楚。
Git 实际上就是 提交 。 Git 与文件无关,但提交 包含 文件。 Git 也与 branches 无关,尽管我们(和 Git)使用分支名称来查找提交。所以 Git 就是关于 提交 。这意味着我们需要确切地知道提交是什么以及为我们做了什么:
每个提交都有 编号。然而,这些数字又大又丑,random-looking,用hexadecimal表示,例如
e9e5ba39a78c8f5057262d49e261b42a8660d5b9
。我们称这些为 哈希 ID(有时更正式地说,object ID 或 OID)。不知道将来的提交会有什么哈希 ID。但是,一旦提交,that 哈希 ID 指的是 that 提交,并且没有其他提交,任何地方,永远。 2 这允许两个不同的 Git 存储库通过比较提交编号来查看它们是否具有相同的提交。 (我们不会在这里使用 属性,但它很重要。)每个提交存储两件事:
一个提交有每个文件的完整快照(虽然这些是压缩的——有时压缩得非常厉害——并且,通过用于制作提交编号,de-duplicated).
一个提交也有一些关于提交本身的元数据:信息,比如谁做的,什么时候做的。在此提交数据中,每个提交都存储一个 previous 提交哈希 ID 的列表,通常只有一个元素长。单个 previous-commit 哈希 ID 是此提交的 parent。
这个 my-parent-is-Frank,Frank's-is-Barb 的东西将提交粘在一起到他们的祖先链中。当我们使用普通的 git merge
时,Git 使用祖先链来确定要合并的内容。我们不想要一个正常合并在这里。同时,同样的 parent 东西是 Git 如何将提交——一个 快照 ——变成一个“改变”:找出“我”发生了什么变化,如果我的 parent 是提交 feedcab
(不能是 frank
,那个 non-hexadecimal 字母太多)我提交 ee1f00d
,Git 比较 这两个提交中的快照。什么都一样,没变。不同的文件 确实 改变了,并且 Git 通过玩某种 Spot the Difference 游戏弄清楚 在 [=372= 中改变了什么] 他们并生成一个配方:对该文件的 feedcab
版本执行此操作,您将获得 ee1f00d
版本。
现在,实际上没有人使用原始提交编号来查找提交。您最近一次提交的提交编号是多少?你知道吗?你 关心吗? 可能不关心:你只需使用 main
或 master
或 develop
或一些 name找到它。
这是它的工作原理。假设我们有一个很小的存储库,其中只有三个提交。我们称它们为 A
、B
和 C
(而不是使用它们的真实哈希 ID,它们又大又丑,而且我们也不知道它们)。这三个提交看起来像这样:
A <-B <-C <--main
提交 C
是我们最新的。它有一个快照(所有文件的完整副本)和元数据。它的元数据列出了早期提交 B
的原始哈希 ID:我们说 C
指向 B
。与此同时,提交 B
有一个快照和一些元数据,并且 B
的元数据指向 A
。 A
有一个快照和元数据,并且由于 A
是 第一次 提交,它的元数据根本没有列出 parent。这是一个孤儿,有点(所有的提交都是处女出生,有点——好吧,让我们不要再沿着这条路走下去了)。所以这就是操作停止的地方,这就是我们知道只有三个提交的方式。
但是我们找到 commit C
by name: the name main
points to C
(保存 C
的原始哈希 ID),就像 C
指向 B
.
要进行新的提交,我们检查 main
,这样 C
就是我们的 当前 提交。我们更改内容、添加新文件、删除旧文件等等,然后使用 git add
然后 git commit
制作新快照。新快照获得一个新的 random-looking 哈希 ID,但我们将其称为 D
。D
指向 C
:
A <-B <-C <--main
\
D
现在 git commit
开始使用它的巧妙技巧:它将 D
的哈希 ID 写入 name main
:
A--B--C--D <-- main
现在 main
指向 D
而不是 C
,现在有四个提交。
因为人们使用名称,而不是数字来查找提交,我们可以通过丢弃我们对较新提交的访问权来返回到一些旧提交。我们强制使用一个名称,如 main
,指向一些较旧的提交,如 C
或 B
,并忘记 D
存在。这就是 git reset
的意义所在。不过,这可能不是您想要的,尤其是因为 Git 和 GitHub 喜欢 添加新提交 ,而不是将它们带走。特别是拉取请求不会让您取消提交。
不,您想要的是创建一个 new 提交,其 snapshot 匹配一些旧提交。
1如果您没有使用 GitHub,也许您正在使用其他一些也添加了 Pull Requests 的站点。这有点棘手,因为每个添加它们的站点都以自己的方式进行。例如,GitLab 有类似的东西,但称它们为 Merge Requests(我认为这是一个更好的名字)。
2这取决于一些密码技巧,最终会失败。哈希 ID 的大小(big-and-ugly-ness)会根据我们的需要推迟失败,尽管现在它有点太小了,它们很快就会变得更大更丑。
正常合并
在正常的日常 Git 用法中,我们创建分支名称,并使用这些分支名称来添加提交。我已经展示了一个非常简单的例子。让我们变得更复杂一点。和以前一样,我们将从一个小的存储库开始:
...--G--H <-- br1 (HEAD)
我在这里添加了 HEAD
符号来表示这是我们签出 的分支的名称。现在让我们添加另一个分支名称 br2
, 现在也选择提交 H
:
...--G--H <-- br1 (HEAD), br2
由于我们通过名称 br1
使用提交 H
,因此我们现在所做的任何 new 提交仅更新名称 br1
.让我们做两个新的提交:
I--J <-- br1 (HEAD)
/
...--G--H <-- br2
现在让我们再次检查提交 H
,git switch br2
:
I--J <-- br1
/
...--G--H <-- br2 (HEAD)
并再提交两次:
I--J <-- br1
/
...--G--H
\
K--L <-- br2 (HEAD)
我们现在可以 运行 git checkout br1
然后 git merge br2
,或者现在 运行 git merge br1
。先做前者吧:最后得到的snapshot两种方式都是一样的,只是其他的东西有点变化,所以我们只好挑一个。
无论哪种方式,Git 现在必须执行 真正的合并 (不是 fast-forward 假合并,而是真正的合并)。要执行合并,Git 需要弄清楚 we 在 br1
上发生了什么变化,以及 they 发生了什么(好吧,我们,但暂时没有)在 br2
上发生了变化。这意味着 Git 必须弄清楚我们俩 从哪里开始 — 如果我们只看图,就很清楚:我们都从提交 H
开始。我们进行了“我们的”更改并提交(多次)并获得了 J
.
从H
到J
的区别:
git diff --find-renames <hash-of-H> <hash-of-J>
告诉 Git 我们 在 br1
.
相似的区别:
git diff --find-renames <hash-of-H> <hash-of-L>
告诉 Git 他们 在 br2
上发生了什么变化。 (请注意 Git 在这里使用 commits:分支名称 br1
和 br2
,只是用于 find 提交。Git 然后使用历史记录(记录在每个提交的 parent 中)来找到 最佳共享 starting-point 提交 H
.)
为了执行合并本身,Git 现在 合并 两个差异列表。我们更改了一些文件而他们没有更改的地方,Git 使用我们的更改。他们更改了文件而我们没有更改的地方,Git 使用他们的更改。我们都更改了 相同的 文件,Git 必须合并这些更改。
如果我们都进行了完全相同的更改,那很好。如果我们触及 不同的行 ,那也很好——尽管这里有一个边缘情况:如果我们的更改邻接,Git 声明一个 合并冲突; 但如果它们完全重叠,并进行相同的更改,那没关系)。如果一切顺利,合并更改时不会发生合并冲突,Git 可以将合并的更改应用到来自 H
的快照。这会保留我们的更改并添加他们的 - 或者,等效地保留他们的更改并添加我们的更改。在我们的更改完全重叠的地方,Git 只保留一份更改。
生成的快照——H
加上两组更改——进入我们新的合并提交。不过,这个新的合并提交有一点特别之处。而不是只有 一个正常的 parent,在这种情况下——在分支 br1
上——将是 J
,它得到两个 parents:
I--J
/ \
...--G--H M <-- br1 (HEAD)
\ /
K--L <-- br2
一如既往,Git 更新 当前分支名称 以指向新的 merge commit M
.合并现已完成。
git merge -s ours
让我们画你想要的。你是从这个开始的:
o--o--...--R <-- br-A
/
...--o--*
\
o--o--...--L <-- br-B (HEAD)
您想 git merge br-A
,但 保留 br-B
末尾提交 L
的快照。
要在原始 Git 中完成您想要的 ,您需要 运行:
git switch br-B
git merge -s ours br-A
Git 现在会找到合并基础 *
(或者真的不麻烦),然后......完全忽略 他们的 更改,并进行当前分支上的新合并提交 M
:
o--o--...--R <-- br-A
/ \
...--o--* \
\ \
o--o--...--L---M <-- br-B (HEAD)
其中合并提交 M
有 L
和 R
作为它的两个 parent,但是使用提交 L
作为 快照.
这很简单,原始 Git。但是 GitHub 不会这样做!我们如何让 GitHub 提供这种结果?
我们得对 GitHub 耍点小花招
为了论证,假设我们要 git switch br-A
——即检查提交 R
——然后创建一个新提交,其 快照 是来自提交 L
吗?也就是说,我们制作:
o--...--R--L' <-- br-A (HEAD)
/
...--o--*
\
o--o--...--L <-- br-B
提交 L'
与提交 L
具有不同的 哈希 ID ,并且具有不同的 元数据 — 我们刚刚完成,带有我们的姓名和电子邮件以及日期和时间等等,它的 parent 是 R
——但具有与提交相同的 snapshot L
.
如果我们 Git 在此处执行 正常合并 ,Git 将:
git diff --find-renames <hash-of-*> <hash-of-L>
git diff --find-renames <hash-of-*> <hash-of-L'>
获取 Git 需要合并的两个差异。 这些差异将显示完全相同的变化。
正常合并将合并这些更改,方法是获取所有更改的一个副本。这就是我们想要的!最终的合并结果将是:
o--...--R--L' <-- br-A
/ \
...--o--* M <-- br-B (HEAD)
\ /
o--o--...--L
我没有特别的原因用另一种风格绘制它(中间有 M
)。 M
中的快照将匹配提交 L
和 L'
,分支 br-B
将在新提交处结束,没有 更改 到任何 文件,但最后有一个新的提交。
我们可以轻松地在 Git 中提交 L'
,然后通过 [=127] 上的 L'
向上发送提交,从而在 GitHub 上提出合并请求=] 分支。 PR 将顺利合并,通过在 br-B
中“改变”任何内容,只需添加新的合并提交 M
。所以——除了额外的 L'
提交——我们在分支 br-B
.[= 上得到与 git merge -s ours
运行 相同的 effect 154=]
困难重重
将快照 L'
添加到分支 br-A
的困难方法是:
git switch br-A
git rm -r . # from the top level
git restore -SW --source br-B -- .
git commit -C br-B
例如。第一步将我们置于 br-A
并检出提交 R
。第二个——git rm -r .
——从 Git 的索引 / staging-area 中删除所有文件,并从我们的工作树中删除相应的文件。 git restore
将所有文件 放回 但从 --source br-B
或提交 L
中取出它们,最后一步 git commit -C br-B
创建一个新文件使用来自提交 L
的消息提交。 (使用 -C
你可以编辑它。)
这很好用,就是有点慢。为了走得更快,我们可以使用两种技巧中的任何一种。这是第一个,可能是我实际使用的那个:
git switch br-A
git read-tree -u --reset br-B
git commit -C br-B
这消除了remove-and-restore,取而代之的是git read-tree
,可以一举完成。 (您可以使用 -m
而不是 --reset
但两个标志之一是必需的,并且 git read-tree
是一个棘手的命令,我不喜欢使用太多,所以我从来不记得随手使用哪一个:幸运的是,在这里并不重要。)
或者,我们可以这样做:
git switch br-B # so that we are not on br-A
git branch -f br-A $(git log --no-walk --format=%B br-B | git commit-tree -F - -p br-A br-B^{tree})
如果我没有打错字的话。但是,这使您没有机会编辑提交消息。你不需要直接签出 br-B
,你只需要确保你不是 on br-A
,或者你使用 git merge --ff-only
提交后继续前进。
如果 GitHub 可以做一个 git merge -s ours
就好了
但不能,就这样。