Merge branch 'stable-3.1'
* stable-3.1:
Format files with google-java-format
Change-Id: I5617d4ebf7b283a334a77c79fdb7379dd1e87896
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/ItsHookModule.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/ItsHookModule.java
index dc6f4a5..8835389 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/ItsHookModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/ItsHookModule.java
@@ -38,9 +38,11 @@
import com.googlesource.gerrit.plugins.its.base.workflow.Condition;
import com.googlesource.gerrit.plugins.its.base.workflow.CreateVersionFromProperty;
import com.googlesource.gerrit.plugins.its.base.workflow.CustomAction;
+import com.googlesource.gerrit.plugins.its.base.workflow.FireEventOnCommits;
import com.googlesource.gerrit.plugins.its.base.workflow.ItsRulesProjectCacheImpl;
import com.googlesource.gerrit.plugins.its.base.workflow.LogEvent;
import com.googlesource.gerrit.plugins.its.base.workflow.Rule;
+import com.googlesource.gerrit.plugins.its.base.workflow.commit_collector.SinceLastTagCommitCollector;
import java.nio.file.Path;
public class ItsHookModule extends FactoryModule {
@@ -78,6 +80,8 @@
factory(AddPropertyToField.Factory.class);
DynamicMap.mapOf(binder(), CustomAction.class);
install(ItsRulesProjectCacheImpl.module());
+ factory(FireEventOnCommits.Factory.class);
+ factory(SinceLastTagCommitCollector.Factory.class);
}
@Provides
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/its/ItsConfig.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/its/ItsConfig.java
index 08f73c5..20c6c4a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/its/ItsConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/its/ItsConfig.java
@@ -94,16 +94,17 @@
}
public boolean isEnabled(Project.NameKey projectNK, String refName) {
- ProjectState projectState = projectCache.get(projectNK);
- if (projectState == null) {
+ Optional<ProjectState> projectState = projectCache.get(projectNK);
+ if (!projectState.isPresent()) {
log.error(
"Failed to check if {} is enabled for project {}: Project not found",
pluginName,
projectNK.get());
return false;
}
- return isEnforcedByAnyParentProject(refName, projectState)
- || (isEnabledForProject(projectState) && isEnabledForBranch(projectState, refName));
+ return isEnforcedByAnyParentProject(refName, projectState.get())
+ || (isEnabledForProject(projectState.get())
+ && isEnabledForBranch(projectState.get(), refName));
}
private boolean isEnforcedByAnyParentProject(String refName, ProjectState projectState) {
@@ -147,12 +148,14 @@
// Project association
public Optional<String> getItsProjectName(Project.NameKey projectNK) {
- ProjectState projectState = projectCache.get(projectNK);
- if (projectState == null) {
+ Optional<ProjectState> projectState = projectCache.get(projectNK);
+ if (!projectState.isPresent()) {
return Optional.empty();
}
return Optional.ofNullable(
- pluginCfgFactory.getFromProjectConfig(projectState, pluginName).getString("its-project"));
+ pluginCfgFactory
+ .getFromProjectConfig(projectState.get(), pluginName)
+ .getString("its-project"));
}
// Issue association --------------------------------------------------------
@@ -266,7 +269,7 @@
private List<CommentLinkInfo> getCommentLinkInfo(final String commentlinkName) {
NameKey projectName = currentProjectName.get();
if (projectName != null) {
- List<CommentLinkInfo> commentlinks = projectCache.get(projectName).getCommentLinks();
+ List<CommentLinkInfo> commentlinks = projectCache.get(projectName).get().getCommentLinks();
return commentlinks.stream()
.filter(input -> input.name.equals(commentlinkName))
.collect(toList());
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/util/PropertyExtractor.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/util/PropertyExtractor.java
index 926db56..bf9dc0c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/util/PropertyExtractor.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/util/PropertyExtractor.java
@@ -34,6 +34,7 @@
import com.google.gerrit.server.events.WorkInProgressStateChangedEvent;
import com.google.inject.Inject;
import com.googlesource.gerrit.plugins.its.base.workflow.RefEventProperties;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -230,16 +231,33 @@
associations = extractFrom((WorkInProgressStateChangedEvent) event, common);
}
- Set<Map<String, String>> ret = new HashSet<>();
- if (associations != null) {
- for (Entry<String, Set<String>> assoc : associations.entrySet()) {
- Map<String, String> properties = new HashMap<>();
- properties.put("issue", assoc.getKey());
- properties.put("association", String.join(" ", assoc.getValue()));
- properties.putAll(common);
- ret.add(properties);
- }
+ Set<Map<String, String>> issuesProperties = extractIssuesProperties(common, associations);
+
+ Map<String, String> projectProperties = new HashMap<>(common);
+ projectProperties.put("source", "gerrit");
+ return new RefEventProperties(projectProperties, issuesProperties);
+ }
+
+ public Set<Map<String, String>> extractIssuesProperties(
+ Map<String, String> commonProperties, Map<String, Set<String>> associations) {
+ if (associations == null) {
+ return Collections.emptySet();
}
- return new RefEventProperties(common, ret);
+
+ Set<Map<String, String>> issuesProperties = new HashSet<>();
+ Map<String, String> completedCommonProperties = new HashMap<>(commonProperties);
+ if (!completedCommonProperties.containsKey("source")) {
+ completedCommonProperties.put("source", "its");
+ }
+
+ for (Entry<String, Set<String>> assoc : associations.entrySet()) {
+ Map<String, String> properties = new HashMap<>();
+ properties.put("issue", assoc.getKey());
+ properties.put("association", String.join(" ", assoc.getValue()));
+ properties.putAll(completedCommonProperties);
+ issuesProperties.add(properties);
+ }
+
+ return issuesProperties;
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionExecutor.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionExecutor.java
index b8682ad..44f5d73 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionExecutor.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionExecutor.java
@@ -36,6 +36,7 @@
private final LogEvent.Factory logEventFactory;
private final AddPropertyToField.Factory addPropertyToFieldFactory;
private final CreateVersionFromProperty.Factory createVersionFromPropertyFactory;
+ private final FireEventOnCommits.Factory fireEventOnCommitsFactory;
private final DynamicMap<CustomAction> customActions;
@Inject
@@ -47,6 +48,7 @@
LogEvent.Factory logEventFactory,
AddPropertyToField.Factory addPropertyToFieldFactory,
CreateVersionFromProperty.Factory createVersionFromPropertyFactory,
+ FireEventOnCommits.Factory fireEventOnCommitsFactory,
DynamicMap<CustomAction> customActions) {
this.itsFactory = itsFactory;
this.addCommentFactory = addCommentFactory;
@@ -55,6 +57,7 @@
this.logEventFactory = logEventFactory;
this.addPropertyToFieldFactory = addPropertyToFieldFactory;
this.createVersionFromPropertyFactory = createVersionFromPropertyFactory;
+ this.fireEventOnCommitsFactory = fireEventOnCommitsFactory;
this.customActions = customActions;
}
@@ -72,6 +75,8 @@
return addPropertyToFieldFactory.create();
case "create-version-from-property":
return createVersionFromPropertyFactory.create();
+ case "fire-event-on-commits":
+ return fireEventOnCommitsFactory.create();
default:
return customActions.get(PluginName.GERRIT, actionName);
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/FireEventOnCommits.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/FireEventOnCommits.java
new file mode 100644
index 0000000..7ade76b
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/FireEventOnCommits.java
@@ -0,0 +1,89 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.its.base.workflow;
+
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.its.base.its.ItsFacade;
+import com.googlesource.gerrit.plugins.its.base.util.IssueExtractor;
+import com.googlesource.gerrit.plugins.its.base.util.PropertyExtractor;
+import com.googlesource.gerrit.plugins.its.base.workflow.commit_collector.CommitCollector;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/** Fires the triggering event on collected past commits */
+public class FireEventOnCommits extends ProjectAction {
+
+ public interface Factory {
+ FireEventOnCommits create();
+ }
+
+ private final PropertyExtractor propertyExtractor;
+ private final IssueExtractor issueExtractor;
+ private final RuleBase ruleBase;
+ private final ActionExecutor actionExecutor;
+ private final FireEventOnCommitsParametersExtractor parametersExtractor;
+
+ @Inject
+ public FireEventOnCommits(
+ PropertyExtractor propertyExtractor,
+ IssueExtractor issueExtractor,
+ RuleBase ruleBase,
+ ActionExecutor actionExecutor,
+ FireEventOnCommitsParametersExtractor parametersExtractor) {
+ this.propertyExtractor = propertyExtractor;
+ this.issueExtractor = issueExtractor;
+ this.ruleBase = ruleBase;
+ this.actionExecutor = actionExecutor;
+ this.parametersExtractor = parametersExtractor;
+ }
+
+ @Override
+ public void execute(
+ ItsFacade its, String itsProject, ActionRequest actionRequest, Map<String, String> properties)
+ throws IOException {
+ Optional<FireEventOnCommitsParameters> extractedParameters =
+ parametersExtractor.extract(actionRequest, properties);
+ if (!extractedParameters.isPresent()) {
+ return;
+ }
+
+ CommitCollector commitCollector = extractedParameters.get().getCommitCollector();
+ String projectName = extractedParameters.get().getProjectName();
+
+ Set<Map<String, String>> issuesProperties =
+ commitCollector.collect(properties).stream()
+ .map(commitId -> issueExtractor.getIssueIds(projectName, commitId))
+ .flatMap(Stream::of)
+ .map(
+ associations -> propertyExtractor.extractIssuesProperties(properties, associations))
+ .flatMap(Collection::stream)
+ .collect(Collectors.toSet());
+
+ issuesProperties.forEach(this::doExecute);
+ }
+
+ private void doExecute(Map<String, String> issueProperties) {
+ Collection<ActionRequest> actions = ruleBase.actionRequestsFor(issueProperties);
+ if (actions.isEmpty()) {
+ return;
+ }
+ actionExecutor.executeOnIssue(actions, issueProperties);
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/FireEventOnCommitsParameters.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/FireEventOnCommitsParameters.java
new file mode 100644
index 0000000..d6ee4db
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/FireEventOnCommitsParameters.java
@@ -0,0 +1,41 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.its.base.workflow;
+
+import com.googlesource.gerrit.plugins.its.base.workflow.commit_collector.CommitCollector;
+
+/** Parameters needed by {@link FireEventOnCommits} action */
+public class FireEventOnCommitsParameters {
+
+ private final CommitCollector commitCollector;
+ private final String projectName;
+
+ public FireEventOnCommitsParameters(CommitCollector commitCollector, String projectName) {
+ this.commitCollector = commitCollector;
+ this.projectName = projectName;
+ }
+
+ /**
+ * @return The collector that will be used to collect the commits on which the event will be fired
+ */
+ public CommitCollector getCommitCollector() {
+ return commitCollector;
+ }
+
+ /** @return The name of the project from which the commits will be collected */
+ public String getProjectName() {
+ return projectName;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/FireEventOnCommitsParametersExtractor.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/FireEventOnCommitsParametersExtractor.java
new file mode 100644
index 0000000..d7ae586
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/FireEventOnCommitsParametersExtractor.java
@@ -0,0 +1,78 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.its.base.workflow;
+
+import com.google.common.base.Strings;
+import com.googlesource.gerrit.plugins.its.base.workflow.commit_collector.CommitCollector;
+import com.googlesource.gerrit.plugins.its.base.workflow.commit_collector.SinceLastTagCommitCollector;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Optional;
+import javax.inject.Inject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FireEventOnCommitsParametersExtractor {
+
+ private static final Logger log =
+ LoggerFactory.getLogger(FireEventOnCommitsParametersExtractor.class);
+
+ private final SinceLastTagCommitCollector.Factory sinceLastTagCommitCollectorFactory;
+
+ @Inject
+ public FireEventOnCommitsParametersExtractor(
+ SinceLastTagCommitCollector.Factory sinceLastTagCommitCollectorFactory) {
+ this.sinceLastTagCommitCollectorFactory = sinceLastTagCommitCollectorFactory;
+ }
+
+ private CommitCollector getCommitCollector(String name) {
+ switch (name) {
+ case "since-last-tag":
+ return sinceLastTagCommitCollectorFactory.create();
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * @return The parameters needed by {@link FireEventOnCommits}. Empty if the parameters could not
+ * be extracted.
+ */
+ public Optional<FireEventOnCommitsParameters> extract(
+ ActionRequest actionRequest, Map<String, String> properties) {
+ String[] parameters = actionRequest.getParameters();
+ if (parameters.length != 1) {
+ log.error(
+ "Wrong number of received parameters. Received parameters are {}. Only one parameter is expected, the collector name.",
+ Arrays.toString(parameters));
+ return Optional.empty();
+ }
+
+ String collectorName = parameters[0];
+ if (Strings.isNullOrEmpty(collectorName)) {
+ log.error("Received collector name is blank");
+ return Optional.empty();
+ }
+
+ CommitCollector commitCollector = getCommitCollector(collectorName);
+ if (commitCollector == null) {
+ log.error("No commit collector found for name {}", collectorName);
+ return Optional.empty();
+ }
+
+ String projectName = properties.get("project");
+ return Optional.of(new FireEventOnCommitsParameters(commitCollector, projectName));
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ItsRulesProjectCacheImpl.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ItsRulesProjectCacheImpl.java
index 72fe43f..4dc46d3 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ItsRulesProjectCacheImpl.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ItsRulesProjectCacheImpl.java
@@ -98,7 +98,10 @@
@Override
public List<Rule> load(String projectName) throws IOException {
- ProjectState project = projectCache.checkedGet(Project.nameKey(projectName));
+ ProjectState project =
+ projectCache
+ .get(Project.nameKey(projectName))
+ .orElseThrow(() -> new IOException("Can't load " + projectName));
List<Rule> projectRules = readRulesFrom(project);
if (projectRules.isEmpty()) {
for (ProjectState parent : project.parents()) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/RefEventProperties.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/RefEventProperties.java
index 30940bb..01b6395 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/RefEventProperties.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/RefEventProperties.java
@@ -14,7 +14,6 @@
package com.googlesource.gerrit.plugins.its.base.workflow;
-import java.util.Collections;
import java.util.Map;
import java.util.Set;
@@ -31,8 +30,8 @@
*/
public RefEventProperties(
Map<String, String> projectProperties, Set<Map<String, String>> issuesProperties) {
- this.projectProperties = Collections.unmodifiableMap(projectProperties);
- this.issuesProperties = Collections.unmodifiableSet(issuesProperties);
+ this.projectProperties = projectProperties;
+ this.issuesProperties = issuesProperties;
}
/** @return Properties of the ref event */
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/commit_collector/CommitCollector.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/commit_collector/CommitCollector.java
new file mode 100644
index 0000000..73ea110
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/commit_collector/CommitCollector.java
@@ -0,0 +1,24 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.its.base.workflow.commit_collector;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/** Collects commits based on the event properties provided in input */
+public interface CommitCollector {
+ List<String> collect(Map<String, String> properties) throws IOException;
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/commit_collector/SinceLastTagCommitCollector.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/commit_collector/SinceLastTagCommitCollector.java
new file mode 100644
index 0000000..d8f1ec1
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/commit_collector/SinceLastTagCommitCollector.java
@@ -0,0 +1,97 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.its.base.workflow.commit_collector;
+
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import javax.inject.Inject;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+/** Collects all commits between the last tag and HEAD */
+public class SinceLastTagCommitCollector implements CommitCollector {
+
+ public interface Factory {
+ SinceLastTagCommitCollector create();
+ }
+
+ private final GitRepositoryManager repoManager;
+
+ @Inject
+ public SinceLastTagCommitCollector(GitRepositoryManager repoManager) {
+ this.repoManager = repoManager;
+ }
+
+ @Override
+ public List<String> collect(Map<String, String> properties) throws IOException {
+ String projectName = properties.get("project");
+ String revision = properties.get("revision");
+
+ try (Repository repo = repoManager.openRepository(Project.nameKey(projectName))) {
+ return collect(repo, revision);
+ }
+ }
+
+ private List<String> collect(Repository repo, String currentRevision) throws IOException {
+ try (RevWalk revWalk = new RevWalk(repo)) {
+ RevCommit currentCommit = revWalk.parseCommit(repo.resolve(currentRevision));
+ revWalk.markStart(currentCommit);
+ List<String> commitIds = new ArrayList<>();
+ for (RevCommit commit : revWalk) {
+ if (!currentCommit.getId().equals(commit.getId()) && isTagged(repo, commit)) {
+ break;
+ }
+ commitIds.add(ObjectId.toString(commit.getId()));
+ }
+ return commitIds;
+ }
+ }
+
+ /** @return True if {@code commit} is tagged */
+ private boolean isTagged(Repository repo, RevCommit commit) throws IOException {
+ ObjectId commitId = commit.getId();
+ try (RevWalk revWalk = new RevWalk(repo)) {
+ return repo.getRefDatabase().getRefsByPrefix(Constants.R_TAGS).stream()
+ .map(Ref::getObjectId)
+ .map(
+ refObjectId -> {
+ try {
+ return revWalk.parseTag(refObjectId);
+ } catch (IncorrectObjectTypeException e) {
+ return null;
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ })
+ .filter(Objects::nonNull)
+ .map(RevTag::getObject)
+ .map(RevObject::getId)
+ .anyMatch(commitId::equals);
+ }
+ }
+}
diff --git a/src/main/resources/Documentation/config-rulebase-common.md b/src/main/resources/Documentation/config-rulebase-common.md
index d232e57..50e1023 100644
--- a/src/main/resources/Documentation/config-rulebase-common.md
+++ b/src/main/resources/Documentation/config-rulebase-common.md
@@ -574,6 +574,13 @@
`uploaderUsername`
: username of the user that uploaded this patch set.
+### Common properties for all events
+
+`source`
+: source of the event that triggered the action. Can be `its` or `gerrit`.
+ `its` sourced events are fired by its actions. e.g. `fire-event-on-commits` action fires `its` sourced events.
+ `gerrit` sourced events are directly fired by the Gerrit event system.
+
## Actions
Lines of the form
@@ -693,6 +700,26 @@
action = create-version-from-property ref
```
+### Action: fire-event-on-commits
+
+The `fire-event-on-commits` action start by collecting commits using a collector then fire the event
+on each collected commit.
+
+This is useful when you want to trigger rules on a multiple past commits.
+
+Available collectors are:
+- `since-last-tag`: Collects all commits between the current ref and previous tag
+
+To avoid to trigger issue actions twice for the same event, you should condition your rule on
+the event property `source`.
+
+Example:
+
+```
+ action = fire-event-on-commits since-last-tag
+```
+
+
### Action: log-event
The `log-event` action appends the event's properties to Gerrit's log.
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/base/its/ItsConfigTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/its/ItsConfigTest.java
index 3379908..34068bb 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/its/base/its/ItsConfigTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/its/ItsConfigTest.java
@@ -56,8 +56,8 @@
String enabled, String itsProject, String parentEnabled, String[] branches) {
ProjectState projectState = mock(ProjectState.class);
- when(projectCache.get(Project.nameKey("testProject"))).thenReturn(projectState);
- when(projectCache.get(Project.nameKey("parentProject"))).thenReturn(projectState);
+ when(projectCache.get(Project.nameKey("testProject"))).thenReturn(Optional.of(projectState));
+ when(projectCache.get(Project.nameKey("parentProject"))).thenReturn(Optional.of(projectState));
Iterable<ProjectState> parents;
if (parentEnabled == null) {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/base/util/PropertyExtractorTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/util/PropertyExtractorTest.java
index ac7ea92..d4f0605 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/its/base/util/PropertyExtractorTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/util/PropertyExtractorTest.java
@@ -41,6 +41,7 @@
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
+import com.googlesource.gerrit.plugins.its.base.workflow.RefEventProperties;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -374,13 +375,30 @@
when(issueExtractor.getIssueIds("testProject", "testRevision")).thenReturn(issueMap);
}
- Set<Map<String, String>> actual = propertyExtractor.extractFrom(event).getIssuesProperties();
+ RefEventProperties refEventProperties = propertyExtractor.extractFrom(event);
+
+ Map<String, String> actualProjectProperties = refEventProperties.getProjectProperties();
+
+ Map<String, String> expectedProjectProperties =
+ ImmutableMap.<String, String>builder()
+ .put("itsName", "ItsTestName")
+ .put("event", "com.google.gerrit.server.events." + className)
+ .put("event-type", type)
+ .put("source", "gerrit")
+ .putAll(common)
+ .build();
+
+ assertEquals(
+ "Project properties do not match", expectedProjectProperties, actualProjectProperties);
+
+ Set<Map<String, String>> actualIssuesProperties = refEventProperties.getIssuesProperties();
Map<String, String> propertiesIssue4711 =
ImmutableMap.<String, String>builder()
.put("itsName", "ItsTestName")
.put("event", "com.google.gerrit.server.events." + className)
.put("event-type", type)
+ .put("source", "its")
.put("association", "body anywhere")
.put("issue", "4711")
.putAll(common)
@@ -390,15 +408,17 @@
.put("itsName", "ItsTestName")
.put("event", "com.google.gerrit.server.events." + className)
.put("event-type", type)
+ .put("source", "its")
.put("association", "anywhere footer")
.put("issue", "42")
.putAll(common)
.build();
- Set<Map<String, String>> expected = new HashSet<>();
- expected.add(propertiesIssue4711);
- expected.add(propertiesIssue42);
+ Set<Map<String, String>> expectedIssuesProperties = new HashSet<>();
+ expectedIssuesProperties.add(propertiesIssue4711);
+ expectedIssuesProperties.add(propertiesIssue42);
- assertEquals("Properties do not match", expected, actual);
+ assertEquals(
+ "Issues properties do not match", expectedIssuesProperties, actualIssuesProperties);
}
@Override
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionExecutorTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionExecutorTest.java
index 5b8c43f..822602b 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionExecutorTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionExecutorTest.java
@@ -48,6 +48,7 @@
private LogEvent.Factory logEventFactory;
private AddPropertyToField.Factory addPropertyToFieldFactory;
private CreateVersionFromProperty.Factory createVersionFromPropertyFactory;
+ private FireEventOnCommits.Factory fireEventOnCommitsFactory;
private CustomAction customAction;
private Map<String, String> properties =
@@ -212,7 +213,8 @@
CreateVersionFromProperty createVersionFromProperty = mock(CreateVersionFromProperty.class);
when(createVersionFromPropertyFactory.create()).thenReturn(createVersionFromProperty);
- when(itsFacadeFactory.getFacade(Project.nameKey(properties.get("project")))).thenReturn(its);
+ when(itsFacadeFactory.getFacade(Project.nameKey(projectProperties.get("project"))))
+ .thenReturn(its);
ActionExecutor actionExecutor = createActionExecutor();
actionExecutor.executeOnProject(Collections.singleton(actionRequest), projectProperties);
@@ -237,6 +239,21 @@
verify(addPropertyToField).execute(its, "4711", actionRequest, properties);
}
+ public void testFireEventOnCommitsDelegation() throws IOException {
+ ActionRequest actionRequest = mock(ActionRequest.class);
+ when(actionRequest.getName()).thenReturn("fire-event-on-commits");
+
+ FireEventOnCommits fireEventOnCommits = mock(FireEventOnCommits.class);
+ when(fireEventOnCommitsFactory.create()).thenReturn(fireEventOnCommits);
+ when(itsFacadeFactory.getFacade(Project.nameKey(projectProperties.get("project"))))
+ .thenReturn(its);
+
+ ActionExecutor actionExecutor = createActionExecutor();
+ actionExecutor.executeOnProject(Collections.singleton(actionRequest), projectProperties);
+
+ verify(fireEventOnCommits).execute(its, "itsTestProject", actionRequest, projectProperties);
+ }
+
public void testExecuteIssueCustomAction() throws IOException {
when(customAction.getType()).thenReturn(ActionType.ISSUE);
@@ -304,6 +321,9 @@
createVersionFromPropertyFactory = mock(CreateVersionFromProperty.Factory.class);
bind(CreateVersionFromProperty.Factory.class).toInstance(createVersionFromPropertyFactory);
+ fireEventOnCommitsFactory = mock(FireEventOnCommits.Factory.class);
+ bind(FireEventOnCommits.Factory.class).toInstance(fireEventOnCommitsFactory);
+
DynamicMap.mapOf(binder(), CustomAction.class);
customAction = mock(CustomAction.class);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/FireEventOnCommitsParametersExtractorTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/FireEventOnCommitsParametersExtractorTest.java
new file mode 100644
index 0000000..c99bf33
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/FireEventOnCommitsParametersExtractorTest.java
@@ -0,0 +1,102 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.its.base.workflow;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.googlesource.gerrit.plugins.its.base.workflow.commit_collector.SinceLastTagCommitCollector;
+import java.util.Collections;
+import java.util.Optional;
+import junit.framework.TestCase;
+
+public class FireEventOnCommitsParametersExtractorTest extends TestCase {
+
+ private static final String SINCE_LAST_TAG_COLLECTOR = "since-last-tag";
+
+ private SinceLastTagCommitCollector.Factory sinceLastTagCommitCollectorFactory;
+ private FireEventOnCommitsParametersExtractor extractor;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ Injector injector = Guice.createInjector(new TestModule());
+ extractor = injector.getInstance(FireEventOnCommitsParametersExtractor.class);
+ }
+
+ private class TestModule extends FactoryModule {
+ @Override
+ protected void configure() {
+ sinceLastTagCommitCollectorFactory = mock(SinceLastTagCommitCollector.Factory.class);
+ bind(SinceLastTagCommitCollector.Factory.class)
+ .toInstance(sinceLastTagCommitCollectorFactory);
+ }
+ }
+
+ public void testNoParameter() {
+ testWrongNumberOfReceivedParameters(new String[] {});
+ }
+
+ public void testTwoParameters() {
+ testWrongNumberOfReceivedParameters(
+ new String[] {SINCE_LAST_TAG_COLLECTOR, SINCE_LAST_TAG_COLLECTOR});
+ }
+
+ private void testWrongNumberOfReceivedParameters(String[] parameters) {
+ ActionRequest actionRequest = mock(ActionRequest.class);
+ when(actionRequest.getParameters()).thenReturn(parameters);
+
+ Optional<FireEventOnCommitsParameters> extractedParameters =
+ extractor.extract(actionRequest, Collections.emptyMap());
+ assertFalse(extractedParameters.isPresent());
+ }
+
+ public void testBlankCollectorName() {
+ ActionRequest actionRequest = mock(ActionRequest.class);
+ when(actionRequest.getParameters()).thenReturn(new String[] {""});
+
+ Optional<FireEventOnCommitsParameters> extractedParameters =
+ extractor.extract(actionRequest, Collections.emptyMap());
+ assertFalse(extractedParameters.isPresent());
+ }
+
+ public void testUnknownCollectorName() {
+ ActionRequest actionRequest = mock(ActionRequest.class);
+ when(actionRequest.getParameters()).thenReturn(new String[] {"foo"});
+
+ Optional<FireEventOnCommitsParameters> extractedParameters =
+ extractor.extract(actionRequest, Collections.emptyMap());
+ assertFalse(extractedParameters.isPresent());
+ }
+
+ public void testSinceLastTagCollector() {
+ ActionRequest actionRequest = mock(ActionRequest.class);
+ when(actionRequest.getParameters()).thenReturn(new String[] {SINCE_LAST_TAG_COLLECTOR});
+
+ SinceLastTagCommitCollector collector = mock(SinceLastTagCommitCollector.class);
+ when(sinceLastTagCommitCollectorFactory.create()).thenReturn(collector);
+
+ Optional<FireEventOnCommitsParameters> extractedParameters =
+ extractor.extract(actionRequest, Collections.singletonMap("project", "testProject"));
+ if (!extractedParameters.isPresent()) {
+ fail();
+ }
+ assertEquals(collector, extractedParameters.get().getCommitCollector());
+ assertEquals("testProject", extractedParameters.get().getProjectName());
+ }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/FireEventOnCommitsTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/FireEventOnCommitsTest.java
new file mode 100644
index 0000000..e80081d
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/FireEventOnCommitsTest.java
@@ -0,0 +1,115 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.its.base.workflow;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.googlesource.gerrit.plugins.its.base.its.ItsFacade;
+import com.googlesource.gerrit.plugins.its.base.util.IssueExtractor;
+import com.googlesource.gerrit.plugins.its.base.util.PropertyExtractor;
+import com.googlesource.gerrit.plugins.its.base.workflow.commit_collector.SinceLastTagCommitCollector;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import junit.framework.TestCase;
+
+public class FireEventOnCommitsTest extends TestCase {
+
+ private static final String ITS_PROJECT = "test-project";
+ private static final String COMMIT = "1234";
+ private static final String PROJECT = "testProject";
+
+ private Injector injector;
+ private ItsFacade its;
+ private PropertyExtractor propertyExtractor;
+ private IssueExtractor issueExtractor;
+ private RuleBase ruleBase;
+ private ActionExecutor actionExecutor;
+ private FireEventOnCommitsParametersExtractor parametersExtractor;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ injector = Guice.createInjector(new TestModule());
+ }
+
+ private class TestModule extends FactoryModule {
+ @Override
+ protected void configure() {
+ its = mock(ItsFacade.class);
+ bind(ItsFacade.class).toInstance(its);
+
+ propertyExtractor = mock(PropertyExtractor.class);
+ bind(PropertyExtractor.class).toInstance(propertyExtractor);
+
+ ruleBase = mock(RuleBase.class);
+ bind(RuleBase.class).toInstance(ruleBase);
+
+ issueExtractor = mock(IssueExtractor.class);
+ bind(IssueExtractor.class).toInstance(issueExtractor);
+
+ actionExecutor = mock(ActionExecutor.class);
+ bind(ActionExecutor.class).toInstance(actionExecutor);
+
+ parametersExtractor = mock(FireEventOnCommitsParametersExtractor.class);
+ bind(FireEventOnCommitsParametersExtractor.class).toInstance(parametersExtractor);
+ }
+ }
+
+ public void testHappyPath() throws IOException {
+ Map<String, String> properties = ImmutableMap.of("project", PROJECT);
+
+ FireEventOnCommitsParameters parameters = mock(FireEventOnCommitsParameters.class);
+ SinceLastTagCommitCollector collector = mock(SinceLastTagCommitCollector.class);
+ when(parameters.getCommitCollector()).thenReturn(collector);
+ when(parameters.getProjectName()).thenReturn(PROJECT);
+
+ ActionRequest actionRequest = mock(ActionRequest.class);
+ when(parametersExtractor.extract(actionRequest, properties))
+ .thenReturn(Optional.of(parameters));
+ when(collector.collect(properties)).thenReturn(Collections.singletonList(COMMIT));
+
+ Map<String, Set<String>> associations = Maps.newHashMap();
+ when(issueExtractor.getIssueIds(PROJECT, COMMIT)).thenReturn(associations);
+
+ Set<Map<String, String>> issuesProperties = ImmutableSet.of(properties);
+ when(propertyExtractor.extractIssuesProperties(properties, associations))
+ .thenReturn(issuesProperties);
+
+ ActionRequest subActionRequest = mock(ActionRequest.class);
+ Collection<ActionRequest> subActionRequests = Collections.singleton(subActionRequest);
+ when(ruleBase.actionRequestsFor(properties)).thenReturn(subActionRequests);
+
+ FireEventOnCommits fireEventOnCommits = createFireEventOnCommits();
+ fireEventOnCommits.execute(its, ITS_PROJECT, actionRequest, properties);
+
+ verify(actionExecutor).executeOnIssue(subActionRequests, properties);
+ }
+
+ private FireEventOnCommits createFireEventOnCommits() {
+ return injector.getInstance(FireEventOnCommits.class);
+ }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ItsRulesProjectCacheTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ItsRulesProjectCacheTest.java
index 6b83097..592722c 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ItsRulesProjectCacheTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ItsRulesProjectCacheTest.java
@@ -36,6 +36,7 @@
import java.io.IOException;
import java.util.Collection;
import java.util.List;
+import java.util.Optional;
import org.eclipse.jgit.lib.Config;
public class ItsRulesProjectCacheTest extends LoggingMockingTestCase {
@@ -90,7 +91,7 @@
ProjectLevelConfig projectLevelConfigPlugin = mock(ProjectLevelConfig.class);
when(projectLevelConfigPlugin.get()).thenReturn(new Config());
when(projectState.getConfig(RuleBaseKind.ITS.fileName)).thenReturn(projectLevelConfigPlugin);
- when(projectCache.checkedGet(Project.nameKey(TEST_PROJECT))).thenReturn(projectState);
+ when(projectCache.get(Project.nameKey(TEST_PROJECT))).thenReturn(Optional.of(projectState));
when(rulesConfigReader.getRulesFromConfig(any(Config.class)))
.thenReturn(ImmutableList.of(rule1))
.thenReturn(ImmutableList.of());
@@ -132,7 +133,7 @@
when(parentProjectState.getConfig(RuleBaseKind.ITS.fileName))
.thenReturn(parentProjectConfigPlugin);
when(projectState.parents()).thenReturn(FluentIterable.of(parentProjectState));
- when(projectCache.checkedGet(Project.nameKey(TEST_PROJECT))).thenReturn(projectState);
+ when(projectCache.get(Project.nameKey(TEST_PROJECT))).thenReturn(Optional.of(projectState));
when(rulesConfigReader.getRulesFromConfig(any(Config.class)))
.thenReturn(ImmutableList.of())