git 合并远程分支后重新设置分支
git rebase a branch when it has merged a remote branch
我对 github 分支的 PR 显示如下提交:
- "commit msg 1"
- "commit msg 2"
- "Merge remote-tracking branch 'upstream/dev' into this branch."
- "commit msg 3"
- "commit msg 4"
- "Merge remote-tracking branch 'upstream/dev' into this branch."
我想重新设置此分支的基线,并将带有消息 "commit msg *"
的所有四次提交压缩为一次提交。
首先我尝试了:
git rebase -i <commit id of the first commit>
它向我展示了包含许多其他提交的历史记录,这些提交是合并 upstream/dev
的结果;显示如下输出:
- pick "commit msg1"
- pick "someone else's commit 1"
- pick "someone else's commit 2"
- pick "someone else's commit 3"
- pick "commit msg2"
- pick "someone else's commit 4"
- pick "someone else's commit 5"
...
我尝试将所有提交的 pick
设置为 f
,解决合并冲突后,它显示了我分支中 upstream/dev
中所做的所有更改,就好像我是重新实现它们。
我试过:
-
-
我知道我可以尝试 merge --squash
(例如,),但这会创建一个单独的分支。
为了清楚起见,此处的示例提交已简化,实际分支包含约 250 个提交,使用 rebase 时,它显示约 300,000 个提交,这是有道理的,因为它是在超过 2 年的活跃存储库。
关于如何最好地将此分支重新设置为单个提交有什么建议吗?
TL;DR
你几乎可以肯定做想要git merge --squash
,但是用一个分离的HEAD然后branch-movement操作完成,例如:
$ git checkout upstream/master
$ git merge --squash yourbranch
$ git checkout -B yourbranch # or git branch -f yourbranch HEAD
(但请参阅下面的长答案)。
长
I know I can try merge --squash (e.g., ), but that creates a separate branch.
使用 git merge --squash
不会 创建一个单独的分支(它只是创建一个提交)。但即使有也没关系,因为 Git 的分支基本上没有意义:您可以随时以您喜欢的方式更改或重新排列您的分支名称。 Git 中重要的不是 branches——或者更准确地说,branch names——而是 commits. Git 存储库是提交的集合,加上一些辅助信息。可以更改辅助信息。提交不能。 分支名称是这个可变辅助信息的一部分。
每个提交都有自己独特的丑陋大哈希 ID。这些哈希 ID 是提交的真实名称。每次提交都是完全、完全 read-only。您不能更改任何现有提交。提交的哈希 ID 表示 that 提交,没有其他提交。但是关于这些哈希 ID 的事情是它们看起来是完全随机的。您将如何找到 正确的哈希 ID?
好吧,一方面,每个提交都存储了一组其他早期提交的哈希 ID。这些是这个特定提交的 parent 提交。大多数提交只存储一个 parent 哈希 ID。
当一个提交存储另一个较早提交的哈希 ID 时,我们说较晚的提交 指向 较早的提交。 (请注意,没有提交可以存储 later 提交的哈希 ID,因为在创建较早的提交时,稍后提交的哈希 ID 尚不存在,并且一旦创建,任何提交都不能更改。)因此,当您有一个接一个创建的一长串提交时,每个提交都指向 backwards 上一个提交。如果我们把它画出来——使用大写字母代表真正的提交哈希 ID——我们会得到一张看起来像这样的图片:
... <-F <-G <-H
这里 H
是 最新的 提交,带有一些哈希 ID H。现在已永久冻结的实际提交本身包含早期提交的原始哈希 ID G
。提交 G
包含较早提交 F
的原始哈希 ID,后者又包含另一个较早提交的哈希 ID,依此类推。
这是 分支名称 的用武之地。分支名称只包含 last 提交的哈希 ID,我们想说的是"on the branch"。因此,如果 H
是某个分支上的 last 提交,我们只需将其哈希 ID 放入分支名称即可。此名称现在指向提交 H
,就像 H
指向 G
:
...--F--G--H <-- branch1
我们现在可以创建另一个分支名称,例如 base
,并使其指向现有提交 F
(使用 git branch base <hash-of-F>
):
...--F <-- base
\
G--H <-- branch1
我们不得不绘制提交图 - F-G-H
行 - 有点不同以挤入名称,但 commits 通过此操作完全没有改变。我们所做的只是创建一个指向 F
的新名称,以便 F
是分支 base
上的最后一次提交。名称 branch1
仍然标识 H
,因此 H
是分支 branch1
上的最后一次提交。
让我们删除 base
(git branch -d base
),并创建一个也指向 H
的新名称 feature
。我们也会确保 git checkout feature
,以便 HEAD
附加到名称 feature
:
...--F--G--H <-- branch1, feature (HEAD)
(例如,我们可以用 git checkout -b feature branch1
来做到这一点。)现在我们将以通常的方式进行新的提交。这个新的提交获得了一个新的、唯一的哈希 ID,但我们只称它为 I
。 Git 现在所做的是 移动 名称 feature
以便它指向新提交 I
。新提交 I
的 parent 是 H
,因此 I
指向 H
:
...--F--G--H <-- branch1
\
I <-- feature (HEAD)
这就是分支:它们只是名称或标签,指向特定的提交,具有特殊技巧,当您 git checkout
其中一个时,您不仅可以让该提交准备好工作同时,您还安排 next git commit
操作来 update 名称。
您在 Git 中所做的几乎所有事情都是关于创建或获取提交,然后使各种名称指向这些新创建或获取的提交中的特定提交。分支名称只是让您 找到 一些特定的提交。根据定义,名称是该分支上的 last 提交。如何移动分支名称并不重要。只要该名称存在,它就指向某个提交。该提交是分支上的最后一次提交。移动名称,您就更改了哪个提交是分支上的最后一次提交。您没有更改任何 提交 — 它们都还在那里 — 您只是更改了 哪个 提交那个分支的最后一个。
这给我们带来了变基
git rebase
所做的是复制一些提交集,然后移动分支名称。例如,考虑这张图:
...--F--G--H <-- master
\
I--J <-- feature
你首先做一个 git checkout
你想要的名字 move-after-copying:
$ git checkout feature
然后你 运行 一个 git rebase
命令。它需要一些参数,文档称之为 --onto
和 upstream
参数。这些指定了一个 target 提交,这是副本应该去的地方,也应该复制哪些提交:
$ git rebase master
你可以只给出一个参数,就像这里一样——git rebase master
——在这种情况下,目标提交和提交集都是使用同一个名称找到的。在这里,目标提交是提交 H
,要复制的提交集是提交 I
和 J
.
rebase 命令现在复制每个提交,就像使用 git cherry-pick
一样。副本获得新的哈希 ID。这里有很多繁琐的极端情况,您可以使用 git rebase
的选项,但在这种情况下很简单,我们最终会得到一个 I
的副本,它有一个新的和不同的哈希 ID,我们称之为 I'
,以及 J
的副本,我们称之为 J'
。 I
和I'
有两个很大的区别,这里图中我们可以看到的一个是I'
的parent不是G
而是H
。 commit-copy J'
:
也是如此
I'-J' <-- HEAD (detached)
/
...--F--G--H <-- master
\
I--J <-- feature
(你无法在这张图中看到的区别是提交I'
保存的快照可能与I
保存的快照不同,因为 cherry-pick 有效地接受了从 G
到 I
的更改,并将该更改应用于 H
中的快照,而不是 G
。)
复制这两个提交后,通过移动分支名称完成变基:
I'-J' <-- feature (HEAD)
/
...--F--G--H <-- master
\
I--J [abandoned]
提交 I
和 J
会怎样?答案有点复杂,但我们现在只需要:还没有。 Git 将它们保留一段时间,以防您认为变基是个坏主意。但是它们变得很难找到。新提交 J'
很容易找到:名称 feature
找到它。提交 I'
很容易找到:我们只需转到 J'
,然后按照其 backwards-pointing 箭头到达 I'
。但是提交 J
的哈希 ID 是什么?它 在名字 feature
中是 ,但现在不是了。如果你能找到J
,你可以用它来找到I
,但是除非你在某处保存了J
的散列ID,否则这可能有点棘手。 1 最终——通常是从现在起 30 天后的某个时间——Git 将把它们完全回收为不需要的,如果你没有使用其他名称——例如其他分支或标签名称——来确保他们留下来。
1就目前而言,很容易找到:Git将其保存在名称ORIG_HEAD
下。但其他命令将替换 ORIG_HEAD
的哈希 ID。不过,还有第二种方法可以找到它,使用 git reflog
,这就是默认情况下至少保持提交至少一个月左右的原因。
为什么 rebase 复制太多
一个合并提交有两个(或更多)parent。当我们 Git 遵循 backwards-pointing link 从提交到他们的 parent 时,它通常遵循 all [=416] =]秒。因此,从合并提交中,Git 下降 both 路径。
您的真实提交图一点也不简单,但是您的 question-example 提交图可能还不错。它可能看起来像这样:
Y--Z <-- upstream/master
/
...--o--o--o--W--o--o--o--X <-- upstream/dev
\ \ \
A--B-----M--C--D--N <-- yourbranch (HEAD)
这里,"on"(可从)yourbranch
无法从 upstream/dev
访问的六个提交,即提交 X
,是 A-B-M-C-D-N
.让我们更仔细地看一下:
如果我们从提交 X
开始并向后工作,我们将找到的提交都是 W
和 X
之间的所有未字母提交,加上 W
,加上W
.
前所有不带字母的
如果我们从 yourbranch
开始——提交 N
——并向后工作,我们访问提交 X
(通过 link 来自 N
) 和 提交 D
(通过 N
中的 link)。从 D
我们到达 C
,然后到 M
,然后到 both B
和一些未命名的提交。我们也从 X
得到那个未命名的提交。
如果我们从 origin/master
开始,或者提交 Z
,我们将访问 Z
,然后是 Y
,然后是 W
,然后是 W
.
之前的所有未命名提交
所以如果我们 运行 git rebase
像这样:
git checkout yourbranch
git rebase upstream/master
您的 Git 将列出所有可从 N
访问的提交,这些提交 not 可从 Z
访问。您使用 upstream/master
作为目标 (--onto
) 和上游 ("don't copy commits reachable from Z
")。这意味着 Git 不会 复制提交 W
和更早的提交——它们可以从 Z
访问——但是 会 也复制 o-o-o-X
提交 A-B-C-D
。 Rebase 通常会丢弃所有合并提交所以它会抛出 M
和 N
,但是你只剩下八个提交被复制而不是四个。
您可以使 rebase 复制更少的提交
你可以做的一件事是 运行:
git rebase --onto upstream/master upstream/dev
这将不复制 参数与放置副本 参数的位置分开。我们仍然告诉 rebase:将副本放在提交 Z
之后,但是这次,我们告诉 rebase:*不要复制可从 X
访问的提交。所以 Git 列出提交 A-B-M-C-D-N
作为要复制的提交,然后扔掉 M
和 N
因为它们是合并的,剩下的就是复制 [=148] =].
如果这个 rebase 一切顺利,你将得到这个:
A'-B'-C'-D' <-- yourbranch (HEAD)
/
Y--Z <-- upstream/master
/
...--o--o--o--W--o--o--o--X <-- upstream/dev
\ \ \
A--B-----M--C--D--N [abandoned]
您现在可以从中创建拉取请求。
您想要一次提交
I want to [end up with] a single commit.
也就是说,如果我们绘制想要的结果,它可能看起来像这样:
ABCD <-- yourbranch (HEAD)
/
Y--Z <-- upstream/master
/
...--o--o--o--W--o--o--o--X <-- upstream/dev
\ \ \
A--B-----M--C--D--N [abandoned]
其中 ABCD
是单个提交,如果您第一次变基,然后进行第二次变基以将它们全部压缩为一个提交,就会产生这样的效果。
要到达那里,您可以使用以下命令序列:
$ git checkout upstream/master
$ git merge --squash yourbranch
$ git checkout -B yourbranch # or git branch -f yourbranch HEAD
第一个 git checkout
为您提供了一个 分离的 HEAD 指向由 upstream/master
标识的提交,即提交 Z
。如果愿意,可以使用临时分支名称:
$ git checkout -b temp upstream/master
这给你:
Y--Z <-- upstream/master, HEAD
/
...--o--o--o--W--o--o--o--X <-- upstream/dev
\ \ \
A--B-----M--C--D--N <-- yourbranch
或:
Y--Z <-- upstream/master, temp (HEAD)
/
...--o--o--o--W--o--o--o--X <-- upstream/dev
\ \ \
A--B-----M--C--D--N <-- yourbranch
git merge --squash
使用您想要的内容构建一个新的 non-merge 提交:
ABCD <-- HEAD
/
Y--Z <-- upstream/master
/
...--o--o--o--W--o--o--o--X <-- upstream/dev
\ \ \
A--B-----M--C--D--N <-- yourbranch
(或名称 temp
附有 HEAD
的同一绘图,现在已移至指向 ABCD
)。
最后一步是从提交 N
中提取(yoink?)名称 yourbranch
并使其指向新提交 ABCD
,这是 git branch -f
的位置或git checkout -B
进来。这两者之间的主要区别在于HEAD
之后是否附加到yourbranch
:
ABCD <-- HEAD, yourbranch
/
Y--Z <-- upstream/master
/
...--o--o--o--W--o--o--o--X <-- upstream/dev
\ \ \
A--B-----M--C--D--N [abandoned]
或:
ABCD <-- yourbranch (HEAD)
/
Y--Z <-- upstream/master
/
...--o--o--o--W--o--o--o--X <-- upstream/dev
\ \ \
A--B-----M--C--D--N [abandoned]
(在 HEAD
和 yourbranch
的 reflog 中最终出现的内容存在一些其他细微差别,但我们并未真正涵盖此处的 reflogs)。
(我不会讨论 git merge --squash
是如何工作的,因为这已经很长了。)
我对 github 分支的 PR 显示如下提交:
- "commit msg 1"
- "commit msg 2"
- "Merge remote-tracking branch 'upstream/dev' into this branch."
- "commit msg 3"
- "commit msg 4"
- "Merge remote-tracking branch 'upstream/dev' into this branch."
我想重新设置此分支的基线,并将带有消息 "commit msg *"
的所有四次提交压缩为一次提交。
首先我尝试了:
git rebase -i <commit id of the first commit>
它向我展示了包含许多其他提交的历史记录,这些提交是合并 upstream/dev
的结果;显示如下输出:
- pick "commit msg1"
- pick "someone else's commit 1"
- pick "someone else's commit 2"
- pick "someone else's commit 3"
- pick "commit msg2"
- pick "someone else's commit 4"
- pick "someone else's commit 5"
...
我尝试将所有提交的 pick
设置为 f
,解决合并冲突后,它显示了我分支中 upstream/dev
中所做的所有更改,就好像我是重新实现它们。
我试过: - -
我知道我可以尝试 merge --squash
(例如,),但这会创建一个单独的分支。
为了清楚起见,此处的示例提交已简化,实际分支包含约 250 个提交,使用 rebase 时,它显示约 300,000 个提交,这是有道理的,因为它是在超过 2 年的活跃存储库。
关于如何最好地将此分支重新设置为单个提交有什么建议吗?
TL;DR
你几乎可以肯定做想要git merge --squash
,但是用一个分离的HEAD然后branch-movement操作完成,例如:
$ git checkout upstream/master
$ git merge --squash yourbranch
$ git checkout -B yourbranch # or git branch -f yourbranch HEAD
(但请参阅下面的长答案)。
长
I know I can try merge --squash (e.g., ), but that creates a separate branch.
使用 git merge --squash
不会 创建一个单独的分支(它只是创建一个提交)。但即使有也没关系,因为 Git 的分支基本上没有意义:您可以随时以您喜欢的方式更改或重新排列您的分支名称。 Git 中重要的不是 branches——或者更准确地说,branch names——而是 commits. Git 存储库是提交的集合,加上一些辅助信息。可以更改辅助信息。提交不能。 分支名称是这个可变辅助信息的一部分。
每个提交都有自己独特的丑陋大哈希 ID。这些哈希 ID 是提交的真实名称。每次提交都是完全、完全 read-only。您不能更改任何现有提交。提交的哈希 ID 表示 that 提交,没有其他提交。但是关于这些哈希 ID 的事情是它们看起来是完全随机的。您将如何找到 正确的哈希 ID?
好吧,一方面,每个提交都存储了一组其他早期提交的哈希 ID。这些是这个特定提交的 parent 提交。大多数提交只存储一个 parent 哈希 ID。
当一个提交存储另一个较早提交的哈希 ID 时,我们说较晚的提交 指向 较早的提交。 (请注意,没有提交可以存储 later 提交的哈希 ID,因为在创建较早的提交时,稍后提交的哈希 ID 尚不存在,并且一旦创建,任何提交都不能更改。)因此,当您有一个接一个创建的一长串提交时,每个提交都指向 backwards 上一个提交。如果我们把它画出来——使用大写字母代表真正的提交哈希 ID——我们会得到一张看起来像这样的图片:
... <-F <-G <-H
这里 H
是 最新的 提交,带有一些哈希 ID H。现在已永久冻结的实际提交本身包含早期提交的原始哈希 ID G
。提交 G
包含较早提交 F
的原始哈希 ID,后者又包含另一个较早提交的哈希 ID,依此类推。
这是 分支名称 的用武之地。分支名称只包含 last 提交的哈希 ID,我们想说的是"on the branch"。因此,如果 H
是某个分支上的 last 提交,我们只需将其哈希 ID 放入分支名称即可。此名称现在指向提交 H
,就像 H
指向 G
:
...--F--G--H <-- branch1
我们现在可以创建另一个分支名称,例如 base
,并使其指向现有提交 F
(使用 git branch base <hash-of-F>
):
...--F <-- base
\
G--H <-- branch1
我们不得不绘制提交图 - F-G-H
行 - 有点不同以挤入名称,但 commits 通过此操作完全没有改变。我们所做的只是创建一个指向 F
的新名称,以便 F
是分支 base
上的最后一次提交。名称 branch1
仍然标识 H
,因此 H
是分支 branch1
上的最后一次提交。
让我们删除 base
(git branch -d base
),并创建一个也指向 H
的新名称 feature
。我们也会确保 git checkout feature
,以便 HEAD
附加到名称 feature
:
...--F--G--H <-- branch1, feature (HEAD)
(例如,我们可以用 git checkout -b feature branch1
来做到这一点。)现在我们将以通常的方式进行新的提交。这个新的提交获得了一个新的、唯一的哈希 ID,但我们只称它为 I
。 Git 现在所做的是 移动 名称 feature
以便它指向新提交 I
。新提交 I
的 parent 是 H
,因此 I
指向 H
:
...--F--G--H <-- branch1
\
I <-- feature (HEAD)
这就是分支:它们只是名称或标签,指向特定的提交,具有特殊技巧,当您 git checkout
其中一个时,您不仅可以让该提交准备好工作同时,您还安排 next git commit
操作来 update 名称。
您在 Git 中所做的几乎所有事情都是关于创建或获取提交,然后使各种名称指向这些新创建或获取的提交中的特定提交。分支名称只是让您 找到 一些特定的提交。根据定义,名称是该分支上的 last 提交。如何移动分支名称并不重要。只要该名称存在,它就指向某个提交。该提交是分支上的最后一次提交。移动名称,您就更改了哪个提交是分支上的最后一次提交。您没有更改任何 提交 — 它们都还在那里 — 您只是更改了 哪个 提交那个分支的最后一个。
这给我们带来了变基
git rebase
所做的是复制一些提交集,然后移动分支名称。例如,考虑这张图:
...--F--G--H <-- master
\
I--J <-- feature
你首先做一个 git checkout
你想要的名字 move-after-copying:
$ git checkout feature
然后你 运行 一个 git rebase
命令。它需要一些参数,文档称之为 --onto
和 upstream
参数。这些指定了一个 target 提交,这是副本应该去的地方,也应该复制哪些提交:
$ git rebase master
你可以只给出一个参数,就像这里一样——git rebase master
——在这种情况下,目标提交和提交集都是使用同一个名称找到的。在这里,目标提交是提交 H
,要复制的提交集是提交 I
和 J
.
rebase 命令现在复制每个提交,就像使用 git cherry-pick
一样。副本获得新的哈希 ID。这里有很多繁琐的极端情况,您可以使用 git rebase
的选项,但在这种情况下很简单,我们最终会得到一个 I
的副本,它有一个新的和不同的哈希 ID,我们称之为 I'
,以及 J
的副本,我们称之为 J'
。 I
和I'
有两个很大的区别,这里图中我们可以看到的一个是I'
的parent不是G
而是H
。 commit-copy J'
:
I'-J' <-- HEAD (detached)
/
...--F--G--H <-- master
\
I--J <-- feature
(你无法在这张图中看到的区别是提交I'
保存的快照可能与I
保存的快照不同,因为 cherry-pick 有效地接受了从 G
到 I
的更改,并将该更改应用于 H
中的快照,而不是 G
。)
复制这两个提交后,通过移动分支名称完成变基:
I'-J' <-- feature (HEAD)
/
...--F--G--H <-- master
\
I--J [abandoned]
提交 I
和 J
会怎样?答案有点复杂,但我们现在只需要:还没有。 Git 将它们保留一段时间,以防您认为变基是个坏主意。但是它们变得很难找到。新提交 J'
很容易找到:名称 feature
找到它。提交 I'
很容易找到:我们只需转到 J'
,然后按照其 backwards-pointing 箭头到达 I'
。但是提交 J
的哈希 ID 是什么?它 在名字 feature
中是 ,但现在不是了。如果你能找到J
,你可以用它来找到I
,但是除非你在某处保存了J
的散列ID,否则这可能有点棘手。 1 最终——通常是从现在起 30 天后的某个时间——Git 将把它们完全回收为不需要的,如果你没有使用其他名称——例如其他分支或标签名称——来确保他们留下来。
1就目前而言,很容易找到:Git将其保存在名称ORIG_HEAD
下。但其他命令将替换 ORIG_HEAD
的哈希 ID。不过,还有第二种方法可以找到它,使用 git reflog
,这就是默认情况下至少保持提交至少一个月左右的原因。
为什么 rebase 复制太多
一个合并提交有两个(或更多)parent。当我们 Git 遵循 backwards-pointing link 从提交到他们的 parent 时,它通常遵循 all [=416] =]秒。因此,从合并提交中,Git 下降 both 路径。
您的真实提交图一点也不简单,但是您的 question-example 提交图可能还不错。它可能看起来像这样:
Y--Z <-- upstream/master
/
...--o--o--o--W--o--o--o--X <-- upstream/dev
\ \ \
A--B-----M--C--D--N <-- yourbranch (HEAD)
这里,"on"(可从)yourbranch
无法从 upstream/dev
访问的六个提交,即提交 X
,是 A-B-M-C-D-N
.让我们更仔细地看一下:
如果我们从提交
X
开始并向后工作,我们将找到的提交都是W
和X
之间的所有未字母提交,加上W
,加上W
. 前所有不带字母的
如果我们从
yourbranch
开始——提交N
——并向后工作,我们访问提交X
(通过 link 来自N
) 和 提交D
(通过N
中的 link)。从D
我们到达C
,然后到M
,然后到 bothB
和一些未命名的提交。我们也从X
得到那个未命名的提交。如果我们从
origin/master
开始,或者提交Z
,我们将访问Z
,然后是Y
,然后是W
,然后是W
. 之前的所有未命名提交
所以如果我们 运行 git rebase
像这样:
git checkout yourbranch
git rebase upstream/master
您的 Git 将列出所有可从 N
访问的提交,这些提交 not 可从 Z
访问。您使用 upstream/master
作为目标 (--onto
) 和上游 ("don't copy commits reachable from Z
")。这意味着 Git 不会 复制提交 W
和更早的提交——它们可以从 Z
访问——但是 会 也复制 o-o-o-X
提交 A-B-C-D
。 Rebase 通常会丢弃所有合并提交所以它会抛出 M
和 N
,但是你只剩下八个提交被复制而不是四个。
您可以使 rebase 复制更少的提交
你可以做的一件事是 运行:
git rebase --onto upstream/master upstream/dev
这将不复制 参数与放置副本 参数的位置分开。我们仍然告诉 rebase:将副本放在提交 Z
之后,但是这次,我们告诉 rebase:*不要复制可从 X
访问的提交。所以 Git 列出提交 A-B-M-C-D-N
作为要复制的提交,然后扔掉 M
和 N
因为它们是合并的,剩下的就是复制 [=148] =].
如果这个 rebase 一切顺利,你将得到这个:
A'-B'-C'-D' <-- yourbranch (HEAD)
/
Y--Z <-- upstream/master
/
...--o--o--o--W--o--o--o--X <-- upstream/dev
\ \ \
A--B-----M--C--D--N [abandoned]
您现在可以从中创建拉取请求。
您想要一次提交
I want to [end up with] a single commit.
也就是说,如果我们绘制想要的结果,它可能看起来像这样:
ABCD <-- yourbranch (HEAD)
/
Y--Z <-- upstream/master
/
...--o--o--o--W--o--o--o--X <-- upstream/dev
\ \ \
A--B-----M--C--D--N [abandoned]
其中 ABCD
是单个提交,如果您第一次变基,然后进行第二次变基以将它们全部压缩为一个提交,就会产生这样的效果。
要到达那里,您可以使用以下命令序列:
$ git checkout upstream/master
$ git merge --squash yourbranch
$ git checkout -B yourbranch # or git branch -f yourbranch HEAD
第一个 git checkout
为您提供了一个 分离的 HEAD 指向由 upstream/master
标识的提交,即提交 Z
。如果愿意,可以使用临时分支名称:
$ git checkout -b temp upstream/master
这给你:
Y--Z <-- upstream/master, HEAD
/
...--o--o--o--W--o--o--o--X <-- upstream/dev
\ \ \
A--B-----M--C--D--N <-- yourbranch
或:
Y--Z <-- upstream/master, temp (HEAD)
/
...--o--o--o--W--o--o--o--X <-- upstream/dev
\ \ \
A--B-----M--C--D--N <-- yourbranch
git merge --squash
使用您想要的内容构建一个新的 non-merge 提交:
ABCD <-- HEAD
/
Y--Z <-- upstream/master
/
...--o--o--o--W--o--o--o--X <-- upstream/dev
\ \ \
A--B-----M--C--D--N <-- yourbranch
(或名称 temp
附有 HEAD
的同一绘图,现在已移至指向 ABCD
)。
最后一步是从提交 N
中提取(yoink?)名称 yourbranch
并使其指向新提交 ABCD
,这是 git branch -f
的位置或git checkout -B
进来。这两者之间的主要区别在于HEAD
之后是否附加到yourbranch
:
ABCD <-- HEAD, yourbranch
/
Y--Z <-- upstream/master
/
...--o--o--o--W--o--o--o--X <-- upstream/dev
\ \ \
A--B-----M--C--D--N [abandoned]
或:
ABCD <-- yourbranch (HEAD)
/
Y--Z <-- upstream/master
/
...--o--o--o--W--o--o--o--X <-- upstream/dev
\ \ \
A--B-----M--C--D--N [abandoned]
(在 HEAD
和 yourbranch
的 reflog 中最终出现的内容存在一些其他细微差别,但我们并未真正涵盖此处的 reflogs)。
(我不会讨论 git merge --squash
是如何工作的,因为这已经很长了。)