Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >10.2 Git 内部原理 - Git 对象

10.2 Git 内部原理 - Git 对象

作者头像
shaonbean
发布于 2019-05-26 03:19:35
发布于 2019-05-26 03:19:35
77900
代码可运行
举报
文章被收录于专栏:运维前线运维前线
运行总次数:0
代码可运行

Git 对象

Git 是一个内容寻址文件系统。 看起来很酷, 但这是什么意思呢? 这意味着,Git 的核心部分是一个简单的键值对数据库(key-value data store)。 你可以向该数据库插入任意类型的内容,它会返回一个键值,通过该键值可以在任意时刻再次检索(retrieve)该内容。 可以通过底层命令 hash-object 来演示上述效果——该命令可将任意数据保存于 .git 目录,并返回相应的键值。 首先,我们需要初始化一个新的 Git 版本库,并确认 objects 目录为空:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ git init test
Initialized empty Git repository in /tmp/test/.git/
$ cd test
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack
$ find .git/objects -type f

可以看到 Git 对 objects 目录进行了初始化,并创建了 packinfo 子目录,但均为空。 接着,往 Git 数据库存入一些文本:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4

-w 选项指示 hash-object 命令存储数据对象;若不指定此选项,则该命令仅返回对应的键值。 --stdin 选项则指示该命令从标准输入读取内容;若不指定此选项,则须在命令尾部给出待存储文件的路径。 该命令输出一个长度为 40 个字符的校验和。 这是一个 SHA-1 哈希值——一个将待存储的数据外加一个头部信息(header)一起做 SHA-1 校验运算而得的校验和。后文会简要讨论该头部信息。 现在我们可以查看 Git 是如何存储数据的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ find .git/objects -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

可以在 objects 目录下看到一个文件。 这就是开始时 Git 存储内容的方式——一个文件对应一条内容,以该内容加上特定头部信息一起的 SHA-1 校验和为文件命名。 校验和的前两个字符用于命名子目录,余下的 38 个字符则用作文件名。

可以通过 cat-file 命令从 Git 那里取回数据。 这个命令简直就是一把剖析 Git 对象的瑞士军刀。 为cat-file 指定 -p 选项可指示该命令自动判断内容的类型,并为我们显示格式友好的内容:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content

至此,你已经掌握了如何向 Git 中存入内容,以及如何将它们取出。 我们同样可以将这些操作应用于文件中的内容。 例如,可以对一个文件进行简单的版本控制。 首先,创建一个新文件并将其内容存入数据库:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ echo 'version 1' > test.txt
$ git hash-object -w test.txt
83baae61804e65cc73a7201a7252750c76066a30

接着,向文件里写入新内容,并再次将其存入数据库:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ echo 'version 2' > test.txt
$ git hash-object -w test.txt
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a

数据库记录下了该文件的两个不同版本,当然之前我们存入的第一条内容也还在:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ find .git/objects -type f
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

现在可以把文件内容恢复到第一个版本:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt
$ cat test.txt
version 1

或者第二个版本:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a > test.txt
$ cat test.txt
version 2

然而,记住文件的每一个版本所对应的 SHA-1 值并不现实;另一个问题是,在这个(简单的版本控制)系统中,文件名并没有被保存——我们仅保存了文件的内容。 上述类型的对象我们称之为数据对象(blob object)。 利用 cat-file -t 命令,可以让 Git 告诉我们其内部存储的任何对象类型,只要给定该对象的 SHA-1 值:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
blob

树对象

接下来要探讨的对象类型是树对象(tree object),它能解决文件名保存的问题,也允许我们将多个文件组织到一起。 Git 以一种类似于 UNIX 文件系统的方式存储内容,但作了些许简化。 所有内容均以树对象和数据对象的形式存储,其中树对象对应了 UNIX 中的目录项,数据对象则大致上对应了 inodes 或文件内容。 一个树对象包含了一条或多条树对象记录(tree entry),每条记录含有一个指向数据对象或者子树对象的 SHA-1 指针,以及相应的模式、类型、文件名信息。 例如,某项目当前对应的最新树对象可能是这样的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ git cat-file -p master^{tree}
100644 blob a906cb2a4a904a152e80877d4088654daad0c859      README
100644 blob 8f94139338f9404f26296befa88755fc2598c289      Rakefile
040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0      lib

