Add support for jiri manifest Change-Id: I8b1a2c63e472b15253939df15617ef362919a064
diff --git a/src/main/java/com/googlesource/gerrit/plugins/supermanifest/ConfigEntry.java b/src/main/java/com/googlesource/gerrit/plugins/supermanifest/ConfigEntry.java index ddb2a4f..2d4e5ec 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/supermanifest/ConfigEntry.java +++ b/src/main/java/com/googlesource/gerrit/plugins/supermanifest/ConfigEntry.java
@@ -25,10 +25,6 @@ import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Repository; -enum ToolType { - Repo -} - class ConfigEntry { public static final String SECTION_NAME = "superproject"; @@ -78,6 +74,9 @@ case "repo": this.toolType = ToolType.Repo; break; + case "jiri": + this.toolType = ToolType.Jiri; + break; default: throw new ConfigInvalidException( String.format("entry %s has invalid toolType: %s", name, toolType)); @@ -134,7 +133,7 @@ @Override public String toString() { - return String.format("%s => %s", src(), dest()); + return String.format("%s (%s) => %s", src(), toolType, dest()); } @Override @@ -191,4 +190,9 @@ public String getDestBranch() { return destBranch; } + + enum ToolType { + Repo, + Jiri + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/supermanifest/JiriManifest.java b/src/main/java/com/googlesource/gerrit/plugins/supermanifest/JiriManifest.java new file mode 100644 index 0000000..3c68ba1 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/supermanifest/JiriManifest.java
@@ -0,0 +1,97 @@ +// Copyright (C) 2017 Google Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.supermanifest; + +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** Refer https://fuchsia.googlesource.com/jiri/+/HEAD/manifest.md for manifest specification. */ +@XmlRootElement(name = "manifest") +class JiriManifest { + @XmlElement public Imports imports; + + @XmlElement public JiriProjects projects; + + public JiriManifest() { + imports = new Imports(); + projects = new JiriProjects(); + } + + @Override + public String toString() { + StringBuffer buf = new StringBuffer("\nmanifest:\n"); + buf.append(StringUtil.addTab(imports.toString())); + buf.append(StringUtil.addTab(projects.toString())); + return buf.toString(); + } + + static class LocalImport { + @XmlAttribute(required = true) + private String file; + + @Override + public String toString() { + return file; + } + + /** @return the file */ + public String getFile() { + return file; + } + } + + static class Import { + // Don't need all fields, as we don't use it + } + + static class Imports { + @XmlElement(name = "import") + private Import[] imports; + + @XmlElement(name = "localimport") + private LocalImport[] localImports; + + public Imports() { + this.imports = new Import[0]; + this.localImports = new LocalImport[0]; + } + + @Override + public String toString() { + StringBuffer buf = new StringBuffer(""); + if (imports.length > 0) { + buf.append("import: " + imports.length + "\n"); + } + if (localImports.length > 0) { + buf.append("localImports:\n"); + for (LocalImport l : localImports) { + buf.append(StringUtil.addTab(l.toString())); + } + } + return buf.toString(); + } + + /** @return the imports */ + public Import[] getImports() { + return imports; + } + + /** @return the localImports */ + public LocalImport[] getLocalImports() { + return localImports; + } + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/supermanifest/JiriManifestParser.java b/src/main/java/com/googlesource/gerrit/plugins/supermanifest/JiriManifestParser.java new file mode 100644 index 0000000..411e5b4 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/supermanifest/JiriManifestParser.java
@@ -0,0 +1,83 @@ +// Copyright (C) 2017 Google Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.supermanifest; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Queue; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import org.eclipse.jgit.lib.Repository; + +class JiriManifestParser { + public static JiriProjects GetProjects(Repository repo, String ref, String manifest) + throws Exception { + Queue<String> q = new LinkedList<>(); + q.add(manifest); + HashSet<String> processedFiles = new HashSet<>(); + HashMap<String, JiriProjects.Project> projectMap = new HashMap<>(); + + while (q.size() != 0) { + String file = q.remove(); + if (processedFiles.contains(file)) { + continue; + } + processedFiles.add(file); + JiriManifest m = parseManifest(repo, ref, file); + if (m.imports.getImports().length != 0) { + throw new Exception( + String.format("Manifest %s contains remote imports which are not supported", file)); + } + + for (JiriProjects.Project project : m.projects.getProjects()) { + project.fillDefault(); + if (projectMap.containsKey(project.Key())) { + if (!projectMap.get(project.Key()).equals(project)) + throw new Exception( + String.format( + "Duplicate conflicting project %s in manifest %s\n%s\n%s", + project.Key(), + file, + project.toString(), + projectMap.get(project.Key()).toString())); + } else { + projectMap.put(project.Key(), project); + } + } + + URI parentURI = new URI(file); + for (JiriManifest.LocalImport l : m.imports.getLocalImports()) { + q.add(parentURI.resolve(l.getFile()).getPath()); + } + } + return new JiriProjects(projectMap.values().toArray(new JiriProjects.Project[0])); + } + + private static JiriManifest parseManifest(Repository repo, String ref, String file) + throws JAXBException, IOException { + byte b[] = Utils.readBlob(repo, ref + ":" + file); + JAXBContext jc = JAXBContext.newInstance(JiriManifest.class); + Unmarshaller unmarshaller = jc.createUnmarshaller(); + InputStream is = new ByteArrayInputStream(b); + JiriManifest manifest = (JiriManifest) unmarshaller.unmarshal(is); + return manifest; + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/supermanifest/JiriProjects.java b/src/main/java/com/googlesource/gerrit/plugins/supermanifest/JiriProjects.java new file mode 100644 index 0000000..663a6af --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/supermanifest/JiriProjects.java
@@ -0,0 +1,187 @@ +// Copyright (C) 2017 Google Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.supermanifest; + +import java.util.Arrays; +import java.util.Comparator; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; + +class JiriProjects { + @XmlElement(name = "project") + private Project[] projects; + + public JiriProjects() { + projects = new Project[0]; + } + + /** @return the projects */ + public Project[] getProjects() { + return projects; + } + + public void sortByPath() { + Arrays.sort(projects, new SortbyPath()); + } + + public String toSubmodules() { + StringBuffer buf = new StringBuffer(); + sortByPath(); + for (Project p : projects) { + buf.append(p.toSubmodules()); + buf.append("\n"); + } + return buf.toString(); + } + + public JiriProjects(Project[] projects) { + this.projects = projects; + } + + @Override + public String toString() { + StringBuffer buf = new StringBuffer(); + if (projects.length > 0) { + buf.append("projects:\n"); + for (Project p : projects) { + buf.append(StringUtil.addTab(p.toString())); + } + } + return buf.toString(); + } + + static class Project { + @XmlAttribute(required = true) + private String name; + + @XmlAttribute(required = true) + private String path; + + @XmlAttribute(required = true) + private String remote; + + @XmlAttribute private String remotebranch; + + @XmlAttribute private String revision; + + @XmlAttribute private int historydepth; + + /** @return the name */ + public String getName() { + return name; + } + + /** @return the path */ + public String getPath() { + return path; + } + + /** @return the remote */ + public String getRemote() { + return remote; + } + + /** @return the historydepth */ + public int getHistorydepth() { + return historydepth; + } + + public String getRef() { + if (!revision.isEmpty()) { + return revision; + } + return remotebranch; + } + + public Project() { + name = ""; + path = ""; + remote = ""; + remotebranch = ""; + revision = ""; + historydepth = 0; + } + + public void fillDefault() { + if (remotebranch.isEmpty()) { + remotebranch = "master"; + } + } + + @Override + public String toString() { + return String.format( + "project:\n\tname: %s\n\tpath: %s\n\tremote: %s\n\tremotebranch: %s\n\trevision: %s", + name, path, remote, remotebranch, revision); + } + + @Override + public boolean equals(Object obj) { + Project p = (Project) obj; + + if (!name.equals(p.name)) { + return false; + } + if (!path.equals(p.path)) { + return false; + } + if (!remote.equals(p.remote)) { + return false; + } + if (!remotebranch.equals(p.remotebranch)) { + return false; + } + if (!revision.equals(p.revision)) { + return false; + } + + return true; + } + + public String toSubmodules() { + StringBuffer buf = new StringBuffer(String.format("[submodule \"%s\"]", name)); + buf.append("\n\tpath = " + path); + buf.append("\n\turl = " + remote); + String branch = ""; + if (!remotebranch.isEmpty()) { + branch = remotebranch; + } + + if (!revision.isEmpty()) { + branch = revision; + } + + if (!branch.isEmpty()) { + buf.append("\n\tbranch = " + branch); + } + + buf.append("\n"); + return buf.toString(); + } + + public String Key() { + return name + "=" + remote; + } + } + + static class SortbyPath implements Comparator<Project> { + @Override + public int compare(Project a, Project b) { + String p1 = StringUtil.stripAndaddCharsAtEnd(a.getPath(), "/"); + String p2 = StringUtil.stripAndaddCharsAtEnd(b.getPath(), "/"); + return p1.compareTo(p2); + } + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/supermanifest/JiriUpdater.java b/src/main/java/com/googlesource/gerrit/plugins/supermanifest/JiriUpdater.java new file mode 100644 index 0000000..21fcbef --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/supermanifest/JiriUpdater.java
@@ -0,0 +1,168 @@ +package com.googlesource.gerrit.plugins.supermanifest; + +import static com.google.gerrit.reviewdb.client.RefNames.REFS_HEADS; +import static com.google.gerrit.reviewdb.client.RefNames.REFS_TAGS; + +import com.googlesource.gerrit.plugins.supermanifest.SuperManifestRefUpdatedListener.GerritRemoteReader; +import java.net.URI; +import java.text.MessageFormat; +import java.util.Map; +import org.eclipse.jgit.api.LsRemoteCommand; +import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.gitrepo.internal.RepoText; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.RefUpdate.Result; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevWalk; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class JiriUpdater implements SubModuleUpdater { + PersonIdent serverIdent; + URI canonicalWebUrl; + + public JiriUpdater(PersonIdent serverIdent, URI canonicalWebUrl) { + this.serverIdent = serverIdent; + this.canonicalWebUrl = canonicalWebUrl; + } + + private static final Logger log = LoggerFactory.getLogger(SuperManifestRefUpdatedListener.class); + + private void warn(String formatStr, Object... args) { + // The docs claim that log.warn() uses format strings, but it doesn't seem to work, so we do it + // explicitly. + log.warn(canonicalWebUrl + " : " + String.format(formatStr, args)); + } + + private void updateSubmodules( + Repository repo, String targetRef, JiriProjects projects, GerritRemoteReader reader) + throws Exception { + + DirCache index = DirCache.newInCore(); + DirCacheBuilder builder = index.builder(); + ObjectInserter inserter = repo.newObjectInserter(); + try (RevWalk rw = new RevWalk(repo)) { + Config cfg = new Config(); + projects.sortByPath(); + String parent = null; + for (JiriProjects.Project proj : projects.getProjects()) { + String path = proj.getPath(); + String nameUri = proj.getRemote(); + if (parent != null) { + String p1 = StringUtil.stripAndaddCharsAtEnd(path, "/"); + String p2 = StringUtil.stripAndaddCharsAtEnd(parent, "/"); + if (p1.startsWith(p2)) { + warn( + "Skipping project %s(%s) as git doesn't support nested submodules", + proj.getName(), path); + continue; + } + } + + ObjectId objectId; + String ref = proj.getRef(); + + if (ObjectId.isId(ref)) { + objectId = ObjectId.fromString(ref); + } else { + objectId = reader.sha1(nameUri, ref); + if (objectId == null) { + warn("failed to get ref '%s' for '%s', skipping", ref, nameUri); + continue; + } + } + + // can be branch or tag + cfg.setString("submodule", path, "branch", ref); + + if (proj.getHistorydepth() > 0) { + cfg.setBoolean("submodule", path, "shallow", true); + if (proj.getHistorydepth() != 1) { + warn( + "Project %s(%s) has historydepth other than 1. Submodule only support shallow of depth 1.", + proj.getName(), proj.getPath()); + } + } + + nameUri = URI.create(nameUri).toString(); + cfg.setString("submodule", path, "path", path); + cfg.setString("submodule", path, "url", nameUri); + + // create gitlink + DirCacheEntry dcEntry = new DirCacheEntry(path); + dcEntry.setObjectId(objectId); + dcEntry.setFileMode(FileMode.GITLINK); + builder.add(dcEntry); + parent = path; + } + + String content = cfg.toText(); + + // create a new DirCacheEntry for .gitmodules file. + final DirCacheEntry dcEntry = new DirCacheEntry(Constants.DOT_GIT_MODULES); + ObjectId objectId = + inserter.insert(Constants.OBJ_BLOB, content.getBytes(Constants.CHARACTER_ENCODING)); + dcEntry.setObjectId(objectId); + dcEntry.setFileMode(FileMode.REGULAR_FILE); + builder.add(dcEntry); + + builder.finish(); + ObjectId treeId = index.writeTree(inserter); + + // Create a Commit object, populate it and write it + ObjectId headId = repo.resolve(targetRef + "^{commit}"); + CommitBuilder commit = new CommitBuilder(); + commit.setTreeId(treeId); + if (headId != null) commit.setParentIds(headId); + commit.setAuthor(serverIdent); + commit.setCommitter(serverIdent); + commit.setMessage(RepoText.get().repoCommitMessage); + + ObjectId commitId = inserter.insert(commit); + inserter.flush(); + + RefUpdate ru = repo.updateRef(targetRef); + ru.setNewObjectId(commitId); + ru.setExpectedOldObjectId(headId != null ? headId : ObjectId.zeroId()); + Result rc = ru.update(rw); + + switch (rc) { + case NEW: + case FORCED: + case FAST_FORWARD: + // Successful. Do nothing. + break; + case REJECTED: + case LOCK_FAILURE: + throw new ConcurrentRefUpdateException( + MessageFormat.format(JGitText.get().cannotLock, targetRef), ru.getRef(), rc); + default: + throw new JGitInternalException( + MessageFormat.format( + JGitText.get().updatingRefFailed, targetRef, commitId.name(), rc)); + } + } + } + + @Override + public void update(GerritRemoteReader reader, ConfigEntry c, String srcRef) throws Exception { + Repository srcRepo = reader.openRepository(c.getSrcRepoKey().toString()); + Repository destRepo = reader.openRepository(c.getDestRepoKey().toString()); + JiriProjects projects = JiriManifestParser.GetProjects(srcRepo, srcRef, c.getXmlPath()); + String targetRef = c.getDestBranch().equals("*") ? srcRef : REFS_HEADS + c.getDestBranch(); + updateSubmodules(destRepo, targetRef, projects, reader); + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/supermanifest/StringUtil.java b/src/main/java/com/googlesource/gerrit/plugins/supermanifest/StringUtil.java new file mode 100644 index 0000000..278a2b1 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/supermanifest/StringUtil.java
@@ -0,0 +1,35 @@ +// Copyright (C) 2017 Google Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.supermanifest; + +import org.apache.commons.lang.StringUtils; + +class StringUtil { + public static String addTab(String str) { + StringBuffer buf = new StringBuffer(""); + String arr[] = str.split("\n"); + for (String s : arr) { + if (!s.trim().isEmpty()) { + buf.append("\t" + s + " \n"); + } + } + return buf.toString(); + } + + public static String stripAndaddCharsAtEnd(String str, String chs) { + StringUtils.stripEnd(str, chs); + return str + chs; + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestRefUpdatedListener.java b/src/main/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestRefUpdatedListener.java index 9f83d76..ed153d6 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestRefUpdatedListener.java +++ b/src/main/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestRefUpdatedListener.java
@@ -237,6 +237,9 @@ case Repo: subModuleUpdater = new RepoUpdater(serverIdent, canonicalWebUrl); break; + case Jiri: + subModuleUpdater = new JiriUpdater(serverIdent, canonicalWebUrl); + break; default: throw new ConfigInvalidException( String.format("invalid toolType: %s", c.getToolType().name()));
diff --git a/src/test/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestIT.java b/src/test/java/com/googlesource/gerrit/plugins/supermanifest/JiriSuperManifestIT.java similarity index 66% copy from src/test/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestIT.java copy to src/test/java/com/googlesource/gerrit/plugins/supermanifest/JiriSuperManifestIT.java index d9dc131..8556150 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestIT.java +++ b/src/test/java/com/googlesource/gerrit/plugins/supermanifest/JiriSuperManifestIT.java
@@ -15,7 +15,6 @@ package com.googlesource.gerrit.plugins.supermanifest; import static com.google.common.truth.Truth.assertThat; -import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.fail; import com.google.gerrit.acceptance.GitUtil; @@ -24,25 +23,22 @@ import com.google.gerrit.acceptance.TestPlugin; import com.google.gerrit.extensions.api.projects.BranchApi; import com.google.gerrit.extensions.restapi.ResourceNotFoundException; -import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.reviewdb.client.Project.NameKey; import com.google.gerrit.reviewdb.client.RefNames; -import java.net.URI; import java.util.Arrays; import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; import org.eclipse.jgit.junit.TestRepository; -import org.eclipse.jgit.lib.BlobBasedConfig; -import org.eclipse.jgit.lib.Config; import org.junit.Test; @TestPlugin( name = "supermanifest", sysModule = "com.googlesource.gerrit.plugins.supermanifest.SuperManifestModule" ) -public class SuperManifestIT extends LightweightPluginDaemonTest { - Project.NameKey[] testRepoKeys; +public class JiriSuperManifestIT extends LightweightPluginDaemonTest { + NameKey[] testRepoKeys; void setupTestRepos(String prefix) throws Exception { - testRepoKeys = new Project.NameKey[2]; + testRepoKeys = new NameKey[2]; for (int i = 0; i < 2; i++) { testRepoKeys[i] = createProject(prefix + i); @@ -71,10 +67,10 @@ setupTestRepos("project"); // Make sure the manifest exists so the configuration loads successfully. - Project.NameKey manifestKey = createProject("manifest"); + NameKey manifestKey = createProject("manifest"); TestRepository<InMemoryRepository> manifestRepo = cloneProject(manifestKey, admin); - Project.NameKey superKey = createProject("superproject"); + NameKey superKey = createProject("superproject"); cloneProject(superKey, admin); pushConfig( @@ -85,23 +81,23 @@ + manifestKey.get() + "\n" + " srcRef = refs/heads/srcbranch\n" - + " srcPath = default.xml\n"); + + " srcPath = default\n" + + " toolType = jiri\n"); - String remoteXml = " <remote name=\"origin\" fetch=\"" + canonicalWebUrl.get() + "\" />\n"; - String defaultXml = " <default remote=\"origin\" revision=\"refs/heads/master\" />\n"; // XML change will trigger commit to superproject. String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" - + "<manifest>\n" - + remoteXml - + defaultXml - + " <project name=\"" + + "<manifest>\n<projects>\n" + + "<project name=\"" + + testRepoKeys[0].get() + + "\" remote=\"" + + canonicalWebUrl.get() + testRepoKeys[0].get() + "\" path=\"project1\" />\n" - + "</manifest>\n"; + + "</projects>\n</manifest>\n"; pushFactory - .create(db, admin.getIdent(), manifestRepo, "Subject", "default.xml", xml) + .create(db, admin.getIdent(), manifestRepo, "Subject", "default", xml) .to("refs/heads/srcbranch") .assertOkStatus(); @@ -116,19 +112,23 @@ xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" - + "<manifest>\n" - + remoteXml - + defaultXml + + "<manifest>\n<projects>\n" + " <project name=\"" + testRepoKeys[0].get() + + "\" remote=\"" + + canonicalWebUrl.get() + + testRepoKeys[0].get() + "\" path=\"project1\" />\n" + " <project name=\"" + testRepoKeys[1].get() + + "\" remote=\"" + + canonicalWebUrl.get() + + testRepoKeys[1].get() + "\" path=\"project2\" />\n" - + "</manifest>\n"; + + "</projects>\n</manifest>\n"; pushFactory - .create(db, admin.getIdent(), manifestRepo, "Subject", "default.xml", xml) + .create(db, admin.getIdent(), manifestRepo, "Subject", "default", xml) .to("refs/heads/srcbranch") .assertOkStatus(); @@ -144,21 +144,23 @@ + manifestKey.get() + "\n" + " srcRef = refs/heads/srcbranch\n" - + " srcPath = default.xml\n"); + + " srcPath = default\n" + + " toolType = jiri\n"); // Push another XML change; this should trigger a commit using the new config. xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" - + "<manifest>\n" - + remoteXml - + defaultXml + + "<manifest>\n<projects>\n" + " <project name=\"" + testRepoKeys[1].get() + + "\" remote=\"" + + canonicalWebUrl.get() + + testRepoKeys[1].get() + "\" path=\"project3\" />\n" - + "</manifest>\n"; + + "</projects>\n</manifest>\n"; pushFactory - .create(db, admin.getIdent(), manifestRepo, "Subject", "default.xml", xml) + .create(db, admin.getIdent(), manifestRepo, "Subject", "default", xml) .to("refs/heads/srcbranch") .assertOkStatus(); @@ -198,10 +200,10 @@ setupTestRepos("project"); // Make sure the manifest exists so the configuration loads successfully. - Project.NameKey manifestKey = createProject("manifest"); + NameKey manifestKey = createProject("manifest"); TestRepository<InMemoryRepository> manifestRepo = cloneProject(manifestKey, admin); - Project.NameKey superKey = createProject("superproject"); + NameKey superKey = createProject("superproject"); cloneProject(superKey, admin); pushConfig( @@ -212,39 +214,39 @@ + manifestKey.get() + "\n" + " srcRef = blablabla\n" - + " srcPath = default.xml\n"); - - String remoteXml = " <remote name=\"origin\" fetch=\"" + canonicalWebUrl.get() + "\" />\n"; - String originXml = " <default remote=\"origin\" revision=\"refs/heads/master\" />\n"; + + " srcPath = default\n" + + " toolType = jiri\n"); // XML change will trigger commit to superproject. String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" - + "<manifest>\n" - + remoteXml - + originXml + + "<manifest>\n<projects>\n" + " <project name=\"" + testRepoKeys[0].get() + + "\" remote=\"" + + canonicalWebUrl.get() + + testRepoKeys[0].get() + "\" path=\"project1\" />\n" - + "</manifest>\n"; + + "</projects>\n</manifest>\n"; pushFactory - .create(db, admin.getIdent(), manifestRepo, "Subject", "default.xml", xml) + .create(db, admin.getIdent(), manifestRepo, "Subject", "default", xml) .to("refs/heads/src1") .assertOkStatus(); xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" - + "<manifest>\n" - + remoteXml - + originXml + + "<manifest>\n<projects>\n" + " <project name=\"" + testRepoKeys[1].get() + + "\" remote=\"" + + canonicalWebUrl.get() + + testRepoKeys[1].get() + "\" path=\"project2\" />\n" - + "</manifest>\n"; + + "</projects>\n</manifest>\n"; pushFactory - .create(db, admin.getIdent(), manifestRepo, "Subject", "default.xml", xml) + .create(db, admin.getIdent(), manifestRepo, "Subject", "default", xml) .to("refs/heads/src2") .assertOkStatus(); @@ -271,37 +273,36 @@ public void manifestIncludesOtherManifest() throws Exception { setupTestRepos("project"); - String remoteXml = " <remote name=\"origin\" fetch=\"" + canonicalWebUrl.get() + "\" />\n"; - String originXml = " <default remote=\"origin\" revision=\"refs/heads/master\" />\n"; String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" - + "<manifest>\n" - + remoteXml - + originXml - + " <project name=\"" + + "<manifest>\n<projects>\n" + + "<project name=\"" + + testRepoKeys[0].get() + + "\" remote=\"" + + canonicalWebUrl.get() + testRepoKeys[0].get() + "\" path=\"project1\" />\n" - + "</manifest>\n"; + + "</projects>\n</manifest>\n"; - Project.NameKey manifestKey = createProject("manifest"); + NameKey manifestKey = createProject("manifest"); TestRepository<InMemoryRepository> manifestRepo = cloneProject(manifestKey, admin); pushFactory - .create(db, admin.getIdent(), manifestRepo, "Subject", "default.xml", xml) + .create(db, admin.getIdent(), manifestRepo, "Subject", "default", xml) .to("refs/heads/master") .assertOkStatus(); String superXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" - + "<manifest>" - + " <include name=\"default.xml\"/>" - + "</manifest>"; + + "<manifest>\n<imports>\n" + + " <localimport file=\"default\"/>" + + "</imports>\n</manifest>"; pushFactory - .create(db, admin.getIdent(), manifestRepo, "Subject", "super.xml", superXml) + .create(db, admin.getIdent(), manifestRepo, "Subject", "super", superXml) .to("refs/heads/master") .assertOkStatus(); - Project.NameKey superKey = createProject("superproject"); + NameKey superKey = createProject("superproject"); cloneProject(superKey, admin); pushConfig( @@ -312,13 +313,14 @@ + manifestKey.get() + "\n" + " srcRef = refs/heads/master\n" - + " srcPath = super.xml\n"); + + " srcPath = super\n" + + " toolType = jiri\n"); // Push a change to the source branch. We intentionally change the included XML file // (rather than the one mentioned in srcPath), to double check that we don't try to be too - // smart about eliding nops. + // smart about eluding nops. pushFactory - .create(db, admin.getIdent(), manifestRepo, "Subject", "default.xml", xml + " ") + .create(db, admin.getIdent(), manifestRepo, "Subject", "default", xml + " ") .to("refs/heads/master") .assertOkStatus(); @@ -327,82 +329,63 @@ } @Test - public void relativeFetch() throws Exception { - // Test the setup that Android uses, where the "fetch" field is relative to the location of the - // manifest repo. - setupTestRepos("platform/project"); + public void remoteImportFails() throws Exception { + setupTestRepos("project"); - // The test framework adds more cruft to the prefix. - String realPrefix = testRepoKeys[0].get().split("/")[0]; + String xml = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + + "<manifest>\n<projects>\n" + + "<project name=\"" + + testRepoKeys[0].get() + + "\" remote=\"" + + canonicalWebUrl.get() + + testRepoKeys[0].get() + + "\" path=\"project1\" />\n" + + "</projects>\n</manifest>\n"; - Project.NameKey manifestKey = createProject(realPrefix + "/manifest"); - TestRepository<InMemoryRepository> manifestRepo = cloneProject(manifestKey, admin); + NameKey manifestKey = createProject("manifest"); + NameKey superKey = createProject("superproject"); - Project.NameKey superKey = createProject("superproject"); - - TestRepository<InMemoryRepository> superRepo = cloneProject(superKey, admin); + cloneProject(superKey, admin); pushConfig( "[superproject \"" + superKey.get() - + ":refs/heads/destbranch\"]\n" + + ":refs/heads/master\"]\n" + " srcRepo = " + manifestKey.get() + "\n" - + " srcRef = refs/heads/srcbranch\n" - + " srcPath = default.xml\n"); - - String url = canonicalWebUrl.get(); - String remoteXml = " <remote name=\"origin\" fetch=\"..\" review=\"" + url + "\" />\n"; - String defaultXml = " <default remote=\"origin\" revision=\"refs/heads/master\" />\n"; - - String xml = - "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" - + "<manifest>\n" - + remoteXml - + defaultXml - + " <project name=\"" - + testRepoKeys[0].get() - + "\" path=\"path1\" />\n" - + "</manifest>\n"; + + " srcRef = refs/heads/master\n" + + " srcPath = super\n" + + " toolType = jiri\n"); + TestRepository<InMemoryRepository> manifestRepo = cloneProject(manifestKey, admin); pushFactory - .create(db, admin.getIdent(), manifestRepo, "Subject", "default.xml", xml) - .to("refs/heads/srcbranch") + .create(db, admin.getIdent(), manifestRepo, "Subject", "default", xml) + .to("refs/heads/master") .assertOkStatus(); - BranchApi branch = gApi.projects().name(superKey.get()).branch("refs/heads/destbranch"); - assertThat(branch.file("path1").getContentType()).isEqualTo("x-git/gitlink; charset=UTF-8"); + String superXml = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + + "<manifest>\n<imports>\n" + + "<import manifest=\"" + + manifestKey.get() + + "\" name=\"default\" remote=\"" + + canonicalWebUrl.get() + + manifestKey.get() + + "\"/>" + + "</imports>\n</manifest>"; - Config base = new Config(); - BlobBasedConfig cfg = - new BlobBasedConfig(base, branch.file(".gitmodules").asString().getBytes(UTF_8)); + pushFactory + .create(db, admin.getIdent(), manifestRepo, "Subject", "super", superXml) + .to("refs/heads/master") + .assertOkStatus(); - String subUrl = cfg.getString("submodule", "path1", "url"); - - // URL is valid. - URI.create(subUrl); - - // The suburls must be interpreted as relative to the parent project as a directory, i.e. - // to go from superproject/ to platform/project0, you have to do ../platform/project0 - - // URL is clean. - assertThat(subUrl).isEqualTo("../" + realPrefix + "/project0"); + BranchApi branch = gApi.projects().name(superKey.get()).branch("refs/heads/master"); + try { + branch.file("project1"); + fail("wanted exception"); + } catch (ResourceNotFoundException e) { + // all fine. + } } - - @Test - public void testToRepoKey() { - URI base = URI.create("https://gerrit-review.googlesource.com"); - assertThat( - SuperManifestRefUpdatedListener.urlToRepoKey( - base, "https://gerrit-review.googlesource.com/repo")) - .isEqualTo("repo"); - assertThat(SuperManifestRefUpdatedListener.urlToRepoKey(base, "repo")).isEqualTo("repo"); - assertThat( - SuperManifestRefUpdatedListener.urlToRepoKey( - URI.create("https://gerrit-review.googlesource.com/"), - "https://gerrit-review.googlesource.com/repo")) - .isEqualTo("repo"); - } - - // TODO - should add tests for all the error handling in configuration parsing? }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestIT.java b/src/test/java/com/googlesource/gerrit/plugins/supermanifest/RepoSuperManifestIT.java similarity index 99% rename from src/test/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestIT.java rename to src/test/java/com/googlesource/gerrit/plugins/supermanifest/RepoSuperManifestIT.java index d9dc131..6775998 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestIT.java +++ b/src/test/java/com/googlesource/gerrit/plugins/supermanifest/RepoSuperManifestIT.java
@@ -38,7 +38,7 @@ name = "supermanifest", sysModule = "com.googlesource.gerrit.plugins.supermanifest.SuperManifestModule" ) -public class SuperManifestIT extends LightweightPluginDaemonTest { +public class RepoSuperManifestIT extends LightweightPluginDaemonTest { Project.NameKey[] testRepoKeys; void setupTestRepos(String prefix) throws Exception {