Git應用詳解第六講:Git協作與Git pull常見問題

AhuntSun發表於2020-04-16

前言

前情提要:Git應用詳解第五講:遠端倉庫Github與Git圖形化介面

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方式(典型模型)

image-20200416135410464

首先有兩位使用者ABA的本地倉庫不為空B的倉庫為空,還有一個遠端倉庫C

  • A首先將本地倉庫的程式碼推送(push)到C中,此時AC兩個倉庫的檔案一致,如圖中1所示;
  • 隨後BC的程式碼拉取(pull)下來,如圖中2所示,此時ABC三個倉庫中的檔案一致;隨後AB繼續在本地進行開發,並向各自的本地倉庫進行了數次提交;
  • 此時,A先向C推送修改過後的本地倉庫檔案,由於這是遠端倉庫C的首次修改,C中的檔案A中都有,所以可以直接推送,不用先執行git pull,如圖中3所示;
  • 隨後,在B將修改過的本地倉庫檔案推送到C的過程中會出現錯誤。原因在於:此時的C中有A做出的修改,不能讓B進行覆蓋,此時B要想成功推送,應該先將C中的檔案拉取(pull)到本地;如圖中4所示,拉取時有兩種情況:
    • 成功:說明AB修改的不是同一個檔案,採用Fast-forward方式自動合併;
    • 失敗:說明AB修改了同一個檔案,需要手動解決衝突併合並;
  • B成功將C中的檔案拉取到本地合併後,就能將B對本地倉庫所做的修改推送(push)到遠端倉庫C了,如圖中的5所示;

在整個過程中,可以發現遠端倉庫C僅僅是起到程式碼第三方託管的作用;

3.模擬多人協作

為了模擬多使用者協作,可以使用--local來設定每個倉庫的使用者資訊:

git config --local user.name '張三'

--local是一個配置作用域的引數,其他的還有:

  • --global :作用域為每個計算機使用者,優先度第二,實際上常用這個引數進行配置;
  • --system :作用域為整個系統,優先順序最低;

可以使用:git clone將遠端倉庫的程式碼下載到本地某資料夾中,下面使用的是SSH的方式:

image-20200328170839067

還可以通過在連結後面加上一個字串,重新命名下載到本地的遠端倉庫檔案的名字:

git clone git@gitee.com:ahuntsun/MY.git mygit2

image-20200328171302207

4.協作的本質

遠端倉庫通常有多個分支,而在本地倉庫進行一次推送時並不是將本地倉庫的所有分支都推送到遠端倉庫,而是選擇本地倉庫中的一個分支,將其推送到遠端倉庫的其中一個分支上:

image-20200411182057807

比如本地的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命令時,由於本地倉庫與遠端倉庫歷史提交記錄的不同,往往會出現各種各樣的合併錯誤;在分析這些錯誤之前,首先搭建測試環境:

分別建立兩個本地倉庫mygitmygit2,並且這兩個本地倉庫與同一個遠端倉庫建立聯絡,如下圖所示:

image-20200416141016230

1.不發生合併衝突

在本地倉庫mygit2中使用--local引數配置新的使用者lisi模擬多人協作,隨後通過lisi給遠端倉庫推送一個新的檔案。回到mygit後執行git remote show origin指令,會顯示如下資訊:

image-20200328172158711

表示,本地倉庫mygit相對於遠端倉庫而言已經過時了,即遠端倉庫中有mygit2推送的,mygit中沒有的檔案;此時可以在mygit中執行git pull,將遠端倉庫中的檔案拉取到本地倉庫mygit中進行合併:

image-20200328172428901

上圖中的第二個箭頭表示,在pull操作的過程中mygit中的master分支與遠端倉庫中的master分支採用Fast-forward方式進行了合併,並達到了同步。

這裡的本地遠端分支origin/master代表著遠端master分支,關於本地遠端分支將會在下一節進行詳細講解;

關於Fast-forward方式之前已經介紹過了,在上述合併過程中origin/master分支直接指向了最新提交,中間沒有其他分支,也就不會出現合併衝突,這種合併方式稱為快進。如下圖所示:

