Support to enable the ITS integration only for certain branches

To limit the issue tracker system integration to certain branches one
or many refs, ref patterns or regular expressions can be specified as
plugin.<its-name>.branch in the project.config file. This is similar
to how custom labels in Gerrit core can be limited to certain
branches.

Change-Id: Ia63c4502ade394c414a406c70f8d34b1cbb77435
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
diff --git a/its-base/src/main/java/com/googlesource/gerrit/plugins/hooks/its/ItsConfig.java b/its-base/src/main/java/com/googlesource/gerrit/plugins/hooks/its/ItsConfig.java
index 2a5ba9f..87758d6 100644
--- a/its-base/src/main/java/com/googlesource/gerrit/plugins/hooks/its/ItsConfig.java
+++ b/its-base/src/main/java/com/googlesource/gerrit/plugins/hooks/its/ItsConfig.java
@@ -14,8 +14,10 @@
 
 package com.googlesource.gerrit.plugins.hooks.its;
 
+import com.google.gerrit.common.data.RefConfigSection;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.config.PluginConfig;
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.events.ChangeAbandonedEvent;
@@ -27,7 +29,9 @@
 import com.google.gerrit.server.events.RefUpdatedEvent;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.RefPatternMatcher;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -38,35 +42,43 @@
   private final String pluginName;
   private final ProjectCache projectCache;
   private final PluginConfigFactory pluginCfgFactory;
+  private final Provider<CurrentUser> self;
 
   @Inject
   public ItsConfig(@PluginName String pluginName, ProjectCache projectCache,
-      PluginConfigFactory pluginCfgFactory) {
+      PluginConfigFactory pluginCfgFactory, Provider<CurrentUser> self) {
     this.pluginName = pluginName;
     this.projectCache = projectCache;
     this.pluginCfgFactory = pluginCfgFactory;
+    this.self = self;
   }
 
   public boolean isEnabled(ChangeEvent event) {
     if (event instanceof PatchSetCreatedEvent) {
-      return isEnabled(((PatchSetCreatedEvent) event).change.project);
+      PatchSetCreatedEvent e = (PatchSetCreatedEvent) event;
+      return isEnabled(e.change.project, e.change.branch);
     } else if (event instanceof CommentAddedEvent) {
-      return isEnabled(((CommentAddedEvent) event).change.project);
+      CommentAddedEvent e = (CommentAddedEvent) event;
+      return isEnabled(e.change.project, e.change.branch);
     } else if (event instanceof ChangeMergedEvent) {
-      return isEnabled(((ChangeMergedEvent) event).change.project);
+      ChangeMergedEvent e = (ChangeMergedEvent) event;
+      return isEnabled(e.change.project, e.change.branch);
     } else if (event instanceof ChangeAbandonedEvent) {
-      return isEnabled(((ChangeAbandonedEvent) event).change.project);
+      ChangeAbandonedEvent e = (ChangeAbandonedEvent) event;
+      return isEnabled(e.change.project, e.change.branch);
     } else if (event instanceof ChangeRestoredEvent) {
-      return isEnabled(((ChangeRestoredEvent) event).change.project);
+      ChangeRestoredEvent e = (ChangeRestoredEvent) event;
+      return isEnabled(e.change.project, e.change.branch);
     } else if (event instanceof RefUpdatedEvent) {
-      return isEnabled(((RefUpdatedEvent) event).refUpdate.project);
+      RefUpdatedEvent e = (RefUpdatedEvent) event;
+      return isEnabled(e.refUpdate.project, e.refUpdate.refName);
     } else {
       log.debug("Event " + event + " not recognised and ignored");
       return false;
     }
   }
 
