上篇Git入门中介绍了一些Git基本操作过程,其中提到了Git版本库的概念,Git到底是如何管理版本信息的呢?常用的git add、git commit、git branch命令在git版本库中会产生什么内容?Gitlab服务器是如何存放项目内容的?看完本篇文章,相关疑惑你都会得到解答
一、基本介绍
.git目录也可以称为”版本库”(Version Control Repository)。该目录包含了Git版本控制系统所需的所有信息,包括历史提交、分支、标签、配置等。它是Git用来管理和追踪项目版本的核心组成部分。通过.git目录,Git可以跟踪和记录项目中每个文件的变化,以及各个提交版本之间的关系。因此,可以将.git目录视为整个项目的版本库,用于管理和存储项目的版本历史。当我们使用git init 命令的时候,就会创建一个新的Git仓库。执行该命令后,Git会在当前目录下生成一个.git目录,该目录用来存储Git仓库的元数据和对象。
admin@admindeMacBook-Pro Workspace % mkdir LearnGit
admin@admindeMacBook-Pro Workspace % cd LearnGit
admin@admindeMacBook-Pro LearnGit % ls
admin@admindeMacBook-Pro LearnGit % git init
Initialized empty Git repository in /Users/admin/Workspace/LearnGit/.git/
admin@admindeMacBook-Pro LearnGit % ls -la
total 0
drwxr-xr-x@ 3 admin staff 96 11 2 18:08 .
drwxr-xr-x 14 admin staff 448 11 2 18:08 ..
drwxr-xr-x@ 9 admin staff 288 11 2 18:08 .git
二、初始化
2.1 目录结构
查看.git 仓库目录结构
admin@admindeMacBook-Pro LearnGit % tree .git
.git
├── HEAD
├── config
├── description
├── hooks
│ ├── applypatch-msg.sample
│ ├── commit-msg.sample
│ ├── fsmonitor-watchman.sample
│ ├── post-update.sample
│ ├── pre-applypatch.sample
│ ├── pre-commit.sample
│ ├── pre-merge-commit.sample
│ ├── pre-push.sample
│ ├── pre-rebase.sample
│ ├── pre-receive.sample
│ ├── prepare-commit-msg.sample
│ ├── push-to-checkout.sample
│ └── update.sample
├── info
│ └── exclude
├── objects
│ ├── info
│ └── pack
└── refs
├── heads
└── tags
9 directories, 17 files
-
HEAD文件:该文件指向当前分支的引用。它通常包含了一个指向refs/heads/目录下某个分支的引用。 -
config文件:该文件存储了Git仓库的配置信息。它包含了仓库级别的配置项,如用户名、邮箱、远程仓库等 -
description文件是一个可选的文件,用于给Git仓库提供一个简短的描述。该文件的内容通常是一个简单的文本字符串,用于描述该Git仓库的用途或特点。这 -
hooks/目录:该目录包含了Git的钩子脚本。你可以在这里编写自定义脚本,用于在特定的Git操作(如提交、合并等)前后执行一些自定义的操作。 -
info/目录是一个可选的目录,用于存储一些Git相关的辅助信息。在info/目录中,最常见的文件是info/exclude。exclude文件用于指定Git仓库中不希望被跟踪的文件或目录。可以在exclude文件中添加模式匹配规则,告诉Git忽略特定的文件或目录。 -
objects/目录:该目录存储了Git仓库中的对象。Git使用对象来存储文件的内容和元数据。每个对象都有一个唯一的SHA-1哈希值作为标识。对象可以是文件内容、提交历史、分支、标签等。 -
refs/目录:该目录存储了Git仓库中的引用。引用是指向某个提交对象的指针,它们可以是分支、标签或其他的引用。refs/heads/目录存储分支引用,refs/tags/目录存储标签引用。
2.2 文件变动
新增了一个文件,并添加到暂存区
admin@admindeMacBook-Pro LearnGit % echo 'test' > file
admin@admindeMacBook-Pro LearnGit % git add file
此时查看.git 目录结构,发现多了个index文件,且objects新增了一个名为9d的文件夹。index文件:该文件存储了暂存区(也称为索引)的内容。暂存区是Git用来暂存将要提交的文件修改的区域。
.git
├── HEAD
├── config
├── description
├── hooks
│ ├── applypatch-msg.sample
│ ├── commit-msg.sample
│ ├── fsmonitor-watchman.sample
│ ├── post-update.sample
│ ├── pre-applypatch.sample
│ ├── pre-commit.sample
│ ├── pre-merge-commit.sample
│ ├── pre-push.sample
│ ├── pre-rebase.sample
│ ├── pre-receive.sample
│ ├── prepare-commit-msg.sample
│ ├── push-to-checkout.sample
│ └── update.sample
├── index
├── info
│ └── exclude
├── objects
│ ├── 9d
│ │ └── aeafb9864cf43055ae93beb0afd6c7d144bfa4
│ ├── info
│ └── pack
└── refs
├── heads
└── tags
2.3 git 存储机制
使用file命令查看objects 里存储的文件格式,发现是zlib压缩后的内容
admin@admindeMacBook-Pro LearnGit % file .git/objects/9d/aeafb9864cf43055ae93beb0afd6c7d144bfa4
.git/objects/9d/aeafb9864cf43055ae93beb0afd6c7d144bfa4: zlib compressed data
想查看zlib压缩后的内容具体是什么,需要先解压缩查看,下面命令通过Python的zlib库解压缩查看
admin@admindeMacBook-Pro LearnGit % python3 -c "import zlib; import sys; data = open('.git/objects/9d/aeafb9864cf43055ae93beb0afd6c7d144bfa4', 'rb').read(); print(zlib.decompress(data))"
b'blob 5x00testn'
从结果可以看出,这个文件记录了之前通过 git add 命令添加的 file 文件的相关信息,包括文件的类型、大小和内容。具体地说,文件类型为 blob,大小为 4,内容则是 test。9d/aeafb9864cf43055ae93beb0afd6c7d144bfa4 这个目录和文件名是怎么来的 目录和文件名其实是基于内容的 sha1 哈希值生成的。通过对 zlib 压缩的数据进行 sha1sum 处理,我们就可以得到这样的值。Git在存储内容时,取其前两个字符作为文件夹名(如 9d),余下的部分作为文件名。这种方式是为了确保在 objects 文件夹中不会有过多的文件,从而使文件系统保持高效。
admin@admindeMacBook-Pro LearnGit % python3 -c "import zlib, hashlib; print(hashlib.sha1(zlib.decompress(open('.git/objects/9d/aeafb9864cf43055ae93beb0afd6c7d144bfa4', 'rb').read())).hexdigest())"
9daeafb9864cf43055ae93beb0afd6c7d144bfa4
当时上面用Python的zlib库这种方式查看zlib压缩后的内容比较麻烦,Git专门提供了git cat-file命令进行查看 git cat-file命令的一般用法是:
git cat-file <options> <object>
其中,是命令的选项,
-
-t:显示对象的类型(blob、tree、commit等)。 -
-s:显示对象的大小(以字节为单位)。 -
-p:以人类可读的方式显示对象的内容。备注:必须要引用从根对象文件夹开始的完整文件名,也就是前面一定要加上9d,否则会出现如下报错
admin@admindeMacBook-Pro 9d % git cat-file -s 9daeafb9864cf43055ae93beb0afd6c7d144bfa4
5
admin@admindeMacBook-Pro 9d % git cat-file -t 9daeafb9864cf43055ae93beb0afd6c7d144bfa4
blob
admin@admindeMacBook-Pro 9d % git cat-file -p 9daeafb9864cf43055ae93beb0afd6c7d144bfa4
test
admin@admindeMacBook-Pro 9d % ls -la
total 8
drwxr-xr-x@ 3 admin staff 96 11 2 18:17 .
drwxr-xr-x@ 5 admin staff 160 11 2 18:17 ..
-r--r--r--@ 1 admin staff 20 11 2 18:17 aeafb9864cf43055ae93beb0afd6c7d144bfa4
admin@admindeMacBook-Pro 9d % git cat-file -p aeafb9864cf43055ae93beb0afd6c7d144bfa4
fatal: Not a valid object name aeafb9864cf43055ae93beb0afd6c7d144bfa4
三、文件提交
上面一步介绍了执行git add命令,增加一个文件到暂存区,.git 下的文件有哪些变化。那么执行commit 提交后,又会发生哪些变化呢?
admin@admindeMacBook-Pro LearnGit % git commit -m "first commit"
[main (root-commit) 89b57e3] first commit
1 file changed, 1 insertion(+)
create mode 100644 file
admin@admindeMacBook-Pro LearnGit % tree .git
.git
├── COMMIT_EDITMSG
├── HEAD
├── config
├── description
├── hooks
│ ├── applypatch-msg.sample
│ ├── commit-msg.sample
│ ├── fsmonitor-watchman.sample
│ ├── post-update.sample
│ ├── pre-applypatch.sample
│ ├── pre-commit.sample
│ ├── pre-merge-commit.sample
│ ├── pre-push.sample
│ ├── pre-rebase.sample
│ ├── pre-receive.sample
│ ├── prepare-commit-msg.sample
│ ├── push-to-checkout.sample
│ └── update.sample
├── index
├── info
│ └── exclude
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ └── main
├── objects
│ ├── 89
│ │ └── b57e32d3250db54dc189ad0a9a1c81a41ea724
│ ├── 9d
│ │ └── aeafb9864cf43055ae93beb0afd6c7d144bfa4
│ ├── e1
│ │ └── b8ecbb1f19709f3a4867a0ffe08bb2e07acf19
│ ├── info
│ └── pack
└── refs
├── heads
│ └── main
└── tags
首先有一个新文件 COMMIT_EDITMSG,它保存了最新的提交信息。另外新增了一个 logs 目录,git 通过它来记录所有的提交变动。在此,可以查看所有引用(refs)及 HEAD 的提交记录。object 文件夹也发生了些变化,另外 refs/heads 目录,里面出现有一个 main 文件。这是 main 分支的引用。查看内容发现其指向了一个新的对象commit。查看commit对象的内容时,显示提交的作者和提交者信息,另外内容里返回了一个e1b8ecbb1f19709f3a4867a0ffe08bb2e07acf19 tree对象
admin@admindeMacBook-Pro LearnGit % cat .git/refs/heads/main
89b57e32d3250db54dc189ad0a9a1c81a41ea724
admin@admindeMacBook-Pro LearnGit % git cat-file -t 89b57e32d3250db54dc189ad0a9a1c81a41ea724
commit
admin@admindeMacBook-Pro LearnGit % git cat-file -p 89b57e32d3250db54dc189ad0a9a1c81a41ea724
tree e1b8ecbb1f19709f3a4867a0ffe08bb2e07acf19
author 洋洋 <yangyang@test.com> 1698931679 +0800
committer 洋洋 <yangyang@test.com> 1698931679 +0800
first commit
也可以使用 git cat-file -t refs/heads/main
命令查看 查看tree对象的内容,发现这个文件指向了我们在执行 git add file 时加入的那个初始对象。
admin@admindeMacBook-Pro LearnGit % git cat-file -p e1b8ecbb1f19709f3a4867a0ffe08bb2e07acf19
100644 blob 9daeafb9864cf43055ae93beb0afd6c7d144bfa4 file
四、文件修改
让对文件file内容进行修改,新增一行aaa内容,
admin@admindeMacBook-Pro LearnGit % echo "aaa" > file
admin@admindeMacBook-Pro LearnGit % git add file
admin@admindeMacBook-Pro LearnGit % git commit -m "add new line"
[main cc5ad72] add new line
1 file changed, 1 insertion(+), 1 deletion(-)
admin@admindeMacBook-Pro LearnGit % tree .git
.git
├── COMMIT_EDITMSG
├── HEAD
├── config
├── description
├── hooks
│ ├── applypatch-msg.sample
│ ├── commit-msg.sample
│ ├── fsmonitor-watchman.sample
│ ├── post-update.sample
│ ├── pre-applypatch.sample
│ ├── pre-commit.sample
│ ├── pre-merge-commit.sample
│ ├── pre-push.sample
│ ├── pre-rebase.sample
│ ├── pre-receive.sample
│ ├── prepare-commit-msg.sample
│ ├── push-to-checkout.sample
│ └── update.sample
├── index
├── info
│ └── exclude
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ └── main
├── objects
│ ├── 72
│ │ └── 943a16fb2c8f38f9dde202b7a70ccc19c52f34
│ ├── 89
│ │ └── b57e32d3250db54dc189ad0a9a1c81a41ea724
│ ├── 9d
│ │ └── aeafb9864cf43055ae93beb0afd6c7d144bfa4
│ ├── cc
│ │ └── 5ad72bced41c7e9ebea0a2adb85ba5b26dd344
│ ├── e1
│ │ └── b8ecbb1f19709f3a4867a0ffe08bb2e07acf19
│ ├── f6
│ │ └── 722b5327845e79fbc2ce8a8e008d417566dcb3
│ ├── info
│ └── pack
└── refs
├── heads
│ └── main
└── tags
18 directories, 28 files
总的来说,新增了三个对象。一个是含有文件新内容的 blob 对象,还有一个是 tree 对象,以及一个 commit 对象。再次从 HEAD 或 refs/heads/main 开始追踪这些对象。
admin@admindeMacBook-Pro LearnGit % git cat-file -p refs/heads/main
tree f6722b5327845e79fbc2ce8a8e008d417566dcb3
parent 89b57e32d3250db54dc189ad0a9a1c81a41ea724
author 洋洋 <yangyang@test.com> 1698932923 +0800
committer 洋洋 <yangyang@test.com> 1698932923 +0800
add new line
admin@admindeMacBook-Pro LearnGit % git cat-file -p f6722b5327845e79fbc2ce8a8e008d417566dcb3
100644 blob 72943a16fb2c8f38f9dde202b7a70ccc19c52f34 file
admin@admindeMacBook-Pro LearnGit % git cat-file -p 72943a16fb2c8f38f9dde202b7a70ccc19c52f34
aaa
commit 对象现在有了一个额外的键名为 parent,它链接到上一个提交,因为当前提交是基于上一个提交创建的。
五、创建分支
创建一个新的分支。执行 git branch test
命令
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ ├── main
│ └── test
└── refs
├── heads
│ ├── main
│ └── test
└── tags
此操作会在 refs/heads 目录下加入一个新的文件。该文件的名称就是我们新建的分支名,而内容则是最新的提交标识 id。
admin@admindeMacBook-Pro LearnGit % cat .git/refs/heads/test
cc5ad72bced41c7e9ebea0a2adb85ba5b26dd344
admin@admindeMacBook-Pro LearnGit % git log
commit cc5ad72bced41c7e9ebea0a2adb85ba5b26dd344 (HEAD -> main, test)
Author: 洋洋 <yangyang@test.com>
Date: Thu Nov 2 21:48:43 2023 +0800
add new line
commit 89b57e32d3250db54dc189ad0a9a1c81a41ea724
Author: 洋洋 <yangyang@test.com>
Date: Thu Nov 2 21:27:59 2023 +0800
first commit
在 git 中,标签的创建也是类似的操作,但它们是被创建在 refs/tags 目录下。在 logs 目录下也新增了一个文件,该文件用于记录与 main 分支类似的提交历史信息。当创建一个新的分支,Git 会在当前的提交基础上创建一个指针,指向新的分支。
六、执行推送
对本地 git 仓库进行了一系列操纵之后,需要把本地仓库推送到远程。远程 git 仓库会接收哪些数据?提前在gitlab仓库上建立一个仓库,用git remote命令做远程关联,然后把代码推送到远程
admin@admindeMacBook-Pro LearnGit % git remote add origin git@git.github.com:yangyang/git-learn.git
admin@admindeMacBook-Pro LearnGit % git push -u origin --all
Enumerating objects: 6, done.
Counting objects: 100% (6/6), done.
Delta compression using up to 10 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (6/6), 452 bytes | 452.00 KiB/s, done.
Total 6 (delta 0), reused 0 (delta 0), pack-reused 0
remote:
remote: To create a merge request for test, visit:
remote: https://git.github.com/yangyang/git-learn/-/merge_requests/new?merge_request[source_branch]=test
remote:
To git.github.com:yangyang/git-learn.git
● [new branch] main -> main
● [new branch] test -> test
branch 'main' set up to track 'origin/main'.
branch 'test' set up to track 'origin/test'.
此时查看,发现新增了一个新的 refs/remotes 目录,这是用来存储不同远程仓库相关信息的。
tree .git
└── refs
├── heads
│ ├── main
│ └── test
├── remotes
│ └── origin
│ ├── main
│ └── test
└── tags
实际上传送到远程 git 仓库的数据是什么呢?那就是 objects 文件夹内的所有数据,以及明确推送的 refs 下的分支和标签。PS:类似常用的Gitlab 管理平台,其背后存储的项目就是.GIT 目录
七、.Git 还原
通过上面的介绍,可以了解到.git目录包含了几乎所有 Git 存储和操作的东西。如若想备份或复制一个版本库,只需把这个目录拷贝至另一处即可。那么我们应该也可以把.git 文件目录结构给还原,如何还原呢?直接把.git 目录拷贝到 LearnGit2 目录,执行git checkout .
命令即可还原
admin@admindeMacBook-Pro Workspace % cp -r LearnGit/.git LearnGit2
admin@admindeMacBook-Pro Workspace % cd LearnGit2
admin@admindeMacBook-Pro LearnGit2 % ls -la
total 0
drwxr-xr-x@ 3 admin staff 96 11 3 10:48 .
drwxr-xr-x 15 admin staff 480 11 3 10:48 ..
drwxr-xr-x@ 12 admin staff 384 11 3 10:48 .git
admin@admindeMacBook-Pro LearnGit2 % git checkout .
Updated 1 path from the index
admin@admindeMacBook-Pro LearnGit2 % ls
file
八、总结
Git 本质上是一个内容寻址文件系统。在 Git 中,每个文件和目录都被视为一个对象,每个对象都有一个唯一的 SHA-1 哈希值作为其标识符。Git 使用这些哈希值来寻址和识别文件和目录。当对文件进行更改时,Git 会根据文件内容生成一个新的哈希值,并将其作为新的对象存储在 Git 仓库中。Git 的内容寻址机制使得文件的历史版本可以被有效地管理和追踪。每个提交(commit)都包含一个指向根目录的指针,这个根目录指针又指向其他子目录和文件,形成了一个有向无环图(DAG)的结构。这种结构可以让我们轻松地查看、回溯和恢复文件的历史版本。另外,Git 还使用了一种称为“对象存储”的方式来存储文件和目录对象。这些对象被压缩和存储在 Git 仓库的 .git/objects 目录下。通过使用哈希值作为对象的标识符,Git 可以通过快速的哈希查找来读取和写入这些对象。另外阅读了本文,你会发现.git/hooks 目录下的文件作用并没有讲到,那一个个如pre-commit.sample文件是什么意思,有什么作用?下一篇会单独再讲一下.git/hooks下的文件作用。
参考链接
-
https://git-scm.com/book/zh/v2/Git-内部原理-Git-对象 -
https://jvns.ca/blog/2023/09/14/in-a-git-repository–where-do-your-files-live-/ -
https://blog.meain.io/2023/what-is-in-dot-git/?continueFlag=83c2a4620ff11f71fb87e48f91cc14f5
原文始发于微信公众号(洋洋自语):Git探秘: 深度解析版本控制神器
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/273085.html