如何知道工作目录指的是git中的哪个分支?

Mar*_* AJ 0 git git-branch

根据一些研究,我发现 git 在两个地方保留了两个版本的代码:

  • .git/refs/heads (本地存储库)
  • .git/refs/remotes/ (工作目录)

首先,我的理解可以吗?

然后我需要知道两者headworking directory指的是哪个分支。有两个命令:

  1. cat .git/head

  2. git branch

你能告诉我这两个命令指的是哪一个吗?(在 中的分支head或在 中的分支working directory)?

当您运行时git status,您的更改是否会与head或 中的版本进行比较working directory

tor*_*rek 6

不,您的理解不正确:中的实体.git/refs/references,而不是work-trees

(恐怕这会持续很长时间,因为您的问题有点没有重点。)

术语定义

Git 过去常常将术语工作目录与术语工作树工作树互换使用。由于与操作系统定义的工作目录混淆,这在几年前开始发生变化,您可以在其中运行以打印您的工作目录。我会坚持使用术语work-treecd pathpwd

默认情况下,Git 存储库带有一 (1) 个工作树。工作树与.git包含存储库的子目录位于同一目录中,因此如果您有一个.git目录,则您位于此特定 Git 工作树的顶层。

Git 存储库的主要功能(至少就 Git 本身而言)是保存提交。提交是一些文件集的完整快照。存储在提交中的文件被称为版本化,用于提取提交会将提交中所有文件的特定版本提取到工作树中。以这种方式存储在提交中的文件被称为版本控制的。您已检出的提交(从存储库中提取到工作树中)是当前提交git checkout commit-specifier

请注意,存储在提交中的文件是一种特殊的、压缩的(通常是高度压缩的)只读格式。它们无法更改。一旦 Git 将某些内容作为 Git 格式对象1存储在存储库数据库中,该内容将永远无法更改。它可以被复制——提取为文本格式,以某种方式进行操作,然后存储为一个新的不同版本——但每个 Git 对象都由其表面上随机的哈希 ID 记录,这实际上是其数据的加密校验和。通过更改内部 Git 对象的某些内容并再次存储它,您只需创建一个具有新的不同哈希 ID的对象。2

Git 允许工作树包含不受版本控制的其他文件。用 Git 的术语来说,这些文件是untracked;通过逻辑反转,必须跟踪受版本控制的文件。未被跟踪的文件也可以被忽略(被跟踪的文件不能被忽略)。

Git 存储库还附带一 (1) 个索引。Git 的索引是一个关键的数据结构,在将 Git 与工作树一起使用时,您必须始终了解它。这个索引非常重要,或者最初命名非常糟糕,以至于它有两个额外的名称:它也被称为staging area,有时也称为cache。它最初被称为索引的原因是,在内部,它索引(或缓存)工作树。不过,这不是索引对很重要的方面。

索引为做的主要事情是保存每个文件的一个版本。也就是说,当你提取的当前提交到工作树,第一件事情的Git真的是复制的每个文件提交,进入该指数。(这个副本速度快且轻,因为索引以相同的压缩格式保存文件,仅 Git 格式,在这种情况下实际上共享底层 Git 对象。)一旦文件在索引中,Git 可以并且确实提取它进入工作树,将其扩展为正常格式,以便非 Git 程序(以及您自己)可以查看和/或修改它。

文件的索引副本与同一文件的当前提交副本之间的主要区别在于索引版本可以被覆盖。此外,文件索引中的事实是 Git 决定文件被跟踪的方式。这为我们提供了被跟踪文件的真正定义:当且仅当文件存在于索引中时才被跟踪。3

Git 有多个不同的东西叫做head :分支 head,我认为将它们称为分支名称更合适,但 Git 使用这些术语可以互换,并且有一个特殊的区别HEAD,应该像这样用全大写字母输入。它实际上存储在一个名为.git/HEAD. (single)HEAD始终是当前提交的符号名称。中的项目.git/refs/heads/包括(但不一定是全部)分支名称,并且项目中的项目.git/refs/remotes/包括(但不一定是全部)Git 调用的,不同的,远程跟踪分支名称远程跟踪分支,以及其他几个误导性术语。我更喜欢称这些远程跟踪名称。稍后我们将对此进行更多讨论,但现在请记住,这些都是 Git 称为引用的实例。


