Merge "Fix all remaining lit lint errors"
diff --git a/Documentation/pg-plugin-endpoints.txt b/Documentation/pg-plugin-endpoints.txt
index c16d0d4..f2a72f1 100644
--- a/Documentation/pg-plugin-endpoints.txt
+++ b/Documentation/pg-plugin-endpoints.txt
@@ -228,3 +228,13 @@
 +
 current revision displayed, an instance of
 link:rest-api-changes.html#revision-info[RevisionInfo]
+
+=== account-status-icon
+The `account-status-icon` extension point adds an icon to all account chips and
+labels.
+
+In addition to default parameters, the following are available:
+
+* `accountId`
++
+the Id of the account that the status icon should correspond to.
\ No newline at end of file
diff --git a/java/com/google/gerrit/server/git/CodeReviewCommit.java b/java/com/google/gerrit/server/git/CodeReviewCommit.java
index d7538ba..79df21a 100644
--- a/java/com/google/gerrit/server/git/CodeReviewCommit.java
+++ b/java/com/google/gerrit/server/git/CodeReviewCommit.java
@@ -24,6 +24,9 @@
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.submit.CommitMergeStatus;
 import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
 import java.util.Optional;
 import java.util.Set;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -35,7 +38,10 @@
 import org.eclipse.jgit.revwalk.RevWalk;
 
 /** Extended commit entity with code review specific metadata. */
