== Git大師技 ==

到現在，你應該有能力查閲 *git help* 頁，並理解几乎所有東西。然而，查明解決特
定問題需要的確切命令可能是乏味的。或許我可以省你點功夫：以下是我過去曾經需要
的一些食譜。

=== 源碼發佈 ===

就我的項目而言，Git完全跟蹤了我想打包並發佈給用戶的檔案。創建一個源碼包，我運
行：

 $ git archive --format=tar --prefix=proj-1.2.3/ HEAD

=== 提交變更  ===

對特定項目而言，告訴Git你增加，刪除和重命名了一些檔案很麻煩。而鍵入如下命令會容易的多：

 $ git add .
 $ git add -u

Git將查找當前目錄的檔案並自己算出具體的情況。除了用第二個add命令，如果你也打
算這時提交，可以運行`git commit -a`。關於如何指定應被忽略的檔案，參見 *git
help ignore* 。

你也可以用一行命令完成以上任務：

 $ git ls-files -d -m -o -z | xargs -0 git update-index --add --remove

這裡 *-z* 和 *-0* 選項可以消除包含特殊字元的檔案名引起的不良副作用。注意這個
命令也添加應被忽略的檔案，這時你可能需要加上 `-x` 或 `-X` 選項。

=== 我的提交太大了！ ===

是不是忽視提交太久了？痴迷地編碼，直到現在才想起有源碼控制工具這回事？提交一
系列不相關的變更，因為那是你的風格？

別擔心，運行：

 $ git add -p

為你做的每次修改，Git將展示給你變動的代碼，並詢問該變動是否應是下一次提交的一
部分。回答“y”或者“n”。也有其他選項，比如延遲決定；鍵入“？”來學習更多。

一旦你滿意，鍵入

 $ git commit

來精確地提交你所選擇的變更（階段變更）。確信你沒加上 *-a* 選項，否則Git將提交
所有修改。

如果你修改了許多地方的許多檔案怎麼辦？一個一個地查看變更令人沮喪，心態麻木。
這種情況下，使用 *git add -i* ， 它的界面不是很直觀，但更靈活。敲幾個鍵，你可
以一次決定階段或非階段性提交幾個檔案，或查看並只選擇特定檔案的變更。作為另一
種選擇，你還可以運行 *git commit --interactive* ，這個命令會在你操作完後自動
進行提交。

=== 索引：Git的中轉區域 ===

當目前為止，我們已經忽略Git著名的'索引‘概念，但現在我們必須面對它，以解釋上
面發生的。索引是一個臨時中轉區。Git很少在你的項目和它的歷史之間直接倒騰數據。
通常，Git先寫數據到索引，然後拷貝索引中的數據到最終目的地。

例如， *commit -a* 實際上是一個兩步過程。第一步把每個追蹤檔案當前狀態的快照放
到索引中。第二步永久記錄索引中的快照。 沒有 *-a* 的提交只執行第二步，並且只在
運行不知何故改變索引的命令才有意義，比如 *git add* 。

通常我們可以忽略索引並假裝從歷史中直接讀並直接寫。在這個情況下，我們希望更好
地控制，因此我們操作索引。我們放我們變更的一些的快照到索引中，而不是所有的，
然後永久地記錄這個小心操縱的快照。

=== 別丟了你的HEAD ===

HEAD好似一個游標，通常指向最新提交，隨最新提交向前移動。一些Git命令讓你來移動
它。 例如：

 $ git reset HEAD~3

將立即向回移動HEAD三個提交。這樣所有Git命令都表現得好似你沒有做那最後三個提交，
然而你的檔案保持在現在的狀態。具體應用參見幫助頁。

但如何回到將來呢？過去的提交對將來一無所知。

如果你有原先Head的SHA1值，那麼：

 $ git reset 1b6d

但假設你從來沒有記下呢？別擔心，像這些命令，Git保存原先的Head為一個叫
ORGI_HEAD的標記，你可以安全體面的返回：

 $ git reset ORIG_HEAD

=== HEAD捕獵 ===

或許ORG_HEAD不夠。或許你剛認識到你犯了個歷史性的錯誤，你需要回到一個早已忘記
分支上一個遠古的提交。

預設，Git保存一個提交至少兩星期，即使你命令Git摧毀該提交所在的分支。難點是找
到相應的哈希值。你可以查看在.git/objects裡所有的哈希值並嘗試找到你期望的。但
有一個更容易的辦法。

Git把算出的提交哈希值記錄在“.git/logs”。這個子目錄引用包括所有分支上所有活
動的歷史，同時檔案HEAD顯示它曾經有過的所有哈希值。後者可用來發現分支上一些不
小心丟掉提交的哈希值。

The reflog command provides a friendly interface to these log files. Try

命令reflog為訪問這些日誌檔案提供友好的介面，試試

  $ git reflog

而不是從reflog拷貝粘貼哈希值，試一下：

 $ git checkout "@{10 minutes ago}"

