git 工作目录 vs 暂存区 vs 本地仓库 vs .git 文件夹

Posted

技术标签:

【中文标题】git 工作目录 vs 暂存区 vs 本地仓库 vs .git 文件夹【英文标题】:git working directory vs staging area vs local repo vs .git folder 【发布时间】:2020-01-16 07:07:39 【问题描述】:

git 本地仓库包括什么?它是否包括代码库和历史记录?

我读到 .git 文件夹是 git 存储库。但它只是包含更改历史而不是代码库。存储库是否只是更改的历史记录,而本地存储库包含历史记录和代码库?

工作目录是代码库吗?

【问题讨论】:

What is the Working Directory of a GIT repository?的可能重复 ***.com/… 【参考方案1】:

一个存储库由几个部分组成,您可以按不同的方式对它们进行分组。我将从这个分组开始:

即使使用git clone --bare,存储库的主要部分也是一种数据库,或者实际上是一对数据库,以及使用它们所需的一堆辅助文件。这是普通(非裸)克隆中.git 目录中的内容。

这个数据库中的东西是适合Git使用的形式,但不是适合你的形式,或者你在电脑上做的任何事情。因此:

存储库的另一部分是您的工作树。工作树,或工作树,或这个名称的一些变体,是你工作的地方。裸克隆会省略工作树,因此您无法在其中进行任何工作。

在适当的存储库和您的工作树之间是 Git 的 index,Git 也将其称为 暂存区(或者,现在很少称为 缓存)。索引的当前实际实现是.git/index 中的一个文件,有时还有一个或多个附加文件以使事情进展得更快,尽管通常您不应该过多关注索引的内部工作。

索引不太适合这张图片,这是有充分理由的:它确实是为了与工作树组合在一起,而不是与主 Git 存储库。克隆存储库不会克隆索引,从 Git 2.5 开始,Git 提供了一个命令 git worktree,允许您添加更多工作树。当您添加工作树时,您实际上会得到一整套额外的文件:HEAD 和其他特殊引用,例如git bisect;指数;工作树>。但是由于HEAD 和这些不同的引用不会被git clone 复制,并且确实都存在于.git 目录下的某个地方,你总是必须处理这个略显混乱、混乱的图像。

然后,从很远的距离来看,有一个清晰的分离:.git 保存被克隆的东西(Git 处理),你的工作树保存你处理的东西(没有被克隆)。一个裸存储库只有被克隆的东西。但实际上.git 中的某些内容也不会被克隆,包括索引/暂存区。一个裸存储库仍然有一个 HEAD 和一个索引,即使它们没有被克隆。最后,使用git worktree add 添加工作树不仅会创建新的工作树,还会在.git 内创建一堆文件,这些文件也不会被克隆,并且仅用于添加的工作树。

存储库是否只是更改的历史...

在某种意义上这无关紧要,但 Git 对其存储系统非常先进,这需要进行一些调整:Git 根本不存储更改!相反,Git 存储快照

我在第一个要点中提到,.git 中的内容主要是一对数据库。这两个数据库都是简单的键值存储。一个数据库,通常比较小,存储 nameshash IDs。名称是分支、标签和其他名称的通用形式。例如名称master,几乎可以肯定是一个分支名称,实际上是refs/heads/master绝对是一个分支名称。名称v2.5.0——引入git worktree 的Git 版本——是一个标签 名称,实际上是refs/tags/v2.5.0。运行 git rev-parse 允许您将任意名称(包括分支或标签名称)转换为哈希 ID,如果此数据库中有这样的名称:

$ git rev-parse v2.5.0
8d1720157c660d9e0f96d2c5178db3bc8c950436

这个哈希 ID 是更大的,在某种意义上是主数据库的关键。该数据库将哈希 ID 映射到 Git objects。 Git 对象是 Git 存储数据和元数据的方式,包括提交和在该提交中充当快照的文件。

给定任何哈希 ID,您可以使用低级 Git 命令获取对象的类型:

$ git cat-file -t 8d1720157c660d9e0f96d2c5178db3bc8c950436
tag

或内容:

$ git cat-file -p 8d1720157c660d9e0f96d2c5178db3bc8c950436 | sed 's/@/ /'
object a17c56c056d5fea0843b429132904c429a900229
type commit
tag v2.5.0
tagger Junio C Hamano <gitster pobox.com> 1438025401 -0700

Git 2.5
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1

