Browse Source

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
tags/v1.3.0.201202121842-rc4
Kevin Sawicki 12 years ago
parent
commit
b57845c0cc

+ 43
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java View File

@@ -47,6 +47,7 @@ import static org.junit.Assert.assertNotNull;
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.DirCache;
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 class SubmoduleAddTest extends RepositoryTestCase {
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));
}
}

+ 219
- 13
org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleInitTest.java View File

@@ -46,12 +46,14 @@ import static org.junit.Assert.assertEquals;
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 class SubmoduleInitTest extends RepositoryTestCase {
@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 class SubmoduleInitTest extends RepositoryTestCase {
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<String> 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<String> 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<String> 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<String> 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;
}
}

+ 69
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleSyncTest.java View File

@@ -137,4 +137,73 @@ public class SubmoduleSyncTest extends RepositoryTestCase {
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<String, String> synced = command.call();
assertNotNull(synced);
assertEquals(1, synced.size());
Entry<String, String> 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));
}
}

+ 1
- 0
org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties View File

@@ -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}

+ 1
- 0
org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java View File

@@ -484,6 +484,7 @@ public class JGitText extends TranslationBundle {
/***/ public String statelessRPCRequiresOptionToBeEnabled;
/***/ public String submoduleExists;
/***/ public String submodulesNotSupported;
/***/ public String submoduleParentRemoteUrlInvalid;
/***/ public String symlinkCannotBeWrittenAsTheLinkTarget;
/***/ public String systemConfigFileInvalid;
/***/ public String tagNameInvalid;

+ 8
- 2
org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java View File

@@ -148,12 +148,18 @@ public class SubmoduleAddCommand extends
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 class SubmoduleAddCommand extends
// 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) {

+ 1
- 1
org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java View File

@@ -106,7 +106,7 @@ public class SubmoduleInitCommand extends GitCommand<Collection<String>> {
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,

+ 1
- 1
org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java View File

@@ -116,7 +116,7 @@ public class SubmoduleSyncCommand extends GitCommand<Map<String, String>> {
Map<String, String> synced = new HashMap<String, String>();
StoredConfig config = repo.getConfig();
while (generator.next()) {
String remoteUrl = generator.getModulesUrl();
String remoteUrl = generator.getRemoteUrl();
if (remoteUrl == null)
continue;


+ 94
- 0
org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java View File

@@ -44,7 +44,9 @@ package org.eclipse.jgit.submodule;

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 class SubmoduleWalk {
return new File(getSubmoduleDirectory(parent, path), Constants.DOT_GIT);
}

/**
* Resolve submodule repository URL.
* <p>
* This handles relative URLs that are typically specified in the
* '.gitmodules' file by resolving them against the remote URL of the parent
* repository.
* <p>
* 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 class SubmoduleWalk {
Ref head = subRepo.getRef(Constants.HEAD);
return head != null ? head.getLeaf().getName() : null;
}

/**
* Get the resolved remote URL for the current submodule.
* <p>
* 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;
}
}

Loading…
Cancel
Save