Predicates to expose commit filelist to Prolog

commit_delta/1,3,4 each takes a regular expression and matches it to
the path of all the files in the latest patchset of a commit.
If applicable (changes where the file is renamed or copied), the
regex is also checked against the old path.

commit_delta/1 returns true if any files match the regex
commit_delta/3 returns the changetype and path, if the changetype is
renamed, it also returns the old path. If the changetype is rename,
it returns a delete for oldpath and an add for newpath. If the
changetype is copy, an add is returned along with newpath.
commit_delta/4 returns the changetype, new path, and old path
(if applicable)

Change-Id: I05424029624b7d4bced05ebd642471fc27a0f50a
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchSetInfo.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchSetInfo.java
index 839dbfd..f96a5b3 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchSetInfo.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchSetInfo.java
@@ -51,6 +51,9 @@
   /** List of parents of the patch set. */
   protected List<ParentInfo> parents;
 
+  /** SHA-1 of commit */
+  protected String revId;
+
   protected PatchSetInfo() {
   }
 
@@ -105,4 +108,12 @@
   public List<ParentInfo> getParents() {
     return parents;
   }
+
+  public void setRevId(final String s) {
+    revId = s;
+  }
+
+  public String getRevId() {
+    return revId;
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
index 646d4ce..16a9582 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
@@ -67,6 +67,7 @@
     info.setMessage(src.getFullMessage());
     info.setAuthor(toUserIdentity(src.getAuthorIdent()));
     info.setCommitter(toUserIdentity(src.getCommitterIdent()));
+    info.setRevId(src.getName());
 
     return info;
   }
