Git 工作原理浅析

1 How Git works?

git_architecture

Staging Area

在Git中,staging area(暂存区)是一个工作区间,用于准备下一次提交。当你在工作目录中修改了文件后,这些修改并不会立即记录到Git仓库中。相反,你需要先将这些修改添加到暂存区。

这个机制的好处是,它允许开发者在提交前精确地控制哪些修改(新增、修改、删除的文件)将被包含在提交中。这使得提交记录更清晰、更有目的。

暂存区的使用流程通常如下:

  1. 修改文件:在工作目录中对文件进行修改。
  2. 暂存修改:使用命令 git add <文件名> 将修改后的文件添加到暂存区。可以多次添加不同的文件,以便一次性提交多个文件的修改。
  3. 提交更改:使用命令 git commit 将暂存区中的更改提交到Git仓库。这会生成一个新的提交(commit)记录,表示项目的一个新版本。

暂存区提供了一个灵活的中间层,可以帮助开发者组织和审核即将进行的提交,确保每次提交都是有意义的。

2 TestGit[1] 实际仓库分析

2.1 提交记录

  • 提交记录图示
    TestGit_commit_history

2.2 TestGit项目本地目录

  • 项目本地目录图示
    git_directory

2.3 Git 中的 origin

origin 在 Git 中通常是远程仓库的默认名称。当你克隆一个仓库时,Git 自动将这个远程仓库命名为 origin。这是一个约定俗成的名字,表示原始或源仓库。虽然 origin 不是强制的命名,但它是最常用的。在添加远程仓库时可以使用其他名字,但 origin 是默认和最常见的选择。

例如,命令 git push origin main 表示将本地的 main 分支的改动推送到名为 origin 的远程仓库的 main 分支上。在本项目中,origin 代表 GitHub 上的 TestGit 项目。

2.4 .git/objects 目录详细介绍

2.4.1 松散对象 (loose objects)

.git/objects 目录中,除了 pack 文件(packidx 文件)之外,其他文件通常被称为松散对象。这些是 Git 仓库中存储的最基本形式的对象,每个对象对应一个文件。

特点

  1. 单一对象存储:每个松散对象文件包含一个 Git 对象,如提交(commit)、树(tree)或数据块(blob)。
  2. 命名方式:松散对象的文件名是基于对象内容的 SHA-1 哈希。

    例如,如果一个对象的哈希是 de9f2c7fd25e1b3afad3e85a0bd17d9b100db4b3,它会被存储在 .git/objects/de/9f2c7fd25e1b3afad3e85a0bd17d9b100db4b3。

  3. 压缩存储:这些对象文件是压缩存储的,使用 zlib 压缩算法。

创建和转换

松散对象通常在对象初次创建时生成,随着时间的推移和操作的积累,Git 可能会自动将它们打包到 pack 文件中以优化存储和提升性能。

优点和缺点

  • 优点:简单易于管理,每个对象独立存储,便于直接访问和修改。
  • 缺点:随着对象数量增加,松散对象的管理和存储效率降低。

对象类型

  1. Commit 对象:保存特定提交的信息,包括作者、提交者、提交信息和指向树对象的指针。

  2. Tree 对象:代表一次提交时的快照,包含指向其他树对象或 blob 对象的指针。

  3. Blob 对象:存储文件的实际内容。

    在计算机科学中,"blob" 是 "Binary Large Object" 的缩写,指的是存储在数据库或文件系统中的一段大的二进制数据。BLOB 类型的文件通常用于存储多媒体数据,如图像、视频、音频文件,以及其他需要存储为二进制形式的复杂数据。
    BLOB 并不是特定的文件格式,而是一种数据类型或数据存储方式。

相关命令

1
2
3
4
5
# 查看对象类型
git cat-file -t <sha-1>

# 查看对象内容
git cat-file -p <sha-1>

2.4.2 Pack 文件

Pack 文件是一种存储机制,用于优化大仓库的存储空间。它包含许多对象的集合,通常在执行 Git 的垃圾回收命令 git gc 时生成。

  • 每个 pack 文件都有一个 .pack 文件和一个索引文件 .idx

相关命令

1
2
# 显示 pack 文件的内容
git verify-pack -v .git/objects/pack/pack-*.idx

