| // 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 static com.google.gerrit.reviewdb.client.RefNames.REFS_HEADS; |
| |
| import com.google.gerrit.reviewdb.client.Project; |
| import com.googlesource.gerrit.plugins.supermanifest.SuperManifestRefUpdatedListener.GerritRemoteReader; |
| import java.io.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| 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.stream.XMLInputFactory; |
| import javax.xml.stream.XMLStreamException; |
| import javax.xml.stream.XMLStreamReader; |
| import javax.xml.transform.stream.StreamSource; |
| import org.apache.commons.lang.StringUtils; |
| import org.eclipse.jgit.errors.ConfigInvalidException; |
| import org.eclipse.jgit.lib.Repository; |
| |
| class JiriManifestParser { |
| static class ManifestItem { |
| public ManifestItem( |
| String repoKey, String manifest, String ref, String pKey, boolean revisionPinned) { |
| this.repoKey = repoKey; |
| this.manifest = manifest; |
| this.ref = ref; |
| this.revisionPinned = revisionPinned; |
| this.projectKey = pKey; |
| } |
| |
| String repoKey; |
| String manifest; |
| String ref; |
| boolean revisionPinned; |
| |
| // In jiri if import is pinned to a revision and if |
| // we have a corresponding project in the manifest, jiri would |
| // pin that project to same revision. So passing key to match |
| // project to import tag. |
| // For Eg, if you have manifest in manifest2 repo |
| // <manifest><projects><project name="manifest2" .../> |
| // And If you import this from your main manifest |
| // <manifest><imports><import name="manifest2" revision="A"... /> |
| // jiri will pin manifest2 project to A as well. |
| String projectKey; |
| } |
| |
| static class RepoMap<K, V extends Repository> extends HashMap<K, V> implements AutoCloseable { |
| @Override |
| public void close() { |
| for (Repository repo : this.values()) { |
| repo.close(); |
| } |
| } |
| } |
| |
| public static JiriProjects getProjects( |
| GerritRemoteReader reader, String repoKey, String ref, String manifest) |
| throws ConfigInvalidException, IOException { |
| |
| try (RepoMap<String, Repository> repoMap = new RepoMap<>()) { |
| repoMap.put(repoKey, reader.openRepository(repoKey)); |
| Queue<ManifestItem> q = new LinkedList<>(); |
| q.add(new ManifestItem(repoKey, manifest, ref, "", false)); |
| HashMap<String, HashSet<String>> processedRepoFiles = new HashMap<>(); |
| HashMap<String, JiriProjects.Project> projectMap = new HashMap<>(); |
| |
| while (q.size() != 0) { |
| ManifestItem mi = q.remove(); |
| Repository repo = repoMap.get(mi.repoKey); |
| if (repo == null) { |
| repo = reader.openRepository(mi.repoKey); |
| repoMap.put(mi.repoKey, repo); |
| } |
| HashSet<String> processedFiles = processedRepoFiles.get(mi.repoKey); |
| if (processedFiles == null) { |
| processedFiles = new HashSet<String>(); |
| processedRepoFiles.put(mi.repoKey, processedFiles); |
| } |
| if (processedFiles.contains(mi.manifest)) { |
| continue; |
| } |
| processedFiles.add(mi.manifest); |
| JiriManifest m; |
| try { |
| m = parseManifest(repo, mi.ref, mi.manifest); |
| } catch (JAXBException | XMLStreamException e) { |
| throw new ConfigInvalidException("XML parse error", e); |
| } |
| |
| for (JiriProjects.Project project : m.projects.getProjects()) { |
| project.fillDefault(); |
| if (mi.revisionPinned && project.Key().equals(mi.projectKey)) { |
| project.setRevision(mi.ref); |
| } |
| if (projectMap.containsKey(project.Key())) { |
| if (!projectMap.get(project.Key()).equals(project)) |
| throw new ConfigInvalidException( |
| String.format( |
| "Duplicate conflicting project %s in manifest %s\n%s\n%s", |
| project.Key(), |
| mi.manifest, |
| project.toString(), |
| projectMap.get(project.Key()).toString())); |
| } else { |
| projectMap.put(project.Key(), project); |
| } |
| } |
| |
| URI parentURI; |
| try { |
| parentURI = new URI(mi.manifest); |
| } catch (URISyntaxException e) { |
| throw new ConfigInvalidException("Invalid parent URI", e); |
| } |
| for (JiriManifest.LocalImport l : m.imports.getLocalImports()) { |
| ManifestItem tw = |
| new ManifestItem( |
| mi.repoKey, |
| parentURI.resolve(l.getFile()).getPath(), |
| mi.ref, |
| mi.projectKey, |
| mi.revisionPinned); |
| q.add(tw); |
| } |
| |
| for (JiriManifest.Import i : m.imports.getImports()) { |
| i.fillDefault(); |
| URI uri; |
| try { |
| uri = new URI(i.getRemote()); |
| } catch (URISyntaxException e) { |
| throw new ConfigInvalidException("Invalid URI", e); |
| } |
| String iRepoKey = new Project.NameKey(StringUtils.strip(uri.getPath(), "/")).toString(); |
| String iRef = i.getRevision(); |
| boolean revisionPinned = true; |
| if (iRef.isEmpty()) { |
| iRef = REFS_HEADS + i.getRemotebranch(); |
| revisionPinned = false; |
| } |
| |
| ManifestItem tmi = |
| new ManifestItem(iRepoKey, i.getManifest(), iRef, i.Key(), revisionPinned); |
| q.add(tmi); |
| } |
| } |
| return new JiriProjects(projectMap.values().toArray(new JiriProjects.Project[0])); |
| } |
| } |
| |
| private static JiriManifest parseManifest(Repository repo, String ref, String file) |
| throws JAXBException, IOException, XMLStreamException { |
| byte[] b = Utils.readBlob(repo, ref + ":" + file); |
| JAXBContext jc = JAXBContext.newInstance(JiriManifest.class); |
| |
| XMLInputFactory inf = XMLInputFactory.newFactory(); |
| inf.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false); |
| inf.setProperty(XMLInputFactory.SUPPORT_DTD, false); |
| XMLStreamReader sr = inf.createXMLStreamReader(new StreamSource(new ByteArrayInputStream(b))); |
| |
| return (JiriManifest) jc.createUnmarshaller().unmarshal(sr); |
| } |
| } |