这篇短文将介绍如何用500行的Javascript代码,写一个你自己专属的GIT。 这不是一个如何使用GIT的工具,而是GIT的底层实现。目的是希望能加深对GIT的底层实现原理,而不是想换掉GIT,这只是一个GIT的雏形而已
代码来自开源,也回到开源,有需要且不嫌弃的可以上去看看 https://github.com/notechsolution/gitdou
缘起
跟GIT的结缘开始于2011年,公司决定不用原来的IBM Clearcase,改用开源的GIT。作为当时GIT的内部support,确实有很长一段时间跟它厮混在一起。后来还写了几篇如何使用GIT的文章,有空可以翻翻 GIT七年之痒. 前两年回一下炉,又写了几篇 GIT入门.
最近看到一个叫Richard Feynman的人说过这么一句话
What I cannot create, I do not understand - Richard Feynman
嗯嗯,有点意思,扒拉了一下,还有不少人用Javascript写GIT。这次的实现主要也是参考了其中一个叫gitlet的
用什么锤子?| 技术栈
GIT是Linux Torvalds用C语言写的。小的不才不懂C,那就用Javascript写写吧, ES6 可以让代码可以写得比较简洁。既然重造轮子,那就尽量少用框架吧。但是作为lodash粉,还是忍不住了,最后还是用了lodash~~~.
当然,Pivotal Lab中毒较深,做个练习也离不开TDD,所以这次也用了Ava作为testing框架。 但功力尚浅,有些case也偷懒了,testcase跟代码的函数比例只做到1:1, 500行的代码只有500行的unittest。
锤出个什么东东?| 实现哪些功能
这次的目的是为了加深对GIT底层实现原理的理解,而不是做出一个真正的产品出来,所以对于用户操作没有做出各种友好的提醒,比如没有像Already up to date
这样的提醒等等,只要实现了GIT的如下核心命令:
- init
- add
- rm
- commit
- checkout
- branch
- remote
- fetch
- merge
- pull
- push
咋锤的?| 实现过程
下面尝试逐一来解释一下每个命令是干什么的。
gitdou.init
首先是初始化一个GIT的项目。GIT在某种程度上可以理解为一个文件的数据库,里面保存着所有文件的所有版本。初始化的过程也就是创建各个文件以及目录.
.gitdou
├── HEAD
├── config
├── objects
└── refs
├── heads
- .gitdou: 当前repository的根目录
- config: 当前repository的配置文件,记录当前repository的各种配置,比如是不是bare,远程协作仓库地址等等
- HEAD: 存放repository指向哪个branch,由于初始branch为master,所有HEAD的初始值一般为
ref: refs/heads/master
- objects : 存放数据库文件的目录
- refs:存放local branch或者remote branch的当前commit,类似于数据库的游标
初始化的过程就是在指定的目录.gitdou
下生成这些目录及文件的过程。代码就比较简单,根据目录结构,生成对应的文件树:
init: () => {
const gitdouStructure = {
HEAD: 'ref: refs/heads/master',
objects: {
},
refs: {
heads: {
}
},
config: JSON.stringify({
core: {
bare: false}}, null, 2)
}
files.writeFilesFromTree({
'.gitdou': gitdouStructure}, process.cwd());
},
add
前面说到了git实际是一个数据库,存放了所有文件的所有历史版本。为了更方便高效地查询,数据库都会建立索引。git也不例外,它也有一个index文件,记录所有文件的路径,这些文件的状态以及当前版本的hash值。
add
命令就是将指定路径的所有文件的路径,状态以及当前的hash值记录保存到index文件里面。其实现过程就是扫出指定目录下的所有文件,逐一计算他们的hash值,然后写到index文件里面
add: path => {
const addedFiles = files.listA