Add a dummy issue pattern

This setting is useful to bypass the MANDATORY check for commits
matching a specific pattern.

Change-Id: I9c76af5e94c767889472cd0c74a599664235f231
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 55edae3..2f06f8b 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
@@ -203,6 +203,17 @@
   }
 
   /**
+   * Pattern to skip the mandatory check for an issue. Can be used to explicitly bypass the
+   * mandatory issue pattern check for some commits.
+   *
+   * <p>When no pattern is specified, it will return a pattern which never matches.
+   */
+  public Optional<Pattern> getDummyIssuePattern() {
+    return Optional.ofNullable(getPluginConfigString("dummyIssuePattern"))
+        .map(Pattern::compile);
+  }
+
+  /**
    * Gets how necessary it is to associate commits with issues
    *
    * @return policy on how necessary association with issues is
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/validation/ItsValidateComment.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/validation/ItsValidateComment.java
index 969eeb0..d45d0a6 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/validation/ItsValidateComment.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/validation/ItsValidateComment.java
@@ -92,7 +92,10 @@
 
             ret.add(commitValidationFailure(synopsis, details));
           }
-        } else {
+        } else if (!itsConfig
+            .getDummyIssuePattern()
+            .map(p -> p.matcher(commitMessage).find())
+            .orElse(false)) {
           synopsis = "Missing issue-id in commit message";
 
           StringBuilder sb = new StringBuilder();
diff --git a/src/main/resources/Documentation/config-common.md b/src/main/resources/Documentation/config-common.md
index d53b0c6..9532510 100644
--- a/src/main/resources/Documentation/config-common.md
+++ b/src/main/resources/Documentation/config-common.md
@@ -42,8 +42,8 @@
 values are supported (default is `OPTIONAL`):
 
 MANDATORY
-:	 One or more issue-ids are required in the git commit message, otherwise
-	 the git push will be rejected.
+:	 One or more issue-ids are required in the git commit message.  The git push will
+	 be rejected otherwise.
 
 SUGGESTED
 :	 Whenever the git commit message does not contain one or more issue-ids,
@@ -185,6 +185,12 @@
     the `@PLUGIN@.commentlink` comment link, and the default is `0`, if there
     are no such groups.
 
+<a name="common-config-dummyIssuePattern">`@PLUGIN@.dummyIssuePattern`
+:   Pattern which can be specified to match a dummy issue.
+
+    This setting is useful to bypass the MANDATORY check for commits matching
+    a specific pattern.
+
 [Back to @PLUGIN@ documentation index][index]
 
 [index]: index.html
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/base/validation/ItsValidateCommentTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/validation/ItsValidateCommentTest.java
index 5573878..14744c4 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/its/base/validation/ItsValidateCommentTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/validation/ItsValidateCommentTest.java
@@ -30,6 +30,7 @@
 import com.googlesource.gerrit.plugins.its.base.util.IssueExtractor;
 import java.io.IOException;
 import java.util.List;
+import java.util.Optional;
 import java.util.regex.Pattern;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.transport.ReceiveCommand;
@@ -75,6 +76,7 @@
     expect(itsConfig.getItsAssociationPolicy())
         .andReturn(ItsAssociationPolicy.SUGGESTED)
         .atLeastOnce();
+    expect(itsConfig.getDummyIssuePattern()).andReturn(Optional.empty()).atLeastOnce();
     expect(commit.getFullMessage()).andReturn("TestMessage").atLeastOnce();
     expect(commit.getId()).andReturn(commit).anyTimes();
     expect(commit.getName()).andReturn("TestCommit").anyTimes();
@@ -99,6 +101,7 @@
     expect(itsConfig.getItsAssociationPolicy())
         .andReturn(ItsAssociationPolicy.MANDATORY)
         .atLeastOnce();
+    expect(itsConfig.getDummyIssuePattern()).andReturn(Optional.empty()).atLeastOnce();
     expect(commit.getFullMessage()).andReturn("TestMessage").atLeastOnce();
     expect(commit.getId()).andReturn(commit).anyTimes();
     expect(commit.getName()).andReturn("TestCommit").anyTimes();
@@ -116,6 +119,31 @@
     }
   }
 
+  public void testOnlySkipMatching() throws CommitValidationException {
+    List<CommitValidationMessage> ret;
+    ItsValidateComment ivc = injector.getInstance(ItsValidateComment.class);
+    ReceiveCommand command = createMock(ReceiveCommand.class);
+    RevCommit commit = createMock(RevCommit.class);
+    CommitReceivedEvent event = newCommitReceivedEvent(command, project, null, commit, null);
+
+    expect(itsConfig.getItsAssociationPolicy())
+        .andReturn(ItsAssociationPolicy.MANDATORY)
+        .atLeastOnce();
+    expect(itsConfig.getDummyIssuePattern())
+        .andReturn(Optional.of(Pattern.compile("SKIP")))
+        .atLeastOnce();
+    expect(commit.getFullMessage()).andReturn("TestMessage SKIP").atLeastOnce();
+    expect(commit.getId()).andReturn(commit).anyTimes();
+    expect(commit.getName()).andReturn("TestCommit").anyTimes();
+    expect(issueExtractor.getIssueIds("TestMessage SKIP")).andReturn(new String[] {}).atLeastOnce();
+
+    replayMocks();
+
+    ret = ivc.onCommitReceived(event);
+
+    assertEmptyList(ret);
+  }
+
   public void testSuggestedMatchingSingleExisting() throws CommitValidationException, IOException {
     List<CommitValidationMessage> ret;
     ItsValidateComment ivc = injector.getInstance(ItsValidateComment.class);