RepoUpdater: store in destination the source manifest revision

Right now it is not possible to know the specific revision of a manifest
that defined the destination repository. This is problematic for
debugging and to detect mismatches e.g. when the manifest is updated but
the translation to superproject failed.

Write to the destination repository a ".supermanifest" file with the
repo, reference and commit-id of the source manifest used.

Alternatives considered to write the source repo/ref/hash:

  * .gitattributes of the .gitmodules file. Some updates in the manifest
    don't touch the .gitmodules (e.g. a linkfile change), so it can fall
    out of sync.

  * commit message. Caller would need to follow the commit history to
    find the latest modification by repo command. This is not helpful
    e.g. for build bots that want to get the value in one call.

This requires the RepoCommand#addToDestination method introduced in JGit
6.1

Change-Id: Iad66199d40828143f4764421247714b6f0625233
diff --git a/java/com/googlesource/gerrit/plugins/supermanifest/RepoUpdater.java b/java/com/googlesource/gerrit/plugins/supermanifest/RepoUpdater.java
index 99ea16e..a0fb237 100644
--- a/java/com/googlesource/gerrit/plugins/supermanifest/RepoUpdater.java
+++ b/java/com/googlesource/gerrit/plugins/supermanifest/RepoUpdater.java
@@ -27,6 +27,9 @@
 import org.eclipse.jgit.lib.Repository;
 
 class RepoUpdater implements SubModuleUpdater {
+
+  static String SUPERMANIFEST_STAMP = ".supermanifest";
+
   PersonIdent serverIdent;
 
   public RepoUpdater(PersonIdent serverIdent) {
@@ -56,6 +59,11 @@
         .setRecordSubmoduleLabels(c.isRecordSubmoduleLabels())
         .setIgnoreRemoteFailures(c.ignoreRemoteFailures)
         .setInputStream(manifestStream)
+        .addToDestination(
+            SUPERMANIFEST_STAMP,
+            String.format(
+                "%s %s %s",
+                c.getSrcRepoKey().toString(), srcRef, srcRepo.resolve(srcRef).getName()))
         .setRecommendShallow(true)
         .setRemoteReader(reader)
         .setTargetURI(c.getDestRepoKey().toString())
diff --git a/javatests/com/googlesource/gerrit/plugins/supermanifest/RepoSuperManifestIT.java b/javatests/com/googlesource/gerrit/plugins/supermanifest/RepoSuperManifestIT.java
index 5ab0ec7..67e7410 100644
--- a/javatests/com/googlesource/gerrit/plugins/supermanifest/RepoSuperManifestIT.java
+++ b/javatests/com/googlesource/gerrit/plugins/supermanifest/RepoSuperManifestIT.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static com.googlesource.gerrit.plugins.supermanifest.RepoUpdater.SUPERMANIFEST_STAMP;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.gerrit.acceptance.GitUtil;
@@ -44,6 +45,7 @@
     name = "supermanifest",
     sysModule = "com.googlesource.gerrit.plugins.supermanifest.SuperManifestModule")
 public class RepoSuperManifestIT extends LightweightPluginDaemonTest {
+
   Project.NameKey[] testRepoKeys;
   String[] testRepoCommits;
   Project.NameKey manifestKey;
@@ -119,15 +121,20 @@
             + "\" path=\"project1\" />\n"
             + "</manifest>\n";
 
-    pushFactory
-        .create(admin.newIdent(), manifestRepo, "Subject", "default.xml", xml)
-        .to("refs/heads/srcbranch")
-        .assertOkStatus();
+    Result manifestPush =
+        pushFactory
+            .create(admin.newIdent(), manifestRepo, "Subject", "default.xml", xml)
+            .to("refs/heads/srcbranch");
+    manifestPush.assertOkStatus();
 
     BranchApi branch = gApi.projects().name(superKey.get()).branch("refs/heads/destbranch");
     assertThat(branch.file("project1").getContentType()).isEqualTo("x-git/gitlink; charset=UTF-8");
     assertThrows(ResourceNotFoundException.class, () -> branch.file("project2"));
 
+    assertThat(branch.file(SUPERMANIFEST_STAMP).asString())
+        .isEqualTo(
+            manifestKey.get() + " refs/heads/srcbranch " + manifestPush.getCommit().getName());
+
     xml =
         "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
             + "<manifest>\n"
@@ -240,10 +247,11 @@
             + "\" path=\"project1\" />\n"
             + "</manifest>\n";
 
-    pushFactory
-        .create(admin.newIdent(), manifestRepo, "Subject", "default.xml", xml)
-        .to("refs/heads/srcbranch")
-        .assertOkStatus();
+    Result manifestPush =
+        pushFactory
+            .create(admin.newIdent(), manifestRepo, "Subject", "default.xml", xml)
+            .to("refs/heads/srcbranch");
+    manifestPush.assertOkStatus();
     pushFactory
         .create(admin.newIdent(), manifestRepo, "Subject", "default.xml", xml)
         .to("refs/heads/anotherbranch")
@@ -272,6 +280,9 @@
 
     BranchApi branch = gApi.projects().name(superKey.get()).branch("refs/heads/destbranch");
     assertThat(branch.file("project1").getContentType()).isEqualTo("x-git/gitlink; charset=UTF-8");
+    assertThat(branch.file(SUPERMANIFEST_STAMP).asString())
+        .isEqualTo(
+            manifestKey.get() + " refs/heads/srcbranch " + manifestPush.getCommit().getName());
   }
 
   @Test
@@ -293,10 +304,11 @@
             + "\" />\n"
             + "</manifest>\n";
 
-    pushFactory
-        .create(admin.newIdent(), manifestRepo, "Subject", "default.xml", xml)
-        .to("refs/heads/srcbranch")
-        .assertOkStatus();
+    Result manifestPush =
+        pushFactory
+            .create(admin.newIdent(), manifestRepo, "Subject", "default.xml", xml)
+            .to("refs/heads/srcbranch");
+    manifestPush.assertOkStatus();
 
     // Push config after XML. Needs a manual trigger to create the destination.
     pushConfig(
@@ -326,6 +338,10 @@
     BranchApi branch = gApi.projects().name(superKey.get()).branch("refs/heads/destbranch");
     assertThat(branch.file("project1").getContentType()).isEqualTo("x-git/gitlink; charset=UTF-8");
     assertThat(branch.file("project1").asString()).isEqualTo(testRepoCommits[0]);
+
+    assertThat(branch.file(SUPERMANIFEST_STAMP).asString())
+        .startsWith(
+            manifestKey.get() + " refs/heads/srcbranch " + manifestPush.getCommit().getName());
   }
 
   @Test
@@ -435,10 +451,11 @@
             + "\" path=\"project1\" />\n"
             + "</manifest>\n";
 
-    pushFactory
-        .create(admin.newIdent(), manifestRepo, "Subject", "default.xml", xml)
-        .to("refs/heads/src1")
-        .assertOkStatus();
+    Result src1ManifestPush =
+        pushFactory
+            .create(admin.newIdent(), manifestRepo, "Subject", "default.xml", xml)
+            .to("refs/heads/src1");
+    src1ManifestPush.assertOkStatus();
 
     xml =
         "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
@@ -450,18 +467,25 @@
             + "\" path=\"project2\" />\n"
             + "</manifest>\n";
 
-    pushFactory
-        .create(admin.newIdent(), manifestRepo, "Subject", "default.xml", xml)
-        .to("refs/heads/src2")
-        .assertOkStatus();
+    Result src2ManifestPush =
+        pushFactory
+            .create(admin.newIdent(), manifestRepo, "Subject", "default.xml", xml)
+            .to("refs/heads/src2");
+    src2ManifestPush.assertOkStatus();
 
     BranchApi branch1 = gApi.projects().name(superKey.get()).branch("refs/heads/src1");
     assertThat(branch1.file("project1").getContentType()).isEqualTo("x-git/gitlink; charset=UTF-8");
     assertThrows(ResourceNotFoundException.class, () -> branch1.file("project2"));
+    assertThat(branch1.file(SUPERMANIFEST_STAMP).asString())
+        .isEqualTo(
+            manifestKey.get() + " refs/heads/src1 " + src1ManifestPush.getCommit().getName());
 
     BranchApi branch2 = gApi.projects().name(superKey.get()).branch("refs/heads/src2");
     assertThat(branch2.file("project2").getContentType()).isEqualTo("x-git/gitlink; charset=UTF-8");
     assertThrows(ResourceNotFoundException.class, () -> branch2.file("project1"));
+    assertThat(branch2.file(SUPERMANIFEST_STAMP).asString())
+        .isEqualTo(
+            manifestKey.get() + " refs/heads/src2 " + src2ManifestPush.getCommit().getName());
   }
 
   @Test
@@ -619,6 +643,8 @@
 
     BranchApi branch1 = gApi.projects().name(superKey.get()).branch("refs/heads/src1");
     assertThat(branch1.file("project1").getContentType()).isEqualTo("x-git/gitlink; charset=UTF-8");
+    assertThat(branch1.file(SUPERMANIFEST_STAMP).asString())
+        .startsWith(manifestKey.get() + " refs/heads/src1");
 
     // This branch should not exist
     BranchApi branch2 = gApi.projects().name(superKey.get()).branch("refs/heads/src2");
@@ -630,6 +656,8 @@
 
     BranchApi branch4 = gApi.projects().name(superKey.get()).branch("refs/heads/src4");
     assertThat(branch4.file("project1").getContentType()).isEqualTo("x-git/gitlink; charset=UTF-8");
+    assertThat(branch4.file(SUPERMANIFEST_STAMP).asString())
+        .startsWith(manifestKey.get() + " refs/heads/src4");
   }
   // TODO - should add tests for all the error handling in configuration parsing?
 }