Consider the headName value when creating a new repository

The creation of a new repository is notified through the
ProjectCreatedEvent but the headName was ignored, causing the new
project to be always created with a default head.

Use the headName in the ProjectCreatedEvent and use it for creating the
new empty repository.

Also consider the 'headName' parameter when the creation happens via
REST-API, even though the client-side of the replication via HTTP isn't
implemented yet in this change and will be expanded in a follow-up
change.

Bug: Issue 430486435
Change-Id: I2a37b01e55c1acbc5e51bde365dd84c9943c9b31
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
index b94189e..e21aaf3 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
@@ -750,10 +750,10 @@
 
   private Optional<HttpResult> initProject(
       Project.NameKey project, URIish uri, FetchApiClient fetchClient, Optional<HttpResult> result)
-      throws IOException {
+      throws IOException, URISyntaxException {
+    RevisionReader revisionReader = revReaderProvider.get();
     RevisionData refsMetaConfigRevisionData =
-        revReaderProvider
-            .get()
+        revisionReader
             .read(project, null, RefNames.REFS_CONFIG, 0)
             .orElseThrow(
                 () ->
@@ -764,7 +764,12 @@
     List<RevisionData> refsMetaConfigDataList =
         fetchWholeMetaHistory(project, RefNames.REFS_CONFIG, refsMetaConfigRevisionData);
     HttpResult initProjectResult =
-        fetchClient.initProject(project, uri, System.currentTimeMillis(), refsMetaConfigDataList);
+        fetchClient.initProject(
+            project,
+            revisionReader.getHeadName(project),
+            uri,
+            System.currentTimeMillis(),
+            refsMetaConfigDataList);
     if (initProjectResult.isSuccessful()) {
       result = Optional.of(fetchClient.callFetch(project, FetchOne.ALL_REFS, uri));
     } else {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReader.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReader.java
index 9458083..68fa98a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReader.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReader.java
@@ -19,6 +19,7 @@
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Inject;
 import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig;
@@ -319,4 +320,15 @@
         return "type:" + type;
     }
   }
+
+  @Nullable
+  public String getHeadName(Project.NameKey project) throws IOException {
+    try (Repository repo = gitRepositoryManager.openRepository(project)) {
+      Ref ref = repo.exactRef(RefNames.HEAD);
+      if (ref == null) {
+        return null;
+      }
+      return ref.getTarget().getName();
+    }
+  }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java
index 3214787..c7e8a8c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java
@@ -22,6 +22,7 @@
 import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
 import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
 
+import com.google.common.base.MoreObjects;
 import com.google.common.base.Strings;
 import com.google.common.flogger.FluentLogger;
 import com.google.common.net.MediaType;
@@ -103,15 +104,17 @@
     }
 
     String gitRepositoryName = getGitRepositoryName(httpServletRequest);
+    String headName = getGitRepositoryHeadName(httpServletRequest);
     try {
       boolean initProjectStatus;
       String contentType = httpServletRequest.getContentType();
       if (checkContentType(contentType, MediaType.JSON_UTF_8)) {
         // init project request includes project configuration in JSON format.
-        initProjectStatus = initProjectWithConfiguration(httpServletRequest, gitRepositoryName);
+        initProjectStatus =
+            initProjectWithConfiguration(httpServletRequest, gitRepositoryName, headName);
       } else if (checkContentType(contentType, MediaType.PLAIN_TEXT_UTF_8)) {
         // init project request does not include project configuration.
-        initProjectStatus = initProject(gitRepositoryName);
+        initProjectStatus = initProject(gitRepositoryName, RefNames.HEAD);
       } else {
         setResponse(
             httpServletResponse,
@@ -146,17 +149,17 @@
     }
   }
 
-  public boolean initProject(String gitRepositoryName)
+  public boolean initProject(String gitRepositoryName, String headName)
       throws AuthException, PermissionBackendException, IOException {
-    if (initProject(gitRepositoryName, true)) {
-      repLog.info("Init project API from {}", gitRepositoryName);
+    if (initProject(gitRepositoryName, headName, true)) {
+      repLog.info("Init project {} with head {}", gitRepositoryName, headName);
       return true;
     }
     return false;
   }
 
   private boolean initProjectWithConfiguration(
-      HttpServletRequest httpServletRequest, String gitRepositoryName)
+      HttpServletRequest httpServletRequest, String gitRepositoryName, String headName)
       throws AuthException,
           PermissionBackendException,
           IOException,
@@ -167,7 +170,7 @@
 
     RevisionsInput input = PayloadSerDes.parseRevisionsInput(httpServletRequest);
     validateInput(input);
-    if (!initProject(gitRepositoryName, false)) {
+    if (!initProject(gitRepositoryName, headName, false)) {
       return false;
     }
 
@@ -206,7 +209,8 @@
     return true;
   }
 
-  private boolean initProject(String gitRepositoryName, boolean needsProjectReindexing)
+  private boolean initProject(
+      String gitRepositoryName, String headName, boolean needsProjectReindexing)
       throws AuthException, PermissionBackendException, IOException {
     // When triggered internally(for example by consuming stream events) user is not provided
     // and internal user is returned. Project creation should be always allowed for internal user.
@@ -220,7 +224,7 @@
     }
     LocalFS localFS = new LocalFS(maybeUri.get());
     Project.NameKey projectNameKey = Project.NameKey.parse(gitRepositoryName);
-    if (localFS.createProject(projectNameKey, RefNames.HEAD)) {
+    if (localFS.createProject(projectNameKey, headName)) {
       if (needsProjectReindexing) {
         projectCache.onCreateProject(projectNameKey);
       }
@@ -247,6 +251,11 @@
     return Url.decode(path.substring(path.lastIndexOf('/') + 1));
   }
 
+  private String getGitRepositoryHeadName(HttpServletRequest httpServletRequest) {
+    String headName = httpServletRequest.getParameter("headName");
+    return MoreObjects.firstNonNull(headName, RefNames.HEAD);
+  }
+
   private void logExceptionAndUpdateResponse(
       HttpServletResponse httpServletResponse,
       Exception e,
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchApiClient.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchApiClient.java
index b908435..67651ff 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchApiClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchApiClient.java
@@ -16,6 +16,7 @@
 
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.Project.NameKey;
 import com.googlesource.gerrit.plugins.replication.pull.Source;
@@ -23,6 +24,7 @@
 import com.googlesource.gerrit.plugins.replication.pull.api.data.BatchApplyObjectData;
 import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionData;
 import java.io.IOException;
+import java.net.URISyntaxException;
 import java.util.List;
 import org.eclipse.jgit.transport.URIish;
 
@@ -68,10 +70,11 @@
    */
   HttpResult initProject(
       Project.NameKey project,
+      @Nullable String headName,
       URIish uri,
       long eventCreatedOn,
       List<RevisionData> refsMetaConfigRevisionData)
-      throws IOException;
+      throws IOException, URISyntaxException;
 
   HttpResult deleteProject(Project.NameKey project, URIish apiUri) throws IOException;
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java
index c3bb378..ec33a64 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java
@@ -44,6 +44,8 @@
 import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionsInput;
 import com.googlesource.gerrit.plugins.replication.pull.filter.SyncRefsFilter;
 import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.nio.charset.StandardCharsets;
 import java.util.List;
 import java.util.Optional;
@@ -59,6 +61,7 @@
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.methods.HttpPut;
 import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.client.utils.URIBuilder;
 import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.auth.BasicScheme;
 import org.apache.http.message.BasicHeader;
@@ -190,11 +193,12 @@
   @Override
   public HttpResult initProject(
       NameKey project,
+      @Nullable String headName,
       URIish uri,
       long eventCreatedOn,
       List<RevisionData> refsMetaConfigRevisionData)
-      throws IOException {
-    String url = formatInitProjectUrl(uri.toString(), project);
+      throws IOException, URISyntaxException {
+    URI url = formatInitProjectUrl(uri.toString(), project, headName);
 
     RevisionData[] inputData = new RevisionData[refsMetaConfigRevisionData.size()];
     RevisionsInput input =
@@ -321,10 +325,18 @@
         targetUri, urlAuthenticationPrefix, Url.encode(project.get()), pluginName, api);
   }
 
-  private String formatInitProjectUrl(String targetUri, Project.NameKey project) {
-    return String.format(
-        "%s/%splugins/%s/init-project/%s.git",
-        targetUri, urlAuthenticationPrefix, pluginName, Url.encode(project.get()));
+  private URI formatInitProjectUrl(
+      String targetUri, Project.NameKey project, @Nullable String headName)
+      throws URISyntaxException {
+    String baseUrl =
+        String.format(
+            "%s/%splugins/%s/init-project/%s.git",
+            targetUri, urlAuthenticationPrefix, pluginName, Url.encode(project.get()));
+    URIBuilder baseUriBuilder = new URIBuilder(baseUrl);
+    if (headName != null) {
+      baseUriBuilder = baseUriBuilder.addParameter("headName", headName);
+    }
+    return baseUriBuilder.build();
   }
 
   private void requireNull(Object object, String string) {
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 eb2d7e0..d8cf100 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
@@ -164,7 +164,8 @@
     } else if (event instanceof ProjectCreatedEvent) {
       ProjectCreatedEvent projectCreatedEvent = (ProjectCreatedEvent) event;
       try {
-        projectInitializationAction.initProject(getProjectRepositoryName(projectCreatedEvent));
+        projectInitializationAction.initProject(
+            getProjectRepositoryName(projectCreatedEvent), projectCreatedEvent.headName);
         fetchRefsAsync(
             FetchOne.ALL_REFS,
             projectCreatedEvent.instanceId,
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationITBase.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationITBase.java
index a6058d3..a73a97e 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationITBase.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationITBase.java
@@ -376,6 +376,7 @@
     FetchApiClient client = getInstance(FetchApiClient.Factory.class).create(source);
     client.initProject(
         projectToCreate,
+        null,
         new URIish(source.getApis().get(0)),
         System.currentTimeMillis(),
         Collections.emptyList());
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
index 7f4ca4e..88a7ba1 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
@@ -182,7 +182,7 @@
     when(fetchRestApiClient.callFetch(any(), anyString(), any(), anyLong(), anyBoolean()))
         .thenReturn(fetchHttpResult);
     when(fetchRestApiClient.callBatchFetch(any(), any(), any())).thenReturn(batchFetchHttpResult);
-    when(fetchRestApiClient.initProject(any(), any(), anyLong(), any()))
+    when(fetchRestApiClient.initProject(any(), any(), any(), anyLong(), any()))
         .thenReturn(successfulHttpResult);
     when(successfulHttpResult.isSuccessful()).thenReturn(true);
     when(httpResult.isSuccessful()).thenReturn(true);
@@ -273,7 +273,7 @@
     objectUnderTest.start();
     objectUnderTest.onEvent(event);
 
-    verify(fetchRestApiClient).initProject(any(), any(), anyLong(), any());
+    verify(fetchRestApiClient).initProject(any(), any(), any(), anyLong(), any());
   }
 
   @Test
@@ -299,7 +299,7 @@
     objectUnderTest.start();
     objectUnderTest.onEvent(event);
 
-    verify(fetchRestApiClient, never()).initProject(any(), any(), anyLong(), any());
+    verify(fetchRestApiClient, never()).initProject(any(), any(), any(), anyLong(), any());
   }
 
   @Test
@@ -316,7 +316,7 @@
     objectUnderTest.start();
     objectUnderTest.onEvent(event);
 
-    verify(fetchRestApiClient, never()).initProject(any(), any(), anyLong(), any());
+    verify(fetchRestApiClient, never()).initProject(any(), any(), any(), anyLong(), any());
   }
 
   @Test
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientBase.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientBase.java
index cfff177..4c756f6 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientBase.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientBase.java
@@ -557,8 +557,10 @@
 
   @Test
   public void shouldCallInitProjectEndpoint() throws Exception {
+    String headName = RefNames.REFS_HEADS + "main";
     objectUnderTest.initProject(
         Project.nameKey("test_repo"),
+        headName,
         new URIish(api),
         eventCreatedOn,
         Collections.singletonList(createSampleRevisionData()));
@@ -576,6 +578,7 @@
             String.format(
                 "%s/plugins/pull-replication/init-project/test_repo.git",
                 urlAuthenticationPrefix()));
+    assertThat(httpPut.getURI().getQuery()).isEqualTo("headName=" + headName);
     assertThat(payload).isEqualTo(expectedInitProjectWithConfigPayload);
     assertAuthentication(httpPut);
   }
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 980c509..dd6b110 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
@@ -25,6 +25,7 @@
 import com.google.common.cache.CacheBuilder;
 import com.google.common.collect.Lists;
 import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.server.data.RefUpdateAttribute;
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.ProjectCreatedEvent;
@@ -208,10 +209,12 @@
     ProjectCreatedEvent event = new ProjectCreatedEvent();
     event.instanceId = REMOTE_INSTANCE_ID;
     event.projectName = TEST_PROJECT;
+    event.headName = RefNames.HEAD;
 
     objectUnderTest.onEvent(event);
 
-    verify(projectInitializationAction).initProject(String.format("%s.git", TEST_PROJECT));
+    verify(projectInitializationAction)
+        .initProject(String.format("%s.git", TEST_PROJECT), RefNames.HEAD);
   }
 
   @Test
@@ -221,10 +224,11 @@
     ProjectCreatedEvent event = new ProjectCreatedEvent();
     event.instanceId = REMOTE_INSTANCE_ID;
     event.projectName = TEST_PROJECT;
+    event.headName = RefNames.HEAD;
 
     objectUnderTest.onEvent(event);
 
-    verify(projectInitializationAction, never()).initProject(any());
+    verify(projectInitializationAction, never()).initProject(any(), eq(RefNames.HEAD));
   }
 
   @Test
@@ -234,10 +238,11 @@
     ProjectCreatedEvent event = new ProjectCreatedEvent();
     event.instanceId = REMOTE_INSTANCE_ID;
     event.projectName = TEST_PROJECT;
+    event.headName = RefNames.HEAD;
 
     objectUnderTest.onEvent(event);
 
-    verify(projectInitializationAction, never()).initProject(any());
+    verify(projectInitializationAction, never()).initProject(any(), eq(RefNames.HEAD));
   }
 
   @Test