Add an option to filter roots

Allow filtering roots as it can help CI systems wanting to evaluate
only tasks under roots they own for improved query performance.

Change-Id: I566744ab4a9633b4ac9c8b0bfa561009ba8e3996
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/Modules.java b/src/main/java/com/googlesource/gerrit/plugins/task/Modules.java
index 6bedb5e..a97abf7 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/task/Modules.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/task/Modules.java
@@ -57,6 +57,13 @@
   }
 
   public static class MyOptions implements DynamicBean {
+    @Option(
+        name = "--only",
+        usage =
+            "Evaluate tasks under this task only. Only root task names are supported."
+                + " This option can be provided multiple times.")
+    public List<String> includedRoots = new ArrayList<>();
+
     @Option(name = "--all", usage = "Include all visible tasks in the output")
     public boolean all = false;
 
@@ -97,5 +104,9 @@
     public MyOptions(PatchSetArgument.Factory patchSetArgumentFactory) {
       this.patchSetArgumentFactory = patchSetArgumentFactory;
     }
+
+    public boolean shouldFilterRoot(String rootName) {
+      return !includedRoots.isEmpty() && !includedRoots.contains(rootName);
+    }
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/TaskAttributeFactory.java b/src/main/java/com/googlesource/gerrit/plugins/task/TaskAttributeFactory.java
index 6262420..90454f7 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/task/TaskAttributeFactory.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/task/TaskAttributeFactory.java
@@ -147,11 +147,14 @@
   protected PluginDefinedInfo createWithExceptions(ChangeData c) {
     TaskPluginAttribute a = new TaskPluginAttribute();
     try {
-      for (Node node : definitions.getRootNodes(c)) {
-        if (node instanceof Node.Invalid) {
+      for (Node root : definitions.getRootNodes(c)) {
+        if (root instanceof Node.Invalid) {
           a.roots.add(invalid());
         } else {
-          new AttributeFactory(node).create().ifPresent(t -> a.roots.add(t));
+          if (options.shouldFilterRoot(root.task.name())) {
+            continue;
+          }
+          new AttributeFactory(root).create().ifPresent(t -> a.roots.add(t));
         }
       }
     } catch (ConfigInvalidException | IOException | StorageException e) {
diff --git a/src/main/resources/Documentation/task.md b/src/main/resources/Documentation/task.md
index 7a0cf1e..75ee189 100644
--- a/src/main/resources/Documentation/task.md
+++ b/src/main/resources/Documentation/task.md
@@ -588,6 +588,13 @@
         status: PASS
 ```
 
+**\-\-@PLUGIN@\-\-only**
+
+This switch can be used to only evaluate tasks under a certain root when tasks
+from other roots are unwanted. For example, a CI system may not be interested
+in evaluating tasks for another CI system. The switch can be provided multiple
+times.
+
 Examples
 --------
 See [task_states](test/task_states.html) for a comprehensive list of examples
diff --git a/src/main/resources/Documentation/test/task_states.md b/src/main/resources/Documentation/test/task_states.md
index 709de95..e3f1834 100644
--- a/src/main/resources/Documentation/test/task_states.md
+++ b/src/main/resources/Documentation/test/task_states.md
@@ -56,12 +56,12 @@
 [root "Root PASS"]
   pass = True
 
-{
-   "applicable" : true,
-   "hasPass" : true,
-   "name" : "Root PASS",
-   "status" : "PASS"
-}
+{                              # Test Suite: task_only
+   "applicable" : true,        # Test Suite: task_only
+   "hasPass" : true,           # Test Suite: task_only
+   "name" : "Root PASS",       # Test Suite: task_only
+   "status" : "PASS"           # Test Suite: task_only
+}                              # Test Suite: task_only
 
 [root "Root FAIL"]
   fail = True
@@ -2620,10 +2620,10 @@
 [root "Root INVALID Preload"]
   preload-task = missing
 
-{
-   "name" : "UNKNOWN",
-   "status" : "INVALID"
-}
+{                         # Test Suite: task_only
+   "name" : "UNKNOWN",    # Test Suite: task_only
+   "status" : "INVALID"   # Test Suite: task_only
+}                         # Test Suite: task_only
 
 [root "INVALIDS"]
   subtasks-file = invalids.config
diff --git a/test/check_task_statuses.sh b/test/check_task_statuses.sh
index 1e262b3..30e2ece 100755
--- a/test/check_task_statuses.sh
+++ b/test/check_task_statuses.sh
@@ -249,6 +249,10 @@
 test_generated preview-non-secret -l "$NON_SECRET_USER" --task--preview "$cnum,1" --task--all "$query"
 test_generated preview-invalid --task--preview "$cnum,1" --task--invalid "$query"
 
+example "$DOC_STATES" 2 | keep_suites "task_only" | testdoc_2_pjson | \
+    ensure json_pp > "$EXPECTED".task-roots-filter
+test_generated task-roots-filter --task--all --task--only "Root\ PASS" "$query"
+
 example "$DOC_PATHS" 1 | testdoc_2_cfg | replace_user > "$ROOT_CFG"
 q_setup update_repo "$ALL" "$REMOTE_ALL" "$REF_ALL"
 ROOTS=$(config_section_keys "root")
diff --git a/test/lib/lib_helper.sh b/test/lib/lib_helper.sh
index efb6de9..f4006db 100644
--- a/test/lib/lib_helper.sh
+++ b/test/lib/lib_helper.sh
@@ -59,7 +59,17 @@
 
 remove_suites() { # suites... < pre_json > json
     grep -vE "# Only Test Suite: ($(echo "$@" | sed "s/ /|/g"))" | \
-         sed -e's/# Only Test Suite:.*$//; s/ *$//'
+         sed -e's/# Only Test Suite:.*$//; s/# Test Suite:.*$//; s/ *$//'
+}
+
+# pre_json is a "templated json" used in the test docs to express test results. It looks
+# like json but has some extra comments to express when a certain output should be used.
+# These comments look like: "# Test Suite: <suite>[, <suite>][, <suite>]..."
+#
+
+keep_suites() { # suites... < pre_json > json
+    grep -E "# Test Suite: (.*, )?($(echo "$@" | sed "s/ /|/g"))(, .*)?$" | \
+         sed -e's/# Only Test Suite:.*$//; s/# Test Suite:.*$//; s/ *$//'
 }
 
 remove_not_suite() { remove_suites !"$1" ; } # suite < pre_json > json