Replicate first commit tree objects

Set postOrderTraversal to true in the TreeWalk object when reading
revision data for commit without parents. When post order traversal
is enabled then the walker will return a subtree after it has
returned the last entry within that subtree. This fix the issue with
"Missing Tree Object" exception for commits without parents.

Bug: Issue 40015563

Change-Id: I83a8e329917109f7d14c6006ea7609083634a772
Signed-off-by: jianghaozhe1 <jhzgg2017@gmail.com>
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 3f03ed6..f9882e8 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
@@ -141,6 +141,7 @@
           blobs = readBlobs(project, refName, git, totalRefSize, diffEntries);
         } else {
           walk.setRecursive(true);
+          walk.setPostOrderTraversal(true);
           walk.addTree(tree);
           blobs = readBlobs(project, refName, git, totalRefSize, walk);
         }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReaderIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReaderIT.java
index fcd9b19..a541d7e 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReaderIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReaderIT.java
@@ -27,6 +27,7 @@
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.Change.Id;
 import com.google.gerrit.entities.Patch;
+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.changes.ReviewInput.CommentInput;
@@ -43,6 +44,9 @@
 import java.io.IOException;
 import java.util.List;
 import java.util.Optional;
+import java.util.stream.Collectors;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
@@ -89,6 +93,54 @@
   }
 
   @Test
+  public void shouldReadForcePushCommitWithoutParent() throws Exception {
+    String refName = "refs/heads/master";
+    Project.NameKey emptyProjectName =
+        createProjectOverAPI("emptyProject", project, false, /* submitType= */ null);
+    TestRepository<InMemoryRepository> emptyRepo = cloneProject(emptyProjectName);
+    PushOneCommit push =
+        pushFactory.create(
+            admin.newIdent(),
+            emptyRepo,
+            "subject1",
+            ImmutableMap.of("a/fileA.txt", "content 1", "b/fileB.txt", "content 2"));
+    push.setForce(true);
+    Result pushResult = push.to(refName);
+    pushResult.assertOkStatus();
+
+    try (Repository repo = repoManager.openRepository(emptyProjectName)) {
+      Optional<RevisionData> revisionDataOption =
+          Optional.ofNullable(repo.exactRef(refName))
+              .map(Ref::getObjectId)
+              .flatMap(
+                  objId -> readRevisionFromObjectUnderTest(emptyProjectName, refName, objId, 0));
+
+      assertThat(revisionDataOption.isPresent()).isTrue();
+      RevisionData revisionData = revisionDataOption.get();
+      assertThat(revisionData.getCommitObject()).isNotNull();
+      assertThat(revisionData.getCommitObject().getType()).isEqualTo(Constants.OBJ_COMMIT);
+      assertThat(revisionData.getCommitObject().getContent()).isNotEmpty();
+
+      assertThat(revisionData.getTreeObject()).isNotNull();
+      assertThat(revisionData.getTreeObject().getType()).isEqualTo(Constants.OBJ_TREE);
+      assertThat(revisionData.getTreeObject().getContent()).isNotEmpty();
+
+      assertThat(revisionData.getBlobs()).hasSize(4);
+
+      assertThat(
+              revisionData.getBlobs().stream()
+                  .filter(b -> b.getType() == Constants.OBJ_TREE)
+                  .collect(Collectors.toList()))
+          .hasSize(2);
+      assertThat(
+              revisionData.getBlobs().stream()
+                  .filter(b -> b.getType() == Constants.OBJ_BLOB)
+                  .collect(Collectors.toList()))
+          .hasSize(2);
+    }
+  }
+
+  @Test
   public void shouldReadRefMetaObjectWithMaxNumberOfParents() throws Exception {
     int numberOfParents = 3;
     setReplicationConfig(numberOfParents);
@@ -145,6 +197,11 @@
 
   private Optional<RevisionData> readRevisionFromObjectUnderTest(
       String refName, ObjectId objId, int maxParentsDepth) {
+    return readRevisionFromObjectUnderTest(project, refName, objId, maxParentsDepth);
+  }
+
+  private Optional<RevisionData> readRevisionFromObjectUnderTest(
+      Project.NameKey project, String refName, ObjectId objId, int maxParentsDepth) {
     try {
       return objectUnderTest.read(project, objId, refName, maxParentsDepth);
     } catch (Exception e) {