Git最佳实践:原子性提交(atomic commits)

什么是原子性提交?

作为版本控制的最佳实践之一,不管你是用什么工具,都应该保持原子性提交。在百科中对原子性的定义是:

原子性:在一个大型系统中,形成一个不可分割的最简单元或组件。

当代码变动时你想创建提交时,这个提交应该尽可能的小量,并且包含一个不可分割的特性(feature)、修复(fix)或优化(improved)。以下是创建一个简单的联系人表单的git log样例:

  • Create HTML for form
  • Style form
  • Add HTML5 validation
  • Fix unrelated JS bug
  • Add ajax submit to form with mock server results from PHP
  • Add JS validation to form
  • Send form results via email
  • Log form results to database
  • Style form validation and success/error messages


每个提交都是添加了一个最小基本的特性、修复活着对特性或代码的小改善。另外提交日志输入时使用『语义化日志风格』(semantic commit message style) 会帮助我们的提交更加原子化。

如果你打算基于多次提交做pull request的时候,相当于把多个小特性合并成一个大特性,比如:Create working contact form"。关于pull request的操作实践可以参考这篇文章 Creating good pull requests

为什么要原子性提交

Code reviews会更简单

当你像这样保持小量提交时,别人review你代码或检查每次递增的变动时会更容易。

更容易回滚

有时候你开发了一个特性,但是却跟不相关的bug修复操作混在一起提交了。然后之后发现这个特性不够好决定回滚的时候,这就会出问题了。所以提交特性如果只是包含这个特性的话,回滚时候会更容易处理。但是如果它跟bug修复混在一起再回滚,就会非常麻烦,尤其是如果处理这个回滚的人是其他人而不是你的时候。

提交变动文件部分

在尝试实践原子性提交的时候,最大的问题是经常在当前任务的时候必须做一些不相关的代码变动。有个比较好的解决办法是,在需要变动的地方写下简单的Todo,等当前任务完成并且提交之后,再来实现这部分的代码。

提交当前任务的时候,如果不想把其他变动的文件混在这次提交中,你可以先使用git add命令把相关文件添加到stage暂存再提交。

例如:

$ git st

 M contact.html

 M contact.php

$ git add contact.html

$ git st

 A contact.html

 M contact.php

$ git ci -m "Add required attribute to form fields"

如果跟当前任务不相干的变动在同一个文件时候,你可以使用git add --patch 或者 git add -p 来实现文件局部区块的提交。

在使用git add命令的时候加上patch参数,它会显示文件中的第一个变动,并且会询问是否要暂存到stage区。

$ git add -p

diff --git a/coffee/forms.coffee b/coffee/forms.coffee

index a9f4d6d..40a8ed8 100644

--- a/coffee/forms.coffee

+++ b/coffee/forms.coffee

@@ -15,6 +15,7 @@ window.xhrForms =

       $this = $(@)

       $this.mask $this.data("mask")



+  # Submit handler for our ajax forms

   handleSubmit: (form) ->

     $form = $(form)

     url = $form.attr "action"

Stage this hunk [y,n,a,d,/,j,J,g,e,?]? ?

y - stage this hunk

n - do not stage this hunk

a - stage this and all the remaining hunks in the file

d - do not stage this hunk nor any of the remaining hunks in the file

g - select a hunk to go to

/ - search for a hunk matching the given regex

j - leave this hunk undecided, see next undecided hunk

J - leave this hunk undecided, see next hunk

k - leave this hunk undecided, see previous undecided hunk

K - leave this hunk undecided, see previous hunk

s - split the current hunk into smaller hunks

e - manually edit the current hunk

? - print help

Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]? n

然后会跳到下一个变动区块,然后我们可以选择yes。

@@ -40,7 +41,7 @@ window.xhrForms =

     $msg = $(".form-notice", $form)

     if !$msg.length

       $msg = $("<div/>").addClass("form-notice").prependTo($form)

-    $msg.removeClass "form-success"

+    $msg.removeClass "form-success form-error"

     if data.status

       $msg.addClass "form-success"

     else

Stage this hunk [y,n,q,a,d,/,K,g,e,?]? y

如果我们再来检查状态,会发现我们的文件被局部暂存了。

最后我们可以提交我们刚才暂存的代码变动了。

$git commit -m "Remove form-error class too"

## 不要找借口

除了一些比较相互影响比较复杂的变动,一般来说,原子性提交是非常简单的。所以也没有理由再把bug修复混在特性开发中了。持续保持原子性提交的习惯,会对你的工作流很有好处,并且后续的开发者也会非常感谢你的。