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 {