1 Git 在主存储库数据库中有四种类型的对象:committreeblobannotated tag。你通常不需要关心任何事情,但在这里提交。数据库本身只是一个键值存储,键是哈希 ID,值是对象的数据。Git 可以(并且确实)在通过密钥提取对象时检测对象损坏,因为数据的加密散列必须与密钥匹配。

2请注意,每个提交都有自己独特的哈希 ID。文件不是这样:如果一个文件的内容在多次提交中是相同的,那么 Git 中该文件的内部对象在所有这些提交中都具有相同的哈希 ID。这是因为哈希 ID 是内容的哈希。每个提交在某些方面都是独一无二的——例如,它们都有时间戳,所以只要你的计算机时钟正常工作并且你每秒提交的次数不超过一次,每次提交都有一个唯一的时间戳。实际上,有两种时间戳,一种用于“作者”,一种用于“提交者”,但我们现在不必担心这一点。

3当您运行git commit,Git会使得提交使用无论是在指数/舞台面积在那一刻。因为索引中的文件已经是 Git-only 格式,这使得提交非常快。在包含数万个文件的大型项目中,重新压缩每个跟踪的工作树文件需要的时间太长(有时需要很多秒),但使用索引中已经压缩的数据只需要几毫秒。这就是为什么索引存在; 并考虑到该指数存在,git commit简单地包起来,这就是为什么一个文件存在索引是什么使跟踪的文件。


分支名称、引用以及“在分支上”的含义

正如我们刚刚看到的,分支名称是一种特定的引用。一个 Git 引用只包含一 (1) 个哈希 ID。这适用于分支名称、远程跟踪名称和所有其他类型的引用(标记名称、来自 的替换条目git replace等)。

分支名称只是全名以refs/heads/. 这就是为什么.git/refs/heads. 然而,分公司名称意图是区分大小写的:分支xyzzy是从分支不同的Xyzzy是从不同的xyZZy,等等。这在折叠大小写的 Windows 和 MacOS 系统中目前部分被打破,但有时引用存储在文件 ( .git/packed-refs) 中,然后它在 Windows 和 MacOS 上工作。4 将来,引用可能会存储在真实的数据库中(与存储库对象数据库相邻),这将使它们区分大小写,并且无法通过在.git/refs/heads/.

远程跟踪名称是全名以refs/remotes/. 他们的名字继续包含一个更多的元素,通常是origin/。您的 Git 使用远程跟踪名称来记住您的 Git 在其他某个 Git 存储库中找到的分支名称及其对应的单个哈希 ID。虽然它们实际上是您的名字,并且您可以随意更改它们,但最好让您的 Git 自动更新它们以匹配存储在其他 Git 中的分支名称。

请注意,Git 通常会剥离该部分,因此全名是分支名称的分支名称仅称为. 这在下一步中至关重要。refs/whatever/refs/heads/mastermaster

在 Git 的术语中,你可以在一个分支上,也可以有一个分离的 HEAD。当您运行 时,Git 会根据哈希 ID 存储在分支名称中的提交填充索引和工作树。它还将该分支名称复制到 中,以便包含文字字符串。此时 Git 会声明您在该分支上。另一方面,如果您运行,Git 会根据您提供的哈希 ID 的提交填充索引和工作树(这需要是有效的现有提交),然后将哈希 ID 本身写入. 此时 Git 会声称您有一个分离的 HEAD。git checkout branchnamebranchname HEAD.git/HEADref: refs/heads/branchnamegit checkout hash-id.git/HEAD

因此,如果您的HEAD文件包含原始哈希 ID — 已分离 — Git 可以读取.git/HEAD以获取当前提交的哈希 ID。如果附加了 HEAD,Git 可以读取.git/HEAD以获取分支名称,然后读取分支名称(可能存储也可能不存储,但肯定存储在某处)以获取当前提交哈希 ID。无论哪种方式,Git 都可以使用您的来查找当前提交。.git/refs/heads/nameHEAD

