一個健康的版本庫,是可閱讀,可追溯的。對於每一次提交,我們需要有清晰的把握。 Git 的暫存區(index),就是我們的得力工具。暫存區,就像是超市裏的購物車。 我們在貨架(工作區)上挑選商品(文件的修改),並帶到收銀臺結帳(提交)。 有了暫存區,我們可以對一大堆修改進行分門別類,先後有序的提交。

基本命令

   對暫存區的操作,主要使用三個命令 addresetcheckout。 它們的作用分別爲:

  • add 將修改加入暫存區
  • reset 將修改從暫存區移出
  • checkout 放棄未加入暫存區的修改(從暫存區同步到工作區)    其中 ressetcheckout 都是多用途的命令,分別還有另一個用途。 reset 還可用於重置當前分支,checkout 還可用於重置 HEAD 並檢出文件。 區別在於若命令後跟提交(hash)或指向提交的指針(分支、tag 等), 則是重置分支和 HEAD 的功能; 命令後跟文件,或沒有參數,則是本文所說的用途。

精细操作 --patch

   addresetcheckout 三個命令都可以加 --patch 選項,簡寫爲 -p。 遍歷每一處修改進行精細處理。 後面可以跟文件名作爲參數;也可以不加參數,表示對所有修改進行處理。 三個帶 -p 的命令組合使用,我給它起了個俗名,叫“3P 大法”。 是目前處理 git 暫存區的最理想方案。

   執行命令將進入一個交互界面。大致如下:

diff --git a/index.md b/index.md
index 921ba51..1c0d98b 100644
--- a/index.md
+++ b/index.md
@@ -3,5 +3,5 @@
 
 
 layout: home
-list_title: foo
+list_title: bar
 ---
Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,s,e,?]? 

最後一行的內容三個命令個有不同,分別爲:

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

reset
Unstage this hunk [y,n,q,a,d,k,K,j,J,g,/,s,e,?]? 

checkout
Discard this hunk from worktree [y,n,q,a,d,k,K,j,J,g,/,s,e,?]? 

說明了各自的用途。

   這裏可見,git 展示了我們對 index.md 文件作出的一處修改。 並提供了 y,n,q,a,d,k,K,j,J,g,/,s,e,? 十一個可用的操作。分別表示什麼呢? 別的不清楚,但 ? 想必是用來提供幫助。我們輸入 ? Enter。 得到提示信息:

add
y - stage this hunk
n - do not stage this hunk
q - quit; do not stage this hunk or any of the remaining ones
a - stage this hunk and all later hunks in the file
d - do not stage this hunk or any of the later 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
reset
y - unstage this hunk
n - do not unstage this hunk
q - quit; do not unstage this hunk or any of the remaining ones
a - unstage this hunk and all later hunks in the file
d - do not unstage this hunk or any of the later 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
checkout
y - discard this hunk from worktree
n - do not discard this hunk from worktree
q - quit; do not discard this hunk or any of the remaining ones
a - discard this hunk and all later hunks in the file
d - do not discard this hunk or any of the later 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

   初次看簡直眼花繚亂,讓我們整理一下:

處理命令:
`y` yes       要(加入/移出/丟棄);
`n` no        不要動;
`a` all       此文件的修改,全都要(加入/移出/丟棄);
`d` don't     此文件的修改,全都不要;
`e` edit      編輯,後面詳細說明;
`s` split     分割,有時候 git 會把幾個修改併在一起,若想分開處理,用此操作;

導航命令(只能在本文件中導航):
`g` goto      跳轉到第 n 個修改,如 `g 1`,`g 2`,需要注意序號從 1 開始;
`/` search    搜索,和 vim、less 等軟件的搜索差不多,在大文件中有大量修改的時候很方便;
`j` next      下一處沒處理的修改;
`J` Next      下一處修改;
`k` previous  上一處沒處理的修改;
`K` Previous  上一處修改;

其它命令:
`q` quit      退出;
`?` help      幫助;

   基本使用只需要掌握 ynsq,高級使用就再加上個 e。 程序員一般有 ynse 就夠用了。 導航命令則對於字典維護人員,webpack 配置師這樣的工種比較有用。

   下面我們詳細講一下這個 e。既然是 edit,自然需要 editor。 和不帶 -m 選項的 commit 一樣,它會調起編輯器,vim、nano 或是其它,依據你的配置。 內容如下:

# Manual hunk edit mode -- see bottom for a quick guide
@@ -20,4 +20,5 @@
 	><ul
 		><li>foo</li
-		><li>bar</li
+		><li>baz</li
+		><li>qux</li
 	></ul
# ---
# 這裏會有一些操作說明,可以作爲參考,我就不粘貼了。

   以上展示了一處修改,我們刪除了 bar 行,並增加了 baz、qux 兩行。 @@ ... @@ 中的內容展示了修改所在的行號和修改前後的行數。 緊接着是內容,在每行的行首第一個字符,表示行的類型,可以是 空格、-+。 空格表示普通行,此行沒有修改;- 表示刪除的行,而 + 則表示新加的行。 注意,此空格不是縮進。若你使用縮進符,則非常清晰;若使用空格符代替縮進符,則要仔細了,要多一個空格。

   此時,若保存退出編輯器,則表示應用(加入/移出/丟棄)此修改,相當於直接敲 y。 若我們將修改還原,即刪除所有新加的行,即把刪除的行取消刪除,即將- 改爲空格, 則等於不應用此修改,相當於直接敲 n。 若我們修改內容,則可以得到更多的效果。要注意不是所有修改都是允許的。

   在 add 時,情況比較容易理解。例如我們修改爲如下的樣子:

 	><ul
