diff的两个参数

最近在读滨野纯所著的《入门Git》,很有意思,主要集中于书中散见各处的作者提及Git当初的设计思想和实现过程的时候。我一直相信,理解一件事物最好的方式就是理解历史。作者作为在Git尚且只是雏形时(2005年7月末)就从Linus手中接过Git的维护工作,并在接下来的数年中将其发扬光大的关键人物( Linus: “In the last four years under his stewardship, git has flourished and become not just a technically advanced source control manager, but one that is a pleasure to use as well.” ),在书中谈论当初开发时的想法和过程自然是信手拈来——

会这么期待说明我还是太天真了。我可以感受到作者希望让这本书“名符其实”的良苦用心,但事实证明写好入门书的权力永远都只属于入门者,作者从来就不曾是一名“Git入门者”自然不会理解初学者最需要的是什么、最困惑的是什么,一上来就讲 Three-way Merge 的原理这是要闹哪样……

不过还是有这么一章,相对完整地讲述了当初的设计思想,并提到了diff的两个我之前并不知道的参数,让我觉得值得一写。其实对于Git的diff和log命令,日常使用在大多数情况无参数的输出已经足够,就我个人的经验,最常使用的参数也不外乎于-p--name-only,至多再修改一下--pretty。然而这一章告诉我,diff能做的不止于此,或者更确切地说,Git当初的目标就考虑得比这更远。Git试图成为的是一个理解历史的文件系统1

仿照书中的写法,这里也首先从Linus的一封邮件谈起。这封邮件是Linus于2005年4月投向Git的开发者邮件列表的,起因是有开发者提议在commit对象中添加rename信息,以便于SCM日后能够输出更详细的变更历史,Linus对此非常不以为然,于是就发了这封邮件,阐释他心目中理想的Git应该是什么,而按照《入门Git》中的说法,“这封邮件成为了其后的开发工作最重要的指导方针之一”。2

From: Linus Torvalds <torvalds@osdl.org>
Subject: Re: Merge with git-pasky II.
Date: 2005-04-16 01:32:46

好了,现在来听听我的建议。

  • 一个真正的信息追踪系统所记录的只有信息。所以SCM需要跟踪的只有对整个Project有意义的信息,而在比这更低的粒度下操作是不合理的。3

  • ……(在现有的Git系统上)事实上你能做到的比你在邮件中提到的那些还要多得多,只要你肯花点功夫。假设你现在想知道某一行代码是从哪里来的,而你正处在代码库历史的某个节点上,于是你开始挖掘历史。……你需要做的就是回溯历史,查看tree对象,确定这个文件是否有改动过。当发现文件被改动过时,你继续查看感兴趣的那行是否被改动过,如果没有改动,那很好,继续回溯就可以了;如果你运气不错,那行被改动了,你的Git可视化前端应该把结果显示给你,同时因为它能够查看到(那个commit的)全部变更,它可以提示你这些代码是从哪里来的,这只需要很少的CPU时间去扫描一下差分文件即可。你的前端程序会这么说:

“噢,那几行代码在上一个版本里似乎不存在。不过在差分里我找到了五处几乎完全相同的代码,你可以看看。”

就这样,你的前端程序非常高效地向程序员指出了这是一次将五个分布在不同文件中的类似函数合并为一个函数的重构。那如果你没能找到完全匹配的源代码,或者上一个版本的文件在那行周围都很相似,而那一行也不是“全新”的呢?这又是一个简单情况——你只需要把差分显示给程序员,由他来决定这是否就是他所要的,或是继续“追寻”历史。

相信读到这里,比较熟悉Git的同学已经猜到,Linus在这封邮件中所提出的这种利用Git的commit树和差分文件来提炼出重命名信息的功能,已经在Git中被实现,这就是标题中的“两个参数”

-M: Detect renames.

-C: Detect copies as well as renames.

说实话,这是我第一次在SCM中看到此类功能,多少有点“被震撼”到(subversion据我所知是没有类似功能的,mercurial有类似的选项--similarity,不过使用的地方并不同)。这两个选项基本就是依照Linus以上的想法实现的,而且不仅可以应用于diff命令,log和blame也支持同样的参数(意义则根据命令略有区别)。特别是对blame来说,-C参数能够更方便地找出代码的“始作俑者”,事实上我在知道这两个参数之前(就是最近不久),为了不至于因为修改了一下别人的屎代码的缩进而被blame误认为是屎代码作者,我还写过这样一个插件……可惜的是最后也没有实际使用过。


  1. 这是Git尚且只是一些c和shell脚本时的论断,或者说Linus设想中的Git。Linus最初将Git设计为一个记录文件变更历史的文件系统,而SCM是在其上运行的前端,因此才有了git-pasky。不过后来Git加入了越来越多的SCM功能,自己成为了一个SCM,这就是后话了。
  2. 因为原文比较长,以下仅为摘译,全文请参考这里
  3. 这里我的理解是,rename信息不是文件系统需要记录的信息,它只是SCM关注的东西,不应该在底层的文件系统中夹杂进本应属于上层的内容。不过说实话,我个人觉得加入rename信息也未为不可,毕竟“程序员A在一次重构中把文件X重命名为Y,并把其中的大部分函数移动到了文件Z”,在Git中的形式是“程序员A删除了文件X,添加了文件Y,其中只有很少的一些内容,同时在文件Z中添加了大量内容”,如果有rename信息的话,应该能更好地还原整个过程。
More Reading
comments powered by Disqus