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
中的内容主要是一对数据库。这两个数据库都是简单的键值存储。一个数据库,通常比较小,存储 names 和 hash 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。
index 或 staging 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 directory
和work tree
。有几种方法可以告诉这些命令关于 git 目录和工作树的位置。
通常它们都组合在一个目录结构中:
topdir <-- work tree
|- .dir <-- git directlry
|- checked out files an directories
这样,两者都会被自动发现(并作为 git 存储库引用)。
【讨论】:
以上是关于git 工作目录 vs 暂存区 vs 本地仓库 vs .git 文件夹的主要内容,如果未能解决你的问题,请参考以下文章