Merge "edit: Prevent adding, restoring, renaming or deleting if input is empty"
diff --git a/java/com/google/gerrit/server/project/ProjectConfig.java b/java/com/google/gerrit/server/project/ProjectConfig.java
index 3253282..0d710b9 100644
--- a/java/com/google/gerrit/server/project/ProjectConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectConfig.java
@@ -126,12 +126,12 @@
   public static final String KEY_CAN_OVERRIDE = "canOverride";
   public static final String KEY_BRANCH = "branch";
 
-  public static final String SUBMIT_REQUIREMENT = "submitRequirement";
+  public static final String SUBMIT_REQUIREMENT = "submit-requirement";
   public static final String KEY_SR_NAME = "name";
   public static final String KEY_SR_DESCRIPTION = "description";
-  public static final String KEY_SR_APPLICABILITY_EXPRESSION = "applicabilityExpression";
-  public static final String KEY_SR_SUBMITTABILITY_EXPRESSION = "submittabilityExpression";
-  public static final String KEY_SR_OVERRIDE_EXPRESSION = "overrideExpression";
+  public static final String KEY_SR_APPLICABILITY_EXPRESSION = "applicableIf";
+  public static final String KEY_SR_SUBMITTABILITY_EXPRESSION = "submittableIf";
+  public static final String KEY_SR_OVERRIDE_EXPRESSION = "overrideIf";
   public static final String KEY_SR_OVERRIDE_IN_CHILD_PROJECTS = "canOverrideInChildProjects";
 
   public static final String KEY_MATCH = "match";
diff --git a/java/com/google/gerrit/server/update/BatchUpdate.java b/java/com/google/gerrit/server/update/BatchUpdate.java
index 3b0cd9a..f558d30 100644
--- a/java/com/google/gerrit/server/update/BatchUpdate.java
+++ b/java/com/google/gerrit/server/update/BatchUpdate.java
@@ -75,6 +75,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.TimeZone;
@@ -553,11 +554,15 @@
     try {
       logDebug("Executing updateRepo on %d ops", ops.size());
       RepoContextImpl ctx = new RepoContextImpl();
-      for (BatchUpdateOp op : ops.values()) {
+      for (Entry<Change.Id, BatchUpdateOp> op : ops.entries()) {
         try (TraceContext.TraceTimer ignored =
             TraceContext.newTimer(
-                op.getClass().getSimpleName() + "#updateRepo", Metadata.empty())) {
-          op.updateRepo(ctx);
+                op.getClass().getSimpleName() + "#updateRepo",
+                Metadata.builder()
+                    .projectName(project.get())
+                    .changeId(op.getKey().get())
+                    .build())) {
+          op.getValue().updateRepo(ctx);
         }
       }
 
@@ -672,7 +677,8 @@
       for (BatchUpdateOp op : e.getValue()) {
         try (TraceContext.TraceTimer ignored =
             TraceContext.newTimer(
-                op.getClass().getSimpleName() + "#updateChange", Metadata.empty())) {
+                op.getClass().getSimpleName() + "#updateChange",
+                Metadata.builder().projectName(project.get()).changeId(id.get()).build())) {
           dirty |= op.updateChange(ctx);
         }
       }
diff --git a/javatests/com/google/gerrit/server/project/ProjectConfigTest.java b/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
index 2e934e9..9130d3e 100644
--- a/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
+++ b/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
@@ -209,15 +209,15 @@
             .add("groups", group(developers))
             .add(
                 "project.config",
-                "[submitRequirement \"Code-review\"]\n"
+                "[submit-requirement \"Code-review\"]\n"
                     + "  description =  At least one Code Review +2\n"
-                    + "  applicabilityExpression = branch(refs/heads/master)\n"
-                    + "  submittabilityExpression = label(code-review, +2)\n"
-                    + "[submitRequirement \"api-review\"]\n"
+                    + "  applicableIf =branch(refs/heads/master)\n"
+                    + "  submittableIf =  label(code-review, +2)\n"
+                    + "[submit-requirement \"api-review\"]\n"
                     + "  description =  Additional review required for API modifications\n"
-                    + "  applicabilityExpression = commit_filepath_contains(\\\"/api/.*\\\")\n"
-                    + "  submittabilityExpression = label(api-review, +2)\n"
-                    + "  overrideExpression = label(build-cop-override, +1)\n"
+                    + "  applicableIf =commit_filepath_contains(\\\"/api/.*\\\")\n"
+                    + "  submittableIf =  label(api-review, +2)\n"
+                    + "  overrideIf =  label(build-cop-override, +1)\n"
                     + "  canOverrideInChildProjects = true\n")
             .create();
 
