## 使用 Git 調試
Git 也提供了兩個工具來輔助你調試項目中的問題。 由于 Git 被設計成適用于幾乎所有類型的項目,這些工具是比較通用的,但它們可以在出現問題的時候幫助你找到 bug 或者錯誤。
## 文件標注
如果你在追蹤代碼中的一個 bug,并且想知道是什么時候以及為何會引入,文件標注通常是最好用的工具。 它展示了文件中每一行最后一次修改的提交。 所以,如果你在代碼中看到一個有問題的方法,你可以使用?`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-1 值的不同含義,但這里確實就是這個意思。
另一件比較酷的事情是 Git 不會顯式地記錄文件的重命名。 它會記錄快照,然后在事后嘗試計算出重命名的動作。 這其中有一個很有意思的特性就是你可以讓 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 會告訴你,你第一次寫這幾行代碼的那個提交才是原始提交,即使這是在另外一個文件里寫的。
## 二分查找
當你知道問題是在哪里引入的情況下文件標注可以幫助你查找問題。 如果你不知道哪里出了問題,并且自從上次可以正常運行到現在已經有數十個或者上百個提交,這個時候你可以使用?`git bisect`來幫助查找。?`bisect`?命令會對你的提交歷史進行二分查找來幫助你盡快找到是哪一個提交引入了問題。
假設你剛剛在線上環境部署了你的代碼,接著收到一些 bug 反饋,但這些 bug 在你之前的開發環境里沒有出現過,這讓你百思不得其解。 你重新查看了你的代碼,發現這個問題是可以被重現的,但是你不知道哪里出了問題。 你可以用二分法來找到這個問題。 首先執行?`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 檢出中間的那個提交。 現在你可以執行測試,看看在這個提交下問題是不是還是存在。 如果還存在,說明問題是在這個提交之前引入的;如果問題不存在,說明問題是在這個提交之后引入的。 假設測試結果是沒有問題的,你可以通過?`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 值并顯示一些提交說明,以及哪些文件在那次提交里修改過,這樣你可以找出引入 bug 的根源:
~~~
$ 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
~~~
這是一個可以幫助你在幾分鐘內從數百個提交中找到 bug 的強大工具。 事實上,如果你有一個腳本在項目是正常的情況下返回 0,在不正常的情況下返回非 0,你可以使?`git bisect`?自動化這些操作。 首先,你設定好項目正常以及不正常所在提交的二分查找范圍。 你可以通過?`bisect start`命令的參數來設定這兩個提交,第一個參數是項目不正常的提交,第二個參數是項目正常的提交:
~~~
$ git bisect start HEAD v1.0
$ git bisect run test-error.sh
~~~
Git 會自動在每個被檢出的提交里執行?`test-error.sh`?直到找到第一個項目不正常的提交。 你也可以執行?`make`?或者?`make tests`?或者其他東西來進行自動化測試。
- 前言
- Scott Chacon 序
- Ben Straub 序
- 獻辭
- 貢獻者
- 引言
- 1. 起步
- 1.1 關于版本控制
- 1.2 Git 簡史
- 1.3 Git 基礎
- 1.4 命令行
- 1.5 安裝 Git
- 1.6 初次運行 Git 前的配置
- 1.7 獲取幫助
- 1.8 總結
- 2. Git 基礎
- 2.1 獲取 Git 倉庫
- 2.2 記錄每次更新到倉庫
- 2.3 查看提交歷史
- 2.4 撤消操作
- 2.5 遠程倉庫的使用
- 2.6 打標簽
- 2.7 Git 別名
- 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 Git 守護進程
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 第三方托管的選擇
- 4.10 總結
- 5. 分布式 Git
- 5.1 分布式工作流程
- 5.2 向一個項目貢獻
- 5.3 維護項目
- 5.4 總結
- 6. GitHub
- 6.1 賬戶的創建和配置
- 6.2 對項目做出貢獻
- 6.3 維護項目
- 6.4 管理組織
- 6.5 腳本 GitHub
- 6.6 總結
- 7. Git 工具
- 7.1 選擇修訂版本
- 7.2 交互式暫存
- 7.3 儲藏與清理
- 7.4 簽署工作
- 7.5 搜索
- 7.6 重寫歷史
- 7.7 重置揭密
- 7.8 高級合并
- 7.9 Rerere
- 7.10 使用 Git 調試
- 7.11 子模塊
- 7.12 打包
- 7.13 替換
- 7.14 憑證存儲
- 7.15 總結
- 8. 自定義 Git
- 8.1 配置 Git
- 8.2 Git 屬性
- 8.3 Git 鉤子
- 8.4 使用強制策略的一個例子
- 8.5 總結
- 9. Git 與其他系統
- 9.1 作為客戶端的 Git
- 9.2 遷移到 Git
- 9.3 總結
- 10. Git 內部原理
- 10.1 底層命令和高層命令
- 10.2 Git 對象
- 10.3 Git 引用
- 10.4 包文件
- 10.5 引用規格
- 10.6 傳輸協議
- 10.7 維護與數據恢復
- 10.8 環境變量
- 10.9 總結
- A. 其它環境中的 Git
- A1.1 圖形界面
- A1.2 Visual Studio 中的 Git
- A1.3 Eclipse 中的 Git
- A1.4 Bash 中的 Git
- A1.5 Zsh 中的 Git
- A1.6 Powershell 中的 Git
- A1.7 總結
- B. 將 Git 嵌入你的應用
- A2.1 命令行 Git 方式
- A2.2 Libgit2
- A2.3 JGit
- C. Git 命令
- A3.1 設置與配置
- A3.2 獲取與創建項目
- A3.3 快照基礎
- A3.4 分支與合并
- A3.5 項目分享與更新
- A3.6 檢查與比較
- A3.7 調試
- A3.8 補丁
- A3.9 郵件
- A3.10 外部系統
- A3.11 管理
- A3.12 底層命令