| // Copyright (C) 2015 The Android Open Source Project |
| // |
| // 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.repositoryuse; |
| |
| import com.google.gerrit.reviewdb.client.Project; |
| import com.google.gerrit.server.config.CanonicalWebUrl; |
| import com.google.gerrit.server.git.GitRepositoryManager; |
| import com.google.inject.Inject; |
| import com.google.inject.assistedinject.Assisted; |
| |
| import org.eclipse.jgit.diff.DiffEntry; |
| import org.eclipse.jgit.diff.DiffFormatter; |
| import org.eclipse.jgit.diff.RawTextComparator; |
| import org.eclipse.jgit.errors.ConfigInvalidException; |
| import org.eclipse.jgit.errors.RepositoryNotFoundException; |
| import org.eclipse.jgit.lib.Constants; |
| import org.eclipse.jgit.lib.FileMode; |
| import org.eclipse.jgit.lib.ObjectLoader; |
| import org.eclipse.jgit.lib.ObjectReader; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| import org.eclipse.jgit.revwalk.RevTree; |
| import org.eclipse.jgit.revwalk.RevWalk; |
| import org.eclipse.jgit.submodule.SubmoduleWalk; |
| import org.eclipse.jgit.treewalk.TreeWalk; |
| import org.eclipse.jgit.util.io.DisabledOutputStream; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.io.IOException; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| public class RefUpdateHandlerImpl implements RefUpdateHandler { |
| private static final Logger log = |
| LoggerFactory.getLogger(RefUpdateHandlerImpl.class); |
| |
| private RefUpdate event; |
| private final GitRepositoryManager repoManager; |
| private final String serverName; |
| |
| @Inject |
| public RefUpdateHandlerImpl(@Assisted RefUpdate event, |
| GitRepositoryManager repoManager, |
| @CanonicalWebUrl String canonicalWebUrl) { |
| this.event = event; |
| this.repoManager = repoManager; |
| if (canonicalWebUrl != null) { |
| try { |
| URL url = new URL(canonicalWebUrl); |
| canonicalWebUrl = url.getHost(); |
| } catch (MalformedURLException e) { |
| log.warn("Could not parse canonicalWebUrl", e); |
| } |
| } |
| this.serverName = canonicalWebUrl; |
| } |
| |
| @Override |
| public void run() { |
| if (event.isDelete() && event.getRefName().startsWith(Constants.R_HEADS) |
| || event.getRefName().startsWith(Constants.R_TAGS)) { |
| // Ref was deleted... clean up any references |
| Ref ref = Ref.fetchByRef(event.getProjectName(), event.getRefName()); |
| if (ref != null) { |
| ref.delete(); |
| } |
| if (event.getRefName().startsWith(Constants.R_HEADS)) { |
| // Also clean up uses from this ref |
| Usage.deleteByBranch(getCanonicalProject(event.getProjectName()), |
| event.getRefName()); |
| } |
| } else if (event.getRefName().startsWith(Constants.R_TAGS)) { |
| Ref updatedRef = new Ref(event.getProjectName(), event.getRefName(), |
| event.getNewObjectId()); |
| updatedRef.save(); |
| } else if (event.getRefName().startsWith(Constants.R_HEADS)) { |
| Ref updatedRef = new Ref(event.getProjectName(), event.getRefName(), |
| event.getNewObjectId()); |
| updatedRef.save(); |
| Project.NameKey nameKey = new Project.NameKey(event.getProjectName()); |
| try { |
| if (Config.refreshAllSubmodules() || event.isCreate() |
| || isSubmoduleUpdate(event, nameKey)) { |
| Map<String, String> submodules = getSubmodules(event, nameKey); |
| updateProjects(event.getProjectName(), event.getRefName(), |
| submodules); |
| } |
| if (Config.parseManifests()) { |
| parseManifests(event, nameKey); |
| } |
| } catch (IOException e) { |
| log.error(e.getMessage(), e); |
| } |
| } |
| } |
| |
| private void parseManifests(RefUpdate event, Project.NameKey project) |
| throws RepositoryNotFoundException, IOException { |
| if (event.isDelete()) { |
| return; |
| } |
| try (Repository repo = repoManager.openRepository(project)) { |
| try (RevWalk walk = new RevWalk(repo); TreeWalk tw = new TreeWalk(repo)) { |
| RevCommit commit = |
| walk.parseCommit(repo.resolve(event.getNewObjectId())); |
| |
| tw.setRecursive(false); |
| tw.addTree(commit.getTree()); |
| ObjectReader or = tw.getObjectReader(); |
| while (tw.next()) { |
| String path = tw.getPathString(); |
| if (path.endsWith(".xml")) { |
| ManifestParser mp = new ManifestParser(); |
| ObjectLoader ol = or.open(tw.getObjectId(0)); |
| if (!ol.isLarge()) { |
| Map<String, String> tmp = mp.parseManifest(ol.getBytes()); |
| HashMap<String, String> projects = new HashMap<>(); |
| for (String key : tmp.keySet()) { |
| projects |
| .put( |
| normalizePath(String.format("%s:%s", |
| event.getProjectName(), path), key, true), |
| tmp.get(key)); |
| } |
| updateProjects( |
| String.format("%s:%s", event.getProjectName(), path), |
| event.getRefName(), projects); |
| } else { |
| log.warn(String.format("%s is too large, skipping manifest parse", |
| tw.getPathString())); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Has a submodule been updated? |
| * |
| * @param event the Event |
| * @return True if a submodule update occurred, otherwise False. |
| */ |
| private boolean isSubmoduleUpdate(RefUpdate event, Project.NameKey project) |
| throws RepositoryNotFoundException, IOException { |
| if (event.isDelete()) { |
| return false; |
| } |
| try (Repository repo = repoManager.openRepository(project)) { |
| try (RevWalk walk = new RevWalk(repo); |
| DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE)) { |
| RevTree aTree = null; |
| if (!event.isCreate()) { |
| // If this is a new ref, we can't get the original commit. |
| // We can still use the DiffFormatter to give us what changed |
| // by passing null, however. |
| RevCommit aCommit = |
| walk.parseCommit(repo.resolve(event.getOldObjectId())); |
| aTree = aCommit.getTree(); |
| } |
| RevCommit bCommit = |
| walk.parseCommit(repo.resolve(event.getNewObjectId())); |
| RevTree bTree = bCommit.getTree(); |
| |
| df.setRepository(repo); |
| df.setDiffComparator(RawTextComparator.DEFAULT); |
| df.setDetectRenames(true); |
| List<DiffEntry> diffEntries = df.scan(aTree, bTree); |
| for (DiffEntry de : diffEntries) { |
| FileMode oldMode = de.getOldMode(); |
| FileMode newMode = de.getNewMode(); |
| if ((oldMode != null && oldMode == FileMode.GITLINK) |
| || (newMode != null && newMode == FileMode.GITLINK)) { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| private Map<String, String> getSubmodules(RefUpdate event, |
| Project.NameKey project) throws RepositoryNotFoundException, IOException { |
| HashMap<String, String> submodules = new HashMap<>(); |
| try (Repository repo = repoManager.openRepository(project)) { |
| try (RevWalk walk = new RevWalk(repo); |
| SubmoduleWalk sw = new SubmoduleWalk(repo)) { |
| RevCommit commit = |
| walk.parseCommit(repo.resolve(event.getNewObjectId())); |
| sw.setTree(commit.getTree()); |
| sw.setRootTree(commit.getTree()); |
| while (sw.next()) { |
| submodules.put( |
| normalizePath(event.getProjectName(), sw.getModulesUrl(), false), |
| sw.getObjectId().name()); |
| } |
| } catch (ConfigInvalidException e) { |
| log.warn("Invalid .gitmodules configuration while parsing " |
| + event.getProjectName()); |
| } |
| } |
| return submodules; |
| } |
| |
| private void updateProjects(String project, String branch, |
| Map<String, String> projects) { |
| String canonicalProject = getCanonicalProject(project); |
| List<Usage> uses = Usage.fetchByProject(canonicalProject, branch); |
| for (Usage use : uses) { |
| if (!projects.containsKey(use.getDestination())) { |
| // No longer exists; delete. |
| use.delete(); |
| } else { |
| // Update SHA1 here. |
| use.setRef(projects.get(use.getDestination())); |
| use.save(); |
| projects.remove(use.getDestination()); |
| } |
| } |
| // At this point, submodules only contains new elements. |
| // Create them. |
| for (String key : projects.keySet()) { |
| Usage use = new Usage(canonicalProject, branch, key, projects.get(key)); |
| use.save(); |
| } |
| } |
| |
| private String getCanonicalProject(String project) { |
| String canonicalProject = |
| String.format("https://%s/%s", serverName, project); |
| try { |
| URL url = new URL(canonicalProject); |
| canonicalProject = url.getHost() + url.getPath(); |
| } catch (MalformedURLException e) { |
| log.warn("Could not parse project as URL: " + canonicalProject); |
| } |
| return canonicalProject; |
| } |
| |
| private String normalizePath(String project, String destination, |
| boolean isManifest) { |
| String originalProject = |
| isManifest ? project.substring(0, project.lastIndexOf(":")) : project; |
| |
| // Handle relative and absolute paths on the same server |
| if (destination.startsWith("/")) { |
| if (serverName != null) { |
| destination = serverName + destination; |
| } else { |
| log.warn("Could not parse absolute path; canonicalWebUrl not set"); |
| } |
| } else if (destination.startsWith(".")) { |
| if (serverName != null) { |
| Path path = Paths.get(String.format("/%s/%s", project, destination)); |
| destination = serverName + path.normalize().toString(); |
| } else { |
| log.warn("Could not parse relative path; canonicalWebUrl not set"); |
| } |
| } else if (!destination.matches("^[^:]+://.*")) { |
| if (serverName != null) { |
| destination = serverName + "/" + originalProject + "/" + destination; |
| } else { |
| log.warn("Could not parse relative path; canonicalWebURl not set"); |
| } |
| } |
| |
| try { |
| // Replace the protocol with a known scheme, to avoid angering URL |
| destination = destination.replaceFirst("^[^:]+://", ""); |
| URL url = new URL("https://" + destination); |
| destination = url.getHost() + url.getPath(); |
| } catch (MalformedURLException e) { |
| log.warn("Could not parse destination as URL: " + destination); |
| } |
| return destination; |
| } |
| } |