開篇就提到過,Git是一個分佈式版本管理系統。但是到現在爲止,我們所有的演練都是在本地Git倉庫。如果想與他人合作,還需要一個遠程的 Git 倉庫。儘管技術上可以從個人的倉庫裏推送和拉取修改內容,但我們不鼓勵這樣做,因爲一不留心就很容易弄混其他人的進度。另外,你也一定希望合作者們即使在自己不開機的時候也能從倉庫獲取數據——擁有一個更穩定的公共倉庫十分有用。因此,更好的合作方式是建立一個大家都可以訪問的共享倉庫,從那裏推送和拉取數據。我們將把這個倉庫稱爲“Git 服務器”。
傳輸協議
理論上來說,Git 可以使用四種協議來傳輸數據:本地傳輸協議,SSH 協議,Git 協議和HTTP 協議。
1、本地傳輸協議
本地傳輸,其實就是在使用本地的某個Git倉庫當做遠程倉庫。這常見於團隊每一個成員都對一個共享的文件系統(例如NFS)擁有訪問權。基於文件倉庫的優點在於它的簡單,同時保留了現存文件的權限和網絡訪問權限。如果團隊已經有一個全體共享的文件系統,建立倉庫就十分容易。
這種方法的缺點是,與基本的網絡連接訪問相比,難以控制從不同位置來的訪問權限。
2、Git 協議
Git 協議是一個包含在Git 軟件包中的特殊守護進程; 它會監聽一個提供類似於 SSH 服務的特定端口(9418),而無需任何授權。打算支持 Git 協議的倉庫,需要先創建git-export-daemon-ok 文件——它是協議進程提供倉庫服務的必要條件——但除此之外該服務沒有什麼安全措施。要麼所有人都能克隆 Git 倉庫,要麼誰也不能。這也意味着該協議通常不能用來進行推送。你可以允許推送操作;然而由於沒有授權機制,一旦允許該操作,網絡上任何一個知道項目 URL 的人將都有推送權限。
Git 協議是現存最快的傳輸協議。如果在提供一個有很大訪問量的公共項目,或者一個不需要對讀操作進行授權的龐大項目,架設一個Git 守護進程來供應倉庫是個不錯的選擇。它使用與 SSH 協議相同的數據傳輸機制,但省去了加密和授權的開銷。
Git 協議消極的一面是缺少授權機制。用 Git 協議作爲訪問項目的唯一方法通常是不可取的。一般的做法是,同時提供 SSH 接口,讓幾個開發者擁有推送(寫)權限,其他人通過git:// 擁有隻讀權限。Git 協議可能也是最難架設的協議。它要求有單獨的守護進程,需要定製。該協議還要求防火牆開放 9418 端口,而企業級防火牆一般不允許對這個非標準端口的訪問。大型企業級防火牆通常會封鎖這個少見的端口。
3、SSH 協議
SSH 爲 Secure Shell 的縮寫,是建立在應用層和傳輸層基礎上的安全協議,可以有效防止遠程管理過程中的信息泄露問題。通過SSH,可以把所有傳輸的數據進行加密,這樣“中間人”這種攻擊方式就不可能實現了,而且也能夠防止DNS欺騙和IP欺騙。使用SSH,還有一個額外的好處就是傳輸的數據是經過壓縮的,所以可以加快傳輸的速度。
Git 使用的傳輸協議中最常見就是 SSH 。因爲大多數環境已經支持通過 SSH 對服務器的訪問——即便還沒有,架設起來也很容易。
使用 SSH 的好處有很多。首先,如果想擁有對網絡倉庫的寫權限,基本上不可能不使用 SSH。其次,SSH 架設相對比較簡單,而且很多操作系統都自帶了它或者相關的管理工具。再次,通過 SSH 進行訪問是安全的——所有數據傳輸都是加密和授權的。最後,和 Git 及本地協議一樣,SSH 也很高效,會在傳輸之前儘可能壓縮數據。
SSH 的限制在於不能通過它實現倉庫的匿名訪問。即使僅爲讀取數據,人們也必須在能通過 SSH 訪問主機的前提下才能訪問倉庫,這使得 SSH 不利於開源的項目。但是如果是在公司網絡裏使用,SSH 絕對是首選協議。
4、HTTP/S協議
HTTP 或 HTTPS 協議的優美之處在於架設的簡便性。基本上,只需要把 Git 的裸倉庫文件放在 HTTP 的根目錄下,配置一個特定的post-update 掛鉤(hook)就可以搞定。
HTTP 協議不會佔用過多服務器資源。因爲它一般只用到靜態的 HTTP 服務提供所有數據,普通的 Apache 服務器平均每秒都能支撐數千個文件的併發訪問。
HTTP 協議的消極面在於,相對來說客戶端效率更低。克隆或者下載倉庫內容可能會花費更多時間,而且 HTTP 傳輸的體積和網絡開銷比其他任何一個協議都大。
以上四個協議中,本地傳輸協議與Git協議只會在實驗中使用,企業級應用中普遍以SSH與HTTP爲主。
在與遠程倉庫進行信息交換之前,我們先要獲得訪問權限,對Git服務器上的某個項目,自己是否可讀、是否可寫。這就需要確立一個Git服務器有相互識別的手段,最常用、最可靠的就是公鑰私鑰認證,github上使用的就是這種手段,如何設置官網有詳細的教程,不再贅述。
add操作
git remote add [shortname] [url]
可以將一個遠程倉庫與本地Git倉庫關聯起來。
比如,我在github上有一個名爲“MyProject”的項目,在本地的Git倉庫目錄下運行:
git remote add gitHubProjectgit@github.com:Winner2015/MyProject.git
然後使用“git remote”查看現有的遠程倉庫列表:
“[email protected]:Winner2015/MyProject.git”是我的項目在github上的地址,“gitHubProject”是我給遠程倉庫起的別名,以後就可以用gitHubProject來代指這個遠程倉庫。
注意:該命令只是建立了本地Git倉庫與一個URL地址(其實本質上就是一個字符串)的關聯關係,但是這個URL存不存在(比如,你有可能輸錯了一個字母)、你有沒有權限訪問,Git暫時並不知道。
fetch操作
git fetch [remote-name]
可以從遠程倉庫抓取數據到本地。
例如,將剛添加的遠程倉庫拉取到本地:
現在github上的項目MyProject已經抓取到本地了,“gitHubProject/master”代表該項目的master分支。
使用命令“git checkout gitHubProject/master”命令切換到該項目的master分支,Git會提示:
遠程倉庫雖然已經抓取到本地,但是並沒有與本地的任何分支關聯,所以Git警告,遠程分支處於“detached HEAD”狀態,遊離於所有已知分支之外。
fetch 命令只是將遠端的數據拉到本地倉庫,並不自動合併到當前工作分支。
實際上,如果我們想將自己的修改提交到遠程倉庫,首先必須提交到本地,然後再push到遠程倉庫。所以,應該將抓取下來的數據手工merge到某個現有分支或者新建的一個分支當中。
該命令在實際應用中很少使用,常用的是更高級的pull命令。
pull操作
如果設置了本地某個分支用於跟蹤某個遠端倉庫的分支,可以使用 git pull 命令自動抓取數據下來,並將遠端分支自動合併到本地倉庫中當前分支(前提是該遠程分支已經與本地的某個分支建立了關聯)。在日常工作中我們經常這麼用,既快且好。
git pull [remote-name]
pull之後,我們的本地分支很可能與遠程分支出現衝突,同樣需要解決之後才能順利合併、提交。
push操作
項目進行到一個階段,要同別人分享目前的成果,可以將本地倉庫中的數據推送到遠程倉庫。實現這個任務的命令很簡單:
git push [remote-name] [branch-name]
set-upstream設置
某種情況下,初次運行git pull或者git push的時候,Git會提示說“no tracking information”,無法完成操作,則說明本地分支和遠程分支之間的關聯沒有創建。用命令:
git branch --set-upstream [branch-name] [origin/branch-name]
可以將某個遠程分支設置爲本地分支的“上游”。
在版本教新的Git中,該命令已經不推薦使用,而是使用“–track”參數或“–set-upstream-to”參數。
創建本地分支並追蹤遠程某個分支,可以用一個命令搞定:
git branch --track local_branchnameorigin/remote_branchname
手動設置本地分支的上游時,推薦使用命令:
git branch --set-upstream-to=origin/ remote_branchname
取消對某個分支的跟蹤,使用命令:
git branch --unset-upstream local_branchname
clone操作
使用clone命令可以將Git服務器上的數據克隆到本地,例如:
git clone git@github.com:Winner2015/MyProject.git
默認情況下git clone 命令自動創建本地的 master 分支用於跟蹤遠程倉庫中的 master 分支,並且將遠程倉庫命名爲“origin”。
使用命令“git remote show origin”可以查看名爲“origin”的遠程倉庫的信息:
Git友好地告訴你,pull操作會將遠程倉庫的master分支自動與本地的master分支合併;類似地,push操作會自動將本地的master合併到遠程的master分支。
刪除和重命名
重命名命令:
git remote rename [newName]
刪除命令:
git remote rm [remoteName]
注意:該命令只是將本地分支與遠程分支之間的關聯刪除,而不是將遠程倉庫對應的分支刪除。
遠程分支詳解
遠程分支是對遠程倉庫中的分支的索引。它們是一些無法移動的本地分支;只有在 Git 進行網絡交互時纔會更新。遠程分支就像是書籤,提醒着上次連接遠程倉庫時上面各分支的位置。
我們用“遠程倉庫名/分支名”這樣的形式表示遠程分支。比如我們想看看上次同 origin 倉庫通訊時master 的樣子,就應該查看 origin/master 分支。
假設Git服務器地址爲 git.ourcompany.com,如果從這裏克隆,Git 會自動將此遠程倉庫命名爲origin,並下載其中所有的數據,同時建立一個指向它的 master 分支的指針,在本地命名爲origin/master,但你無法在本地更改其數據。接着,Git 建立一個屬於你自己的本地master 分支,始於 origin 上 master分支相同的位置,你可以就此開始工作:
一次 Git 克隆會建立本地分支 master 和遠程分支 origin/master,它們都指向 origin/master 分支的最後一次提交。
如果在本地 master 分支做了些改動,與此同時,其他人向 git.ourcompany.com 推送了他們的更新,那麼服務器上的master 分支就會向前推進,而於此同時,在本地的提交歷史正朝向不同方向發展。不過只要不和服務器通訊,你的 origin/master 指針仍然保持原位不會移動:
可以運行 git fetch origin 來同步遠程服務器上的數據到本地。該命令首先找到 origin 是哪個服務器,從上面獲取尚未擁有的數據,更新本地的數據庫,然後把 origin/master 的指針移到它最新的位置上:
要想和其他人分享某個本地分支,需要把它push到一個擁有寫權限的遠程倉庫。本地分支不會被自動同步到引入的遠程服務器上,除非明確執行推送操作。換句話說,對於無意分享的分支,儘管保留爲私人分支好了,而只推送那些協同工作要用到的特性分支。
如果有個叫 serverfix 的分支需要和他人一起開發,可以運行git push 遠程倉庫名 分支名:
也可以運行 git push origin serverfix:serferfix 來實現相同的效果,它的意思是“上傳我本地的 serverfix 分支到遠程倉庫中去,仍舊稱它爲 serverfix 分支”。通過此命令,可以把本地分支推送到某個命名不同的遠程分支:若想把遠程分支叫作awesomebranch,可以用 git push origin serverfix:awesomebranch 來推送數據。
接下來,當你的協作者再次從服務器上獲取數據時,他們將得到一個新的遠程分支 origin/serverfix。
值得注意的是,在 你的協作者fetch好新的遠程分支之後,仍然無法在本地編輯該遠程倉庫中的分支。換句話說,在本例中,不會有一個新的serverfix分支,有的只是一個無法移動的 origin/serverfix 指針。
如果要把該內容合併到當前分支,可以運行 git merge origin/serverfix。如果想要一份自己的serverfix 來開發,可以在遠程分支的基礎上分化出一個新的分支來:
git checkout -b serverfix origin/serverfix
這會切換到新建的serverfix 本地分支,其內容同遠程分支 origin/serverfix 一致,這樣你就可以在裏面繼續開發了。
從遠程分支 checkout 出來的本地分支,稱爲跟蹤分支(tracking branch)。跟蹤分支是一種和遠程分支有直接聯繫的本地分支。在跟蹤分支裏輸入git push,Git 會自行推斷應該向哪個服務器的哪個分支推送數據。反過來,在這些分支裏運行 git pull 會獲取所有遠程索引,並把它們的數據都合併到本地分支中來。
在克隆倉庫時,Git 通常會自動創建一個名爲 master 的分支來跟蹤 origin/master。這正是git push 和 git pull 一開始就能正常工作的原因。當然,你可以隨心所欲地設定爲其它跟蹤分支,比如origin 上除了 master 之外的其它分支:
git checkout -b[分支名] [遠程名]/[分支名]
還可以“–track” 選項簡化:
git checkout--track origin/serverfix
要爲本地分支設定不同於遠程分支的名字,只需在前個版本的命令裏換個名字:
git checkout -b sf origin/serverfix
現在你的本地分支 sf 會自動向 origin/serverfix 推送和抓取數據了。
如果不再需要某個遠程分支了,可以用這個非常無厘頭的語法來刪除它:
git push [遠程名] :[分支名]
如果想在服務器上刪除serverfix分支,運行下面的命令:
git push origin :serverfix
這個命令比較怪異,其實它與“git push [遠程名] [本地分支]:[遠程分支] ”的語法一樣,只不過省略 [本地分支],等於是在說“在這裏提取空白然後把它變成[遠程分支]”,也就是刪除。