-		><li>foo</li
+		><li>qux</li
 		><li>bar</li
+		><li>Baz</li
 	></ul

表示我不刪除 bar 行,而刪除 foo 行。 baz 行的內容進行修改。而 qux 行則換個位置插入。 理解起來並不困難。 我們可以總結出規律:在 add 時,空格行和 - 行可以相互轉換,但的內容不能修改; 而 + 行則可隨意修改,刪除,還可以在任意地方插入。

   而 reset 時,情況就和 add 相反了。空格行和 + 行可以相互轉換,但的內容不能修改; 而 - 行則可隨意修改,刪除,還可以在任意地方插入。 這讓人十分費解,還是用例子說明吧。還是上面的內容,我們作如下編輯:

 	><ul
+		><li>foo</li
-		><li>Bar</li
+		><li>baz</li
 		><li>qux</li
-		><li>ET</li
 	></ul

我們得以“放棄”,“取消”這樣的字眼來分析。 我們將空格行 foo 改爲 + 行,表示取消對 foo 行的添加。 可 foo 行並非添加的,而是原來就在,所以就是刪除 foo 行囉。 刪除的 bar 行改爲 Bar 行,表示不放棄對 bar 行的刪除,而取消對 Bar 行的刪除。 那麼 bar 行還是要刪,然後添加 Bar 行。取消不存在的刪除,等於添加。即 bar 行改爲 Bar 行。 最後,放棄對 ET 行的刪除,也等於新插入一個 ET 行。 有點繞腦。有一個囫圇吞棗的辦法:reset 時,把 +-,把 -+ 就行了。

   checkout 時,由於方向和 reset 一致,因此允許的修改和效果,都和 reset 一致,是反的。 區別只是 reset 改的是暫存區,而 checkout 改的是文件內容。 因此,可以 checkout 來做做實驗。躬行所悟,可比紙上所得要深刻百倍。

   需要注意,對於新文件,add -p 是無效的,它會跳過這些文件,而 reset -p 則沒有這個問題。 對於新文件的 add,必須整個文件進行操作。

整個文件操作和全局操作

   通常,用 -p 瀏覽一遍到底提交了些什麼總是好的。 但也會有需要整文件操作,甚至全局操作的時候。 只是勿圖方便而濫用。

  • git add file 將文件中的全部修改加入暫存區
  • git reset file 將文件中的全部修改從暫存區移出
  • git checkout file 放棄文件中的全部未加入暫存區的修改

  • git add . 將文件中的全部修改加入暫存區
  • git reset 將暫存區中的全部修改移出(清空購物車)
  • git checkout 全部未加入暫存區的修改

未完善的終極武器 add --interactive

   git add 中,還有一個更加新潮的選項:--interactive,簡寫爲 -i。 顧名思義,這也是一個交互界面。樣子如下:

           staged     unstaged path
  1:    unchanged        +0/-1 TODO
  2:    unchanged        +1/-1 index.html

*** Commands ***
  1: status       2: update       3: revert       4: add untracked
  5: patch        6: diff         7: quit         8: help
What now> 

上方顯示 status 內容,與 git status 得到得主體內容差不多,列表得方式看起來更加清晰。 下方顯示可用的操作,一共有 8 個,此外還有個隱藏的 ?,分別爲:

1 status         顯示主界面,通常用於刷新
2 update         加入暫存區,相當於 add file
3 revert         移出暫存區,相當於 reset file
4 add untracked  新文件加入暫存區
5 patch          相當於 add -p file
6 diff           查看暫存區中的內容
7 quit           退出
8 help           幫助,關於以上操作
?                幫助,關於交互界面

實際使用時,敲數字或首字母即可,無需鍵入完整命令。

   其中 u r a p d 爲核心操作,執行後,會進入到二級界面,我們看到命令提示符由 > 變爲 >>。 這時候,我們可以選取要操作的文件。可用序號選擇,也可輸入文件名(前面一段,沒有重複即可)。 目前,文件名還不支持分段匹配、模糊匹配、* ?,也不支持 tab 鍵輸入提示,和上下鍵輸入歷史,希望將來能有。 但序號也夠用了。在所選的序號或文件名前加 - 則表示取消選擇。第一次回車,執行選擇,然後可以繼續選擇。 第二次回車,執行操作。

   add --interactive 給了我們一個很棒的操作方式,我們可流暢地操作,不必一而再再而三地執行 git status。 就操作方式而言,可以說是終極武器。然而這個未來的終極武器還有需多不夠完善之處。 它有 add filereset file 以及 add -p file 卻缺少了 reset -p filecheckout filecheckout -p file。 可以說只整合了一半的功能。最關鍵的 add -p filereset -p file,就少了一個。 我們只能先 reset fileadd -p file。若文件中有很多修改,這會相當麻煩。 因此,這個命令還需要觀望。目前最佳方案,還是三個 patch,俗稱 3P 大法。