實現 Git 目錄許可權控制功能
前言
Git 與 Subversion 有諸多不同,最核心的一點是前者屬於分散式版本控制工具,後者屬於集中式版本控制工具。前者的提交行為是離線的,本地的,後者的提交是線上的,需要與遠端中央伺服器通訊,線上建立提交。基於這種現實,Git 和 Subversion 在原生提供的附加功能也存在很大的差別。比如目錄許可權控制。Git 原生並不支援目錄許可權控制,而 Subversion 支援。
## Subversion 的目錄許可權控制
使用者接入遠端伺服器上的 Subversion 儲存庫通常可以使用 HTTP 協議 SVN 協議以及 SVN+SSH 協議,HTTP 協議本質上是 HTTP 客戶端與 Apache httpd 伺服器通訊,此時,請求由mod_dav_svn.so
模組處理,然後呼叫 subversion 的核心模組,包括檔案系統和儲存庫模組。使用 HTTP 訪問 Subversion 儲存庫時,可以如下:
svn co https://llvm.org/svn/llvm-project/llvm/trunk
還可以直接檢出儲存庫的子目錄:
svn co https://llvm.org/svn/llvm-project/llvm/trunk/include/
使用 SVN 協議與 SVN+SSH 協議本質上都是與遠端伺服器上的svnserve
通訊,前者是 svnserve 監聽 3690 TCP 埠,後者是在遠端伺服器上執行svnserve -t
,但內部的細節都是一致的。檢出程式碼如下:
svn co svn://llvm.org/svn/llvm-project/llvm/trunk
還可以直接檢出儲存庫的子目錄:
svn co svn://llvm.org/svn/llvm-project/llvm/trunk/include/
Subversion 的提交是線上的,以 SVN 協議為例,當用戶提交程式碼時,svn 客戶端將傳送commit
命令訊息給 svnserve,然後在這個命令中將修改的檔案傳送到遠端伺服器,遠端伺服器上作出一些列操作,然後建立提交。在這個過程中,只要對檔案相對儲存庫的路徑與目錄許可權規則作出匹配,就可以知道使用者是否由能力修改相應檔案。
Subversion 的原理也就使其很容易支援細粒度的許可權控制,可以精確到檔案。svnserve 可以修改 svnserve.conf 實現,而 HTTP 則與 apache mod_dav_svn 模組有關。
如果要禁止使用者讀取,則在 checkout 的時候禁止檢出即可,實現起來並不困難。
Git 不支援目錄許可權控制
像 Git 這樣的分散式版本控制系統,獲得遠端儲存庫的行為叫做clone
,常規情況下獲得遠端儲存庫的所有資料,但這種方式畢竟需要獲得大量資料,因此 Git 還提供淺表克隆,還有 VFSForGit 這樣的方式只獲得目錄結構,不獲取檔案內容,還有規劃中的部分克隆。
在伺服器上,要控制儲存庫的資料按目錄粒度提供給使用者讀取,通常無法通過 Git 客戶端實現,這是由 Git 傳輸協議決定的,Git 在克隆時先要發現引用列表,然後按需求獲得引用列表的提交 ID,伺服器將這些 Commit 以及相關的 Tree Blob 等等回溯打包傳送給客戶端。Git 伺服器最多能實現的是在引用發現時隱藏特定的引用實現禁止讀取,要做到目錄級別的禁止讀取,這是無法實現的。
分散式版本控制系統的提交是發生在本地的,當人們執行git commit -m "message"
時,就會在本地建立一個提交。無論是 Subversion 還是 Git,一個提交在一個儲存庫中是唯一的,比如 Subversion 中佔據唯一的一個修訂號(Revision),在 Git 中則是唯一的 commitID (目前為 SHA1,未來計劃轉變為 SHA-256)。在本地提交後,目錄結構已經確定,推送到伺服器並不能改變其目錄結構。這就意味著,Git 不支援目錄級別粒度的寫入控制。
綜合來看,Git 是完全不支援目錄許可權控制的。
Git 實現目錄許可權控制的一種方法
Git 的目錄級別粒度的禁止讀取並不在本文的研究範圍,本文所述的目錄許可權控制僅指 ‘目錄的只讀’。
在設計此功能前,我們需要了解 Git 的一些原理,比如,目錄許可權控制發生在服務端,因此,處理的時機為 Git 推送程式碼代遠端儲存庫時,一旦發現推送中修改了只讀檔案(目錄),則拒絕推送。在不修改 Git 原始碼的基礎上要實現這些功能通常可以使用服務端鉤子,伺服器上的主要的鉤子有三種,pre-receive
,update
,post-receive
,相關鉤子的原理在Git 原生鉤子的深度優化
一文中有介紹,要實現本節所述功能首先得排除post-receive
鉤子,此鉤子的成功與否不會影響引用的更新,所以並不合適,而pre-receive
鉤子目前使用了環境隔離: Quarantine Environment
機制,目前 libgit2 並不支援此特性,所以要實現目錄只讀也不能直接使用pre-receive
鉤子。
最終,只有update
鉤子可用。
在實現目錄許可權檢測時,我們應當先初始化規則,在git-analyze
專案中,我添加了一個update
鉤子作為目錄許可權檢測的原型,規則檔案如下:
{ "master":{ "dirs":[ "lib/", "build/" ], "regex":[ "build$" ] } }
規則按分支名劃分,其中.all
表示所有分支,.all
不能被用於 git 分支或者引用名稱,因此代表所有的分支。規則有目錄級別匹配和正則匹配,lib
表示lib
檔案或者目錄不可被修改。而regex
則表示目錄符合正則表示式的路徑不可被修改。
反思
雖然本文講述了實現 Git 目錄許可權控制的一個實現機制,但筆者比較反對過分使用 Git 服務端鉤子實現過多額外的功能。這些功能大多數都能可以通過規範的協作方式實現相同的目的。