2.4.3 完整目录及解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
├── 23
│ └── e1fbf7017ac308a1acc0db5f4797f404f1b25b
├── 2f
│ └── 40ce79af9c0f4d5156bf10c683f8c20e2b41a9
├── 68
│ └── d2491744e247bd77ac0bc349dce38529b30593
├── 91
│ └── d32d52966303a0b7a5864b37d27f1f201cea68
├── f8
│ └── 5b16aa4ca04a8aa9a9a7869ff06e460f172ee9
├── fb
│ └── 8842d6a9421ffef675728bd1ef4845e7a2b977
├── info
└── pack
├── pack-dece91b354fed0a17c55af86a1b11b4d6229752a.idx
└── pack-dece91b354fed0a17c55af86a1b11b4d6229752a.pack

松散对象解析

哈希值 对象类型 对象内容
23e1 commit tree 68d2491744e247bd77ac0bc349dce38529b30593
parent 2f40ce79af9c0f4d5156bf10c683f8c20e2b41a9
author hwollin 2457935544@qq.com 1715333026 +0800
committer hwollin 2457935544@qq.com 1715333026 +0800

add c
2f40 commit tree fb8842d6a9421ffef675728bd1ef4845e7a2b977
parent 1f8d4ae8f80dee3cb51555b8f4c0cdd61ec62765
author hwollin 2457935544@qq.com 1715332991 +0800
committer hwollin 2457935544@qq.com 1715332991 +0800

modify a
68d2 tree 100644 blob c6127b38c1aa25968a88db3940604d41529e4cf5 .gitignore
100644 blob 8741d0c6a3b5c9d8bb35352e176c29f76264c533 README.md
100644 blob f85b16aa4ca04a8aa9a9a7869ff06e460f172ee9 a
100644 blob 91d32d52966303a0b7a5864b37d27f1f201cea68 c

——————————————————————————————————–

解释:
100644:这是文件模式,表示文件的权限。对于 blob(文件)来说,100644 表示这是一个普通文件,可读可写。
91d3 blob I am ccc
f85b blob I am aaa, modified from branch hwoll
fb88 tree 100644 blob c6127b38c1aa25968a88db3940604d41529e4cf5 .gitignore
100644 blob 8741d0c6a3b5c9d8bb35352e176c29f76264c533 README.md
100644 blob f85b16aa4ca04a8aa9a9a7869ff06e460f172ee9 a

pack 文件解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Hw-MacBook-Pro:TestGit hw$ git verify-pack -v .git/objects/pack/pack-dece91b354fed0a17c55af86a1b11b4d6229752a.idx
344ed794cd78c2909c0fe3ead020ad9e81fcc27c commit 208 143 12
1f8d4ae8f80dee3cb51555b8f4c0cdd61ec62765 commit 208 142 155
4e927065d31a21494d8c9ad3332b0ebc4bc6914d commit 1018 787 297
2adbc303e52c7e48d4cd64c7f9ac81638c3e8584 tree 104 108 1084
7a558fe8ade4ceed19e03ba02273d2d846b0ead4 tree 35 47 1192 1 2adbc303e52c7e48d4cd64c7f9ac81638c3e8584
c6127b38c1aa25968a88db3940604d41529e4cf5 blob 430 285 1239
8741d0c6a3b5c9d8bb35352e176c29f76264c533 blob 32 41 1524
94d3705f95c4c221da0076319e19c54b0c443713 blob 9 18 1565
240dba691157e1bf28266a24dc7bc87c8069b93b blob 9 18 1583

# a76802aeb5a869f7810cf86bab5833f9f87bee62 是当前对象的 SHA-1 哈希值。
# 类型是 tree
# 压缩前大小为 4 字节,压缩后大小为 15 字节。(因为文件太小,导致压缩后反而变大了)
# 数据在 pack 文件中的偏移是 1601。
# 数字 1 表示这是一个增量对象,且链的长度为 1。
# 2adbc303e52c7e48d4cd64c7f9ac81638c3e8584 是它的基础对象(即 delta base)。
a76802aeb5a869f7810cf86bab5833f9f87bee62 tree 4 15 1601 1 2adbc303e52c7e48d4cd64c7f9ac81638c3e8584

