Trigger owners-autoassign when setting WIP to false

Fix a problem where the owners-autoassign action was not
triggered when the WIP flag was set to false as part of
a general review feedback, hence without the specific
change notes tag setReadyForReview.

Bug: Issue 14751
Change-Id: I44137a7cf6fa7677065b7d2c1cfc65164dbda657
diff --git a/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/GitRefListener.java b/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/GitRefListener.java
index b6a1474..2a072d1 100644
--- a/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/GitRefListener.java
+++ b/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/GitRefListener.java
@@ -43,6 +43,8 @@
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.notedb.ChangeNotesCommit;
+import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
 import com.google.gerrit.server.patch.PatchList;
 import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gerrit.server.patch.PatchListKey;
@@ -53,9 +55,13 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import java.io.IOException;
+import java.util.List;
 import java.util.Set;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.FooterKey;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -64,6 +70,8 @@
 public class GitRefListener implements GitReferenceUpdatedListener {
   private static final Logger logger = LoggerFactory.getLogger(GitRefListener.class);
 
+  private static final FooterKey FOOTER_WORK_IN_PROGRESS = new FooterKey("Work-in-progress");
+
   private final GerritApi api;
 
   private final PatchListCache patchListCache;
@@ -154,7 +162,7 @@
           ChangeNotes changeNotes = notesFactory.createChecked(projectNameKey, changeId);
           if ((!RefNames.isNoteDbMetaRef(refName)
                   && isChangeToBeProcessed(changeNotes.getChange(), cfg))
-              || isChangeSetReadyForReview(changeNotes, event.getNewObjectId())) {
+              || isChangeSetReadyForReview(repository, changeNotes, event.getNewObjectId())) {
             processEvent(repository, event, changeId);
           }
         }
@@ -171,13 +179,32 @@
         || cfg.getEnum(PROJECT_CONFIG_AUTOASSIGN_WIP_CHANGES, TRUE).equals(TRUE);
   }
 
-  private boolean isChangeSetReadyForReview(ChangeNotes changeNotes, String metaObjectId) {
-    return !changeNotes.getChange().isWorkInProgress()
-        && changeNotes.getChangeMessages().stream()
-            .filter(message -> message.getKey().uuid().equals(metaObjectId))
-            .map(message -> message.getTag())
-            .filter(Predicates.notNull())
-            .anyMatch(tag -> tag.contains(ChangeMessagesUtil.TAG_SET_READY));
+  private boolean isChangeSetReadyForReview(
+      Repository repository, ChangeNotes changeNotes, String metaObjectId)
+      throws MissingObjectException, IncorrectObjectTypeException, IOException {
+    if (changeNotes.getChange().isWorkInProgress()) {
+      return false;
+    }
+
+    if (changeNotes.getChangeMessages().stream()
+        .filter(message -> message.getKey().uuid().equals(metaObjectId))
+        .map(message -> message.getTag())
+        .filter(Predicates.notNull())
+        .anyMatch(tag -> tag.contains(ChangeMessagesUtil.TAG_SET_READY))) {
+      return true;
+    }
+
+    try (ChangeNotesRevWalk revWalk = ChangeNotesCommit.newRevWalk(repository)) {
+      ChangeNotesCommit metaCommit = revWalk.parseCommit(ObjectId.fromString(metaObjectId));
+      if (metaCommit.getParentCount() == 0) {
+        // The first commit cannot be a 'Set ready' operation
+        return false;
+      }
+      List<String> wipFooterLines = metaCommit.getFooterLines(FOOTER_WORK_IN_PROGRESS);
+      return wipFooterLines != null
+          && !wipFooterLines.isEmpty()
+          && Boolean.FALSE.toString().equalsIgnoreCase(wipFooterLines.get(0));
+    }
   }
 
   public void processEvent(Repository repository, Event event, Change.Id cId) {
diff --git a/owners-autoassign/src/test/java/com/vmware/gerrit/owners/common/GitRefListenerIT.java b/owners-autoassign/src/test/java/com/vmware/gerrit/owners/common/GitRefListenerIT.java
index db7e305..2cbceda 100644
--- a/owners-autoassign/src/test/java/com/vmware/gerrit/owners/common/GitRefListenerIT.java
+++ b/owners-autoassign/src/test/java/com/vmware/gerrit/owners/common/GitRefListenerIT.java
@@ -19,9 +19,10 @@
 import static org.junit.Assert.assertEquals;
 
 import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
+import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.acceptance.TestPlugin;
 import com.google.gerrit.entities.Project;
-import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.api.projects.ConfigInput;
 import com.google.gerrit.extensions.api.projects.ConfigValue;
 import com.google.gerrit.extensions.client.InheritableBoolean;
@@ -63,19 +64,10 @@
 
   @Test
   public void shouldNotProcessNoteDbOnlyRefs() throws Exception {
-    String changeRefPrefix = createChange().getChange().getId().toRefPrefix();
+    int changeNum = createChange().getChange().getId().get();
     int baselineProcessedEvents = gitRefListener().getProcessedEvents();
 
-    ReferenceUpdatedEventTest refUpdatedEvent =
-        new ReferenceUpdatedEventTest(
-            project,
-            changeRefPrefix + RefNames.META_SUFFIX.substring(1),
-            anOldObjectId,
-            aNewObjectId,
-            Type.CREATE,
-            admin.id());
-
-    gitRefListener().onGitReferenceUpdated(refUpdatedEvent);
+    gApi.changes().id(changeNum).current().review(new ReviewInput().message("Foo comment"));
     assertEquals(baselineProcessedEvents, gitRefListener().getProcessedEvents());
   }
 
@@ -91,6 +83,23 @@
   }
 
   @Test
+  public void shouldProcessSendAndStartReviewOnNoteDb() throws Exception {
+    int wipChangeNum = createChange().getChange().getId().get();
+    gApi.changes().id(wipChangeNum).setWorkInProgress();
+
+    int baselineProcessedEvents = gitRefListener().getProcessedEvents();
+
+    ReviewInput input = new ReviewInput().message("Let's start the review");
+    input.setWorkInProgress(false);
+
+    RestResponse resp =
+        adminRestSession.post("/changes/" + wipChangeNum + "/revisions/1/review", input);
+    resp.assertOK();
+
+    assertEquals(1, gitRefListener().getProcessedEvents() - baselineProcessedEvents);
+  }
+
+  @Test
   public void shouldProcessWipChangesByDefault() throws Exception {
     int baselineProcessedEvents = gitRefListener().getProcessedEvents();