前言
這一節來介紹本地倉庫與遠端倉庫的分支對映關係:git refspec
。徹底弄清楚本地倉庫到底是如何與遠端倉庫進行聯絡的。
一、Git refspec
refspec
是Reference Specification
的縮寫,字面意思就是具體的引用。它其實是一種格式,git
通過這種格式來表示本地分支與遠端分支的對映關係;
在本地倉庫建立master
分支外的其他兩個分支develop
和test
:
在develop
分支上執行git push
命令,出現如下錯誤:
這是由於本地分支develop
沒有與任何的遠端分支建立聯絡導致的。通過git branch -vv
檢視本地與遠端分支的關聯情況,可見並沒有建立任何聯絡:
二、本地遠端分支
在講解如何建立與本地分支關聯的遠端分支之前,首先我們來介紹期待已久的本地遠端分支:
git
中其實有三種分支:本地分支、本地遠端分支、遠端分支;- 可以這樣理解:本地遠端分支是遠端分支的一個映象,並且在本地倉庫與遠端倉庫之間起到一個橋樑的作用;
- 在沒有辦法直接檢視遠端倉庫的時候,可以通過本地遠端分支觀察遠端分支的變化情況。比如本地遠端分支
origin/develop
就對應著遠端分支develop
。
1.三分支關係
當本地master
分支建立了與之關聯的遠端分支master
後,檢視當前分支狀態:
圖中的origin/master
為本地遠端分支,代表的是遠端倉庫的master
分支,而這個分支是在本地的;也就是說加上遠端倉庫的master
分支,一共有三個master
分支:
並且,當本地倉庫中的每一個分支都有與之關聯的遠端分支之後,本地倉庫都會建立對應的本地遠端分支,它們所處的位置和關係如下圖所示:
可以這樣理解:本地遠端分支
origin/master
為遠端分支master
的本地化形式;
假設遠端倉庫和本地倉庫檔案內容是一樣的,都只有兩次提交,此時三個分支的狀態如下圖所示:
然後,在本地的master
分支中新增了提交3rd
,本地倉庫的分支情況變為:
上圖中的
git dog
為指令:git log --all --decorate --oneline --graph
的別名,有關內容將在下一節講解。
分支的示意圖如下:
可見本地master
分支比本地遠端分支origin/master
多了一次提交。這是因為本地遠端分支是為了追蹤遠端分支而存在的,只有在執行pull
或push
操作時它的指向才會更新。比如在執行推送(push
)指令時:
-
首先,本地
master
分支對應的本地遠端分支(origin/master
)會指向本地master
分支最新的提交(向前走了幾步); -
然後,本地
master
分支再將檔案推送到遠端master
分支中。完成推送後,三分支的狀態為:
回到終端,我們將剛才新增的提交3rd
推送到遠端分支,成功後檢視本地分支以及本地遠端分支的提交歷史:
可見,本地遠端分支的指向得到了更新,指向了最新的提交3rd
,由此驗證了上述說法。
檢視分支關聯
可以通過以下指令檢視本地分支與本地遠端分支的關聯情況:
git branch -vv
可以看到:本地的master
分支有本地遠端分支origin/master
關聯,說明本地master
分支已經和遠端master
分支建立了關聯;
其餘兩個本地分支pop
和develop
並沒有與之關聯的本地遠端分支,所以它們並沒有與遠端分支建立聯絡。
簡單點說:只要本地分支有與之對應的本地遠端分支,就有與之對應的遠端分支。
總結:origin/master
作用:追蹤遠端分支。當執行git push/pull
操作時,該分支的指向都會相應地發生變化,用於與遠端倉庫保持同步;比如:本地倉庫在執行git push
操作的時候,不僅會把本地的修改推送到遠端;還會同時修改origin/master
分支的指向;
2.實戰演示
可通過該指令檢視本地的所有分支及其最新的提交資訊:
git branch -av
首先,在master
分支上進行三次提交,並將它推送到與之關聯的遠端master
分支,此時各分支的提交歷史為:
三個分支的狀態為:
在此基礎上,在master
分支上進行一次提交4th
,然後檢視狀態git status
:
圖中提示資訊表明,當前分支(master
)已經領先於origin/master
分支一次提交。為了看得更清楚,我們檢視本地各分支的提交歷史:
從圖中可看出,origin/master
分支確實落後了一次提交,表示遠端master
分支落後了一次提交。此時可以使用git push
將新增的提交推送到遠端master
分支,在這個過程中會將本地遠端分支origin/master
指向最新的提交4th
。成功推送之後,再次檢視本地各分支的提交歷史:
可見,通過git push
操作本地遠端分支確實發生了更新,指向了最新提交4th
。這就驗證了執行git push
時進行了兩步操作:
- 將本地
master
分支的新提交推送到與之關聯的遠端master
分支; - 將本地遠端分支
origin/master
指向本地master
分支的最新提交;
git pull
操作同理,也會更新本地遠端分支的指向;
也就是說:每次執行push
或pull
操作後,本地分支、本地遠端分支、遠端分支三個分支的指向都會達到同步。
當切換到origin/master
分支上時,如下圖所示:
git
並不會直接將分支切換到origin/master
上,而是切換到最新的一次提交上,即一個遊離的提交。這從側面說明了:git
是禁止我們直接修改origin/master
分支的,只允許我們切換到最新的提交上;
也就是說本地遠端分支(如origin/master
)是隻讀的,只能由git
來改變,這就解釋了為何使用git branch
無法檢視本地遠端分支。
三、設定遠端分支
弄清楚了什麼是本地遠端分支,就能更好地理解接下來所要介紹的,如何建立本地分支與遠端分支的聯絡了。
1.設定同名遠端分支
上圖提示資訊中的:upstream branch
表示上游分支,即遠端倉庫的分支。當前的本地分支develop
並沒有一個遠端倉庫的develop
分支與之對應;要想推送develop
分支到遠端倉庫的同名分支,首先要建立對應的遠端分支,有以下兩種型別四種方法:
-
型別一:建立本地與遠端分支追蹤關係的。
git push --set-upstream origin <branch> git push -u origin <branch>
使用該型別方法,只需設定一次,之後就可以使用簡寫形式
git push
進行推送。 -
型別二:不建立本地與遠端分支追蹤關係的。
git push origin HEAD git push origin <branch_name>
使用該型別方法,每次推送都需要採用上述的完整寫法。
下面就來詳細介紹這四種方法:
git push --set-upstream origin <branch>
方法一:採用下述指令為本地倉庫mygit
的develop
分支建立遠端分支:
git push --set-upstream origin develop
該命令的作用為:在遠端倉庫創鍵一個與本地分支develop
關聯的同名分支develop
,並將本地分支develop
的檔案推送到該遠端分支上。
也就是將本地分支develop
的上游分支設定為遠端倉庫的develop
分支,並進行檔案同步。
執行完上述命令後會有這樣的提示:
表示本地的develop
分支已與遠端的develop
分支建立聯絡;此時檢視本地分支,會發現多了一個本地遠端分支origin/develop
,並且已與本地develop
分支建立了聯絡:
隨後再次執行git push
就不會出現問題了:
此時在github
上檢視對應的遠端倉庫,就能檢視到新增的遠端分支develop
了:
上圖中的
master
分支是遠端倉庫建立時預設建立的,並沒有與本地master
分支建立聯絡。
隨後點開branch
可以看到:
當前一共有兩個分支,master
分支是default
(預設)分支,是不能夠被刪除的;活躍的分支為deavelop
;
git push -u origin <branch>
方法二:先切換到test
分支,再執行以下命令,為本地倉庫mygit
的test
分支建立對應的遠端分支:
git push -u origin test
-u
與--set-upstream
作用是類似的,都是在遠端倉庫新建一個新的分支,並與本地分支建立聯絡。
執行完上述指令後,再次檢視本地分支的詳細情況,以及分支對應關係,可以發現test
分支已與遠端test
分支建立聯絡:
git push origin HEAD
方法三:
如下圖所示,通過該指令成功設定了本地develop
分支對應的遠端develop
分支。但沒有顯示追蹤資訊,之後不能使用git push
推送。
git push origin <branch>
方法四:
如下圖所示,該方法實質上與方法三相同,因為HEAD
指向的就是當前分支。同樣沒有顯示追蹤資訊,之後也不能使用git push
推送。
總結:當本地分支與遠端分支同名時,一旦手動建立了它們之間的聯絡。之後推送本地分支的檔案到對應的遠端分支時可以採用簡寫形式:
git push
。這是因為在已經建立三個分支的對應關係並後,再執行
git push
,git
會自動地將同名的本地分支與遠端分支進行匹配;而其他情況則要採用完整寫法進行推送。關於這些結論,將在第三大點
-u
引數的作用中詳細介紹。
2.設定不同名遠端分支
主要有以下四種方法,注意:使用每種方法前都需要先切換到對應分支上。
git push --set-upstream origin <branch1>:<branch2>
方法一: 比如當前位於develop
分支,如果採用的是以下簡寫命令:
git push --set-upstream origin develop
則會建立一個同名的遠端分支develop
。而如果採用該命令的完整寫法,就可以自定義遠端分支的名字了,比如設為develop2
:
git push --set-upstream origin develop:develop2
執行上述指令後,成功建立了對應的,不同名的本地遠端分支origin/develop2
。表示本地develop
分支已與遠端develop2
分支建立聯絡(因為遠端分支與本地遠端分支是一一對應的關係):
在github
上檢視本地倉庫關聯的遠端倉庫MY
,可以看到順利建立了develop2
分支:
可以發現這麼一個規律:在建立遠端分支的同時會建立同名的本地遠端分支。
git push -u origin <branch1>:<branch2>
方法二:
如下圖所示,使用-u
引數也能將本地develop
分支的遠端分支自定義為develop2
。
git push origin HEAD:<branch>
方法三:
通過該方法也能成功設定與本地分支關聯的,不同名的遠端分支develop2
:
git push origin <branch1>:<branch2>
方法四:該方法與方法二實質上是一樣的,因為方法二中的HEAD
指標指向的就是當前所在的分支,也就是develop
分支。過程與方法二類似:
上面這四種設定不同名遠端分支的方法,都有一個共同特點:不能使用
git push
進行推送。
若使用git push
都會出現找不到對應遠端分支的錯誤:
原因在下面第三點的-u
引數作用中會詳細講解。
既然是-u
引數追蹤問題,那我加上-u
引數不就行了麼?其實這樣也行不通:
解決方案:每次推送的時候,指明本地分支與遠端分支的對應關係,即採用上述命令的完整寫法,比如:
git push --set-upstream origin develop:develop2
git push -u origin develop:develop2
git push origin develop:develop2
git push origin HEAD:develop2
採用了完整寫法後,成功地進行了推送,如下圖所示:
注意:雖然可以自定義遠端分支與本地遠端分支的名字,但是十分不推薦,因為容易出錯。所以,建議本地遠端分支和遠端分支都使用預設的,與本地分支相同的名字。
3.總結
以本地分支develop
為例,不難發現:
-
使用下列簡寫命令時,遠端分支和本地遠端分支都會採用預設的,與本地分支相同的名字:
git push --set-upstream origin develop git push -u origin develop
-
而使用下列命令的完整寫法時,就可以自定義遠端分支與本地遠端分支的名字:
git push --set-upstream origin develop:develop2
git push -u origin develop:develop2
git push origin develop:develop2
git push origin HEAD:develop2
四、git push origin master
與git push -u origin master
的區別
第一次將本地倉庫的master
分支推送到遠端倉庫的master
分支上時,使用前者和後者都可以順利推送,區別在於是否使用了-u
引數:
-
推送時不使用
-u
引數: -
推送時使用
-u
引數:
注意到推送時使用-u
引數會列印下列提示資訊:
Branch 'master' set up to track remote branch 'master' from 'origin'.
表示本地的master
分支被設定去追蹤遠端的master
分支,在第2~n
次推送中,只需要使用git push
這樣的簡寫命令(當然,完整寫法效果等同)。git
就會自動將本地的master
分支與遠端的master
分支進行匹配,完成推送:
而不使用-u
引數時,沒有上述的分支追蹤資訊。此時使用簡寫git push
進行推送會出現錯誤:
錯誤資訊顯示:當前分支沒有與之對應的遠端分支。這個時候想要成功推送,必須採用指明對應關係的完整寫法,比如:
git push origin master
這就是推送時使不使用-u
引數的區別。並且,根據上面的介紹,使用如下指令進行推送也能達到-u
引數的效果:
git push --set-upstream origin develop
之後也可以使用簡寫的git pull
指令進行推送:
細心的你一定發現了,以上都只是本地分支與遠端分支同名的情況。不同名的情況下,上面的兩個方法還好使嗎?
首先驗證方法一:-u
引數:
設定不同名的遠端分支時要注意寫成完整形式:
pop:pop2
可以看到,即使建立不同名的遠端分支,-u
引數也一樣能夠設定追蹤關係;但是,奇怪的是git push
卻不好使了:
還是和沒使用-u
引數時一樣,找不到對應的遠端分支,需要採用指明對應關係的完整寫法,比如:
git push origin pop:pop2
其次驗證方法二:--set-upstream
:
同樣設定分支對應關係時要使用完整寫法。可以看到,該方法也設定了追蹤關係。奇怪的是git push
同樣不管用:
同樣找不到對應的遠端分支,需要採用指明對應關係的完整寫法,比如:
git push origin bob:bob2
所以可以得出結論:
- 本地/遠端分支同名時:
-u
引數的作用是設定本地分支與遠端分支的追蹤關係,設定了追蹤關係後,之後的推送可使用簡寫git push
,git
內部會自動進行匹配;--set-upstream
引數與-u
引數效果等同;
- 本地/遠端分支不同名時:
--set-upstream
引數與-u
引數依然可以設定分支的追蹤關係,但是,之後的推送不能使用簡寫git push
,只能使用指定分支對應關係的完整寫法;
總結:十分建議將所有的本地分支與對應的遠端分支設為同名,並且第一次推送使用--set-upstream
或-u
引數建立分支追蹤關係,之後就可以使用簡寫git push
進行推送了!
五、git push -f
該命令的完整寫法為:
git push -f origin master
意思為強制推送:直接跳過與遠端倉庫的master
分支合併的環節,強制覆蓋遠端倉庫上master
分支的內容,即以本地的master
分支內容為準。應慎用該命令,否則將覆蓋遠端倉庫中master
分支上其他人推送的檔案(一星期的成果沒了)。
1.應用場合
- 當遠端倉庫的歷史提交記錄太亂了,想要重新整理時。注意:一定要與其他人協商好再用本地分支強制覆蓋遠端分支。
- 只有一個人開發時,程式碼以本地為準。為了避免推送時繁瑣的合併,可以使用
-f
強制推送,直接覆蓋遠端分支上的內容;
分兩種寫法:
-
第一種:已經通過
-u
引數等方式,設定了本地分支與遠端分支的追蹤關係時,採用:git push -f
-
第二種:還未設定追蹤關係,採用:
git push -u origin master -f
2.預防措施
Github
提供了相應的分支保護機制,可以在Settings
選項中進行設定:
可以看到Github
預設是保護分支的:
3.補救措施
讓有進度的人,再次對被強制覆蓋的遠端分支執行一次 git push -f
指令,把正確的內容強制推送上去,覆蓋前一次 git push -f
所造成的災難。
六、設定遠端分支對應的本地分支
假如遠端倉庫M3Y
中有master
和develop
兩個分支,此時新建一個空的本地倉庫mygit
,通過以下指令將它的遠端倉庫地址origin
設定為M3Y
的地址:
git remote add origin git@github.com:AhuntSun/M3Y.git
此時兩倉庫的狀態為:
由於mygit
是空倉庫與遠端倉庫M3Y
沒有任何公共提交歷史,所以在執行git pull
時會出現下圖所示的不同源衝突(上一節中詳細介紹過該衝突):
雖然git pull
操作失敗了,但是也成功地將遠端倉庫M3Y
的分支拉取了下來。但是,通過git branch -vv
檢視分支追蹤關係,發現並沒有本地分支與這兩個遠端分支建立了聯絡:
如何建立這兩個本地遠端分支對應的本地分支?可以通過以下兩種方法:
1.git checkout -b <branch> origin/<branch>
比如可以通過以下命令,設定本地遠端分支origin/master
與本地master
分支的追蹤關係:
git checkout -b master origin/master
以上為本地master
分支已存在的情況,如果本地分支develop
未建立,可以採用下述命令建立並切換到develop
分支,並且設定origin/develop
與develop
的追蹤關係:
git checkout -b develop origin/develop
設定了本地分支與遠端分支的追蹤關係,接下來就可以在本地倉庫執行git push
進行推送了:
2.git checkout --track origin/<branch>
重置條件,新建立一個空的本地倉庫mygit2
,同樣將其遠端地址origin
設定為遠端倉庫M3Y
的地址。隨後在本地倉庫mygit2
中執行git pull
操作,將遠端倉庫M3Y
中的兩個分支拉取到本地:
與上次一樣,拉取到本地的兩個本地遠端分支沒有與任何本地分支建立追蹤關係。這次可以採用另外一種方法:
git checkout --track origin/test
建立並切換到develop
分支,並且設定該分支與origin/develop
分支的追蹤關係:
可以說該方法是方法一的特殊情況,因為該方法沒有指明建立的本地分支的名字,所以預設採用與遠端分支一樣的名字develop
來命名;
如果想在本地建立一個develop2
(不同名)的分支與本地遠端分支origin/test
建立追蹤關係,則應採用第一種方法。
七、遠端分支資訊
可以進入.git
目錄,檢視儲存遠端分支資訊的檔案:
1.檢視config
檔案
使用vim
編輯器開啟該檔案,可以檢視到關於遠端分支的資訊:
可以看到remote
這一欄中有兩個資訊,第一個是遠端倉庫的url
,第二個是fetch
資訊,這兩個資訊尤為重要:
refs/heads/*
表示遠端倉庫的refs/heads
目錄下的所有引用都會寫入到本地的refs/remotes/origin
目錄中;- 其中的
+
號是可選的,加了表示無論是否能夠自動合併,即是否為Fast Forward
方式,都將遠端倉庫所有檔案拉取到本地。 - 而不加
+
則表示如果不是Fast Forward
方式就不拉取。一般情況下都是加上+
號的,先把檔案拉取到本地,不是Fast Forward
方式就手動合併;
2.檢視refs
檔案
refs
資料夾儲存著refspec
的檔案,裡面維護著三個目錄:
-
第一個目錄
heads
:儲存的是本地倉庫的分支資訊:可以檢視其中一個分支:
是一個
SHA1
值,表示分支就是一個指標,指向當前提交。 -
第二個目錄
remotes
:裡面存放著遠端分支資訊,遠端倉庫中也存在這樣的目錄與檔案;從上圖可以看到,遠端分支只有
master
,沒有develop
(因為之前被刪除了)。並且它們本質上也是一個代表提交的SHA1
值:建立
refspec
對映(即本地分支、本地遠端分支、遠端分支三者間對應關係)後,git
會獲取遠端上refs/heads
下的所有引用,並將它們寫入本地的refs/remotes/origin
目錄下。所以,可以通過檢視本地遠端分支(如origin/master
)的方式檢視本地倉庫最後一次訪問遠端倉庫時,遠端倉庫master
分支上的歷史提交記錄://完整寫法 git log refs/remotes/origin/master //進一步簡寫 git log remotes/origin/master //繼續簡寫 git log origin/master
上述兩種省略的寫法最終都會轉換為完整的寫法:
-
第三個目錄
tags
:存放標籤資訊,也是一個SHA1
值:詳細內容將在下一節介紹。
八、刪除遠端分支
如下圖所示,遠端倉庫有三個分支master
、develop
和test
:
通過前面的學習,我們知道通過下述指令可以刪除本地develop
分支:
git branch -d develop
那麼如何刪除遠端分支呢?
首先我們來看看git push
的完整寫法:
git push origin srcBranch:destBranch
-
srcBranch
表示本地的分支,destBranch
表示對應的遠端分支; -
表示將本地的分支推送到遠端分支,這兩個分支可以不同;
-
之所以可以直接使用
git push
是因為我們設定的本地分支和遠端分支的名字是相同的,並且手動建立了聯絡,所以git
能夠自動識別;
明白了這點後,就不難理解下列刪除遠端分支的兩種做法了:
1.git push origin :destBranch
將空的分支推送到遠端分支,這樣就能將該遠端分支刪除;比如刪除本地分支develop
的遠端分支:
git push origin :develop
可以看到成功刪除了遠端分支develop
以及它所對應的本地遠端分支origin/develop
。
注意:並不需要切換到需要刪除遠端分支的本地分支
develop
上,再執行上述指令。也就是說,可以在任意本地分支上刪除任意本地分支對應的遠端分支。
2.git push origin --delete destBranch
還可以採用更加直觀的--delete
引數,比如刪除遠端分支develop
:
git push origin --delete develop
這兩種方式是等價的,可根據需求選擇。
3.git remote prune origin
該方法用於刪除無效的遠端分支對應的本地遠端分支,具體場合如下:
如圖所示mygit
與mygit2
共享一個有三個分支的遠端倉庫:
首先在mygit2
中刪除遠端倉庫的develop
分支,可以看到mygit2
中遠端分支develop
對應的本地遠端分支origin/develop
被刪除了:
然後在mygit
中檢視遠端分支詳細資訊:
可以看到提示資訊中顯示遠端分支develop
對應的本地遠端分支origin/develop
處於stale
(腐爛,遊離)狀態,即該分支對於mygit
來說已經失效,可以使用:
git remote prune origin
(prune
:裁剪)刪除mygit
上這個無效的本地遠端分支:
再次檢視分支資訊,可發現mygit
中的本地遠端分支origin/develop
已經被刪除了:
注意:一般本地遠端分支設定了保護措施,不能隨意刪除;
九、重新命名分支
1.本地分支
可以通過以下命令,將本地分支dev
重新命名為develop
:
git branch -m dev develop
2.遠端分支
無法直接重新命名遠端分支,只能通過先刪除原來的遠端分支,再建立重新命名後develop
分支對應的遠端分支,過程為:
//刪除遠端分支dev
git push origin :dev
//建立重新命名後develop分支對應的遠端分支
git push -u origin develop
由此間接地完成了遠端分支的重新命名。
以上就是本節的全部內容,相信看到這裡的你已經十分熟悉
git refspec
了。下一節將介紹git
標籤與別名。