When you clone an existing repository, your Git makes a new and different repository, and copies into this new repository all1 of the commits and none of the branches from the original repository. The last step of git clone
is to create one branch. This branch name is yours, not theirs; it's just spelled the same as one of their names.
当您使用克隆(一个不同的存储库)时,您可以向其中添加越来越多的分支。如果你添加所有same原始存储库中的分支,您现在拥有它们的所有提交and他们所有的分支名称(请注意,作为您自己的分支)。但在那之前,你就拥有了他们的所有commits。没关系,因为 Git 与分支无关。 Git 是关于commits.
1The precise description is much more complicated than this, but thinking of it as "copy all of their commits and none of their branches" will get you started.
我尝试通过将分支克隆到新文件夹中(键入git clone -b
)现在只有我克隆的分支出现了。
When you make a new clone—which, again, is a new repository, where you get all of the previous repository's commits but none of its branches yet—the last step of the git clone
command is to run a git checkout
or git switch
command2 that makes one branch. The -b
flag exists so that you can tell your Git which of their branch names to copy, as the last step. If you omit the -b
flag, your Git asks their Git repository—the one you're cloning—which branch they recommend. But either way you get only one branch.
你实际上并不需要any在 Git 中工作的分支名称。你确实需要some不过,分支名称是最好的名称。这就是为什么你的 Git 在末尾添加了一个名字git clone
过程。你起的每一个名字都会让你多做一件事情。
要了解发生了什么,请继续阅读。如果您对当前问题已得到解答感到满意,则可以在此停止。
2The git switch
command was first added in Git version 2.23, to split up the overly-complicated git checkout
command into two separate commands, git switch
and git restore
. The existing git checkout
remains; you can use it instead of the two new, simpler commands. The new simplified commands are in a sense safer, though: the git switch
command tries to be very safe, as does the half of git checkout
that it copied. The git restore
command, however, is deliberately unsafe in that it will irrevocably destroy work; it copies the other half of git checkout
. So if you use git checkout
, you can accidentally invoke the "destroy my work" half when you think you're invoking the "safely do stuff" half.
Git 就是关于提交
要理解 Git 在这里做什么以及为什么这样做,首先要了解 Git 本身实际上就是关于提交的。这与分支无关,尽管分支名称可以帮助您(和 Git)find承诺。尽管提交,但这与文件无关contain文件。这实际上与提交有关:Git 所做的其他一切都是为了保留和添加提交。提交是事情的开始,也是其他一切的目的。
这意味着了解提交的含义至关重要is, 你怎么命名一个特定的提交,以及如何制作new犯罪。我们先从名字开始吧。
提交的真实名称是其哈希 ID
You might think that a branch name would name a commit—and it sort of does, but indirectly. In fact, every commit is named by its number. Each commit has a unique number. No other commit can ever have that number: once that commit is made, that number is allocated to that commit. Because that commit takes up that number forever, the number has to be really big, and it is. Currently, each Git commit gets one out of 2160 possible numbers.3 This number gets expressed in hexadecimal as a big ugly string like e31aba42fb12bdeb0f850829e008e1e3f43af500 https://github.com/git/git/commit/e31aba42fb12bdeb0f850829e008e1e3f43af500 (this is an actual commit in a Git repository for Git itself).
这个数字总是有效的:如果你有这个提交,那就是它的编号,并且git show e31aba42fb12bdeb0f850829e008e1e3f43af500
例如,将显示它。如果没有歧义,您通常可以将数字缩写为前四个字符,因此如果您有 Git 的 Git 存储库的克隆,git show e31aba42fb12bdeb0f850829e008
几乎可以保证工作。但git show e31a
不是因为它可能是此提交或提交的缩写e31a17f741...
, 例如。尽管e31ab
今天有效,随着添加更多提交,它可能会停止工作。
These numbers look random, but aren't. In fact, each one is a cryptographic checksum of the complete contents of the commit.4 Git does a double-check when extracting any of its internal objects, including commits, that the checksum still matches, so as to detect storage failures: you tell Git to find a commit (or other object) by hash ID and it checks that the hash ID still matches. So this in turn means that no part of any commit—or any of Git's other internal objects—can ever change, either. You can make new ones, each of which gets a new and different ID, but this does not affect the existing ones, which remain in the repository.
3There are plans to redo the numbering system to use 2256 numbers, with some kind of ugly transition.
4In fact, all of Git's internal objects use this scheme. This means all saved objects are frozen for all time. This is how Git freezes and de-duplicates file contents, for instance.
提交中有什么内容
现在我们知道了一种(可以说是最深入的)通过哈希 ID 查找提交的方法,现在是时候查看每个提交中的内容了。每个提交都有两部分:
-
一个提交持有一个完整快照您的所有文件。这是大多数提交的主要数据(通常也是存储库的大部分)。每个文件都存储为内部文件斑点对象,使用相同的哈希名称编码技巧。这会自动删除重复的文件,因此,如果您连续进行一百次提交并且大部分重复使用其大部分文件,它们实际上不会占用任何额外的空间。
-
每个提交还包含一些metadata,或有关提交本身的信息:例如,谁制作的、何时制作以及为什么制作。 “为什么”部分是您的日志消息:您稍后对自己和/或其他人的解释。为什么是this比上一个承诺更好?或者至少,如果它不一定更好的话,为什么它有任何不同。此特定提交的目标可能是修复某些错误,或添加某些新功能,或为添加新功能做好准备,等等。提交本身具有更新的源代码,但不一定具有任何有关bug更新应该可以修复。这是你解释这一点的机会。
Git 为您生成并稍后使用的元数据中有一段您很少直接看到,那就是:每个提交都保存其前一个提交的原始哈希 ID。这串字符串一起提交,向后,进入以最新提交结束的提交链。
我们可以画这个。想象一下,我们有一个存储库,其中只有三个提交。我们将使用单个大写字母来代表提交,而不是真正的哈希 ID。第一次提交将是A
,接下来将是B
,第三次提交是commitC
:
A <-B <-C
自提交以来C
是最后一个,它有较早的提交B
其元数据中的哈希 ID。我们这么说C
指着 B
。同样的道理,提交B
指着A
。自从A
是有史以来的第一个提交,它缺少这个向后指向的箭头:它不指向任何地方。 Git 称其为 a(或 the)根提交。这是我们停止逆向工作的地方。
我刚才提到,每次提交都有每个文件的完整快照。但如果你有 Gitshow一次提交,Git 向您展示了什么changed。 Git 如何以及为何这样做?
The why也许是最容易解释的。如果您想查看所有文件in提交,你可以查看提交。 Git 会将所有这些文件从提交中复制出来(请记住,它们以特殊的冻结 Git 格式存储,并进行了重复数据删除(并且也进行了压缩))到常规的普通计算机文件。您可能有一群比 Git 更强大的文件查看器:它们可以向您显示图像as图像、在文本编辑器中打开文本文档、使用 PDF 查看器打开 PDF 等等。但你的文件查看器可能can't将整个快照与之前的整个快照进行比较。 gitcan.
Git可以比较快照C
对照快照B
很容易,因为提交C
保留提交B
的哈希 ID。所以Git可以直接提取both承诺。此外,由于 Git 去重复文件的方式,Git 可以立即知道——甚至不需要bother提取——重复的文件。 Git只需要提取并比较不同的文件。 Git 会这样做,并构建一组更改,将旧文件变成新文件。这就是 Git 将向您展示的内容:这组指令。
(请注意,Gitcreates按需提供的指令集。在您要求 Git 比较任意两个提交之前,Git 拥有的只是两个快照。您可以根据传递给比较命令的选项获得不同的指令集。例如,Git 可以根据单词进行差异检查,或者忽略某些类型的空白更改。 Git 在这方面的能力并不总是像我们希望的那样好,但是有一些我们可以使用的技巧。不过,它们超出了这个特定答案的范围。)
按分支名称查找提交
我们已经知道,如果我们记住大而难看的哈希 ID(或写下来),我们可以使用它们来查找提交。但这是荒谬的。我们有一个computer。为什么我们没有computer为我们写下哈希 ID?
这就是分支名称的作用。但这有点偷偷摸摸。分支名称的真正作用是仅存储last提交的哈希 ID。让我们再次绘制该三提交存储库,并添加一个名称,main
,这标识了last commit:
A--B--C <-- main
在这里,而不是试图remember C
的哈希ID,我们只知道名字main
为我们做到这一点。所以git checkout main
(2.23 Git 之前)或git switch main
(2.23 及更高版本)为我们提供当前最新的提交C
——不管它有什么哈希 ID。
我们现在可以添加一个new name这也表明要承诺C
:
A--B--C <-- main, dev
现在我们还需要一件事:我们使用这些名称中的哪一个?现在,这并不重要,因为两个名字都选择提交C
。但让我们附上特殊的名字HEAD
两个分支名称之一,如下所示:
A--B--C <-- main (HEAD), dev
If we git switch dev
,我们重新附加特殊名称HEAD
到名字dev
, 像这样:
A--B--C <-- main, dev (HEAD)
现在让我们做一个new犯罪。无需担心how我们进行了一次新的提交,让我们假设一切都已完成。这个新的提交D
必然会指向现有的提交C
,因为我们做了D
from C
。所以看起来像这样:
A--B--C
\
D
But D
现在是latest提交,所以 Git 必须更新name。应该更新哪个名称?答案很明确:应该更新HEAD
附加到:
A--B--C <-- main
\
D <-- dev (HEAD)
我们现在有两个分支名称,这两个名称指定了两个不同的“最新”提交。最新提交于main
is C
,以及最新提交dev
is D
。犯罪D
指向回提交C
,这又指向B
,这又指向A
;所以所有四个提交都是on branch dev
,当其中三个处于开启状态时main
.
如果我们切换回名字main
并在那里进行新的提交,我们得到:
E <-- main (HEAD)
/
A--B--C
\
D <-- dev
这意味着我们现在有三个在两个分支上共享的提交,以及一个仅在main
和一个仅在dev
。现在我们need both用于查找所有五个提交的名称;一个名字会找到一个提交,这会找到三个shared提交,但我们需要其他名称来查找最后剩余的提交。
注意分支名称move。事实上,当我们进行新的提交时,它们会自动移动:无论哪个分支名称有HEAD
附加到它的自动移动以包含新的提交。所有其他分支名称此时都保持不变,但因为它们是our分支名称,我们掌控。我们可以随时让 Git 移动这些名称。唯一的限制是我们必须有一个commit将名称移至。
克隆创造远程跟踪名称
当我们克隆其他人的存储库时,我们会得到他们的所有提交,但不会得到他们的任何分支。这是如何运作的?好吧,假设我们有上面的内容,有两个实际的分支名称main
and dev
选择提交E
and D
分别。我们现在做一个new我们复制所有五个提交的存储库,为我们提供:
E
/
A--B--C
\
D
We do actually need two names to find all the commits. But we don't need branch names. The other Git, working with the other repository, has branch names, because those are his branches that he'll move around as he makes new commits. So what our Git does is copy their names but change them. We have our Git take their branch names and create our remote-tracking names, by adding something—usually origin/
—to the names.5 So we get:
E <-- origin/main
/
A--B--C
\
D <-- origin/dev
Git 将拒绝附加特殊名称HEAD
这些远程跟踪名称之一。HEAD
只允许附加到branch姓名。所以我们的最后一步git clone
是使用-b
选项或他们的建议,选择这两个名称之一,并从中创建一个分支名称,如下所示:
E <-- main (HEAD), origin/main
/
A--B--C
\
D <-- origin/dev
注意我们的分行名称选择相同的提交作为我们的远程跟踪名称git clone
由...制成their分店名称。但我们现在只有一个分支名称,而不是两个。如果我们运行:
git switch dev
这使用了 Git 提供的一个特殊功能,即找到它们的origin/dev
并创造了我们自己的新名字dev
:
E <-- main, origin/main
/
A--B--C
\
D <-- dev (HEAD), origin/dev
and now我们有两个分支名称。但我们一开始并没有。请注意,我们现在也有提交D
签出,而不是提交E
, 因为git switch
(or git checkout
,如果我们使用它)不仅切换分支,而且还选择分支名称标识的提交作为要签出的提交,因此可供我们使用。
5Technically, a remote-tracking name is in a separate namespace https://en.wikipedia.org/wiki/Namespace. Our Git doesn't just tack origin/
in front, it replaces refs/heads/
with refs/remotes/origin/
. The name origin
is actually a remote and we can have as many remotes as we like in our Git repository. But this is a topic for another question.