熟練使用 git 分支管理

卡巴拉的樹發表於2017-12-21

Git
觀察過很多使用git的人,只會用add, commit,push,pull這幾個命令,包括恢復版本之類的,多半也會暴力的刪除整個專案,再重新clone乾淨的程式碼。雖說也能工作了,但是無疑沒有領會到git的精華。 這篇文章主要說明如何使用分支使我們的開發工作更加順滑,如何讓分支成為你日常工作流不可缺失的一部分。

git branch 用法

git branch   //列出所有的分支
git branch <branch> //建立名為<branch>的分支,但是不會切換過去
git branch -d <branch>  //刪除指定分支,這是一個“安全”操作,git會阻止你刪除包含未合併更改的分支。
git branch -D <branch>  //強制刪除分支
git branch -m <branch> //重新命名當前分支
複製程式碼

在日常開發中,無論是修復一個bug或者新增一個功能,我們都應該新建一個分支來封裝我們的修改。這樣可以保證我們不穩定的程式碼永遠不會提交到主程式碼分支上。

下面我們具體來看在執行分支有關的操作,分支的變化:

建立分支

分支只是指向提交的指標,理解這一點很重要,當你建立新分支,實際上只是建立了一個新的指標,倉庫本身不會受到影響,一開始你的倉庫只有一條分支:

master
然後你執行下面的命令建立一個分支,用於加一個新feature:

git branch new-feature
複製程式碼

new-feature
當然執行後,你只是建立了這個分支,還需要執行git chekcout new-feature切換到new-feature分支,然後再使用git add,git commit

刪除分支

假如你已經開發完了new-feature,並且已經commit程式碼了, 你就可以自由刪除這個分支了。

git branch -d new-feature
複製程式碼

如果分支還沒有合入master,會報下面的錯誤:

error: The branch 'new feature' is not fully merged.
If you are sure you want to delete it, run 'git branch -D crazy-experiment'.
複製程式碼

這時候你可以合併分支(下面會說如何合併分支),如果你真的確定了要刪除分支,可以用-D執行強制刪除:

git branch -D new-feature
複製程式碼

切換分支(git checkout)

git checkout 命令允許你切換到用git branch建立的分支。切換分支會更新當前工作目錄中的檔案,還可以用git log檢視當前分支的歷史。 用法

git checkout <existing-branch> //切換到一個已有分支上
git checkout -b <new-branch> // -b 標記 可以方便讓你先建立一個新的new-branch,再直接切換過去
git checkout -b <new-branch> <existing-branch> //在已有的分支上建立分支,原來的分支使新分支的基
複製程式碼

git branchgit chekout 是一對好基友,你可以使用git checkout在不同的功能分支或者bug分支之間切換,而不產生相互影響。

關於分離的HEAD(detached HEAD) 一般我們有時候需要恢復到以前commit的版本上檢視原來的一些檔案時,我們會git checkout commit的hash碼或者tag, 這時候會提醒我們進入了detached HEAD

detached HEAD
拿上圖舉例,我們當前的HEAD在4,然後假設 git checkout 2,我們的程式碼回到2的狀態了,通常git會顯示下面的warning:

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 2
複製程式碼

如果在這種狀態下開發,然後又add,commit,沒有分支可以讓你回到之前的狀態。當你不可避免的需要checkout到另外一個分支,想再回來就是不可能的了,因為像圖中的那個X狀態,根本就不在分支上,你將再也不能引用你之前新增的程式碼了。

重點:永遠記得在開發分支上開發,不要在分離的HEAD上開發,這可以確保你可以引用到你的新提交,如果只是checkout到以前看看無所謂,如果真的需要在以前的版本上新增什麼程式碼,記得上面warning 中的git checkout -b new_branch_name,是自己處於一個確切的分支中。

遠離detached HEAD,我們來看實際上git分支流程是什麼樣子的:

git branch new-feature
git checkout new-feature
複製程式碼

接下來我們做一些程式碼改動,提交:

git add <some file>
git commit -m "A new feature"
##你接著改程式碼,接著提交很多次
複製程式碼

當你git log會顯示每一次的commit, 和master分支完全獨立,當你checkout到master分支去,再git log,會發現new-feature分支的提交都不在,這就是不影響master分支。 這時候,你可以考慮合併new-feature或者在master分支上開始別的工作。

合併(git merge)

合併是git將被fork的歷史放回到一起的方式。 git merge 命令允許你將 git branch 建立的多條分支合併成一個。 用法

git merge <branch>  //將指定分支併入當前分支
git merge --no-ff <branch>  //將指定分支併入當前分支,但 總是 生成一個合併提交(即使是快速向前合併)。這可以用來記錄倉庫中發生的所有合併。
複製程式碼

一旦在新分支上完成開發,我們需要把新分支的提交合併到主分支,git會根據目前分支之間的結構資訊,選擇不同的演算法來完成合並:

  • 快速向前合併
  • 三路合併

快速向前合併 當new-feature的分支與原有的master分支呈現線性關係時,執行快速向前合併,git將當前的HEAD指標快速移到目標分支的頂端,master分支也就具有了new-feature分支的歷史了,如圖:

快速向前合併

來看一個快速向前合併的例項:

# 開始新功能
git checkout -b new-feature master

# 編輯檔案
git add <file>
git commit -m "開始新功能"

# 編輯檔案
git add <file>
git commit -m "完成功能"

# 合併new-feature分支
git checkout master
git merge new-feature
git branch -d new-feature

複製程式碼

對於合作開發的人少的專案,這是一種主要的工作流,合作開發的人多的話,主master分支經常都會有新提交,如果你的new-feature耗時比較久,再提交時,master分支可能已經過去幾個版本了,這時候就需要下面的三路合併了。

三路合併 但是如果master分支在new-feature分離後,又有了新的提交,即開始分叉了,git只能執行三路合併,三路合併使用一個專門的提交來合併兩個分支的歷史。

已經分叉的branch
所謂的三路也就是:兩個分支的頂端以及他們共同的祖先。 在執行三路合併後:

三路合併後
使用三路合併產生的合併提交作為兩個分支的連線標誌。

解決衝突 如果兩個分支對同一個檔案的同一部分均有修改時,git將無法判斷應該使用哪個,這時候合併提交會停止,需要你手動解決這些衝突。你可以使用git status來檢視哪裡存在衝突,很多時候我都會在目錄下執行grep -rn HEAD來檢視哪些檔案裡有這個標記,有這個標記的地方都是有衝突的。

當修改完所有的衝突後,git add所有的衝突檔案,執行git commit生成一個合併提交,這和提交一個普通快照的流程相同。提交衝突只會存在在三路合併中,快速向前合併中不可能出現針對同一檔案同一部分的不一樣修改。

下面例項來看看三路合併是怎麼產生的:

# 開始新功能
git checkout -b new-feature master

# 編輯檔案
git add <file>
git commit -m "開始新功能"

# 編輯檔案
git add <file>
git commit -m "完成功能"

# 在master分支上開發
git checkout master

# 編輯檔案
git add <file>
git commit -m "在master上新增了一些極其穩定的功能"

# 合併new-feature分支
git merge new-feature
git branch -d new-feature
複製程式碼

這時候,merge會停止,因為無法將 master 直接移動到 new-feature。所以需要你手動合併衝突後再提交。

git merge會產生合併提交,有的人會選擇使用git rebase來合併以確保一個乾淨的提交歷史。關於這兩個的區別,我會另寫一篇介紹。

相關文章