image-20200407142030574

這是一個理想的情況,很多情況下執行git pull操作時,都會出現合併衝突,需要解決衝突,再進行手動合併;

2.git pull同源合併衝突

所謂同源,指的是本地倉庫與遠端倉庫中的分支從根提交節點開始,有共同的提交歷史;簡而言之,有共同的根提交節點的兩個分支稱為同源;如下圖所示,兩倉庫中的master分支有共同的根提交節點A,所以這兩個倉庫的master分支是同源的:

image-20200410115057180

這種情況下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再進行推送時會出現如下錯誤:

image-20200328173921075

提示資訊表明:遠端倉庫中有一些檔案是你沒有的,無法更新遠端倉庫;這是因為,mygit先把修改的hello.txt推送到了遠端倉庫;此時mygit2想要成功進行推送,需要先將遠端倉庫中經過mygit修改的hello.txt與本地倉庫的hello.txt進行合併。

解決方案

可以使用git pull來解決這一問題,那麼我們首先執行一次git pull操作:

image-20200328174312298

可以發現git pull指令在進行自動合併時發生了錯誤,這是因為mygitmygit2都對hello.txt的同一個地方做了修改,git不知道以誰為準,所以會導致自動合併失敗,此時需要通過解決衝突三步曲來手動合併:

第一步:

開啟衝突檔案hello.txt可以看到典型的衝突檔案顯示方式:

image-20200328180835818

箭頭<<<>>>範圍內表示的是發生衝突的位置。2mygit2hello.txt的修改,1為遠端倉庫中hello.txt的內容;

經過協商後,留下第3行,其餘刪除:

image-20200328181200775

由此手動合併了對檔案hello.txt的修改,解決了衝突。

vim指令補充:通過esc進入命令列模式後,通過上下方向鍵選中某一行,再雙擊d就可以刪除游標所在的行;刪除多行時,在命令列中輸入:2,4d表示刪除第2~4行;

第二步:

再次檢視狀態:

image-20200328181841135

發現hello.txt處於工作區,git提示我們要通過git add指令將解決衝突時對hello.txt所做的修改納入暫存區。

第三步:

執行完git add之後,再進行提交git commit:
image-20200328182755564

由此,解決了衝突;

從上圖中箭頭所指內容可以看出:本地倉庫mygit2中的master分支已經比本地遠端分支origin/master分支多了兩次提交。由於origin/master分支代表著遠端倉庫的master分支,也就是說本地倉庫mygit2中的master分支比遠端倉庫的master分支領先了兩次提交;過程如下圖所示:

image-20200407152200173

  • 首先mygit在提交1st的基礎上進行了第2次提交(修改hello.txt),之後mygit將本地倉庫推送(push)到遠端倉庫;

  • 此時mygit2同樣在本地倉庫中進行了一次提交3rd(修改hello.txt),此時推送到遠端倉庫會出現錯誤,需要進行pull操作;

  • mygit2執行pull操作,將遠端倉庫拉取到本地後,由於發生衝突,所以暫時不會將origin/master的指向更新到最新提交;隨後,在mygit2中手動解除衝突並進行合併後,mygit2的狀態為:

image-20200407151529950

可以看到解決衝突,手動合併後,mygit2已經往前更新了兩次提交,而此時origin/master仍然指向提交1st

所以解決衝突後,mygit2中的master分支會比origin/master分支領先兩次提交;再次執行git push後,origin/master分支就會指向最新的提交點4th了,此時三個倉庫的狀態為:

image-20200407152431572

在實際開發當中,難免會出現多個人修改了同一個檔案的情況,在進行手動合併的過程中一定要與對方協商應該如何合併,而不是直接覆蓋

3.git pull不同源合併衝突

所謂不同源,指的是兩個倉庫中的分支,根提交節點不同,如下圖所示:

image-20200410123001021

假如本地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分支由於根提交節點不同,沒有共同的提交歷史。所以,會導致採用三方合併原則合併分支時,找不到公共提交節點而無法合併:

