每一行代码都有记录—如何用git一步步探索项目的历史

每一行代码都有一块被隐藏了的文档信息。

下面的代码片段不管是谁写的,其第4行因为某些原因要访问一个DOM结点的clientLeft属性,但却对结果不作任何处理。这十分的莫名其妙,你能告诉我他们为什么要这样做吗?以后改变或移除这个调用安全吗?

1
2
3
4
5
6
// ...
if (duration > 0) this.bind(endEvent, wrappedCallback)
 
this.get(0).clientLeft
 
this.css(cssValues)

即使曾有人像我一样给你贴过该段代码,你可能还是不知道谁写的这行代码,他们的用意是什么,有必要继续保留这行吗?无论如何,大部分在你致力于一个项目时,你一般会通过版本控制系统访问它的历史。

项目的历史是其最有价值的记录

当我们观察下面这行命令输出的提交信息时,一切谜底都揭开了:

1
$ git show $(git blame example.js -L 4,4 | awk '{print $1}')

Fix animate() for elements just added to DOM

Activating CSS transitions for an element just added to the DOM won’t work in either Webkit or Mozilla. To work around this, we used to defer setting CSS properties with setTimeout (see 272513b).

This solved the problem for Webkit, but not for latest versions of Firefox. Mozilla seems to need at least 15ms timeout, and even this value varies.

A better solution for both engines is to trigger “layout”. This is done here by reading clientLeft from an element. There are other properties and methods that trigger layout; see gent.ilcore.com/2011/03/how-not-to-trigger-layout-in-webkit

正如结果所示,这一行—更确切地说,是引进这一行所做的改动—大量记载了有关于为什么它是必要的,为什么先前的方法(指的是通过提交SHA)不奏效,哪些浏览器受到影响以及进一步阅读的链接这些信息。

上面的结果也显示,这行莫名其妙的代码的作者就是我,实际上我有方法将这行代码本身写的更好:通过在函数中用意图明显的名字如triggerLayout()封装magic属性的访问,或者至少通过添加触发动画这样一个简短的解释作为代码注释。不管什么原因,我那天就是没能让这段特殊的代码赋有表达力。这样的代码发生了,就不总是完美的。

即使这段代码更具表达力,或者它已经包含了数行代码注释,项目的历史都能提供更加丰富的信息:
1. 谁添加的这行代码;
2. 他们什么时候添加的这行代码;
3. 哪部分是accompanying test(如果有的话);
4. 完整的提交信息可以是整部小说(但其中的代码注释应该保持简洁)。

代码质量仍然十分重要。但是在琢磨如何进一步提高你的编码水平时,你应该考虑完成更好的提交信息。你不仅仅应该请求你自己,还应该请求整个团队甚至所有的贡献者做到这一点。一个软件的故事与其最新的检出一样重要

对项目历史有效的深层次探索

git blame

我已经在上面的命令中证明了如何使用git blame。当你不能够访问本地git库时,你也可以打开GitHub上任何文件的“Blame”视图。

一个非常有效的探索文件历史的方法是使用Vim和Fugitive:
1. 在缓冲区里使用:Gblame打开blame视图;
2. 如果你需要进一步探索,在blame面板那行按下Shift-P在那次提交的parent上重新blame;
3. 按下o打开一个拆分面板显示出blame面板当前选中的提交信息。
4. 在提交分区使用:Gbrowse打开GitHub web接口的commit;
5. 按下gq关闭blame面板返回到主缓冲区。

每一行代码都有记录—如何用git一步步探索项目的历史

:help Gblame了解更多信息。

找到一次commit起源的pull request

用git blame你可以获得每次改动后提交的SHA值,但是提交信息并不总是携带足够的信息或上下文来解释此次改动背后的原因和依据。无论如何,如果项目背后的团队练习过GitHub Flow,那么上下文就可能在pull request讨论里找到:

1
2
3
4
$ git log --merges --ancestry-path --oneline <SHA>..origin | tail
...
bc4712d Merge pull request #42 from sticky-sidebar
3f883f0 Merge branch 'master' into sticky-sidebar

这里,单次提交的SHA值就足以发现它起源于pull request #42。

git的pickaxe选项-S

有时你会试图找出已丢失的东西:例如,一个函数过去的某一次调用,该函数如今不再被任何地方调用。找出是哪次提交引进或删除某个关键字的最好方法就是使用git log的“pickaxe”参数:

