Merge branch 'stable-3.3'

* stable-3.3:
  RenameEmail: Preserve files which are not updated

Signed-off-by: Edwin Kempin <ekempin@google.com>
Change-Id: I1bff81db7d1e7aa55ae83c1b4e2c060611f429f0
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigFileUpdateScanner.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigFileUpdateScanner.java
index 9e15746..97d9ad2 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigFileUpdateScanner.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigFileUpdateScanner.java
@@ -112,7 +112,7 @@
                 /** pathGlob */
                 null)) {
       RevCommit revision = treeWalk.getRevision();
-      DirCache newTree = DirCache.newInCore();
+      DirCache newTree = DirCache.read(rw.getObjectReader(), revision.getTree());
       DirCacheEditor editor = newTree.editor();
 
       boolean dirty = false;
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/RenameEmailIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/RenameEmailIT.java
index 463d623..8a920dd 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/RenameEmailIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/RenameEmailIT.java
@@ -15,11 +15,13 @@
 package com.google.gerrit.plugins.codeowners.acceptance.api;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
 import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
 import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
 import static com.google.gerrit.plugins.codeowners.testing.CodeOwnerConfigSubject.assertThat;
 import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
 import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.base.Splitter;
 import com.google.common.collect.Iterables;
@@ -43,6 +45,14 @@
 import com.google.gerrit.plugins.codeowners.restapi.RenameEmail;
 import com.google.inject.Inject;
 import java.util.Optional;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -668,7 +678,7 @@
   }
 
   @Test
-  public void renameEmail_emailThatContainsEmailToBeReplacesAsSubstringStaysIntact()
+  public void renameEmail_emailThatContainsEmailToBeReplacedAsSubstringStaysIntact()
       throws Exception {
     skipTestIfRenameEmailNotSupportedByCodeOwnersBackend();
 
@@ -719,6 +729,122 @@
             secondaryEmail, otherUser1.email(), otherUser2.email(), otherUser3.email());
   }
 
+  @Test
+  public void renameEmailDoesNotTouchCodeOwnerConfigsThatDoNotContainTheEmail() throws Exception {
+    skipTestIfRenameEmailNotSupportedByCodeOwnersBackend();
+
+    TestAccount user2 = accountCreator.user2();
+
+    CodeOwnerConfig.Key codeOwnerConfigKey1 =
+        codeOwnerConfigOperations
+            .newCodeOwnerConfig()
+            .project(project)
+            .branch("master")
+            .folderPath("/")
+            .addCodeOwnerEmail(admin.email())
+            .addCodeOwnerEmail(user.email())
+            .create();
+
+    // Create a code owner config that doesn't contain the email to be replaced.
+    CodeOwnerConfig.Key codeOwnerConfigKey2 =
+        codeOwnerConfigOperations
+            .newCodeOwnerConfig()
+            .project(project)
+            .branch("master")
+            .folderPath("/foo/")
+            .addCodeOwnerEmail(user2.email())
+            .create();
+
+    // grant all users direct push permissions
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(allow(Permission.PUSH).ref("refs/*").group(REGISTERED_USERS))
+        .update();
+
+    String secondaryEmail = "user-foo@example.com";
+    accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update();
+
+    requestScopeOperations.setApiUser(user.id());
+    RenameEmailInput input = new RenameEmailInput();
+    input.oldEmail = user.email();
+    input.newEmail = secondaryEmail;
+    RenameEmailResultInfo result = renameEmail(project, "master", input);
+    assertThat(result.commit).isNotNull();
+
+    assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey1).get())
+        .hasCodeOwnerSetsThat()
+        .onlyElement()
+        .hasCodeOwnersEmailsThat()
+        .containsExactly(secondaryEmail, admin.email());
+
+    // Check that the second code owner config is still intact.
+    assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey2).get())
+        .hasCodeOwnerSetsThat()
+        .onlyElement()
+        .hasCodeOwnersEmailsThat()
+        .containsExactly(user2.email());
+  }
+
+  @Test
+  public void renameEmailDoesNotTouchNonCodeOwnerConfigFiles() throws Exception {
+    skipTestIfRenameEmailNotSupportedByCodeOwnersBackend();
+
+    CodeOwnerConfig.Key codeOwnerConfigKey =
+        codeOwnerConfigOperations
+            .newCodeOwnerConfig()
+            .project(project)
+            .branch("master")
+            .folderPath("/")
+            .addCodeOwnerEmail(admin.email())
+            .addCodeOwnerEmail(user.email())
+            .create();
+
+    // Create non code owner config files.
+    String contentFileA = "some content";
+    String contentFileB =
+        String.format(
+            "some content that contains the email %s that is being renamed", user.email());
+    try (TestRepository<Repository> testRepo =
+        new TestRepository<>(repoManager.openRepository(project))) {
+      testRepo.update(
+          "master",
+          testRepo
+              .commit()
+              .message("Update project.config from test")
+              .parent(projectOperations.project(project).getHead("master"))
+              .add("A", contentFileA)
+              .add("B", contentFileB));
+    }
+
+    // grant all users direct push permissions
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(allow(Permission.PUSH).ref("refs/*").group(REGISTERED_USERS))
+        .update();
+
+    String secondaryEmail = "user-foo@example.com";
+    accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update();
+
+    requestScopeOperations.setApiUser(user.id());
+    RenameEmailInput input = new RenameEmailInput();
+    input.oldEmail = user.email();
+    input.newEmail = secondaryEmail;
+    RenameEmailResultInfo result = renameEmail(project, "master", input);
+    assertThat(result.commit).isNotNull();
+
+    assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).get())
+        .hasCodeOwnerSetsThat()
+        .onlyElement()
+        .hasCodeOwnersEmailsThat()
+        .containsExactly(secondaryEmail, admin.email());
+
+    // Check that the non code owner config files are still intact.
+    assertThat(getFileContent(project, "master", "A")).hasValue(contentFileA);
+    assertThat(getFileContent(project, "master", "B")).hasValue(contentFileB);
+  }
+
   private RenameEmailResultInfo renameEmail(
       Project.NameKey projectName, String branchName, RenameEmailInput input)
       throws RestApiException {
@@ -732,4 +858,27 @@
     // the proto backend doesn't support renaming emails
     assumeThatCodeOwnersBackendIsNotProtoBackend();
   }
+
+  private Optional<String> getFileContent(Project.NameKey project, String branch, String fileName) {
+    try (Repository repo = repoManager.openRepository(project);
+        RevWalk rw = new RevWalk(repo)) {
+      if (!branch.startsWith(Constants.R_REFS)) {
+        branch = Constants.R_HEADS + branch;
+      }
+      Ref ref = repo.exactRef(branch);
+      if (ref == null) {
+        return Optional.empty();
+      }
+      RevTree tree = rw.parseTree(ref.getObjectId());
+      TreeWalk tw = TreeWalk.forPath(rw.getObjectReader(), fileName, tree);
+      if (tw == null) {
+        return Optional.empty();
+      }
+      ObjectLoader loader = rw.getObjectReader().open(tw.getObjectId(0));
+      String fileContent = new String(loader.getCachedBytes(), UTF_8);
+      return Optional.of(fileContent);
+    } catch (Exception e) {
+      throw new IllegalStateException(e);
+    }
+  }
 }