@@ -257,8 +257,8 @@
             .add("groups", group(developers))
             .add(
                 "project.config",
-                "[submitRequirement \"code-review\"]\n"
-                    + "  submittabilityExpression = label(code-review, +2)\n")
+                "[submit-requirement \"code-review\"]\n"
+                    + "  submittableIf =  label(code-review, +2)\n")
             .create();
 
     ProjectConfig cfg = read(rev);
@@ -281,12 +281,12 @@
             .add("groups", group(developers))
             .add(
                 "project.config",
-                "[submitRequirement \"code-review\"]\n"
+                "[submit-requirement \"code-review\"]\n"
                     + "  description = At least one Code Review +2\n"
-                    + "  submittabilityExpression = label(code-review, +2)\n"
-                    + "[submitRequirement \"Code-Review\"]\n"
+                    + "  submittableIf =  label(code-review, +2)\n"
+                    + "[submit-requirement \"Code-Review\"]\n"
                     + "  description = Another code review label\n"
-                    + "  submittabilityExpression = label(code-review, +2)\n"
+                    + "  submittableIf =  label(code-review, +2)\n"
                     + "  canOverrideInChildProjects = true\n")
             .create();
 
@@ -317,8 +317,8 @@
             .add("groups", group(developers))
             .add(
                 "project.config",
-                "[submitRequirement \"code-review\"]\n"
-                    + "  applicabilityExpression = label(code-review, +2)\n")
+                "[submit-requirement \"code-review\"]\n"
+                    + "  applicableIf =label(code-review, +2)\n")
             .create();
 
     ProjectConfig cfg = read(rev);
@@ -943,10 +943,10 @@
         tr.commit()
             .add(
                 "project.config",
-                "[submitRequirement \"code-review\"]\n"
+                "[submit-requirement \"code-review\"]\n"
                     + "  description =  At least one Code Review +2\n"
-                    + "  applicabilityExpression = branch(refs/heads/master)\n"
-                    + "  submittabilityExpression = label(code-review, +2)\n"
+                    + "  applicableIf =branch(refs/heads/master)\n"
+                    + "  submittableIf =  label(code-review, +2)\n"
                     + "[notify \"name\"]\n"
                     + "  email = example@example.com\n")
             .create();
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index 1591738..fcf1cf4 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -121,8 +121,6 @@
     "elements/gr-app-element_html.ts",
     "elements/settings/gr-settings-view/gr-settings-view_html.ts",
     "elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_html.ts",
-    "elements/shared/gr-account-entry/gr-account-entry_html.ts",
-    "elements/shared/gr-account-label/gr-account-label_html.ts",
     "elements/shared/gr-account-list/gr-account-list_html.ts",
     "elements/shared/gr-autocomplete/gr-autocomplete_html.ts",
     "elements/shared/gr-change-status/gr-change-status_html.ts",
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 5436128..f703037 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
@@ -173,9 +173,9 @@
         <gr-account-link
           .account="${this.account}"
           .change="${this.change}"
-          ?force-attention=${this.forceAttention}
-          ?highlight-attention=${this.highlightAttention}
-          .voteable-text=${this.voteableText}
+          ?forceAttention=${this.forceAttention}
+          ?highlightAttention=${this.highlightAttention}
+          .voteableText=${this.voteableText}
         >
         </gr-account-link>
         <gr-button
diff --git a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts
index 944054e..c250428 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts
@@ -54,13 +54,13 @@
    */
 
   @property({type: Boolean})
-  allowAnyInput?: boolean;
+  allowAnyInput = false;
 
   @property({type: Boolean})
