== 關於歷史 ==

Git分散式本性使得歷史可以輕易編輯。但你若篡改過去，需要小心：只重寫你獨自擁有
的那部分。正如民族間會無休止的爭論誰犯下了什麼暴行一樣，如果在另一個人的克隆
裡，歷史版本與你的不同，當你們的樹互操作時，你會遇到一致性方面的問題。

一些開發人員強烈地感覺歷史應該永遠不變，不好的部分也不變所有都不變。另一些覺
得代碼樹在向外發佈之前，應該整得漂漂亮亮的。Git同時支持兩者的觀點。像克隆，分
支和合併一樣，重寫歷史只是Git給你的另一強大功能，至于如何明智地使用它，那是你
的事了。

=== 我認錯 ===

剛提交，但你期望你輸入的是一條不同的信息？那麼鍵入：

 $ git commit --amend

來改變上一條信息。意識到你還忘記了加一個檔案？運行git add來加，然後運行上面的
命令。

希望在上次提交裡包括多一點的改動？那麼就做這些改動並運行：

 $ git commit --amend -a

=== 更複雜情況 ===

假設前面的問題還要糟糕十倍。在漫長的時間裡我們提交了一堆。但你不太喜歡他們的
組織方式，而且一些提交信息需要重寫。那麼鍵入：

 $ git rebase -i HEAD~10

並且後10個提交會出現在你喜愛的$EDITOR。一個例子：

    pick 5c6eb73 Added repo.or.cz link
    pick a311a64 Reordered analogies in "Work How You Want"
    pick 100834f Added push target to Makefile

之後：

- 通過刪除行來移去提交。
- 通過為行重新排序行來重新排序提交。
- 替換 `pick` 使用：
   * `edit` 標記一個提交需要修訂。
   * `reword` 改變日誌信息。
   * `squash` 將一個提交與其和前一個合併。
   * `fixup` 將一個提交與其和前一個合併，並丟棄日誌信息。

保存退出。如果你把一個提交標記為可編輯，那麼運行

 $ git commit --amend

否則，運行：

 $ git rebase --continue

這樣儘早提交，經常提交：你之後還可以用rebase來規整。

=== 本地變更之後 ===

你正在一個活躍的項目上工作。隨着時間推移，你做了幾個本地提交，然後你使用合併
與官方版本同步。在你準備好提交到中心分支之前，這個循環會重複幾次。

但現在你本地Git克隆摻雜了你的改動和官方改動。你更期望在變更列表裡，你所有的變
更能夠連續。

這就是上面提到的 *git rebase* 所做的工作。在很多情況下你可以使用 *--onto* 標
記以避免交互。

另外參見 *git help rebase* 以獲取這個讓人驚奇的命令更詳細的例子。你可以拆分提
交。你甚至可以重新組織一棵樹的分支。

=== 重寫歷史 ===

偶爾，你需要做一些代碼控制，好比從正式的照片中去除一些人一樣，需要從歷史記錄
裡面徹底的抹掉他們。例如，假設我們要發佈一個項目，但由於一些原因，項目中的某
個檔案不能公開。或許我把我的信用卡號記錄在了一個文本檔案裡，而我又意外的把它
加入到了這個項目中。僅僅刪除這個檔案是不夠的，因為從別的提交記錄中還是可以訪
問到這個檔案。因此我們必須從所有的提交記錄中徹底刪除這個檔案。

 $ git filter-branch --tree-filter 'rm top/secret/file' HEAD

參見 *git help filter-branch* ，那裡討論了這個例子並給出一個更快的方法。一般
地， *filter-branch* 允許你使用一個單一命令來大範圍地更改歷史。

此後，+.git/refs/original+目錄描述操作之前的狀態。檢查命令filter-branch的確做
了你想要做的，然後刪除此目錄，如果你想運行多次filter-branch命令。

最後，用你修訂過的版本替換你的項目克隆，如果你想之後和它們交互的話。

=== 製造歷史 ===

[[makinghistory]]
想把一個項目遷移到Git嗎？如果這個項目是在用比較有名氣的系統，那可以使用一些其
他人已經寫好的腳本，把整個項目歷史記錄導出來放到Git裡。

否則，查一下 *git fast-import* ，這個命令會從一個特定格式的文本讀入，從頭來創
建Git歷史記錄。通常可以用這個命令很快寫一個腳本運行一次，一次遷移整個項目。

作為一個例子，粘貼以下所列到臨時檔案，比如/tmp/history：

----------------------------------
commit refs/heads/master
committer Alice <alice@example.com> Thu, 01 Jan 1970 00:00:00 +0000
data <<EOT
Initial commit.
EOT

M 100644 inline hello.c
data <<EOT
#include <stdio.h>

int main() {
  printf("Hello, world!\n");
  return 0;
}
EOT


