gpt4 book ai didi

git - 来自master分支的分支的 'rebase master'和 'rebase --onto master'之间的区别

转载 作者:行者123 更新时间:2023-12-02 04:49:40 33 4
gpt4 key购买 nike

给定以下分支结构:

  *------*---*
Master \
*---*--*------*
A \
*-----*-----*
B (HEAD)

如果我想将我的B更改(并且我的B更改 ,没有A更改)合并到master中,这两组命令之间有什么区别?
>(B)      git rebase master
>(B) git checkout master
>(master) git merge B
>(B)      git rebase --onto master A B
>(B) git checkout master
>(master) git merge B

我主要感兴趣的是学习使用第一种方法是否可以将来自Branch A的代码转换为Master。

最佳答案

在我按要求回答问题之前,请耐心等待一段时间。较早的答案之一是正确的,但存在标签和其他相对较小(但可能造成混淆)的问题,因此我想从分支图和分支标签开始。另外,来自其他系统的人,甚至是刚接触修订控制和git的人,经常将分支视为“发展路线”,而不是“历史痕迹”(git将它们实现为后者,而不是前者,因此提交不一定一定要在任何特定的“开发线”上进行)。

首先,绘制图形的方式存在一个小问题:

  *------*---*
Master \
*---*--*------*
A \
*-----*-----*
B (HEAD)

这是完全相同的图,但是标签的绘制方式不同,并且添加了更多的箭头(并且我在下面编号了要使用的提交节点):
0 <- 1 <- 2         <-------------------- master
\
3 <- 4 <- 5 <- 6 <------ A
\
7 <- 8 <- 9 <-- HEAD=B

之所以如此重要,是因为git对于将某个提交“放在”某个分支上的含义非常不了解–也许更好的说法是说某个提交“包含在”某些分支集中。提交不能移动或更改,但是分支标签可以并且确实可以移动。

更具体地说,像 masterAB这样的分支名称指向一个特定的提交。在这种情况下, master指向提交2, A指向提交6, B指向提交9。前三个提交0到2包含在所有三个分支中。提交3、4和5都包含在 AB中;提交6仅包含在 A中;提交7至9仅包含在 B中。 (顺便说一句,多个名称可以指向同一提交,这在创建新分支时是正常的。)

在继续之前,让我再画一幅图:
0
\
1
\
2 <-- master
\
3 - 4 - 5
|\
| 6 <-- A
\
7
\
8
\
9 <-- HEAD=B

这只是要强调,重要的不是提交的水平线,而是父/子关系。分支标签指向一个开始的提交,然后(至少这些图的绘制方式)我们向左移动,也可以根据需要向上或向下移动,以找到父提交。

对提交进行重新设置基准时,实际上是在复制这些提交。

Git永远不会更改任何提交

任何提交(或git存储库中的任何对象)都有一个“真名”,即SHA-1:一个40十六进制数字的字符串,例如 9f317ce...,例如您在 git log中看到的字符串。 SHA-1是对象内容的cryptographic1校验和。内容包括作者和提交者(名称和电子邮件),时间戳,源树和父提交列表。提交#7的父级始终是提交#5。如果您制作了提交#7的大部分副本,但将其父级设置为提交#2而不是提交#5,则您将获得一个具有不同ID的提交。 (此时,我已经用完一个数字了-通常我使用单个大写字母来表示提交ID,但是我认为这会引起混淆,因为分支名为 AB。所以我将其称为#7,#下方的7a)。
git rebase的作用