diff --git a/gerrit-server/src/main/java/gerrit/PRED_commit_delta_4.java b/gerrit-server/src/main/java/gerrit/PRED_commit_delta_4.java
new file mode 100644
index 0000000..7917bbe
--- /dev/null
+++ b/gerrit-server/src/main/java/gerrit/PRED_commit_delta_4.java
@@ -0,0 +1,214 @@
+// 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.AccountDiffPreference.Whitespace;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.Patch;
+import com.google.gerrit.reviewdb.PatchSet;
+import com.google.gerrit.reviewdb.PatchSetInfo;
+import com.google.gerrit.reviewdb.Project;
+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.PatchListCache;
+import com.google.gerrit.server.patch.PatchListEntry;
+import com.google.gerrit.server.patch.PatchListKey;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+
+import com.googlecode.prolog_cafe.lang.IllegalTypeException;
+import com.googlecode.prolog_cafe.lang.JavaException;
+import com.googlecode.prolog_cafe.lang.JavaObjectTerm;
+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.SymbolTerm;
+import com.googlecode.prolog_cafe.lang.Term;
+
+import org.eclipse.jgit.lib.ObjectId;
+
+import java.util.Iterator;
+import java.util.regex.Pattern;
+
+/**
+ * Given a regular expression, checks it against the file list in the most
+ * recent patchset of a change. For all files that match the regex, returns the
+ * (new) path of the file, the change type, and the old path of the file if
+ * applicable (if the file was copied or renamed).
+ *
+ * <pre>
+ *   'commit_delta'(+Regex, -ChangeType, -NewPath, -OldPath)
+ * </pre>
+ */
+public class PRED_commit_delta_4 extends Predicate.P4 {
+  private static final long serialVersionUID = 1L;
+  private static final SymbolTerm add = SymbolTerm.intern("add");
+  private static final SymbolTerm modify = SymbolTerm.intern("modify");
+  private static final SymbolTerm delete = SymbolTerm.intern("delete");
+  private static final SymbolTerm rename = SymbolTerm.intern("rename");
+  private static final SymbolTerm copy = SymbolTerm.intern("copy");
+  static final Operation commit_delta_check = new PRED_commit_delta_check();
+  static final Operation commit_delta_next = new PRED_commit_delta_next();
+  static final Operation commit_delta_empty = new PRED_commit_delta_empty();
+
+  public PRED_commit_delta_4(Term a1, Term a2, Term a3, Term a4, Operation n) {
+    arg1 = a1;
+    arg2 = a2;
+    arg3 = a3;
+    arg4 = a4;
+    cont = n;
+  }
+
+  @Override
+  public Operation exec(Prolog engine) throws PrologException {
+    engine.cont = cont;
+    engine.setB0();
+
+    Term a1 = arg1.dereference();
+    if (a1.isVariable()) {
+      throw new PInstantiationException(this, 1);
+    }
+    if (!a1.isSymbol()) {
+      throw new IllegalTypeException(this, 1, "symbol", a1);
+    }
+    Pattern regex = Pattern.compile(a1.name());
+    engine.areg1 = new JavaObjectTerm(regex);
+    engine.areg2 = arg2;
+    engine.areg3 = arg3;
+    engine.areg4 = arg4;
+
+    PrologEnvironment env = (PrologEnvironment) engine.control;
+    PatchSetInfo psInfo;
+    try {
+      psInfo = getPatchSetInfo(env);
+    } catch (PatchSetInfoNotAvailableException err) {
+      throw new JavaException(this, 1, err);
+    }
+
+    PatchListCache plCache = env.getInjector().getInstance(PatchListCache.class);
+    Change change = StoredValues.CHANGE.get(engine);
+
+    Project.NameKey projectKey = change.getProject();
+    ObjectId a = null;
+    ObjectId b = ObjectId.fromString(psInfo.getRevId());
+    Whitespace ws = Whitespace.IGNORE_NONE;
+    PatchListKey plKey = new PatchListKey(projectKey, a, b, ws);
+
+    PatchList pl = plCache.get(plKey);
+    Iterator<PatchListEntry> iter = pl.getPatches().iterator();
+
+    engine.areg5 = new JavaObjectTerm(iter);
+
+    return engine.jtry5(commit_delta_check, commit_delta_next);
+  }
+
+  private static final class PRED_commit_delta_check extends Operation {
+    @Override
+    public Operation exec(Prolog engine) {
+      Term a1 = engine.areg1;
+      Term a2 = engine.areg2;
+      Term a3 = engine.areg3;
+      Term a4 = engine.areg4;
+      Term a5 = engine.areg5;
+
+      Pattern regex = (Pattern)((JavaObjectTerm)a1).object();
+      Iterator<PatchListEntry> iter =
+        (Iterator<PatchListEntry>)((JavaObjectTerm)a5).object();
+      if (iter.hasNext()) {
+        PatchListEntry patch = iter.next();
+        String newName = patch.getNewName();
+        String oldName = patch.getOldName();
+        Patch.ChangeType changeType = patch.getChangeType();
+
+        if (regex.matcher(newName).matches() ||
+            (oldName != null && regex.matcher(oldName).matches())) {
+          SymbolTerm changeSym = getTypeSymbol(changeType);
+          SymbolTerm newSym = SymbolTerm.create(newName);
+          SymbolTerm oldSym = Prolog.Nil;
+          if (oldName != null) {
+            oldSym = SymbolTerm.create(oldName);
+          }
+
+          if (!a2.unify(changeSym, engine.trail)) {
+            return engine.fail();
+          }
+          if (!a3.unify(newSym, engine.trail)) {
+            return engine.fail();
+          }
+          if (!a4.unify(oldSym, engine.trail)) {
+            return engine.fail();
+          }
+          return engine.cont;
+        }
+      }
+      return engine.fail();
+    }
+  }
+
+  private static final class PRED_commit_delta_next extends Operation {
+    @Override
+    public Operation exec(Prolog engine) {
+      return engine.trust(commit_delta_empty);
+    }
+  }
+
+  private static final class PRED_commit_delta_empty extends Operation {
+    @Override
+    public Operation exec(Prolog engine) {
+      Term a5 = engine.areg5;
+
+      Iterator<PatchListEntry> iter =
+        (Iterator<PatchListEntry>)((JavaObjectTerm)a5).object();
+      if (!iter.hasNext()) {
+        return engine.fail();
+      }
+
+      return engine.jtry5(commit_delta_check, commit_delta_next);
+    }
+  }
+
+  private static SymbolTerm getTypeSymbol(Patch.ChangeType type) {
+    switch (type) {
+      case ADDED:
+        return add;
+      case MODIFIED:
+        return modify;
+      case DELETED:
+        return delete;
+      case RENAMED:
+        return rename;
+      case COPIED:
+        return copy;
+    }
+    throw new IllegalArgumentException("ChangeType not recognized");
+  }
+
+  protected PatchSetInfo getPatchSetInfo(PrologEnvironment env)
+      throws PatchSetInfoNotAvailableException {
+    PatchSetInfo psInfo = env.get(StoredValues.PATCH_SET_INFO);
+    if (psInfo == null) {
+      PatchSet.Id patchSetId = env.get(StoredValues.PATCH_SET_ID);
+      PatchSetInfoFactory patchInfoFactory =
+        env.getInjector().getInstance(PatchSetInfoFactory.class);
+      psInfo = patchInfoFactory.get(patchSetId);
+      env.set(StoredValues.PATCH_SET_INFO, psInfo);
+    }
+
+    return psInfo;
+  }
+}
\ 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 b66311b..7ff05c1 100644
--- a/gerrit-server/src/main/prolog/gerrit_common.pl
+++ b/gerrit-server/src/main/prolog/gerrit_common.pl
@@ -280,3 +280,25 @@
 %%
 commit_committer(Committer) :-
   commit_committer(Committer, _, _).
+
+
+%% commit_delta/1:
+%%
+:- public commit_delta/1.
+%%
+commit_delta(Regex) :-
+  once(commit_delta(Regex, _, _, _)).
+
+
+%% commit_delta/3:
+%%
+:- public commit_delta/3.
+%%
+commit_delta(Regex, Type, Path) :-
+  commit_delta(Regex, TmpType, NewPath, OldPath),
+  split_commit_delta(TmpType, NewPath, OldPath, Type, Path).
+
+split_commit_delta(rename, NewPath, OldPath, delete, OldPath).
+split_commit_delta(rename, NewPath, OldPath, add, NewPath) :- !.
+split_commit_delta(copy, NewPath, OldPath, add, NewPath) :- !.
+split_commit_delta(Type, Path, _, Type, Path).