用 JGit 初始化 Git 庫
最近,我被問及如何用 JGit 來初始化一個新的 Git 庫,比如實現初始化一個庫 /path/to/repodoes。
當我用 JGit 來創建庫時其實并不難,這里有些細節值得提一提。因為在網上幾乎少有關于這個主題的資料,而且有些還是錯誤的,因此本文就總結了如何使用 JGit API 來初始化一個 Git 庫的方法。
本地庫
為了用 JGit 來初始化一個庫,那就要使用初始化命令。factoryGit 命令擁有一個靜態方法 init() 來創建初始化命令。
Git git = Git.init().setDirectory( directory ).call();
在上述代碼執行后,一個新的Git庫就已經創建完成了。這個庫的存儲位置是通過setDirectory()來給定的,這個庫構成了工作目錄,它包含了已簽出的文件及文件夾。若這個目錄并不存在,那么通過上述方法會一直創建這個工作目錄。
一個名為 .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() );</pre>
在FileRepositoryBuilder幫助下,Repository實例被創建,代表沒有現有的庫。Callingcreate()方法具體化倉庫。結果和InitCommand一樣。
為了創建空倉庫,還有一個重載的方法:create(boolean bare)。
結論
我希望這篇文章能夠幫助理解怎樣用JGit創建一個新倉庫。這里使用的代碼收集于學習測試,可以在這里發現它的全部內容:
https://gist.github.com/rherrmann/4bacb68b23be1f12c73d它詳細說明了對InitCommand API正確和不正確的使用,或許可以作為進一步實驗JGit的一個起點。
如果你有任何難點或問題,可以留下評論或在友善且樂于助人的JGit社區提問以獲得幫助。