iQIcBAABAgAGBQJVtoa5AAoJELC16IaWr+bLRtQP/0RYjVe9fLubiN5vLaAJ98B5
K3apw8bScJ4bZQJiOGMZg7AJ8pSB9XchqopjNlO2v8XVrZEkFPQ7ln3ELjOITusO
[snip rest of PGP signature]

在这种情况下,tag 对象保存 commit 对象的哈希 ID。这是上面的第一行。所以接下来,我们可以让 Git 找出提交对象,并打印出来:

$ git cat-file -p a17c56c056d5fea0843b429132904c429a900229 | sed 's/@/ /'
tree deec48fbc77f5951f81d7b5559360cdefe88ce7e
parent 7a2c87b1524e7e0fbb6c9eef03610b4f5b87236a
author Junio C Hamano <gitster pobox.com> 1438025387 -0700
committer Junio C Hamano <gitster pobox.com> 1438025387 -0700

Git 2.5

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

实际上,以上内容是 Git 2.15 提交的全部内容(@ 更改为空格,以减少垃圾邮件负载)。 tree 行是提交保存每个文件的完整快照的方式,因为它提供了另一个内部对象的另一个哈希 ID:

$ git cat-file -t deec48fbc77f5951f81d7b5559360cdefe88ce7e
tree

如果我们查看 tree 内部,我们会发现,例如,它有一个条目:

100644 blob 5ca601ee14fd2ab3b78577aa22a5db778bc7fbe0    base85.c

它为我们提供了作为该提交一部分的完整文件 base85.c 的哈希 ID。

那个文件在当前版本的Git中还是一样的,我们可以看到使用git rev-parse:

$ git rev-parse master:base85.c
100644 blob 5ca601ee14fd2ab3b78577aa22a5db778bc7fbe0    base85.c

这是我们刚刚在上面所做的一种快捷方式:

$ git rev-parse v2.5.0:base85.c
5ca601ee14fd2ab3b78577aa22a5db778bc7fbe0

Git 在第一个数据库中查找v2.5.0(如refs/tags/v2.5.0),发现是一个标签哈希ID。所以git rev-parse 找到了实际的提交、树和base85.c 的行,并提取了哈希ID。

使用该哈希 ID,我们可以直接提取 base85.c 的全部内容,使用 git cat-file -p。文件以这种方式开始:

$ git cat-file -p 5ca601ee14fd2ab3b78577aa22a5db778bc7fbe0
#include "cache.h"

#undef DEBUG_85

#ifdef DEBUG_85
#define say(a) fprintf(stderr, a)
#define say1(a,b) fprintf(stderr, a, b)
#define say2(a,b,c) fprintf(stderr, a, b, c)
#else
#define say(a) do  /* nothing */  while (0)

从哈希 ID 到内容有一条直线,而从名称(无论它们是分支名称还是标签名称,还是 v2.5.0:base85.c 之类的组合名称)到内容的直线不太直接,这涉及到将标签跟踪到提交树到特定条目以获取哈希 ID。

从快照到更改

几乎 Git 所做的一切都是从这种数据库查找开始的。但是,如果您希望比较两个提交,您可以让 Git 提取两个它们,然后告诉你有什么不同。例如,commit 745f6812895b31c02b29bdfe4ae8e5498f776c26 有 commit d4b12b9e07eba2e4ec1eff38a4151c9302bd1e2c 作为它的父级,所以我们可以运行:

git diff d4b12b9e07eba2e4ec1eff38a4151c9302bd1e2c 745f6812895b31c02b29bdfe4ae8e5498f776c26

让 Git 提取两个提交,比较它们,并向我们展示发生了什么变化:

$ git diff d4b12b9e07eba2e4ec1eff38a4151c9302bd1e2c 745f6812895b31c02b29bdfe4ae8e5498f776c26
diff --git a/Documentation/RelNotes/2.24.0.txt b/Documentation/RelNotes/2.24.0.txt
new file mode 100644
index 0000000000..a95a8b0084
--- /dev/null
+++ b/Documentation/RelNotes/2.24.0.txt
[actual diff snipped]

等等。

请注意,当我们查看 2.5.0 提交时,我们看到:

tree deec48fbc77f5951f81d7b5559360cdefe88ce7e
parent 7a2c87b1524e7e0fbb6c9eef03610b4f5b87236a

