From b57845c0cc7d4d9428f26701b1c21e03982639c8 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 17 Jan 2012 09:35:29 -0800 Subject: [PATCH] Support relative submodule URLs on init/add/sync Interpret submodule URLs that start with './' or '../' as relative to either the configured remote for the HEAD branch, or 'origin', or the parent repository working directory if no remote URL is configured Bug: 368536 Change-Id: Id4985824023b75cd45cd64a4dd9d421166391e10 --- .../jgit/submodule/SubmoduleAddTest.java | 43 ++++ .../jgit/submodule/SubmoduleInitTest.java | 232 +++++++++++++++++- .../jgit/submodule/SubmoduleSyncTest.java | 69 ++++++ .../org/eclipse/jgit/JGitText.properties | 1 + .../src/org/eclipse/jgit/JGitText.java | 1 + .../eclipse/jgit/api/SubmoduleAddCommand.java | 10 +- .../jgit/api/SubmoduleInitCommand.java | 2 +- .../jgit/api/SubmoduleSyncCommand.java | 2 +- .../eclipse/jgit/submodule/SubmoduleWalk.java | 94 +++++++ 9 files changed, 437 insertions(+), 17 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java index d14d11b62..dee2accf2 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java @@ -47,6 +47,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.File; import java.text.MessageFormat; import org.eclipse.jgit.JGitText; @@ -58,6 +59,7 @@ import org.eclipse.jgit.dircache.DirCacheEditor; import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; @@ -169,4 +171,45 @@ public void apply(DirCacheEntry ent) { e.getMessage()); } } + + @Test + public void addSubmoduleWithRelativeUri() throws Exception { + Git git = new Git(db); + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + RevCommit commit = git.commit().setMessage("create file").call(); + + SubmoduleAddCommand command = new SubmoduleAddCommand(db); + String path = "sub"; + String uri = "./.git"; + command.setPath(path); + command.setURI(uri); + Repository repo = command.call(); + assertNotNull(repo); + + SubmoduleWalk generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertEquals(path, generator.getPath()); + assertEquals(commit, generator.getObjectId()); + assertEquals(uri, generator.getModulesUrl()); + assertEquals(path, generator.getModulesPath()); + String fullUri = db.getDirectory().getAbsolutePath(); + if (File.separatorChar == '\\') + fullUri = fullUri.replace('\\', '/'); + assertEquals(fullUri, generator.getConfigUrl()); + assertNotNull(generator.getRepository()); + assertEquals( + fullUri, + generator + .getRepository() + .getConfig() + .getString(ConfigConstants.CONFIG_REMOTE_SECTION, + Constants.DEFAULT_REMOTE_NAME, + ConfigConstants.CONFIG_KEY_URL)); + assertEquals(commit, repo.resolve(Constants.HEAD)); + + Status status = Git.wrap(db).status().call(); + assertTrue(status.getAdded().contains(Constants.DOT_GIT_MODULES)); + assertTrue(status.getAdded().contains(path)); + } } \ No newline at end of file diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleInitTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleInitTest.java index 727368f34..f0a0750c5 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleInitTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleInitTest.java @@ -46,12 +46,14 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.File; import java.io.IOException; import java.util.Collection; import org.eclipse.jgit.api.SubmoduleInitCommand; +import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheEditor; import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; @@ -81,19 +83,7 @@ public void repositoryWithNoSubmodules() { @Test public void repositoryWithUninitializedModule() throws IOException, ConfigInvalidException { - final ObjectId id = ObjectId - .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); - final String path = "sub"; - DirCache cache = db.lockDirCache(); - DirCacheEditor editor = cache.editor(); - editor.add(new PathEdit(path) { - - public void apply(DirCacheEntry ent) { - ent.setFileMode(FileMode.GITLINK); - ent.setObjectId(id); - } - }); - editor.commit(); + final String path = addSubmoduleToIndex(); SubmoduleWalk generator = SubmoduleWalk.forIndex(db); assertTrue(generator.next()); @@ -123,4 +113,220 @@ public void apply(DirCacheEntry ent) { assertEquals(url, generator.getConfigUrl()); assertEquals(update, generator.getConfigUpdate()); } + + @Test + public void resolveSameLevelRelativeUrl() throws Exception { + final String path = addSubmoduleToIndex(); + + String base = "git://server/repo.git"; + FileBasedConfig config = db.getConfig(); + config.setString(ConfigConstants.CONFIG_REMOTE_SECTION, + Constants.DEFAULT_REMOTE_NAME, ConfigConstants.CONFIG_KEY_URL, + base); + config.save(); + + SubmoduleWalk generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertNull(generator.getConfigUrl()); + assertNull(generator.getConfigUpdate()); + + FileBasedConfig modulesConfig = new FileBasedConfig(new File( + db.getWorkTree(), Constants.DOT_GIT_MODULES), db.getFS()); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_PATH, path); + String url = "./sub.git"; + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, url); + String update = "rebase"; + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_UPDATE, update); + modulesConfig.save(); + + SubmoduleInitCommand command = new SubmoduleInitCommand(db); + Collection modules = command.call(); + assertNotNull(modules); + assertEquals(1, modules.size()); + assertEquals(path, modules.iterator().next()); + + generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertEquals("git://server/repo.git/sub.git", generator.getConfigUrl()); + assertEquals(update, generator.getConfigUpdate()); + } + + @Test + public void resolveOneLevelHigherRelativeUrl() throws IOException, + ConfigInvalidException { + final String path = addSubmoduleToIndex(); + + String base = "git://server/repo.git"; + FileBasedConfig config = db.getConfig(); + config.setString(ConfigConstants.CONFIG_REMOTE_SECTION, + Constants.DEFAULT_REMOTE_NAME, ConfigConstants.CONFIG_KEY_URL, + base); + config.save(); + + SubmoduleWalk generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertNull(generator.getConfigUrl()); + assertNull(generator.getConfigUpdate()); + + FileBasedConfig modulesConfig = new FileBasedConfig(new File( + db.getWorkTree(), Constants.DOT_GIT_MODULES), db.getFS()); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_PATH, path); + String url = "../sub.git"; + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, url); + String update = "rebase"; + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_UPDATE, update); + modulesConfig.save(); + + SubmoduleInitCommand command = new SubmoduleInitCommand(db); + Collection modules = command.call(); + assertNotNull(modules); + assertEquals(1, modules.size()); + assertEquals(path, modules.iterator().next()); + + generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertEquals("git://server/sub.git", generator.getConfigUrl()); + assertEquals(update, generator.getConfigUpdate()); + } + + @Test + public void resolveTwoLevelHigherRelativeUrl() throws IOException, + ConfigInvalidException { + final String path = addSubmoduleToIndex(); + + String base = "git://server/repo.git"; + FileBasedConfig config = db.getConfig(); + config.setString(ConfigConstants.CONFIG_REMOTE_SECTION, + Constants.DEFAULT_REMOTE_NAME, ConfigConstants.CONFIG_KEY_URL, + base); + config.save(); + + SubmoduleWalk generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertNull(generator.getConfigUrl()); + assertNull(generator.getConfigUpdate()); + + FileBasedConfig modulesConfig = new FileBasedConfig(new File( + db.getWorkTree(), Constants.DOT_GIT_MODULES), db.getFS()); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_PATH, path); + String url = "../../server2/sub.git"; + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, url); + String update = "rebase"; + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_UPDATE, update); + modulesConfig.save(); + + SubmoduleInitCommand command = new SubmoduleInitCommand(db); + Collection modules = command.call(); + assertNotNull(modules); + assertEquals(1, modules.size()); + assertEquals(path, modules.iterator().next()); + + generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertEquals("git://server2/sub.git", generator.getConfigUrl()); + assertEquals(update, generator.getConfigUpdate()); + } + + @Test + public void resolveWorkingDirectoryRelativeUrl() throws IOException, + ConfigInvalidException { + final String path = addSubmoduleToIndex(); + + String base = db.getWorkTree().getAbsolutePath(); + if (File.separatorChar == '\\') + base = base.replace('\\', '/'); + FileBasedConfig config = db.getConfig(); + config.setString(ConfigConstants.CONFIG_REMOTE_SECTION, + Constants.DEFAULT_REMOTE_NAME, ConfigConstants.CONFIG_KEY_URL, + null); + config.save(); + + SubmoduleWalk generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertNull(generator.getConfigUrl()); + assertNull(generator.getConfigUpdate()); + + FileBasedConfig modulesConfig = new FileBasedConfig(new File( + db.getWorkTree(), Constants.DOT_GIT_MODULES), db.getFS()); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_PATH, path); + String url = "./sub.git"; + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, url); + String update = "rebase"; + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_UPDATE, update); + modulesConfig.save(); + + SubmoduleInitCommand command = new SubmoduleInitCommand(db); + Collection modules = command.call(); + assertNotNull(modules); + assertEquals(1, modules.size()); + assertEquals(path, modules.iterator().next()); + + generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertEquals(base + "/sub.git", generator.getConfigUrl()); + assertEquals(update, generator.getConfigUpdate()); + } + + @Test + public void resolveInvalidParentUrl() throws IOException, + ConfigInvalidException { + final String path = addSubmoduleToIndex(); + + String base = "no_slash"; + FileBasedConfig config = db.getConfig(); + config.setString(ConfigConstants.CONFIG_REMOTE_SECTION, + Constants.DEFAULT_REMOTE_NAME, ConfigConstants.CONFIG_KEY_URL, + base); + config.save(); + + SubmoduleWalk generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertNull(generator.getConfigUrl()); + assertNull(generator.getConfigUpdate()); + + FileBasedConfig modulesConfig = new FileBasedConfig(new File( + db.getWorkTree(), Constants.DOT_GIT_MODULES), db.getFS()); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_PATH, path); + String url = "../sub.git"; + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, url); + modulesConfig.save(); + + try { + new SubmoduleInitCommand(db).call(); + fail("Exception not thrown"); + } catch (JGitInternalException e) { + assertTrue(e.getCause() instanceof IOException); + } + } + + private String addSubmoduleToIndex() throws IOException { + final ObjectId id = ObjectId + .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); + final String path = "sub"; + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id); + } + }); + editor.commit(); + return path; + } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleSyncTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleSyncTest.java index f61aad2f3..4df9077ba 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleSyncTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleSyncTest.java @@ -137,4 +137,73 @@ public void apply(DirCacheEntry ent) { ConfigConstants.CONFIG_REMOTE_SECTION, Constants.DEFAULT_REMOTE_NAME, ConfigConstants.CONFIG_KEY_URL)); } + + @Test + public void repositoryWithRelativeUriSubmodule() throws Exception { + writeTrashFile("file.txt", "content"); + Git git = Git.wrap(db); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("create file").call(); + + final ObjectId id = ObjectId + .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); + final String path = "sub"; + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id); + } + }); + editor.commit(); + + String base = "git://server/repo.git"; + FileBasedConfig config = db.getConfig(); + config.setString(ConfigConstants.CONFIG_REMOTE_SECTION, + Constants.DEFAULT_REMOTE_NAME, ConfigConstants.CONFIG_KEY_URL, + base); + config.save(); + + FileBasedConfig modulesConfig = new FileBasedConfig(new File( + db.getWorkTree(), Constants.DOT_GIT_MODULES), db.getFS()); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_PATH, path); + String current = "git://server/repo.git"; + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, current); + modulesConfig.save(); + + Repository subRepo = Git.cloneRepository() + .setURI(db.getDirectory().toURI().toString()) + .setDirectory(new File(db.getWorkTree(), path)).call() + .getRepository(); + assertNotNull(subRepo); + + SubmoduleWalk generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertNull(generator.getConfigUrl()); + assertEquals(current, generator.getModulesUrl()); + + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, "../sub.git"); + modulesConfig.save(); + + SubmoduleSyncCommand command = new SubmoduleSyncCommand(db); + Map synced = command.call(); + assertNotNull(synced); + assertEquals(1, synced.size()); + Entry module = synced.entrySet().iterator().next(); + assertEquals(path, module.getKey()); + assertEquals("git://server/sub.git", module.getValue()); + + generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertEquals("git://server/sub.git", generator.getConfigUrl()); + StoredConfig submoduleConfig = generator.getRepository().getConfig(); + assertEquals("git://server/sub.git", submoduleConfig.getString( + ConfigConstants.CONFIG_REMOTE_SECTION, + Constants.DEFAULT_REMOTE_NAME, ConfigConstants.CONFIG_KEY_URL)); + } } diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties index a80e6bcae..5b2280134 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties @@ -423,6 +423,7 @@ staleRevFlagsOn=Stale RevFlags on {0} startingReadStageWithoutWrittenRequestDataPendingIsNotSupported=Starting read stage without written request data pending is not supported statelessRPCRequiresOptionToBeEnabled=stateless RPC requires {0} to be enabled submoduleExists=Submodule ''{0}'' already exists in the index +submoduleParentRemoteUrlInvalid=Cannot remove segment from remote url ''{0}'' submodulesNotSupported=Submodules are not supported symlinkCannotBeWrittenAsTheLinkTarget=Symlink "{0}" cannot be written as the link target cannot be read from within Java. systemConfigFileInvalid=Systen wide config file {0} is invalid {1} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java index f4de48755..dbc3bcae6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java @@ -484,6 +484,7 @@ public static JGitText get() { /***/ public String statelessRPCRequiresOptionToBeEnabled; /***/ public String submoduleExists; /***/ public String submodulesNotSupported; + /***/ public String submoduleParentRemoteUrlInvalid; /***/ public String symlinkCannotBeWrittenAsTheLinkTarget; /***/ public String systemConfigFileInvalid; /***/ public String tagNameInvalid; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java index ac81ccb45..e1b293c43 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java @@ -148,12 +148,18 @@ public Repository call() throws JGitInternalException { throw new JGitInternalException(e.getMessage(), e); } + final String resolvedUri; + try { + resolvedUri = SubmoduleWalk.getSubmoduleRemoteUrl(repo, uri); + } catch (IOException e) { + throw new JGitInternalException(e.getMessage(), e); + } // Clone submodule repository File moduleDirectory = SubmoduleWalk.getSubmoduleDirectory(repo, path); CloneCommand clone = Git.cloneRepository(); configure(clone); clone.setDirectory(moduleDirectory); - clone.setURI(uri); + clone.setURI(resolvedUri); if (monitor != null) clone.setProgressMonitor(monitor); Repository subRepo = clone.call().getRepository(); @@ -161,7 +167,7 @@ public Repository call() throws JGitInternalException { // Save submodule URL to parent repository's config StoredConfig config = repo.getConfig(); config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, - ConfigConstants.CONFIG_KEY_URL, uri); + ConfigConstants.CONFIG_KEY_URL, resolvedUri); try { config.save(); } catch (IOException e) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java index ad8f02e47..fef13704e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java @@ -106,7 +106,7 @@ public Collection call() throws JGitInternalException { String path = generator.getPath(); // Copy 'url' and 'update' fields from .gitmodules to config // file - String url = generator.getModulesUrl(); + String url = generator.getRemoteUrl(); String update = generator.getModulesUpdate(); if (url != null) config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java index 43647a0c6..fd8ddc941 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java @@ -116,7 +116,7 @@ public Map call() throws JGitInternalException { Map synced = new HashMap(); StoredConfig config = repo.getConfig(); while (generator.next()) { - String remoteUrl = generator.getModulesUrl(); + String remoteUrl = generator.getRemoteUrl(); if (remoteUrl == null) continue; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java index ebe994826..78896f8a5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java @@ -44,7 +44,9 @@ import java.io.File; import java.io.IOException; +import java.text.MessageFormat; +import org.eclipse.jgit.JGitText; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.CorruptObjectException; @@ -176,6 +178,83 @@ public static File getSubmoduleGitDirectory(final Repository parent, return new File(getSubmoduleDirectory(parent, path), Constants.DOT_GIT); } + /** + * Resolve submodule repository URL. + *

+ * This handles relative URLs that are typically specified in the + * '.gitmodules' file by resolving them against the remote URL of the parent + * repository. + *

+ * Relative URLs will be resolved against the parent repository's working + * directory if the parent repository has no configured remote URL. + * + * @param parent + * parent repository + * @param url + * absolute or relative URL of the submodule repository + * @return resolved URL + * @throws IOException + */ + public static String getSubmoduleRemoteUrl(final Repository parent, + final String url) throws IOException { + if (!url.startsWith("./") && !url.startsWith("../")) + return url; + + String remoteName = null; + // Look up remote URL associated wit HEAD ref + Ref ref = parent.getRef(Constants.HEAD); + if (ref != null) { + if (ref.isSymbolic()) + ref = ref.getLeaf(); + remoteName = parent.getConfig().getString( + ConfigConstants.CONFIG_BRANCH_SECTION, + Repository.shortenRefName(ref.getName()), + ConfigConstants.CONFIG_KEY_REMOTE); + } + + // Fall back to 'origin' if current HEAD ref has no remote URL + if (remoteName == null) + remoteName = Constants.DEFAULT_REMOTE_NAME; + + String remoteUrl = parent.getConfig().getString( + ConfigConstants.CONFIG_REMOTE_SECTION, remoteName, + ConfigConstants.CONFIG_KEY_URL); + + // Fall back to parent repository's working directory if no remote URL + if (remoteUrl == null) { + remoteUrl = parent.getWorkTree().getAbsolutePath(); + // Normalize slashes to '/' + if ('\\' == File.separatorChar) + remoteUrl = remoteUrl.replace('\\', '/'); + } + + // Remove trailing '/' + if (remoteUrl.charAt(remoteUrl.length() - 1) == '/') + remoteUrl = remoteUrl.substring(0, remoteUrl.length() - 1); + + char separator = '/'; + String submoduleUrl = url; + while (submoduleUrl.length() > 0) { + if (submoduleUrl.startsWith("./")) + submoduleUrl = submoduleUrl.substring(2); + else if (submoduleUrl.startsWith("../")) { + int lastSeparator = remoteUrl.lastIndexOf('/'); + if (lastSeparator < 1) { + lastSeparator = remoteUrl.lastIndexOf(':'); + separator = ':'; + } + if (lastSeparator < 1) + throw new IOException(MessageFormat.format( + JGitText.get().submoduleParentRemoteUrlInvalid, + remoteUrl)); + remoteUrl = remoteUrl.substring(0, lastSeparator); + submoduleUrl = submoduleUrl.substring(3); + } else + break; + } + return remoteUrl + separator + submoduleUrl; + } + private final Repository repository; private final TreeWalk walk; @@ -432,4 +511,19 @@ public String getHeadRef() throws IOException { Ref head = subRepo.getRef(Constants.HEAD); return head != null ? head.getLeaf().getName() : null; } + + /** + * Get the resolved remote URL for the current submodule. + *

+ * This method resolves the value of {@link #getModulesUrl()} to an absolute + * URL + * + * @return resolved remote URL + * @throws IOException + * @throws ConfigInvalidException + */ + public String getRemoteUrl() throws IOException, ConfigInvalidException { + String url = getModulesUrl(); + return url != null ? getSubmoduleRemoteUrl(repository, url) : null; + } }