歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> 如何用JGit管理Git子模塊

如何用JGit管理Git子模塊

日期:2017/2/28 14:02:01   编辑:Linux教程

對於一個較大的Git工程,你可能會想在多個倉庫之間共享代碼,不管這些代碼是在多個不同產品間使用的項目共享庫或是一些模板。Git通過子模塊來實現這樣的需求。子模塊允許將其他代碼倉庫的克隆作為子目錄放到一個父倉庫(有時候也稱為父項目)中。一個子模塊也是一個獨立的倉庫,你可以像其他倉庫一樣執行commit,branch,rebase等等操作。

JGit提供了實現大部分Git子模塊命令的API。我將在這兒給大家介紹這些API。

設置

本文中用到的代碼片段將作為學習測試程序。簡單的測試程序有助於理解第三方庫是如何工作,以及如何使用新的API。你可以將這些測試程序看做是可控制的試驗,幫助你更加直觀地發現第三方代碼是如何執行的。

除此之外,如果你保持編寫測試程序,可以幫助你檢驗第三方代碼的新版本。如果你的測試程序涵蓋了如何調用這些庫,那麼第三方代碼中不兼容的修改將會盡早展現出來。

回到之前的話題,所有的測試程序共享同一個設置,詳細信息請查看源代碼。現在有一個空的倉庫,叫parent,以及另一個倉庫叫library。測試程序中,library將會作為子模塊添加到parent倉庫中。library倉庫初始化提交了一個readme.txt文件。測試程序中有一個setUp方法,用來創建這兩個倉庫,如下所示:

1 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目錄下。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Test public void testAddSubmodule() throws Exception { String uri = library.getRepository().getDirectory().getCanonicalPath(); SubmoduleAddCommand addCommand = parent.submoduleAdd(); addCommand.setURI( uri ); addCommand.setPath( "modules/library" ); Repository repository = addCommand.call(); repository.close(); F‌ile workDir = parent.getRepository().getWorkTree(); F‌ile readme = new F‌ile( workDir, "modules/library/readme.txt" ); F‌ile gitmodules = new F‌ile( workDir, ".gitmodules" ); assertTrue( readme.isF‌ile() ); assertTrue( gitmodules.isF‌ile() ); }

SubmoduleAddCommand對象需要知道兩件事,第一是子模塊從哪裡克隆而來,第二是它應該存放在哪裡。URI屬性表示倉庫庫的克隆地址,這個克隆地址將會傳遞給clone命令。path屬性則指定了相對於parent倉庫根工作目錄的路徑,子模塊將被存放在這個路徑。這個指令執行之後,parent倉庫的工作目錄將會變成這樣:

library倉庫存放在modules/library目錄下,而且它的工作目錄樹被檢出。call()方法返回一個Repository對象,你可以把它當做一個常規的倉庫來使用。這也意味著,你必須在程序中明確顯式地關閉返回的倉庫,以避免文件句柄洩露。

從上圖我們可以看到,SubmoduleAddCommand做了一件事,它在parent倉庫的根工作目錄下創建了一個.git模塊文件,並把它添加到索引中。

1 2 3 [submodule "modules/library"] path = modules/library url = [email protected]:path/to/lib.git

如果你打開過Git的配置文件,你會發現以上句法。這個文件列出了當前倉庫的所有子模塊。對於每個模塊,文件中列出了它倉庫URL地址以及本地路徑。一旦commit並push了這個文件,克隆這個倉庫的一方就知道哪裡可以獲取相應的子模塊(稍後會詳細講解)。

列出子模塊

當我們添加了一個子模塊之後,我們可以會想知道,它是否對於父倉庫來說是可知的。第一項測試中我們做了一個基礎的檢測,驗證了某些文件和目錄的存在。我們也可以使用一個API來列出一個倉庫的子模塊,如下所示:

1 2 3 4 5 6 7 8 9 10 11 @Test public void testListSubmodules() throws Exception { 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,那麼所有對子模塊工作目錄的修改都會被忽略。

更新子模塊

子模塊通常指向他們所在的倉庫的一次特殊的提交。如果之後有人克隆了父倉庫,他們也會獲得與之完全相同的子模塊狀態,即便子模塊的上游有新的提交。

為了修改子模塊,你像一下代碼一樣明確地對其進行更新:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void testUpdateSubmodule() throws Exception { addLibrarySubmodule(); ObjectId newHead = library.commit().setMessage( "msg" ).call(); File workDir = parent.getRepository().getWorkTree(); Git libModule = Git.open( new F‌ile( workDir, "modules/library" ) ); libModule.pull().call(); libModule.close(); parent.add().addF‌ilepattern( "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命令會被遞歸地執行,並且父倉庫的工作目錄會被檢出。

移除一個子模塊

如果要移除一個子模塊,你會希望可以這樣寫:

1 git.submoduleRm().setPath( ... ).call();

但是很不幸,不管是原生的Git或者JGit都沒有提供內置的移除子模塊的指令,希望將來會添加這樣的指令,在這之前,我們必須手動去移除子模塊。如果你滾動到removeSubmodule()方法你會發現這並不是一件復雜的事。

首先,各個子模塊會從.gitsubmodules和.git/config配置文件中移除。其次,子模塊的入口會從索引中被移除。最後,.gitsubmodules文件以及索引的修改會被提交,並且子模塊的內容會從工作目錄中刪除。

遍歷子模塊

原生的Git提供了git submodule foreach命令為每個子模塊執行一個shell指令。JGit並沒有直接支持這樣的指令,而是提供了SubmoduleWalk。該類可以用來迭代倉庫中子模塊。以下示例程序實現了為所有子模塊拉取上游的提交。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test public void testSubmoduleWalk() throws Exception { addLibrarySubmodule(); int submoduleCount = 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社區去尋求幫助。

Copyright © Linux教程網 All Rights Reserved