《Pro Git》阅读随想

  之前做版本管理,我使用最多的是SVN,而且也只是在用一些最常用的操作。最近公司里很多项目都开始上Git,借这个机会,我计划好好学习一下Git的操作和原理,以及蕴含在其中的设计思想。同事推荐了一本《Pro Git》,读起来感觉很好,在这里分享下阅读时的思考。此书的在线阅读地址:http://iissnan.com/progit/


第一章 起步

  这一章介绍了Git的相关历史和基本特点,以及安装配置方法。这里提到的Git的特点包括“直接记录快照,而非差异比较”、“近乎所有操作都是本地执行”、“时刻保持数据完整性”、“多数操作仅添加数据”、“文件的三种状态”,除了最后一点我会放在下一章里梳理,下面会对其中一部分进行一些思考的分享。

直接记录快照,而非差异比较

Git 和其他版本控制系统的主要差别在于,Git 只关心文件数据的整体是否发生变化,而大多数其他系统则只关心文件内容的具体差异。

  这个策略要求Git记录每个版本的完整文件。如果需要对比同一个文件的两个连续版本间差异,Git会直接比较两个文件,而其他系统可以直接把保存的具体差异取出来;但是如果比较间隔版本的文件,后者需要将差异全部合并,才能显示。这意味着版本的间隔越多,基于差异的系统在比较差异所需要的计算量会越大,而Git完全不会受到这个影响。

  可以把这个策略看作是空间换时间的实践。现在单位存储空间的费用越来越低,TB级硬盘也已沦为白菜价,即使是开发人员使用的百兆级SSD也已经普及,额外的空间消耗完全可以不做考虑。

时刻保持数据完整性

在保存到 Git 之前,所有数据都要进行内容的校验和(checksum)计算,并将此结果作为数据的唯一标识和索引。换句话说,不可能在你修改了文件或目录之后,Git 一无所知。这项特性作为 Git 的设计哲学,建在整体架构的最底层。所以如果文件在传输时变得不完整,或者磁盘损坏导致文件数据缺失,Git 都能立即察觉。

Git 使用 SHA-1 算法计算数据的校验和,通过对文件的内容或目录的结构计算出一个 SHA-1 哈希值,作为指纹字符串。该字串由 40 个十六进制字符(0-9 及 a-f)组成,看起来就像是:

24b9da6552252987aa493b52f8696cd6d3b00373

Git 的工作完全依赖于这类指纹字串,所以你会经常看到这样的哈希值。实际上,所有保存在 Git 数据库中的东西都是用此哈希值来作索引的,而不是靠文件名

  使用SHA-1产生的hash值而不是文件名做索引的好处是,hash值的长度固定,并且随机性很好,符合哈希充分散列的要求。SHA-1本身就是一种常用的hash函数,其应用不在这里重述。前一段时间Google宣布“将在Chrome浏览器中逐渐降低SHA-1证书的安全指示”,但它这样做的原因是出于安全考虑,并不意味着Git使用SHA-1做hash函数不合适,有兴趣的读者可以看看相关的分析,如:深度:为什么Google急着杀死加密算法SHA-1

  文件名做索引有什么坏处呢?长度不固定并不是主要的问题。以用maven管理的代码为例,如果依赖比较复杂,那么各个package中都有各自的pom.xml,它们的文件名是完全一样的,会导致严重的hash碰撞。


第二章 Git基础

  这章介绍了最基本的 Git 本地操作:创建和克隆仓库,做出修改,暂存并提交这些修改,以及查看所有历史修改记录。这些操作的命令不再一一列出,来看看第一章提到但没有详细讲述的文件状态。

文件状态

  梳理一下文件各个状态的转换过程和逻辑,可以画出下面的图示。在这张图中,常用的本地的文件操作命令以及将会导致的状态变更就很清楚了:

  除了文件状态,简单说下Git里标签的意义。众所周知,SVN里每个版本都是有版本号的,从1开始,每次提交都会升高。而在Git中,每次提交只会返回一个SHA-1 校验和其他的信息,是没有版本号的。

  发布时,如何指定Git上的代码版本?这时就可以用tag来做标记了。tag相当于为一个特定的版本增加的标记,可以替代SVN里版本号的功能,而且更强大。


第三章 Git分支