master^{tree} 语法表示 master 分支上最新的提交所指向的树对象。 请注意,lib 子目录(所对应的那条树对象记录)并不是一个数据对象,而是一个指针,其指向的是另一个树对象:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ git cat-file -p 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0
100644 blob 47c6340d6459e05787f644c2447d2595f5d3a54b      simplegit.rb

从概念上讲,Git 内部存储的数据有点像这样:

Figure 10-1. 简化版的 Git 数据模型。

你可以轻松创建自己的树对象。 通常,Git 根据某一时刻暂存区(即 index 区域,下同)所表示的状态创建并记录一个对应的树对象,如此重复便可依次记录(某个时间段内)一系列的树对象。 因此,为创建一个树对象,首先需要通过暂存一些文件来创建一个暂存区。 可以通过底层命令 update-index 为一个单独文件——我们的 test.txt 文件的首个版本——创建一个暂存区。 利用该命令,可以把 test.txt 文件的首个版本人为地加入一个新的暂存区。 必须为上述命令指定 --add 选项,因为此前该文件并不在暂存区中(我们甚至都还没来得及创建一个暂存区呢);同样必需的还有 --cacheinfo 选项,因为将要添加的文件位于 Git 数据库中,而不是位于当前目录下。 同时,需要指定文件模式、SHA-1 与文件名:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ git update-index --add --cacheinfo 100644 \
  83baae61804e65cc73a7201a7252750c76066a30 test.txt

本例中,我们指定的文件模式为 100644,表明这是一个普通文件。 其他选择包括:100755,表示一个可执行文件;120000,表示一个符号链接。 这里的文件模式参考了常见的 UNIX 文件模式,但远没那么灵活——上述三种模式即是 Git 文件(即数据对象)的所有合法模式(当然,还有其他一些模式,但用于目录项和子模块)。

现在,可以通过 write-tree 命令将暂存区内容写入一个树对象。 此处无需指定 -w 选项——如果某个树对象此前并不存在的话,当调用 write-tree 命令时,它会根据当前暂存区状态自动创建一个新的树对象:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ git write-tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
100644 blob 83baae61804e65cc73a7201a7252750c76066a30      test.txt

不妨验证一下它确实是一个树对象:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
tree

接着我们来创建一个新的树对象,它包括 test.txt 文件的第二个版本,以及一个新的文件:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ echo 'new file' > new.txt
$ git update-index test.txt
$ git update-index --add new.txt

暂存区现在包含了 test.txt 文件的新版本,和一个新文件:new.txt。 记录下这个目录树(将当前暂存区的状态记录为一个树对象),然后观察它的结构:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ git write-tree
0155eb4229851634a0f03eb265b69f5a2d56f341
$ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
100644 blob fa49b077972391ad58037050f2a75f74e3671e92      new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a      test.txt

我们注意到,新的树对象包含两条文件记录,同时 test.txt 的 SHA-1 值(1f7a7a)是先前值的“第二版”。 只是为了好玩:你可以将第一个树对象加入第二个树对象,使其成为新的树对象的一个子目录。 通过调用 read-tree 命令,可以把树对象读入暂存区。 本例中,可以通过对 read-tree 指定 --prefix 选项,将一个已有的树对象作为子树读入暂存区:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git write-tree
3c4e9cd789d88d8d89c1073707c3585e41b0e614
$ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614
040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579      bak
100644 blob fa49b077972391ad58037050f2a75f74e3671e92      new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a      test.txt

如果基于这个新的树对象创建一个工作目录,你会发现工作目录的根目录包含两个文件以及一个名为 bak的子目录,该子目录包含 test.txt 文件的第一个版本。 可以认为 Git 内部存储着的用于表示上述结构的数据是这样的:

Figure 10-2. 当前 Git 的数据内容结构。

提交对象

现在有三个树对象,分别代表了我们想要跟踪的不同项目快照。然而问题依旧:若想重用这些快照,你必须记住所有三个 SHA-1 哈希值。 并且,你也完全不知道是谁保存了这些快照,在什么时刻保存的,以及为什么保存这些快照。 而以上这些,正是提交对象(commit object)能为你保存的基本信息。