每当您进行新的提交时,Git:

  • 创建提交对象。Git 基本上冻结索引中的所有文件版本以用作快照。它用这些文件构建一个新的提交,你作为作者和提交者,用“现在”作为时间戳。它使用您的提交消息作为提交日志。而且,至关重要的是,它使用当前提交 ID作为新提交的提交。

  • 既然提交已经存在并且拥有自己唯一的哈希 ID,那么 Git 会更新当前分支(如果您的 HEAD 已附加)。也就是说,如果其中.git/HEAD有分支名称,Git 会用新的哈希 ID 覆盖分支名称的存储哈希 ID。如果您的 HEAD 已分离,Git.git/HEAD将使用新的哈希 ID 进行覆盖。无论哪种方式,HEAD继续命名当前提交,因为新提交现在是当前提交。

请注意,因为新提交完全包含索引/暂存区中的那些文件,一旦您提交,索引和HEAD提交就会匹配,就像您第一次运行git checkout以检查前一个提交时所做的那样。该工作树根本不会进入这个画面!


4 例如,因为 Windows 和 MacOS 不能有两个不同的文件命名为MASTERand master,所以让分支名称只在大小写不同时是不明智的。出于同样的原因,提交包含名称仅在大小写不同的文件的提交是一个坏主意——例如,在较旧的 Linux 内核中就是如此。当您检出这样的提交时,您的索引/暂存区会获得两个文件,例如,README.TXTreadme.txt,但是您的工作树只能保存其中一个,并且使用 Git 变得太困难了。


关于你的问题

然后我需要知道头和工作目录都指的是哪个分支。

有两个命令:

  1. cat .git/head
  2. git branch

正如我上面提到的,该文件.git/HEAD包含分支名称(如果您的 HEAD 已附加)或原始提交哈希(如果您的 HEAD 已分离)。所以cat .git/HEAD——你应该全部使用大写,这样它就可以在其他系统上工作——如果你在一个分支上,它会告诉你你在哪个分支上。

git branch默认情况下,该命令会列出您的分支名称(您的所有.git/refs/heads/文件以及存储在其他地方的任何分支名称),如果您在某个分支上*,则在该名称的前面添加一个前缀.git/HEAD。如果您有一个分离的 HEAD,git branch将在其输出中包含列出字符串* HEAD detached at ...* HEAD detach from .... 确切的细节因 Git 版本而异。

还有几个命令旨在编写使用 Git 的代码:git symbolic-ref将读取HEAD附加到的分支名称并打印它,或者如果 HEAD 被分离则简单地失败;并且git rev-parse --symbolic-full-name HEAD将打印全名,例如,refs/heads/master如果您在分支上,或者仅HEAD在 HEAD 分离时打印。使用git rev-parse --abbrev-ref HEAD您可以获得分支的短名称(refs/heads/剥离),或者HEAD如果 HEAD 分离,则再次使用。

当您运行时git status,您的更改是否会与位于 head 或工作目录中的版本进行比较?

这个特定的问题不能以被问到的方式回答。什么git status是运行两个比较 - 两个git diff --name-statuses,或多或少:

  • HEAD 提交和索引之间有什么不同(如果有)?
  • 如果有的话,索引和工作树之间有什么不同?

第一个差异的结果为提交暂存的更改——如果您现在提交,使用当前索引,新快照将与旧快照不同。第二个差异的结果是暂存以进行提交的更改。您可以使用git add将工作树文件复制到索引文件上,以便索引版本与工作树版本匹配。

请记住,索引中的任何内容实际上都是提议的 commit。更新每个文件的索引/暂存区副本会更改您接下来提议提交的内容。

  • 这不仅仅是一个答案,这实际上是 git *(换句话说,一个有用且简短的 git 教程)* 的速成课程,它极大地帮助了我理解 git 的核心。谢谢你。 (2认同)