Merge branch 'stable-3.2' into stable-3.3

* stable-3.2:
  PG: Remove legacy permission 'publishDrafts'
  Add searching duplicate "external" externalIds only differing in case
  find-duplicate-usernames.sh: add example output of git grep
  Fix empty TopMenu drop down list
  Script to find duplicate usernames only differing in case
  AccountPatchReview mariadb: fix key length
  RepositorySizeQuotaIT: change try/catch to assertThrows
  Enforce repository size on pack rather than on object

Change-Id: I1cca72c0c6d47e7ed085d8a26af115f1a903c576
diff --git a/contrib/find-duplicate-usernames.sh b/contrib/find-duplicate-usernames.sh
new file mode 100755
index 0000000..b59e5be
--- /dev/null
+++ b/contrib/find-duplicate-usernames.sh
@@ -0,0 +1,56 @@
+#!/bin/bash
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+usage() {
+  f="$(basename -- $0)"
+  cat <<EOF
+Usage:
+    cd /path/to/All-Users.git
+    "$f [username|gerrit|external]"
+
+This script finds duplicate usernames only differing in case in the given
+account schema ("username", "gerrit" or "external") and their respective accountIds.
+EOF
+  exit 1
+}
+
+if [[ "$#" -ne "1" ]] || ! [[ "$1" =~ ^(gerrit|username|external)$ ]]; then
+  usage
+fi
+
+# 1. find lines with user name and subsequent line in external-ids notes branch
+#    example output of git grep -A1 "\[externalId \"username:" refs/meta/external-ids:
+#    refs/meta/external-ids:00/1d/abd037e437f71d42134e6ad532a06948a2ba:[externalId "username:johndoe"]
+#    refs/meta/external-ids:00/1d/abd037e437f71d42134e6ad532a06948a2ba-      accountId = 1000815
+#    --
+#    refs/meta/external-ids:00/1f/0270fc2a6fc3a2439c454c8ab0c75323fdb0:[externalId "username:JohnDoe"]
+#    refs/meta/external-ids:00/1f/0270fc2a6fc3a2439c454c8ab0c75323fdb0-      accountId = 1000816
+#    --
+# 2. remove group separators
+# 3. remove line break between user name and accountId lines
+# 4. unify separators to ":"
+# 5. cut on ":", select username and accountId fields
+# 6. sort case-insensitive
+# 7. flip columns
+# 8. uniq case-insensitive, only show duplicates, avoid comparing first field
+# 9. flip columns back
+git grep -A1 "\[externalId \"$1:" refs/meta/external-ids \
+  | sed -E "/$1/,/accountId/!d" \
+  | paste -d ' ' - - \
+  | tr \"= : \
+  | cut -d: --output-delimiter="" -f 5,8 \
+  | sort -f \
+  | sed -E "s/(.*) (.*)/\2 \1/" \
+  | uniq -Di -f1 \
+  | sed -E "s/(.*) (.*)/\2 \1/"
diff --git a/java/com/google/gerrit/acceptance/InProcessProtocol.java b/java/com/google/gerrit/acceptance/InProcessProtocol.java
index 2a3a35f..83c63f9 100644
--- a/java/com/google/gerrit/acceptance/InProcessProtocol.java
+++ b/java/com/google/gerrit/acceptance/InProcessProtocol.java
@@ -338,7 +338,7 @@
                 .project(req.project)
                 .availableTokens(REPOSITORY_SIZE_GROUP);
         availableTokens.throwOnError();
-        availableTokens.availableTokens().ifPresent(v -> rp.setMaxObjectSizeLimit(v));
+        availableTokens.availableTokens().ifPresent(rp::setMaxPackSizeLimit);
 
         ImmutableList<PostReceiveHook> hooks =
             ImmutableList.<PostReceiveHook>builder()
diff --git a/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java b/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
index 2177485..d037994 100644
--- a/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
@@ -296,7 +296,7 @@
           REPOSITORY_SIZE_GROUP, projectName);
       throw new RuntimeException(e);
     }
-    availableTokens.availableTokens().ifPresent(v -> receivePack.setMaxObjectSizeLimit(v));
+    availableTokens.availableTokens().ifPresent(receivePack::setMaxPackSizeLimit);
   }
 
   /** Determine if the user can upload commits. */
diff --git a/java/com/google/gerrit/server/schema/MariaDBAccountPatchReviewStore.java b/java/com/google/gerrit/server/schema/MariaDBAccountPatchReviewStore.java
index b0a3370..dd82be2 100644
--- a/java/com/google/gerrit/server/schema/MariaDBAccountPatchReviewStore.java
+++ b/java/com/google/gerrit/server/schema/MariaDBAccountPatchReviewStore.java
@@ -22,6 +22,7 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.sql.SQLException;
+import java.sql.Statement;
 import org.eclipse.jgit.lib.Config;
 
 @Singleton
