网站首页 > 技术文章 正文
Git内部如何工作以组织数据和历史记录
大多数开发人员都熟悉版本控制系统,而在大多数情况下git实际上是选择。 本文实际上是介绍git的工作原理,这将使开发人员可以更好地理解git。
Git是分布式版本控制,用于存储文件和目录更改的历史记录,以便开发人员可以穿越时间了解所有更改的发生方式。 最初,git是由Linux创建者创建的,用于在有多个开源开发人员时管理Linux内核开发。
在Linux手册页(man git)中,Git被标识为"愚蠢的内容跟踪器"。 通过本文,您可以确定为什么要这样调用git。
Git的"Porcelain"和"Plumbing"命令
开发人员工作流程中使用的大多数命令被称为"Porcelain瓷器命令"。 但是这些命令是通过称为"管道命令"的低级命令构造的。 让我们举个例子:
· Porcelain命令→git add,git commit,git push,git pull,git branch,git checkout,git tag,git merge,git rebase等…
· Plumbing命令→git cat-file,git has-object,git count-objects
了解哈希和SHA-1
即使大家都熟悉哈希,也需要总结一下才能使本文完整。 通过加密函数为某些输入生成哈希值。 Git使用SHA-1算法。
这个简单的函数返回哈希值,以git计算。 它在stdin输入上执行git hash-object。
echo 'aaa' | git hash-object --
stdin→ 72943a16fb2c8f38f9dde202b7a70ccc19c52f34
同样,哈希值包括:
"aaa" → 72943a16fb2c8f38f9dde202b7a70ccc19c52f34
"aab" → 6f27bcf7c99320f97e935dac870033e697bc5b11
"bbb" → f761ec192d9f0dca3329044b96ebdb12839dbff6
· 哈希值始终从任何输入生成40位十六进制数
· 相同的输入将始终生成相同的哈希值
· 输入的微小差异将完全改变哈希值
· 无法从哈希值识别输入,并且哈希函数不可逆
· 哈希值不是无限的,因为它限制为40位数字。 但是两个输入产生相同哈希的概率几乎为零,从而使哈希成为唯一的哈希。
Git目录
让我们使用git init初始化空的git仓库,您将看到在该位置创建了.git的隐藏目录。 让我们检查.git /:
branches/
config
description
HEAD
hooks/
info/
objects/
refs/
因为这些内部目录是刚刚启动的,所以大多数内部目录(例如branchs或objects /)将为空。 该.git将具有与git相关的文件,目录和其他信息的所有详细信息,稍后将进行讨论。
Git对象数据库
让我们检查一下哈希值如何存储在.git中的git数据库中。 要将其写入git数据库,我们可以使用更早的命令来计算哈希,但要使用-w。
echo 'aaa' | git hash-object --stdin -w
在这里,预期aaa以某种方式存储在git对象数据库中。
Git对象数据库实际上是持久化的键-值映射。 因此,对于任何元素,内容的哈希值将是关键,而值将是内容。
这里72943a16fb2c8f38f9dde202b7a70ccc19c52f34将是键,值将是aaa。 在编写此元素时,请检查将其放置在.git / objects中。 在哈希表72中将有一个包含前两个元素的目录,其中包含一个具有其余哈希值的文件:943a16fb2c8f38f9dde202b7a70ccc19c52f34。
user@ubuntu:~/git-internals/.git/objects/72$
ls 943a16fb2c8f38f9dde202b7a70ccc19c52f34
该文件的内容已加密,无法直接读取。 因此,有一个函数可用于从返回aaa的git数据库读取值。
git cat-file 72943a16fb2c8f38f9dde202b7a70ccc19c52f34 -p
这显示了如何从git objects数据库中写入和读取数据,以及如何将其组织为键-值映射,以及如何将其除以哈希值以提高性能。
Git对象
Git跟踪所有内容,并作为对象存储在git数据库中。 类型包括:
· Blob→将文件内容存储为原始二进制数据。
· Tree 树→已存储目录内容。
· Submit 提交→提交存储的内容。
· Tag 带注释的标签→已存储标签内容。
这些内容中的任何一个都可以用于计算哈希值,并且内容将存储在对象数据库中的哈希值中。 Git不会区分这些类型,而是将所有类型均等地存储在.git / objects中。 最好通过示例学习这些类型。
用于提交,文件和目录的Git对象
为了清楚地了解Git对象及其数据库,让我们创建一个文件并提交到git仓库中。
$ echo "aaa" >> a.txt
$ git add a.txt
$ git commit -m "Add a file"
通过git log标识最后一次提交的哈希值,然后从对象数据库中读取该值:git cat-file 239d1a0 -p。 (在大多数项目中,可使用7位数字标识git对象,称为短SHA)
tree 37057b2e8a9041ef88b805a5b7c4e0e668a03be4
author Thomas Shelby <user@gmail.com> 1588048341 +0530
committer Thomas Shelby <user@gmail.com> 1588048341 +0530
Add a file
用于提交的git对象包含作者,时间,消息和树。 这里的树值似乎是另一个哈希值,它表示提交时项目的状态。 让我们检查tree→git cat文件37057b2 -p的git对象。
100644 blob 72943a16fb2c8f38f9dde202b7a70ccc19c52f34 a.txt
这是指项目在基本目录中只有一个名为a.txt的blob(不是目录的文件)。 因此,可以从哈希值读取此文件的内容。
aaa
这将返回预期的a.txt内容。 在这里,通过commit我们可以通过从tree和blob对象导航来构造目录的全部内容。 因此,应该看到,从.git目录开始,所有信息都作为git对象从提交中构建工作目录。
在此初始提交之后,让我们检查git对象数据库:tree .git / objects
.git/objects/
├── 23
│ └── 9d1a0f75b596d7d67e23721f11066abf144982
├── 37
│ └── 057b2e8a9041ef88b805a5b7c4e0e668a03be4
├── 72
│ └── 943a16fb2c8f38f9dde202b7a70ccc19c52f34
├── info
└── pack
这具有引用提交,树和Blob git对象的所有3个对象。 git count-objects也可以用来标识git对象的数量。
添加顺序提交和嵌套目录/文件
它只有一个文件,没有目录。 让我们添加更多的Blob和树,以清楚地了解文件和目录在对象数据库中的存储方式。
$ mkdir files
$ echo "aaa" >> files/aaa.txt
$ echo "bbb" >> files/bbb.txt
$ git add .
$ git commit -m "Add files"
$ git log
故意为新文件添加了aaa内容。 让我们通过读取git对象→git cat文件5c45ebb -p来检查提交内容。
tree 21e6981939eae6277ad2128753e2984b552868cf
parent 239d1a0f75b596d7d67e23721f11066abf144982
重要的更改包括引用父提交的父属性。 然后观察由于文件和目录更改而树已更改。 让我们一步一步检查树和斑点的内容以构建文件和目录。
100644 blob 72943a16fb2c8f38f9dde202b7a70ccc19c52f34 a.txt
040000 tree 75d27669a0c4e9dd702c71c6ac3307d533493ba5 files
a.txt的Git对象与内容保持相同。 但是在父树对象中,存在另一棵树,该树表示其目录。 检查此嵌套树的内容时:git cat-file 75d2766 -p
100644 blob 72943a16fb2c8f38f9dde202b7a70ccc19c52f34 aaa.txt
100644 blob f761ec192d9f0dca3329044b96ebdb12839dbff6 bbb.txt
如果选中斑点,它将具有预期的内容。 请注意,这里的aaa.txt将具有与a.txt相同的对象,因为文件内容包含aaa。 但是,文件名更改并没有影响,因为它不是存储在blob本身中,而是存储在其包含的树中。
可以看出git如何在两次提交之间重用包含aaa的公共blob。 只有7个对象
.git/objects/
├── 21
│ └── e6981939eae6277ad2128753e2984b552868cf
├── 23
│ └── 9d1a0f75b596d7d67e23721f11066abf144982
├── 37
│ └── 057b2e8a9041ef88b805a5b7c4e0e668a03be4
├── 5c
│ └── 45ebb2052428ce037e2fbf760cb0ec9a18f6e2
├── 72
│ └── 943a16fb2c8f38f9dde202b7a70ccc19c52f34
├── 75
│ └── d27669a0c4e9dd702c71c6ac3307d533493ba5
├── f7
│ └── 61ec192d9f0dca3329044b96ebdb12839dbff6
从提交解释工作目录
当前有两个提交,用户可以检出这两个提交中的一个。 对于任何提交,其对文件和目录的观点可能有所不同。 对于提交,它将忽略连接的提交,并使用与其连接的树和Blob建立文件和目录。此处将显示两次提交的方式
编辑现有文件
先前的提交能够显示如何表示新添加的目录或文件。 在此提交中,它将显示文件编辑如何反映在git对象模型中:将根目录内容中的a.txt编辑为aab。
总结一下git对象,这些简单地引用如下:
· 树→表示目录。 包含包含树/斑点的文件结构
· Blob→表示文件。 包含加密的文件内容。
· 提交→表示提交。 包含作者和被引用的父树
· 标签→表示带注释的标签。 包含引用的提交。
git分支
默认情况下,git创建默认的master分支。 因此,此引用将使用.git存储在某个位置。 它实际上存储在.git / refs / heads / master中,而不是加密文件,因此可以读为cat .git / refs / heads / master。 这仅包含最后一次提交的哈希值。 因此,git分支只是对git commit hash值的引用。
让我们尝试添加分支并提交以检查引用的保留方式。
$ git checkout -b branch1 # create branch and switch to it$ cat .git/refs/heads/branch1 # refers to same commit as master$ nano a.txt # edit content of a.txt$ echo "ccc" >> c.txt # add new file of c.txt$ git add .$ git commit -m "branch1"
验证branch1引用已修改,并且现在正在引用新的提交。 当前分支存储在更少的.git / HEAD中,作为refs / heads / branch1。
让我们使用git checkout master签出回master分支,请注意,这将更改HEAD文件中的当前分支以及根据该提交构造文件和目录。 因此,请注意,删除了在新提交中添加的c.txt。
$ git checkout master # create branch and switch to it
$ nano a.txt # edit content of a.txt
$ git add .
$ git commit -m "master"
由于已经将其切换为master作为当前分支,因此不必在每次提交时都更新HEAD。 但是在每次提交时,对提交的master分支引用都会更新为新的提交。
用分支包起来:
· 分支→是提交的参考
· HEAD→是对分支的引用。 (如果将头部检出到提交的头部分离,则可以直接引用该提交。git checkout commit_id)
Git垃圾收集器
如图所示,git分支仅引用提交ID,因此可以更改git文件以进行提交而无需任何引用。 (注意:这是出于演示目的而进行的修改。)
cat .git/refs/heads/master > .git/refs/heads/branch1
这用主提交引用替换了branch1提交引用。
这里两个分支在3aefd7b中引用相同的提交,而在533503b中的提交未被分支,另一个提交或标记引用。
一段时间后,这些没有任何参考的git对象将被垃圾收集并删除。
正如"愚蠢的内容跟踪器"所建议的定义一样,可以看出git在项目的每个时间框架都跟踪文件和目录。 它具有在给定时间内完全构建工作目录的能力。 因此可以说git跟踪内容。
这是git如何在后台为最终用户管理高级命令的简要介绍。 这旨在阐明git如何管理文件,提交和分支。 正确理解git的工作原理也将有助于掌握高级git概念。
(本文翻译自Udara Bibile的文章《Understanding Git under the hood》,参考:https://medium.com/swlh/understanding-git-under-the-hood-b1aeae1d02f5)
- 上一篇: Git这些高级用法,喜欢就拿去用
- 下一篇: 8 个 Git 的小技巧 ,你知道几个?
猜你喜欢
- 2024-12-11 程序员必备的高效开发工具盘点与使用技巧
- 2024-12-11 每个开发人员都必须掌握的20个Git基本命令
- 2024-12-11 一起来用GIT吧!十分钟解读自动化测试中GIT的常见问题及解决方法
- 2024-12-11 使用 Git 命令去管理项目的版本控制(二)
- 2024-12-11 99%的时间里使用的14个git命令
- 2024-12-11 在游戏中学习git操作(六)
- 2024-12-11 一篇搞懂Git 和 SVN 的区别
- 2024-12-11 Git使用教程
- 2024-12-11 Git中常用指令原理解析
- 2024-12-11 一文读懂Git
- 最近发表
- 标签列表
-
- cmd/c (90)
- c++中::是什么意思 (84)
- 标签用于 (71)
- 主键只能有一个吗 (77)
- c#console.writeline不显示 (95)
- pythoncase语句 (88)
- es6includes (74)
- sqlset (76)
- apt-getinstall-y (100)
- node_modules怎么生成 (87)
- chromepost (71)
- flexdirection (73)
- c++int转char (80)
- mysqlany_value (79)
- static函数和普通函数 (84)
- el-date-picker开始日期早于结束日期 (76)
- js判断是否是json字符串 (75)
- c语言min函数头文件 (77)
- asynccallback (87)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- 无效的列索引 (74)