Support importing tasks from All-Projects:refs/meta/config

This change adds support to import common tasks located from
All-Projects:refs/meta/config. See docs for syntax.

Change-Id: I83665cd0c8dc79c1b804c3b84aa51864446ceeee
diff --git a/src/main/antlr4/com/googlesource/gerrit/plugins/task/TaskReference.g4 b/src/main/antlr4/com/googlesource/gerrit/plugins/task/TaskReference.g4
index b360040..fc461d7 100644
--- a/src/main/antlr4/com/googlesource/gerrit/plugins/task/TaskReference.g4
+++ b/src/main/antlr4/com/googlesource/gerrit/plugins/task/TaskReference.g4
@@ -17,6 +17,7 @@
  * This file defines the grammar used for Task Reference
  *
  * TASK_REFERENCE = [
+ *                    [ // TASK_FILE_PATH ] |
  *                    [ @USERNAME [ TASK_FILE_PATH ] ] |
  *                    [ TASK_FILE_PATH ]
  *                  ] '^' TASK_NAME
@@ -53,6 +54,15 @@
  * Implied task:
  *     file: All-Users:refs/users/<jim>:task/foo^simple task: sample
  *
+ * file: Any projects, ref, file
+ * reference: //foo.config^sample
+ * Implied task:
+ *     file: All-Projects:refs/meta/config:task/foo task: sample
+ *
+ * file: Any projects, ref, file
+ * reference: //^simple
+ * Implied task:
+ *     file: All-Projects:refs/meta/config:task.config task: sample
  */
 
 grammar TaskReference;
@@ -66,7 +76,9 @@
   ;
 
 file_path
- : user absolute? TASK_DELIMETER
+ : ALL_PROJECTS_ROOT
+ | FWD_SLASH absolute TASK_DELIMETER
+ | user absolute? TASK_DELIMETER
  | (absolute| relative)? TASK_DELIMETER
  ;
 
@@ -75,7 +87,7 @@
  ;
 
 absolute
- : '/' relative
+ : FWD_SLASH relative
  ;
 
 relative
@@ -83,7 +95,7 @@
  ;
 
 dir
- : (NAME '/')
+ : (NAME FWD_SLASH)
  ;
 
 TASK
@@ -110,3 +122,11 @@
 TASK_DELIMETER
  : '^'
  ;
+
+ALL_PROJECTS_ROOT
+ : FWD_SLASH FWD_SLASH TASK_DELIMETER
+ ;
+
+FWD_SLASH
+ : '/'
+ ;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/TaskKey.java b/src/main/java/com/googlesource/gerrit/plugins/task/TaskKey.java
index 8cddac0..a6bc7e1 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/task/TaskKey.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/task/TaskKey.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.entities.BranchNameKey;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.AllUsersName;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -59,14 +60,20 @@
 
   public static class Builder {
     protected final AccountCache accountCache;
+    protected final AllProjectsName allProjectsName;
     protected final AllUsersName allUsersName;
     protected final FileKey relativeTo;
     protected BranchNameKey branch;
     protected String file;
     protected String task;
 
-    Builder(FileKey relativeTo, AllUsersName allUsersName, AccountCache accountCache) {
+    Builder(
+        FileKey relativeTo,
+        AllProjectsName allProjectsName,
+        AllUsersName allUsersName,
+        AccountCache accountCache) {
       this.relativeTo = relativeTo;
+      this.allProjectsName = allProjectsName;
       this.allUsersName = allUsersName;
       this.accountCache = accountCache;
     }
@@ -127,6 +134,10 @@
                       .id()));
     }
 