parent 行为 Git 提供了 2.5.0 提交之前 的提交的哈希 ID。因此 Git 可以自动将提交与其父级进行比较。如果我们知道一个提交的哈希 ID,我们可以让 Git 找出其父级的哈希 ID——事实上,我们可以运行 git diff,而不是运行 git show,这一切都为我们完成了。所以这就是我们倾向于做的事情。

一个简单的:

git show master

真的包括:

解析名称master以获取哈希ID 使用它来查找提交 显示提交的作者、时间戳、日志消息等 使用提交查找提交的父级 使用两个提交哈希 ID 提取两棵树 比较两个快照中的所有文件 对于每个不同的文件,显示不同之处

所有这些都通过.git 存储库中的内容进行。索引和工作树中的内容并不重要,在这里也不需要,因此所有这些都可以使用裸存储库完成。

总结

如果您想用 Git 存储库实际执行任何工作,您需要一个非裸存储库,以便拥有工作树。 Git 将从对象数据库中提取由大而丑陋的哈希 ID 发现的东西到您的工作树中,以便您可以查看并处理它。 Git 将允许您使用名称,前提是这些名称在 name-to-hash-ID 数据库中,而不是 hash ID。 Git 需要哈希ID;但您可能需要这些名称来查找哈希 ID。

indexstaging area 位于 工作树和存储库之间。它的主要功能是保存从存储库(从对象数据库)中提取的文件的副本,以便它们准备好进入 new 提交。因此,您可以将其视为您组装新提交的地方。

所以:

您的工作树以您计算机的普通格式保存文件,而不是索引/暂存区保存的特殊 Git 专用格式,并且该格式会进入您所做的每个新提交。

索引/暂存区保存提议的下一个快照。这与 current 快照开始相同:您签出的提交以便将其放入您的工作树中。如果您更改工作树中的文件,则需要将其复制回索引中,以便更新后的文件是进入下一次提交的文件。

每次提交都包含每个文件的完整快照,无论您运行 git commit 时它在索引中的任何形式。

在 Git 存储库中,历史只不过是提交本身。每个提交都会记住它的直接前任——先前提交的原始哈希 ID——并且每个提交都通过它的哈希 ID 找到。像master 这样的名字主要是为了人类,由于某种原因他们似乎无法记住看起来随机的哈希 ID。

分支和标签名称还有另一个重要作用,但为此,您应该以 Think Like (a) Git 开头。

【讨论】:

【参考方案2】:

git 本地仓库包括什么?它是否包括代码库和历史记录?

git 本地存储库包括给定修订版的所有文件和更改历史记录。

工作目录是代码库吗?

是的,在给定的修订版中。

修订是给定分支的代码库的“版本”。

例如,当您 git clone https://github.com/expressjs/express 时,您克隆了 Express 的整个存储库,其中包括其更改历史记录。

git clone https://github.com/expressjs/express.git
Cloning into 'express'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 30279 (delta 0), reused 0 (delta 0), pack-reused 30276
Receiving objects: 100% (30279/30279), 8.60 MiB | 10.08 MiB/s, done.
Resolving deltas: 100% (17089/17089), done.

然后,您可以使用git checkout 4.x 将代码库切换到4.x,而无需访问互联网。

git checkout 4.x
Branch '4.x' set up to track remote branch '4.x' from 'origin'.
Switched to a new branch '4.x'

【讨论】:

【参考方案3】:

您需要了解两个概念:

    git directory 包含 git 元数据、提交历史记录、分支信息……

    work tree 包含从 git 目录(您的工作目录)签出的文件。

Git 存储库通常意味着:git 目录和工作树。但是,有时人们将 git 目录称为 git 存储库。

多个git命令只需要知道git directory。其他人则需要git directorywork tree。有几种方法可以告诉这些命令关于 git 目录和工作树的位置。

通常它们都组合在一个目录结构中:

 topdir <-- work tree
 |- .dir <-- git directlry
 |- checked out files an directories  

这样,两者都会被自动发现(并作为 git 存储库引用)。

【讨论】:

以上是关于git 工作目录 vs 暂存区 vs 本地仓库 vs .git 文件夹的主要内容,如果未能解决你的问题,请参考以下文章

一文吃透 VS Code+Git 操作(vs code中git的相关配置与使用)

Git---工作区暂存区版本库远程仓库

git常用命令

Git本地仓库远程仓库操作和分支操作命令

git基础教程 认识几个概念--工作区暂存区版本库远程仓库

Git的使用