Allow tasks to define a duplicate key to identify more duplicates
This allows more taks to be viewed as duplicates at the task
configurators discretion. This can be useful when defining
the key = ${_change_number} to prevent looping on tasks
which preload the same task. This also allows similar "end"
tasks to be defined to behave as if they had looped.
Change-Id: Id9f0311ca3709bd875e2017a4af42938ae83fded
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/TaskConfig.java b/src/main/java/com/googlesource/gerrit/plugins/task/TaskConfig.java
index 698b33a..47283a3 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/task/TaskConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/task/TaskConfig.java
@@ -55,6 +55,7 @@
public class TaskBase extends SubSection {
public String applicable;
+ public String duplicateKey;
public Map<String, String> exported;
public String fail;
public String failHint;
@@ -76,6 +77,7 @@
this.isVisible = isVisible;
this.isTrusted = isTrusted;
applicable = getString(s, KEY_APPLICABLE, null);
+ duplicateKey = getString(s, KEY_DUPLICATE_KEY, null);
exported = getProperties(s, KEY_EXPORT_PREFIX);
fail = getString(s, KEY_FAIL, null);
failHint = getString(s, KEY_FAIL_HINT, null);
@@ -194,6 +196,7 @@
protected static final String SECTION_TASKS_FACTORY = "tasks-factory";
protected static final String KEY_APPLICABLE = "applicable";
protected static final String KEY_CHANGES = "changes";
+ protected static final String KEY_DUPLICATE_KEY = "duplicate-key";
protected static final String KEY_EXPORT_PREFIX = "export-";
protected static final String KEY_FAIL = "fail";
protected static final String KEY_FAIL_HINT = "fail-hint";
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/TaskTree.java b/src/main/java/com/googlesource/gerrit/plugins/task/TaskTree.java
index d08a68e..5461d43 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/task/TaskTree.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/task/TaskTree.java
@@ -104,12 +104,14 @@
throws ConfigInvalidException, IOException, OrmException {
this.changeData = changeData;
root.path = Collections.emptyList();
+ root.duplicateKeys = Collections.emptyList();
return root.getSubNodes();
}
protected class NodeList {
protected NodeList parent = null;
protected Collection<String> path;
+ protected Collection<String> duplicateKeys;
protected Map<TaskKey, Node> cachedNodeByTask = new HashMap<>();
protected List<Node> nodes;
protected Set<String> names = new HashSet<>();
@@ -214,6 +216,12 @@
this.task = properties.getTask(getChangeData());
+ this.duplicateKeys = new LinkedList<>(parent.duplicateKeys);
+ if (task.duplicateKey != null) {
+ isDuplicate |= duplicateKeys.contains(task.duplicateKey);
+ duplicateKeys.add(task.duplicateKey);
+ }
+
if (nodes != null && properties.isSubNodeReloadRequired()) {
cachedNodeByTask.clear();
nodes.stream().filter(n -> n != null).forEach(n -> cachedNodeByTask.put(n.task.key(), n));
diff --git a/src/main/resources/Documentation/task.md b/src/main/resources/Documentation/task.md
index 3f2166e..ce2dd40 100644
--- a/src/main/resources/Documentation/task.md
+++ b/src/main/resources/Documentation/task.md
@@ -237,6 +237,48 @@
subtasks-file = common.config # references the file named task/common.config
```
+`duplicate-key`
+
+: This key defines an identifier to help identify tasks which should be
+considered duplicates even if they are not exact duplicates. When the task
+plugin encounters a task with the same duplicate-key as one of its
+ancestors, it will be considered a duplicate of that ancestor. Tasks such as
+a starting task and a looping tasks-factory that preload the same base task
+are not exact duplicates, yet they may logically represent duplicates. In
+this case, defining a `duplicate-key` on the base task which is preloaded
+from two different places (usually a root and a change tasks-factory), will
+ensure that any loops are halted once the original change is reached. Without
+a duplicate-key, the walking would generally walk one task further than
+desired.
+
+Outlined below is a simple way to walk a change's git dependencies in the
+task plugin. While Git does not allow loops in commit histories, sometimes
+in Gerrit when changes get rebased, it can cause loops (because Gerrit
+sometimes tracks outdated dependencies). The use of the duplicate-key
+below results in the loop being detected when you would expect it to be.
+
+Example:
+
+```
+[root "git dependencies"]
+ applicable = status:new
+ preload-task = git dependencies
+
+[task "git dependencies"]
+ fail = -status:new
+ fail-hint = [${_change_status}] dependency needs to be OPEN
+ subtasks-factory = git dependencies
+ duplicate-key = git dependencies ${_change_number}
+
+[tasks-factory "git dependencies"]
+ names-factory = git dependencies
+ preload-task = git dependencies
+
+[names-factory "git dependencies"]
+ type = change
+ changes = -status:merged parentof:${_change_number} project:${_change_project} branch:${_change_branch}
+```
+
Root Tasks
----------
Root tasks typically define the "final verification" tasks for changes. Each
diff --git a/src/main/resources/Documentation/test/task_states.md b/src/main/resources/Documentation/test/task_states.md
index 3b5d034..fede191 100644
--- a/src/main/resources/Documentation/test/task_states.md
+++ b/src/main/resources/Documentation/test/task_states.md
@@ -1845,6 +1845,34 @@
]
}
+[root "Root Looping DuplicateKey"]
+ preload-task = DuplicateKey
+
+[task "Looping DuplicateKey"]
+ preload-task = DuplicateKey
+ pass = True
+
+[task "DuplicateKey"]
+ duplicate-key = 1234
+ subtask = Looping DuplicateKey
+
+
+{
+ "applicable" : true,
+ "hasPass" : false,
+ "name" : "Root Looping DuplicateKey",
+ "status" : "PASS",
+ "subTasks" : [
+ {
+ "applicable" : true,
+ "hasPass" : false,
+ "hint" : "Duplicate task is non blocking and empty to break the loop",
+ "name" : "Looping DuplicateKey",
+ "status" : "DUPLICATE"
+ }
+ ]
+}
+
[root "Root changes loop"]
subtask = task (tasks-factory changes loop)