可以通过调用 commit-tree 命令创建一个提交对象,为此需要指定一个树对象的 SHA-1 值,以及该提交的父提交对象(如果有的话)。 我们从之前创建的第一个树对象开始:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ echo 'first commit' | git commit-tree d8329f
fdf4fc3344e67ab068f836878b6c4951e3b15f3d

现在可以通过 cat-file 命令查看这个新提交对象:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ git cat-file -p fdf4fc3
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author Scott Chacon <schacon@gmail.com> 1243040974 -0700
committer Scott Chacon <schacon@gmail.com> 1243040974 -0700

first commit

提交对象的格式很简单:它先指定一个顶层树对象,代表当前项目快照;然后是作者/提交者信息(依据你的 user.nameuser.email 配置来设定,外加一个时间戳);留空一行,最后是提交注释。

接着,我们将创建另两个提交对象,它们分别引用各自的上一个提交(作为其父提交对象):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ echo 'second commit' | git commit-tree 0155eb -p fdf4fc3
cac0cab538b970a37ea1e769cbbde608743bc96d
$ echo 'third commit'  | git commit-tree 3c4e9c -p cac0cab
1a410efbd13591db07496601ebc7a059dd55cfe9

这三个提交对象分别指向之前创建的三个树对象快照中的一个。 现在,如果对最后一个提交的 SHA-1 值运行 git log 命令,会出乎意料的发现,你已有一个货真价实的、可由 git log 查看的 Git 提交历史了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ git log --stat 1a410e
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri May 22 18:15:24 2009 -0700

	third commit

 bak/test.txt | 1 +
 1 file changed, 1 insertion(+)

commit cac0cab538b970a37ea1e769cbbde608743bc96d
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri May 22 18:14:29 2009 -0700

	second commit

 new.txt  | 1 +
 test.txt | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri May 22 18:09:34 2009 -0700

    first commit

 test.txt | 1 +
 1 file changed, 1 insertion(+)

太神奇了: 就在刚才,你没有借助任何上层命令,仅凭几个底层操作便完成了一个 Git 提交历史的创建。 这就是每次我们运行 git addgit commit 命令时, Git 所做的实质工作——将被改写的文件保存为数据对象,更新暂存区,记录树对象,最后创建一个指明了顶层树对象和父提交的提交对象。 这三种主要的 Git 对象——数据对象、树对象、提交对象——最初均以单独文件的形式保存在.git/objects 目录下。 下面列出了目前示例目录内的所有对象,辅以各自所保存内容的注释:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ find .git/objects -type f
.git/objects/01/55eb4229851634a0f03eb265b69f5a2d56f341 # tree 2
.git/objects/1a/410efbd13591db07496601ebc7a059dd55cfe9 # commit 3
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a # test.txt v2
.git/objects/3c/4e9cd789d88d8d89c1073707c3585e41b0e614 # tree 3
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30 # test.txt v1
.git/objects/ca/c0cab538b970a37ea1e769cbbde608743bc96d # commit 2
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 # 'test content'
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 # tree 1
.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 # new.txt
.git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit 1

如果跟踪所有的内部指针,将得到一个类似下面的对象关系图:

Figure 10-3. 你的 Git 目录下的所有对象。

对象存储

前文曾提及,在存储内容时,会有个头部信息一并被保存。 让我们略花些时间来看看 Git 是如何存储其对象的。 通过在 Ruby 脚本语言中交互式地演示,你将看到一个数据对象——本例中是字符串“what is up, doc?”——是如何被存储的。

可以通过 irb 命令启动 Ruby 的交互模式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ irb
>> content = "what is up, doc?"
=> "what is up, doc?"

Git 以对象类型作为开头来构造一个头部信息,本例中是一个“blob”字符串。 接着 Git 会添加一个空格,随后是数据内容的长度,最后是一个空字节(null byte):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
>> header = "blob #{content.length}\0"
=> "blob 16\u0000"

Git 会将上述头部信息和原始数据拼接起来,并计算出这条新内容的 SHA-1 校验和。 在 Ruby 中可以这样计算 SHA-1 值——先通过 require 命令导入 SHA-1 digest 库,然后对目标字符串调用Digest::SHA1.hexdigest()

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
>> store = header + content
=> "blob 16\u0000what is up, doc?"
>> require 'digest/sha1'
=> true
>> sha1 = Digest::SHA1.hexdigest(store)
=> "bd9dbf5aae1a3862dd1526723246b20206e5fc37"

