根據 RhodeCode 在 2016 年作過的一項分析報告 Version Control Systems Popularity in 2016,在現在的 VCS(版本控制系統)領域,Git 幾乎已經一統江山,在選擇本身的 VCS 時,有 87% 的人會選擇使用 Git,排在第二名的 SVN 只佔 6%,不管是從 Google Trends,仍是在 Stack Overflow 上的提問,均可以看到 Git 的爆發式增加。另外,根據 Eclipse 的社區調查 (Eclipse Community Survey),在 2010 年先後,SVN 的使用率都遠超其餘幾款 VCS,從 2010 年開始,SVN 的使用率開始快速下滑,相應的,Git 的使用率快速上升,並在 2014 年超過了 SVN。php
如今,Git 已經成爲了程序員的必備技能,愈來愈多的企業開始採用 Git。在開源的世界裏,Github 是程序員彙集的狂歡之地,但這並不適合企業的私有項目,雖然 Github 也支持建立私有項目,可是搭建一個本身的 Git 服務器在不少時候多是更好的選擇,這篇博客將介紹並學習幾種搭建 Git 服務器的方法。html
Git 支持四種不一樣的傳輸協議:本地協議(Local)、HTTP(S) 協議、SSH(Secure Shell)協議以及 Git 協議,這四種協議在不一樣的場合有不一樣的用途,而且各有利弊,能夠根據實際狀況來選擇。java
本地協議是 Git 最基本的協議,當咱們想在本地作一些 Git 實驗時,這將很是有用。咱們首先創建兩個目錄:/git/repo
和 ~/working
,前者做爲遠程版本庫,後者做爲本地工做目錄。nginx
aneasystone@little-stone:~$ sudo mkdir -p /git/repo
aneasystone@little-stone:~$ sudo git init --bare /git/repo/test.git
已初始化空的 Git 倉庫於 /git/repo/test.git/
複製代碼
咱們在 /git/repo
目錄經過 git init --bare
命令建立一個裸倉庫(bare repository,即一個不包含當前工做目錄的倉庫),只要這一步,咱們就能夠開始使用了。接着咱們在工做目錄 clone
這個版本庫:git
aneasystone@little-stone:~$ cd ~/working/
aneasystone@little-stone:~/working$ git clone /git/repo/test.git
正克隆到 'test'...
warning: 您彷佛克隆了一個空倉庫。
完成。
複製代碼
而後咱們可使用 pull
、push
就像操做其餘的版本庫同樣。程序員
aneasystone@little-stone:~/working$ cd test/
aneasystone@little-stone:~/working/test$ touch 1
aneasystone@little-stone:~/working/test$ touch 2
aneasystone@little-stone:~/working/test$ git add .
aneasystone@little-stone:~/working/test$ git commit -m 'first commit'
[master (根提交) 4983f84] first commit
2 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 1
create mode 100644 2
aneasystone@little-stone:~/working/test$ sudo git push
[sudo] aneasystone 的密碼:
對象計數中: 3, 完成.
Delta compression using up to 8 threads.
壓縮對象中: 100% (2/2), 完成.
寫入對象中: 100% (3/3), 205 bytes | 205.00 KiB/s, 完成.
Total 3 (delta 0), reused 0 (delta 0)
To /git/repo/test.git
* [new branch] master -> master
複製代碼
本地協議不只在作 Git 實驗時頗有用,若是你的團隊有一個共享文件系統,能夠在這個共享文件系統上建立一個遠程版本庫,團隊成員把這個共享文件系統掛在本地,就能夠直接使用本地協議進行協做開發,徹底不須要搭建一臺專門的 Git 服務器。github
本地協議雖然簡單,可是通常來講並不適用,由於你沒法控制用戶對共享文件系統的操做,用戶擁有 push 權限也就意味着用戶對遠程目錄擁有完整的 Shell 權限,他們有可能會無心甚至有意的修改或刪除 Git 內部文件,損壞 Git 倉庫。算法
更安全的作法是使用專門的 Git 服務器,若是你有一臺可使用 SSH 鏈接的服務器,搭建 Git 服務將會很是簡單。首先咱們要確保服務器上運行着 SSH 服務(sshd
),大多數 Linux 服務器版本都默認包含了該服務,若是沒有,能夠先安裝 openssh-server
。而後在服務器上建立 Git 遠程版本庫:shell
root@myserver:~# mkdir -p /git/repo
root@myserver:~# git init --bare /git/repo/test.git
已初始化空的 Git 倉庫於 /git/repo/test.git/
複製代碼
而後在本地 clone
這個版本庫:apache
aneasystone@little-stone:~/working$ git clone ssh://root@myserver/git/repo/test.git
正克隆到 'test'...
root@myserver's password: warning: 您彷佛克隆了一個空倉庫。 複製代碼
能夠看到和使用本地協議幾乎同樣,不一樣的地方在於,在 clone 的時候須要在 URL 前面加上 ssh://root@myserver
,你也可使用 scp 式的寫法:
$ git clone root@myserver:/git/repo/test.git
複製代碼
另一點不一樣的地方是,每次 pull
、push
的時候都須要輸入遠程服務器的 root 密碼。很顯然,讓每一個 Git 用戶都使用 root 來訪問服務器是一種很不安全的作法,有幾種方法能夠解決這個問題:
adduser
手工管理服務器帳號的麻煩;authorized_keys
文件對用戶的公鑰進行管理;下面咱們嘗試下第三種方法。首先在服務器上建立一個名叫 git 的帳號:
root@myserver:~# adduser git
Adding user `git' ... Adding new group `git' (1000) ...
Adding new user `git' (1000) with group `git' ...
Creating home directory `/home/git' ... Copying files from `/etc/skel' ...
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
Changing the user information for git
Enter the new value, or press ENTER for the default
Full Name []: git
Room Number []:
Work Phone []:
Home Phone []:
Other []:
Is the information correct? [Y/n] Y
複製代碼
再設置一下 git 倉庫的權限(默認狀況下,git 倉庫的權限爲 rwxr-xr-x
,只有建立者 root 有寫的權限,也就意味着使用 git 帳號只能 clone
pull
,不能 push
):
# chmod a+w -R /git/repo/test.git
複製代碼
咱們這裏很是粗暴的使用 chmod a+w
將 git 倉庫設置爲對全部人可寫,這裏能夠想想,若是咱們但願設置某些用戶對倉庫具備只讀的權限,該怎麼作呢?
而後就能夠在本地愉快的進行 git 操做了:
$ git clone git@myserver:/git/repo/test.git
複製代碼
到這裏彷佛一切都很正常,可是幾回實操以後你就會發現,每次 git 操做都要輸入一次密碼,這也太麻煩了,能不能「免密提交代碼」呢?首先咱們要知道,只要能經過 SSH 登錄到服務器,咱們就能操做 git,因此若是 SSH 能支持免密登錄,咱們就能夠「免密提交代碼」。還好,SSH 支持公鑰認證,這種認證方式無需密碼登錄。在 Linux 操做系統中,每一個用戶均可以擁有本身的一個或多個密鑰對(公鑰和私鑰成對出現),這些密鑰通常狀況會保存在 ~/.ssh
目錄下,在開始以前,咱們先確認下本身是否已經生成過公鑰了,能夠看下這個目錄下是否有 id_dsa.pub
或 id_rsa.pub
這樣的文件,若是沒有,咱們經過 ssh-keygen
來生成:
aneasystone@little-stone:~/.ssh$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/aneasystone/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/aneasystone/.ssh/id_rsa.
Your public key has been saved in /home/aneasystone/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:4Ulpufuhs/AgDMb0VXnqMUTw6bD/HrAOI2z9c1cod9I aneasystone@little-stone
The key's randomart image is: +---[RSA 2048]----+ | .oo. | | oo+. | | . o.Oo | | o . . B++ | | + . ..So o | | . + . ..+. + E | | * * + oo + | | . o Oo+.o. | | **+. | +----[SHA256]-----+ 複製代碼
這樣咱們在 ~/.ssh
目錄生成了兩個文件,id_rsa
是你的私鑰,id_rsa.pub
是你的公鑰。關於私鑰和公鑰的原理以及 RSA 加密算法等內容能夠參考我以前寫過的一篇介紹 HTTPS 和證書 的文章。
咱們假設你的 Git 服務器是由專門的服務器管理員負責維護和管理,當你生成你的公鑰以後,就能夠給服務器管理員發送一封申請 Git 服務的郵件,並附上你的公鑰。服務器管理員在收到你的申請以後,若是贊成了,就能夠進行下面的操做:
首先將公鑰文件拷貝到服務器上:
# scp id_rsa.pub root@myserver:/home/git
複製代碼
將公鑰文件的內容追加到 git 帳戶的 authorized_keys 文件中(要注意的是,若是是第一次操做,/home/git 目錄下是沒有 .ssh 目錄的,須要手工建立 .ssh 目錄和 authorized_keys 文件):
root@myserver:/home/git# cat id_rsa.pub >> /home/git/.ssh/authorized_keys
複製代碼
後續若是有其餘的用戶申請 Git 服務,均可以按照這個步驟操做。一旦完成這個操做,服務器管理員將會回覆你的郵件,通知你 Git 服務已經開通,這個時候你再進行 git 操做就能夠不用輸入密碼了。關於 SSH 的使用,更詳細的步驟能夠參考 Github 上的這篇指南:Connecting to GitHub with SSH。
做爲服務器管理員,關於 SSH 還有一點須要考慮,那就是 SSH 的安全問題。在上面介紹本地協議時,咱們說這種方式沒法控制用戶對 Git 倉庫的操做,沒法防止用戶有意或無心的損壞 Git 倉庫,使用 SSH 協議同樣存在這樣的問題,用戶能經過 SSH 拉取和提交代碼,也就意味着用戶能夠經過 SSH 鏈接到服務器,對 Git 倉庫進行任何操做,這是一件很讓人擔憂的事情。
所以,咱們還須要對 git 帳號作一些限制。默認狀況下,咱們新建帳號的登錄 shell 是 /bin/bash
,這個配置在 /etc/passwd
文件中:
git:x:1000:1000:git,,,:/home/git:/bin/bash
複製代碼
可使用 chsh
命令修改用戶的登錄 shell,讓他不能經過 SSH 訪問服務器,怎麼修改呢?咱們能夠看一下 /etc/shells
文件,這裏定義了全部可使用的登錄 shell,你能夠將 /bin/bash
改爲這裏的任何一個:
root@myserver:~# cat /etc/shells
# /etc/shells: valid login shells
/bin/sh
/bin/dash
/bin/bash
/bin/rbash
複製代碼
很顯然,這些 shell 並非咱們想要的,有沒有一個 shell 只容許用戶進行 git 操做,而不容許其餘操做呢?還好,Git 的軟件包提供了一個名叫 git-shell
的登錄 shell,咱們能夠把他加進去,通常狀況下位於 /usr/bin/git-shell
。咱們使用 chsh
修改 git 的登錄 shell:
root@myserver:~# chsh git
Changing the login shell for git
Enter the new value, or press ENTER for the default
Login Shell [/bin/bash]: /usr/bin/git-shell
複製代碼
這樣當用戶 git 經過 SSH 鏈接服務器時,就會直接被拒絕了。
SSH 協議解決了用戶直接操做 Git 倉庫的權限問題,可是若是咱們但願對除倉庫維護者以外的全部人都開放 Git 倉庫的只讀權限,這在開源項目中和企業內部每每是很常見的,任何人均可以去查看倉庫的代碼,這時管理員須要給每個用戶配置 SSH 密鑰是很是麻煩的。雖然也可使用變通的方法來達到這個效果,可是很繁瑣,下面是具體的步驟:
g+w
設置 Git 倉庫的權限,讓倉庫建立者所在的用戶組具備寫權限,而不是全部人都有寫權限(這一步一般也能夠在 git init
的時候加上 --shared
參數);能夠看到使用 SSH 協議最終都逃不過受權這一步,並且公開私鑰的作法也不是很優雅。實際上,Git 提供了另外一種方式來讓這個操做更簡單,那就是 Git 協議。使用 Git 協議必需要在服務器上運行 Git 守護進程,git 命令自帶了一個 daemon
參數:
root@myserver:~# git daemon --reuseaddr --base-path=/git/repo/ /git/repo/
複製代碼
上面的各個參數能夠 參考 git-daemon 的文檔。git-daemon 會監聽 9418 端口,若是你的服務器有防火牆,須要將該端口添加到白名單,若是你使用的是阿里雲服務器,須要像下面這樣添加一個安全組規則:
爲了讓全部的用戶均可以訪問咱們的倉庫,還須要在倉庫目錄下建立一個名爲 git-daemon-export-ok
的文件:
root@myserver:~# cd /git/repo/test.git/
root@myserver:/git/repo/test.git/# touch git-daemon-export-ok
複製代碼
至此,全部人均可以經過 Git 協議來克隆或拉取項目源碼了(注意上面指定了 base-path
參數爲 /git/repo/
,因此 URL 能夠直接寫 git://myserver/test.git
):
aneasystone@little-stone:~/working$ git clone git://myserver/test.git
複製代碼
通常狀況下,服務器管理員還會作一些其餘的配置,譬如在服務器重啓時讓 Git 守護進程自動啓動,這有不少種方式能夠實現,能夠參考《Pro Git》 Git 守護進程 這一節的內容。
咱們通常經過 Git 協議進行無受權訪問,經過 SSH 協議進行受權訪問,若是你的項目是內部項目,只針對部分受權用戶,那使用 SSH 協議就足夠了,可是若是既須要受權訪問也須要無受權訪問,可能須要 SSH 協議和 Git 協議搭配使用,這在維護上成本很高。這時就到了咱們的壓軸戲 —— HTTP 協議出場的時候了,它同時支持上面兩種訪問方式。
經過 HTTP 協議訪問 Git 服務是目前使用最普遍的方式,它支持兩種模式:舊版本的 Dumb HTTP
和 新版本的 Smart HTTP
,Dumb HTTP 通常不多使用,下面除非特殊說明,所說的 HTTP 協議都是 Smart HTTP。使用 HTTP 協議的好處是可使用各類 HTTP 認證機制,好比用戶名/密碼,這比配置 SSH 密鑰要簡單的多,對普通用戶來講也更能接受。若是擔憂數據傳輸安全,也能夠配置 HTTPS 協議,這和普通的 Web 服務是同樣的。
下面咱們就來嘗試搭建一個基於 HTTP 協議的 Git 服務器。《Pro Git》上提供了一個基於 Apache 的配置示例,若是你是使用 Apache 做爲 Web 服務器,能夠參考之,咱們這裏使用 Nginx 來做爲 Web 服務器,其原理本質上是同樣的,都是經過 Web 服務器接受 HTTP 請求,並將請求轉發到 Git 自帶的一個名爲 git-http-backend
的 CGI 腳本。
首先咱們安裝所需的軟件:
# apt-get install -y git-core nginx fcgiwrap apache2-utils
複製代碼
其中,Nginx 做爲 Web 服務器,自己是不能執行外部 CGI 腳本的,須要經過 fcgiwrap 來中轉,就像使用 php-fpm 來執行 PHP 腳本同樣。apache2-utils 是 Apache 提供的一個 Web 服務器的工具集,包含了一些有用的小工具,譬以下面咱們會用到的 htpasswd 能夠生成 Basic 認證文件。
啓動 nginx 和 fcgiwrap,並訪問 http://myserver
測試 Web 服務器是否能正常訪問:
# service nginx start
# service fcgiwrap start
複製代碼
而後咱們打開並編輯 Nginx 的配置文件(/etc/nginx/sites-available/default
):
location / {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend;
fastcgi_param GIT_HTTP_EXPORT_ALL "";
fastcgi_param GIT_PROJECT_ROOT /git/repo;
fastcgi_param PATH_INFO $uri;
fastcgi_param REMOTE_USER $remote_user;
fastcgi_pass unix:/var/run/fcgiwrap.socket;
}
複製代碼
這裏經過 fastcgi_param
設置了一堆的 FastCGI 參數,以下:
git-http-backend
的位置,表示每次 HTTP 請求會被轉發到該 CGI 腳本;git-http-backend
默認只能訪問目錄下有 git-daemon-export-ok
文件的 Git 倉庫,和上面介紹的 Git 協議是同樣的,若是指定了 GIT_HTTP_EXPORT_ALL,表示容許訪問全部倉庫;改完以後咱們重啓 Nginx,並經過 HTTP 協議 clone
倉庫:
aneasystone@little-stone:~/working$ git clone http://myserver/test.git
複製代碼
到這裏一切 OK,可是當咱們 push
代碼的時候,卻會報下面的 403 錯誤:
aneasystone@little-stone:~/working/test$ git push origin master
fatal: unable to access 'http://myserver/test.git/': The requested URL returned error: 403
複製代碼
爲了解決這個錯誤,咱們能夠在 git-http-backend 的官網文檔 上找到這樣的一段描述:
By default, only the
upload-pack
service is enabled, which serves git fetch-pack and git ls-remote clients, which are invoked from git fetch, git pull, and git clone. If the client is authenticated, thereceive-pack
service is enabled, which serves git send-pack clients, which is invoked from git push.
第一次讀這段話可能會有些不知所云,這是由於咱們對這裏提到的 upload-pack
、fetch-pack
、receive-pack
、send-pack
這幾個概念尚未什麼認識。可是咱們大抵能夠猜出來,默認狀況下,只有認證的用戶才能夠 push 代碼,若是某個 Git 倉庫但願全部用戶都有權限 push 代碼,能夠爲相應的倉庫設置 http.receivepack
:
root@myserver:/# cd /git/repo/test.git/
root@myserver:/git/repo/test.git# git config http.receivepack true
複製代碼
固然最好的作法仍是對 push 操做開啓認證,官網文檔上有一個 lighttpd 的配置 咱們能夠借鑑:
$HTTP["querystring"] =~ "service=git-receive-pack" {
include "git-auth.conf"
}
$HTTP["url"] =~ "^/git/.*/git-receive-pack$" {
include "git-auth.conf"
}
複製代碼
這個配置看上去很是簡單,可是想要理解爲何這樣配置,就必須去了解下 Git 的內部原理。正如上面 git-http-backend 文檔上的那段描述,當 Git 客戶端執行 git fetch, git pull, and git clone 時,會調用 upload-pack
服務,當執行 git push 時,會調用 receive-pack
服務,爲了更清楚的說明這一點,咱們來看看 Nginx 的訪問日誌。
執行 git clone
:
[27/Nov/2018:22:18:00] "GET /test.git/info/refs?service=git-upload-pack HTTP/1.1" 200 363 "-" "git/1.9.1"
[27/Nov/2018:22:18:00] "POST /test.git/git-upload-pack HTTP/1.1" 200 306 "-" "git/1.9.1"
複製代碼
執行 git pull
:
[27/Nov/2018:22:20:25] "GET /test.git/info/refs?service=git-upload-pack HTTP/1.1" 200 363 "-" "git/1.9.1"
[27/Nov/2018:22:20:25] "POST /test.git/git-upload-pack HTTP/1.1" 200 551 "-" "git/1.9.1"
複製代碼
執行 git push
:
[27/Nov/2018:22:19:33] "GET /test.git/info/refs?service=git-receive-pack HTTP/1.1" 401 204 "-" "git/1.9.1"
admin [27/Nov/2018:22:19:33] "GET /test.git/info/refs?service=git-receive-pack HTTP/1.1" 200 193 "-" "git/1.9.1"
admin [27/Nov/2018:22:19:33] "POST /test.git/git-receive-pack HTTP/1.1" 200 63 "-" "git/1.9.1"
複製代碼
能夠看到執行 clone 和 pull 請求的接口是同樣的,先請求 /info/refs?service=git-upload-pack
,而後再請求 /git-upload-pack
;而 push 是先請求 /info/refs?service=git-receive-pack
,而後再請求 /git-receive-pack
,因此在上面的 lighttpd 的配置中咱們看到了兩條記錄,若是要對 push 作訪問控制,那麼對這兩個請求都要限制。關於 Git 傳輸的原理能夠參考 《Pro Git》的 Git 內部原理 - 傳輸協議 這一節。
咱們依葫蘆畫瓢,Nginx 配置文件以下:
location @auth {
auth_basic "Git Server";
auth_basic_user_file /etc/nginx/passwd;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend;
fastcgi_param GIT_HTTP_EXPORT_ALL "";
fastcgi_param GIT_PROJECT_ROOT /git/repo;
fastcgi_param PATH_INFO $uri;
fastcgi_param REMOTE_USER $remote_user;
fastcgi_pass unix:/var/run/fcgiwrap.socket;
}
location / {
error_page 418 = @auth;
if ( $query_string = "service=git-receive-pack" ) { return 418; }
if ( $uri ~ "git-receive-pack$" ) { return 418; }
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend;
fastcgi_param GIT_HTTP_EXPORT_ALL "";
fastcgi_param GIT_PROJECT_ROOT /git/repo;
fastcgi_param PATH_INFO $uri;
fastcgi_param REMOTE_USER $remote_user;
fastcgi_pass unix:/var/run/fcgiwrap.socket;
}
複製代碼
其中相同的配置咱們也能夠用 include
指令放在一個共用的配置文件裏,這樣咱們就實現了在 push 的時候須要填寫用戶名和密碼了。咱們經過 Nginx 的 auth_basic_user_file
指令來作身份認證,用戶名和密碼保存在 /etc/nginx/passwd
文件中,這個文件可使用上面提到的 apache2-utils 包裏的 htpasswd 來生成:
root@myserver:/# htpasswd -cb /etc/nginx/passwd admin 123456
複製代碼
另外,在 push 的時候,有時候可能會遇到 unpack failed: unable to create temporary object directory
這樣的提示錯誤:
aneasystone@little-stone:~/working/test$ git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 193 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
error: unpack failed: unable to create temporary object directory
To http://myserver/test.git
! [remote rejected] master -> master (unpacker error)
error: failed to push some refs to 'http://myserver/test.git'
複製代碼
這通常狀況下都是因爲 Git 倉庫目錄的權限問題致使的,在這裏 Git 倉庫的根目錄 /git/repo
是 root 建立的,而運行 nginx 和 fcgiwrap 的用戶都是 www-data,咱們能夠把 Git 倉庫目錄設置成對全部人可讀可寫,也能夠像下面這樣將它的擁有者設置成 www-data 用戶:
root@myserver:/# chown -R www-data:www-data /git/repo
複製代碼
上面咱們站在管理員的角度解決了用戶身份認證的問題,可是站在用戶的角度,每次提交代碼都要輸入用戶名和密碼是一件很痛苦的事情。在上面介紹 SSH 協議時,咱們可使用 SSH 協議自帶的公鑰認證機制來省去輸入密碼的麻煩,那麼在 HTTP 協議中是否存在相似的方法呢?答案是確定的,那就是 Git 的憑證存儲工具:credential.helper
。
譬如像下面這樣,將用戶名和密碼信息保存在緩存中:
$ git config --global credential.helper cache
複製代碼
這種方式默認只保留 15 分鐘,若是要改變保留的時間,能夠經過 --timeout
參數設置,或者像下面這樣,將密碼保存在文件中:
$ git config --global credential.helper store
複製代碼
這種方式雖然能夠保證密碼不過時,可是要記住的是,這種方式密碼是以明文的方式保存在你的 home 目錄下的。能夠借鑑操做系統自帶的憑證管理工具來解決這個問題, 好比 OSX Keychain 或者 Git Credential Manager for Windows。更多的內容能夠參考《Pro Git》憑證存儲 這一節。
除此以外,還有一種更簡單粗暴的方式:
aneasystone@little-stone:~/working$ git clone http://admin:123456@myserver/test.git
複製代碼
這一節對 Git 的四大協議作一個綜合對比。
上面介紹的是搭建 Git 服務器最基本的方法,若是你只是但願能找一個版本控制系統來替代現有的 SVN,這也許就足夠了。但若是你但願你的版本控制系統能擁有一個更友好的 UI 界面,能更好的管理你的用戶和權限,能支持更現代的 Pull Request 功能以及能和 CI/CD 系統更緊密的聯繫起來,你就須要一個更高級的工具,你能夠試試 GitWeb、Gitolite、Gitlab、Gogs、Gitea,固然,若是你願意,你也能夠把代碼放在那些流行的代碼託管平臺上,好比 Github、Bitbucket 等等。