-  public boolean isEnabled(String project) {
+  public boolean isEnabled(String project, String branch) {
     ProjectState projectState = projectCache.get(new Project.NameKey(project));
     if (projectState == null) {
       log.error("Failed to check if " + pluginName + " is enabled for project "
@@ -77,12 +89,34 @@
     for (ProjectState parentState : projectState.treeInOrder()) {
       PluginConfig parentCfg =
           pluginCfgFactory.getFromProjectConfig(parentState, pluginName);
-      if ("enforced".equals(parentCfg.getString("enabled"))) {
+      if ("enforced".equals(parentCfg.getString("enabled"))
+          && isEnabledForBranch(parentState, branch)) {
         return true;
       }
     }
 
     return pluginCfgFactory.getFromProjectConfigWithInheritance(
-        projectState, pluginName).getBoolean("enabled", false);
+        projectState, pluginName).getBoolean("enabled", false)
+        && isEnabledForBranch(projectState, branch);
+  }
+
+  private boolean isEnabledForBranch(ProjectState project, String branch) {
+    String[] refPatterns =
+        pluginCfgFactory.getFromProjectConfigWithInheritance(project,
+            pluginName).getStringList("branch");
+    if (refPatterns.length == 0) {
+      return true;
+    }
+    for (String refPattern : refPatterns) {
+      if (RefConfigSection.isValid(refPattern) && match(branch, refPattern)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private boolean match(String branch, String refPattern) {
+    return RefPatternMatcher.getMatcher(refPattern)
+        .match(branch, self.get().getUserName());
   }
 }
diff --git a/its-base/src/main/java/com/googlesource/gerrit/plugins/hooks/validation/ItsValidateComment.java b/its-base/src/main/java/com/googlesource/gerrit/plugins/hooks/validation/ItsValidateComment.java
index bf00f13..f47889e 100644
--- a/its-base/src/main/java/com/googlesource/gerrit/plugins/hooks/validation/ItsValidateComment.java
+++ b/its-base/src/main/java/com/googlesource/gerrit/plugins/hooks/validation/ItsValidateComment.java
@@ -154,7 +154,7 @@
   @Override
   public List<CommitValidationMessage> onCommitReceived(
       CommitReceivedEvent receiveEvent) throws CommitValidationException {
-    if (itsConfig.isEnabled(receiveEvent.project.getName())) {
+    if (itsConfig.isEnabled(receiveEvent.project.getName(), receiveEvent.refName)) {
       return validCommit(receiveEvent.command, receiveEvent.commit);
     } else {
       return Collections.emptyList();
diff --git a/its-base/src/main/resources/Documentation/config.md b/its-base/src/main/resources/Documentation/config.md
index f0de506..2b50952 100644
--- a/its-base/src/main/resources/Documentation/config.md
+++ b/its-base/src/main/resources/Documentation/config.md
@@ -55,6 +55,21 @@
 integration for all child projects by setting
 `plugin.<its-name>.enabled` to `enforced`.
 
+The issue tracker system integration can be limited to specific
+branches by setting `plugin.<its-name>.branch`. The branches may be
+configured using explicit branch names, ref patterns, or regular
+expressions. Multiple branches may be specified.
+
+E.g. to limit the issue tracker system integration to the `master`
+branch and all stable branches the following could be configured:
+
+```
+  [plugin "<its-name>"]
+    enabled = true
+    branch = refs/heads/master
+    branch = ^refs/heads/stable-.*
+```
+
 
 [[config-rule-base]]
 Rule base for Actions
diff --git a/its-base/src/test/java/com/googlesource/gerrit/plugins/hooks/validation/ItsValidateCommentTest.java b/its-base/src/test/java/com/googlesource/gerrit/plugins/hooks/validation/ItsValidateCommentTest.java
index beb4a9a..eee33cc 100644
--- a/its-base/src/test/java/com/googlesource/gerrit/plugins/hooks/validation/ItsValidateCommentTest.java
+++ b/its-base/src/test/java/com/googlesource/gerrit/plugins/hooks/validation/ItsValidateCommentTest.java
@@ -512,9 +512,9 @@
       itsFacade = createMock(ItsFacade.class);
       bind(ItsFacade.class).toInstance(itsFacade);
 
-      bind(ItsConfig.class).toInstance(new ItsConfig(null, null, null) {
+      bind(ItsConfig.class).toInstance(new ItsConfig(null, null, null, null) {
         @Override
-        public boolean isEnabled(String project) {
+        public boolean isEnabled(String project, String branch) {
           return true;
         }
       });
diff --git a/its-base/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionControllerTest.java b/its-base/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionControllerTest.java
index 7266d81..9668d7d 100644
--- a/its-base/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionControllerTest.java
+++ b/its-base/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionControllerTest.java
@@ -198,7 +198,7 @@
       actionExecutor = createMock(ActionExecutor.class);
       bind(ActionExecutor.class).toInstance(actionExecutor);
 
-      bind(ItsConfig.class).toInstance(new ItsConfig(null, null, null) {
+      bind(ItsConfig.class).toInstance(new ItsConfig(null, null, null, null) {
         @Override
         public boolean isEnabled(ChangeEvent event) {
           return true;