git原理简介
原文链接:https://mqcreaple.github.io/blog/2022/03/04/git.html,欢迎大家关注
相信大家都对git耳熟能详了。Git是一个简单易用的版本管理工具,用户可以在git中修改文件、提交commit(更新到本地仓库)、同步远程仓库等。本篇文章将主要讨论git更新文件和提交commit背后的原理。
前置知识:git基本操作,详见runoob和w3schools。
一些名词
- 工作区(working tree) 表示除去
/.git以外的工作目录,即通常写代码的位置。 - 暂存区/索引(index) 是执行完
git add指令时文件被添加到的地方。如果没有执行过git add命令,git并不会为你自动保存。 - 版本库(repository) 是所有当前暂存区和历史上commit过的文件,暂存区可以看作是版本库的一个子集。所有版本库的文件都保存在
/.git目录下。
所有的历史文件、文件目录、commmit记录等全部保存为二进制对象,统一保存在/.git/objects目录下,文件名为该文件的SHA-1哈希值且没有后缀。
- blob:全称为Binary Large Object,是常规文件保存在
/.git/objects下的形式。 - tree:目录文件保存在
/.git/objects下的形式。 - commit:commit记录保存在
/.git/objects下的形式。
└── objects
├── 41
│ └── a1d4060cf09286c1cd8fe8bdab89ce26b71086
├── ce
│ └── 013625030ba8dba906f756967f9e9ca394464a
├── dc
│ └── a98923d43cd634f4359f8a1f897bf585100cfe
├── info
└── pack
上图的文件目录中,三个object的SHA-1值分别为:41a1d4...,ce0136...,和dca989...。
可以使用指令:
git hash-object 文件名
计算一个文件的SHA-1哈希值。
HEAD指针和commit
HEAD是一个指针,默认指向当前分支的最新一个commit(存储了commit文件的哈希值)。每一次提交新的commit时,HEAD也会相应前移。HEAD存储在/.git/HEAD文件中。
每一个分支也有各自的head,指向当前分支的最后一个commit,各分支的head存储在/.git/refs/heads/分支名文件中。每一次执行git checkout切换分支的时候,实际上就是让全局的HEAD赋值成了另一个分支的head。
总结一下,现在讲过的/.git目录结构都有:
.git
├── HEAD 全局HEAD指针
├── objects
│ └── 二进制对象都在这里
└── refs
└── heads
└── 各个分支的head
除了第一个commit,以后的每个commit都会记录上一个commit的哈希值,这样就形成了一个树形结构。
当整个项目只有一个分支时,所有的commit形成一条链:
有多个分支时,则是这样的:
文件和目录
之前说过,blob对象和tree对象都存在.git/objects/目录下。Tree对象存储了其他一系列文件的哈希值,可以理解成是一个多叉树的结点,而一般文件是根节点。
暂存区/索引对应着/.git/index文件,它记录了当前暂存的所有文件和目录的哈希值。每一次执行git add指令,程序就会在/.git/objects目录中生成一个对应着该文件的blob对象,同时将这个对象的地址加到/.git/index中。
每一个commit结点指向了一个tree节点,表示某一次commit的根目录。两次commit中不变文件不会被创建新对象。
这时,如果我们添加了一个.gitignore文件并且执行git add .gitignore命令,就会创建一个新的blob对象并添加进索引。
再接下来提交commit,程序就会新建一个表示根目录的tree对象并且指向所有索引(index)中的文件和文件夹。最后再处理commit结点,将其指向上一次的commit,即完成了git提交,如图:
总结
Git的所有对象,包括文件、目录、和commit,全部存储在
/.git/object/文件夹下。Commit结点指向其上一次的commit,形成一个树形结构,每个叶节点对应一个分支的head。
全局的
HEAD指针指向任意一个commit结点,通常是一个特定分支的head。blob对应一般的文件,tree对应文件夹,同一个文件夹下所有文件和文件夹组成一个树形结构,但同一个文件有可能被不同版本的文件夹同时指向。
每次add文件时,git会生成一个新的blob对象并添加到index中。
每次进行commit时,git会生成tree对象并令其指向所有的子文件,最后让commit结点指向根目录文件夹,同时设置commit结点的上一个结点,最后完成提交。
Git的版本管理逻辑和信息竞赛里的“可持久化算法”思想很像,都是尽量避免记录过多重复的内容从而减少空间占用。
Git在文件和目录这个树形结构之上,还有一个“commit”的分支结构,这意味着它可以应对更复杂的需求,但也意味着使用者需要记忆更多的命令。
本文仅是一个简单介绍,没有涵盖诸如分支合并和tag等更复杂的功能,如果以后有时间可以更新。
参考资料
[1] Chacon, Scott and Straub, Ben. “Pro Git v2”. Git, https://git-scm.com/book/en/v2.
[2] Wiegley, John. “Git from the Bottom to Up”. GitHub, https://jwiegley.github.io/git-from-the-bottom-up/.