您的部分问题是:
这些线代表分支吗?如果是这样,如何确定每条线代表哪个分支?
我在twocomments 中提到它们确实代表了......我们可能会调用分支的东西:
-
branch 这个词在 Git 中往往会失去所有意义,所以除了作为 name 的修饰符之外,何时使用这个词并不清楚。如果我们说master 是一个分支名称,develop 是一个分支名称,等等,这些都很清楚。从技术上讲,master 是 refs/heads/master 的简写,它是 ref 或 reference: 这是一种保存提交哈希 ID 的方式。1
-
但我们还需要一种方法来引用某些提交组。我们也倾向于称其为一个分支。
-
Git 提供远程跟踪名称,例如origin/master。我们经常将使用这样的名称找到的提交集合称为远程分支,Git 本身将这些东西称为远程跟踪分支名称,即使它们不是分支名称。2
我发现branch这个词被过度使用到了经常变得毫无意义的地步。但这些线表示从提交到其他更早提交的链接。
我们通过从提交到提交来找到这些链接。当我们找到 starting 提交时,为了遵循这些提交到提交的链接,使用 分支名称,我们称其为 分支。但这与分支名称不是一回事,当我们看看它们是如何随着时间的推移而演变时,这里的问题就变得很清楚了。
让我们从一个只有三个提交的存储库开始,全部在 master 或 main 上,并使用大写字母而不是 Git 真正用于它们的又大又丑的哈希 ID 来绘制这三个提交:
A <-B <-C <--main
namemain 保存了三个提交中最后的哈希 ID,即提交 C。提交C 本身持有早期提交B 的原始哈希ID,而提交B 持有仍然早期提交A 的原始哈希ID。我们说C指向B,B指向A——当然main本身也指向C。
提交A 是第一次提交。没有更早的提交!所以提交A 没有指向任何地方。这使它成为 root 提交,Git 可以在此处停止倒退。
我们现在向我们的提交集合添加一个新的分支名称。这个新名字也指向提交C,像这样:
A--B--C <-- main, develop
我们选择一个名称使用git checkout:
A--B--C <-- main, develop (HEAD)
我们现在通过名称develop 使用提交C。如果我们git checkout main,我们仍然使用C,只是通过不同的名称,但我们现在将坚持使用develop。
每个提交都包含每个文件的快照,并以特殊的、只读的、仅 Git 的、压缩的和 去重复 的形式永久冻结。所以C 中的大多数文件可能与B 中的文件相同,这意味着C 不会占用太多额外空间。如果 B 和 C 共享一个 100 MB 的文件,则该文件只有一个副本。
但是这些文件不能被其他任何东西使用。所以git checkout 必须将它们复制出来 到一个可用的表单中。这就是您的工作树中的内容: 可用形式的副本。我们不会进一步讨论这个想法,但值得牢记并稍后重新审视。
无论如何,现在让我们以通常的方式进行新的提交D。提交D 将像往常一样保存每个文件的(去重复的)快照,并将向后指向现有的提交C。当 Git 完成 D 的创建后,Git 将更新 当前分支名称,无论它是什么——基于 HEAD 的附件——指向 D,如下所示:
A--B--C <-- main
\
D <-- develop (HEAD)
提交A-B-C 现在在main 上,对吧?而D 仅在develop 上。嗯,last 部分是正确的——但提交 A-B-C 也 在 develop 上。
您的查看器绘制的线,将D 向后连接到C 等等,不告诉您在哪个分支上,部分原因是分支不没关系。只有提交很重要。提交C 很重要,并且可以从两个名称中找到,因此它在两个分支上。将C 向后链接到B 的那一行现在代表两个 分支。
我们可能会继续进行更多的提交:
A--B--C <-- main
\
D--E--F <-- develop (HEAD)
很容易将它们视为独立的,好像develop 有只有 D-E-F。在 Git 中情况并非如此:分支名称只对入门很重要。我们永远不知道什么时候该停止,除非我们遇到像A 这样的根提交。
为了让 Git 更早停止,我们使用 main..develop 之类的表达式。这是develop ^main 的简写。这使用集合论:通过develop,我们找到所有提交,然后通过main,我们找到A-B-C 集合,然后我们减去排除的集合——^main 部分——整体。这给我们留下了D-E-F,这是我们可能想认为是“在分支develop”上的提交。但要到达那里,我们不得不说:develop 上的提交,减去 main 上的提交,因为A-B-C 位于两个分支上。
现在我们可以继续合并新的提交到main。当我们这样做时,我们可以让 Git 进行 快进,这根本不是合并:
git checkout main && git merge --ff-only develop
结果:
A--B--C--D--E--F <-- develop, main (HEAD)
现在所有六个提交都在两个分支上。现在只有一行,而不是两行:我们不必再分解绘图以使 main 指向 C。
或者,我们可以使用显式合并,或者先在main 上提交:
git checkout main; (... make new commit ...); git merge develop
导致:
A--B--C------G--H <-- main (HEAD)
\ /
D--E--F <-- develop
commit H 是一个合并提交。从H 出来有两个指向后的箭头:一个连接到之前的提交G(如果我们成功了,或者直接C,如果没有),另一个指向提交F。现在所有提交都在main上。合并提交 H only 在 main 上(至少目前如此),但因为它向后指向两者......所有这些提交都可从提交H 访问,因此所有提交都“开启”main。 到目前为止有两行,但只有一个分支用于找到它们。
当然还有那个名字develop,找到提交F,找到提交E,以此类推,一路回到A。提交F 因此在分支develop 上,就像以前一样。只是它现在在分支maintoo。
但是:现在我们可以完全删除名称develop,使用git branch -d develop。当我们这样做时,我们会得到这个:
A--B--C------G--H <-- main (HEAD)
\ /
D--E--F
所有提交仍然存在。还有两条线。但现在只涉及一个分支名称。
这就是为什么我喜欢说分支名称无关紧要——当然不是为了找到一个提交。我们需要一些方法来找到提交H。只要我们有这个,我们也会找到所有早期的提交,因为它们是链接的,一个 - 或者对于合并提交 H,两个 - 一次,向后。
您的可视化工具中的线条代表这些联系。链接是分支,或者不是分支,或者是任何你喜欢的,取决于你想如何查看它们。
1我们最后在.git/config 中还有一个配置部分:
[branch "master"]
remote = origin
merge = refs/heads/master
例如,分支名称不仅仅是只是提交哈希ID。但是这两个条目大多是静态的:如果我们运行 git branch --set-upstream-to,remote 和 merge 设置会发生变化,例如,要更改 master 的 upstream 设置。
2运行git checkout origin/master,然后运行git status,我们看到这会产生分离的HEAD状态,而不是on branch origin/master状态。所以origin/master——其全拼是refs/remotes/origin/master——不是一个分支名称。由于 branch 这个词在 Git 中因过度使用而被严重殴打,所以我认为将其称为 远程跟踪分支名称 是个坏主意。 remote-tracking name 短语同样适用于识别这是什么类型的名称。