-public class CodeReviewCommit extends RevCommit {
+public class CodeReviewCommit extends RevCommit implements Serializable {
+
+  private static final long serialVersionUID = 1L;
+
   /**
    * Default ordering when merging multiple topologically-equivalent commits.
    *
@@ -126,7 +132,7 @@
    * Message for the status that is returned to the calling user if the status indicates a problem
    * that prevents submit.
    */
-  private Optional<String> statusMessage = Optional.empty();
+  private transient Optional<String> statusMessage = Optional.empty();
 
   /** List of files in this commit that contain Git conflict markers. */
   private ImmutableSet<String> filesWithGitConflicts;
@@ -191,4 +197,22 @@
   public void setNotes(ChangeNotes notes) {
     this.notes = notes;
   }
+
+  /** Custom serialization due to {@link #statusMessage} not being Serializable by default. */
+  private void writeObject(ObjectOutputStream oos) throws IOException {
+    oos.defaultWriteObject();
+    if (this.statusMessage.isPresent()) {
+      oos.writeUTF(this.statusMessage.get());
+    }
+  }
+
+  /** Custom deserialization due to {@link #statusMessage} not being Serializable by default. */
+  private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
+    ois.defaultReadObject();
+    String statusMessage = null;
+    if (ois.available() > 0) {
+      statusMessage = ois.readUTF();
+    }
+    this.statusMessage = Optional.ofNullable(statusMessage);
+  }
 }
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index 41f68c8..4e22933 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -1431,6 +1431,12 @@
   private void parseCreate(ReceiveCommand cmd)
       throws PermissionBackendException, NoSuchProjectException, IOException {
     try (TraceTimer traceTimer = newTimer("parseCreate")) {
+      if (repo.resolve(cmd.getRefName()) != null) {
+        reject(
+            cmd,
+            String.format("Cannot create ref '%s' because it already exists.", cmd.getRefName()));
+        return;
+      }
       RevObject obj;
       try {
         obj = receivePack.getRevWalk().parseAny(cmd.getNewId());
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index 2253202..ba1e1a7 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -1427,6 +1427,34 @@
   }
 
   @Test
+  public void pushToNonVisibleBranchIsRejected() throws Exception {
+    String master = "refs/heads/master";
+
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(block(Permission.READ).ref(master).group(REGISTERED_USERS))
+        .update();
+
+    testRepo.branch("HEAD").commit().message("New Commit 1").insertChangeId().create();
+    // Since the branch is not visible to the caller, the command tries to create the ref resulting
+    // in the command being rejected because the ref already exists.
+    assertPushRejected(
+        pushHead(testRepo, master),
+        master,
+        "Cannot create ref 'refs/heads/master' because it already exists.");
+
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(allow(Permission.READ).ref(master).group(REGISTERED_USERS))
+        .update();
+
+    testRepo.branch("HEAD").commit().message("New Commit 2").insertChangeId().create();
+    assertPushOk(pushHead(testRepo, master), master);
+  }
+
+  @Test
   public void pushSameCommitTwiceUsingMagicBranchBaseOption() throws Exception {
     projectOperations
         .project(project)
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/AbstractPushTag.java b/javatests/com/google/gerrit/acceptance/rest/project/AbstractPushTag.java
index 531357a..793f256 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/AbstractPushTag.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/AbstractPushTag.java
@@ -22,6 +22,7 @@
 import static com.google.gerrit.acceptance.rest.project.AbstractPushTag.TagType.ANNOTATED;
 import static com.google.gerrit.acceptance.rest.project.AbstractPushTag.TagType.LIGHTWEIGHT;
 import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
 import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.permissionKey;
 import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
 
@@ -29,6 +30,7 @@
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.GitUtil;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.Permission;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.testing.ConfigSuite;
@@ -191,33 +193,57 @@
     pushTagDeletion(tagName, Status.OK);
   }
 
+  @Test
+  public void pushToNonVisibleTagIsRejected() throws Exception {
+    allowTagCreation();
+    allowPushOnRefsTags();
+
+    String tagName = pushTagForExistingCommit(Status.OK);
+
+    removeReadFromRefsTags();
+    removeReadFromRefsHeads();
+
+    pushTag(
+        tagName,
+        /* newCommit= */ true,
+        /* force= */ false,
+        Status.REJECTED_OTHER_REASON,
+        /* expectedMessage= */ String.format(
+            "Cannot create ref '%s' because it already exists.", tagRef(tagName)));
+  }
+
   private String pushTagForExistingCommit(Status expectedStatus) throws Exception {
-    return pushTag(null, false, false, expectedStatus);
+    return pushTag(null, false, false, expectedStatus, /* expectedMessage= */ null);
   }
 
   private String pushTagForNewCommit(Status expectedStatus) throws Exception {
-    return pushTag(null, true, false, expectedStatus);
+    return pushTag(null, true, false, expectedStatus, /* expectedMessage= */ null);
   }
 
   private void fastForwardTagToExistingCommit(String tagName, Status expectedStatus)
       throws Exception {
-    pushTag(tagName, false, false, expectedStatus);
+    pushTag(tagName, false, false, expectedStatus, /* expectedMessage= */ null);
   }
 
   private void fastForwardTagToNewCommit(String tagName, Status expectedStatus) throws Exception {
-    pushTag(tagName, true, false, expectedStatus);
+    pushTag(tagName, true, false, expectedStatus, /* expectedMessage= */ null);
   }
 
   private void forceUpdateTagToExistingCommit(String tagName, Status expectedStatus)
       throws Exception {
-    pushTag(tagName, false, true, expectedStatus);
+    pushTag(tagName, false, true, expectedStatus, /* expectedMessage= */ null);
   }
 
   private void forceUpdateTagToNewCommit(String tagName, Status expectedStatus) throws Exception {
-    pushTag(tagName, true, true, expectedStatus);
+    pushTag(tagName, true, true, expectedStatus, /* expectedMessage= */ null);
   }
 
-  private String pushTag(String tagName, boolean newCommit, boolean force, Status expectedStatus)
+  private String pushTag(
+      String tagName,
+      boolean newCommit,
+      boolean force,
+      Status expectedStatus,
+      @Nullable String expectedMessage)
       throws Exception {
     if (force) {
       testRepo.reset(initialHead);
@@ -256,6 +282,9 @@
             : GitUtil.pushTag(testRepo, tagName, !createTag);
     RemoteRefUpdate refUpdate = r.getRemoteUpdate(tagRef);
     assertWithMessage(tagType.name()).that(refUpdate.getStatus()).isEqualTo(expectedStatus);
+    if (expectedMessage != null) {
+      assertWithMessage(tagType.name()).that(refUpdate.getMessage()).isEqualTo(expectedMessage);
+    }
     return tagName;
   }
 
@@ -352,6 +381,22 @@
         .update();
   }
 
+  private void removeReadFromRefsTags() throws Exception {
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(block(Permission.READ).ref("refs/tags/*").group(REGISTERED_USERS))
+        .update();
+  }
+
+  private void removeReadFromRefsHeads() throws Exception {
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(block(Permission.READ).ref("refs/heads/*").group(REGISTERED_USERS))
+        .update();
+  }
+
   private void commit(PersonIdent ident, String subject) throws Exception {
     commitBuilder().ident(ident).message(subject + " (" + System.nanoTime() + ")").create();
   }
diff --git a/javatests/com/google/gerrit/acceptance/server/git/CodeReviewCommitTest.java b/javatests/com/google/gerrit/acceptance/server/git/CodeReviewCommitTest.java
new file mode 100644
index 0000000..b5e1b07
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/server/git/CodeReviewCommitTest.java
@@ -0,0 +1,74 @@
+// Copyright (C) 2022 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.
+
+package com.google.gerrit.acceptance.server.git;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.testing.InMemoryRepositoryManager;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.junit.Test;
+
+/** Tests for {@link CodeReviewCommit}. */
+public class CodeReviewCommitTest {
+
+  @Test
+  public void checkSerializable_withStatusMessage() throws Exception {
+    CodeReviewCommit commit = new CodeReviewCommit(createCommit());
+    commit.setStatusMessage("Status");
+    CodeReviewCommit deserializedCommit = serializeAndReadBack(commit);
+    assertThat(deserializedCommit).isEqualTo(commit);
+    assertThat(deserializedCommit.getStatusMessage().get()).isEqualTo("Status");
+  }
+
+  @Test
+  public void checkSerializable_emptyStatusMessage() throws Exception {
+    CodeReviewCommit commit = new CodeReviewCommit(createCommit());
+    CodeReviewCommit deserializedCommit = serializeAndReadBack(commit);
+    assertThat(deserializedCommit).isEqualTo(commit);
+    assertThat(deserializedCommit.getStatusMessage().isPresent()).isFalse();
+  }
+
+  private CodeReviewCommit serializeAndReadBack(CodeReviewCommit codeReviewCommit)
+      throws Exception {
+    try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        ObjectOutputStream out = new ObjectOutputStream(bos)) {
+      out.writeObject(codeReviewCommit);
+      out.flush();
+      try (ByteArrayInputStream fileIn = new ByteArrayInputStream(bos.toByteArray());
+          ObjectInputStream in = new ObjectInputStream(fileIn); ) {
+        return (CodeReviewCommit) in.readObject();
+      }
+    }
+  }
+
+  private ObjectId createCommit() throws Exception {
+    InMemoryRepositoryManager repoManager = new InMemoryRepositoryManager();
+    Project.NameKey project = Project.nameKey("test");
+    try (Repository repo = repoManager.createRepository(project);
+        TestRepository<Repository> tr = new TestRepository<>(repo)) {
+      PersonIdent ident = new PersonIdent(new PersonIdent("Test Ident", "test@test.com"));
+      return tr.commit().author(ident).committer(ident).message("Test commit").create();
+    }
+  }
+}
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
index 4d52ee9..7bbb93b 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
@@ -84,6 +84,7 @@
   'is:open',
   'is:owner',
   'is:private',
+  'is:pure-revert',
   'is:reviewed',
   'is:reviewer',
   'is:starred',
diff --git a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts
index 3dba057..9ce62c1 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts
@@ -96,6 +96,7 @@
           border: 1px solid var(--border-color);
           display: inline-flex;
           padding: 0 1px;
+          --account-label-padding-horizontal: 6px;
         }
         :host:focus {
           border-color: transparent;
@@ -131,9 +132,6 @@
     /* eslint-disable lit/prefer-static-styles */
     const customStyle = html`
       <style>
-        .container {
-          --account-label-padding-horizontal: 6px;
-        }
         gr-button.remove::part(paper-button),
         gr-button.remove:hover::part(paper-button),
         gr-button.remove:focus::part(paper-button) {
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
index bff4e20..99e10ae 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
@@ -17,6 +17,8 @@
 import '@polymer/iron-icon/iron-icon';
 import '../gr-avatar/gr-avatar';
 import '../gr-hovercard-account/gr-hovercard-account';
+import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
+import '../../plugins/gr-endpoint-param/gr-endpoint-param';
 import {getAppContext} from '../../../services/app-context';
 import {getDisplayName} from '../../../utils/display-name-util';
 import {isSelf, isServiceUser} from '../../../utils/account-util';
@@ -254,15 +256,16 @@
           ? html`<gr-avatar .account="${account}" imageSize="32"></gr-avatar>`
           : ''}
         <span class="text" part="gr-account-label-text">
-          <span class="name"
-            >${this._computeName(account, this.firstName, this._config)}</span
-          >
+          <span class="name">
+            ${this._computeName(account, this.firstName, this._config)}
+          </span>
           ${!this.hideStatus && account.status
             ? html`<iron-icon
                 class="status"
                 icon="gr-icons:unavailable"
               ></iron-icon>`
             : ''}
+          ${this.renderAccountStatusPlugins()}
         </span>
       </span>`;
   }
