Allow defining commentlinks in project.config
These are inherited from parent projects, including the system-wide commentlinks defined in project.config in all projects. Child projects may override commentlinks defined in parents by name, which project administrators can discover via GET /projects/myparent/config. Change-Id: I96dd6701350761a0af6e3d9babdef4f74ad4e29f
This commit is contained in:
		@@ -741,6 +741,11 @@ how the replacement is displayed to the user.
 | 
			
		||||
  html = $1<a href=\"http://trak.example.com/$2\">$2</a>
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
Comment links can also be specified in `project.config` and sections in
 | 
			
		||||
children override those in parents. The only restriction is that to
 | 
			
		||||
avoid injecting arbitrary user-supplied HTML in the page, comment links
 | 
			
		||||
defined in `project.config` may only supply `link`, not `html`.
 | 
			
		||||
 | 
			
		||||
[[commentlink.name.match]]commentlink.<name>.match::
 | 
			
		||||
+
 | 
			
		||||
A JavaScript regular expression to match positions to be replaced
 | 
			
		||||
 
 | 
			
		||||
@@ -14,12 +14,14 @@
 | 
			
		||||
 | 
			
		||||
package com.google.gerrit.server.git;
 | 
			
		||||
 | 
			
		||||
import static com.google.common.base.Preconditions.checkArgument;
 | 
			
		||||
import static com.google.gerrit.common.data.Permission.isPermission;
 | 
			
		||||
 | 
			
		||||
import com.google.common.base.CharMatcher;
 | 
			
		||||
import com.google.common.base.Joiner;
 | 
			
		||||
import com.google.common.base.Objects;
 | 
			
		||||
import com.google.common.base.Splitter;
 | 
			
		||||
import com.google.common.base.Strings;
 | 
			
		||||
import com.google.common.collect.ImmutableList;
 | 
			
		||||
import com.google.common.collect.ImmutableSet;
 | 
			
		||||
import com.google.common.collect.Lists;
 | 
			
		||||
@@ -66,6 +68,7 @@ import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
import java.util.regex.PatternSyntaxException;
 | 
			
		||||
 | 
			
		||||
public class ProjectConfig extends VersionedMetaData {
 | 
			
		||||
  public static final String COMMENTLINK = "commentlink";
 | 
			