当您要求git重新构建一连串的提交(例如上面的提交#7-8-9)时,它必须将它们复制,至少它们是否要移动到任何地方(如果它们不移动,都可以保留它们)。原件)。它默认是从当前已 checkout 的分支中复制提交,因此 git rebase仅需要两条额外的信息:
  • 应该复制哪些提交?
  • 副本应放在哪里?也就是说,第一次复制的提交的目标父ID是什么? (其他提交仅指向第一副本,第二副本,依此类推。)

  • 运行 git rebase <upstream>时,让git从一条信息中找出两个部分。当您使用 --onto时,您必须分别告诉git这两个部分:您仍然提供 upstream,但是它不根据 <upstream>计算目标,它仅计算要从 <upstream>复制的提交。 (顺便说一句,我认为 <upstream>并不是一个好名字,但这是rebase所使用的,我没有什么更好的方法,所以让我们坚持下去。Rebase调用target <newbase>,但是我认为target是一个更好的名字。)

    首先让我们看一下这两个选项。两者都假设您首先在 B分支上:
  • git rebase master
  • git rebase --onto master A

  • 使用第一个命令, <upstream>rebase参数是 master。第二个是 A

    这是git计算要提交的提交内容的方法:它将当前分支交给 git rev-list ,并且还将 <upstream>交给 git rev-list,但是要使用 --not,或者更精确地说,相当于两点式 exclude..include表示法。这意味着我们需要知道 git rev-list的工作方式。

    尽管 git rev-list非常复杂,但大多数git命令最终都使用了它。它是 git loggit bisectrebasefilter-branch等的引擎–这种特殊情况并不难:用双点符号表示, rev-list列出了从右侧可访问的每个提交(包括该提交本身),但不包括每个提交都可以从左侧到达。

    在这种情况下, git rev-list HEAD可以从 HEAD找到所有提交(即几乎所有提交:提交0-5和7-9),而 git rev-list master可以从 master找到所有可以提交的提交,即提交#0、1和2。从0-5,7-9的0至2离开3-5,7-9。这些是候选副本,如 git rev-list master..HEAD列出。

    对于第二个命令,我们使用 A..HEAD而不是 master..HEAD,因此要减去的提交为0-6。提交#6不会出现在 HEAD集中,但这没关系:减去不存在的内容,使其不存在。因此,最终的待复制副本为7-9。

    这仍然使我们不得不确定重新确定目标的目标,即应该在哪里复制提交土地?使用第二个命令,答案是“由 --onto参数标识的提交”。既然我们说了 --onto master,这意味着目标是提交#2。

    重新设定#1
    git rebase master
    但是,对于第一个命令,我们没有直接指定目标,因此git使用 <upstream>标识的提交。我们提供的 <upstream>master,它指向提交#2,因此目标是提交#2。

    因此,第一个命令将从复制提交#3开始,并进行任何最小的更改,以便其父级成为提交#2。其父级已经是提交#2。无需更改,因此无需更改,重新设置基准仅重用现有的提交3。然后必须复制#4,以使其父级为#3,但父级已为#3,因此它仅重用#4。同样,#5已经很好。它完全忽略了#6(不在复制的提交集中);它会检查#7-9,但它们也都很好,因此整个重设基础最终只是重新使用了所有原始提交。您仍然可以使用 -f强制复制,但是您没有这样做,因此整个重新配置最终无济于事。

    重新设定#2
    git rebase --onto master A
    第二个rebase命令使用 --onto选择#2作为其目标,但是告诉git复制仅提交7-9。提交#7的父级是提交#5,因此该副本确实必须做某事。2因此git进行了一个新的提交-称为#7a-以提交#2作为其父级。 rebase继续提交#8:副本现在需要#7a作为其父对象。最后,rebase继续提交#9,它需要#8a作为其父对象。复制所有提交后,rebase所做的最后一件事就是移动标签(请记住,标签会移动并更改!)。这给出了这样的图形:
              7a - 8a - 9a       <-- HEAD=B
    /
    0 - 1 - 2 <-- master
    \
    3 - 4 - 5 - 6 <-- A
    \
    7 - 8 - 9 [abandoned]

    好的,但是 git rebase --onto master A B呢?

    这几乎与 git rebase --onto master A相同。不同之处在于,最后还有多余的 B。幸运的是,这种区别非常简单:如果给 git rebase一个额外的参数,它将首先在该参数上运行 git checkout。3

    您的原始命令

    在第一组命令中,您在分支 git rebase master上运行了 B。如上所述,这是一个很大的禁忌:因为什么都不需要移动,所以git完全不会复制任何内容(除非您使用 -f / --force,否则就不会使用)。然后,您 checkout 了 master并使用了 git merge B,如果告诉它4,则会通过合并创建一个新的提交。因此,至少从我看到的时候, Dherik's answer在这里是正确的:合并提交有两个父级,其中一个是分支 B的尖端,并且该分支通过分支 A上的三个提交返回,因此有些 A上的内容最终被合并为 master

    在第二个命令序列中,您首先 checkout 了 B(您已经在 B上了,所以这是多余的,但它是 git rebase的一部分)。然后,您对三个提交进行了基准复制,生成了上面的最终图形,其中包含提交7a,8a和9a。然后,您 checkout 了 master并与 B进行了合并提交(再次参见脚注4)。再次,Dherik的回答是正确的:唯一缺少的是原始的,废弃的提交没有被引入,并且新的合并提交不是副本也不是很明显。

    1这仅是因为要针对特定​​的校验和非常困难。就是说,如果您信任的人告诉您“我信任ID为1234567 ...的提交”,那么其他人(您可能不太信任的人)几乎不可能提出具有相同ID的提交,但是有不同的内容。意外发生的可能性是2160分之一,这比您因心脏病发作而被闪电袭击,溺水于海啸,被外星人绑架时心脏病发作的可能性要小得多。 :-)

    2实际的副本是使用 git cherry-pick 等效的副本制作的:git将提交的树和其父级的树进行比较以获取差异,然后将差异应用于新的父级树。

    3这实际上在当时是正确的: git rebase是一个shell脚本,它解析您的选项,然后决定要运行哪种内部数据库:非交互式 git-rebase--am或交互式 git-rebase--interactive。找出所有参数后,如果有一个剩余的分支名称参数,脚本将在开始内部变基之前执行 git checkout <branch-name>

    4因为 master指向提交2,而提交2是提交9的祖先,所以通常毕竟不会进行合并提交,而是执行Git所谓的快速转发操作。您可以使用 git merge --no-ff指示Git不要快速执行这些操作。某些界面(例如GitHub的Web界面以及某些GUI)可能会分隔不同类型的操作,因此它们的“合并”会强制像这样进行真正的合并。

    通过快速合并,第一种情况的最终图形为:
    0 <- 1 <- 2         [master used to be here]
    \
    3 <- 4 <- 5 <- 6 <------ A
    \
    7 <- 8 <- 9 <-- master, HEAD=B

    无论哪种情况,提交1到9现在都在 masterB这两个分支上。与真正的合并相比,不同之处在于,从图中可以看到包含合并的历史记录。

    换句话说,快进合并的优点在于,它不会留下任何无关紧要的操作。快进合并的缺点是,它不会留下任何痕迹。因此,是否允许快进的问题实际上是您是否要在提交形成的历史记录中保留显式合并的问题。

    关于git - 来自master分支的分支的 'rebase master'和 'rebase --onto master'之间的区别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33942588/

    33 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com