【问题标题】:How does Git retrieve all files from a branch?Git 如何从分支中检索所有文件?
【发布时间】:2014-05-31 22:08:28
【问题描述】:

我目前正在学习 Git,但在理解 Git 如何从分支中检索文件时遇到问题。

据我了解,分支只是指向提交的指针。所以我相信,当您签出一个分支时,它会从指针所在的提交开始跟踪从父级到父级的所有提交。但我不明白它如何为具有多个父级的提交选择父级,例如在合并中。

例如:

假设我想查看master,Git 怎么知道在C5 点应该是C4 而不是C8。还是我完全误解了这一点?

当您签出一个分支时,Git 是如何知道将哪些文件放入您的工作树中的?

【问题讨论】:

  • 它不会go 用于父母或其他人。 C5 是一个合并点。此外,每次结帐时,您不会将所有分支回溯到根目录。
  • @njzk2: 那么 checkout 是如何带来正确的文件的呢?
  • 合并点包含有关如何从每个父级完成合并的信息。因此,如果您回溯所有内容,文件应该是什么样子就没有歧义了。
  • 至于 git 究竟是如何存储有关每次提交的信息的,我不确定。
  • 您可能还想阅读这个问题的答案stackoverflow.com/questions/4964099/what-is-a-git-snapshot

标签: git git-branch git-merge git-commit


【解决方案1】:

Git 与大多数其他版本控制系统 (VCS) 不同。

大多数 VCS-es 存储各种形式的“增量”。例如,如果整个存储库中最尖端的提交是C9,由master 标识,并且您提取它,您可能会按原样获取存储库中的所有文件,而如果您提取C5(之前的提交从C9),您将从所有最新文件开始,然后C5 说“撤消这个,撤消那个,撤消另一件事”,版本控制系统会撤消这些,这会让您获得截至提交C5

同样,git 不会 这样做。

相反,git 的存储库存储 git 所谓的“对象”。有四种类型的对象:“commits”、“annotated tags”、“trees”和“blob”。我们将忽略带注释的标签(为此目的不需要它们),只考虑其他三个。

每个对象都有一个唯一的 160 位名称,以 SHA-1 哈希表示。哈希值是通过计算对象内容(加上其类型)的 SHA-1 来构造的。 Git 假定存储库中没有两个 不同的 对象将永远计算相同的 SHA-1(如果它们这样做,git 会乱七八糟地爆炸;但这从未发生过)。 (但请注意,same 对象——例如,许多提交中的相同 foo.c 文件——具有一个唯一的 SHA-1。)

提交对象如下所示:

$ git cat-file -p 5f95c9f850b19b368c43ae399cc831b17a26a5ac
tree 972825cf23ba10bc49e81289f628e06ad44044ff
parent 9c8ce7397bac108f83d77dfd96786edb28937511
author Junio C Hamano <gitster@pobox.com> 1392406504 -0800
committer Junio C Hamano <gitster@pobox.com> 1392406504 -0800

Git 1.9.0

Signed-off-by: Junio C Hamano <gitster@pobox.com>

也就是说,它有一个tree、一个parents 的列表、一个author-and-date、一个committer-and-date 和一条短信。这也是它的全部。每个parent 都是父提交的SHA-1;根提交没有父提交,合并有多个父提交,但大多数提交只有一个父提交,这就是您发布的图表中的箭头。

树对象如下所示:

$ git cat-file -p 972825cf23ba10bc49e81289f628e06ad44044ff
100644 blob 5e98806c6cc246acef5f539ae191710a0c06ad3f    .gitattributes
100644 blob b5f9defed37c43b2c6075d7065c8cbae2b1797e1    .gitignore
100644 blob 11057cbcdf4c9f814189bdbf0a17980825da194c    .mailmap
100644 blob 536e55524db72bd2acf175208aef4f3dfc148d42    COPYING
040000 tree 47fca99809b19aeac94aed024d64e6e6d759207d    Documentation
100755 blob 2b97352dd3b113b46bbd53248315ab91f0a9356b    GIT-VERSION-GEN
[snip lots more]

树为您提供了与该提交相关的顶级目录。大多数树条目是blobs;子目录更多trees。 blob 的 mode 为您提供可执行位(这些看起来像 Unix 文件模式,但 git 实际上只使用一个可执行位,因此模式始终为 100644100755)。还有一些特殊情况的模式(例如,符号链接),但我们现在可以忽略它们。在任何情况下,每个条目都有另一个唯一的 SHA-1,这是 git 查找下一个条目(子树或 blob)的方式。

每个blob 对象 都包含实际文件。例如,GIT-VERSION-GENblob 是 git 版本生成器脚本:

$ git cat-file -p 2b97352dd3b113b46bbd53248315ab91f0a9356b
#!/bin/sh

GVF=GIT-VERSION-FILE
DEF_VER=v1.9.0
[snip]

所以,要提取提交,git 只需要:

  1. HEADmaster 等符号名称转换为提交的SHA-1
  2. 提取提交对象以找到顶层树
  3. 提取顶级树对象以查找所有文件和子树
  4. 对于每个文件,提取文件对象;对于每个子树,递归地提取该树及其对象。

(Git 对象被压缩存储,并最终被进一步压缩为“包文件”,确实使用 deltas,但与其他 VCS-es 的方式非常不同。没有必要 delta-将文件foo.c 压缩到foo.c 的先前版本;例如,git 可以对树进行增量压缩,或者对某些文档进行一些 C 代码。确切的包文件格式也经历了几次修改:如果一些未来的版本有更好的压缩方式,例如,包格式可以从版本 4 更新到版本 5。无论如何,“松散”对象只是 zlib 压缩而不是 delta 压缩。这使得访问和更新它们的速度非常快。包文件用于更静态的项目(未修改的文件)和网络传输。它们是在git gc 期间构建的,并且还用于推送和获取操作[使用称为“的变体”薄”包装,如果可能的话]。)

有关允许您读取和写入单个对象的更多 git“管道”命令,请参阅 the Pro Git book(提醒自 gatkin's answer)。

【讨论】:

    【解决方案2】:

    Git 会在每次提交时存储所有跟踪文件的完整快照,而不仅仅是差异。除了父提交 ID,C9(和每个提交)还有一个树 ID。你可以用

    看到这个
    git log --pretty=format:%T HEAD -1
    

    该命令打印树的 SHA1 哈希,如果您随后对该哈希执行 git show,您将获得项目中顶层文件夹的列表,这是树的开头。在内部,树对象具有指向文件的其他对象和子文件夹的其他树的指针。

    详情请见chapter 9 of Pro Git

    【讨论】:

      【解决方案3】:

      Git 与大多数其他版本控制系统不同。它不依赖修订之间的差异来重新创建存储库中的文件。例如,与 subversion 不同,通常需要访问父提交及其关联的差异来重新创建文件,而 git 不需要。

      换句话说,在任何时候,所有 git 都需要访问一个提交,以便能够重新创建整个存储库。

      因此,提交是否有一个或多个父级并不重要。

      【讨论】:

        猜你喜欢
        • 2018-08-07
        • 2015-03-29
        • 2017-05-10
        • 2010-12-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-10
        相关资源
        最近更新 更多