Merge branch 'stable-3.3' into stable-3.4

* stable-3.3:
  LocalUsernamesToLowerCase: manage duplication automatically
  Update jgit to 73f8acdc5c97e068143c86765995c4fb6923ee91
  Bazel: Extend workspace status command to stamp jgit artifact

Change-Id: I921e768a36e646b64fed27273b2b0046c0f41575
diff --git a/Documentation/pgm-LocalUsernamesToLowerCase.txt b/Documentation/pgm-LocalUsernamesToLowerCase.txt
index 53081a1..a526647 100644
--- a/Documentation/pgm-LocalUsernamesToLowerCase.txt
+++ b/Documentation/pgm-LocalUsernamesToLowerCase.txt
@@ -28,10 +28,14 @@
 Please be aware that the conversion of the local usernames to lower
 case can't be undone.
 
-The program will produce errors if there are accounts that have the
+The program will produce errors if there are accounts with a different
+account-id or other properties (e.g. email, password) that have the
 same local username, but with different case. In this case the local
 username for these accounts is not converted to lower case.
 
+The program will automatically remove duplicates where the username
+differs only in case but all other attributes are identical.
+
 After all usernames have been migrated, the link:pgm-reindex.html[
 reindex] program is automatically invoked to reindex all accounts.
 
diff --git a/java/com/google/gerrit/pgm/BUILD b/java/com/google/gerrit/pgm/BUILD
index 16eebf2..387ff2d 100644
--- a/java/com/google/gerrit/pgm/BUILD
+++ b/java/com/google/gerrit/pgm/BUILD
@@ -49,6 +49,7 @@
         "//lib:servlet-api-without-neverlink",
         "//lib/auto:auto-value",
         "//lib/auto:auto-value-annotations",
+        "//lib/commons:lang",
         "//lib/flogger:api",
         "//lib/guice",
         "//lib/guice:guice-assistedinject",
diff --git a/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java b/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java
index e6e091c..8e2f70f 100644
--- a/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java
+++ b/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java
@@ -36,6 +36,9 @@
 import java.io.IOException;
 import java.util.Collection;
 import java.util.Locale;
+import java.util.Optional;
+import org.apache.commons.lang.StringUtils;
+import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.TextProgressMonitor;
@@ -108,8 +111,38 @@
                 extId.accountId(),
                 extId.email(),
                 extId.password());
+        replaceIfNotExists(extIdNotes, extId, extIdLowerCase);
+      }
+    }
+  }
+
+  private void replaceIfNotExists(
+      ExternalIdNotes extIdNotes, ExternalId extId, ExternalId extIdLowerCase) throws IOException {
+    try {
+      Optional<ExternalId> existingExternalId =
+          extIdNotes
+              .get(extIdLowerCase.key())
+              .filter(eid -> eid.accountId().equals(extIdLowerCase.accountId()))
+              .filter(eid -> StringUtils.equalsIgnoreCase(eid.email(), extId.email()))
+              .filter(eid -> StringUtils.equalsIgnoreCase(eid.password(), extId.password()));
+      if (existingExternalId.isPresent()) {
+        System.err.println(
+            "WARNING: external-id "
+                + extIdLowerCase
+                + " already exists with the same account-id "
+                + extId.accountId()
+                + " :"
+                + "removing the duplicate external-id "
+                + extId.key());
+        extIdNotes.delete(extId);
+      } else {
         extIdNotes.replace(extId, extIdLowerCase);
       }
+    } catch (ConfigInvalidException e) {
+      throw new IOException(
+          "Unable to parse external id definition when looking for current external-id "
+              + extIdLowerCase,
+          e);
     }
   }