前言
到目前為止,你應該已經學會了使用 Git 來完成日常工作。然而,如果想與他人合作,還需要一個遠程的 Git 倉庫。盡管技術上可以從個人的倉庫里推送和拉取修改內容,但我們不鼓勵這樣做,因為一不留心就很容易弄混其他人的進度。另外,你也一定希望合作者們即使在 自己不開機的時候也能從倉庫獲取數據 — 擁有一個更穩定的公共倉庫十分有用。因此,更好的合作方式是建立一個大家都可以訪問的共享倉庫,從那里推送和拉取數據。我們將把這個倉庫稱為 “Git 服務器”;代理一個 Git 倉庫只需要花費很少的資源,幾乎從不需要整個服務器來支持它的運行。
架設一台 Git 服務器並不難。第一步是選擇與服務器通訊的協議。本章第一節將介紹可用的協議以及各自優缺點。下面一節將介紹一些針對各個協議典型的設置以及如何在服務器 上實施。最后,如果你不介意在他人服務器上保存你的代碼,又想免去自己架設和維護服務器的麻煩,倒可以試試我們介紹的幾個倉庫托管服務。
如果你對架設自己的服務器沒興趣,可以跳到本章最后一節去看看如何申請一個代碼托管服務的賬戶然后繼續下一章,我們會在那里討論分布式源碼控制環境的林林總總。
遠程倉庫通常只是一個_裸倉庫(bare repository)_ — 即一個沒有當前工作目錄的倉庫。因為該倉庫只是一個合作媒介,所以不需要從硬盤上取出最新版本的快照;倉庫里存放的僅僅是 Git 的數據。簡單地說,裸倉庫就是你工作目錄中.git 子目錄內的內容。
協議講解
Git 可以使用四種主要的協議來傳輸數據:本地傳輸,SSH 協議,Git 協議和 HTTP 協議。下面分別介紹一下哪些情形應該使用(或避免使用)這些協議。值得注意的是,除了 HTTP 協議外,其他所有協議都要求在服務器端安裝並運行 Git。
本地協議
最基本的就是_本地協議(Local protocol)_,所謂的遠程倉庫在該協議中的表示,就是硬盤上的另一個目錄。這常見於團隊每一個成員都對一個共享的文件系統(例如 NFS)擁有訪問權,或者比較少見的多人共用同一台電腦的情況。后面一種情況並不安全,因為所有代碼倉庫實例都儲存在同一台電腦里,增加了災難性數據損失 的可能性。
如果你使用一個共享的文件系統,就可以在一個本地文件系統中克隆倉庫,推送和獲取。克隆的時候只需要將遠程倉庫的路徑作為 URL 使用,比如下面這樣:
$ git clone /opt/git/project.git
或者這樣
$ git clone file:///opt/git/project.git
如果在 URL 開頭明確使用 file:// ,那么 Git 會以一種略微不同的方式運行。如果你只給出路徑,Git 會嘗試使用硬鏈接或直接復制它所需要的文件。如果使用了file:// ,Git 會調用它平時通過網絡來傳輸數據的工序,而這種方式的效率相對較低。使用 file:// 前綴的主要原因是當你需要一個不包含無關引用或對象的干凈倉庫副本的時候 — 一般指從其他版本控制系統導入的,或類似情形(參見第 9 章的維護任務)。我們這里僅僅使用普通路徑,這樣更快。
要添加一個本地倉庫作為現有 Git 項目的遠程倉庫,可以這樣做:
$ git remote add local_proj /opt/git/project.git
然后就可以像在網絡上一樣向這個遠程倉庫推送和獲取數據了。
優點
基於文件倉庫的優點在於它的簡單,同時保留了現存文件的權限和網絡訪問權限。如果你的團隊已經有一個全體共享的文件系統,建立倉庫就十分容易了。你 只需把一份裸倉庫的副本放在大家都能訪問的地方,然后像對其他共享目錄一樣設置讀寫權限就可以了。我們將在下一節“在服務器上部署 Git ”中討論如何導出一個裸倉庫的副本。
這也是從別人工作目錄中獲取工作成果的快捷方法。假如你和你的同事在一個項目中合作,他們想讓你檢出一些東西的時候,運行類似 git pull /home/john/project 通常會比他們推送到服務器,而你再從服務器獲取簡單得多。
缺點
這種方法的缺點是,與基本的網絡連接訪問相比,難以控制從不同位置來的訪問權限。如果你想從家里的筆記本電腦上推送,就要先掛載遠程硬盤,這和基於網絡連接的訪問相比更加困難和緩慢。
另一個很重要的問題是該方法不一定就是最快的,尤其是對於共享掛載的文件系統。本地倉庫只有在你對數據訪問速度快的時候才快。在同一個服務器上,如果二者同時允許 Git 訪問本地硬盤,通過 NFS 訪問倉庫通常會比 SSH 慢。
SSH 協議
Git 使用的傳輸協議中最常見的可能就是 SSH 了。這是因為大多數環境已經支持通過 SSH 對服務器的訪問 — 即便還沒有,架設起來也很容易。SSH 也是唯一一個同時支持讀寫操作的網絡協議。另外兩個網絡協議(HTTP 和 Git)通常都是只讀的,所以雖然二者對大多數人都可用,但執行寫操作時還是需要 SSH。SSH 同時也是一個驗證授權的網絡協議;而因為其普遍性,一般架設和使用都很容易。
通過 SSH 克隆一個 Git 倉庫,你可以像下面這樣給出 ssh:// 的 URL:
$ git clone ssh://user@server:project.git
或者不指明某個協議 — 這時 Git 會默認使用 SSH :
$ git clone user@server:project.git
如果不指明用戶,Git 會默認使用當前登錄的用戶名連接服務器。
優點
使用 SSH 的好處有很多。首先,如果你想擁有對網絡倉庫的寫權限,基本上不可能不使用 SSH。其次,SSH 架設相對比較簡單 — SSH 守護進程很常見,很多網絡管理員都有一些使用經驗,而且很多操作系統都自帶了它或者相關的管理工具。再次,通過 SSH 進行訪問是安全的 — 所有數據傳輸都是加密和授權的。最后,和 Git 及本地協議一樣,SSH 也很高效,會在傳輸之前盡可能壓縮數據。
缺點
SSH 的限制在於你不能通過它實現倉庫的匿名訪問。即使僅為讀取數據,人們也必須在能通過 SSH 訪問主機的前提下才能訪問倉庫,這使得 SSH 不利於開源的項目。如果你僅僅在公司網絡里使用,SSH 可能是你唯一需要使用的協議。如果想允許對項目的匿名只讀訪問,那么除了為自己推送而架設 SSH 協議之外,還需要支持其他協議以便他人訪問讀取。
Git 協議
接下來是 Git 協議。這是一個包含在 Git 軟件包中的特殊守護進程; 它會監聽一個提供類似於 SSH 服務的特定端口(9418),而無需任何授權。打算支持 Git 協議的倉庫,需要先創建git-export-daemon-ok 文件 — 它是協議進程提供倉庫服務的必要條件 — 但除此之外該服務沒有什么安全措施。要么所有人都能克隆 Git 倉庫,要么誰也不能。這也意味着該協議通常不能用來進行推送。你可以允許推送操作;然而由於沒有授權機制,一旦允許該操作,網絡上任何一個知道項目 URL 的人將都有推送權限。不用說,這是十分罕見的情況。
優點
Git 協議是現存最快的傳輸協議。如果你在提供一個有很大訪問量的公共項目,或者一個不需要對讀操作進行授權的龐大項目,架設一個 Git 守護進程來供應倉庫是個不錯的選擇。它使用與 SSH 協議相同的數據傳輸機制,但省去了加密和授權的開銷。
缺點
Git 協議消極的一面是缺少授權機制。用 Git 協議作為訪問項目的唯一方法通常是不可取的。一般的做法是,同時提供 SSH 接口,讓幾個開發者擁有推送(寫)權限,其他人通過git:// 擁有只讀權限。Git 協議可能也是最難架設的協議。它要求有單獨的守護進程,需要定制 — 我們將在本章的 “Gitosis” 一節詳細介紹它的架設 — 需要設定xinetd 或類似的程序,而這些工作就沒那么輕松了。該協議還要求防火牆開放 9418 端口,而企業級防火牆一般不允許對這個非標准端口的訪問。大型企業級防火牆通常會封鎖這個少見的端口。
HTTP/S 協議
最后還有 HTTP 協議。HTTP 或 HTTPS 協議的優美之處在於架設的簡便性。基本上,只需要把 Git 的裸倉庫文件放在 HTTP 的根目錄下,配置一個特定的post-update 掛鈎(hook)就可以搞定(Git 掛鈎的細節見第 7 章)。此后,每個能訪問 Git 倉庫所在服務器上 web 服務的人都可以進行克隆操作。下面的操作可以允許通過 HTTP 對倉庫進行讀取:
$ cd /var/www/htdocs/ $ git clone --bare /path/to/git_project gitproject.git $ cd gitproject.git $ mv hooks/post-update.sample hooks/post-update $ chmod a+x hooks/post-update
這樣就可以了。Git 附帶的 post-update 掛鈎會默認運行合適的命令(git update-server-info)來確保通過 HTTP 的獲取和克隆正常工作。這條命令在你用 SSH 向倉庫推送內容時運行;之后,其他人就可以用下面的命令來克隆倉庫:
$ git clone http://example.com/gitproject.git
在本例中,我們使用了 Apache 設定中常用的 /var/www/htdocs 路徑,不過你可以使用任何靜態 web 服務 — 把裸倉庫放在它的目錄里就行。 Git 的數據是以最基本的靜態文件的形式提供的(關於如何提供文件的詳情見第 9 章)。
通過 HTTP 進行推送操作也是可能的,不過這種做法不太常見,並且牽扯到復雜的 WebDAV 設定。由於很少用到,本書將略過對該內容的討論。如果對 HTTP 推送協議感興趣,不妨打開這個地址看一下操作方法:http://www.kernel.org/pub/software/scm/git/docs/howto/setup-git-server-over-http.txt 。通過 HTTP 推送的好處之一是你可以使用任何 WebDAV 服務器,不需要為 Git 設定特殊環境;所以如果主機提供商支持通過 WebDAV 更新網站內容,你也可以使用這項功能。
優點
使用 HTTP 協議的好處是易於架設。幾條必要的命令就可以讓全世界讀取到倉庫的內容。花費不過幾分鍾。HTTP 協議不會占用過多服務器資源。因為它一般只用到靜態的 HTTP 服務提供所有數據,普通的 Apache 服務器平均每秒能支撐數千個文件的並發訪問 — 哪怕讓一個小型服務器超載都很難。
你也可以通過 HTTPS 提供只讀的倉庫,這意味着你可以加密傳輸內容;你甚至可以要求客戶端使用特定簽名的 SSL 證書。一般情況下,如果到了這一步,使用 SSH 公共密鑰可能是更簡單的方案;不過也存在一些特殊情況,這時通過 HTTPS 使用帶簽名的 SSL 證書或者其他基於 HTTP 的只讀連接授權方式是更好的解決方案。
HTTP 還有個額外的好處:HTTP 是一個如此常見的協議,以至於企業級防火牆通常都允許其端口的通信。
缺點
HTTP 協議的消極面在於,相對來說客戶端效率更低。克隆或者下載倉庫內容可能會花費更多時間,而且 HTTP 傳輸的體積和網絡開銷比其他任何一個協議都大。因為它沒有按需供應的能力 — 傳輸過程中沒有服務端的動態計算 — 因而 HTTP 協議經常會被稱為_傻瓜(dumb)_協議。更多 HTTP 協議和其他協議效率上的差異見第 9 。
在服務器上部署 Git
開始架設 Git 服務器前,需要先把現有倉庫導出為裸倉庫 — 即一個不包含當前工作目錄的倉庫。做法直截了當,克隆時用 --bare 選項即可。裸倉庫的目錄名一般以.git 結尾,像這樣:
$ git clone --bare my_project my_project.git Initialized empty Git repository in /opt/projects/my_project.git/
該命令的輸出或許會讓人有些不解。其實 clone 操作基本上相當於 git init 加 git fetch,所以這里出現的其實是git init 的輸出,先由它建立一個空目錄,而之后傳輸數據對象的操作並無任何輸出,只是悄悄在幕后執行。現在my_project.git 目錄中已經有了一份 Git 目錄數據的副本。
整體上的效果大致相當於:
$ cp -Rf my_project/.git my_project.git
但在配置文件中有若干小改動,不過對用戶來講,使用方式都一樣,不會有什么影響。它僅取出 Git 倉庫的必要原始數據,存放在該目錄中,而不會另外創建工作目錄。
把裸倉庫移到服務器上
有了裸倉庫的副本后,剩下的就是把它放到服務器上並設定相關協議。假設一個域名為 git.example.com 的服務器已經架設好,並可以通過 SSH 訪問,我們打算把所有 Git 倉庫儲存在/opt/git 目錄下。只要把裸倉庫復制過去:
$ scp -r my_project.git user@git.example.com:/opt/git
現在,所有對該服務器有 SSH 訪問權限,並可讀取 /opt/git 目錄的用戶都可以用下面的命令克隆該項目:
$ git clone user@git.example.com:/opt/git/my_project.git
如果某個 SSH 用戶對 /opt/git/my_project.git 目錄有寫權限,那他就有推送權限。如果到該項目目錄中運行 git init 命令,並加上 --shared 選項,那么 Git 會自動修改該倉庫目錄的組權限為可寫(譯注:實際上 --shared 可以指定其他行為,只是默認為將組權限改為可寫並執行 g+sx,所以最后會得到 rws。)。
$ ssh user@git.example.com $ cd /opt/git/my_project.git $ git init --bare --shared
由此可見,根據現有的 Git 倉庫創建一個裸倉庫,然后把它放上你和同事都有 SSH 訪問權的服務器是多么容易。現在已經可以開始在同一項目上密切合作了。
值得注意的是,這的的確確是架設一個少數人具有連接權的 Git 服務的全部 — 只要在服務器上加入可以用 SSH 登錄的帳號,然后把裸倉庫放在大家都有讀寫權限的地方。一切都准備停當,無需更多。
下面的幾節中,你會了解如何擴展到更復雜的設定。這些內容包含如何避免為每一個用戶建立一個賬戶,給倉庫添加公共讀取權限,架設網頁界面,使用 Gitosis 工具等等。然而,只是和幾個人在一個不公開的項目上合作的話,僅僅是一個 SSH 服務器和裸倉庫就足夠了,記住這點就可以了。
小型安裝
如果設備較少或者你只想在小型開發團隊里嘗試 Git ,那么一切都很簡單。架設 Git 服務最復雜的地方在於賬戶管理。如果需要倉庫對特定的用戶可讀,而給另一部分用戶讀寫權限,那么訪問和許可的安排就比較困難。
SSH 連接
如果已經有了一個所有開發成員都可以用 SSH 訪問的服務器,架設第一個服務器將變得異常簡單,幾乎什么都不用做(正如上節中介紹的那樣)。如果需要對倉庫進行更復雜的訪問控制,只要使用服務器操作系統的本地文件訪問許可機制就行了。
如果需要團隊里的每個人都對倉庫有寫權限,又不能給每個人在服務器上建立賬戶,那么提供 SSH 連接就是唯一的選擇了。我們假設用來共享倉庫的服務器已經安裝了 SSH 服務,而且你通過它訪問服務器。
有好幾個辦法可以讓團隊的每個人都有訪問權。第一個辦法是給每個人建立一個賬戶,直截了當但略過繁瑣。反復運行adduser 並給所有人設定臨時密碼可不是好玩的。
第二個辦法是在主機上建立一個 git 賬戶,讓每個需要寫權限的人發送一個 SSH 公鑰,然后將其加入 git 賬戶的~/.ssh/authorized_keys 文件。這樣一來,所有人都將通過 git 賬戶訪問主機。這絲毫不會影響提交的數據 — 訪問主機用的身份不會影響提交對象的提交者信息。
另一個辦法是讓 SSH 服務器通過某個 LDAP 服務,或者其他已經設定好的集中授權機制,來進行授權。只要每個人都能獲得主機的 shell 訪問權,任何可用的 SSH 授權機制都能達到相同效果。
生成 SSH 公鑰
大多數 Git 服務器都會選擇使用 SSH 公鑰來進行授權。系統中的每個用戶都必須提供一個公鑰用於授權,沒有的話就要生成一個。生成公鑰的過程在所有操作系統上都差不多。首先先確認一下是否已經有一個公鑰了。SSH 公鑰默認儲存在賬戶的主目錄下的~/.ssh 目錄。進去看看:
$ cd ~/.ssh $ ls authorized_keys2 id_dsa known_hosts config id_dsa.pub
關鍵是看有沒有用 something 和 something.pub 來命名的一對文件,這個 something 通常就是 id_dsa 或 id_rsa。有 .pub后綴的文件就是公鑰,另一個文件則是密鑰。假如沒有這些文件,或者干脆連.ssh 目錄都沒有,可以用 ssh-keygen 來創建。該程序在 Linux/Mac 系統上由 SSH 包提供,而在 Windows 上則包含在 MSysGit 包里:
$ ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/Users/schacon/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /Users/schacon/.ssh/id_rsa. Your public key has been saved in /Users/schacon/.ssh/id_rsa.pub. The key fingerprint is: 43:c5:5b:5f:b1:f1:50:43:ad:20:a6:92:6a:1f:9a:3a schacon@agadorlaptop.local
它先要求你確認保存公鑰的位置(.ssh/id_rsa),然后它會讓你重復一個密碼兩次,如果不想在使用公鑰的時候輸入密碼,可以留空。
現在,所有做過這一步的用戶都得把它們的公鑰給你或者 Git 服務器的管理員(假設 SSH 服務被設定為使用公鑰機制)。他們只需要復制 .pub 文件的內容然后發郵件給管理員。公鑰的樣子大致如下:
$ cat ~/.ssh/id_rsa.pub ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSU GPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3 Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK/7XA t3FaoJoAsncM1Q9x5+3V0Ww68/eIFmb1zuUFljQJKprrX88XypNDvjYNby6vw/Pb0rwert/En mZ+AW4OZPnTPI89ZPmVMLuayrD2cE86Z/il8b+gw3r3+1nKatmIkjn2so1d01QraTlMqVSsbx NrRFi9wrf+M7Q== schacon@agadorlaptop.local
關於在多個操作系統上設立相同 SSH 公鑰的教程,可以查閱 GitHub 上有關 SSH 公鑰的向導:http://github.com/guides/providing-your-ssh-key。
架設服務器
現在我們過一邊服務器端架設 SSH 訪問的流程。本例將使用 authorized_keys 方法來給用戶授權。我們還將假定使用類似 Ubuntu 這樣的標准 Linux 發行版。首先,創建一個名為 ‘git’ 的用戶,並為其創建一個.ssh 目錄。
$ sudo adduser git $ su git $ cd $ mkdir .ssh
接下來,把開發者的 SSH 公鑰添加到這個用戶的 authorized_keys 文件中。假設你通過電郵收到了幾個公鑰並存到了臨時文件里。重復一下,公鑰大致看起來是這個樣子:
$ cat /tmp/id_rsa.john.pub ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCB007n/ww+ouN4gSLKssMxXnBOvf9LGt4L ojG6rs6hPB09j9R/T17/x4lhJA0F3FR1rP6kYBRsWj2aThGw6HXLm9/5zytK6Ztg3RPKK+4k Yjh6541NYsnEAZuXz0jTTyAUfrtU3Z5E003C4oxOj6H0rfIF1kKI9MAQLMdpGW1GYEIgS9Ez Sdfd8AcCIicTDWbqLAcU4UpkaX8KyGlLwsNuuGztobF8m72ALC/nLF6JLtPofwFBlgc+myiv O7TCUSBdLQlgMVOFq1I2uPWQOkOWQAHukEOmfjy2jctxSDBQ220ymjaNsHT4kgtZg2AYYgPq dAv8JggJICUvax2T9va5 gsg-keypair
只要把它們逐個追加到 authorized_keys 文件尾部即可:
$ cat /tmp/id_rsa.john.pub >> ~/.ssh/authorized_keys $ cat /tmp/id_rsa.josie.pub >> ~/.ssh/authorized_keys $ cat /tmp/id_rsa.jessica.pub >> ~/.ssh/authorized_keys
現在可以用 --bare 選項運行 git init 來建立一個裸倉庫,這會初始化一個不包含工作目錄的倉庫。
$ cd /opt/git $ mkdir project.git $ cd project.git $ git --bare init
這時,Join,Josie 或者 Jessica 就可以把它加為遠程倉庫,推送一個分支,從而把第一個版本的項目文件上傳到倉庫里了。值得注意的是,每次添加一個新項目都需要通過 shell 登入主機並創建一個裸倉庫目錄。我們不妨以gitserver 作為 git 用戶及項目倉庫所在的主機名。如果在網絡內部運行該主機,並在 DNS 中設定 gitserver 指向該主機,那么以下這些命令都是可用的:
# 在 John 的電腦上 $ cd myproject $ git init $ git add . $ git commit -m 'initial commit' $ git remote add origin git@gitserver:/opt/git/project.git $ git push origin master
這樣,其他人的克隆和推送也一樣變得很簡單:
$ git clone git@gitserver:/opt/git/project.git $ vim README $ git commit -am 'fix for the README file' $ git push origin master
用這個方法可以很快捷地為少數幾個開發者架設一個可讀寫的 Git 服務。
作為一個額外的防范措施,你可以用 Git 自帶的 git-shell 工具限制 git 用戶的活動范圍。只要把它設為git 用戶登入的 shell,那么該用戶就無法使用普通的 bash 或者 csh 什么的 shell 程序。編輯 /etc/passwd 文件:
$ sudo vim /etc/passwd
在文件末尾,你應該能找到類似這樣的行:
git:x:1000:1000::/home/git:/bin/sh
把 bin/sh 改為 /usr/bin/git-shell (或者用 which git-shell 查看它的實際安裝路徑)。該行修改后的樣子如下:
git:x:1000:1000::/home/git:/usr/bin/git-shell
現在 git 用戶只能用 SSH 連接來推送和獲取 Git 倉庫,而不能直接使用主機 shell。嘗試普通 SSH 登錄的話,會看到下面這樣的拒絕信息:
$ ssh git@gitserver fatal: What do you think I am? A shell? Connection to gitserver closed.
公共訪問
匿名的讀取權限該怎么實現呢?也許除了內部私有的項目之外,你還需要托管一些開源項目。或者因為要用一些自動化的服務器來進行編譯,或者有一些經常變化的服務器群組,而又不想整天生成新的 SSH 密鑰 — 總之,你需要簡單的匿名讀取權限。
或許對小型的配置來說最簡單的辦法就是運行一個靜態 web 服務,把它的根目錄設定為 Git 倉庫所在的位置,然后開啟本章第一節提到的 post-update 掛鈎。這里繼續使用之前的例子。假設倉庫處於/opt/git 目錄,主機上運行着 Apache 服務。重申一下,任何 web 服務程序都可以達到相同效果;作為范例,我們將用一些基本的 Apache 設定來展示大體需要的步驟。
首先,開啟掛鈎:
$ cd project.git $ mv hooks/post-update.sample hooks/post-update $ chmod a+x hooks/post-update
如果用的是 Git 1.6 之前的版本,則可以省略 mv 命令 — Git 是從較晚的版本才開始在掛鈎實例的結尾添加 .sample 后綴名的。
post-update 掛鈎是做什么的呢?其內容大致如下:
$ cat .git/hooks/post-update #!/bin/sh exec git-update-server-info
意思是當通過 SSH 向服務器推送時,Git 將運行這個 git-update-server-info 命令來更新匿名 HTTP 訪問獲取數據時所需要的文件。
接下來,在 Apache 配置文件中添加一個 VirtualHost 條目,把文檔根目錄設為 Git 項目所在的根目錄。這里我們假定 DNS 服務已經配置好,會把對.gitserver 的請求發送到這台主機:
ServerName git.gitserver DocumentRoot /opt/git Order allow, deny allow from all
另外,需要把 /opt/git 目錄的 Unix 用戶組設定為 www-data ,這樣 web 服務才可以讀取倉庫內容,因為運行 CGI 腳本的 Apache 實例進程默認就是以該用戶的身份起來的:
$ chgrp -R www-data /opt/git
重啟 Apache 之后,就可以通過項目的 URL 來克隆該目錄下的倉庫了。
$ git clone http://git.gitserver/project.git
這一招可以讓你在幾分鍾內為相當數量的用戶架設好基於 HTTP 的讀取權限。另一個提供非授權訪問的簡單方法是開啟一個 Git 守護進程,不過這將要求該進程作為后台進程常駐 — 接下來的這一節就要討論這方面的細節。
GitWeb
現在我們的項目已經有了可讀可寫和只讀的連接方式,不過如果能有一個簡單的 web 界面訪問就更好了。Git 自帶一個叫做 GitWeb 的 CGI 腳本,運行效果可以到http://git.kernel.org 這樣的站點體驗下(見圖 4-1)。
如果想看看自己項目的效果,不妨用 Git 自帶的一個命令,可以使用類似 lighttpd 或 webrick 這樣輕量級的服務器啟動一個臨時進程。如果是在 Linux 主機上,通常都預裝了lighttpd ,可以到項目目錄中鍵入 git instaweb 來啟動。如果用的是 Mac ,Leopard 預裝了 Ruby,所以webrick 應該是最好的選擇。如果要用 lighttpd 以外的程序來啟動 git instaweb,可以通過--httpd 選項指定:
$ git instaweb --httpd=webrick [2009-02-21 10:02:21] INFO WEBrick 1.3.1 [2009-02-21 10:02:21] INFO ruby 1.8.6 (2008-03-03) [universal-darwin9.0]
這會在 1234 端口開啟一個 HTTPD 服務,隨之在瀏覽器中顯示該頁,十分簡單。關閉服務時,只需在原來的命令后面加上--stop 選項就可以了:
$ git instaweb --httpd=webrick --stop
如果需要為團隊或者某個開源項目長期運行 GitWeb,那么 CGI 腳本就要由正常的網頁服務來運行。一些 Linux 發行版可以通過 apt 或yum 安裝一個叫做 gitweb 的軟件包,不妨首先嘗試一下。我們將快速介紹一下手動安裝 GitWeb 的流程。首先,你需要 Git 的源碼,其中帶有 GitWeb,並能生成定制的 CGI 腳本:
$ git clone git://git.kernel.org/pub/scm/git/git.git $ cd git/ $ make GITWEB_PROJECTROOT="/opt/git" \ prefix=/usr gitweb/gitweb.cgi $ sudo cp -Rf gitweb /var/www/
注意,通過指定 GITWEB_PROJECTROOT 變量告訴編譯命令 Git 倉庫的位置。然后,設置 Apache 以 CGI 方式運行該腳本,添加一個 VirtualHost 配置:
ServerName gitserver DocumentRoot /var/www/gitweb Options ExecCGI +FollowSymLinks +SymLinksIfOwnerMatch AllowOverride All order allow,deny Allow from all AddHandler cgi-script cgi DirectoryIndex gitweb.cgi
不難想象,GitWeb 可以使用任何兼容 CGI 的網頁服務來運行;如果偏向使用其他 web 服務器,配置也不會很麻煩。現在,通過 http://gitserver 就可以在線訪問倉庫了,在http://git.server 上還可以通過 HTTP 克隆和獲取倉庫的內容。
Gitosis
把所有用戶的公鑰保存在 authorized_keys 文件的做法,只能湊和一陣子,當用戶數量達到幾百人的規模時,管理起來就會十分痛苦。每次改刪用戶都必須登錄服務器不去說,這種做法還缺少必要的權限管理 — 每個人都對所有項目擁有完整的讀寫權限。
幸好我們還可以選擇應用廣泛的 Gitosis 項目。簡單地說,Gitosis 就是一套用來管理 authorized_keys 文件和實現簡單連接限制的腳本。有趣的是,用來添加用戶和設定權限的並非通過網頁程序,而只是管理一個特殊的 Git 倉庫。你只需要在這個特殊倉庫內做好相應的設定,然后推送到服務器上,Gitosis 就會隨之改變運行策略,聽起來就很酷,對吧?
Gitosis 的安裝算不上傻瓜化,但也不算太難。用 Linux 服務器架設起來最簡單 — 以下例子中,我們使用裝有 Ubuntu 8.10 系統的服務器。
Gitosis 的工作依賴於某些 Python 工具,所以首先要安裝 Python 的 setuptools 包,在 Ubuntu 上稱為 python-setuptools:
$ apt-get install python-setuptools
接下來,從 Gitosis 項目主頁克隆並安裝:
$ git clone git://eagain.net/gitosis.git $ cd gitosis $ sudo python setup.py install
這會安裝幾個供 Gitosis 使用的工具。默認 Gitosis 會把 /home/git 作為存儲所有 Git 倉庫的根目錄,這沒什么不好,不過我們之前已經把項目倉庫都放在/opt/git 里面了,所以為方便起見,我們可以做一個符號連接,直接划轉過去,而不必重新配置:
$ ln -s /opt/git /home/git/repositories
Gitosis 將會幫我們管理用戶公鑰,所以先把當前控制文件改名備份,以便稍后重新添加,准備好讓 Gitosis 自動管理authorized_keys 文件:
$ mv /home/git/.ssh/authorized_keys /home/git/.ssh/ak.bak
接下來,如果之前把 git 用戶的登錄 shell 改為 git-shell 命令的話,先恢復 ‘git’ 用戶的登錄 shell。改過之后,大家仍然無法通過該帳號登錄(譯注:因為authorized_keys 文件已經沒有了。),不過不用擔心,這會交給 Gitosis 來實現。所以現在先打開 /etc/passwd 文件,把這行:
git:x:1000:1000::/home/git:/usr/bin/git-shell
改回:
git:x:1000:1000::/home/git:/bin/sh
好了,現在可以初始化 Gitosis 了。你可以用自己的公鑰執行 gitosis-init 命令,要是公鑰不在服務器上,先臨時復制一份:
$ sudo -H -u git gitosis-init < /tmp/id_dsa.pub Initialized empty Git repository in /opt/git/gitosis-admin.git/ Reinitialized existing Git repository in /opt/git/gitosis-admin.git/
這樣該公鑰的擁有者就能修改用於配置 Gitosis 的那個特殊 Git 倉庫了。接下來,需要手工對該倉庫中的 post-update 腳本加上可執行權限:
$ sudo chmod 755 /opt/git/gitosis-admin.git/hooks/post-update
基本上就算是好了。如果設定過程沒出什么差錯,現在可以試一下用初始化 Gitosis 的公鑰的擁有者身份 SSH 登錄服務器,應該會看到類似下面這樣:
$ ssh git@gitserver PTY allocation request failed on channel 0 fatal: unrecognized command 'gitosis-serve schacon@quaternion' Connection to gitserver closed.
說明 Gitosis 認出了該用戶的身份,但由於沒有運行任何 Git 命令,所以它切斷了連接。那么,現在運行一個實際的 Git 命令 — 克隆 Gitosis 的控制倉庫:
# 在你本地計算機上
$ git clone git@gitserver:gitosis-admin.git
這會得到一個名為 gitosis-admin 的工作目錄,主要由兩部分組成:
$ cd gitosis-admin $ find . ./gitosis.conf ./keydir ./keydir/scott.pub
gitosis.conf 文件是用來設置用戶、倉庫和權限的控制文件。keydir 目錄則是保存所有具有訪問權限用戶公鑰的地方— 每人一個。在keydir 里的文件名(比如上面的 scott.pub)應該跟你的不一樣 — Gitosis 會自動從使用 gitosis-init 腳本導入的公鑰尾部的描述中獲取該名字。
看一下 gitosis.conf 文件的內容,它應該只包含與剛剛克隆的 gitosis-admin 相關的信息:
$ cat gitosis.conf [gitosis] [group gitosis-admin] writable = gitosis-admin members = scott
它顯示用戶 scott — 初始化 Gitosis 公鑰的擁有者 — 是唯一能管理 gitosis-admin 項目的人。
現在我們來添加一個新項目。為此我們要建立一個名為 mobile 的新段落,在其中羅列手機開發團隊的開發者,以及他們擁有寫權限的項目。由於 ‘scott’ 是系統中的唯一用戶,我們把他設為唯一用戶,並允許他讀寫名為iphone_project 的新項目:
[group mobile] writable = iphone_project members = scott
修改完之后,提交 gitosis-admin 里的改動,並推送到服務器使其生效:
$ git commit -am 'add iphone_project and mobile group' [master]: created 8962da8: "changed name" 1 files changed, 4 insertions(+), 0 deletions(-) $ git push Counting objects: 5, done. Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 272 bytes, done. Total 3 (delta 1), reused 0 (delta 0) To git@gitserver:/opt/git/gitosis-admin.git fb27aec..8962da8 master -> master
在新工程 iphone_project 里首次推送數據到服務器前,得先設定該服務器地址為遠程倉庫。但你不用事先到服務器上手工創建該項目的裸倉庫— Gitosis 會在第一次遇到推送時自動創建:
$ git remote add origin git@gitserver:iphone_project.git $ git push origin master Initialized empty Git repository in /opt/git/iphone_project.git/ Counting objects: 3, done. Writing objects: 100% (3/3), 230 bytes, done. Total 3 (delta 0), reused 0 (delta 0) To git@gitserver:iphone_project.git * [new branch] master -> master
請注意,這里不用指明完整路徑(實際上,如果加上反而沒用),只需要一個冒號加項目名字即可 — Gitosis 會自動幫你映射到實際位置。
要和朋友們在一個項目上協同工作,就得重新添加他們的公鑰。不過這次不用在服務器上一個一個手工添加到~/.ssh/authorized_keys 文件末端,而只需管理keydir 目錄中的公鑰文件。文件的命名將決定在 gitosis.conf 中對用戶的標識。現在我們為 John,Josie 和 Jessica 添加公鑰:
$ cp /tmp/id_rsa.john.pub keydir/john.pub $ cp /tmp/id_rsa.josie.pub keydir/josie.pub $ cp /tmp/id_rsa.jessica.pub keydir/jessica.pub
然后把他們都加進 ‘mobile’ 團隊,讓他們對 iphone_project 具有讀寫權限:
[group mobile] writable = iphone_project members = scott john josie jessica
如果你提交並推送這個修改,四個用戶將同時具有該項目的讀寫權限。Gitosis 也具有簡單的訪問控制功能。如果想讓 John 只有讀權限,可以這樣做:
[group mobile] writable = iphone_project members = scott josie jessica [group mobile_ro] readonly = iphone_project members = john
現在 John 可以克隆和獲取更新,但 Gitosis 不會允許他向項目推送任何內容。像這樣的組可以隨意創建,多少不限,每個都可以包含若干不同的用戶和項目。甚至還可以指定某個組為成員之一(在組名前加上@ 前綴),自動繼承該組的成員:
[group mobile_committers] members = scott josie jessica [group mobile] writable = iphone_project members = @mobile_committers [group mobile_2] writable = another_iphone_project members = @mobile_committers john
如果遇到意外問題,試試看把 loglevel=DEBUG 加到 [gitosis] 的段落(譯注:把日志設置為調試級別,記錄更詳細的運行信息。)。如果一不小心搞錯了配置,失去了推送權限,也可以手工修改服務器上的/home/git/.gitosis.conf 文件 — Gitosis 實際是從該文件讀取信息的。它在得到推送數據時,會把新的 gitosis.conf 存到該路徑上。所以如果你手工編輯該文件的話,它會一直保持到下次向 gitosis-admin 推送新版本的配置內容為止。
Git 守護進程
對於提供公共的,非授權的只讀訪問,我們可以拋棄 HTTP 協議,改用 Git 自己的協議,這主要是出於性能和速度的考慮。Git 協議遠比 HTTP 協議高效,因而訪問速度也快,所以它能節省很多用戶的時間。
重申一下,這一點只適用於非授權的只讀訪問。如果建在防火牆之外的服務器上,那么它所提供的服務應該只是那些公開的只讀項目。如果是在防火牆之內的 服務器上,可用於支撐大量參與人員或自動系統(用於持續集成或編譯的主機)只讀訪問的項目,這樣可以省去逐一配置 SSH 公鑰的麻煩。
但不管哪種情形,Git 協議的配置設定都很簡單。基本上,只要以守護進程的形式運行該命令即可:
git daemon --reuseaddr --base-path=/opt/git/ /opt/git/
這里的 --reuseaddr 選項表示在重啟服務前,不等之前的連接超時就立即重啟。而 --base-path 選項則允許克隆項目時不必給出完整路徑。最后面的路徑告訴 Git 守護進程允許開放給用戶訪問的倉庫目錄。假如有防火牆,則需要為該主機的 9418 端口設置為允許通信。
以守護進程的形式運行該進程的方法有很多,但主要還得看用的是什么操作系統。在 Ubuntu 主機上,可以用 Upstart 腳本達成。編輯該文件:
/etc/event.d/local-git-daemon
加入以下內容:
start on startup stop on shutdown exec /usr/bin/git daemon \ --user=git --group=git \ --reuseaddr \ --base-path=/opt/git/ \ /opt/git/ respawn
出於安全考慮,強烈建議用一個對倉庫只有讀取權限的用戶身份來運行該進程 — 只需要簡單地新建一個名為 git-ro 的用戶(譯注:新建用戶默認對倉庫文件不具備寫權限,但這取決於倉庫目錄的權限設定。務必確認git-ro 對倉庫只能讀不能寫。),並用它的身份來啟動進程。這里為了簡化,后面我們還是用之前運行 Gitosis 的用戶 ‘git’。
這樣一來,當你重啟計算機時,Git 進程也會自動啟動。要是進程意外退出或者被殺掉,也會自行重啟。在設置完成后,不重啟計算機就啟動該守護進程,可以運行:
initctl start local-git-daemon
而在其他操作系統上,可以用 xinetd,或者 sysvinit 系統的腳本,或者其他類似的腳本 — 只要能讓那個命令變為守護進程並可監控。
接下來,我們必須告訴 Gitosis 哪些倉庫允許通過 Git 協議進行匿名只讀訪問。如果每個倉庫都設有各自的段落,可以分別指定是否允許 Git 進程開放給用戶匿名讀取。比如允許通過 Git 協議訪問 iphone_project,可以把下面兩行加到gitosis.conf 文件的末尾:
[repo iphone_project]
daemon = yes
在提交和推送完成后,運行中的 Git 守護進程就會響應來自 9418 端口對該項目的訪問請求。
如果不考慮 Gitosis,單單起了 Git 守護進程的話,就必須到每一個允許匿名只讀訪問的倉庫目錄內,創建一個特殊名稱的空文件作為標志:
$ cd /path/to/project.git $ touch git-daemon-export-ok
該文件的存在,表明允許 Git 守護進程開放對該項目的匿名只讀訪問。
Gitosis 還能設定哪些項目允許放在 GitWeb 上顯示。先打開 GitWeb 的配置文件 /etc/gitweb.conf,添加以下四行:
$projects_list = "/home/git/gitosis/projects.list"; $projectroot = "/home/git/repositories"; $export_ok = "git-daemon-export-ok"; @git_base_url_list = ('git://gitserver');
接下來,只要配置各個項目在 Gitosis 中的 gitweb 參數,便能達成是否允許 GitWeb 用戶瀏覽該項目。比如,要讓 iphone_project 項目在 GitWeb 里出現,把repo 的設定改成下面的樣子:
[repo iphone_project] daemon = yes gitweb = yes
在提交並推送過之后,GitWeb 就會自動開始顯示 iphone_project 項目的細節和歷史。
Git 托管服務
如果不想經歷自己架設 Git 服務器的麻煩,網絡上有幾個專業的倉庫托管服務可供選擇。這樣做有幾大優點:托管賬戶的建立通常比較省時,方便項目的啟動,而且不涉及服務器的維護和監 控。即使內部創建並運行着自己的服務器,同時為開源項目提供一個公共托管站點還是有好處的 — 讓開源社區更方便地找到該項目,並給予幫助。
目前,可供選擇的托管服務數量繁多,各有利弊。在 Git 官方 wiki 上的 Githosting 頁面有一個最新的托管服務列表:http://git.or.cz/gitwiki/GitHosting
由於本書無法全部一一介紹,而本人(譯注:指本書作者 Scott Chacon。)剛好在其中一家公司工作,所以接下來我們將會介紹如何在 GitHub 上建立新賬戶並啟動項目。至於其他托管服務大體也是這么一個過程,基本的想法都是差不多的。
GitHub 是目前為止最大的開源 Git 托管服務,並且還是少數同時提供公共代碼和私有代碼托管服務的站點之一,所以你可以在上面同時保存開源和商業代碼。事實上,本書就是放在 GitHub 上合作編著的。(譯注:本書的翻譯也是放在 GitHub 上廣泛協作的。)
GitHub
GitHub 和大多數的代碼托管站點在處理項目命名空間的方式上略有不同。GitHub 的設計更側重於用戶,而不是完全基於項目。也就是說,如果我在 GitHub 上托管一個名為grit 的項目的話,它的地址不會是 github.com/grit,而是按在用戶底下github.com/shacon/grit (譯注:本書作者 Scott Chacon 在 GitHub 上的用戶名是shacon。)。不存在所謂某個項目的官方版本,所以假如第一作者放棄了某個項目,它可以無縫轉移到其它用戶的名下。
GitHub 同時也是一個向使用私有倉庫的用戶收取費用的商業公司,但任何人都可以方便快捷地申請到一個免費賬戶,並在上面托管數量不限的開源項目。接下來我們快速介紹一下 GitHub 的基本使用。
建立新賬戶
首先注冊一個免費賬戶。訪問 Pricing and Signup 頁面 http://github.com/plans 並點擊 Free acount 里的 Sign Up 按鈕,進入注冊頁面。
選擇一個系統中尚未使用的用戶名,提供一個與之相關聯的電郵地址,並輸入密碼(見圖 4-3):
如果方便,現在就可以提供你的 SSH 公鑰。我們在前文的”小型安裝” 一節介紹過生成新公鑰的方法。把新生成的公鑰復制粘貼到 SSH Public Key 文本框中即可。要是對生成公鑰的步驟不太清楚,也可以點擊 “explain ssh keys” 鏈接,會顯示各個主流操作系統上完成該步驟的介紹。點擊 “I agree,sign me up” 按鈕完成用戶注冊,並轉到該用戶的 dashboard 頁面(見圖 4-4):
接下來就可以建立新倉庫了。
建立新倉庫
點擊用戶面板上倉庫旁邊的 “create a new one” 鏈接,顯示 Create a New Repository 的表單(見圖 4-5):
當然,項目名稱是必不可少的,此外也可以適當描述一下項目的情況或者給出官方站點的地址。然后點擊 “Create Repository” 按鈕,新倉庫就建立起來了(見圖 4-6):
由於尚未提交代碼,點擊項目主頁后 GitHub 會顯示一個簡要的指南,告訴你如何新建一個項目並推送上來,如何從現有項目推送,以及如何從一個公共的 Subversion 倉庫導入項目:
該指南和本書前文介紹的類似,對於新的項目,需要先在本地初始化為 Git 項目,添加要管理的文件並作首次提交:
$ git init $ git add . $ git commit -m 'initial commit'
然后在這個本地倉庫內把 GitHub 添加為遠程倉庫,並推送 master 分支上來:
$ git remote add origin git@github.com:testinguser/iphone_project.git
$ git push origin master
現在該項目就托管在 GitHub 上了。你可以把它的 URL 分享給每位對此項目感興趣的人。本例的 URL 是http://github.com/testinguser/iphone_project。而在項目頁面的摘要部分,你會發現有兩個 Git URL 地址:
Public Clone URL 是一個公開的,只讀的 Git URL,任何人都可以通過它克隆該項目。可以隨意散播這個 URL,比如發布到個人網站之類的地方等等。
Your Clone URL 是一個基於 SSH 協議的可讀可寫 URL,只有使用與上傳的 SSH 公鑰對應的密鑰來連接時,才能通過它進行讀寫操作。其他用戶訪問該項目頁面時只能看到之前那個公共的 URL,看不到這個私有的 URL。
從 Subversion 導入項目
如果想把某個公共 Subversion 項目導入 Git,GitHub 可以幫忙。在指南的最后有一個指向導入 Subversion 頁面的鏈接。點擊它會看到一個表單,包含有關導入流程的信息以及一個用來粘貼公共 Subversion 項目連接的文本框:
如果項目很大,采用非標准結構,或者是私有的,那就無法借助該工具實現導入。到第 7 章,我們會介紹如何手工導入復雜工程的具體方法。
添加協作開發者
現在把團隊里的其他人也加進來。如果 John,Josie 和 Jessica 都在 GitHub 注冊了賬戶,要賦予他們對該倉庫的推送權限,可以把他們加為項目協作者。這樣他們就可以通過各自的公鑰訪問我的這個倉庫了。
點擊項目頁面上方的 “edit” 按鈕或者頂部的 Admin 標簽,進入該項目的管理頁面
為了給另一個用戶添加項目的寫權限,點擊 “Add another collaborator” 鏈接,出現一個用於輸入用戶名的表單。在輸入的同時,它會自動跳出一個符合條件的候選名單。找到正確用戶名之后,點 Add 按鈕,把該用戶設為項目協作者
添加完協作者之后,就可以在 Repository Collaborators 區域看到他們的名單:
如果要取消某人的訪問權,點擊 “revoke” 即可取消他的推送權限。對於將來的項目,你可以從現有項目復制協作者名單,或者直接借用協作者群組。
項目頁面
在推送或從 Subversion 導入項目之后,你會看到一個類似下圖 的項目主頁:
別人訪問你的項目時看到的就是這個頁面。它有若干導航標簽,Commits 標簽用於顯示提交歷史,最新的提交位於最上方,這和 git log 命令的輸出類似。Network 標簽展示所有派生了該項目並做出貢獻的用戶的關系圖譜。Downloads 標簽允許你上傳項目的二進制文件,提供下載該項目各個版本的 tar/zip 包。Wiki 標簽提供了一個用於撰寫文檔或其他項目相關信息的 wiki 站點。Graphs 標簽包含了一些可視化的項目信息與數據。默認打開的 Source 標簽頁面,則列出了該項目的目錄結構和概要信息,並在下方自動展示 README 文件的內容(如果該文件存在的話),此外還會顯示最近一次提交的相關信息。
派生項目
如果要為一個自己沒有推送權限的項目貢獻代碼,GitHub 鼓勵使用派生(fork)。到那個感興趣的項目主頁上,點擊頁面上方的 “fork” 按鈕,GitHub 就會為你復制一份該項目的副本到你的倉庫中,這樣你就可以向自己的這個副本推送數據了。
采取這種辦法的好處是,項目擁有者不必忙於應付賦予他人推送權限的工作。隨便誰都可以通過派生得到一個項目副本並在其中展開工作,事后只需要項目維護者將這些副本倉庫加為遠程倉庫,然后提取更新合並即可。
要派生一個項目,到原始項目的頁面(本例中是 mojombo/chronic)點擊 “fork” 按鈕
幾秒鍾之后,你將進入新建的項目頁面,會顯示該項目派生自哪一個項目
GitHub 小結
關於 GitHub 就先介紹這么多,能夠快速達成這些事情非常重要(譯注:門檻的降低和完成基本任務的簡單高效,對於推動開源項目的協作發展有着舉足輕重的意義。)。短短幾 分鍾內,你就能創建一個新賬戶,添加一個項目並開始推送。如果項目是開源的,整個龐大的開發者社區都可以立即訪問它,提供各式各樣的幫助和貢獻。最起碼, 這也是一種 Git 新手立即體驗嘗試 Git 的捷徑。
小結
我們討論並介紹了一些建立遠程 Git 倉庫的方法,接下來你可以通過這些倉庫同他人分享或合作。
運行自己的服務器意味着更多的控制權以及在防火牆內部操作的可能性,當然這樣的服務器通常需要投入一定的時間精力來架設維護。如果直接托管,雖然能免去這部分工作,但有時出於安全或版權的考慮,有些公司禁止將商業代碼托管到第三方服務商。
所以究竟采取哪種方案,並不是個難以取舍的問題,或者其一,或者相互配合,哪種合適就用哪種。