Git如何存储分支机构?

时间:2017-10-23 14:38:08

标签: git

假设我有两个分支:master和dev。第一个包含名为" 1.txt"的文件。内容

"Hello, world"

第二个包含文件" 1.txt"内容

"Goodbye, world!!"

git将在何处以及如何存储文件的不同副本?我的意思是,在.git文件夹中的确切位置?

3 个答案:

答案 0 :(得分:10)

Git并不完全存储文件。 Git存储的是对象

分支机构也不包含文件。分支名称,如masterdev,存储提交哈希ID。

理解这一点的关键有点循环:当你理解它时,你才真正理解它。 :-)但是要开始,请考虑将Git存储为提交对象,并以提交的概念为中心。

提交是这些Git对象之一。有四种对象:提交 blobs 标记。树和blob用于构建提交。标记对象用于带注释的标记,但不要担心这些标记。

所以Git就是存储提交,并提交给你的文件(通过那些树和blob对象)。但提交不是文件本身:它更像是一个包装器。提交的内容是:您的姓名(作为作者),您的电子邮件地址以及您提交的时间;提交的提交的哈希ID;你的提交日志消息;以及记住哪些文件进入提交的树对象的哈希ID。

所以你可能会认为树对象包含你的文件 - 但它也没有!相反,树对象包含文件的名称,以及blob对象的哈希ID。它是保存文件的blob对象。

所有Git对象都由一个丑陋的哈希ID

命名

提交的名称或任何其他Git对象被写为40个字符的哈希ID,如d35688db19c9ea97e9e2ce751dc7b47aee21636b。您可能已经在git log输出中看到了它们,或者在运行其他Git命令时显示的缩短版本。

这些哈希ID对于人类来说无法以任何实际的方式使用,因此Git提供了一种将简短有意义的名称转换为大丑陋哈希ID的方法。这些名称有多种形式,但您使用的第一个名称是分支名称

这意味着如果您有两个分支名称masterdev,这些实际上会存储哈希ID。

Git使用哈希ID来查找提交对象。然后,每个提交对象都存储树ID。 Git使用它来查找树对象。树对象包含(以及其他内容)名称,例如1.txt与blob哈希ID配对。 Git使用blob哈希ID来查找blob对象,blob对象存储文件的完整内容。

这就是Git存储文件的方式

  

git将在何处以及如何存储文件的不同副本?我的意思是,在.git文件夹中的确切位置?

当你运行git add 1.txt然后提交它时,Git会生成一个blob来保存1.txt中的任何内容。新blob有一些哈希ID。我们说它以1234567...开头。 Git以压缩形式将实际内容存储在.git/objects/12/34567...中,以及一些将对象类型标识为blob的前置位。

如果您再次更改1.txtgit add以及git commit,则会获得一个新的带有新ID的blob。我们说它以fedcba9...开头。该对象进入.git/objects/fe/dcba9...

为了存储这些blob,当然,Git也必须编写 tree 对象和 commit 对象。如果你在分支dev上,当Git写出新的提交时,Git将更改名称dev以存储新的提交哈希ID。

提交形成链

为了在所有这些之前在dev上找到 的提交,Git将新提交与先前的dev提示提交ID一起写为

假设代替大丑陋的哈希ID,我们给每个提交一个字母,从A开始计数。这绘制起来要容易得多,但当然我们在26次提交后会用完字母。 : - )

让我们从只有一次提交的存储库开始:

A   <-- master

分支名称master存储A,以便我们知道提交名为A

这不是很有趣,所以让我们进行新的提交B

A <-B   <-- master

现在名称master存储了字母B。提交本身B对象在其中包含提交A的ID。

要在master上进行另一次新提交,我们为其分配一个新的哈希C,使用相应的日志消息和树写一个提交对象,依此类推,并使C& #39; s 父级B

A <-B <-C

然后我们将C写入master

A <-B <-C   <-- master

这意味着分支名称,如master,只需指向分支的提示。从某种意义上说,分支本身就是提交链从最新开始并向后工作。

请注意,Git的内部箭头都指向后方。 Git始终向后运行所有内容,从最新版开始。

我们可以通过创建一个新的分支dev来使这更有趣。最初,dev指向master相同的提交:

A--B--C   <-- dev (HEAD), master

我们添加了这个有趣的符号(HEAD),以便记住我们正在使用的分支名称。

现在让我们像往常一样进行新的提交。新提交一如既往地获取其作者和日志消息,并将当前提交的哈希ID(C)存储为其父级,但现在我们必须更新分支名称指向D。我们应该更新哪个分支名称? HEAD进来的地方:它告诉我们要更新哪一个!

A--B--C   <-- master
       \
        D   <-- dev (HEAD)

现在dev标识提交D,而master仍标识C

这是分支增长的方式

这是理解Git的第一个主要秘密。 Git不存储文件,它存储提交。提交形成链。这些链 Git存储库中的历史记录。

Git使用分支名称来记住最新提示提交。这些提交提交让我们找到旧的提交。如果我们向E添加新的提交master,我们会得到:

A--B--C--E   <-- master
       \
        D   <-- dev

我们现在可以直观地看到masterdev在提交C时加入。

运行git checkout <branch-name>告诉Git在分支的尖端提取提交,使用提交查找树来查找blob以获取所有文件。然后,作为分支名称的git checkout的最后一步,Git将HEAD附加到该分支名称,以便在我们添加 new 提交时它知道要更新的分支名称

