Merge "Update project HEAD from stream event"
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/EventsBrokerMessageConsumer.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/EventsBrokerMessageConsumer.java
index a9b4945..cf7abba 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/EventsBrokerMessageConsumer.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/EventsBrokerMessageConsumer.java
@@ -23,6 +23,8 @@
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.inject.Inject;
@@ -58,7 +60,11 @@
     try {
       eventListener.fetchRefsForEvent(event);
       if (shutdownState.isShuttingDown()) stop();
-    } catch (AuthException | PermissionBackendException | IOException e) {
+    } catch (AuthException
+        | PermissionBackendException
+        | IOException
+        | UnprocessableEntityException
+        | ResourceNotFoundException e) {
       throw new EventRejectedException(event, e);
     }
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListener.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListener.java
index 4c621ac..2f992cd 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListener.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListener.java
@@ -24,13 +24,16 @@
 import com.google.gerrit.entities.Project.NameKey;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
 import com.google.gerrit.server.config.GerritInstanceId;
 import com.google.gerrit.server.data.RefUpdateAttribute;
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.EventListener;
 import com.google.gerrit.server.events.ProjectCreatedEvent;
 import com.google.gerrit.server.events.ProjectEvent;
+import com.google.gerrit.server.events.ProjectHeadUpdatedEvent;
 import com.google.gerrit.server.events.RefUpdatedEvent;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -47,6 +50,7 @@
 import com.googlesource.gerrit.plugins.replication.pull.api.FetchJob.Factory;
 import com.googlesource.gerrit.plugins.replication.pull.api.ProjectInitializationAction;
 import com.googlesource.gerrit.plugins.replication.pull.api.PullReplicationApiRequestMetrics;
+import com.googlesource.gerrit.plugins.replication.pull.api.UpdateHeadCommand;
 import com.googlesource.gerrit.plugins.replication.pull.filter.ExcludedRefsFilter;
 import java.io.IOException;
 import java.util.Optional;
@@ -59,6 +63,7 @@
   private final DeleteRefCommand deleteCommand;
   private final ExcludedRefsFilter refsFilter;
   private final Factory fetchJobFactory;
+  private final UpdateHeadCommand updateHeadCommand;
   private final ProjectInitializationAction projectInitializationAction;
   private final Provider<PullReplicationApiRequestMetrics> metricsProvider;
   private final SourcesCollection sources;
@@ -70,6 +75,7 @@
   public StreamEventListener(
       @Nullable @GerritInstanceId String instanceId,
       DeleteRefCommand deleteCommand,
+      UpdateHeadCommand updateHeadCommand,
       ProjectInitializationAction projectInitializationAction,
       WorkQueue workQueue,
       FetchJob.Factory fetchJobFactory,
@@ -79,6 +85,7 @@
       @Named(APPLY_OBJECTS_CACHE) Cache<ApplyObjectsCacheKey, Long> refUpdatesSucceededCache) {
     this.instanceId = instanceId;
     this.deleteCommand = deleteCommand;
+    this.updateHeadCommand = updateHeadCommand;
     this.projectInitializationAction = projectInitializationAction;
     this.workQueue = workQueue;
     this.fetchJobFactory = fetchJobFactory;
@@ -95,7 +102,11 @@
   public void onEvent(Event event) {
     try {
       fetchRefsForEvent(event);
-    } catch (AuthException | PermissionBackendException | IOException e) {
+    } catch (AuthException
+        | PermissionBackendException
+        | IOException
+        | UnprocessableEntityException
+        | ResourceNotFoundException e) {
       logger.atSevere().withCause(e).log(
           "This is the event handler of Gerrit's event-bus. It isn't"
               + "supposed to throw any exception, otherwise the other handlers "
@@ -104,7 +115,8 @@
   }
 
   public void fetchRefsForEvent(Event event)
-      throws AuthException, PermissionBackendException, IOException {
+      throws AuthException, PermissionBackendException, IOException, UnprocessableEntityException,
+          ResourceNotFoundException {
     if (instanceId.equals(event.instanceId) || !shouldReplicateProject(event)) {
       return;
     }
@@ -159,6 +171,15 @@
             "Cannot initialise project:%s", projectCreatedEvent.projectName);
         throw e;
       }
+    } else if (event instanceof ProjectHeadUpdatedEvent) {
+      ProjectHeadUpdatedEvent headUpdatedEvent = (ProjectHeadUpdatedEvent) event;
+      try {
+        updateHeadCommand.doUpdate(headUpdatedEvent.getProjectNameKey(), headUpdatedEvent.newHead);
+      } catch (UnprocessableEntityException | ResourceNotFoundException e) {
+        logger.atSevere().withCause(e).log(
+            "Failed to update HEAD on project: %s", headUpdatedEvent.projectName);
+        throw e;
+      }
     }
   }
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListenerTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListenerTest.java
index 552853e..e91ce8d 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListenerTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListenerTest.java
@@ -29,6 +29,7 @@
 import com.google.gerrit.server.data.RefUpdateAttribute;
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.ProjectCreatedEvent;
+import com.google.gerrit.server.events.ProjectHeadUpdatedEvent;
 import com.google.gerrit.server.events.RefUpdatedEvent;
 import com.google.gerrit.server.git.WorkQueue;
 import com.googlesource.gerrit.plugins.replication.pull.ApplyObjectsCacheKey;
@@ -40,6 +41,7 @@
 import com.googlesource.gerrit.plugins.replication.pull.api.FetchJob;
 import com.googlesource.gerrit.plugins.replication.pull.api.ProjectInitializationAction;
 import com.googlesource.gerrit.plugins.replication.pull.api.PullReplicationApiRequestMetrics;
+import com.googlesource.gerrit.plugins.replication.pull.api.UpdateHeadCommand;
 import com.googlesource.gerrit.plugins.replication.pull.filter.ExcludedRefsFilter;
 import java.util.concurrent.ScheduledExecutorService;
 import org.eclipse.jgit.lib.ObjectId;
@@ -66,6 +68,7 @@
   @Mock private ScheduledExecutorService executor;
   @Mock private FetchJob fetchJob;
   @Mock private FetchJob.Factory fetchJobFactory;
+  @Mock private UpdateHeadCommand updateHeadCommand;
   @Mock private DeleteRefCommand deleteRefCommand;
   @Captor ArgumentCaptor<Input> inputCaptor;
   @Mock private PullReplicationApiRequestMetrics metrics;
@@ -92,6 +95,7 @@
         new StreamEventListener(
             INSTANCE_ID,
             deleteRefCommand,
+            updateHeadCommand,
             projectInitializationAction,
             workQueue,
             fetchJobFactory,
@@ -311,4 +315,17 @@
 
     verify(executor).submit(any(FetchJob.class));
   }
+
+  @Test
+  public void shouldUpdateProjectHeadOnProjectHeadUpdatedEvent() throws Exception {
+    ProjectHeadUpdatedEvent event = new ProjectHeadUpdatedEvent();
+    event.projectName = TEST_PROJECT;
+    event.oldHead = "refs/heads/master";
+    event.newHead = "refs/heads/main";
+    event.instanceId = REMOTE_INSTANCE_ID;
+
+    objectUnderTest.onEvent(event);
+
+    verify(updateHeadCommand).doUpdate(event.getProjectNameKey(), event.newHead);
+  }
 }