JGit提供了實現大部分Git子模組命令的API。我將在這兒給大家介紹這些API。
設定
本文中用到的程式碼片段將作為學習測試程式。簡單的測試程式有助於理解第三方庫是如何工作,以及如何使用新的API。你可以將這些測試程式看做是可控制的試驗,幫助你更加直觀地發現第三方程式碼是如何執行的。
除此之外,如果你保持編寫測試程式,可以幫助你檢驗第三方程式碼的新版本。如果你的測試程式涵蓋了如何呼叫這些庫,那麼第三方程式碼中不相容的修改將會盡早展現出來。
回到之前的話題,所有的測試程式共享同一個設定,詳細資訊請檢視原始碼。現在有一個空的倉庫,叫parent,以及另一個倉庫叫library。測試程式中,library將會作為子模組新增到parent倉庫中。library倉庫初始化提交了一個readme.txt檔案。測試程式中有一個setUp方法,用來建立這兩個倉庫,如下所示:
Git git = Git.init().setDirectory("/tmp/path/to/repo").call();
這兩個倉庫用型別為Git的parent和library變數表示。該類封裝了一個倉庫並允許訪問JGit的所有可用指令。就如較早之前我在這裡中提到,每個Commnad類對應於一條原生的Git pocelain指令。呼叫一個指令需要用到生成器模式。舉個例子,執行Git.commit()的結果實際上相當於一個CommitCommand。你可以提供一些必要的引數去呼叫它的call()方法,從而執行相應的指令。
新增一個子模組
第一步當然是在一個已有的倉庫新增子模組。通過上面提到的setUp步驟,library倉庫應當作為子模組新增到parent倉庫的modules/library目錄下。
@Test publicvoidtestAddSubmodule()throwsException { String uri = library.getRepository().getDirectory().getCanonicalPath(); SubmoduleAddCommand addCommand = parent.submoduleAdd(); addCommand.setURI( uri ); addCommand.setPath("modules/library"); Repository repository = addCommand.call(); repository.close(); File workDir = parent.getRepository().getWorkTree(); File readme =newFile( workDir,"modules/library/readme.txt"); File gitmodules =newFile( workDir,".gitmodules"); assertTrue( readme.isFile() ); assertTrue( gitmodules.isFile() ); }
SubmoduleAddCommand物件需要知道兩件事,第一是子模組從哪裡克隆而來,第二是它應該存放在哪裡。URI屬性表示倉庫庫的克隆地址,這個克隆地址將會傳遞給clone命令。path屬性則指定了相對於parent倉庫根工作目錄的路徑,子模組將被存放在這個路徑。這個指令執行之後,parent倉庫的工作目錄將會變成這樣:
library倉庫存放在modules/library目錄下,而且它的工作目錄樹被檢出。call()方法返回一個Repository物件,你可以把它當做一個常規的倉庫來使用。這也意味著,你必須在程式中明確顯式地關閉返回的倉庫,以避免檔案控制程式碼洩露。
從上圖我們可以看到,SubmoduleAddCommand做了一件事,它在parent倉庫的根工作目錄下建立了一個.git模組檔案,並把它新增到索引中。
[submodule"modules/library"] path = modules/library url = git@example.com:path/to/lib.git
如果你開啟過Git的配置檔案,你會發現以上句法。這個檔案列出了當前倉庫的所有子模組。對於每個模組,檔案中列出了它倉庫URL地址以及本地路徑。一旦commit並push了這個檔案,克隆這個倉庫的一方就知道哪裡可以獲取相應的子模組(稍後會詳細講解)。
列出子模組
當我們新增了一個子模組之後,我們可以會想知道,它是否對於父倉庫來說是可知的。第一項測試中我們做了一個基礎的檢測,驗證了某些檔案和目錄的存在。我們也可以使用一個API來列出一個倉庫的子模組,如下所示:
@Test publicvoidtestListSubmodules()throwsException { addLibrarySubmodule(); Map<String,SubmoduleStatus> submodules = parent.submoduleStatus().call(); assertEquals(1, submodules.size() ); SubmoduleStatus status = submodules.get("modules/library"); assertEquals( INITIALIZED, status.getType() ); }
SubmoduleStatus命令返回了一個子模組的Map集合,其中鍵是子模組的路徑,值是這個模組的狀態值。通過以上程式碼我們能夠驗證子模組確實已經新增進去,而且它的狀態是INITIALIZED的。這個命令還允許新增一個或多個路徑來限制子模組狀態。
說到狀態,JGit的StatusCommand並非原生的Git指令。如果在執行指令時新增選項‐‐ignore-submodules=dirty,那麼所有對子模組工作目錄的修改都會被忽略。
更新子模組
子模組通常指向他們所在的倉庫的一次特殊的提交。如果之後有人克隆了父倉庫,他們也會獲得與之完全相同的子模組狀態,即便子模組的上游有新的提交。
為了修改子模組,你像一下程式碼一樣明確地對其進行更新:
@Test publicvoidtestUpdateSubmodule()throwsException { addLibrarySubmodule(); ObjectId newHead = library.commit().setMessage("msg").call(); File workDir = parent.getRepository().getWorkTree(); Git libModule = Git.open(newFile( workDir,"modules/library") ); libModule.pull().call(); libModule.close(); parent.add().addFilepattern("modules/library").call(); parent.commit().setMessage("Update submodule").call(); assertEquals( newHead, getSubmoduleHead("modules/library") ); }
這個較長的程式碼片段中,首先第一件事就是提交一些東西到library倉庫中(第四行),接著將子模組更新到最近的一次提交。
為了讓這種更新持久化儲存下來,子模組必須被提交(第10,11行)。這次提交在子模組的名下(例子中是modules/library)儲存了此次更新的commit-id。最後,通常需要將修改push上去,使得他們對其他倉庫可用。
在父倉庫中更新對子模組的修改
將上游的提交拉取到父倉庫中也會修改子模組的配置。然而子模組本身並不會自動得到更新。
SubmoduleUpdateCommand就是用來解決這個問題。使用這個命令並不需要指定其他引數,它會更新所有已註冊的子模組。該命令會克隆缺失的子模組並檢出其配置中指定的提交。就如其他子模組命令一樣,這裡也有一個addPath()方法,以保證只更新給定路徑下的子模組。
克隆一個包含子模組的倉庫
此時你可能已經掌握一個規律,所有對子模組的操作都是手動的。克隆一個包含子模組配置的倉庫並不會預設克隆它的子模組。但是,CloneCommand命令有一個cloneSubmodules的屬性,如果設定為true,那麼將會克隆所有配置的子模組。從內部看,在對父倉庫進行克隆之後,SubmoduleInitCommand和SubmoduleUpdateCommand命令會被遞迴地執行,並且父倉庫的工作目錄會被檢出。
移除一個子模組
如果要移除一個子模組,你會希望可以這樣寫:
git.submoduleRm().setPath( ... ).call();
但是很不幸,不管是原生的Git或者JGit都沒有提供內建的移除子模組的指令,希望將來會新增這樣的指令,在這之前,我們必須手動去移除子模組。如果你滾動到removeSubmodule()方法你會發現這並不是一件複雜的事。
首先,各個子模組會從.gitsubmodules和.git/config配置檔案中移除。其次,子模組的入口會從索引中被移除。最後,.gitsubmodules檔案以及索引的修改會被提交,並且子模組的內容會從工作目錄中刪除。
遍歷子模組
原生的Git提供了git submodule foreach命令為每個子模組執行一個shell指令。JGit並沒有直接支援這樣的指令,而是提供了SubmoduleWalk。該類可以用來迭代倉庫中子模組。以下示例程式實現了為所有子模組拉取上游的提交。
@Test publicvoidtestSubmoduleWalk()throwsException { addLibrarySubmodule(); intsubmoduleCount =0; Repository parentRepository = parent.getRepository(); SubmoduleWalk walk = SubmoduleWalk.forIndex( parentRepository ); while( walk.next() ) { Repository submoduleRepository = walk.getRepository(); Git.wrap( submoduleRepository ).fetch().call(); submoduleRepository.close(); submoduleCount++; } walk.release(); assertEquals(1, submoduleCount ); }
通過next()方法walk物件可以指向下一個子模組,如果沒有更多的子模組,該方法會返回false。使用SubmoduleWalk時,通過呼叫release()方法可以釋放子模組相關的資源。再次強調,如果你獲得一個子模組的倉庫例項可別忘了關閉它。
SubmoduleWalk也可以用來獲取子模組的詳細資訊。通過它的大部分getter方法可以訪問到當前子模組的屬性,諸如path,head,remote URL等等。
同步遠端URL
從上面我們知道子模組的配置儲存在父倉庫根工作目錄下的.gitsubmodules檔案中。而至少,在.git/config檔案中,我們可以重寫覆蓋子模組的遠端URL。對於每個子模組,它們本身都有一個配置檔案。那麼反過來,每個子模組可以有另一個遠端URL。SubmoduleSyncCommand命令可以用來將所有遠端URL重置為.gitmodules中的配置。
綜上所述,JGit對子模組的支援幾乎與原生的Git一致。大部分Git指令都在JGit中實現了,或可以通過一些途徑進行模擬。如果你發現一些操作缺失或實現不了,可以去友好且幫得上忙的JGit社群去尋求幫助。
原文連結: javacodegeeks 翻譯: ImportNew.com - chenchenchao
評論(1)