Browse Source

Use project names instead of paths for the submodule name

Two submodules at the same path on different branches need not represent
the same repository, and two submodules at different paths can represent
the same one.

The C Git implementation uses the submodule name to internally manage
the submodule repositories under .git/modules. When a submodule
represents different repositories in different branches, it makes a
conflict inside .git/modules.

The current RepoCommand implementation uses submodule paths as the
submodule names. When the manifest file mounts different repositories to
the same path in different branches, this makes a situation described
above. To solve this issue, we can use the project name instead of
the path as the submodule name.

On the other hand, since repo v1.12.8~3^2 (repo: Support multiple
branches for the same project., 2013-10-11), a manifest file can mount
the same project to different paths. If we naively use the project
name as the submodule name, it makes a conflict in .git/modules, too.

This patch uses the project name as the submodule name basically, but
when the same project is mounted to different paths, it uses the project
name and path as the submodule name.

Change-Id: I09dc7d62ba59016fe28852d3139a56ef7ef49b8f
Signed-off-by: Masaya Suzuki <masayasuzuki@google.com>
Reported-by: JP Sugarbroad <jpsugar@google.com>
stable-5.1
Masaya Suzuki 6 years ago
parent
commit
6658f36768
  1. 94
      org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
  2. 91
      org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java

94
org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java

