前言
git
除了可以很好地管理個人專案外,最大的一個用處就是實現團隊協作開發。況且,linus
大神開發git
的初衷就是為了維護Linux
核心這一開源專案。所以,熟悉使用git
進行多人協作開發的一般步驟和方法具有十分重要的意義。這一講將會為你介紹使用git
進行團隊協作開發的一般方式以及git pull
操作常見問題的解決方法。
一、git
協作方式
1.常見開發模式
-
Gitflow
:簡單來說,就是多種開發模式的總稱。例如:使用多少分支,什麼時候合併分支等等。這方面篇幅較長,內容較多,之後會進行詳細講解; -
基於
Git
分支的開發模型:一般最少有三個分支:develop
分支:頻繁變化的分支,供開發人員之間進行協作開發,檔案推送與合併;test
分支:供測試人員與產品等人員使用的一個分支,變化不是特別頻繁;master
分支:生產釋出分支,變化非常不頻繁的一個分支(一般有許可權設定,因為直接與生產有關);bugfix(hotfix)
分支:用於緊急修復的分支;當出現緊急bug
時,在常規的develop
分支上修復已經趕不上了。此時可以直接將master
分支的程式碼拉取到bugfix
分支上,進行bug
修復,修復完之後,再將它合併到master
分支上釋出;
合併方向為:
develop
->test
->master
2.SVN
方式(典型模型)
首先有兩位使用者A
與B
,A
的本地倉庫不為空,B
的倉庫為空,還有一個遠端倉庫C
。
A
首先將本地倉庫的程式碼推送(push
)到C
中,此時A
和C
兩個倉庫的檔案一致,如圖中1
所示;- 隨後
B
將C
的程式碼拉取(pull
)下來,如圖中2
所示,此時A
,B
,C
三個倉庫中的檔案一致;隨後A
,B
繼續在本地進行開發,並向各自的本地倉庫進行了數次提交; - 此時,
A
先向C
推送修改過後的本地倉庫檔案,由於這是遠端倉庫C
的首次修改,C
中的檔案A
中都有,所以可以直接推送,不用先執行git pull
,如圖中3
所示; - 隨後,在
B
將修改過的本地倉庫檔案推送到C
的過程中會出現錯誤。原因在於:此時的C
中有A
做出的修改,不能讓B
進行覆蓋,此時B
要想成功推送,應該先將C
中的檔案拉取(pull
)到本地;如圖中4
所示,拉取時有兩種情況:- 成功:說明
A
與B
修改的不是同一個檔案,採用Fast-forward
方式自動合併; - 失敗:說明
A
與B
修改了同一個檔案,需要手動解決衝突併合並;
- 成功:說明
B
成功將C
中的檔案拉取到本地合併後,就能將B
對本地倉庫所做的修改推送(push
)到遠端倉庫C
了,如圖中的5
所示;
在整個過程中,可以發現遠端倉庫C
僅僅是起到程式碼第三方託管的作用;
3.模擬多人協作
為了模擬多使用者協作,可以使用--local
來設定每個倉庫的使用者資訊:
git config --local user.name '張三'
--local
是一個配置作用域的引數,其他的還有:
--global
:作用域為每個計算機使用者,優先度第二,實際上常用這個引數進行配置;--system
:作用域為整個系統,優先順序最低;
可以使用:git clone
將遠端倉庫的程式碼下載到本地某資料夾中,下面使用的是SSH
的方式:
還可以通過在連結後面加上一個字串,重新命名下載到本地的遠端倉庫檔案的名字:
git clone git@gitee.com:ahuntsun/MY.git mygit2
4.協作的本質
遠端倉庫通常有多個分支,而在本地倉庫進行一次推送時並不是將本地倉庫的所有分支都推送到遠端倉庫,而是選擇本地倉庫中的一個分支,將其推送到遠端倉庫的其中一個分支上:
比如本地的master
分支,如上圖所示,可以選擇遠端倉庫的master、dev、test
其中一條分支進行推送。假如想要推送到遠端倉庫的master
分支,如果一開始兩個分支沒有任何聯絡,自然要:
- ①先建立本地
master
分支與遠端master
分支的關聯(至於如何建立關聯,下一節將會詳細講解); - ②通過本地
master
分支與遠端master
分支的合併,使兩條分支的內容第一次達到同步; - ③在本地
master
分支上進行修改,然後將修改推送到對應的遠端master
分支上。此時,兩分支的內容第二次達到同步;
二、git pull
在實際開發中,在推送程式碼前,往往都要先執行一次git pull
將遠端倉庫的程式碼拉取到本地並進行合併;從前面的學習中我們知道:git pull = git fetch + git merge
:
git fetch
:表示將遠端倉庫的所有檔案拉取到本地版本庫;git merge
:將遠端倉庫中的檔案與本地倉庫中的檔案進行合併;
但是,在執行git pull
命令時,由於本地倉庫與遠端倉庫歷史提交記錄的不同,往往會出現各種各樣的合併錯誤;在分析這些錯誤之前,首先搭建測試環境:
分別建立兩個本地倉庫mygit
和mygit2
,並且這兩個本地倉庫與同一個遠端倉庫建立聯絡,如下圖所示:
1.不發生合併衝突
在本地倉庫mygit2
中使用--local
引數配置新的使用者lisi
模擬多人協作,隨後通過lisi
給遠端倉庫推送一個新的檔案。回到mygit
後執行git remote show origin
指令,會顯示如下資訊:
表示,本地倉庫mygit
相對於遠端倉庫而言已經過時了,即遠端倉庫中有mygit2
推送的,mygit
中沒有的檔案;此時可以在mygit
中執行git pull
,將遠端倉庫中的檔案拉取到本地倉庫mygit
中進行合併:
上圖中的第二個箭頭表示,在pull
操作的過程中mygit
中的master
分支與遠端倉庫中的master
分支採用Fast-forward
方式進行了合併,並達到了同步。
這裡的本地遠端分支
origin/master
代表著遠端master
分支,關於本地遠端分支將會在下一節進行詳細講解;
關於Fast-forward
方式之前已經介紹過了,在上述合併過程中origin/master
分支直接指向了最新提交,中間沒有其他分支,也就不會出現合併衝突,這種合併方式稱為快進。如下圖所示:
這是一個理想的情況,很多情況下執行git pull
操作時,都會出現合併衝突,需要解決衝突,再進行手動合併;
2.git pull
同源合併衝突
所謂同源,指的是本地倉庫與遠端倉庫中的分支從根提交節點開始,有共同的提交歷史;簡而言之,有共同的根提交節點的兩個分支稱為同源;如下圖所示,兩倉庫中的master
分支有共同的根提交節點A
,所以這兩個倉庫的master
分支是同源的:
這種情況下git pull
出現的錯誤為自動合併失敗,比如都同時修改了develop.txt
檔案,錯誤資訊如下:
Auto-merging develop.txt
CONFLICT (content): Merge conflict in develop.txt
Automatic merge failed; fix conflicts and then commit the result.
錯誤原因
具體情況模擬如下:
在mygit
中修改hello.txt
檔案的第二行為1
,在mygit2
中修改hello.txt
檔案的第二行為2
,即對同一檔案的同一處進行了修改。
此時取決於誰先進行git push
操作,若mygit
先將修改後的hello.txt
推送到遠端倉庫。那麼當mygit2
再進行推送時會出現如下錯誤:
提示資訊表明:遠端倉庫中有一些檔案是你沒有的,無法更新遠端倉庫;這是因為,mygit
先把修改的hello.txt
推送到了遠端倉庫;此時mygit2
想要成功進行推送,需要先將遠端倉庫中經過mygit
修改的hello.txt
與本地倉庫的hello.txt
進行合併。
解決方案
可以使用git pull
來解決這一問題,那麼我們首先執行一次git pull
操作:
可以發現git pull
指令在進行自動合併時發生了錯誤,這是因為mygit
和mygit2
都對hello.txt
的同一個地方做了修改,git
不知道以誰為準,所以會導致自動合併失敗,此時需要通過解決衝突三步曲來手動合併:
第一步:
開啟衝突檔案hello.txt
可以看到典型的衝突檔案顯示方式:
箭頭<<<
與>>>
範圍內表示的是發生衝突的位置。2
是mygit2
對hello.txt
的修改,1
為遠端倉庫中hello.txt
的內容;
經過協商後,留下第3
行,其餘刪除:
由此手動合併了對檔案hello.txt
的修改,解決了衝突。
vim
指令補充:通過esc
進入命令列模式後,通過上下方向鍵選中某一行,再雙擊d
就可以刪除游標所在的行;刪除多行時,在命令列中輸入:2,4d
表示刪除第2~4
行;
第二步:
再次檢視狀態:
發現hello.txt
處於工作區,git
提示我們要通過git add
指令將解決衝突時對hello.txt
所做的修改納入暫存區。
第三步:
執行完git add
之後,再進行提交git commit
:
由此,解決了衝突;
從上圖中箭頭所指內容可以看出:本地倉庫mygit2
中的master
分支已經比本地遠端分支origin/master
分支多了兩次提交。由於origin/master
分支代表著遠端倉庫的master
分支,也就是說本地倉庫mygit2
中的master
分支比遠端倉庫的master
分支領先了兩次提交;過程如下圖所示:
-
首先
mygit
在提交1st
的基礎上進行了第2
次提交(修改hello.txt
),之後mygit
將本地倉庫推送(push
)到遠端倉庫; -
此時
mygit2
同樣在本地倉庫中進行了一次提交3rd
(修改hello.txt
),此時推送到遠端倉庫會出現錯誤,需要進行pull
操作; -
當
mygit2
執行pull
操作,將遠端倉庫拉取到本地後,由於發生衝突,所以暫時不會將origin/master
的指向更新到最新提交;隨後,在mygit2
中手動解除衝突並進行合併後,mygit2
的狀態為:
可以看到解決衝突,手動合併後,mygit2
已經往前更新了兩次提交,而此時origin/master
仍然指向提交1st
。
所以解決衝突後,mygit2
中的master
分支會比origin/master
分支領先兩次提交;再次執行git push
後,origin/master
分支就會指向最新的提交點4th
了,此時三個倉庫的狀態為:
在實際開發當中,難免會出現多個人修改了同一個檔案的情況,在進行手動合併的過程中一定要與對方協商應該如何合併,而不是直接覆蓋;
3.git pull
不同源合併衝突
所謂不同源,指的是兩個倉庫中的分支,根提交節點不同,如下圖所示:
假如本地master
分支要將內容推送到遠端master
分支。由於本地master
分支根提交節點為1st
,遠端master
分支根提交節點為A
,兩個分支沒有公共的父提交節點。所以,無法進行合併。這種情況下執行git pull
會出現以下錯誤:
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
錯誤原因
簡單來說git pull
失敗的原因有兩點:
- 第一點:兩倉庫中的
master
分支由於根提交節點不同,沒有共同的提交歷史。所以,會導致採用三方合併原則合併分支時,找不到公共提交節點而無法合併:
- 第二點:本地
master
分支沒有與遠端倉庫中的任一分支建立關聯。因此,本地master
分支不知道將檔案推送給誰,這樣自然會失敗;
解決方案
知道了git pull
失敗的兩點原因,解決方案就很清晰了,同樣分為三步:
-
第一步:執行一次
git pull
將遠端倉庫的分支拉取到本地:這裡的本地遠端分支
origin/master
和origin/dev
是遠端分支master
和dev
的本地形式,代表著它們,內容上與它們一致。雖然git pull
失敗了,但是我們獲得了遠端分支的資訊,方便進行第二步的合併操作; -
第二步:建立兩分支的公共提交歷史。此時兩分支沒有公共父節點,不能採用
merge
方式合併。應該採用rebase
變基的方式,將本地master
分支追加到遠端master
分支後面。由於本地遠端分支origin/master
與遠端master
分支有這相同的提交歷史,所以可以這樣寫:git rebase origin/master
此時,本地
master
分支的提交歷史變為:A <- B <- 1st <- 2nd
。這樣本地master
分支與遠端master
分支就有了公共的提交歷史,即轉換為了同源分支:簡寫:可以將第一步和第二步通過引數的形式合併為一步操作:
git pull --rebase origin master
-
第三步:建立本地
master
分支與遠端master
分支的關聯。常用的有以下三種方式:- 方式一:
//格式 git branch --set-upstream-to=origin/<branch> master //用在這裡具體為 git branch --set-upstream-to=origin/master master
該指令作用為,將本地
master
分支相關聯的遠端分支設定為遠端master
分支,執行該指令後,通過git branch -vv
檢視分支的關聯情況,可見已順利建立關聯:之後就可以進行推送了:
- 方式二:
git push -u origin master
該指令作用為:建立本地
master
分支與遠端master
分支的聯絡,並進行推送:- 方式三:
git push --set-upstream origin master
作用為:建立本地
master
分支與遠端master
分支的聯絡,並進行推送:
實戰演示
- 建立兩個倉庫
mygitA
和mygitB
,首先在mygitA
中的master
分支上新增A.txt
,提交資訊記為A
:
- 在
mygitA
中建立並切換到dev
分支,新增檔案C.txt
,並進行提交,提交資訊記為C
:
- 切換回
mygitA
的master
分支,新增檔案B.txt
,並進行提交,提交資訊為B
:
此時mygitA
中兩分支的狀態如下:
-
隨後,建立本地倉庫
mygitA
與遠端倉庫的關聯: -
設定並推送
mygitA
的master
分支和dev
分支,到遠端倉庫的master
分支和dev
分支上: -
回到
mygitB
,新增檔案1st.txt
和2st.txt
並進行兩次提交1st
和2nd
:
此時三個倉庫的狀態為:
若想將mygitB
的master
分支推送到遠端倉庫的master
分支上,按照上文的討論,採用簡寫形式,可通過以下兩步進行實現:
- 第一步:通過
rebase
合併本地master
分支與遠端master
分支:
git pull --rebase origin master
執行完上述指令後,mygitB
的狀態為:
- 第二步:建立本地
master
分支與遠端master
分支的聯絡,並進行推送:
git push -u origin master
如圖所示,與遠端分支建立了聯絡,並完成了推送;由此解決了由於不同源造成的pull
操作衝突。
以上就是本節的全部內容,細心的你肯定發現了,在這一節中偶爾會提到本地遠端分支
origin/master
,它到底是什麼呢?有什麼作用?其實它是git
進行本地倉庫與遠端倉庫交流的一個重要橋樑。在下一節中將會為你詳細介紹本地遠端分支的由來和作用,以及最重要的:如何建立本地倉庫與遠端倉庫的分支對應關係?我們下一節再見!