或者撿出後五次訪問過的提交，通過：

 $ git checkout "@{5}"

更多內容參見 *git help rev-parse* 的``Specifying Revisions''部分。

你或許期望去為已刪除的提交設置一個更長的保存周期。例如：

  $ git config gc.pruneexpire "30 days"

意思是一個被刪除的提交會在刪除30天後，且運行 *git gc* 以後，被永久丟棄。

你或許還想關掉 *git gc* 的自動運行：

  $ git config gc.auto 0

在這種情況下提交將只在你手工運行 *git gc* 的情況下才永久刪除。

=== 基于Git構建 ===

依照真正的UNIX風格設計，Git允許其易於用作其他程序的底層組件，比如圖形界面，
Web界面，可選擇的命令行界面，補丁管理工具，導入和轉換工具等等。實際上，一些
Git命令它們自己就是站在巨人肩膀上的腳本。通過一點修補，你可以定製Git適應你的
偏好。

一個簡單的技巧是，用Git內建alias命令來縮短你最常使用命令：

  $ git config --global alias.co checkout
  $ git config --global --get-regexp alias  # 顯示當前別名
  alias.co checkout
  $ git co foo                              # 和“git checkout foo”一樣

另一個技巧，在提示符或窗口標題上打印當前分支。調用：

  $ git symbolic-ref HEAD

顯示當前分支名。在實際應用中，你可能最想去掉“refs/heads/”並忽略錯誤：

  $ git symbolic-ref HEAD 2> /dev/null | cut -b 12-

子目錄 +contrib+ 是一個基于Git工具的寶庫。它們中的一些時時會被提升為官方命令。
在Debian和Ubuntu，這個目錄位於 +/usr/share/doc/git-core/contrib+ 。

一個受歡迎的居民是 +workdir/git-new-workdir+ 。通過聰明的符號連結，這個腳本創
建一個新的工作目錄，其歷史與原來的倉庫共享：

  $ git-new-workdir an/existing/repo new/directory

這個新的目錄和其中的檔案可被視為一個克隆，除了既然歷史是共享的，兩者的樹自動
保持同步。不必合併，推入或拉出。

=== 大膽的特技 ===

這些天，Git使得用戶意外摧毀數據變得更困難。但如若你知道你在做什麼，你可以突破
為通用命令所設的防衛保障。

*Checkout*：未提交的變更會導致撿出失敗。銷毀你的變更，並無論如何都checkout一
 個指定的提交，使用強制標記：

  $ git checkout -f HEAD^

另外，如果你為撿出指定特別路徑，那就沒有安全檢查了。提供的路徑將被不加提示地
覆蓋。如你使用這種方式的檢出，要小心。

*Reset*: 如有未提交變更重置也會失敗。強制其通過，運行：

  $ git reset --hard 1b6d

*Branch*: 引起變更丟失的分支刪除會失敗。強制刪除，鍵入：

  $ git branch -D dead_branch  # instead of -d

類似，通過移動試圖覆蓋分支，如果隨之而來有數據丟失，也會失敗。強制移動分支，鍵入：

  $ git branch -M source target  # 而不是 -m

不像checkout和重置，這兩個命令延遲數據銷毀。這個變更仍然存儲在.git的子目錄裡，
並且可以通過恢復.git/logs裡的相應哈希值獲取（參見上面 上面“HEAD獵捕”）。默
認情況下，這些數據會保存至少兩星期。

*Clean*: 一些Git命令拒絶執行，因為它們擔心會重裝未納入管理的檔案。如果你確信
 所有未納入管理的檔案都是消耗，那就無情地刪除它們，使用：

  $ git clean -f -d

下次，那個討厭的命令就會工作！

=== 阻止壞提交 ===

愚蠢的錯誤污染我的倉庫。最可怕的是由於忘記 *git add* 而引起的檔案丟失。較小
的罪過是行末追加空格並引起合併衝突：儘管危害少，我希望浙西永遠不要出現在公開
記錄裡。

不過我購買了傻瓜保險，通過使用一個_鈎子_來提醒我這些問題：

 $ cd .git/hooks
 $ cp pre-commit.sample pre-commit  # 對舊版本Git，先運行chmod +x

現在Git放棄提交，如果檢測到無用的空格或未解決的合併衝突。

對本文檔，我最終添加以下到 *pre-commit* 鈎子的前面，來防止缺魂兒的事：

 if git ls-files -o | grep '\.txt$'; then
   echo FAIL! Untracked .txt files.
   exit 1
 fi

幾個git操作支持鈎子；參見 *git help hooks* 。我們早先激活了作為例子的
*post-update* 鈎子，當討論基于HTTP的Git的時候。無論head何時移動，這個鈎子都會
運行。例子腳本post-update更新Git在基于Git並不知曉的傳輸協議，諸如HTTP，通訊時
所需的檔案。