@ -268,7 +268,8 @@ public class RepoCommandTest extends RepositoryTestCase {
.getCachedBytes(Integer.MAX_VALUE); .getCachedBytes(Integer.MAX_VALUE);
Config base = new Config(); Config base = new Config();
BlobBasedConfig cfg = new BlobBasedConfig(base, bytes); BlobBasedConfig cfg = new BlobBasedConfig(base, bytes);
String subUrl = cfg.getString("submodule", "base", "url"); String subUrl = cfg.getString("submodule", "platform/base",
"url");
assertEquals(subUrl, "../base"); assertEquals(subUrl, "../base");
} }
} }
@ -301,7 +302,8 @@ public class RepoCommandTest extends RepositoryTestCase {
.getCachedBytes(Integer.MAX_VALUE); .getCachedBytes(Integer.MAX_VALUE);
Config base = new Config(); Config base = new Config();
BlobBasedConfig cfg = new BlobBasedConfig(base, bytes); BlobBasedConfig cfg = new BlobBasedConfig(base, bytes);
String subUrl = cfg.getString("submodule", "base", "url"); String subUrl = cfg.getString("submodule", "platform/base",
"url");
assertEquals(subUrl, "https://host.com/platform/base"); assertEquals(subUrl, "https://host.com/platform/base");
} }
} }
@ -387,8 +389,8 @@ public class RepoCommandTest extends RepositoryTestCase {
.getCachedBytes(Integer.MAX_VALUE); .getCachedBytes(Integer.MAX_VALUE);
Config base = new Config(); Config base = new Config();
BlobBasedConfig cfg = new BlobBasedConfig(base, bytes); BlobBasedConfig cfg = new BlobBasedConfig(base, bytes);
String subUrl = cfg.getString("submodule", "src", String subUrl = cfg.getString("submodule",
"url"); "chromium/src", "url");
assertEquals( assertEquals(
"https://chromium.googlesource.com/chromium/src", "https://chromium.googlesource.com/chromium/src",
subUrl); subUrl);
@ -443,8 +445,8 @@ public class RepoCommandTest extends RepositoryTestCase {
.getCachedBytes(Integer.MAX_VALUE); .getCachedBytes(Integer.MAX_VALUE);
Config base = new Config(); Config base = new Config();
BlobBasedConfig cfg = new BlobBasedConfig(base, bytes); BlobBasedConfig cfg = new BlobBasedConfig(base, bytes);
String subUrl = cfg.getString("submodule", "src", String subUrl = cfg.getString("submodule",
"url"); "chromium/src", "url");
assertEquals("../chromium/src", subUrl); assertEquals("../chromium/src", subUrl);
} }
fetchSlash = !fetchSlash; fetchSlash = !fetchSlash;
@ -613,7 +615,7 @@ public class RepoCommandTest extends RepositoryTestCase {
String content = reader.readLine(); String content = reader.readLine();
assertEquals( assertEquals(
"The first line of .gitmodules file should be as expected", "The first line of .gitmodules file should be as expected",
"[submodule \"foo\"]", content); "[submodule \"" + defaultUri + "\"]", content);
} }
// The gitlink should be the same as remote head sha1 // The gitlink should be the same as remote head sha1
String gitlink = localDb.resolve(Constants.HEAD + ":foo").name(); String gitlink = localDb.resolve(Constants.HEAD + ":foo").name();
@ -801,9 +803,9 @@ public class RepoCommandTest extends RepositoryTestCase {
.append("<manifest>") .append("<manifest>")
.append("<remote name=\"remote1\" fetch=\".\" />") .append("<remote name=\"remote1\" fetch=\".\" />")
.append("<default revision=\"master\" remote=\"remote1\" />") .append("<default revision=\"master\" remote=\"remote1\" />")
.append("<project path=\"bar\" name=\"").append(defaultUri) .append("<project path=\"bar\" name=\"").append(notDefaultUri)
.append("\" revision=\"").append(BRANCH).append("\" >") .append("\" >")
.append("<copyfile src=\"hello.txt\" dest=\"Hello.txt\" />") .append("<copyfile src=\"world.txt\" dest=\"World.txt\" />")
.append("</project>").append("</manifest>"); .append("</project>").append("</manifest>");
JGitTestUtil.writeTrashFile(tempDb, "new.xml", xmlContent.toString()); JGitTestUtil.writeTrashFile(tempDb, "new.xml", xmlContent.toString());
command = new RepoCommand(remoteDb); command = new RepoCommand(remoteDb);
@ -819,8 +821,8 @@ public class RepoCommandTest extends RepositoryTestCase {
File hello = new File(localDb.getWorkTree(), "Hello"); File hello = new File(localDb.getWorkTree(), "Hello");
assertFalse("The Hello file shouldn't exist", hello.exists()); assertFalse("The Hello file shouldn't exist", hello.exists());
// The Hello.txt file should exist // The Hello.txt file should exist
File hellotxt = new File(localDb.getWorkTree(), "Hello.txt"); File hellotxt = new File(localDb.getWorkTree(), "World.txt");
assertTrue("The Hello.txt file should exist", hellotxt.exists()); assertTrue("The World.txt file should exist", hellotxt.exists());
dotmodules = new File(localDb.getWorkTree(), dotmodules = new File(localDb.getWorkTree(),
Constants.DOT_GIT_MODULES); Constants.DOT_GIT_MODULES);
} }
@ -835,9 +837,9 @@ public class RepoCommandTest extends RepositoryTestCase {
String line = reader.readLine(); String line = reader.readLine();
if (line == null) if (line == null)
break; break;
if (line.contains("submodule \"foo\"")) if (line.contains("submodule \"" + defaultUri + "\""))
foo = true; foo = true;
if (line.contains("submodule \"bar\"")) if (line.contains("submodule \"" + notDefaultUri + "\""))
bar = true; bar = true;
} }
assertTrue("The bar submodule should exist", bar); assertTrue("The bar submodule should exist", bar);
@ -876,9 +878,7 @@ public class RepoCommandTest extends RepositoryTestCase {
Constants.DOT_GIT_MODULES); Constants.DOT_GIT_MODULES);
} }
// The .gitmodules file should have 'submodule "foo"' and shouldn't // Check .gitmodules file
// have
// 'submodule "foo/bar"' lines.
try (BufferedReader reader = new BufferedReader( try (BufferedReader reader = new BufferedReader(
new FileReader(dotmodules))) { new FileReader(dotmodules))) {
boolean foo = false; boolean foo = false;
@ -888,16 +888,17 @@ public class RepoCommandTest extends RepositoryTestCase {
String line = reader.readLine(); String line = reader.readLine();
if (line == null) if (line == null)
break; break;
if (line.contains("submodule \"foo\"")) if (line.contains("submodule \"" + defaultUri + "\""))
foo = true; foo = true;
if (line.contains("submodule \"foo/bar\"")) if (line.contains("submodule \"" + groupBUri + "\""))
foobar = true; foobar = true;
if (line.contains("submodule \"a\"")) if (line.contains("submodule \"" + groupAUri + "\""))
a = true; a = true;
} }
assertTrue("The foo submodule should exist", foo); assertTrue("The " + defaultUri + " submodule should exist", foo);
assertFalse("The foo/bar submodule shouldn't exist", foobar); assertFalse("The " + groupBUri + " submodule shouldn't exist",
assertTrue("The a submodule should exist", a); foobar);
assertTrue("The " + groupAUri + " submodule should exist", a);
} }
} }
@ -1033,11 +1034,11 @@ public class RepoCommandTest extends RepositoryTestCase {
assertEquals( assertEquals(
"Recording remote branches should work for short branch descriptions", "Recording remote branches should work for short branch descriptions",
"master", "master",
c.getString("submodule", "with-branch", "branch")); c.getString("submodule", notDefaultUri, "branch"));
assertEquals( assertEquals(
"Recording remote branches should work for full ref specs", "Recording remote branches should work for full ref specs",
"refs/heads/master", "refs/heads/master",
c.getString("submodule", "with-long-branch", "branch")); c.getString("submodule", defaultUri, "branch"));
} }
} }
@ -1095,7 +1096,7 @@ public class RepoCommandTest extends RepositoryTestCase {
.append("<project path=\"shallow-please\" ").append("name=\"") .append("<project path=\"shallow-please\" ").append("name=\"")
.append(defaultUri).append("\" ").append("clone-depth=\"1\" />") .append(defaultUri).append("\" ").append("clone-depth=\"1\" />")
.append("<project path=\"non-shallow\" ").append("name=\"") .append("<project path=\"non-shallow\" ").append("name=\"")
.append(defaultUri).append("\" />").append("</manifest>"); .append(notDefaultUri).append("\" />").append("</manifest>");
JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", JGitTestUtil.writeTrashFile(tempDb, "manifest.xml",
xmlContent.toString()); xmlContent.toString());
@ -1115,9 +1116,9 @@ public class RepoCommandTest extends RepositoryTestCase {
FileBasedConfig c = new FileBasedConfig(gitmodules, FS.DETECTED); FileBasedConfig c = new FileBasedConfig(gitmodules, FS.DETECTED);
c.load(); c.load();
assertEquals("Recording shallow configuration should work", "true", assertEquals("Recording shallow configuration should work", "true",
c.getString("submodule", "shallow-please", "shallow")); c.getString("submodule", defaultUri, "shallow"));
assertNull("Recording non shallow configuration should work", assertNull("Recording non shallow configuration should work",
c.getString("submodule", "non-shallow", "shallow")); c.getString("submodule", notDefaultUri, "shallow"));
} }
} }
@ -1176,6 +1177,43 @@ public class RepoCommandTest extends RepositoryTestCase {
} }
} }
@Test
public void testTwoPathUseTheSameName() throws Exception {
Repository remoteDb = createBareRepository();
Repository tempDb = createWorkRepository();
StringBuilder xmlContent = new StringBuilder();
xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
.append("<manifest>")
.append("<remote name=\"remote1\" fetch=\".\" />")
.append("<default revision=\"master\" remote=\"remote1\" />")
.append("<project path=\"path1\" ").append("name=\"")
.append(defaultUri).append("\" />")
.append("<project path=\"path2\" ").append("name=\"")
.append(defaultUri).append("\" />").append("</manifest>");
JGitTestUtil.writeTrashFile(tempDb, "manifest.xml",
xmlContent.toString());
RepoCommand command = new RepoCommand(remoteDb);
command.setPath(
tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml")
.setURI(rootUri).setRecommendShallow(true).call();
File directory = createTempDirectory("testBareRepo");
try (Repository localDb = Git.cloneRepository().setDirectory(directory)
.setURI(remoteDb.getDirectory().toURI().toString()).call()
.getRepository();) {
File gitmodules = new File(localDb.getWorkTree(), ".gitmodules");
assertTrue("The .gitmodules file should exist",
gitmodules.exists());
FileBasedConfig c = new FileBasedConfig(gitmodules, FS.DETECTED);
c.load();
assertEquals("A module should exist for path1", "path1",
c.getString("submodule", defaultUri + "/path1", "path"));
assertEquals("A module should exist for path2", "path2",
c.getString("submodule", defaultUri + "/path2", "path"));
}
}
private void resolveRelativeUris() { private void resolveRelativeUris() {
// Find the longest common prefix ends with "/" as rootUri. // Find the longest common prefix ends with "/" as rootUri.
defaultUri = defaultDb.getDirectory().toURI().toString(); defaultUri = defaultDb.getDirectory().toURI().toString();

91
org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java

@ -52,10 +52,10 @@ import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner; import java.util.StringJoiner;
import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.annotations.Nullable;
@ -124,7 +124,6 @@ public class RepoCommand extends GitCommand<RevCommit> {
private IncludedFileReader includedReader; private IncludedFileReader includedReader;
private boolean ignoreRemoteFailures = false; private boolean ignoreRemoteFailures = false;
private List<RepoProject> bareProjects;
private ProgressMonitor monitor; private ProgressMonitor monitor;
/** /**
@ -519,37 +518,33 @@ public class RepoCommand extends GitCommand<RevCommit> {
} }
if (repo.isBare()) { if (repo.isBare()) {
bareProjects = new ArrayList<>();
if (author == null) if (author == null)
author = new PersonIdent(repo); author = new PersonIdent(repo);
if (callback == null) if (callback == null)
callback = new DefaultRemoteReader(); callback = new DefaultRemoteReader();
for (RepoProject proj : filteredProjects) { List<RepoProject> renamedProjects = renameProjects(filteredProjects);
addSubmoduleBare(proj.getUrl(), proj.getPath(),
proj.getRevision(), proj.getCopyFiles(),
proj.getLinkFiles(), proj.getGroups(),
proj.getRecommendShallow());
}
DirCache index = DirCache.newInCore(); DirCache index = DirCache.newInCore();
DirCacheBuilder builder = index.builder(); DirCacheBuilder builder = index.builder();
ObjectInserter inserter = repo.newObjectInserter(); ObjectInserter inserter = repo.newObjectInserter();
try (RevWalk rw = new RevWalk(repo)) { try (RevWalk rw = new RevWalk(repo)) {
Config cfg = new Config(); Config cfg = new Config();
StringBuilder attributes = new StringBuilder(); StringBuilder attributes = new StringBuilder();
for (RepoProject proj : bareProjects) { for (RepoProject proj : renamedProjects) {
String name = proj.getName();
String path = proj.getPath(); String path = proj.getPath();
String nameUri = proj.getName(); String url = proj.getUrl();
ObjectId objectId; ObjectId objectId;
if (ObjectId.isId(proj.getRevision())) { if (ObjectId.isId(proj.getRevision())) {
objectId = ObjectId.fromString(proj.getRevision()); objectId = ObjectId.fromString(proj.getRevision());
} else { } else {
objectId = callback.sha1(nameUri, proj.getRevision()); objectId = callback.sha1(url, proj.getRevision());
if (objectId == null && !ignoreRemoteFailures) { if (objectId == null && !ignoreRemoteFailures) {
throw new RemoteUnavailableException(nameUri); throw new RemoteUnavailableException(url);
} }
if (recordRemoteBranch) { if (recordRemoteBranch) {
// can be branch or tag // can be branch or tag
cfg.setString("submodule", path, "branch", //$NON-NLS-1$ //$NON-NLS-2$ cfg.setString("submodule", name, "branch", //$NON-NLS-1$ //$NON-NLS-2$
proj.getRevision()); proj.getRevision());
} }
@ -559,7 +554,7 @@ public class RepoCommand extends GitCommand<RevCommit> {
// depth in the 'clone-depth' field, while // depth in the 'clone-depth' field, while
// git core only uses a binary 'shallow = true/false' // git core only uses a binary 'shallow = true/false'
// hint, we'll map any depth to 'shallow = true' // hint, we'll map any depth to 'shallow = true'
cfg.setBoolean("submodule", path, "shallow", //$NON-NLS-1$ //$NON-NLS-2$ cfg.setBoolean("submodule", name, "shallow", //$NON-NLS-1$ //$NON-NLS-2$
true); true);
} }
} }
@ -575,12 +570,13 @@ public class RepoCommand extends GitCommand<RevCommit> {
attributes.append(rec.toString()); attributes.append(rec.toString());
} }
URI submodUrl = URI.create(nameUri); URI submodUrl = URI.create(url);
if (targetUri != null) { if (targetUri != null) {
submodUrl = relativize(targetUri, submodUrl); submodUrl = relativize(targetUri, submodUrl);
} }
cfg.setString("submodule", path, "path", path); //$NON-NLS-1$ //$NON-NLS-2$ cfg.setString("submodule", name, "path", path); //$NON-NLS-1$ //$NON-NLS-2$
cfg.setString("submodule", path, "url", submodUrl.toString()); //$NON-NLS-1$ //$NON-NLS-2$ cfg.setString("submodule", name, "url", //$NON-NLS-1$ //$NON-NLS-2$
submodUrl.toString());
// create gitlink // create gitlink
if (objectId != null) { if (objectId != null) {
@ -591,7 +587,7 @@ public class RepoCommand extends GitCommand<RevCommit> {
for (CopyFile copyfile : proj.getCopyFiles()) { for (CopyFile copyfile : proj.getCopyFiles()) {
byte[] src = callback.readFile( byte[] src = callback.readFile(
nameUri, proj.getRevision(), copyfile.src); url, proj.getRevision(), copyfile.src);
objectId = inserter.insert(Constants.OBJ_BLOB, src); objectId = inserter.insert(Constants.OBJ_BLOB, src);
dcEntry = new DirCacheEntry(copyfile.dest); dcEntry = new DirCacheEntry(copyfile.dest);
dcEntry.setObjectId(objectId); dcEntry.setObjectId(objectId);
@ -691,7 +687,7 @@ public class RepoCommand extends GitCommand<RevCommit> {
} else { } else {
try (Git git = new Git(repo)) { try (Git git = new Git(repo)) {
for (RepoProject proj : filteredProjects) { for (RepoProject proj : filteredProjects) {
addSubmodule(proj.getUrl(), proj.getPath(), addSubmodule(proj.getName(), proj.getUrl(), proj.getPath(),
proj.getRevision(), proj.getCopyFiles(), proj.getRevision(), proj.getCopyFiles(),
proj.getLinkFiles(), git); proj.getLinkFiles(), git);
} }
@ -703,9 +699,9 @@ public class RepoCommand extends GitCommand<RevCommit> {
} }
} }
private void addSubmodule(String url, String path, String revision, private void addSubmodule(String name, String url, String path,
List<CopyFile> copyfiles, List<LinkFile> linkfiles, Git git) String revision, List<CopyFile> copyfiles, List<LinkFile> linkfiles,
throws GitAPIException, IOException { Git git) throws GitAPIException, IOException {
assert (!repo.isBare()); assert (!repo.isBare());
assert (git != null); assert (git != null);
if (!linkfiles.isEmpty()) { if (!linkfiles.isEmpty()) {
@ -713,7 +709,8 @@ public class RepoCommand extends GitCommand<RevCommit> {
JGitText.get().nonBareLinkFilesNotSupported); JGitText.get().nonBareLinkFilesNotSupported);
} }
SubmoduleAddCommand add = git.submoduleAdd().setPath(path).setURI(url); SubmoduleAddCommand add = git.submoduleAdd().setName(name).setPath(path)
.setURI(url);
if (monitor != null) if (monitor != null)
add.setProgressMonitor(monitor); add.setProgressMonitor(monitor);
@ -731,16 +728,42 @@ public class RepoCommand extends GitCommand<RevCommit> {
} }
} }
private void addSubmoduleBare(String url, String path, String revision, /**
List<CopyFile> copyfiles, List<LinkFile> linkfiles, * Rename the projects if there's a conflict when converted to submodules.
Set<String> groups, String recommendShallow) { *
assert (repo.isBare()); * @param projects
assert (bareProjects != null); * parsed projects
RepoProject proj = new RepoProject(url, path, revision, null, groups, * @return projects that are renamed if necessary
recommendShallow); */
proj.addCopyFiles(copyfiles); private List<RepoProject> renameProjects(List<RepoProject> projects) {
proj.addLinkFiles(linkfiles); Map<String, List<RepoProject>> m = new HashMap<>();
bareProjects.add(proj); for (RepoProject proj : projects) {
List<RepoProject> l = m.get(proj.getName());
if (l == null) {
l = new ArrayList<>();
m.put(proj.getName(), l);
}
l.add(proj);
}
List<RepoProject> ret = new ArrayList<>();
for (List<RepoProject> ps : m.values()) {
boolean nameConflict = ps.size() != 1;
for (RepoProject proj : ps) {
String name = proj.getName();
if (nameConflict) {
name += SLASH + proj.getPath();
}
RepoProject p = new RepoProject(name,
proj.getPath(), proj.getRevision(), null,
proj.getGroups(), proj.getRecommendShallow());
p.setUrl(proj.getUrl());
p.addCopyFiles(proj.getCopyFiles());
p.addLinkFiles(proj.getLinkFiles());
ret.add(p);
}
}
return ret;
} }
/* /*

Loading…
Cancel
Save