-  borderless?: boolean;
+  borderless = false;
 
   @property({type: String})
-  placeholder?: string;
+  placeholder = '';
 
   @property({type: Number})
   suggestFrom = 0;
@@ -69,7 +69,7 @@
   querySuggestions: AutocompleteQuery = () => Promise.resolve([]);
 
   @property({type: String, observer: '_inputTextChanged'})
-  _inputText?: string;
+  _inputText = '';
 
   get focusStart() {
     return this.$.input.focusStart;
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 d7078ed..cc66734 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
@@ -190,11 +190,7 @@
       : '';
   }
 
-  _computeName(
-    account?: AccountInfo,
-    config?: ServerInfo,
-    firstName?: boolean
-  ) {
+  _computeName(account: AccountInfo, firstName: boolean, config?: ServerInfo) {
     return getDisplayName(config, account, firstName);
   }
 
@@ -264,12 +260,12 @@
     highlight: boolean,
     account: AccountInfo,
     change: ChangeInfo,
-    selfAccount: AccountInfo,
-    selected: boolean
+    selected: boolean,
+    selfAccount?: AccountInfo
   ) {
     if (selected) return true;
     return (
-      this._hasUnforcedAttention(highlight, account, change) &&
+      !!this._hasUnforcedAttention(highlight, account, change) &&
       (isInvolved(change, selfAccount) || isSelf(account, selfAccount))
     );
   }
@@ -278,16 +274,16 @@
     highlight: boolean,
     account: AccountInfo,
     change: ChangeInfo,
-    selfAccount: AccountInfo,
     force: boolean,
-    selected: boolean
+    selected: boolean,
+    selfAccount?: AccountInfo
   ) {
     const enabled = this._computeAttentionButtonEnabled(
       highlight,
       account,
       change,
-      selfAccount,
-      selected
+      selected,
+      selfAccount
     );
     return enabled
       ? 'Click to remove the user from the attention set'
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_html.ts b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_html.ts
index 03178c7..352763b 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_html.ts
@@ -111,9 +111,9 @@
         link=""
         aria-label="Remove user from attention set"
         on-click="_handleRemoveAttentionClick"
-        disabled="[[!_computeAttentionButtonEnabled(highlightAttention, account, change, _selfAccount, selected)]]"
-        has-tooltip="[[_computeAttentionButtonEnabled(highlightAttention, account, change, _selfAccount, false)]]"
-        title="[[_computeAttentionIconTitle(highlightAttention, account, change, _selfAccount, forceAttention, selected)]]"
+        disabled="[[!_computeAttentionButtonEnabled(highlightAttention, account, change, selected, _selfAccount)]]"
+        has-tooltip="[[_computeAttentionButtonEnabled(highlightAttention, account, change, false, _selfAccount)]]"
+        title="[[_computeAttentionIconTitle(highlightAttention, account, change, forceAttention, selected, _selfAccount)]]"
         ><iron-icon class="attention" icon="gr-icons:attention"></iron-icon>
       </gr-button>
     </template>
@@ -126,7 +126,7 @@
       <gr-avatar account="[[account]]" imageSize="32"></gr-avatar>
     </template>
     <span class="text" part="gr-account-label-text">
-      <span class="name">[[_computeName(account, _config, firstName)]]</span>
+      <span class="name">[[_computeName(account, firstName, _config)]]</span>
       <template is="dom-if" if="[[!hideStatus]]">
         <template is="dom-if" if="[[account.status]]">
           <iron-icon class="status" icon="gr-icons:calendar"></iron-icon>
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 efaa9f7..574e450 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
@@ -55,12 +55,12 @@
   suite('_computeName', () => {
     test('not showing anonymous', () => {
       const account = {name: 'Wyatt'};
-      assert.deepEqual(element._computeName(account), 'Wyatt');
+      assert.deepEqual(element._computeName(account, false), 'Wyatt');
     });
 
     test('showing anonymous but no config', () => {
       const account = {};
-      assert.deepEqual(element._computeName(account), 'Anonymous');
+      assert.deepEqual(element._computeName(account, false), 'Anonymous');
     });
 
     test('test for Anonymous Coward user and replace with Anonymous', () => {
@@ -71,7 +71,10 @@
         },
       };
       const account = {};
-      assert.deepEqual(element._computeName(account, config), 'Anonymous');
+      assert.deepEqual(
+        element._computeName(account, false, config),
+        'Anonymous'
+      );
     });
 
     test('test for anonymous_coward_name', () => {
@@ -82,7 +85,10 @@
         },
       };
       const account = {};
-      assert.deepEqual(element._computeName(account, config), 'TestAnon');
+      assert.deepEqual(
+        element._computeName(account, false, config),
+        'TestAnon'
+      );
     });
   });
 
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
index 7298af7..6b5553d 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
@@ -15,12 +15,10 @@
  * limitations under the License.
  */
 import '../gr-linked-text/gr-linked-text';
