当前位置: 首页 > 科技观察

国庆节快到了,一起来写一个GitforLinux的第一个版本

时间:2023-03-16 14:26:45 科技观察

NaiveGit一起来写一个简单的Git吧!前言我和两个小弟建立了一个gitorg,主要是他们(我需要工作,paddleforideas主要是作为PM)做一些有趣的项目,PioneerIncubator[9],这个git是第三个项目,第一个项目比较好去,我几个月前写了第一个版本,等他们做一些补充工作,后面会单独介绍。第二个项目刚刚开始。他们搜索了一下,发现去年10月份就有人做了,那个项目还有500多star。Git的原理是什么?Git是一种分布式版本控制系统,用于在软件开发过程中跟踪源代码的更改。读者即使不懂git的原理,也一定要用三轴:gitadd;提交;gitpush,简单说一下git是如何进行版本管理的:跟踪文件变化,以commit为标记,与远程服务器同步。跟踪文件变化如果你开发git工具,在初始化一个文件夹(仓库)后,为了记录以后可能的修改,你需要记录当前所有需要跟踪的文件内容。最简单的方法是复制所有这些。文件变了吗?比较文件哈希。Commitasamark,俗话说,就是将当前版本库状态存储为一个commit。您可以通过提交恢复到任何状态。gittag的本质只是给这个commit一个tag(别名),gitbranch也是一样的。恢复到某个commit就是恢复它所代表的repository状态,也就是把文件的全部内容和当前的commit恢复到那个状态。与远程服务器同步。Git自称是一个分布式版本管理系统,因为如果A、B、C一起工作,理论上每个人都有一个版本服务器,他们可以独立开发来解决冲突。Git具体是怎么做的呢?原理说完了,但是commit管理就是用东西来存储和读取管理。Git不使用数据库,直接将其内容放在.git文件夹中。.|--HEAD//指向分支,tag(ref:refs/heads/devbranch)|--index|--objects||--05||`--76fac355dd17e39fd2671b010e36299f713b4d||--0c||`--819c497e4eca8e08422e61adec781cc91d125d||--fe||`--897108953cc224f417551031beacc396b11fb0||--fe||`--897108953cc224f417551031beacc396b11fb0||--info|`--refs|--heads//各个branch的heads|`--master//这个分支最新的commitid|`--devBranch//checkout-bbranch会生成branch`--tags`--v0.1大家我先说下:HEAD:指向branchortag,标记当前所在的分支或标签;index:TODOobjects:记录文件内容,每个文件夹名称为对象sha1值的前两位,文件夹下的文件名为sha1值的后18位;(tips:sha1算法为一种加密算法,计算当前内容的哈希值作为对象的文件名,得到的哈希值是由十六进制数组成的字符串(长度为40)refsheads:heads是每个分支的HEAD指向的地方哪个提交ID;简单的说,每个分支最新的commit是什么,这样gitcheckoutbranch就可以切换到合适的地方tags:同理,这个文件的所有tags都存放在文件夹中。创建新分支时,只需在refs/heads文件夹中创建一个以分支名称命名的新文件,并将当前提交ID保存在其中;在新建commit的时候,只需要根据HEAD文件,找出当前的branch或者tag是什么,修改里面的内容即可。有点难懂?让我们举一个git的例子。默认情况下,在文件夹中执行gitinit后,添加一个文件并提交信息。commitid为017aa3d7851e8bbff78a697566b5f827b183483c:$cat.git/HEADref:refs/heads/master$cat.git/refs/heads/master017aa3d7851e8bbff78a697566b5f827b183483c同上,HEAD指向master的commitid现在是master的commitid.存储读取解决了,那怎么组织commit呢?将当前存储库状态存储为提交。您可以通过提交恢复到任何状态。gittag的本质只是给这个commit一个tag(别名),gitbranch也是一样的。恢复到某个commit就是恢复它所代表的repository状态,也就是把文件的全部内容和当前的commit恢复到那个状态。上面说到管理文件夹(repository)的状态是被管理的,但是文件夹可以嵌套,这一点和文件是不一样的。它需要有这种层级关系,同时保存文件的内容。怎么区分呢?我们可以引入以下概念:Tree:代表一个文件夹,因为在gitinit的时候,将当前文件夹./作为一个项目来管理,那么所有要追踪的item无非就是./中的文件或者文件夹;Blob:文件,在Tree中可以包含;关系如下图所示:看一下我们写的数据结构代码。需要注意的是tree可以有blob也可以有tree,所以用了union;parent和next用作链表,用作文件夹目录管理;structtree_entry_list{structtree_entry_list*next;union{structtree*tree;structblob*blob;}item;structtree_entry_list*parent;};structtree{structtree_entry_list*entries;};而commit,和树一样,也是层级单链表,只是structcommit{structcommit*parents;structtree*tree;char*commit_id[10];char*author;char*committer;char*changelog;};一张图千言万语,看图:如上,一共有三个commit,顺序是:1->2->3,其中3个是最新的。圈出的blob为文件内容,表示该文件在commit1和commit2中没有变化,所以复用同一个;squareone也是同一个文件,但是内容变了,指向不同的blob;标记点以提交2;HEAD和branch都在最新的commit3中,并且添加了一个新文件;所以可以通过commit记录变化的内容,即从上到下恢复所有变化的文件。如图checkout到v0.1标签就是找到commitid,然后恢复commit下的tree文件:云峰的游戏资源仓库和升级发布云峰参考原理做了一个游戏资源仓库的管理混帐。下面说说它和git的区别。我觉得他的文章[10]比较绕口,没有背景知识的人很难看懂。背景我们引擎的一个重要特点是可以在PC上开发,在移动设备上运行调试。我们需要经常将资源同步到设备。当程序以c/s结构运行时,首先在移动端创建一个空的镜像仓库,同步PC端的资源仓库。操作过程如下:首先,客户端启动时,向服务器请求根索引的散列,并将根设置在本地镜像上。客户端请求文件路径时,从根开始查找对应的目录索引文件,逐级查找。如果本地有需要的hash对象,直接使用;否则,向服务器请求,直到最终得到目标文件。api的设计中,打开一个资源路径,要么返回最终文件,要么返回一个hash,说明还是缺少hash对象;这样就可以通过网络模块请求对象了;获取对象后,不管对象是什么,直接写入镜像仓库,然后重复前面的过程,再次请求未完成的路径,最后打开需要的资源文件。场景是:客户端<-他的游戏服务器,单向同步;他是这样操作的,客户端的仓库是一个key-value的文件数据库,key是文件的hash,value是文件内容;同步时,会从根目录开始,到特定的hash,将同步文件全量下载到数据库;如果客户端在使用资源时发现缺少文件,则使用哈希从服务器上拉下来。换句话说,因为不需要管理本地的版本,是同步到上游的,所以不需要在本地记录完整版本状态和Git的区别:场景是:Client<->gitHub,两个-路同步;git需要在本地组织commit,切换本地但是服务器没有的版本(也就是离线运行),变更需要同步到upstream。最后的建议如果你看完这篇文章就急于尝试,请不要用C写,请不要用C写,请不要用C写。我从头写过几个更大的项目,而且每次感觉用C写项目太难受了。这次写gitcommit的时候,发现要读写文件,解析内容。心里感叹:太难了,不是写这个难,是C太难用了。.想着我要遍历这些文件,根据目录得到树的hash,然后更新树,把树、commit、blob倒序存到文件里,读出来,然后整理链表操作,用C写感觉百般受阻。..具体实现,gitrebase,gitmerge等进阶内容,等下一篇吧。本文转载自微信公众号《山尽头写东西的缓存》,可通过以下二维码关注。转载本文请联系写东西的cache公众号。