黑帽联盟

 找回密码
 会员注册
查看: 803|回复: 0
打印 上一主题 下一主题

[基础服务] Git之旅(5):越了解,越喜欢

[复制链接]

852

主题

38

听众

3175

积分

白金VIP

Rank: 8Rank: 8

  • TA的每日心情
    开心
    2024-3-7 12:52
  • 签到天数: 1538 天

    [LV.Master]伴坛终老

    前文中,我们已经初步的使用了git,我们通过创建"提交"的方式将某个状态保存下来,"提交"就相当于我们手动管理版本时所创建的"副本",但是,"提交"其实比"副本"高明的多,那么到底高明在哪里呢?我们一起来看看。

    首先,来说说手动进行版本管理时所创建的副本。
    假设,我的代码都放在一个名为project的目录中,现在project目录中有很多文件,总共占用磁盘空间2M,现在,我想要修改其中的一些代码文件,为了整个项目能够随时恢复到当前状态,我需要手动的创建一个副本,于是,我将整个项目目录拷贝了一份,这个副本也占据了2M磁盘空间,于是我开始了修改工作,修改工作完成后,我觉得修改后的状态也需要保存一下,以便随时回到这个状态,于是,我又创建了一个副本,这个副本也占据2M磁盘空间,也就是说,我每次创建副本,都是上述过程的重复,无论我修改了多少代码,即使只修改了一点点,我也需要将整个目录复制一份,以便保证目录中的所有文件在某一时刻的一致性,换句话说就是,即使两个版本之间的差异只有1k,但是也需要牺牲约2M的空间,来实现版本管理。

    那么git是怎样实现版本管理的呢?git使用的方法,比我们高明的多,事实上,git只会对修改的部分创建副本,而不会对整个目录创建副本,那git是怎么做的呢?如果想要了解git是怎么做的,则需要从git对象说起,"git对象"的概念并不难理解,请坚持看完下文,看完了,自然就理解了。

    当我们使用git进行版本管理时,git会将我们的文件和目录结构转化成git方便操作的数据,也就是说,git会将我们的文件和目录转化成一种叫做"git对象"的东西,然后再对这些"git对象"进行管理,从而实现版本管理的目的,这些git对象存放在git的对象库中。

    我们眼中的文件会被git转化成"块"(blob)
    我们眼中的目录会被git转化成"树"(tree)
    我们眼中的状态会被git转化成"提交"(commit)

    blob、tree、commit都是git对象,是三种不同类型的git对象
    一个blob就是由一个文件转换而来,blob对象中只会存储文件的数据,而不会存储文件的元数据。
    一个tree就是由一个目录转化而来,tree对象中只会存储一层目录的信息,它只存储它的直接文件和直接子目录的信息,但是子目录中的内容它并不会保存。
    一个commit就是一个我们所创建的提交,它指向了一个tree,这个tree保存了某一时刻项目根目录中的直接文件信息和直接目录信息,也就是说,这个tree会指向直接文件的blob对象,并且指向直接子目录的tree对象,子目录的tree对象又指向了子目录中直接文件的blob,以及子目录的直接子目录的tree,依此类推。

    每个git对象都有一个"身份证号",这个身份证号是一个哈希码,这个哈希码通过SHA1算法得出,如果git对象的内容相同,那么他们的哈希码就是相同的,如果git对象的内容不同,那么他们的哈希码必定不同(通常来说,SHA1算法能够保证内容不同时,得到的哈希码必定不同,不过,理论上来说,即使内容不同,也有可能产生相同的哈希码,不过几率非常之小,我们可以忽略这种可能性),一个git对象的哈希码通常长成如下模样:
    875925683e755d94e26a2dc1a1bc4c645a91acbe
    它是一个40位的十六进制数。
    刚才提到,每个git对象都有一个这样的哈希码,所以,每个"提交"(commit)也有一个这样的哈希码,在后文中,我们会使用提交的哈希码来表示某个提交,不过由于这个哈希码比较长,所以通常情况下,我们只会使用哈希码的前几位来表示一个提交,只要这个哈希码的前几位与别的哈希码的前几位不同,能体现出唯一性,我们就能用这个哈希码的前几位来表示这个提交,比如,刚才示例的哈希码如下
    875925683e755d94e26a2dc1a1bc4c645a91acbe
    我们可以使用8759256来表示这个哈希码。

    只看上面一段描述,可能不太容易理解,我们来看张图,用图来描述上述内容似乎更加容易理解,下图模仿于 [Git版本控制管理]一书,下图中的圆形代表commit(即前文中的"小圆球"),三角形代表tree(由目录转化成的git对象),长方形代表blob(由文件转化成的git对象)。假设,第一次提交之前,目录中只有两个文件,file1和file2,file1的内容为f1,file2的内容为f2,那么当第一个提交创建以后,git的对象库中会存在如下图的git对象

    1.png


    也就是说 ,当我们创建第一个提交以后,项目当时的状态已经被转化成了上图中的git对象,我们创建的第一个提交的哈希为8759256,它指向一个tree,这个tree就是当时根目录的状态,这个tree的哈希为e890df4,从上图可以看出,当时的根目录中只有两个文件,也就是两个blob,这个tree指向了这两个blob,这两个blob就是由file1和file2转化而来的。

    如果此时,我们修改了file2,我们将file2的内容从f2修改成f22,并且在根目录中创建一个新的子目录dir1,在dir1中又添加了一个文件d1file1,d1file1的内容是df1,但是我们并没有对file1进行任何修改,那么,当我们再次提交以后,git对象库中会存在如下对象。

    2.png


    如上图所示,我们修改了file2,将其内容从f2修改成了f22,当第二个提交创建以后,git会将file2的新状态转化成一个新的blob对象,file2之前的状态对应的blob对象仍然保存在git对象库中,并且被初始提交引用,以便我们随时能够通过初始提交找到file2当时的状态,file2新的状态被新的提交引用,我们并没有修改file1,也就是说,file1的状态一直没有发生改变,所以,新的提交只是通过tree对象指向了之前file1对应的blob,由于我们在根目录中创建了一个子目录dir1,所以,在新的根目录的tree对象中,也包含了它的直接子目录信息,并且指向了新子目录对应的tree对象,子目录tree对象中又保存了自己目录中的信息,也就是d1file1文件对应的blob对象。

    看完上述过程,我们回到最初的问题,当我们手动创建副本时,为了保存项目中所有文件在某一个时刻的状态的关联性和一致性,我们需要对整个项目(所有文件)创建副本,这种做法就会导致之前描述的问题发生,即使两个副本之间的差异只有1k,也需要牺牲2M的磁盘空间,每次创建副本,无论改动的大小,都会牺牲整个项目大小的磁盘空间,这样在频繁创建副本的情况下,是非常不划算的,但是git的做法就高明的多,它只会对改动的文件创建副本,就像上例中的file2,当file2的状态发生改变时,git才会对file2的新状态创建新的blob对象。
    file1的状态没有发生改变,git就不会对file1创建副本,也就是说,在两个commit中(在整个项目的两个状态中)file1的状态是相同的,于是git并没有对file1重复的创建blob,而是通过引用的方式,指向了file1对应的blob,即两个副本复用了同一个file1的状态,所以,当我们使用git进行版本管理时,只会牺牲最小的磁盘空间,来实现版本管理。

    我对git的理解似乎加深了,因为我明白了,一个commit就代表项目的一个状态(相当于手动创建的副本),一个commit背后是一堆git对象,git将这些git对象巧妙的组织在了一起,从而实现了版本管理的目的。

    帖子永久地址: 

    黑帽联盟 - 论坛版权1、本主题所有言论和图片纯属会员个人意见,与本论坛立场无关
    2、本站所有主题由该帖子作者发表,该帖子作者与黑帽联盟享有帖子相关版权
    3、其他单位或个人使用、转载或引用本文时必须同时征得该帖子作者和黑帽联盟的同意
    4、帖子作者须承担一切因本文发表而直接或间接导致的民事或刑事法律责任
    5、本帖部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责
    6、如本帖侵犯到任何版权问题,请立即告知本站,本站将及时予与删除并致以最深的歉意
    7、黑帽联盟管理员和版主有权不事先通知发贴者而删除本文

    勿忘初心,方得始终!
    您需要登录后才可以回帖 登录 | 会员注册

    发布主题 !fastreply! 收藏帖子 返回列表 搜索
    回顶部