为什么 git 对合并分支的提交显示在我的主分支上?
Why are git commits on a merged branch showing on my main branch?
我目前有一个 git 提交树,看起来像这样,括号中有指针 (?):
* 305f Merge branch 'develop' (HEAD->master, origin/master, origin/HEAD)
|\
| * d97b Some other commit on dev branch (develop)
| * df14 Some commit on dev branch
|/
* 7a761b6 Initial commit
我已经将 master 分支推送到远程(Gitlab,如果重要的话),当我在 Gitlab UI 中查看 master 分支上的提交时,所有 4 个提交都存在,我会在哪里预计只有 "Merge branch 'develop'" 和 "Initial commit" 提交会出现在主分支上。
我的理解是 master
指的是我刚刚列出的两个提交,而 develop
指的是 "Some other..." , "Some commit...",可能还有 "Initial commit"还有,因为它是一个祖先。
我哪里错了?
在某些版本控制系统中,当您在分支 B 上提交 C 时,提交 C 永远在分支 B 上。任何获得提交 C 的人都会获得分支 B。如果他们以前有他们自己的分支B,那么现在他们的分支B 中有一个新提交 C。
Git 不会这样做。提交不会永久固定在分支上。但是,提交 大部分是永久性的,1 并永久固定在它们出现在 提交图 中的位置。为了使这项工作正常进行,提交并不是真正在 any 分支上进行的。相反,分支名称只是一个标签。多个标签都可以指向同一个提交,如:
* 305f Merge branch 'develop' (HEAD->master, origin/master, origin/HEAD)
此处master
和origin/master
2都标识提交305f
。如果您现在创建一个新分支 br2
,该名称将 也 指向提交 305f
.
提交 305f
有 两个 parent:7a761b6
(第一个 parent)和 d97b
(它的第二个 parent)。 305f
的全名就是它的完整哈希 ID。这永远不会改变;并且该哈希 ID 永远保留给 that 提交,它将始终具有相同的两个 parent。该提交将永远冻结并且永远不会移动。
做移动的Git中的东西是分支名称。目前,master
表示 305f
。然而,刚才 master
意味着 7a761b6
。提交将永远保留在原位,如其原始哈希 ID 所发现的那样。分支名称移动。
所有这一切的结果是,当我们创建和销毁分支名称时,包含一些提交的分支集会动态变化。此时,名称 master
可以让您找到所有四个提交。如果您允许 Git 以 Git 喜欢的方式移动名称 master
,则通过从 305f
开始并查看其两个提交,这四个提交将继续可访问parents,然后查看 d97b
的 parent(然后从 d97b
返回到您已经看到的 7a761b6
)。请注意,new 只会提交添加到图表。通常,每个新提交都会有一些现有的提交作为其 parent——如果它是典型提交,则只有 parent,如果是合并提交,则只有两个 parent 之一。3
如果我们把这些东西画在侧面,会容易一些。为了让它更简单,我们可以使用单个大写字母来代表 Git 使用的难以理解的哈希 ID:
D--E <-- br1
/
A--B--C
\
F--G <-- br2
在这里,名称br1
允许我们找到提交E
,它找到D
,然后是C
,然后是B
,然后是A
(并停止,因为 A
是第一个提交)。名称 br2
允许我们找到 G
,然后是 F
,然后是 C
,然后是 B
,然后是 A
(然后停止)。所以前三个提交在两个分支上,而两个提交每个只有一个分支。
擦除 name br1
导致提交 D
和 E
变得无法找到。最终 Git 会真的把它们扔出去。如果我们改为通过执行 git checkout br1
添加新提交,进行我们想要的任何更改,git add
和 git commit
,我们将获得一个新的哈希 ID H
并推进名称 br1
包括它:
D--E--H <-- br1
/
A--B--C
\
F--G <-- br2
现在有六个提交可以从 br1
开始,从 H
开始并向后工作。如果我们创建一个新名称 br3
以记住 E
的位置,我们将需要重新绘制图形:
H <-- br2
/
D--E <-- br3
/
A--B--C
\
F--G <-- br2
请注意 提交 的 none 实际上已经更改:我们只是将 H
向上推以为标签 br3
腾出空间。
如果我们稍后删除 name br3
,那没关系:没有通过 exlusively 找到的提交 br3
。提交 E
不会消失,因为 br1
找到 H
找到 E
.
1大部分是通过可达性实现的。您 find 从某个分支或标记名称开始提交,它提供原始哈希 ID。然后,找到一个提交后,您可以使用它的 parent 哈希 ID 来查找它的前一个提交。然后你使用 those commits 来找到 their parents,等等。
通过从 每个 引用执行此过程——参见脚注 2——Git 找到所有 可达 提交。存储库中存在的任何提交,如果此过程无法访问,最终将 garbage-collected 并删除。
2名字master
是分支名。它的全名真的是refs/heads/master
,全名以refs/heads/
开头的都是分支名。相比之下,origin/master
实际上是一个 remote-tracking 名称:它的全名以 refs/remotes/
开头,然后继续说 origin/master
。 Git 有时只从该名称中删除 refs/
部分,以便您看到remotes/origin/master
.
标签(如果有)位于 refs/tags/
。这些东西统称为refs或references。 reflogs 中还保存了额外的隐藏引用。 reflog 只是一个日志文件,用于保存在您或 Git 更新之前存储在 ref 中的值(以前的哈希 ID)。这些 reflog 条目最终会过期,这就是 deliberately-abandoned 提交的方式——例如,那些你用具有新哈希 ID 的 new-and-improved 版本替换的条目——最终会被清除。
3合并提交的技术定义是它至少有两个parent。因此,您也可以创建包含 3 个或更多 parent 的合并提交——但很少有理由这样做。我还应该提到,可以使用 no parent 创建一个新的提交。除了第一次提交——我在横向图表中标记为 A
的那个——你在正常实践中也不会这样做。
我目前有一个 git 提交树,看起来像这样,括号中有指针 (?):
* 305f Merge branch 'develop' (HEAD->master, origin/master, origin/HEAD)
|\
| * d97b Some other commit on dev branch (develop)
| * df14 Some commit on dev branch
|/
* 7a761b6 Initial commit
我已经将 master 分支推送到远程(Gitlab,如果重要的话),当我在 Gitlab UI 中查看 master 分支上的提交时,所有 4 个提交都存在,我会在哪里预计只有 "Merge branch 'develop'" 和 "Initial commit" 提交会出现在主分支上。
我的理解是 master
指的是我刚刚列出的两个提交,而 develop
指的是 "Some other..." , "Some commit...",可能还有 "Initial commit"还有,因为它是一个祖先。
我哪里错了?
在某些版本控制系统中,当您在分支 B 上提交 C 时,提交 C 永远在分支 B 上。任何获得提交 C 的人都会获得分支 B。如果他们以前有他们自己的分支B,那么现在他们的分支B 中有一个新提交 C。
Git 不会这样做。提交不会永久固定在分支上。但是,提交 大部分是永久性的,1 并永久固定在它们出现在 提交图 中的位置。为了使这项工作正常进行,提交并不是真正在 any 分支上进行的。相反,分支名称只是一个标签。多个标签都可以指向同一个提交,如:
* 305f Merge branch 'develop' (HEAD->master, origin/master, origin/HEAD)
此处master
和origin/master
2都标识提交305f
。如果您现在创建一个新分支 br2
,该名称将 也 指向提交 305f
.
提交 305f
有 两个 parent:7a761b6
(第一个 parent)和 d97b
(它的第二个 parent)。 305f
的全名就是它的完整哈希 ID。这永远不会改变;并且该哈希 ID 永远保留给 that 提交,它将始终具有相同的两个 parent。该提交将永远冻结并且永远不会移动。
做移动的Git中的东西是分支名称。目前,master
表示 305f
。然而,刚才 master
意味着 7a761b6
。提交将永远保留在原位,如其原始哈希 ID 所发现的那样。分支名称移动。
所有这一切的结果是,当我们创建和销毁分支名称时,包含一些提交的分支集会动态变化。此时,名称 master
可以让您找到所有四个提交。如果您允许 Git 以 Git 喜欢的方式移动名称 master
,则通过从 305f
开始并查看其两个提交,这四个提交将继续可访问parents,然后查看 d97b
的 parent(然后从 d97b
返回到您已经看到的 7a761b6
)。请注意,new 只会提交添加到图表。通常,每个新提交都会有一些现有的提交作为其 parent——如果它是典型提交,则只有 parent,如果是合并提交,则只有两个 parent 之一。3
如果我们把这些东西画在侧面,会容易一些。为了让它更简单,我们可以使用单个大写字母来代表 Git 使用的难以理解的哈希 ID:
D--E <-- br1
/
A--B--C
\
F--G <-- br2
在这里,名称br1
允许我们找到提交E
,它找到D
,然后是C
,然后是B
,然后是A
(并停止,因为 A
是第一个提交)。名称 br2
允许我们找到 G
,然后是 F
,然后是 C
,然后是 B
,然后是 A
(然后停止)。所以前三个提交在两个分支上,而两个提交每个只有一个分支。
擦除 name br1
导致提交 D
和 E
变得无法找到。最终 Git 会真的把它们扔出去。如果我们改为通过执行 git checkout br1
添加新提交,进行我们想要的任何更改,git add
和 git commit
,我们将获得一个新的哈希 ID H
并推进名称 br1
包括它:
D--E--H <-- br1
/
A--B--C
\
F--G <-- br2
现在有六个提交可以从 br1
开始,从 H
开始并向后工作。如果我们创建一个新名称 br3
以记住 E
的位置,我们将需要重新绘制图形:
H <-- br2
/
D--E <-- br3
/
A--B--C
\
F--G <-- br2
请注意 提交 的 none 实际上已经更改:我们只是将 H
向上推以为标签 br3
腾出空间。
如果我们稍后删除 name br3
,那没关系:没有通过 exlusively 找到的提交 br3
。提交 E
不会消失,因为 br1
找到 H
找到 E
.
1大部分是通过可达性实现的。您 find 从某个分支或标记名称开始提交,它提供原始哈希 ID。然后,找到一个提交后,您可以使用它的 parent 哈希 ID 来查找它的前一个提交。然后你使用 those commits 来找到 their parents,等等。
通过从 每个 引用执行此过程——参见脚注 2——Git 找到所有 可达 提交。存储库中存在的任何提交,如果此过程无法访问,最终将 garbage-collected 并删除。
2名字master
是分支名。它的全名真的是refs/heads/master
,全名以refs/heads/
开头的都是分支名。相比之下,origin/master
实际上是一个 remote-tracking 名称:它的全名以 refs/remotes/
开头,然后继续说 origin/master
。 Git 有时只从该名称中删除 refs/
部分,以便您看到remotes/origin/master
.
标签(如果有)位于 refs/tags/
。这些东西统称为refs或references。 reflogs 中还保存了额外的隐藏引用。 reflog 只是一个日志文件,用于保存在您或 Git 更新之前存储在 ref 中的值(以前的哈希 ID)。这些 reflog 条目最终会过期,这就是 deliberately-abandoned 提交的方式——例如,那些你用具有新哈希 ID 的 new-and-improved 版本替换的条目——最终会被清除。
3合并提交的技术定义是它至少有两个parent。因此,您也可以创建包含 3 个或更多 parent 的合并提交——但很少有理由这样做。我还应该提到,可以使用 no parent 创建一个新的提交。除了第一次提交——我在横向图表中标记为 A
的那个——你在正常实践中也不会这样做。