1
$ git log -S<string>

利用该方法你可以探索出一些提交信息,例如删除了对某个函数的调用或添加了某个CSS类名。

git churn

从一个项目的历史不仅要观察个人提交信息,通过整体分析改动集也有可能获得宝贵的见解。例如,用来包装git log的git-churn是一个简单的却有价值的脚本,可以汇编统计出哪些文件改动最多。例如为了看一个app开发中哪个地方在过去的6个月里受到重点关注:

1
$ git churn --since='6 months ago' app/ | tail

顺便说一下,这样的分析也强调了在一个项目中因技术债可能出现的潜在问题。经常改动的某个文件通常标注一个红旗,因为这可能意味着那个文件里的代码要么需要频繁的修复bug,要么那个文件通常承载了太多的任务,应该被分割为更小的单元。

相似的历史分析方法可以被用于观察谁近来负责代码库某个部分的开发。例如,为了看谁对一个应用的API部分贡献的最频繁:

1
2
3
4
5
6
$ git log --format='%an' --since='6 months ago' app/controllers/api/ | \
    sort | uniq -c | sort -rn | head
 
 109 Edmond Dantès
  13 Jonathan Livingston
   7 Ebanezer Scrooge

成为历史的正面信息

记住你今天所做的每件事情都会进入项目的历史并永久保存在那儿。为了更加善待与你一起工作的其他人(即使它是一个单独的项目,3个月期限,包括自己在内),在做提交时请遵循下面这些基本规则:

  • 总是编写提交信息,好像你在向正坐在你旁边且完全不知道整个事情缘由的同事解释此次改动。每个Thoughtbot给出的提示都是为了更好的提交信息:回答下面的问题:
    • 为什么此次改动是必需的?
    • 它是如何解决问题的?
    • 这次改动有什么副作用?
    • 考虑包含一个[to the discussion.] 的链接。
  • 避免在一次提交中有不相关的改动。你可能在一个已经做了其它改动的同一个文件里发现了一个错字或做了微小的代码重构,但是一定要抵制诱惑与主要的改动一起记录,除非它们是直接相关的。
  • 在push之前总是清除你的历史。如果提交还没有被共享,那么重新修订它们中不好的部分是安全的。下面的可能是Faraday项目的永久历史,但是我最终把它压缩为仅2次提交,并编辑它们的信息以隐藏我首次设置脚本遇到了麻烦这样一个事实。
    每一行代码都有记录—如何用git一步步探索项目的历史
  • 避免不相关改动间的推导:坚持基于行的编码风格,允许你添加,编辑或删除列表中的值而不用改动相邻行。一些例子:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var one = "foo"
        , two = "bar"
        , three = "baz"   // Comma-first style allows us to add or remove a
                          // new variable without touching other lines
     
      # Ruby:
      result = make_http_request(
        :method => 'POST',
        :url => api_url,
        :body => '...',   // Ruby allows us to leave a trailing comma, making it
      )                   // possible to add/remove params while not touching others

    你为什么要使用这样的编码风格?好吧,总得想想将要git blame这个的人们。在JavaScript的例子里,如果你要添加一个值“baz”然后提交,当有人blame添加“bar”的行时你不想你的名字呈现把,因为这两个变量可能不相关。

奖励脚本

既然你已经读到这里,我将奖励你一个额外的脚本。我称之为git-overwritten,它能够显示出指定分支处所改动或删除的行其原始作者的blame信息:

1
2
3
4
5
$ git overwritten feature origin/master
 
  28 2014-02-04 1fb2633  Mislav Marohni?: Add Makefile for building and testing
   1 2014-01-13 b2d896a  Jingwen Owen Ou: Add -t to mktemp in script/make
  17 2014-01-07 385ccee  Jingwen Owen Ou: Add script/make for homebrew build

当打开每个GitHub Flow的pull
requests时,这是非常有用的;你要是想要你的pull
request被同伴审查,但是你可能不太确定该ping谁,使用git-overwritten你可以获得你刚刚改动代码行的原始作者名字,所以你就会
知道当打开一个pull request时@-mention谁了。

声明:本文编译自Mislav Marohnić,已投稿给伯乐在线

上一篇:Eclipse - 修改默认user和类的创建日期


下一篇:使用深度学习来破解 captcha 验证码(转)