@@ -281,6 +284,22 @@
     });
   }
 
+  // Note: account statuses from plugins are shown regardless of
+  // hideStatus setting
+  private renderAccountStatusPlugins() {
+    if (!this.account?._account_id) {
+      return;
+    }
+    return html`
+      <gr-endpoint-decorator name="account-status-icon">
+        <gr-endpoint-param
+          name="accountId"
+          .value="${this.account._account_id}"
+        ></gr-endpoint-param>
+      </gr-endpoint-decorator>
+    `;
+  }
+
   handleKeyDown(e: KeyboardEvent) {
     if (modifierPressed(e)) return;
     // Only react to `return` and `space`.
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.ts b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.ts
index 731b7c4..edeb399 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.ts
@@ -75,6 +75,9 @@
         <span class="name">
           kermit
         </span>
+        <gr-endpoint-decorator name="account-status-icon">
+          <gr-endpoint-param name="accountId"></gr-endpoint-param>
+        </gr-endpoint-decorator>
       </span>
     </span>
     `);
diff --git a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts
index bbb743a..f0c9106 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts
@@ -99,12 +99,6 @@
           exportparts="gr-account-label-text: gr-account-link-text"
         >
         </gr-account-label>
-        <gr-endpoint-decorator name="account-status">
-          <gr-endpoint-param
-            name="accountId"
-            .value="${this.account._account_id}"
-          ></gr-endpoint-param>
-        </gr-endpoint-decorator>
       </a>
     </span>`;
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.ts b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.ts
index 00990b8..a78f32f 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.ts
@@ -45,9 +45,6 @@
           exportparts="gr-account-label-text: gr-account-link-text"
         >
         </gr-account-label>
-        <gr-endpoint-decorator name="account-status">
-          <gr-endpoint-param name="accountId"></gr-endpoint-param>
-        </gr-endpoint-decorator>
       </a>
     </span>
   `);