Git 会通过 zlib 压缩这条新内容。在 Ruby 中可以借助 zlib 库做到这一点。 先导入相应的库,然后对目标内容调用 Zlib::Deflate.deflate()

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
>> require 'zlib'
=> true
>> zlib_content = Zlib::Deflate.deflate(store)
=> "x\x9CK\xCA\xC9OR04c(\xCFH,Q\xC8,V(-\xD0QH\xC9O\xB6\a\x00_\x1C\a\x9D"

最后,需要将这条经由 zlib 压缩的内容写入磁盘上的某个对象。 要先确定待写入对象的路径(SHA-1 值的前两个字符作为子目录名称,后 38 个字符则作为子目录内文件的名称)。 如果该子目录不存在,可以通过 Ruby 中的 FileUtils.mkdir_p() 函数来创建它。 接着,通过 File.open() 打开这个文件。最后,对上一步中得到的文件句柄调用 write() 函数,以向目标文件写入之前那条 zlib 压缩过的内容:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
>> path = '.git/objects/' + sha1[0,2] + '/' + sha1[2,38]
=> ".git/objects/bd/9dbf5aae1a3862dd1526723246b20206e5fc37"
>> require 'fileutils'
=> true
>> FileUtils.mkdir_p(File.dirname(path))
=> ".git/objects/bd"
>> File.open(path, 'w') { |f| f.write zlib_content }
=> 32