用链表组织分支

  如果要理解Git,要理解Git的分支;如果要理解Git的分支,首先要理解Git中的四个基本的对象模型:blob、tree、commit、tag。这部分原书写的比较简单,具体可以参考《Git Community Book》第一章。幸运的是,该书也有网络版,这一部分内容的地址是:http://gitbook.liuhui998.com/1_2.html。简单地说,这四种对象分别对应于:

  •   blob:表示文件内容,是指向文件的索引。
  •   tree:表示目录层级关系,保存了其他blob对象和tree对象的指针。
  •   commit:保存一次提交的信息,以及一个tree对象根目录。
  •   tag:标记一次commit。

  分支是把commit对象组织成了链表的形式,不同的分支指向了对应的commit对象,每次在分支上提交,都会在链表表头上插入新的对象,如下图上的master和testing两个分支,图中的绿色方框代表一个commit对象。此时可以通过控制HEAD指针所在位置来指明使用了哪个分支。

  简单回忆下链表的相关操作可以发现,只要保存各个分支对应的表头,我们可以很容易的通过给HEAD赋值在各个分支之间切换。同时对于每次提交,链表插入的操作也很简单。

  在理解了Git版本管理的链表式的实现方式之后,只要具有基本的算法知识,其他操作原理的理解会变得非常迅速。以下各图来自于《Pro Git》。

  1.从master拉新分支iss53,只需新增该分支的指针,未做修改后的提交时,iss53指向master。提交新内容时,创建对应commit对象,iss53指针前移。

  2.当分支hotfix的祖先节点中包括master分支,将hotfix分支merge回master分支,只需要把master的指针移动hotfix上,没有任何文件处理工作,因而称之为Fast forward。

  3.当分支iss53合并回master分支,但是master不是iss53的祖先时,先计算二者的最近一个的公共祖先,把它和这两个分支的commit进行合并计算,创建新的commit对象。这个对象有两个祖先。如果合并时遇到冲突,不会提交,而是等人工处理完冲突并git add后才能进行提交。

  如何寻找交叉链表的第一个公共节点?这是一个常见算法问题,可以参考旧作《编程之美》3.6判断链表是否相交之扩展:链表找环方法证明的扩展问题2。

  4.(个人推测)查看某个分支是否已合并到master分支:比较两个分支指针是否指向同一个对象。

  5.(个人推测)删除已合并master的分支:直接删除该分支的指针;删除未合并的分支(git branch -d XXX):删除该分支不在master上所有commit对象及相关的对象、删除分支指针。