image-20200411001626440

  • 第二點:本地master分支沒有與遠端倉庫中的任一分支建立關聯。因此,本地master分支不知道將檔案推送給誰,這樣自然會失敗;
解決方案

知道了git pull失敗的兩點原因,解決方案就很清晰了,同樣分為三步:

  • 第一步:執行一次git pull將遠端倉庫的分支拉取到本地:

    image-20200411211023424

    這裡的本地遠端分支origin/masterorigin/dev是遠端分支masterdev的本地形式,代表著它們,內容上與它們一致。雖然git pull失敗了,但是我們獲得了遠端分支的資訊,方便進行第二步的合併操作;

  • 第二步:建立兩分支的公共提交歷史。此時兩分支沒有公共父節點,不能採用merge方式合併。應該採用rebase變基的方式,將本地master分支追加到遠端master分支後面。由於本地遠端分支origin/master與遠端master分支有這相同的提交歷史,所以可以這樣寫:

    git rebase origin/master
    

    此時,本地master分支的提交歷史變為:A <- B <- 1st <- 2nd。這樣本地master分支與遠端master分支就有了公共的提交歷史,即轉換為了同源分支:

    image-20200411222958428

    簡寫:可以將第一步和第二步通過引數的形式合併為一步操作:

    git pull --rebase origin master
    

    image-20200411222302708

  • 第三步:建立本地master分支與遠端master分支的關聯。常用的有以下三種方式:

    • 方式一:
    //格式
    git branch --set-upstream-to=origin/<branch> master
    //用在這裡具體為
    git branch --set-upstream-to=origin/master master
    

    該指令作用為,將本地master分支相關聯的遠端分支設定為遠端master分支,執行該指令後,通過git branch -vv檢視分支的關聯情況,可見已順利建立關聯:

    image-20200411213556267

    之後就可以進行推送了:

    image-20200411213920987

    • 方式二:
    git push -u origin master
    

    該指令作用為:建立本地master分支與遠端master分支的聯絡,並進行推送:

    image-20200411215931617

    • 方式三:
    git push --set-upstream origin master
    

    作用為:建立本地master分支與遠端master分支的聯絡,並進行推送:

    image-20200411221552193

實戰演示
  • 建立兩個倉庫mygitAmygitB,首先在mygitA中的master分支上新增A.txt,提交資訊記為A

image-20200410125735375

  • mygitA中建立並切換到dev分支,新增檔案C.txt,並進行提交,提交資訊記為C

image-20200410125946568

  • 切換回mygitAmaster分支,新增檔案B.txt,並進行提交,提交資訊為B

image-20200410130141667

​ 此時mygitA中兩分支的狀態如下:

image-20200416153057140

  • 隨後,建立本地倉庫mygitA與遠端倉庫的關聯:

    image-20200410130748140

  • 設定並推送mygitAmaster分支和dev分支,到遠端倉庫的master分支和dev分支上:

    image-20200410232136808

  • 回到mygitB,新增檔案1st.txt2st.txt並進行兩次提交1st2nd

    image-20200410232755681

此時三個倉庫的狀態為:

image-20200410233107154

若想將mygitBmaster分支推送到遠端倉庫的master分支上,按照上文的討論,採用簡寫形式,可通過以下兩步進行實現:

  • 第一步:通過rebase合併本地master分支與遠端master分支:
git pull --rebase origin master

image-20200411231327155

執行完上述指令後,mygitB的狀態為:

image-20200411000848565

  • 第二步:建立本地master分支與遠端master分支的聯絡,並進行推送:
git push -u origin master

image-20200411231531483

如圖所示,與遠端分支建立了聯絡,並完成了推送;由此解決了由於不同源造成的pull操作衝突。

以上就是本節的全部內容,細心的你肯定發現了,在這一節中偶爾會提到本地遠端分支origin/master,它到底是什麼呢?有什麼作用?其實它是git進行本地倉庫與遠端倉庫交流的一個重要橋樑。在下一節中將會為你詳細介紹本地遠端分支的由來和作用,以及最重要的:如何建立本地倉庫與遠端倉庫的分支對應關係?我們下一節再見!

相關文章