就是这样——你已创建了一个有效的 Git 数据对象。 所有的 Git 对象均以这种方式存储,区别仅在于类型标识——另两种对象类型的头部信息以字符串“commit”或“tree”开头,而不是“blob”。 另外,虽然数据对象的内容几乎可以是任何东西,但提交对象和树对象的内容却有各自固定的格式。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2016年10月10日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
git底层原理,从常见操作解释git的底层原理,再也不怯
每个开发者拥有自己仓库的写权限和其他所有人仓库的读权限。这种情形下通常会有个代表“官方”项目的权威的仓库。
公众号 IT老哥
2021/01/12
2.8K0
Git内部原理介绍
git 是一个内容寻址的文件系统,其核心部分是一个简单的键值对数据库(key-value data store),可以向该数据库插入任意类型的内容,它会返回一个40位长的哈希键值。并在此基础上提供了一个版本控制系统的用户界面。
腾讯工蜂
2018/12/07
17.1K1
Git 内部原理之 Git 对象哈希
在上一篇文章中,将了数据对象、树对象和提交对象三种Git对象,每种对象会计算出一个hash值。那么,Git是如何计算出Git对象的hash值?本文的内容就是来解答这个问题。
用户1257393
2018/07/30
1.4K0
洞悉技术的本质-Git内部原理探索
洞悉技术的本质,可以让我们在层出不穷的框架面前仍能泰然处之。用了那么久的 Git,不懂点内部原理,那可不行!懂点原理可以让我们遇到问题的时候能够更好更快的理清解决问题的思路。
Cloud-Cloudys
2020/07/06
8270
Git是如何保存文件名和目录关系的---树对象
下面我们就来模拟一下构建上面这颗树,也就是模拟保存这3个文件,其中的"bak"是一个目录,下面有一个文件
AntDream
2019/07/15
1.3K0
Git是如何保存文件名和目录关系的---树对象
10.4 Git 内部原理 - 包文件
让我们重新回到示例 Git 版本库的对象数据库。 目前为止,可以看到有 11 个对象——4 个数据对象、3 个树对象、3 个提交对象和 1 个标签对象:
shaonbean
2019/05/26
4700
这才是真正的Git——Git内部原理揭秘!
本文作者:lzaneli,腾讯 TEG 前端开发工程师 本文以一个具体例子结合动图介绍了Git的内部原理,包括Git是什么储存我们的代码和变更历史的、更改一个文件时,Git内部是怎么变化的、Git这样实现的有什么好处等等。 通过例子解释清楚上面这张动图,让大家了解Git的内部原理。如果你已经能够看懂这张图了,下面的内容可能对你来说会比较基础。 视频链接: https://www.bilibili.com/video/av77252063 PPT 链接: https://www.lzane.com/
腾讯技术工程官方号
2019/12/18
1.5K0
这才是真正的Git——Git内部原理揭秘!
Git 原理入门
即使天天使用它,很多人也未必了解它的原理。Git 为什么可以管理版本?git add、git commit这些基本命令,到底在做什么,你说得清楚吗?
ruanyf
2018/10/11
7260
Git 原理入门
用了5年的Git,你竟然还不晓得它的实现原理!
作者 | 杨夕 来源 | https://zhuanlan.zhihu.com/p/53750883 越了解事物的本质就越接近真相。我发现学习Git内部是如何工作的以及Git的内部数据结构这部分内容,对于理解Git的用途和强大至关重要。若你理解了Git的思想和基本工作原理,用起来就会知其所以然,游刃有余。这是Git系列的第一篇,主要会介绍Git的特点以及内部数据结构设计,和完成一次完整提交流程的时候数据是如何变化的。 Git有什么特点? fast,scalable,distributed revision
程序猿DD
2023/04/04
3080
用了5年的Git,你竟然还不晓得它的实现原理!
浅谈Git的内部原理
首先提前预祝各位同学元旦快乐,接下来进入正题。身为程序员的大家都知道,做技术一定知其然知其所以然,很多人都会用Git,但却对Git的原理却不是很了解,接下来带领大家简单的探秘下Git的原理。
用户5521279
2020/01/22
5380
git commit时到底发生了什么?
这个步骤是创建了一个提交对象,提交对象里面就记录了提交的时间、作者、以及提交的原因等信息。
AntDream
2019/07/15
2.1K0
git commit时到底发生了什么?
从一个实战的角度来理解 Git 原理~
我之前写了一篇文章 《深入理解 Git 》https://zhuanlan.zhihu.com/p/71577255。
DevOps时代
2020/11/03
6020
从一个实战的角度来理解 Git 原理~
通俗易懂的 Git 教程:核心概念
Git 是目前世界上最优秀的分布式版本控制系统。版本控制系统是能够随着时间的推进记录一系列文件的变化以便于你以后想要的退回到某个版本的系统。版本控制系统分为三大类:本地版本控制系统,集中式版本控制系统和分布式版本控制系统
用户4962466
2019/10/17
5440
这才是真正的Git——Git内部原理
linux 之父 Linus Torvalds 大家应该都知道,而 git 也是由 Linus 开发的。从 1991 年发布了第一版的 linux 内核,Linux 内核开源项目有着众多的参与者,但绝大多数的 Linux 内核维护工作都花在了提交补丁和保存归档的繁琐事务上(1991-2002年间)。到 2002 年,整个项目组开始启用一个专有的分布式版本控制系统 BitKeeper 来管理和维护代码,之前市面上也有其他的版本管理系统,比如 CSV、SVN,但是 Linus 觉得它们很蠢,直到有了 BitKeeper 才开始使用版本管理系统。
前端森林
2021/06/25
3K0
这才是真正的Git——Git内部原理
Git - 使用git不知道内部实现机制怎么行
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
洋仔聊编程
2019/09/25
4310
Git - 使用git不知道内部实现机制怎么行
Git Pro深入浅出(三)
前面已经阐述了Git基本的运作机制和使用方式,介绍了许多Git提供的工具来帮助你简单且有效地使用它。本部分将演示如何借助Git的一些重要的配置方法和钩子机制,来满足自定义的需求。
奋飛
2019/08/15
9130
Git的存储原理
Git 出现前,主流版本控制系统(SVN...)一般为基于增量(delta-based)的系统,如下图:
小江的学习日记
2024/07/27
1310
一篇搞懂Git 和 SVN 的区别【原理篇】
Git和SVN都是版本管理系统,但是他们 命令区别后面会简单进行一个对比,我们先从原理的角度分析
火狼1
2022/03/22
3.5K0
一篇搞懂Git 和 SVN 的区别【原理篇】
.git 目录的内部结构
本篇不是讲git的使用,也不是讲git的源码,但阅读本篇有助于理解git的底层逻辑设计。本篇讲的是一个文件夹,具体就是git分布式仓库中的本地仓库的隐藏文件夹.git。
后端云
2023/02/10
7750
.git 目录的内部结构
Git的核心概念
文章内容来自自己的理解 和 https://git-scm.com/book/en/v2 。
Java后端技术
2018/08/09
4690
Git的核心概念
相关推荐
git底层原理,从常见操作解释git的底层原理,再也不怯
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档