commit refs/heads/master
committer Bob <bob@example.com> Tue, 14 Mar 2000 01:59:26 -0800
data <<EOT
Replace printf() with write().
EOT

M 100644 inline hello.c
data <<EOT
#include <unistd.h>

int main() {
  write(1, "Hello, world!\n", 14);
  return 0;
}
EOT

----------------------------------

之後從這個臨時檔案創建一個Git倉庫，鍵入：

 $ mkdir project; cd project; git init
 $ git fast-import --date-format=rfc2822 < /tmp/history

你可以從這個項目checkout出最新的版本，使用：

 $ git checkout master .

命令*git fast-export* 轉換任意倉庫到 *git fast-import* 格式，你可以研究其輸
出來寫導出程序， 也以可讀格式傳送倉庫。的確，這些命令可以發送倉庫文本檔案
通過只接受文本的渠道。


=== 哪兒錯了？ ===

你剛剛發現程序裡有一個功能出錯了，而你十分確定幾個月以前它運行的很正常。天啊！
這個臭蟲是從哪裡冒出來的？要是那時候能按照開發的內容進行過測試該多好啊。

現在說這個已經太晚了。然而，即使你過去經常提交變更，Git還是可以精確的找出問題所在：

 $ git bisect start
 $ git bisect bad HEAD
 $ git bisect good 1b6d

Git從歷史記錄中檢出一個中間的狀態。在這個狀態上測試功能，如果還是有問題：

 $ git bisect bad

如果可以工作了，則把"bad"替換成"good"。Git會再次幫你找到一個以確定的好版本和
壞版本之間的狀態，通過這種方式縮小範圍。經過一系列的迭代，這種二分搜索會幫你
找到導致這個錯誤的那次提交。一旦完成了問題定位的調查，你可以返回到原始狀態，
鍵入：

 $ git bisect reset

不需要手工測試每一次改動，執行如下命令可以自動的完成上面的搜索：

 $ git bisect run my_script

Git使用指定命令（通常是一個一次性的腳本）的返回值來決定一次改動是否是正確的：
命令退出時的代碼0代表改動是正確的，125代表要跳過對這次改動的檢查，1到127之間
的其他數值代表改動是錯誤的。返回負數將會中斷整個bisect的檢查。

你還能做更多的事情: 幫助文檔解釋了如何展示bisects, 檢查或重放bisect的日誌,並
可以通過排除對已知正確改動的檢查，得到更好的搜索速度。

=== 誰讓事情變糟了？ ===

和其他許多版本控制系統一樣，Git也有一個"blame"命令：

 $ git blame bug.c

這個命令可以標註出一個指定的檔案裡每一行內容的最後修改者，和最後修改時間。但
不像其他版本控制系統，Git的這個操作是在綫下完成的，它只需要從本地磁碟讀取信息。

=== 個人經驗 ===

在一個中心版本控制系統裡，歷史的更改是一個困難的操作，並且只有管理員才有權這
麼做。沒有網絡，克隆，分支和合併都沒法做。像一些基本的操作如瀏覽歷史，或提交
變更也是如此。在一些系統裡，用戶使用網絡連接僅僅是為了查看他們自己的變更，或
打開檔案進行編輯。

中心繫統排斥離線工作，也需要更昂貴的網絡設施，特別是當開發人員增多的時候。最
重要的是，所有操作都一定程度變慢，一般在用戶避免使用那些能不用則不用的高級命
令時。在極端的情況下，即使是最基本的命令也會變慢。當用戶必須運行緩慢的命令的
時候，由於工作流被打斷，生產力降低。

我有這些的一手經驗。Git是我使用的第一個版本控制系統。我很快學會適應了它，用了
它提供的許多功能。我簡單地假設其他系統也是相似的：選擇一個版本控制系統應該和
選擇一個編輯器或瀏覽器沒啥兩樣。

在我之後被迫使用中心繫統的時候，我被震驚了。我那有些脆弱的網絡沒給Git帶來大麻
煩，但是當它需要像本地硬碟一樣穩定的時候，它使開發困難重重。另外，我發現我自
己有選擇地避免特定的命令，以避免踏雷，這極大地影響了我，使我不能按照我喜歡的
方式工作。

當我不得不運行一個慢的命令的時候，這種等待極大地破壞了我思緒連續性。在等待服
務器通訊完成的時候，我選擇做其他的事情以度過這段時光，比如查看郵件或寫其他的
文檔。當我返回我原先的工作場景的時候，這個命令早已結束，並且我還需要浪費時間
試圖記起我之前正在做什麼。人類不擅長場景間的切換。

還有一個有意思的大眾悲劇效應：預料到網絡擁擠，為了減少將來的等待時間，每個人
將比以往消費更多的頻寬在各種操作上。共同的努力加劇了擁擠，這等於是鼓勵個人下
次消費更多頻寬以避免更長時間的等待。