-import '../../../styles/shared-styles';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {customElement, property} from '@polymer/decorators';
-import {htmlTemplate} from './gr-formatted-text_html';
 import {CommentLinks} from '../../../types/common';
 import {appContext} from '../../../services/app-context';
+import {GrLitElement} from '../../lit/gr-lit-element';
+import {css, customElement, html, property} from 'lit-element';
 
 const CODE_MARKER_PATTERN = /^(`{1,3})([^`]+?)\1$/;
 
@@ -36,20 +34,9 @@
     'gr-formatted-text': GrFormattedText;
   }
 }
-
-export interface GrFormattedText {
-  $: {
-    container: HTMLElement;
-  };
-}
-
 @customElement('gr-formatted-text')
-export class GrFormattedText extends PolymerElement {
-  static get template() {
-    return htmlTemplate;
-  }
-
-  @property({type: String, observer: '_contentChanged'})
+export class GrFormattedText extends GrLitElement {
+  @property({type: String})
   content?: string;
 
   @property({type: Object})
@@ -60,43 +47,70 @@
 
   private readonly reporting = appContext.reportingService;
 
-  static get observers() {
-    return ['_contentOrConfigChanged(content, config)'];
+  static get styles() {
+    return [
+      css`
+        :host {
+          display: block;
+          font-family: var(--font-family);
+        }
+        p,
+        ul,
+        code,
+        blockquote,
+        gr-linked-text.pre {
+          margin: 0 0 var(--spacing-m) 0;
+        }
+        p,
+        ul,
+        code,
+        blockquote {
+          max-width: var(--gr-formatted-text-prose-max-width, none);
+        }
+        :host(.noTrailingMargin) p:last-child,
+        :host(.noTrailingMargin) ul:last-child,
+        :host(.noTrailingMargin) blockquote:last-child,
+        :host(.noTrailingMargin) gr-linked-text.pre:last-child {
+          margin: 0;
+        }
+        code,
+        blockquote {
+          border-left: 1px solid #aaa;
+          padding: 0 var(--spacing-m);
+        }
+        code {
+          display: block;
+          white-space: pre-wrap;
+          color: var(--deemphasized-text-color);
+        }
+        li {
+          list-style-type: disc;
+          margin-left: var(--spacing-xl);
+        }
+        code,
+        gr-linked-text.pre {
+          font-family: var(--monospace-font-family);
+          font-size: var(--font-size-code);
+          /* usually 16px = 12px + 4px */
+          line-height: calc(var(--font-size-code) + var(--spacing-s));
+        }
+      `,
+    ];
   }
 
-  /** @override */
-  ready() {
-    super.ready();
+  render() {
+    const nodes = this._computeNodes(this._computeBlocks(this.content));
+    return html`<div id="container">${nodes}</div>`;
+  }
+
+  constructor() {
+    super();
+
     if (this.noTrailingMargin) {
       this.classList.add('noTrailingMargin');
     }
   }
 
-  _contentChanged(content: string) {
-    // In the case where the config may not be set (perhaps due to the
-    // request for it still being in flight), set the content anyway to
-    // prevent waiting on the config to display the text.
-    if (this.config) return;
-    this._contentOrConfigChanged(content);
-  }
-
-  /**
-   * Given a source string, update the DOM inside #container.
-   */
-  _contentOrConfigChanged(content?: string) {
-    const container = this.$.container;
-
-    // Remove existing content.
-    while (container.firstChild) {
-      container.removeChild(container.firstChild);
-    }
-
-    // Add new content.
-    for (const node of this._computeNodes(this._computeBlocks(content))) {
-      if (node) container.appendChild(node);
-    }
-  }
-
   /**
    * Given a source string, parse into an array of block objects. Each block
    * has a `type` property which takes any of the following values.
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_html.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_html.ts
deleted file mode 100644
index 04e4954..0000000
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_html.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 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.
- */
-import {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
-  <style>
-    :host {
-      display: block;
-      font-family: var(--font-family);
-    }
-    p,
-    ul,
-    code,
-    blockquote,
-    gr-linked-text.pre {
-      margin: 0 0 var(--spacing-m) 0;
-    }
-    p,
-    ul,
-    code,
-    blockquote {
-      max-width: var(--gr-formatted-text-prose-max-width, none);
-    }
-    :host(.noTrailingMargin) p:last-child,
-    :host(.noTrailingMargin) ul:last-child,
-    :host(.noTrailingMargin) blockquote:last-child,
-    :host(.noTrailingMargin) gr-linked-text.pre:last-child {
-      margin: 0;
-    }
-    code,
-    blockquote {
-      border-left: 1px solid #aaa;
-      padding: 0 var(--spacing-m);
-    }
-    code {
-      display: block;
-      white-space: pre-wrap;
-      color: var(--deemphasized-text-color);
-    }
-    li {
-      list-style-type: disc;
-      margin-left: var(--spacing-xl);
-    }
-    code,
-    gr-linked-text.pre {
-      font-family: var(--monospace-font-family);
-      font-size: var(--font-size-code);
-      /* usually 16px = 12px + 4px */
-      line-height: calc(var(--font-size-code) + var(--spacing-s));
-    }
-  </style>
-  <div id="container"></div>
-`;
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.js b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.js
index fd5a9ba..3e05f11 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.js
@@ -387,20 +387,5 @@
     assert.equal(result[3].type, 'code');
     assert.equal(result[4].type, 'quote');
   });
-
-  test('_computeNodes called without config', () => {
-    const computeNodesSpy = sinon.spy(element, '_computeNodes');
-    element.content = 'some text';
-    assert.isTrue(computeNodesSpy.called);
-  });
-
-  test('_contentOrConfigChanged called with config', () => {
-    const contentStub = sinon.stub(element, '_contentChanged');
-    const contentConfigStub = sinon.stub(element, '_contentOrConfigChanged');
-    element.content = 'some text';
-    element.config = {};
-    assert.isTrue(contentStub.called);
-    assert.isTrue(contentConfigStub.called);
-  });
 });
 
diff --git a/polygerrit-ui/app/types/events.ts b/polygerrit-ui/app/types/events.ts
index 6e37697..5145527 100644
--- a/polygerrit-ui/app/types/events.ts
+++ b/polygerrit-ui/app/types/events.ts
@@ -26,6 +26,7 @@
   CHANGE = 'change',
   CHANGED = 'changed',
   CHANGE_MESSAGE_DELETED = 'change-message-deleted',
+  COMMIT = 'commit',
   DIALOG_CHANGE = 'dialog-change',
   DROP = 'drop',
   EDITABLE_CONTENT_SAVE = 'editable-content-save',
@@ -58,6 +59,8 @@
     /* prettier-ignore */
     'changed': ChangedEvent;
     'change-message-deleted': ChangeMessageDeletedEvent;
+    /* prettier-ignore */
+    'commit': CommitEvent;
     'dialog-change': DialogChangeEvent;
     /* prettier-ignore */
     'drop': DropEvent;
@@ -109,6 +112,8 @@
 }
 export type ChangeMessageDeletedEvent = CustomEvent<ChangeMessageDeletedEventDetail>;
 
+export type CommitEvent = CustomEvent;
+
 // TODO(milutin) - remove once new gr-dialog will do it out of the box
 // This informs gr-app-element to remove footer, header from a11y tree
 export interface DialogChangeEventDetail {