答案 1 :(得分:4)

Torek有一个excellent answer,我不会尝试复制......但如果它仍然让你感到困惑,那么让我试着用Javascript来演示它是如何工作的。我将简化一些事情,所以这不是JS中Git的精确实现,但它足以理解一些基本原理。

创建文件

文件由两个不同的部分组成:文件的实际内容;和该文件的元数据(它的名称和模式)。让我们定义内容并存储它们,以便我们以后可以引用它们:

allTheThings['06f19763'] = "blob " + "Hello, world";

这里的变量名称是值的SHA1哈希值。这是一个非常重要的概念... git中的所有东西都是SHA1哈希值。你可以使用你想要的任何SHA1工具自己生成这些哈希值(我使用了online tool)。

为简洁起见,我将哈希值截断为前8个字符。在git中工作时,只要git仍然能够唯一地标识一个对象,就可以截断你想要的数量。通常8个字符就足够了(两个具有相同前8个提交的对象的几率非常非常小),这就是大多数示例中甚至大部分文档中都会看到的内容。

创建树

很酷......所以现在我们已经有了内容。但我们现在想要文件的另一半...它的名字。为此,我们需要创建一个基本上复制文件夹/目录的树对象。

allTheThings['5e91b67a'] = "tree " + "100644 blob 06f19763 file1.txt";

此树对象表示06f19763(或“Hello,world”)引用的文件内容名为file1.txt且可读/写(100644基于Unix模式 - 这意味着file1.txt是普通文件。)

除了文件,树还可以包含其他树,这就是我们如何创建任意深度的目录。

创建提交

每个提交都包含对树的引用,表示repo的根目录。在我们的示例中,file1.txt位于根目录中,是repo中唯一的文件。所以让我们创建一个提交:

allTheThings['a9d13be8'] =
    "commit\n" +
    "tree 5e91b67a\n" +
    "author JD <email> 1508777071\n" +
    "committer JD <email> 1508777071\n" +
    "\n" +
    "Commit message";

提交指向我们的树,并包含一些其他信息,如提交作者和提交消息。

分支几乎只是提交的方便名称。更新分支时,您只需创建一个新提交,然后重置分支以指向它。

那么一切都在哪里?

到目前为止我们创建的所有内容都存储在allTheThings对象中,因此它们都存储在一起。我们可以根据前缀(“blob”,“tree”和“commit”)判断一切是什么。每个条目都是从内容的哈希中键入的,几乎保证是唯一的。每当我们更改文件的内容,文件名,提交消息等时,我们都会更改哈希值,但原始对象仍然存在,并且仍然可以被其他对象(树,提交等)引用。

例如,如果我们更新文件,我们最终会在整个链中使用新的哈希值:

allTheThings['3e103e35'] = "blob " + "Goodbye, world!!";
allTheThings['05abc8ab'] = "tree " + "100644 blob 3e103e35 file1.txt";
allTheThings['a5944bfa'] =
    "commit\n" +
    "tree 05abc8ab\n" +
    "author JD <email> 1508777071\n" +
    "committer JD <email> 1508777071\n" +
    "\n" +
    "Commit message";

请注意,即使文件名和提交消息/作者/等没有改变,对file1内容的更改也会导致链接反应直到提交:

06f19763 => 3e103e35 (the contents changed...)
5e91b67a => 05abc8ab (so the content reference in the tree changed)
a9d13be8 => a5944bfa (so the tree reference in the commit changed )

我们的allTheThings对象中存在所有六个对象,幸福地生活在彼此旁边:

allTheThings = {
    06f19763: "blob Hello, world",
    3e103e35: "blob Goodbye, world!!",
    5e91b67a: "tree 100644 blob 06f19763 file1.txt",
    05abc8ab: "tree 100644 blob 3e103e35 file1.txt",
    a9d13be8: "commit\ntree 5e91b67a\nauthor JD <email> 1508777071\ncommitter JD <email> 1508777071\n\nCommit message",
    a5944bfa: "commit\ntree 05abc8ab\nauthor JD <email> 1508777071\ncommitter JD <email> 1508777071\n\nCommit message",
}

最后,您的master分支指向a9d13be8,而您的dev分支指向a5944bfa

在真正的git中,这些对象作为单独的(压缩的)文件存储在.git目录中(.git/objects/12/34567...作为Torek said),但它是相同的概念。

因为git repo可以包含这么多对象,所以哈希的前两个字符用于将文件细分为目录,以确保目录中的maximum file count不是超过(特别是在旧系统上)。很容易认为这些前缀比这更具意义,比如对象类型,但它们没有。

这就是它。文件,树,提交和其他一些东西都被认为是Git Objects,并且在对象目录中被集中在一起。您可以使用plumbing commands直接使用这些对象并提取它们以供使用,但使用许多porcelain commands间接使用它们几乎总是容易得多。

答案 2 :(得分:0)

分支是一个文本文件,其中包含提交的哈希

它是 Git 引用的一部分——一组引用提交的对象。

Git 将所有引用存储在 .git/refs 文件夹下,分支存储在 .git/refs/heads 目录中。

由于 branch 是一个简单的文本文件,我们可以使用提交哈希的内容创建一个文件。