non delta: 8 objects
chain length = 1: 2 objects
.git/objects/pack/pack-dece91b354fed0a17c55af86a1b11b4d6229752a.pack: ok

non delta: 8 objects
这行表明在 pack 文件中,有 8 个对象是以非增量(non-delta)形式存储的。在 Git 中,对象可以存储为完整对象或增量(delta)对象。此处的“non delta”指的是那些存储为完整数据的对象。

chain length = 1: 2 objects
这行指的是存在两个对象,它们形成了长度为 1 的增量链。在 Git 的 pack 文件中,增量对象通过指向另一个对象来存储数据的差异。这里的“链长度”是指在恢复(reconstruct)对象时需要遍历的增量对象的数量。长度为 1 的链意味着每个增量对象直接引用了一个完整对象。

完整目录分析

TestGit_Objects

2.5 git merge

在这个项目中,main 是主分支,hwoll 是工作分支。以下是将 hwoll 分支合并到 main 分支的步骤:

步骤 1: 切换到主分支 main

1
git checkout main

步骤 2: 更新主分支

1
git pull

步骤 3: 合并 hwoll 分支到 main

1
git merge hwoll

步骤 4: 解决可能的合并冲突

如果出现冲突,解决冲突后添加解决后的文件到暂存区:

1
git add <解决冲突的文件名>

然后完成合并:

1
git commit

步骤 5: 推送更改到远程仓库

最后,将更新后的 main 分支推送到远程仓库:

1
git push

这些步骤将确保 hwoll 分支成功合并到 main 分支,并且所有更改都被正确同步到远程仓库。

  • 合并后的提交和快照图示
    TestGit_commit_merge
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 可以看到 merge commit 有两个 parent
Hw-MacBook-Pro:TestGit hw$ git cat-file -p 98fa
tree c76a4d30938dff856dae1dc8bdb9f6f92c6f39e5
parent 344ed794cd78c2909c0fe3ead020ad9e81fcc27c
parent 23e1fbf7017ac308a1acc0db5f4797f404f1b25b
author hwollin <2457935544@qq.com> 1716044081 +0800
committer hwollin <2457935544@qq.com> 1716044081 +0800

Merge branch 'hwoll'

# 查看 merge commit 快照
Hw-MacBook-Pro:TestGit hw$ git cat-file -p c76a
100644 blob c6127b38c1aa25968a88db3940604d41529e4cf5 .gitignore
100644 blob 8741d0c6a3b5c9d8bb35352e176c29f76264c533 README.md
100644 blob f85b16aa4ca04a8aa9a9a7869ff06e460f172ee9 a
100644 blob 240dba691157e1bf28266a24dc7bc87c8069b93b b
100644 blob 91d32d52966303a0b7a5864b37d27f1f201cea68 c

2.6 回退到上次提交

假设最近一次提交是 7eb3 ,再往前一次是 b4d1 ,现在远程仓库和本地仓库 main 分支均处于 7eb3 ,我如何将远程和本地同时回退到 b4d1 ?
以下是具体步骤:

步骤 1: 切换到 main 分支

首先,确保你在 main 分支上。使用以下命令切换到 main 分支:

1
git checkout main

步骤 2: 回退本地仓库到 b4d1

使用 git reset 命令将本地仓库回退到指定的提交 b4d1

1
git reset --hard b4d1

这个命令会将本地的 main 分支回退到提交 b4d1,并且丢弃所有在此之后的提交和更改。

步骤 3: 强制推送回退后的提交到远程仓库

使用 git push 命令将本地的变更强制推送到远程仓库,使远程仓库的 main 分支同步回退到 b4d1

1
git push origin main --force

步骤 4: 检查回退结果

确保回退操作成功,可以使用以下命令查看当前分支的提交历史,确认 main 分支已经回退到 b4d1

1
git log

执行上述步骤后,远程和本地仓库的 main 分支将会回退到提交 b4d1,并且所有在 b4d1 之后的提交将被移除。

注意:使用 --force 选项会覆盖远程仓库的历史,因此在执行这一步之前,确保你已经告知团队成员并获得他们的同意,以避免不必要的数据丢失或混乱。

3 引用


Git 工作原理浅析
https://hwollin.github.io/2024/05/20/git/
作者
Wei Han
发布于
2024年5月20日
许可协议