		||||
@@ -138,6 +141,7 @@ public class ProjectConfig extends VersionedMetaData {
 | 
			
		||||
  private Map<String, ContributorAgreement> contributorAgreements;
 | 
			
		||||
  private Map<String, NotifyConfig> notifySections;
 | 
			
		||||
  private Map<String, LabelType> labelSections;
 | 
			
		||||
  private List<CommentLinkInfo> commentLinkSections;
 | 
			
		||||
  private List<ValidationError> validationErrors;
 | 
			
		||||
  private ObjectId rulesId;
 | 
			
		||||
 | 
			
		||||
@@ -155,8 +159,8 @@ public class ProjectConfig extends VersionedMetaData {
 | 
			
		||||
    return r;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static CommentLinkInfo buildCommentLink(Config cfg, String name)
 | 
			
		||||
      throws IllegalArgumentException {
 | 
			
		||||
  public static CommentLinkInfo buildCommentLink(Config cfg, String name,
 | 
			
		||||
      boolean allowRaw) throws IllegalArgumentException {
 | 
			
		||||
    String match = cfg.getString(COMMENTLINK, name, KEY_MATCH);
 | 
			
		||||
 | 
			
		||||
    // Unfortunately this validation isn't entirely complete. Clients
 | 
			
		||||
@@ -168,6 +172,8 @@ public class ProjectConfig extends VersionedMetaData {
 | 
			
		||||
 | 
			
		||||
    String link = cfg.getString(COMMENTLINK, name, KEY_LINK);
 | 
			
		||||
    String html = cfg.getString(COMMENTLINK, name, KEY_HTML);
 | 
			
		||||
    checkArgument(allowRaw || Strings.isNullOrEmpty(html),
 | 
			
		||||
        "Raw html replacement not allowed");
 | 
			
		||||
    return new CommentLinkInfo(name, match, link, html);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -256,6 +262,10 @@ public class ProjectConfig extends VersionedMetaData {
 | 
			
		||||
    return labelSections;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public Collection<CommentLinkInfo> getCommentLinkSections() {
 | 
			
		||||
    return commentLinkSections;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public GroupReference resolve(AccountGroup group) {
 | 
			
		||||
    return resolve(GroupReference.forGroup(group));
 | 
			
		||||
  }
 | 
			
		||||
@@ -356,6 +366,7 @@ public class ProjectConfig extends VersionedMetaData {
 | 
			
		||||
    loadAccessSections(rc, groupsByName);
 | 
			
		||||
    loadNotifySections(rc, groupsByName);
 | 
			
		||||
    loadLabelSections(rc);
 | 
			
		||||
    loadCommentLinkSections(rc);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void loadAccountsSection(
 | 
			
		||||
@@ -613,6 +624,25 @@ public class ProjectConfig extends VersionedMetaData {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void loadCommentLinkSections(Config rc) {
 | 
			
		||||
    Set<String> subsections = rc.getSubsections(COMMENTLINK);
 | 
			
		||||
    commentLinkSections = Lists.newArrayListWithCapacity(subsections.size());
 | 
			
		||||
    for (String name : subsections) {
 | 
			
		||||
      try {
 | 
			
		||||
        commentLinkSections.add(buildCommentLink(rc, name, false));
 | 
			
		||||
      } catch (PatternSyntaxException e) {
 | 
			
		||||
        error(new ValidationError(PROJECT_CONFIG, String.format(
 | 
			
		||||
            "Invalid pattern \"%s\" in commentlink.%s.match: %s",
 | 
			
		||||
            rc.getString(COMMENTLINK, name, KEY_MATCH), name, e.getMessage())));
 | 
			
		||||
      } catch (IllegalArgumentException e) {
 | 
			
		||||
        error(new ValidationError(PROJECT_CONFIG, String.format(
 | 
			
		||||
            "Error in pattern \"%s\" in commentlink.%s.match: %s",
 | 
			
		||||
            rc.getString(COMMENTLINK, name, KEY_MATCH), name, e.getMessage())));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    commentLinkSections = ImmutableList.copyOf(commentLinkSections);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private Map<String, GroupReference> readGroupList() throws IOException {
 | 
			
		||||
    groupsByUUID = new HashMap<AccountGroup.UUID, GroupReference>();
 | 
			
		||||
    Map<String, GroupReference> groupsByName =
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,7 @@ public class CommentLinkProvider implements Provider<List<CommentLinkInfo>> {
 | 
			
		||||
    List<CommentLinkInfo> cls =
 | 
			
		||||
        Lists.newArrayListWithCapacity(subsections.size());
 | 
			
		||||
    for (String name : subsections) {
 | 
			
		||||
      cls.add(ProjectConfig.buildCommentLink(cfg, name));
 | 
			
		||||
      cls.add(ProjectConfig.buildCommentLink(cfg, name, true));
 | 
			
		||||
    }
 | 
			
		||||
    return ImmutableList.copyOf(cls);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@ package com.google.gerrit.server.project;
 | 
			
		||||
 | 
			
		||||
import com.google.common.base.Function;
 | 
			
		||||
import com.google.common.base.Predicate;
 | 
			
		||||
import com.google.common.collect.ImmutableList;
 | 
			
		||||
import com.google.common.collect.Iterables;
 | 
			
		||||
import com.google.common.collect.Lists;
 | 
			
		||||
import com.google.common.collect.Maps;
 | 
			
		||||
@@ -295,6 +296,16 @@ public class ProjectState {
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return an iterable that walks in-order from All-Projects through the
 | 
			
		||||
   *     project hierarchy to this project.
 | 
			
		||||
   */
 | 
			
		||||
  public Iterable<ProjectState> treeInOrder() {
 | 
			
		||||
    List<ProjectState> projects = Lists.newArrayList(tree());
 | 
			
		||||
    Collections.reverse(projects);
 | 
			
		||||
    return projects;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return an iterable that walks through the parents of this project. Starts
 | 
			
		||||
   *         from the immediate parent of this project and progresses up the
 | 
			
		||||
@@ -346,9 +357,7 @@ public class ProjectState {
 | 
			
		||||
 | 
			
		||||
  public LabelTypes getLabelTypes() {
 | 
			
		||||
    Map<String, LabelType> types = Maps.newLinkedHashMap();
 | 
			
		||||
    List<ProjectState> projects = Lists.newArrayList(tree());
 | 
			
		||||
    Collections.reverse(projects);
 | 
			
		||||
    for (ProjectState s : projects) {
 | 
			
		||||
    for (ProjectState s : treeInOrder()) {
 | 
			
		||||
      for (LabelType type : s.getConfig().getLabelSections().values()) {
 | 
			
		||||
        String lower = type.getName().toLowerCase();
 | 
			
		||||
        LabelType old = types.get(lower);
 | 
			
		||||
@@ -367,7 +376,16 @@ public class ProjectState {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public List<CommentLinkInfo> getCommentLinks() {
 | 
			
		||||
    return commentLinks;
 | 
			
		||||
    Map<String, CommentLinkInfo> cls = Maps.newLinkedHashMap();
 | 
			
		||||
    for (CommentLinkInfo cl : commentLinks) {
 | 
			
		||||
      cls.put(cl.name.toLowerCase(), cl);
 | 
			
		||||
    }
 | 
			
		||||
    for (ProjectState s : treeInOrder()) {
 | 
			
		||||
      for (CommentLinkInfo cl : s.getConfig().getCommentLinkSections()) {
 | 
			
		||||
        cls.put(cl.name.toLowerCase(), cl);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return ImmutableList.copyOf(cls.values());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private boolean getInheritableBoolean(Function<Project, InheritableBoolean> func) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user