Implement MatchAuthorToCommitterDate for applicable strategies

Bug: https://crbug.com/732968
Change-Id: I0408d3c871d9120855c180d849cafc791f872b23
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index 4334555..0cb5e81 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -42,6 +42,7 @@
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.extensions.api.changes.SubmitInput;
 import com.google.gerrit.extensions.api.projects.BranchInput;
+import com.google.gerrit.extensions.api.projects.ConfigInput;
 import com.google.gerrit.extensions.api.projects.ProjectInput;
 import com.google.gerrit.extensions.client.ChangeStatus;
 import com.google.gerrit.extensions.client.InheritableBoolean;
@@ -989,6 +990,31 @@
     assertThat(input.generateLockFailures).containsExactly(false);
   }
 
+  @Test
+  public void authorAndCommitDateAreEqual() throws Exception {
+    assume().that(getSubmitType()).isNotEqualTo(SubmitType.FAST_FORWARD_ONLY);
+
+    ConfigInput ci = new ConfigInput();
+    ci.matchAuthorToCommitterDate = InheritableBoolean.TRUE;
+    gApi.projects().name(project.get()).config(ci);
+
+    RevCommit initialHead = getRemoteHead();
+    testRepo.reset(initialHead);
+    PushOneCommit.Result change = createChange("Change 1", "b", "b");
+
+    testRepo.reset(initialHead);
+    PushOneCommit.Result change2 = createChange("Change 2", "c", "c");
+
+    if (getSubmitType() == SubmitType.MERGE_IF_NECESSARY
+        || getSubmitType() == SubmitType.REBASE_IF_NECESSARY) {
+      // Merge another change so that change2 is not a fast-forward
+      submit(change.getChangeId());
+    }
+
+    submit(change2.getChangeId());
+    assertAuthorAndCommitDateEquals(getRemoteHead());
+  }
+
   private void setChangeStatusToNew(PushOneCommit.Result... changes) throws Exception {
     for (PushOneCommit.Result change : changes) {
       try (BatchUpdate bu =
@@ -1142,6 +1168,12 @@
     assertThat(actual.getTimeZone()).isEqualTo(expected.getTimeZone());
   }
 
+  protected void assertAuthorAndCommitDateEquals(RevCommit commit) {
+    assertThat(commit.getAuthorIdent().getWhen()).isEqualTo(commit.getCommitterIdent().getWhen());
+    assertThat(commit.getAuthorIdent().getTimeZone())
+        .isEqualTo(commit.getCommitterIdent().getTimeZone());
+  }
+
   protected void assertSubmitter(String changeId, int psId) throws Exception {
     assertSubmitter(changeId, psId, admin);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
index 50c03fe..34d239c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
@@ -67,6 +67,7 @@
   private boolean copyApprovals = true;
   private boolean detailedCommitMessage;
   private boolean postMessage = true;
+  private boolean matchAuthorToCommitterDate = false;
 
   private RevCommit rebasedCommit;
   private PatchSet.Id rebasedPatchSetId;
@@ -131,6 +132,11 @@
     return this;
   }
 
+  public RebaseChangeOp setMatchAuthorToCommitterDate(boolean matchAuthorToCommitterDate) {
+    this.matchAuthorToCommitterDate = matchAuthorToCommitterDate;
+    return this;
+  }
+
   @Override
   public void updateRepo(RepoContext ctx)
       throws MergeConflictException, InvalidChangeOperationException, RestApiException, IOException,
@@ -263,6 +269,11 @@
     } else {
       cb.setCommitter(ctx.getIdentifiedUser().newCommitterIdent(ctx.getWhen(), ctx.getTimeZone()));
     }
+    if (matchAuthorToCommitterDate) {
+      cb.setAuthor(
+          new PersonIdent(
+              cb.getAuthor(), cb.getCommitter().getWhen(), cb.getCommitter().getTimeZone()));
+    }
     ObjectId objectId = ctx.getInserter().insert(cb);
     ctx.getInserter().flush();
     return ctx.getRevWalk().parseCommit(objectId);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
index 6e17cdf..f0ec192 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
@@ -248,6 +248,7 @@
       mergeCommit.setAuthor(originalCommit.getAuthorIdent());
       mergeCommit.setCommitter(cherryPickCommitterIdent);
       mergeCommit.setMessage(commitMsg);
+      matchAuthorToCommitterDate(project, mergeCommit);
       return rw.parseCommit(inserter.insert(mergeCommit));
     }
     throw new MergeConflictException("merge conflict");
@@ -857,4 +858,14 @@
       throw new ResourceNotFoundException(e.getMessage());
     }
   }
+
+  private static void matchAuthorToCommitterDate(ProjectState project, CommitBuilder commit) {
+    if (project.isMatchAuthorToCommitterDate()) {
+      commit.setAuthor(
+          new PersonIdent(
+              commit.getAuthor(),
+              commit.getCommitter().getWhen(),
+              commit.getCommitter().getTimeZone()));
+    }
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeOneOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeOneOp.java
index 9fb75a8..3c3812d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeOneOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeOneOp.java
@@ -28,7 +28,8 @@
   @Override
   public void updateRepoImpl(RepoContext ctx) throws IntegrationException, IOException {
     PersonIdent caller =
-        ctx.getIdentifiedUser().newCommitterIdent(ctx.getWhen(), ctx.getTimeZone());
+        ctx.getIdentifiedUser()
+            .newCommitterIdent(args.serverIdent.getWhen(), args.serverIdent.getTimeZone());
     if (args.mergeTip.getCurrentTip() == null) {
       throw new IllegalStateException(
           "cannot merge commit "
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseSubmitStrategy.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseSubmitStrategy.java
index d4487b5..7f99228 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseSubmitStrategy.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseSubmitStrategy.java
@@ -179,7 +179,8 @@
                 .setDetailedCommitMessage(rebaseAlways)
                 // Do not post message after inserting new patchset because there
                 // will be one about change being merged already.
-                .setPostMessage(false);
+                .setPostMessage(false)
+                .setMatchAuthorToCommitterDate(args.project.isMatchAuthorToCommitterDate());
         try {
           rebaseOp.updateRepo(ctx);
         } catch (MergeConflictException | NoSuchChangeException e) {