Git源码学习(一)

趁着最近工作清闲,学习一下Git的源码。搜了一下网上没有这方面的资料,只能自己慢慢的看。为了降低难度,并且更好的理解Git的发展历程,我决定从最初版的Git开始看起,跟着Git学Git。

选择Git v0.99开始学习。从https://github.com/git/git/tree/v0.99/处下载源码,这个版本的Git非常简单,功能也很少,可以从中看到初期Git设计的初衷。Clone下来之后,reset到第一次commit,开始源码的阅读之旅。


第一次Commit的文件很少,目录结构如下所示:

Git源码学习(一)_第1张图片


编译

依赖包:libssl-dev、zlib

修改编译选项:Makefile中LIBS= -lcrypto -lz

编译完成之后在~/bin/下会产生以下命令:cat-filecommit-treeinit-dbread-treeshow-diffupdate-cachewrite-tree


用法

1、使用init-db初始化工作目录,类似于git init的作用。

2、项目编写,增删改各种文件等等。

3、使用update-cache [file-path],保存更改至缓存中。这会生成一个index文件,改文件用于保存当前的cache

4、使用write-tree提交缓存中的更改。这会生成一个tree文件,当前的cache中的文件会写入到tree文件中去。命令结果会返回tree文件的sha1值。

5、使用commit-tree  [-p parent-sha1]* < changelog提交这次更改。如果没有parent的信息,就会当做是第一次提交。有的话就表示改次提交是在parent基础上提交的。这里也只是一个信息的记录,其实并不会检查是否存在child-parent的关系。

6、工具命令show-diff,用来比较当前工作目录下的文件和cache中(即index文件)记录的文件的区别。

7、工具命令cat-file ,查看某个sha1文件。会生成一个TEMP文件用来保存改SHA1文件中的内容。


概念

1、The Object Database(SHA1_FILE_DIRECTORY)

这是一个用于存储SHI1文件的文件数据库,其实本质上只是一个文件夹,用于存放所提交的文件。文件包含Metadata信息和Blob内容,经由Zlib压缩后算出SHA1,该SHA1的前2位作为子文件夹名,后38位作为文件名。Directory如下图所示。

Git源码学习(一)_第2张图片


这里存放的文件包括三种:treeblobcommit

Treetree文件中存放的是所提交的文件列表,每一行描述所记录的一个文件,包括:文件的权限、路径名、SHA1值。这个就能够用于保存每一次提交的具体内容,通过查询tree文件,可以知道该次提交时所含有的所有文件,然后根据每一个文件的SHA1,可以在object database中搜索出该文件。这样就达到了保存每一次提交的具体内容的目的。

 

BLOBblob文件是指具体的文件内容,即我们所提交的文件。Blob文件会被压缩,然后计算SHA1值,所以如果文件的内容没有发生变化,那么就不会产生新的Blob文件。因为它们算出的SHA1是相同的,而SHA1值就是它们实际的存放路径。

 

Commitcommit文件是用于记录每一次提交的文件。包含的内容有:treeparentsauthorcommitterchangelog。其中tree是指用于保存此次提交的tree文件。Parents是指此次提交的父分支是哪些,也是对应的tree文件。Authorcommitterchangelog是提交的记录信息。

一个commit文件的例子:

Git源码学习(一)_第3张图片


源码分析

init-db.c

该文件的工作很简单,就是在工作目录下创建一个.dircache的目录,并在其中创建objects目录作为database的主目录,在objects中,创建了从00ff一共255个子目录。这些子目录将被用来存储SHA1文件。如计算出sha1值为b7140ba6976a2a2bf3cb31bf3aa2bd3e3619d521的文件,将被保存为.dircache/objects/b7/140ba6976a2a2bf3cb31bf3aa2bd3e3619d521

 

Update-cache.c

该文件实现了update-cache命令,主要流程为:从index中读取cache,根据传入的file,添加或者替换cache,讲新的cache写回index文件。

下面分几个主要部分记录主要的代码工作。

1、index文件的更新。

这里使用的方式是先更新内存中的cache内容,然后create一个index.lock文件,将新的cache写入index.lock文件中,然后将index.lock改名为index

2、Cache的更新。

Cache在内存中使用一个数组保存,本地使用index文件存储。通常是从index文件中读取到数组中。Cache中的条目根据cache->name有序排放,更新cache时,会通过二分查找检查该条目是否在cache中,如果已存在则进行替换,否则直接加入到cache中(注意有序排放)。

 

Show-diff.c

这里需要解释的是,实际的工作是通过diff命令来实现的。为了避免所有的文件都会调用diff,先通过比较文件的属性来判断文件是否发生了改变。如果已经能够判断出文件未发生改变,就无需再调用diff来比较新旧文件。此外,没有加入到cache中的文件不会被比较,也就是说对于添加和删除文件来说,这里是不能看到diff的结果的。并且如果是删除文件的话,使用show-diff还会报segment fault.

代码的大致逻辑:从index中读取cache到数组中,根据cache_entry->name找到工作目录下的文件(即新的文件),根据cache_entry->sha1objects中读取旧的文件,使用cache_entry中存放的旧文件的属性和新文件的stat比较,如果不同则调用diff来显示两者的差异。


总结:

从这个最初版的git中可以看到我们日常使用的git的一点影子。它能够跟踪项目的版本,但是还没有实现控制的功能。使用文件database的方式存储历史文件,并且利用sha1来简单的实现文件的复用,即相同的blob只需要存储一份。目前能够查看历史版本的文件,能够手动的记录提交的信息,手动的跟踪版本更新的信息。从今天的角度来看,但是这些功能都有待优化,优化的初衷就是更加易用。期待着它一步一步变成今天强大的Git!


附上Linus在这个版本README中对Git的描述:

"git" can mean anything, depending on your mood.


 - random three-letter combination that is pronounceable, and not
   actually used by any common UNIX command.  The fact that it is a
   mispronounciation of "get" may or may not be relevant.
 - stupid. contemptible and despicable. simple. Take your pick from the
   dictionary of slang.
 - "global information tracker": you're in a good mood, and it actually
   works for you. Angels sing, and a light suddenly fills the room. 
 - "goddamn idiotic truckload of sh*t": when it breaks




你可能感兴趣的:(Git源码学习)