2013年5月28日 星期二

Git 初學 2

這篇是我又從別的地方看到了一些 git 的教學,再補充一些上一篇的不足。
原文來自:http://ihower.tw/blog/archives/2620

首先,先複習一下 branch 相關指令。大家都說 git 開 branch 不用錢。

  • git branch <new_branch_name> 建立本地 local branch
    • 如果要把新的 local branch push 上去,需要下 git push origin <local_branch_name> 指令。
  • git branch -m <old_name> <new_name> 改名字 (如果有同名會失敗,改用 -M 可以強制覆蓋)
  • git branch 列出目前有那些 branch 以及目前在那個 branch
  • git checkout <branch_name> 切換 branch (注意到如果你有檔案修改了卻還沒 commit,會不能切換 branch)
  • git checkout -b <new_branch_name> (<from_branch_name>) 本地建立 branch 並立即 checkout 切換過去
  • git branch -d <branch_name> 刪除 local branch



開了 branch 之後,總是會遇上需要 Merge的時候。Git 的 Merge 方式可以分成四種:


  • Straight merge 預設的合併模式,會有全部的被合併的 branch commits 記錄加上一個 merge-commit,看線圖會有兩條 Parents 線,並保留所有 commit log。
  • Squashed commit 壓縮成只有一個 merge-commit,不會有被合併的 log。SVN 的 merge 即是如此。
  • cherry-pick 只合併指定的 commit
  • rebase 變更 branch 的分支點:將目前 branch 的 commits,一個個重新重新 apply (或叫做patch) 到要被 rebase 的 branch 上。例如在 A branch rebase B branch,就會把原本分支點之後所有的 A commits 記錄,重新在 B branch 上再 commit 一遍,這時新分支點變成 B branch 的最新的 commit。這方式僅適合還沒分享給別人的 local branch,因為等於砍掉重練目前 branch 的 commits 記錄。

Merge 的 command :

  • git merge <branch_name> 合併另一個 branch,若沒有 conflict 衝突會直接 commit。若需要解決衝突則會再多一個 commit。
  • git merge --squash <branch_name> 將另一個 branch 的 commit 合併為一筆,特別適合需要做實驗的 fixes bug 或 new feature,最後只留結果。合併完不會幫你先 commit。
  • git cherry-pick 321d76f 只合併特定其中一個 commit。如果要合併多個,可以加上 -n 指令就不會先幫你 commit,這樣可以多 pick幾個要合併的 commit,最後再 git commit 即可。


使用 merge 可能會有部分程式碼會 conflict 衝突,簡單的情況只要編輯檔案處理 <<<< ===== >>>>> 即可,然後重新 add 到 staging area 並 commit。複雜一點的可以再用 git mergetool 選檔案合併的 GUI 工具 (OS X 下面可以用 opendiff, linux 可以用 kdiff3 ),處理好後 git commit。(這些 GUI 工具我都沒使用過,以公司裡的狀況也不太容易使用到)

一旦 merge 好了,git branch -d <branch_name> 可以刪除 branch。但如果要刪除的 branch 還沒有合併,就會有錯誤訊息。如果真的要強制刪除可以用 -D

Git 的 working tree 是從 SVN 換過來一個不習慣的地方,因為它只是一個工作暫存區,在切換 Branch 時就會整個換掉。也因為如此,如果有檔案有修改還沒有 commit 出去,切換 branch 時就會出現 error 不能切換 (除非是新的 untracking 檔案),例如有修改還沒 add 會出現 error: Entry ‘ooxx’ not uptodate. Cannot merge. 有修改且已經add(還沒ci)會出現 error: Entry ‘ooxx’ would be overwritten by merge. Cannot merge.

最理想的處理當然是事情剛好做到一個段落,把東西 commit 出去才切換 branch 做事。不過事情總有臨時,如果要換 branch 的暫時的解決方式是使用 git stash 會先把修改暫存下來,要回復則執行 git stash pop。下一篇等你學會 git reset 之後,你會發現就算把還沒完成的東西 commit 也不會怎麼樣,只要還沒 push 出去一切 commit 紀錄都是可以改的。

接下來是跟遠端的操作:

  • git clone <remote_address>
  • git checkout --track -b foobar origin/foobar 將遠端的 branch checkout 回來並建立一個新的 local branch,加上 --track 表示你之後還要pull、push回去,所以請 Git 記住對應關係。
  • git pull (<local_branch_name> origin/<remote_branch_name>) 去遠端 fetch 新版並 merge 進 local branch
  • git push 將 local branch 的 commit 紀錄更新到遠端



git pull 要注意的是,如果別人在你上次 pull 之後有 push 新東西上去(也就是說跟你的 branch 產生分岔了),此時有兩種情況: 一是 Git 可以順利 auto merge 的話,git 會自動多一次 merge commit,這也就為什麼常常 log 會跑出 Merge branch ‘master’ of git@foobar.com。二是如果有 conflict,這時候就需要你手動處理然後 commit。話說如果覺得這種 local branch 和 remote branch 的 merge commit log 很煩,建議可以改使用 git pull –rebase 指令來變成 fast-forward 形式 (就會變得像 svn up,而不會有 merge commit log)。rebase 的意思可能要下一篇才會詳細說明的清楚,簡單的說(?),就是先砍掉 local branch 分岔點之後自己的 commit,然後把遠端的 commit 先一個個 apply 進來,最後再把自己的 commit 再 apply 進去 (如果有 conflict 會中途停下來,等你修好才會繼續 apply),如此一來看線圖就會變成一條線而已,也就沒有所謂 merge 這個動作了。

git push 預設的遠端是 origin,並且會將所有有和 remote 有對應的 local branch 都 push 上去。如果要把新的 local branch push 上去,需要下 git push origin <local_branch_name> 指令。

git push 也可能會失敗,例如出現 ! [rejected] master -> master (non-fast forward),這個 non-fast forward 的意思是你的 parent commit 和遠端的不相同,也就是線圖有分岔,需要先 pull 回來處理好 merge 才能 push 上去。

fast-forward 在 Git 是一種 merge 術語,當 B branch (例如一個 local branch) 是從 A branch (例如一個 remote branch) 的最新版(HEAD)分支出來的,那當 A 要把 B merge 進來時,因為 B 的 parent commit 是 A 的 HEAD,所以這兩個 branch 唯一的差異就是 B 後來的 commit 而已,而不會有任何 conflict。所以實際上的動作只要把 A 的 HEAD 改成 B 的 HEAD 就好了,線圖上這兩個 branch 根本是同一條線,此謂 fast-forward。

其他操作還有:


  • git fetch 把遠端的 branch 更新下載回來,但不會 merge 到 local branch
  • git branch -r 顯示 local 有追蹤的遠端 branch。注意到你不能直接修改這個 remote branch,一定要用一個 local branch 對應它。
  • git remote show origin 顯示遠端 server 的 branch
  • git remote add foobar git:// 可以新增別的 repo. 位置,於是 pull 的時候就可以指定要從哪一個遠端更新回來。
  • git push origin :foobar 刪除遠端的 branch

沒有留言:

張貼留言