用 JGit 初始化 Git 庫

oschina發表於2015-05-08

最近,我被問及如何用 JGit 來初始化一個新的 Git 庫,比如實現初始化一個庫 /path/to/repodoes。

當我用 JGit 來建立庫時其實並不難,這裡有些細節值得提一提。因為在網上幾乎少有關於這個主題的資料,而且有些還是錯誤的,因此本文就總結了如何使用 JGit API 來初始化一個 Git 庫的方法。

本地庫

為了用 JGit 來初始化一個庫,那就要使用初始化命令。factoryGit 命令擁有一個靜態方法 init() 來建立初始化命令。

Git git = Git.init().setDirectory( directory ).call();

在上述程式碼執行後,一個新的Git庫就已經建立完成了。這個庫的儲存位置是通過setDirectory()來給定的,這個庫構成了工作目錄,它包含了已簽出的檔案及資料夾。若這個目錄並不存在,那麼通過上述方法會一直建立這個工作目錄。

用 JGit 初始化 Git 庫

一個名為 .git 的子目錄在整個工作目錄中處於最頂層。這裡麵包含了本地庫的歷史日誌,配置引數,分支指標,索引(又名中轉區)等等。

上面的截圖中展示了.git 目錄的內部結構。refs 目錄維護了分支和標籤資訊。其實際內容將會儲存在物件目錄中。在 logs 目錄中,有關對分割槽的修改都會被記錄。舉個例子,一個提交和簽出操作都將建立一個日誌記錄,這個記錄可以用 gitreflog 命令來檢視。多年前我寫的這個帖子 Explore Git Internals with the JGit API 裡面詳細的寫明瞭如何使用 Git 來管理這個庫裡內容。

為了確保命令事實上成功執行,狀態命令常用於查詢庫的狀態,很像git status做的一樣。但不幸的是JGit的status實現不同於原生Git,因為如果沒有庫,它不會報錯。這使得檢查一個已存在HEAD ref變得更為必要,它表示事實上有一個庫。

assertNotNull( git.getRepository().getRef( Constants.HEAD ) );
assertTrue( git.status().call().isClean() );

對於最近初始化的庫來說,isClean()返回true,是因為在工作目錄裡邊沒有任何改動或者有未跟蹤檔案。

斷言使用theInitCommandscall()方法返回的GIT例項。這個類充當工廠的作用,並且常用於建立在庫上執行的Git命令(例如:add,commit,checkout)。

新初始化的庫比較特殊,由於還沒有分支被建立。儘管有一個引用分支的HEAD(指向當前分支),(預設叫做master)這個特有的分支並不存在。

就第一個提交來說通常沒有什麼需要擔心的,缺少的分支將會被建立。不管怎樣,像git branch這樣的操作可以建立分支並會在最初的提交被提交之前,會伴隨著略微令人誤導的錯誤資訊’Ref HEAD cannot be resolved’而失敗。

確定庫是否包含一個提交,檢查HEAD ref如下:

Ref headRef = git.getRepository().getRef( Constants.HEAD );
if( headRef == null || headRef.getObjectId() == null ) {
  // no commit yet
}

將目錄變成庫

如上所示,在必要的情況下,InitCommand會建立缺少的目錄。但是這個命令也常用於已存在的目錄,從而將其變成一個git庫。

下面的程式碼片段在一個包含一個檔案的已存在目錄裡邊初始化庫,並保留其內容。

F‌ile f‌ile = new F‌ile( "/path/to/existing/directory/readme.txt" );
f‌ile.createNewF‌ile();
Git git = Git.init().setDirectory( f‌ile.getParentF‌ile() ).call();
assertTrue( git.status().call().getUntracked().contains( f‌ile.getName() ) );

由於檔案未被跟蹤,status命令會提示這個檔案。直到將這個檔案加入索引才能夠被提交到剛剛建立的庫裡邊。

如果指定的目錄已經持有一個Git庫,那就沒有必要擔心。既然這樣,JGit將不做任何事並返回一個指向已存在庫的例項。

分離工作和.git目錄

預設情況下,儘管一個庫有一個工作目錄,它的.git目錄直接位於工作目錄下面,但這不是必須的。一個庫完全可以沒有工作目錄(後續討論)或者工作目錄可以位於一個除.git目錄之外完全不同的位置。

下面的初始化命令建立了這樣一個庫

Git git = Git.init().setDirectory( workDir ).setGitDir( gitDir ).call();

最終的庫將會位於gitDir目錄(儲存歷史記錄,分支,標籤等的目錄),但這將會使它的工作目錄在workDir上面。

對於一個已存在的庫來說,工作目錄配置也是可以改變的。要麼手動編輯配置檔案.git,或者通過JGit API配置。

StoredConfig config = git.getRepository().getConfig();
config.setString( "core", null, "worktree", workDir.getCanonicalPath() );
config.save();

沒有必要把工作目錄內容手動從舊的位置移動到新的位置。

空倉庫

剛剛建立的庫是在本地處理的,也叫非空倉庫。另一種Git庫叫做空倉庫。

這些目的在於用作中心倉庫,將被其他使用者共享。沒有直接的提交可以提交到空倉庫中。一個空倉庫收到提交,是由於它們從使用者本地倉庫被推送。團隊成員從這個倉庫中拿下其他人提交的提交。

下面的程式碼片段將建立一個空倉庫。

Git git = Git.init().setDirectory( directory ).setBare( true ).call();

一個空倉庫沒有工作目錄。反之,它的目錄結構可以在一個非空倉庫中的.git目錄下面找到並在指定的目錄中直接被建立。

因為status命令需要一個工作目錄,所以它不能夠驗證上述程式碼成功執行。而是倉庫例項應該為空並想下面驗證指向所需目錄。

assertTrue( git.getRepository().isBare() );
assertEquals( directory, git.getRepository().getDirectory() );

值得注意的是為這個工作目錄查詢倉庫(也就是callinggetWorkTree())將會對空倉庫丟擲NoWorkTreeException。

可選API: Repository.create()

一個可選的方法來初始化倉庫就是使用Repository的create方法。

Repository repository = new F‌ileRepositoryBuilder().setGitDir( directory ).build();
repository.create();

assertNotNull( git.getRepository().getRef( Constants.HEAD ) );
assertTrue( Git.wrap( repository ).status().call().isClean() );

在FileRepositoryBuilder幫助下,Repository例項被建立,代表沒有現有的庫。Callingcreate()方法具體化倉庫。結果和InitCommand一樣。

為了建立空倉庫,還有一個過載的方法:create(boolean bare)。

結論

我希望這篇文章能夠幫助理解怎樣用JGit建立一個新倉庫。這裡使用的程式碼收集於學習測試,可以在這裡發現它的全部內容:
https://gist.github.com/rherrmann/4bacb68b23be1f12c73d

它詳細說明了對InitCommand API正確和不正確的使用,或許可以作為進一步實驗JGit的一個起點。

如果你有任何難點或問題,可以留下評論或在友善且樂於助人的JGit社群提問以獲得幫助。

相關文章