Git 同樣提供了一些工具來幫助你調試項目中遇到的問題。由于 Git 被設計為可應用于幾乎任何類型的項目,這些工具是通用型,但是在遇到問題時可以經常幫助你查找缺陷所在。
## 文件標注
如果你在追蹤代碼中的缺陷想知道這是什么時候為什么被引進來的,文件標注會是你的最佳工具。它會顯示文件中對每一行進行修改的最近一次提交。因此,如果你發現自己代碼中的一個方法存在缺陷,你可以用git blame來標注文件,查看那個方法的每一行分別是由誰在哪一天修改的。下面這個例子使用了-L選項來限制輸出范圍在第12至22行:
~~~
$ git blame -L 12,22 simplegit.rb
^4832fe2 (Scott Chacon 2008-03-15 10:31:28 -0700 12) def show(tree = 'master')
^4832fe2 (Scott Chacon 2008-03-15 10:31:28 -0700 13) command("git show #{tree}")
^4832fe2 (Scott Chacon 2008-03-15 10:31:28 -0700 14) end
^4832fe2 (Scott Chacon 2008-03-15 10:31:28 -0700 15)
9f6560e4 (Scott Chacon 2008-03-17 21:52:20 -0700 16) def log(tree = 'master')
79eaf55d (Scott Chacon 2008-04-06 10:15:08 -0700 17) command("git log #{tree}")
9f6560e4 (Scott Chacon 2008-03-17 21:52:20 -0700 18) end
9f6560e4 (Scott Chacon 2008-03-17 21:52:20 -0700 19)
42cf2861 (Magnus Chacon 2008-04-13 10:45:01 -0700 20) def blame(path)
42cf2861 (Magnus Chacon 2008-04-13 10:45:01 -0700 21) command("git blame #{path}")
42cf2861 (Magnus Chacon 2008-04-13 10:45:01 -0700 22) end
~~~
請注意第一個域里是最后一次修改該行的那次提交的 SHA-1 值。接下去的兩個域是從那次提交中抽取的值——作者姓名和日期——所以你可以方便地獲知誰在什么時候修改了這一行。在這后面是行號和文件的內容。請注意^4832fe2提交的那些行,這些指的是文件最初提交的那些行。那個提交是文件第一次被加入這個項目時存在的,自那以后未被修改過。這會帶來小小的困惑,因為你已經至少看到了Git使用^來修飾一個提交的SHA值的三種不同的意義,但這里確實就是這個意思。
另一件很酷的事情是在 Git 中你不需要顯式地記錄文件的重命名。它會記錄快照然后根據現實嘗試找出隱式的重命名動作。這其中有一個很有意思的特性就是你可以讓它找出所有的代碼移動。如果你在git blame后加上-C,Git會分析你在標注的文件然后嘗試找出其中代碼片段的原始出處,如果它是從其他地方拷貝過來的話。最近,我在將一個名叫`GITServerHandler.m`的文件分解到多個文件中,其中一個是GITPackUpload.m。通過對GITPackUpload.m執行帶-C參數的blame命令,我可以看到代碼塊的原始出處:
~~~
$ git blame -C -L 141,153 GITPackUpload.m
f344f58d GITServerHandler.m (Scott 2009-01-04 141)
f344f58d GITServerHandler.m (Scott 2009-01-04 142) - (void) gatherObjectShasFromC
f344f58d GITServerHandler.m (Scott 2009-01-04 143) {
70befddd GITServerHandler.m (Scott 2009-03-22 144) //NSLog(@"GATHER COMMI
ad11ac80 GITPackUpload.m (Scott 2009-03-24 145)
ad11ac80 GITPackUpload.m (Scott 2009-03-24 146) NSString *parentSha;
ad11ac80 GITPackUpload.m (Scott 2009-03-24 147) GITCommit *commit = [g
ad11ac80 GITPackUpload.m (Scott 2009-03-24 148)
ad11ac80 GITPackUpload.m (Scott 2009-03-24 149) //NSLog(@"GATHER COMMI
ad11ac80 GITPackUpload.m (Scott 2009-03-24 150)
56ef2caf GITServerHandler.m (Scott 2009-01-05 151) if(commit) {
56ef2caf GITServerHandler.m (Scott 2009-01-05 152) [refDict setOb
56ef2caf GITServerHandler.m (Scott 2009-01-05 153)
~~~
這真的非常有用。通常,你會把你拷貝代碼的那次提交作為原始提交,因為這是你在這個文件中第一次接觸到那幾行。Git可以告訴你編寫那些行的原始提交,即便是在另一個文件里。
## 二分查找
標注文件在你知道問題是哪里引入的時候會有幫助。如果你不知道,并且自上次代碼可用的狀態已經經歷了上百次的提交,你可能就要求助于bisect命令了。bisect會在你的提交歷史中進行二分查找來盡快地確定哪一次提交引入了錯誤。
例如你剛剛推送了一個代碼發布版本到產品環境中,對代碼為什么會表現成那樣百思不得其解。你回到你的代碼中,還好你可以重現那個問題,但是找不到在哪里。你可以對代碼執行bisect來尋找。首先你運行git bisect start啟動,然后你用git bisect bad來告訴系統當前的提交已經有問題了。然后你必須告訴bisect已知的最后一次正常狀態是哪次提交,使用`git bisect good [good_commit]:`
~~~
$ git bisect start
$ git bisect bad
$ git bisect good v1.0
Bisecting: 6 revisions left to test after this
[ecb6e1bc347ccecc5f9350d878ce677feb13d3b2] error handling on repo
~~~
Git 發現在你標記為正常的提交(v1.0)和當前的錯誤版本之間有大約12次提交,于是它檢出中間的一個。在這里,你可以運行測試來檢查問題是否存在于這次提交。如果是,那么它是在這個中間提交之前的某一次引入的;如果否,那么問題是在中間提交之后引入的。假設這里是沒有錯誤的,那么你就通過`git bisect good`來告訴 Git 然后繼續你的旅程:
~~~
$ git bisect good
Bisecting: 3 revisions left to test after this
[b047b02ea83310a70fd603dc8cd7a6cd13d15c04] secure this thing
~~~
現在你在另外一個提交上了,在你剛剛測試通過的和一個錯誤提交的中點處。你再次運行測試然后發現這次提交是錯誤的,因此你通過git bisect bad來告訴Git:
~~~
$ git bisect bad
Bisecting: 1 revisions left to test after this
[f71ce38690acf49c1f3c9bea38e09d82a5ce6014] drop exceptions table
~~~
這次提交是好的,那么 Git 就獲得了確定問題引入位置所需的所有信息。它告訴你第一個錯誤提交的 SHA-1 值并且顯示一些提交說明以及哪些文件在那次提交里修改過,這樣你可以找出缺陷被引入的根源:
~~~
$ git bisect good
b047b02ea83310a70fd603dc8cd7a6cd13d15c04 is first bad commit
commit b047b02ea83310a70fd603dc8cd7a6cd13d15c04
Author: PJ Hyett <pjhyett@example.com>
Date: Tue Jan 27 14:48:32 2009 -0800
secure this thing
:040000 040000 40ee3e7821b895e52c1695092db9bdc4c61d1730
f24d3c6ebcfc639b1a3814550e62d60b8e68a8e4 M config
~~~
當你完成之后,你應該運行git bisect reset來重設你的HEAD到你開始前的地方,否則你會處于一個詭異的地方:
`$ git bisect reset`
這是個強大的工具,可以幫助你檢查上百的提交,在幾分鐘內找出缺陷引入的位置。事實上,如果你有一個腳本會在工程正常時返回0,錯誤時返回非0的話,你可以完全自動地執行git bisect。首先你需要提供已知的錯誤和正確提交來告訴它二分查找的范圍。你可以通過`bisect start`命令來列出它們,先列出已知的錯誤提交再列出已知的正確提交:
~~~
$ git bisect start HEAD v1.0
$ git bisect run test-error.sh
~~~
這樣會自動地在每一個檢出的提交里運行test-error.sh直到Git找出第一個破損的提交。你也可以運行像make或者make tests或者任何你所擁有的來為你執行自動化的測試。
- 1. 起步
- 1.1 關于版本控制
- 1.2 Git 簡史
- 1.3 Git 基礎
- 1.4 安裝 Git
- 1.5 初次運行 Git 前的配置
- 1.6 獲取幫助
- 1.7 小結
- 2. Git基礎
- 2.1 取得項目的 Git 倉庫
- 2.2 記錄每次更新到倉庫
- 2.3 查看提交歷史
- 2.4 撤消操作
- 2.5 遠程倉庫的使用
- 2.6 打標簽
- 2.7 技巧和竅門
- 2.8 小結
- 3. Git分支
- 3.1 何謂分支
- 3.2 分支的新建與合并
- 3.3 分支的管理
- 3.4 利用分支進行開發的工作流程
- 3.5 遠程分支
- 3.6 分支的衍合
- 3.7 小結
- 4. 服務器上的Git
- 4.1 協議
- 4.2 在服務器上部署 Git
- 4.3 生成 SSH 公鑰
- 4.4 架設服務器
- 4.5 公共訪問
- 4.6 GitWeb
- 4.7 Gitosis
- 4.8 Gitolite
- 4.9 Git 守護進程
- 4.10 Git 托管服務
- 4.11 小結
- 5. 分布式Git
- 5.1 分布式工作流程
- 5.2 為項目作貢獻
- 5.3 項目的管理
- 5.4 小結
- 6. Git工具
- 6.1 修訂版本(Revision)選擇
- 6.2 交互式暫存
- 6.3 儲藏(Stashing)
- 6.4 重寫歷史
- 6.5 使用 Git 調試
- 6.6 子模塊
- 6.7 子樹合并
- 6.8 總結
- 7. 自定義Git
- 7.1 配置 Git
- 7.2 Git屬性
- 7.3 Git掛鉤
- 7.4 Git 強制策略實例
- 7.5 總結
- 8. Git與其他系統
- 8.1 Git 與 Subversion
- 8.2 遷移到 Git
- 8.3 總結
- 9. Git 內部原理
- 9.2 Git 對象
- 9.3 Git References
- 9.4 Packfiles
- 9.5 The Refspec
- 9.6 傳輸協議
- 9.7 維護及數據恢復
- 9.8 總結
- 9.1 底層命令 (Plumbing) 和高層命令 (Porcelain)