2012年8月13日 星期一

git 之簡單介紹 part 3

這個部份會介紹
git commit (進階)
git add (進階)
git merge
git stash
git rebase
這些指令
基本上了解 part 1-3 這些指令就已經能滿足大部分需求了,至少對作者來說是如此

git commit --amend:
這個指令跟 git commit 基本上相同
不同的地方是他會把現在這個 HEAD 所在的 branch 的 commit 覆蓋掉
打個比方,我們想像 commit 是「一本書疊在一本書上」的過程
git commit 就像在「現在最上面這本書」上再疊上另外一本
git commit --amend 就像把「現在最上面這本書」拿起來,然後改寫一番,再把這本書放回去

git add -i <file(s)>:
git add -e <file(s)>:
git add -p <file(s)>:
這三種指令提供了以「行」為單位,將更動加入 cache(見 part 2)
這三個指令可以不打 <file(s)> 參數,這樣 git 就會自動選擇全部有更動的檔案

首先,-i 這個因為作者也不常用,就不講了 XD

而 -p 以一個「更動區域 (hunk)」單位
對每一個更動區域,git 都會問你要選哪個選項,以下我選常用的選項講

  1. y 把這個 hunk 加入 cache
  2. n 不把這個 hunk 加入 cache
  3. q 離開這個模式,前面選了 y 的 hunk 仍然有效
  4. a 這個檔案中,把這個 hunk (含)之後的所有剩下的 hunk 加入 cache
  5. d 這個檔案中,把這個 hunk (含)之後的所有剩下的 hunk 都不加入 cache
  6. e 編輯這個 hunk,會進入跟 git add -e <file(s)> 相同的模式,稍候講
  7. s 你覺得 git 選取的 hunk 太大了,用這個選項告訴 git 把這個 hunk 分成多個

-e 呢,會進入編輯模式,也就是把一個檔案當作一個 hunk
編輯模式下,有四種行 (line),以開頭的第一個字為區分
編輯模式可以直接在編輯器中以「行」為單位進行修改

  1. # 開頭的行是註解
  2. 空白鍵開頭的行,空白鍵之後就是你沒有動的程式碼
  3. + 開頭的行代表你新增了這行,+ 之後就是你現在的程式碼
  4. - 開頭的行代表你刪除了這行,- 之後就是你原來的程式碼

要怎麼操作,其實 git add -i 進入的編輯模式有說明
# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.
翻譯一下,就是說如果你並不想把一行的「刪除」紀錄到 commit 中,就要把那行開頭的減號改成空白鍵(注意,不是刪掉減號或是整行)
如果你並不想把一行的「新增」紀錄到 commit 中,就直接刪掉那行

git stash:
git stash 能把 commit 之後的更動紀錄下來,並把檔案恢復到上次 commit 時的狀態
需要的時候就可以對現在的檔案重現這個更動
在 part 2 中,有提到如果距離上次 commit 有更動的話,不能 checkout 到別的 branch
這時 stash 就是一個常見、簡單的解法
最常見的作法是
git stash  # 恢復檔案到上次 commit 時的狀態,並記下更動
git checkout <another branch>
git stash pop  # 重現更動
其實 stash 不只能紀錄一次更動(像上面這樣),以下這兩個指令提供了更精細的 stash 操作,不過因為上面的方法已經蠻好用了,而且 stash 我認為最好不要太多個,所以我就不講解了,有興趣的自己去查。
git stash apply
git stash drop

git merge <source branch>:
merge 提供了把兩個獨立工作的 branch 合併的功能
他可以把
merge 之後大概會有三種狀態 fast-forward, auto merge, conflict

fast-forward 發生在現在所在的 branch 為 <source branch> 的祖先,這時,HEAD 與現在這個 branch 的參照直接移動到 <source branch>,像下圖,這個機制常常會讓初學者混淆 branch 的行為(包括我 XD):
git merge: fast-forward
另外,使用 git merge --no-ff <source branch> 可以強迫 git 進入下面的這兩個模式,不過沒什麼必要,而且相較之下,下面這兩個模式會讓 commit tree 長得比較亂。

auto merge 發生的條件如下:雖然現在所在的 branch 並不是 <source branch> 的祖先,,不過好險兩邊的更動沒有重疊,git 會很聰明的把現在的 branch 指向一個新的 commit,然後把 <source branch> 的更動加到這個 commit 上,於是一來這個 commit 就保留了兩邊的更動。而這個 commit 會有兩個祖先:原來 HEAD 在的 commit 以及 <source branch>。
git merge: auto merge
conflict 基本上跟 auto merge 一樣,但是兩邊的更動重疊了,這時 git 會把告訴你哪些檔案有重疊,git 會把兩方的改動放入檔案之中讓你看,你必須手動更改這些檔案,這個步驟在 auto merge 中是由 git 自動幫你完成的。改完這些檔案之後存檔,接著使用 git commit 命令來完成手動 merge。

總之,不管是 fast-forward, auto merge, conflict,merge 成功之後,現在 HEAD 在的這個 commit 一定會包含了兩個 branch 的更動,除非你在 conflict 時手動砍掉了其中一方的更動。透過這個機制,便能達到多人開發,或是一個人開發多個功能的目的。

git rebase:
rebase 能移動 commit 的順序,也就是改變一個 branch 的基準點
可以想像 C/C++ 中,把 link list 中,一串 nodes 的頭,接到另外一個 node 上面

一般來說只用 merge 已經可以達到版本控制的功能
但是 rebase 可以做讓 commit tree 長得更漂亮
俗話說得好,不要重新發明輪子
網路上已經有現成,其他人寫的很好的講解文章了
加上如作者不斷強調的,作者很懶惰(←這句才是真正的原因)
所以我在這邊附上我認為他寫得很清楚的文章,有需要的可以點進去看
http://blog.yorkxin.org/2011/07/29/git-rebase

其他還有一些功能,如果你使用的 git 夠新(通常夠)
使用 git status 會看得到這些功能
#   (use "git reset HEAD <file>..." to unstage)
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
第一行可以把 cache 的檔案取消,這步驟在 git 中叫 unstage,不會更動現在的檔案內容
第二行已經在 part 2 講解過了
第三行會把所有上次 commit 之後的更動的檔案完全復原,不留痕跡,請小心使用。使用時機是改了 code,卻發現這個改動完全不需要(或不能用)時使用。

沒有留言:

張貼留言