| // Copyright (C) 2015 Advanced Micro Devices, Inc. All rights reserved. |
| // |
| // 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.amd.gerrit.plugins.manifestsubscription; |
| |
| import com.amd.gerrit.plugins.manifestsubscription.manifest.Manifest; |
| import com.google.common.collect.HashBasedTable; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Table; |
| import com.google.gerrit.reviewdb.client.Project; |
| import com.google.gerrit.server.extensions.events.GitReferenceUpdated; |
| import com.google.gerrit.server.git.GitRepositoryManager; |
| import com.google.gerrit.server.git.VersionedMetaData; |
| import org.eclipse.jgit.api.Git; |
| import org.eclipse.jgit.api.errors.GitAPIException; |
| import org.eclipse.jgit.errors.ConfigInvalidException; |
| import org.eclipse.jgit.lib.*; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| import org.eclipse.jgit.revwalk.RevWalk; |
| import org.eclipse.jgit.treewalk.TreeWalk; |
| import org.eclipse.jgit.treewalk.filter.PathSuffixFilter; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import javax.xml.bind.JAXBContext; |
| import javax.xml.bind.JAXBException; |
| import javax.xml.bind.Marshaller; |
| import javax.xml.bind.Unmarshaller; |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.util.*; |
| |
| public class VersionedManifests extends VersionedMetaData implements ManifestProvider { |
| private static final Logger log = |
| LoggerFactory.getLogger(VersionedManifests.class); |
| |
| private String refName; |
| private Unmarshaller manifestUnmarshaller; |
| private Marshaller manifestMarshaller; |
| private Map<String, Manifest> manifests; |
| private String srcManifestRepo = ""; |
| private String extraCommitMsg = ""; |
| |
| public String getExtraCommitMsg() { |
| return extraCommitMsg; |
| } |
| |
| public void setExtraCommitMsg(String extraCommitMsg) { |
| this.extraCommitMsg = extraCommitMsg; |
| } |
| |
| public String getSrcManifestRepo() { |
| return srcManifestRepo; |
| } |
| |
| public void setSrcManifestRepo(String srcManifestRepo) { |
| this.srcManifestRepo = srcManifestRepo; |
| } |
| |
| public Map<String, Manifest> getManifests() { |
| return Collections.unmodifiableMap(manifests); |
| } |
| |
| public void setManifests(Map<String, Manifest> manifests) { |
| this.manifests = manifests; |
| } |
| |
| public Set<String> getManifestPaths() { |
| return manifests.keySet(); |
| } |
| |
| private VersionedManifests() throws JAXBException { |
| JAXBContext jaxbctx = JAXBContext.newInstance(Manifest.class); |
| this.manifestUnmarshaller = jaxbctx.createUnmarshaller(); |
| this.manifestMarshaller = jaxbctx.createMarshaller(); |
| this.manifestMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); |
| } |
| |
| public VersionedManifests(String refName) |
| throws JAXBException { |
| this(); |
| this.refName = refName; |
| |
| } |
| |
| public VersionedManifests(String refName, |
| Map<String, Manifest> manifests) throws JAXBException { |
| this(refName); |
| this.manifests = manifests; |
| |
| } |
| |
| @Override |
| protected String getRefName() { |
| return refName; |
| } |
| |
| @Override |
| protected void onLoad() throws IOException, ConfigInvalidException { |
| manifests = Maps.newHashMap(); |
| |
| String path; |
| Manifest manifest; |
| |
| try (RevWalk rw = new RevWalk(reader); |
| TreeWalk treewalk = new TreeWalk(reader)) { |
| // This happens when someone configured a invalid branch name |
| if (getRevision() == null) { |
| throw new ConfigInvalidException(refName); |
| } |
| RevCommit r = rw.parseCommit(getRevision()); |
| treewalk.addTree(r.getTree()); |
| treewalk.setRecursive(false); |
| treewalk.setFilter(PathSuffixFilter.create(".xml")); |
| while (treewalk.next()) { |
| if (treewalk.isSubtree()) { |
| treewalk.enterSubtree(); |
| } else { |
| path = treewalk.getPathString(); |
| //TODO: Should this be done more lazily? |
| //TODO: difficult to do when reader is not available outside of onLoad? |
| try (ByteArrayInputStream input |
| = new ByteArrayInputStream(readFile(path))) { |
| manifest = (Manifest) manifestUnmarshaller.unmarshal(input); |
| manifests.put(path, manifest); |
| } catch (JAXBException e) { |
| e.printStackTrace(); |
| } |
| } |
| } |
| } |
| |
| //TODO load changed manifest |
| // DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE); |
| } |
| |
| @Override |
| protected boolean onSave(CommitBuilder commit) throws IOException { |
| // extra commit message (such as the one-liner git log) is put in |
| // the body because: |
| // 1) not all manifest update has a one-line git log (such as |
| // the manifest generated from the initial subscription) |
| // 2) An manifest update may contain multiple project, which means |
| // multiple one-line git log |
| StringBuilder commitMsg = new StringBuilder(); |
| commitMsg.append("Snapshot manifest from " + |
| srcManifestRepo + " updated\n\n"); |
| |
| if (extraCommitMsg != null && extraCommitMsg.length() > 0) { |
| commitMsg.append(extraCommitMsg); |
| } |
| |
| String path; |
| Manifest manifest; |
| for (Map.Entry<String, Manifest> entry : manifests.entrySet()) { |
| path = entry.getKey(); |
| manifest = entry.getValue(); |
| |
| try { |
| saveManifest(path, manifest); |
| } catch (JAXBException e) { |
| throw new IOException(e); |
| } |
| } |
| |
| // For some reason the default author and committer date is |
| // invalid (always the same date and time) |
| Date date = new Date(); |
| commit.setAuthor(new PersonIdent(commit.getAuthor(), date)); |
| commit.setCommitter(new PersonIdent(commit.getCommitter(), date)); |
| |
| if (commit.getMessage() == null || "".equals(commit.getMessage())) { |
| commit.setMessage(commitMsg.toString()); |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public Manifest readManifest(String path) throws ManifestReadException { |
| if (manifests.containsKey(path)) { |
| return manifests.get(path); |
| } |
| |
| throw new ManifestReadException(path); |
| } |
| |
| /** |
| * Must be called inside onSave |
| * |
| * @param path |
| * @param manifest |
| * @throws JAXBException |
| * @throws IOException |
| */ |
| private void saveManifest(String path, Manifest manifest) |
| throws JAXBException, IOException { |
| ByteArrayOutputStream output = new ByteArrayOutputStream(); |
| manifestMarshaller.marshal(manifest, output); |
| saveFile(path, output.toByteArray()); |
| } |
| |
| static void tagManifest(GitRepositoryManager gitRepoManager, |
| Manifest manifest, |
| final String tag) |
| throws GitAPIException, IOException { |
| Table<String, String, String> lookup = HashBasedTable.create(); |
| String defaultRef = null; |
| |
| if (manifest.getDefault() != null) { |
| defaultRef = manifest.getDefault().getRevision(); |
| } |
| |
| ManifestOp op = new ManifestOp() { |
| @Override |
| public boolean apply(com.amd.gerrit.plugins.manifestsubscription.manifest.Project project, |
| String hash, String name, |
| GitRepositoryManager gitRepoManager) throws |
| GitAPIException, IOException { |
| Project.NameKey p = new Project.NameKey(project.getName()); |
| try (Repository db = gitRepoManager.openRepository(p); |
| Git git = new Git(db); |
| RevWalk walk = new RevWalk(db)) { |
| RevCommit commit = walk.parseCommit(db.resolve(hash)); |
| git.tag().setName(tag).setObjectId(commit).setAnnotated(true).call(); |
| } |
| return true; |
| } |
| }; |
| |
| traverseManifestAndApplyOp(gitRepoManager, manifest.getProject(), defaultRef, op, lookup); |
| } |
| |
| static void branchManifest(GitRepositoryManager gitRepoManager, |
| Manifest manifest, |
| final String branch, |
| final GitReferenceUpdated gitReferenceUpdated) |
| throws GitAPIException, IOException { |
| Table<String, String, String> lookup = HashBasedTable.create(); |
| String defaultRef = null; |
| |
| if (manifest.getDefault() != null) { |
| defaultRef = manifest.getDefault().getRevision(); |
| } |
| |
| ManifestOp op = new ManifestOp() { |
| @Override |
| public boolean apply(com.amd.gerrit.plugins.manifestsubscription.manifest.Project project, |
| String hash, String name, |
| GitRepositoryManager gitRepoManager) throws |
| GitAPIException, IOException { |
| Project.NameKey p = new Project.NameKey(project.getName()); |
| try (Repository db = gitRepoManager.openRepository(p); |
| Git git = new Git(db)) { |
| try { |
| Ref r = git.branchCreate().setName(branch).setStartPoint(hash).call(); |
| RefUpdate refUpdate = db.updateRef(branch); |
| refUpdate.setNewObjectId(r.getObjectId()); |
| gitReferenceUpdated.fire(p, refUpdate, null); |
| } catch (Exception e) { |
| |
| } |
| } |
| return true; |
| } |
| }; |
| |
| traverseManifestAndApplyOp(gitRepoManager, manifest.getProject(), defaultRef, op, lookup); |
| } |
| |
| /** |
| * Pass in a {@link com.google.common.collect.Table} if you want to reuse |
| * the lookup cache |
| * |
| * @param gitRepoManager |
| * @param manifest |
| * @param lookup |
| */ |
| static void affixManifest(GitRepositoryManager gitRepoManager, |
| Manifest manifest, |
| Table<String, String, String> lookup) |
| throws GitAPIException, IOException { |
| if (lookup == null) { |
| // project, branch, hash |
| lookup = HashBasedTable.create(); |
| } |
| |
| String defaultRef = null; |
| |
| if (manifest.getDefault() != null) { |
| defaultRef = manifest.getDefault().getRevision(); |
| } |
| |
| ManifestOp op = new ManifestOp() { |
| @Override |
| public boolean apply(com.amd.gerrit.plugins.manifestsubscription.manifest.Project project, |
| String hash, String name, |
| GitRepositoryManager gitRepoManager) { |
| project.setRevision(hash); |
| if (!ObjectId.isId(name)) { |
| project.setUpstream(name); |
| } |
| return true; |
| } |
| }; |
| |
| traverseManifestAndApplyOp(gitRepoManager, manifest.getProject(), defaultRef, op, lookup); |
| } |
| |
| static void traverseManifestAndApplyOp( |
| GitRepositoryManager gitRepoManager, |
| List<com.amd.gerrit.plugins.manifestsubscription.manifest.Project> projects, |
| String defaultRef, |
| ManifestOp op, |
| Table<String, String, String> lookup) throws GitAPIException, IOException { |
| |
| String ref; |
| String hash; |
| String projectName; |
| Project.NameKey p; |
| for (com.amd.gerrit.plugins.manifestsubscription.manifest.Project project : projects) { |
| projectName = project.getName(); |
| ref = project.getRevision(); |
| |
| ref = (ref == null) ? defaultRef : ref; |
| |
| if (ref != null) { |
| if (lookup != null) { |
| hash = lookup.get(projectName, ref); |
| } else { |
| hash = null; |
| } |
| |
| if (hash == null) { |
| p = new Project.NameKey(projectName); |
| try (Repository db = gitRepoManager.openRepository(p)) { |
| hash = db.resolve(ref).getName(); |
| } catch (IOException | NullPointerException e) { |
| log.warn("Cannot resolve ref: " + ref + |
| "\n\t" + projectName + |
| "\n\t" + defaultRef + |
| "\n\t" + Arrays.toString(e.getStackTrace())); |
| } |
| } |
| |
| if (hash != null) { |
| if (lookup != null) lookup.put(projectName, ref, hash); |
| op.apply(project, hash, ref, gitRepoManager); |
| } |
| } |
| |
| if (project.getProject().size() > 0) { |
| traverseManifestAndApplyOp(gitRepoManager, project.getProject(), defaultRef, op, lookup); |
| } |
| } |
| } |
| } |