+    public void setReferringAllProjectsTask() {
+      branch = BranchNameKey.create(allProjectsName, RefNames.REFS_CONFIG);
+    }
+
     protected void throwIfInvalidPath() throws ConfigInvalidException {
       Path path = Paths.get(file);
       if (!path.startsWith(TaskFileConstants.TASK_DIR)
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/TaskReference.java b/src/main/java/com/googlesource/gerrit/plugins/task/TaskReference.java
index 308bae8..91c6f10 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/task/TaskReference.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/task/TaskReference.java
@@ -16,6 +16,7 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
 import com.google.gerrit.server.config.AllUsersNameProvider;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
@@ -42,11 +43,15 @@
 
   @Inject
   public TaskReference(
+      AllProjectsNameProvider allProjectsNameProvider,
       AllUsersNameProvider allUsersNameProvider,
       AccountCache accountCache,
       @Assisted FileKey relativeTo,
       @Assisted String reference) {
-    this(new TaskKey.Builder(relativeTo, allUsersNameProvider.get(), accountCache), reference);
+    this(
+        new TaskKey.Builder(
+            relativeTo, allProjectsNameProvider.get(), allUsersNameProvider.get(), accountCache),
+        reference);
   }
 
   @VisibleForTesting
@@ -122,6 +127,10 @@
 
     @Override
     public void enterFile_path(TaskReferenceParser.File_pathContext ctx) {
+      if (ctx.ALL_PROJECTS_ROOT() != null || (ctx.FWD_SLASH() != null && ctx.absolute() != null)) {
+        builder.setReferringAllProjectsTask();
+      }
+
       if (ctx.absolute() == null && ctx.relative() == null) {
         try {
           builder.setRefRootFile();
diff --git a/src/main/resources/Documentation/task_expression.md b/src/main/resources/Documentation/task_expression.md
index 498d2b4..66afaf8 100644
--- a/src/main/resources/Documentation/task_expression.md
+++ b/src/main/resources/Documentation/task_expression.md
@@ -38,6 +38,7 @@
 
 ```
  TASK_REFERENCE = [
+                    [ // TASK_FILE_PATH ]
                     [ @USERNAME [ TASK_FILE_PATH ] ] |
                     [ TASK_FILE_PATH ]
                   ] '^' TASK_NAME
@@ -150,3 +151,33 @@
     preload-task = @user_a_username/dir/common.config^common task
     ...
 ```
+
+To reference a task from root task.config on the All-Projects.git, prefix the task name with `//^`
+and to reference a task from task dir on the All-Projects.git, use
+`//<relative path from task dir>^<task_name>`. It doesn't matter which project, ref and file one
+is referencing from while using this syntax.
+
+Example:
+
+All-Projects:refs/meta/config:task.config
+```
+    ...
+    [task "root task"]
+    ...
+```
+
+All-Projects:refs/meta/config:/task/dir/sample.config
+
+```
+    ...
+    [task "sample task"]
+    ...
+```
+
+All-Users:refs/users/00/1000000:task.config
+```
+    ...
+    preload-task = //dir/sample.config^sample task
+    preload-task = //^root task
+    ...
+```
diff --git a/src/main/resources/Documentation/test/task_states.md b/src/main/resources/Documentation/test/task_states.md
index e325a9a..315b05f 100644
--- a/src/main/resources/Documentation/test/task_states.md
+++ b/src/main/resources/Documentation/test/task_states.md
@@ -2298,6 +2298,55 @@
    ]
 }
 
+[root "Root Reference tasks from All-Projects"]
+  applicable = is:open
+  subtask = //^Subtask PASS
+  subtask = @testuser/dir/relative.config^Import All-Projects root task
+  subtask = @testuser/dir/relative.config^Import All-Projects non-root task
+
+{
+   "applicable" : true,
+   "hasPass" : false,
+   "name" : "Root Reference tasks from All-Projects",
+   "status" : "PASS",
+   "subTasks" : [
+      {
+         "applicable" : true,
+         "hasPass" : true,
+         "name" : "Subtask PASS",
+         "status" : "PASS"
+      },
+      {
+         "applicable" : true,
+         "hasPass" : false,
+         "name" : "Import All-Projects root task",
+         "status" : "PASS",
+         "subTasks" : [
+            {
+               "applicable" : true,
+               "hasPass" : true,
+               "name" : "Subtask PASS",
+               "status" : "PASS"
+            }
+         ]
+      },
+      {
+         "applicable" : true,
+         "hasPass" : false,
+         "name" : "Import All-Projects non-root task",
+         "status" : "PASS",
+         "subTasks" : [
+            {
+               "applicable" : true,
+               "hasPass" : true,
+               "name" : "Sample relative task in sub dir",
+               "status" : "PASS"
+            }
+         ]
+      }
+   ]
+}
+
 [root "Root INVALID Preload"]
   preload-task = missing
 
@@ -3114,6 +3163,14 @@
 [task "Relative Task"]
   applicable = is:open
   pass = is:open
+
+[task "Import All-Projects root task"]
+  applicable = is:open
+  subtask = //^Subtask PASS
+
+[task "Import All-Projects non-root task"]
+  applicable = is:open
+  subtask = //dir/common.config^Sample relative task in sub dir
 ```
 
 file: `All-Users:refs/users/self:task/dir/sub_dir/relative.config`
diff --git a/src/test/java/com/googlesource/gerrit/plugins/task/TaskExpressionTest.java b/src/test/java/com/googlesource/gerrit/plugins/task/TaskExpressionTest.java
index fe01ee2..87042a9 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/task/TaskExpressionTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/task/TaskExpressionTest.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.entities.BranchNameKey;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.AllUsersName;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
@@ -239,6 +240,7 @@
                 new TaskReference(
                     new TaskKey.Builder(
                         (FileKey) invocation.getArguments()[0],
+                        new AllProjectsName("All-Projects"),
                         new AllUsersName("All-Users"),
                         accountCache),
                     (String) invocation.getArguments()[1]));
diff --git a/src/test/java/com/googlesource/gerrit/plugins/task/TaskReferenceTest.java b/src/test/java/com/googlesource/gerrit/plugins/task/TaskReferenceTest.java
index 2036f16..9da1f44 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/task/TaskReferenceTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/task/TaskReferenceTest.java
@@ -17,8 +17,10 @@
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.BranchNameKey;
 import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.AllUsersName;
 import java.sql.Timestamp;
 import java.util.NoSuchElementException;
@@ -30,14 +32,17 @@
 
 public class TaskReferenceTest extends TestCase {
   private static final String ALL_USERS = "All-Users";
-
+  private static final String ALL_PROJECTS = "All-Projects";
   public static String SIMPLE = "simple";
   public static String ROOT = "task.config";
   public static String COMMON = "task/common.config";
   public static String SUB_COMMON = "task/dir/common.config";
-  public static FileKey ROOT_CFG = createFileKey("project", "branch", ROOT);
-  public static FileKey COMMON_CFG = createFileKey("project", "branch", COMMON);
-  public static FileKey SUB_COMMON_CFG = createFileKey("project", "branch", SUB_COMMON);
+  public static FileKey ROOT_CFG = createFileKey(ALL_PROJECTS, RefNames.REFS_CONFIG, ROOT);
+  public static FileKey COMMON_CFG = createFileKey(ALL_PROJECTS, RefNames.REFS_CONFIG, COMMON);
+  public static FileKey SUB_COMMON_CFG =
+      createFileKey(ALL_PROJECTS, RefNames.REFS_CONFIG, SUB_COMMON);
+
+  public static FileKey SAMPLE_PROJ_CFG = createFileKey("foo", RefNames.REFS_CONFIG, ROOT);
 
   public static final String TEST_USER = "testuser";
   public static final int TEST_USER_ID = 100000;
@@ -93,6 +98,19 @@
   }
 
   @Test
+  public void testReferencingRootAllProjectsTask() throws Exception {
+    String reference = "//^" + SIMPLE;
+    assertEquals(createTaskKey(ROOT_CFG, SIMPLE), getTaskFromReference(SAMPLE_PROJ_CFG, reference));
+  }
+
+  @Test
+  public void testReferencingAllProjectsTask() throws Exception {
+    String reference = "//common.config^" + SIMPLE;
+    assertEquals(
+        createTaskKey(COMMON_CFG, SIMPLE), getTaskFromReference(SAMPLE_PROJ_CFG, reference));
+  }
+
+  @Test
   public void testReferencingRootUserTask() throws Exception {
     String reference = "@" + TEST_USER + "^" + SIMPLE;
     assertEquals(
@@ -125,7 +143,12 @@
         .thenReturn(Optional.of(AccountState.forAccount(TEST_USER_ACCOUNT)));
     try {
       return new TaskReference(
-              new TaskKey.Builder(file, new AllUsersName(ALL_USERS), accountCache), expression)
+              new TaskKey.Builder(
+                  file,
+                  new AllProjectsName(ALL_PROJECTS),
+                  new AllUsersName(ALL_USERS),
+                  accountCache),
+              expression)
           .getTaskKey();
     } catch (ConfigInvalidException e) {
       throw new NoSuchElementException();