merge还是rebase?

  merge是直接将两个分支合并到一起:创建一个合并后的commit节点,祖先有两个,是被合并的两个分支A和分支B,节点内容是三方(分支A、分支B、分支A和B的共有最近祖先)合并的结果。原有的链表上的节点保留,分支上的提交历史没有发生改变。如下图(来自《Pro Git》)所示:

  rebase则是将一个分支A中的内容产生的补丁在另一个分支B上重新打一遍,打完之后,分支A的节点变成了分支B的后继。rebase完成后,分支A的特有节点发生了变动。如下图(来自《Pro Git》)所示,C3和C3`是不同的节点:

  实际上,merge和rebase产生的节点的内容上是一样的,发生冲突时仍需要人工解决,不同点只是提交的历史节点。rebase更适用于未公开提交(可以理解为push到远程仓库)的对象,清理提交历史;如果对已公开的提交对象rebase,并且已经有人对这些已提交对象开展了后续开发,会使得提交历史非常混乱。详细的例子可以查看原书“分支的衍合”一节。


第六章 Git工具

  在“使用Git调试”一节,提到了git bisect进行各个commit的二分查找。众所周知,单链表本身是不支持二分查找的,推测Git可能使用了以下两种方式支持:

  (1)将起始和结束的两个commit中间所有节点的指针保存到一个临时数组中,二分查找基于这个临时数组进行;

  (2)git使用的了类似于跳表的链表。跳表可参考http://www.cnblogs.com/liuhao/archive/2012/07/26/2610218.html。

  进一步地推测,在进行二分查找时,对commit进行修改,可能会导致查询错误。


第九章 Git内部原理

底层命令究竟做了些什么

  在第一次阅读这一章时,我从第二节开始就有点晕头转向,不知道究竟行文的思路是什么。第二次阅读时才有点眉目,并发觉第一次没看懂的原因是,原文很多地方只是描述底层命令执行后发生的现象,并没有完整地告诉读者这个命令执行的结果。网上很多对git的介绍文章偏实用主义,对这些底层命令并没有花费多少笔墨。好在git自身的文档很完善,git -help <command>对底层命令也有效,可以自行查看。不过方便起见,这里会简单介绍下这些底层命令。以下介绍底层命令时,实际用法为git XXX,如git hash-object,简记为hash-object。

  当然,这里的介绍不是文档的翻译,其中也加入了一些个人的理解,因此,各个命令的介绍可能有少量的连续性。

  另外一个有趣的事实:Git高层命令是可以自动补全的,而底层命令不行。

hash-object

  计算一个文件(可以通过--stdin指定为从标准输入读取)的对象ID,这个对象ID实际上是git这个内容寻址文件系统的K-V关系的键值。可以使用-w选项将该对象添加到git的文件对象库,而不仅仅是把对象的键值显示在屏幕上。

cat-file

  显示git对象的内容或类型,需要指定对象ID。-p用于输出格式化内容。你会发现,通过hash-object生成的文件直接打开是乱码,想要查看原始内容,必须用git cat-file。可以推测,git对象中不仅保存了文件内容,还应该保存了结构信息等,并有被压缩的可能。这节的结尾便证实了这点:先写文件头(包括文件类型和内容长度)、内容正文,再计算SHA-1校验和(作为文件路径和文件名,不参与文件本身的保存),最后进行压缩。

  如果对一个tree对象使用cat-file -p,可以看到这个tree对象包括了其他tree或blob对象的引用(同样是对象ID)。

update-index

  为文件创建或更新index。这样做会导致文件被放入暂存区域(回想下git中文件的staged状态)。运行这个命令之后,往往接下来要运行git write-tree。对同一个文件重复运行也没有任何提示。

write-tree

  为当前的index(注意:此时暂存区可能有多个文件)创建一个tree对象。把update-index和write-tree分开的目的,我认为是Git为了获得更细粒度的控制能力。

read-tree

  将一个的tree对象(可以以--prefix指定其对应的目录名,这个目录此时还不存在)读入index。通过这个命令以及update-index、write-tree,我们就可以任意地装配出任何目录-文件的结构了。注意我这里使用的是“装配”而非“组装”,是因为这三个命令是无法进行目录结构的拆分的。

commit-tree

  指定一个tree对象,以此创建一个commit对象。如果对一个commit对象再次运行该命令,可以git log看到完整的提交历史,也即这两个commit对象。

update-ref

   安全地更新一个用对象ID表示的文件的引用。其结果和git branch指定某个分支(对应于update-ref的引用)为某个commit(对应于update-ref的对象ID)一样。

  轻量级tag对象是可以通过update-ref进行创建的。

symbolic-ref

  给一个标记(如最常见的HEAD)指定一个引用。不指定引用则读取这个标记当前的引用。

gc

  清理文件。实际上是将文件进行打包压缩。

verify-pack

  查看通过gc进行的已打包的git对象。

阅读的时候遇到了两处翻译错误,我已提交了pull request:

1.第9-2节“-stdin 指定从标准输入设备 (stdin) 来读取内容,若不指定这个参数则需指定一个要存储的文件的路径。”应为“要读取的文件的路径”。

2.第9-4节 “然后可以用git cat-file命令...”,下面用的是du命令。实际原文中在这里没有提到“git cat-file”。

时间: 2024-10-22 22:26:05

《Pro Git》阅读随想的相关文章

Pro Git阅读笔记

一.什么是分布式版本控制系统 变迁: 本地版本控制系统(RCS)--> 集中化的版本控制系统(CVCS) --> 分布式版本控制系统(DVCS)          本地版本控制不利于多人协同合作  -->   集中化版本控制系统(服务器单点故障可能会丢失所有数据) --> 分布式版本控制系统(本地仓库是代码仓库的完全镜像,如果服务器故障,可以用本地仓库恢复) 二.Git与其他版本控制系统的区别 其他一些版本控制系统保存的信息是文件之间差异,而Git直接记录快照,而不是差异比较 Gi

【Tools】Pro Git 一二章读书笔记

记得知乎以前有个问题说:如果用一天的时间学习一门技能,选什么好?里面有个说学会Git是个很不错选择,今天就抽时间感受下Git的魅力吧. Pro Git (Scott Chacon) 读书笔记: 第一章:Git起步 版本控制出现的原因及分类 类别 基本原理 优点 缺点 本地版本控制系统 自己把复制整个项目,改名或加上备份时间来区别 采用某种简单的数据库来记录文件的历次更新差异 简单 有时会混淆所在的工作目录, 丢了文件毛了数据就没有后退的路 集中化的版本控制系统 解决在不同系统上的开发者协同工作,

【重要】Pro Git 第二版 简体中文

不管是入门还是精通git,下面这本书都是必读,同时它也是官方推荐书籍. Pro Git 第二版 简体中文 我自己还收集了一份网页版的progit,但可能不是progit第二版. 下载地址 http://download.csdn.net/detail/lhat_7/9840352 网页阅读地址 http://git.oschina.net/progit/

[Git00] Pro Git 一二章读书笔记

记得知乎以前有个问题说:如果用一天的时间学习一门技能,选什么好?里面有个说学会Git是个很不错选择,今天就抽时间感受下Git的魅力吧. Pro Git (Scott Chacon) 读书笔记: 第一章:Git起步 版本控制出现的原因及分类 类别 基本原理 优点 缺点 本地版本控制系统 自己把复制整个项目,改名或加上备份时间来区别 采用某种简单的数据库来记录文件的历次更新差异 简单 有时会混淆所在的工作目录, 丢了文件毛了数据就没有后退的路 集中化的版本控制系统 解决在不同系统上的开发者协同工作,

Git学习圣经《Pro Git》第二版

Git已经成为最流行的分布式版本控制系统,GitHub.CSDN CODE等提供的代码托管服务都是基于Git的.<Pro Git>由GitHub员工Scott Chacon和另一位爱好者Ben Straub共同编写,主要介绍了Git使用基础和原理,适合Git爱好者和初学者参考. 官网:http://git-scm.com/book/en/v2中文翻译:http://git-scm.com/book/zh/v1(第一版) 各种格式下载: PDF格式 Epub格式 Mobi格式 HTML在线阅读

Pro Git 转载 - 1.1 起步 - 关于版本控制

转载序: 最近单位开始用Git,网上虽然有各资料,然而大多顾此失彼. 这是git官网上找到的书 http://git-scm.com/book,显然不是速成资料.如需快速上手,原作推荐第二章. 版权: The entire Pro Git book, written by Scott Chacon and Ben Straub and published by Apress, is available here. All content is licensed under the Creativ

[QCon] Scrum阅读随想

最近从群里面下载到几篇文章,看到QCon出来的相关文章,觉得都写的很不错,都是一些个大公司的非常好的方法   QCon:是为团队领导者.架构师.项目经理和高级软件开发人员量身打造的企业软件开发大会,其所覆盖的主题内容与InfoQ网站相同,关注架构与设计.真实案例分析等等.   个人感觉,看过几篇文章之后 ,发现讲解的诸多内容确实是业界比较先进的案例而且是很真实的案例.   回到正题,用这篇文章来记录一下,Autodesk的scrum之路: 第一阶段:Form Teams,形成team阶段,这个阶

《Pro Git》笔记3:分支基本操作

<Pro Git>笔记3:Git分支基本操作 分支使多线开发和合并非常容易.Git的分支就是一个指向提交对象的可变指针,极其轻量.Git的默认分支为master. 1.Git数据存储结构和分支 git提交时会将暂存文件的内容,暂存的目录结构,提交对象,含附注标签对象都以包含信息头的二进制文件形式存储到版本库中(.git/objects目录),存储的对象以其自身SHA1值作为唯一标识,SHA1前两位为存储对象所在目录名,SHA1后38位为存储对象的文件名.存储的数据对象类型有: blob(文件内

Pro Git 转载 - 目录

转载序: 最近单位开始用Git,因为我天资的原因,花了好多时间,也没理解个所以然. 这是git官网上找到的书 http://git-scm.com/book,显然不是速成资料.如需快速上手,原作推荐第二章. 版权: The entire Pro Git book, written by Scott Chacon and Ben Straub and published by Apress, is available here. All content is licensed under the