Predicates to check commit messages and edits

Adds predicate commit_message that returns the commit message as a symbol.
commit_message_matches takes in a regex pattern and checks it against
the commit message, returns true if a match is found.
Adds predicate commit_edits that takes in a regex pattern for filenames
and a regex pattern for edits. For all files in a commit that match
the filename regex, if the edits in any of those files match the
edit regex, then the predicate returns true.

Change-Id: I1c0b5ddb669aaca77908e18d7bb314c5aa6aec70
(cherry picked from commit 0cec9e6f7ff1e62ce7f4e487e6a06e2a973baeb7)
diff --git a/gerrit-server/src/main/java/gerrit/PRED_commit_edits_2.java b/gerrit-server/src/main/java/gerrit/PRED_commit_edits_2.java
new file mode 100644
index 0000000..f9e3036
--- /dev/null
+++ b/gerrit-server/src/main/java/gerrit/PRED_commit_edits_2.java
@@ -0,0 +1,171 @@
+// Copyright (C) 2011 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 gerrit;
+
+import com.google.gerrit.reviewdb.PatchSetInfo;
+import com.google.gerrit.rules.PrologEnvironment;
+import com.google.gerrit.rules.StoredValues;
+import com.google.gerrit.server.patch.PatchList;
+import com.google.gerrit.server.patch.PatchListEntry;
+import com.google.gerrit.server.patch.Text;
+
+import com.googlecode.prolog_cafe.lang.IllegalTypeException;
+import com.googlecode.prolog_cafe.lang.JavaException;
+import com.googlecode.prolog_cafe.lang.Operation;
+import com.googlecode.prolog_cafe.lang.PInstantiationException;
+import com.googlecode.prolog_cafe.lang.Predicate;
+import com.googlecode.prolog_cafe.lang.Prolog;
+import com.googlecode.prolog_cafe.lang.PrologException;
+import com.googlecode.prolog_cafe.lang.SystemException;
+import com.googlecode.prolog_cafe.lang.Term;
+
+import org.eclipse.jgit.diff.Edit;
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Returns true if any of the files that match FileNameRegex have edited lines
+ * that match EditRegex
+ *
+ * <pre>
+ *   'commit_edits'(+FileNameRegex, +EditRegex)
+ * </pre>
+ */
+public class PRED_commit_edits_2 extends Predicate.P2 {
+  private static final long serialVersionUID = 1L;
+
+  public PRED_commit_edits_2(Term a1, Term a2, Operation n) {
+    arg1 = a1;
+    arg2 = a2;
+    cont = n;
+  }
+
+  @Override
+  public Operation exec(Prolog engine) throws PrologException {
+    engine.setB0();
+
+    Term a1 = arg1.dereference();
+    Term a2 = arg2.dereference();
+
+    Pattern fileRegex = getRegexParameter(a1);
+    Pattern editRegex = getRegexParameter(a2);
+
+    PrologEnvironment env = (PrologEnvironment) engine.control;
+    PatchSetInfo psInfo = StoredValues.PATCH_SET_INFO.get(engine);
+    PatchList pl = StoredValues.PATCH_LIST.get(engine);
+    Repository repo = StoredValues.REPOSITORY.get(engine);
+
+    final ObjectReader reader = repo.newObjectReader();
+    final RevTree aTree;
+    final RevTree bTree;
+    try {
+      final RevWalk rw = new RevWalk(reader);
+      final RevCommit bCommit = rw.parseCommit(pl.getNewId());
+
+      if (pl.getOldId() != null) {
+        aTree = rw.parseTree(pl.getOldId());
+      } else {
+        // Octopus merge with unknown automatic merge result, since the
+        // web UI returns no files to match against, just fail.
+        return engine.fail();
+      }
+      bTree = bCommit.getTree();
+
+      for (PatchListEntry entry : pl.getPatches()) {
+        String newName = entry.getNewName();
+        String oldName = entry.getOldName();
+
+        if (newName.equals("/COMMIT_MSG")) {
+          continue;
+        }
+
+        if (fileRegex.matcher(newName).find() ||
+            (oldName != null && fileRegex.matcher(oldName).find())) {
+          List<Edit> edits = entry.getEdits();
+
+          if (edits.isEmpty()) {
+            continue;
+          }
+          Text tA;
+          if (oldName != null) {
+            tA = load(aTree, oldName, reader);
+          } else {
+            tA = load(aTree, newName, reader);
+          }
+          Text tB = load(bTree, newName, reader);
+          for (Edit edit : edits) {
+            if (tA != Text.EMPTY) {
+              String aDiff = tA.getString(edit.getBeginA(), edit.getEndA(), true);
+              if (editRegex.matcher(aDiff).find()) {
+                return cont;
+              }
+            }
+            if (tB != Text.EMPTY) {
+              String bDiff = tB.getString(edit.getBeginB(), edit.getEndB(), true);
+              if (editRegex.matcher(bDiff).find()) {
+                return cont;
+              }
+            }
+          }
+        }
+      }
+    } catch (IOException err) {
+      throw new JavaException(this, 1, err);
+    } finally {
+      reader.release();
+    }
+
+    return engine.fail();
+  }
+
+  private Pattern getRegexParameter(Term term) {
+    if (term.isVariable()) {
+      throw new PInstantiationException(this, 1);
+    }
+    if (!term.isSymbol()) {
+      throw new IllegalTypeException(this, 1, "symbol", term);
+    }
+    return Pattern.compile(term.name(), Pattern.MULTILINE);
+  }
+
+  private Text load(final ObjectId tree, final String path, final ObjectReader reader)
+      throws MissingObjectException, IncorrectObjectTypeException,
+      CorruptObjectException, IOException {
+    if (path == null) {
+      return Text.EMPTY;
+    }
+    final TreeWalk tw = TreeWalk.forPath(reader, path, tree);
+    if (tw == null) {
+      return Text.EMPTY;
+    }
+    if (tw.getFileMode(0).getObjectType() != Constants.OBJ_BLOB) {
+      return Text.EMPTY;
+    }
+    return new Text(reader.open(tw.getObjectId(0), Constants.OBJ_BLOB));
+  }
+}
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/gerrit/PRED_commit_message_1.java b/gerrit-server/src/main/java/gerrit/PRED_commit_message_1.java
new file mode 100644
index 0000000..8c94b34
--- /dev/null
+++ b/gerrit-server/src/main/java/gerrit/PRED_commit_message_1.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2011 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 gerrit;
+
+import com.google.gerrit.reviewdb.PatchSetInfo;
+import com.google.gerrit.rules.StoredValues;
+
+import com.googlecode.prolog_cafe.lang.Operation;
+import com.googlecode.prolog_cafe.lang.Predicate;
+import com.googlecode.prolog_cafe.lang.Prolog;
+import com.googlecode.prolog_cafe.lang.PrologException;
+import com.googlecode.prolog_cafe.lang.SymbolTerm;
+import com.googlecode.prolog_cafe.lang.Term;
+
+/**
+ * Returns the commit message as a symbol
+ *
+ * <pre>
+ *   'commit_message'(-Msg)
+ * </pre>
+ */
+public class PRED_commit_message_1 extends Predicate.P1 {
+  private static final long serialVersionUID = 1L;
+
+  public PRED_commit_message_1(Term a1, Operation n) {
+    arg1 = a1;
+    cont = n;
+  }
+
+  @Override
+  public Operation exec(Prolog engine) throws PrologException {
+    engine.setB0();
+    Term a1 = arg1.dereference();
+
+    PatchSetInfo psInfo = StoredValues.PATCH_SET_INFO.get(engine);
+
+    SymbolTerm msg = SymbolTerm.create(psInfo.getMessage());
+    if (!a1.unify(msg, engine.trail)) {
+      return engine.fail();
+    }
+    return cont;
+  }
+}
\ No newline at end of file
diff --git a/gerrit-server/src/main/prolog/gerrit_common.pl b/gerrit-server/src/main/prolog/gerrit_common.pl
index eb83ffd..5342b84 100644
--- a/gerrit-server/src/main/prolog/gerrit_common.pl
+++ b/gerrit-server/src/main/prolog/gerrit_common.pl
@@ -394,3 +394,12 @@
 split_commit_delta(rename, NewPath, OldPath, add, NewPath) :- !.
 split_commit_delta(copy, NewPath, OldPath, add, NewPath) :- !.
 split_commit_delta(Type, Path, _, Type, Path).
+
+
+%% commit_message_matches/1:
+%%
+:- public commit_message_matches/1.
+%%
+commit_message_matches(Pattern) :-
+  commit_message(Msg),
+  regex_matches(Pattern, Msg).