@@ -50,4 +51,17 @@
         return new StorageException(op + " failure on ACCOUNT_PATCH_REVIEWS", err);
     }
   }
+
+  @Override
+  protected void doCreateTable(Statement stmt) throws SQLException {
+    stmt.executeUpdate(
+        "CREATE TABLE IF NOT EXISTS account_patch_reviews ("
+            + "account_id INTEGER DEFAULT 0 NOT NULL, "
+            + "change_id INTEGER DEFAULT 0 NOT NULL, "
+            + "patch_set_id INTEGER DEFAULT 0 NOT NULL, "
+            + "file_name VARCHAR(255) DEFAULT '' NOT NULL, "
+            + "CONSTRAINT primary_key_account_patch_reviews "
+            + "PRIMARY KEY (change_id, patch_set_id, account_id, file_name)"
+            + ")");
+  }
 }
diff --git a/javatests/com/google/gerrit/acceptance/server/quota/RepositorySizeQuotaIT.java b/javatests/com/google/gerrit/acceptance/server/quota/RepositorySizeQuotaIT.java
index 801288a..2692584 100644
--- a/javatests/com/google/gerrit/acceptance/server/quota/RepositorySizeQuotaIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/quota/RepositorySizeQuotaIT.java
@@ -34,7 +34,7 @@
 import com.google.gerrit.server.quota.QuotaResponse;
 import com.google.inject.Module;
 import java.util.Collections;
-import org.eclipse.jgit.api.errors.TooLargeObjectInPackException;
+import org.eclipse.jgit.api.errors.TooLargePackException;
 import org.eclipse.jgit.api.errors.TransportException;
 import org.junit.Before;
 import org.junit.Test;
@@ -77,7 +77,7 @@
   @Test
   public void pushWithAvailableTokens() throws Exception {
     when(quotaBackendWithResource.availableTokens(REPOSITORY_SIZE_GROUP))
-        .thenReturn(singletonAggregation(ok(276L)));
+        .thenReturn(singletonAggregation(ok(277L)));
     when(quotaBackendWithResource.requestTokens(eq(REPOSITORY_SIZE_GROUP), anyLong()))
         .thenReturn(singletonAggregation(ok()));
     when(quotaBackendWithUser.project(project)).thenReturn(quotaBackendWithResource);
@@ -91,12 +91,10 @@
     when(quotaBackendWithResource.availableTokens(REPOSITORY_SIZE_GROUP))
         .thenReturn(singletonAggregation(ok(availableTokens)));
     when(quotaBackendWithUser.project(project)).thenReturn(quotaBackendWithResource);
-    TooLargeObjectInPackException thrown =
-        assertThrows(TooLargeObjectInPackException.class, () -> pushCommit());
-    assertThat(thrown).hasMessageThat().contains("Object too large");
-    assertThat(thrown)
-        .hasMessageThat()
-        .contains(String.format("Max object size limit is %d bytes.", availableTokens));
+    assertThat(assertThrows(TooLargePackException.class, () -> pushCommit()).getMessage())
+        .contains(
+            String.format(
+                "Pack exceeds the limit of %d bytes, rejecting the pack", availableTokens));
   }
 
   @Test
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
index caa0521..ee103b0 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
@@ -243,7 +243,7 @@
         items.forEach(link => {
           topMenuLinks[m.name].push(link);
         });
-      } else {
+      } else if (items.length > 0) {
         links.push({
           title: m.name,
           links: topMenuLinks[m.name] = items,
diff --git a/polygerrit-ui/app/utils/access-util.ts b/polygerrit-ui/app/utils/access-util.ts
index 4af5533..44830e2 100644
--- a/polygerrit-ui/app/utils/access-util.ts
+++ b/polygerrit-ui/app/utils/access-util.ts
@@ -32,7 +32,6 @@
   FORGE_COMMITTER = 'forgeCommitter',
   FORGE_SERVER_AS_COMMITTER = 'forgeServerAsCommitter',
   OWNER = 'owner',
-  PUBLISH_DRAFTS = 'publishDrafts',
   PUSH = 'push',
   PUSH_MERGE = 'pushMerge',
   READ = 'read',
@@ -108,10 +107,6 @@
     id: AccessPermissionId.OWNER,
     name: 'Owner',
   },
-  [AccessPermissionId.PUBLISH_DRAFTS]: {
-    id: AccessPermissionId.PUBLISH_DRAFTS,
-    name: 'Publish Drafts',
-  },
   [AccessPermissionId.PUSH]: {
     id: AccessPermissionId.PUSH,
     name: 'Push',