Git: 为什么之前的fetch+merge commit在fetch+rebase之后就消失了
Git: Why does previous fetch+merge commit disappear after fetch+rebase
TL;DR: 如果我将远程更改获取到本地 git 存储库,然后进行合并,一段时间后我获取一些新更改,但是这次我做的是变基而不是合并,然后之前创建的合并提交就消失了。为什么?
例子
考虑以下由命令 git log --all --graph --decorate --oneline
创建的起点:
* 28992d3 (repo1/master) hello4
* 3610bdf hello3
| * f113d63 (HEAD -> master) bye-bye
| * cabc896 bye
|/
* 75f7ca9 hello2
* 525cb4a hello1
即,有一个 git 回购,有一个 master
分支,其中有一些本地的、未推送的更改。刚刚从远程获取了一些其他更改(在本例中 repo1
)。
下一个命令:git merge repo1/master
。结果:
* b94aa29 (HEAD -> master) Merge remote-tracking branch 'repo1/master'
|\
| * 28992d3 (repo1/master) hello4
| * 3610bdf hello3
* | f113d63 bye-bye
* | cabc896 bye
|/
* 75f7ca9 hello2
* 525cb4a hello1
现在假设在本地和远程 repo1
中都有一些新提交,然后再次通过 git fetch repo1 master
从 repo1
获取远程内容.结果如下所示:
* 2e3d749 (repo1/master) hello6
* b17983d hello5
| * 2e49819 (HEAD -> master) see ya
| * c2f2d5a good-bye
| * b94aa29 Merge remote-tracking branch 'repo1/master'
| |\
| |/
|/|
* | 28992d3 hello4
* | 3610bdf hello3
| * f113d63 bye-bye
| * cabc896 bye
|/
* 75f7ca9 hello2
* 525cb4a hello1
到目前为止一切顺利。
现在让我们做 git rebase repo1/master
,结果是一个很好的线性提交日志:
* 101e524 (HEAD -> master) see ya
* 3ce7543 good-bye
* 849cbd4 bye-bye
* 483bab8 bye
* 2e3d749 (repo1/master) hello6
* b17983d hello5
* 28992d3 hello4
* 3610bdf hello3
* 75f7ca9 hello2
* 525cb4a hello1
问题:提交 b94aa29 Merge remote-tracking branch 'repo1/master'
去了哪里?(据我所知,即使是 "dead" 提交也没有保留,例如在独立的头脑中进行提交。)
备注:
- 我想答案一定与 "git notices we don't need
b94aa29
anymore, because we will have all its contents anyway" 类似,但您能否更详细地解释一下这是怎么回事?而且,这是否总是正确的,基于先前合并的分支将丢弃所有合并提交?
- 很高兴知道,如果您能以某种方式强制保留合并提交。
- 如果可以简化示例,我愿意编辑问题。
TL;答案的 DR 版本
git rebase
在功能上意味着:
- 挑选出一组要复制的提交;
- 复制这些提交,一次一个,就像
git cherry-pick
;
- 完成后,更改当前分支名称(无论是什么)以指向最终复制的提交。
字面上的复制不能复制合并,所以通常不用费心去尝试。
更长(但更多信息请参阅我的其他答案)
这里的总体思路是进行一系列提交:
A--B--C--D <-- topic (HEAD)
/
...--o--o--*--o--o <-- mainline
并将它们移植到一系列 new-and-improved 提交中:
A--B--C--D [abandoned]
/
...--o--o--*--o--o <-- mainline
\
A'-B'-C'-D' <-- topic (HEAD)
"improvement" 是将新链建立在其他分支的顶端,例如 mainline
。为了实现这一点,Git 字面上 必须 复制原始提交 - A-B-C-D
,此处 - 到具有不同哈希 ID 的不同提交,因为每个提交一旦完成, 是永久的1 并且一成不变;即使只有一点不同的提交也会为您提供一个新的 不同 提交哈希 ID,即使唯一的区别是存储在新提交中的 parent ID。因此,即使快照 A'
中的源树与快照 A
中的源树相匹配——而且可能不匹配——A'
的提交 ID 与 [= 的提交 ID 不同20=].
(当然,这也会通过其余的提交进行。)
你给git rebase
的参数select:
- 承诺复制,
- 从哪里开始复制(first-copied 提交的地方)。
通常情况下,您可以为这两个名称取一个名字。例如,git rebase mainline
表示将副本放在mainline
指向的提交之后,并复制从topic
(当前分支名称)指向的提交可以到达的那些提交——即,D
——不包括从 mainline
的顶端可以到达的任何提交。 未复制的第一个提交是提交*
,两个分支重新加入(在这种情况下永远)。
在某些情况下,您可能需要使用git rebase --onto
来区分这两个概念。使用 --onto
,您告诉 rebase 将副本 放在哪里,释放剩余的参数来表示 不复制的内容 。这里不需要。
有一堆 kinds/flavors 的变基:git rebase
没有参数使用 git format-patch | git am
来复制提交,而不是实际 运行 git cherry-pick
,而 git rebase -i
实际上使用 git cherry-pick
。 (在 Git 的旧版本中,git rebase -i
是一个 shell 脚本,它实际上运行 git cherry-pick
。为了使 Windows 更快,git rebase
是修改后 -i
内置于 Git 的 sequencer,这是实现 cherry-pick 和恢复的代码。)
请注意,所有这些一次复制一个,最终会构建一个线性提交链。 即使输入可能包含合并也会发生这种情况,如:
A--B--M--C--D <-- master
/ /
...--o--*--o--S------o--T <-- repo1/master
你现在要求 Git 变基(即复制)一些提交——在这种情况下,一些提交在 master
——--onto
目标是 T
,并且限制是*从 T
/ origin/master
到达的第一个提交,它也在 master
上,即提交 *
.
此类提交的完整列表是 A
然后 B
然后 M
然后 C
然后 D
。但是Git应该怎么复制M
呢?如果尝试,结果可能看起来很像:
A--B--M--C--D [abandoned]
/ /
...--o--*--o--S------o--T <-- repo1/master
\
A'-B'-M'-C'-D <-- master (HEAD)
/
???----------
除了 M'
,要合并,需要有 两个 parent。 parent 还应该有什么?如果它的另一个parent是S
,嗯,那是可能,但是它带来什么价值呢?
(合并的要点是结合两条不同开发线的变化。由于A'
基于T
,T
基于S
,A'
已经包含了 S
中的所有内容,因此无需合并它。)
一般来说,Git 只是 忽略了 合并完全在这里提交,所以它最终只复制 A-B-C-D
。请注意,如果您对包含 internal 合并的内容进行变基,则会发生同样的事情:Git 只是复制合并的两个 "sides",线性化结果:
C--D
/ \
A--B M--G <-- topic (HEAD)
/ \ /
/ E--F
/
...--o--*--o--o <-- mainline
此处 git rebase
将复制 A-B-C-D-E-F-G
或 A-B-E-F-C-D-G
,删除 M
并展平拓扑。
git rebase -i
有一个 -p
标志,它的拼写较长 --preserve-merges
,但它实际上 保留 合并(也不 cherry-pick 它们,这是不可能的)。相反,它 进行新的合并 (通过 运行 git merge
)。这非常棘手,但可用于变基上述 A-B-(C-D, E-F)-M-G
拓扑。请注意,如果您在 M
中解决了合并冲突,则当 Git 进行合并 D'
和 F'
的新合并 M'
时,您将不得不再次解决它们( git rerere
在这里可能会有用)。
1永久,也就是说,直到整个提交被放弃足够长的时间 Git 以确保没有人愿意是它;然后它被 git gc
.
清除
TL;DR: 如果我将远程更改获取到本地 git 存储库,然后进行合并,一段时间后我获取一些新更改,但是这次我做的是变基而不是合并,然后之前创建的合并提交就消失了。为什么?
例子
考虑以下由命令 git log --all --graph --decorate --oneline
创建的起点:
* 28992d3 (repo1/master) hello4
* 3610bdf hello3
| * f113d63 (HEAD -> master) bye-bye
| * cabc896 bye
|/
* 75f7ca9 hello2
* 525cb4a hello1
即,有一个 git 回购,有一个 master
分支,其中有一些本地的、未推送的更改。刚刚从远程获取了一些其他更改(在本例中 repo1
)。
下一个命令:git merge repo1/master
。结果:
* b94aa29 (HEAD -> master) Merge remote-tracking branch 'repo1/master'
|\
| * 28992d3 (repo1/master) hello4
| * 3610bdf hello3
* | f113d63 bye-bye
* | cabc896 bye
|/
* 75f7ca9 hello2
* 525cb4a hello1
现在假设在本地和远程 repo1
中都有一些新提交,然后再次通过 git fetch repo1 master
从 repo1
获取远程内容.结果如下所示:
* 2e3d749 (repo1/master) hello6
* b17983d hello5
| * 2e49819 (HEAD -> master) see ya
| * c2f2d5a good-bye
| * b94aa29 Merge remote-tracking branch 'repo1/master'
| |\
| |/
|/|
* | 28992d3 hello4
* | 3610bdf hello3
| * f113d63 bye-bye
| * cabc896 bye
|/
* 75f7ca9 hello2
* 525cb4a hello1
到目前为止一切顺利。
现在让我们做 git rebase repo1/master
,结果是一个很好的线性提交日志:
* 101e524 (HEAD -> master) see ya
* 3ce7543 good-bye
* 849cbd4 bye-bye
* 483bab8 bye
* 2e3d749 (repo1/master) hello6
* b17983d hello5
* 28992d3 hello4
* 3610bdf hello3
* 75f7ca9 hello2
* 525cb4a hello1
问题:提交 b94aa29 Merge remote-tracking branch 'repo1/master'
去了哪里?(据我所知,即使是 "dead" 提交也没有保留,例如在独立的头脑中进行提交。)
备注:
- 我想答案一定与 "git notices we don't need
b94aa29
anymore, because we will have all its contents anyway" 类似,但您能否更详细地解释一下这是怎么回事?而且,这是否总是正确的,基于先前合并的分支将丢弃所有合并提交? - 很高兴知道,如果您能以某种方式强制保留合并提交。
- 如果可以简化示例,我愿意编辑问题。
TL;答案的 DR 版本
git rebase
在功能上意味着:
- 挑选出一组要复制的提交;
- 复制这些提交,一次一个,就像
git cherry-pick
; - 完成后,更改当前分支名称(无论是什么)以指向最终复制的提交。
字面上的复制不能复制合并,所以通常不用费心去尝试。
更长(但更多信息请参阅我的其他答案)
这里的总体思路是进行一系列提交:
A--B--C--D <-- topic (HEAD)
/
...--o--o--*--o--o <-- mainline
并将它们移植到一系列 new-and-improved 提交中:
A--B--C--D [abandoned]
/
...--o--o--*--o--o <-- mainline
\
A'-B'-C'-D' <-- topic (HEAD)
"improvement" 是将新链建立在其他分支的顶端,例如 mainline
。为了实现这一点,Git 字面上 必须 复制原始提交 - A-B-C-D
,此处 - 到具有不同哈希 ID 的不同提交,因为每个提交一旦完成, 是永久的1 并且一成不变;即使只有一点不同的提交也会为您提供一个新的 不同 提交哈希 ID,即使唯一的区别是存储在新提交中的 parent ID。因此,即使快照 A'
中的源树与快照 A
中的源树相匹配——而且可能不匹配——A'
的提交 ID 与 [= 的提交 ID 不同20=].
(当然,这也会通过其余的提交进行。)
你给git rebase
的参数select:
- 承诺复制,
- 从哪里开始复制(first-copied 提交的地方)。
通常情况下,您可以为这两个名称取一个名字。例如,git rebase mainline
表示将副本放在mainline
指向的提交之后,并复制从topic
(当前分支名称)指向的提交可以到达的那些提交——即,D
——不包括从 mainline
的顶端可以到达的任何提交。 未复制的第一个提交是提交*
,两个分支重新加入(在这种情况下永远)。
在某些情况下,您可能需要使用git rebase --onto
来区分这两个概念。使用 --onto
,您告诉 rebase 将副本 放在哪里,释放剩余的参数来表示 不复制的内容 。这里不需要。
有一堆 kinds/flavors 的变基:git rebase
没有参数使用 git format-patch | git am
来复制提交,而不是实际 运行 git cherry-pick
,而 git rebase -i
实际上使用 git cherry-pick
。 (在 Git 的旧版本中,git rebase -i
是一个 shell 脚本,它实际上运行 git cherry-pick
。为了使 Windows 更快,git rebase
是修改后 -i
内置于 Git 的 sequencer,这是实现 cherry-pick 和恢复的代码。)
请注意,所有这些一次复制一个,最终会构建一个线性提交链。 即使输入可能包含合并也会发生这种情况,如:
A--B--M--C--D <-- master
/ /
...--o--*--o--S------o--T <-- repo1/master
你现在要求 Git 变基(即复制)一些提交——在这种情况下,一些提交在 master
——--onto
目标是 T
,并且限制是*从 T
/ origin/master
到达的第一个提交,它也在 master
上,即提交 *
.
此类提交的完整列表是 A
然后 B
然后 M
然后 C
然后 D
。但是Git应该怎么复制M
呢?如果尝试,结果可能看起来很像:
A--B--M--C--D [abandoned]
/ /
...--o--*--o--S------o--T <-- repo1/master
\
A'-B'-M'-C'-D <-- master (HEAD)
/
???----------
除了 M'
,要合并,需要有 两个 parent。 parent 还应该有什么?如果它的另一个parent是S
,嗯,那是可能,但是它带来什么价值呢?
(合并的要点是结合两条不同开发线的变化。由于A'
基于T
,T
基于S
,A'
已经包含了 S
中的所有内容,因此无需合并它。)
一般来说,Git 只是 忽略了 合并完全在这里提交,所以它最终只复制 A-B-C-D
。请注意,如果您对包含 internal 合并的内容进行变基,则会发生同样的事情:Git 只是复制合并的两个 "sides",线性化结果:
C--D
/ \
A--B M--G <-- topic (HEAD)
/ \ /
/ E--F
/
...--o--*--o--o <-- mainline
此处 git rebase
将复制 A-B-C-D-E-F-G
或 A-B-E-F-C-D-G
,删除 M
并展平拓扑。
git rebase -i
有一个 -p
标志,它的拼写较长 --preserve-merges
,但它实际上 保留 合并(也不 cherry-pick 它们,这是不可能的)。相反,它 进行新的合并 (通过 运行 git merge
)。这非常棘手,但可用于变基上述 A-B-(C-D, E-F)-M-G
拓扑。请注意,如果您在 M
中解决了合并冲突,则当 Git 进行合并 D'
和 F'
的新合并 M'
时,您将不得不再次解决它们( git rerere
在这里可能会有用)。
1永久,也就是说,直到整个提交被放弃足够长的时间 Git 以确保没有人愿意是它;然后它被 git gc
.