Merge "Parse ChangeResource from cache (not index) on /create.change endpoint"
diff --git a/plugins/codemirror-editor b/plugins/codemirror-editor
index 13dcd3f..25301d7 160000
--- a/plugins/codemirror-editor
+++ b/plugins/codemirror-editor
@@ -1 +1 @@
-Subproject commit 13dcd3f4673c56e2b1001ef19eb54236943a35e6
+Subproject commit 25301d7a9cd661122d3be0723a246be6bdcd74c4
diff --git a/plugins/package.json b/plugins/package.json
index 134c433..612062b 100644
--- a/plugins/package.json
+++ b/plugins/package.json
@@ -29,6 +29,7 @@
     "@codemirror/lint": "^6.1.1",
     "@codemirror/search": "^6.2.3",
     "@codemirror/state": "^6.2.0",
+    "@codemirror/theme-one-dark": "^6.1.1",
     "@codemirror/view": "^6.9.1",
     "lit": "^2.2.3",
     "rxjs": "^6.6.7",
diff --git a/plugins/yarn.lock b/plugins/yarn.lock
index 1512abe..3156f4c 100644
--- a/plugins/yarn.lock
+++ b/plugins/yarn.lock
@@ -129,9 +129,9 @@
     "@lezer/php" "^1.0.0"
 
 "@codemirror/lang-python@^6.0.0", "@codemirror/lang-python@^6.1.1":
-  version "6.1.1"
-  resolved "https://registry.yarnpkg.com/@codemirror/lang-python/-/lang-python-6.1.1.tgz#378c69199da41e0b09eaadc56f6d70ad6001fd34"
-  integrity sha512-AddGMIKUssUAqaDKoxKWA5GAzy/CVE0eSY7/ANgNzdS1GYBkp6N49XKEyMElkuN04UsZ+bTIQdj+tVV75NMwJw==
+  version "6.1.2"
+  resolved "https://registry.yarnpkg.com/@codemirror/lang-python/-/lang-python-6.1.2.tgz#cabb57529679981f170491833dbf798576e7ab18"
+  integrity sha512-nbQfifLBZstpt6Oo4XxA2LOzlSp4b/7Bc5cmodG1R+Cs5PLLCTUvsMNWDnziiCfTOG/SW1rVzXq/GbIr6WXlcw==
   dependencies:
     "@codemirror/autocomplete" "^6.3.2"
     "@codemirror/language" "^6.0.0"
@@ -239,6 +239,16 @@
   resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.2.0.tgz#a0fb08403ced8c2a68d1d0acee926bd20be922f2"
   integrity sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==
 
+"@codemirror/theme-one-dark@^6.1.1":
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/@codemirror/theme-one-dark/-/theme-one-dark-6.1.1.tgz#76600555cbb314c495216f018f75b0c28daff158"
+  integrity sha512-+CfzmScfJuD6uDF5bHJkAjWTQ2QAAHxODCPxUEgcImDYcJLT+4l5vLnBHmDVv46kCC5uUJGMrBJct2Z6JbvqyQ==
+  dependencies:
+    "@codemirror/language" "^6.0.0"
+    "@codemirror/state" "^6.0.0"
+    "@codemirror/view" "^6.0.0"
+    "@lezer/highlight" "^1.0.0"
+
 "@codemirror/view@^6.0.0", "@codemirror/view@^6.2.2", "@codemirror/view@^6.6.0", "@codemirror/view@^6.9.1":
   version "6.9.1"
   resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.9.1.tgz#2ce4c528974b6172a5a4a738b7b0a0f04a4c1140"
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
index ccb750d..aba9a8b 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
@@ -1569,6 +1569,7 @@
     const payload = {
       base: e.detail.base,
       allow_conflicts: e.detail.allowConflicts,
+      on_behalf_of_uploader: e.detail.onBehalfOfUploader,
     };
     const rebaseChain = !!e.detail.rebaseChain;
     this.fireAction(
@@ -1576,7 +1577,10 @@
       assertUIActionInfo(this.revisionActions.rebase),
       rebaseChain ? false : true,
       payload,
-      {allow_conflicts: payload.allow_conflicts}
+      {
+        allow_conflicts: payload.allow_conflicts,
+        on_behalf_of_uploader: payload.on_behalf_of_uploader,
+      }
     );
   }
 
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
index 9c01f2f..277eddd 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
@@ -624,15 +624,20 @@
       assert.isTrue(fetchChangesStub.called);
       element.handleRebaseConfirm(
         new CustomEvent('', {
-          detail: {base: '1234', allowConflicts: false, rebaseChain: false},
+          detail: {
+            base: '1234',
+            allowConflicts: false,
+            rebaseChain: false,
+            onBehalfOfUploader: true,
+          },
         })
       );
       assert.deepEqual(fireActionStub.lastCall.args, [
         '/rebase',
         assertUIActionInfo(rebaseAction),
         true,
-        {base: '1234', allow_conflicts: false},
-        {allow_conflicts: false},
+        {base: '1234', allow_conflicts: false, on_behalf_of_uploader: true},
+        {allow_conflicts: false, on_behalf_of_uploader: true},
       ]);
     });
 
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
index 34869f2..5604b95 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
@@ -23,6 +23,7 @@
 import {ValueChangedEvent} from '../../../types/events';
 import {throwingErrorCallback} from '../../shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper';
 import {fireNoBubbleNoCompose} from '../../../utils/event-util';
+import {KnownExperimentId} from '../../../services/flags/flags';
 
 export interface RebaseChange {
   name: string;
@@ -33,6 +34,7 @@
   base: string | null;
   allowConflicts: boolean;
   rebaseChain: boolean;
+  onBehalfOfUploader: boolean;
 }
 
 @customElement('gr-confirm-rebase-dialog')
@@ -88,6 +90,9 @@
   @query('#rebaseOnOtherInput')
   rebaseOnOtherInput!: HTMLInputElement;
 
+  @query('#rebaseOnBehalfOfUploader')
+  private rebaseOnBehalfOfUploader?: HTMLInputElement;
+
   @query('#rebaseAllowConflicts')
   private rebaseAllowConflicts!: HTMLInputElement;
 
@@ -99,6 +104,8 @@
 
   private readonly restApiService = getAppContext().restApiService;
 
+  private readonly flagsService = getAppContext().flagsService;
+
   constructor() {
     super();
     this.query = input => this.getChangeSuggestions(input);
@@ -135,7 +142,7 @@
         display: block;
         width: 100%;
       }
-      .rebaseAllowConflicts {
+      .rebaseCheckbox:first-of-type {
         margin-top: var(--spacing-m);
       }
       .rebaseOption {
@@ -225,7 +232,20 @@
             >
             </gr-autocomplete>
           </div>
-          <div class="rebaseAllowConflicts">
+          ${when(
+            this.flagsService.isEnabled(
+              KnownExperimentId.REBASE_ON_BEHALF_OF_UPLOADER
+            ),
+            () => html`
+              <div class="rebaseCheckbox">
+                <input id="rebaseOnBehalfOfUploader" type="checkbox" checked />
+                <label for="rebaseOnBehalfOfUploader"
+                  >Rebase on behalf of uploader</label
+                >
+              </div>
+            `
+          )}
+          <div class="rebaseCheckbox">
             <input id="rebaseAllowConflicts" type="checkbox" />
             <label for="rebaseAllowConflicts"
               >Allow rebase with conflicts</label
@@ -234,7 +254,7 @@
           ${when(
             this.hasParent,
             () =>
-              html`<div>
+              html`<div class="rebaseCheckbox">
                 <input
                   id="rebaseChain"
                   type="checkbox"
@@ -351,6 +371,7 @@
       base: this.getSelectedBase(),
       allowConflicts: this.rebaseAllowConflicts.checked,
       rebaseChain: !!this.rebaseChain?.checked,
+      onBehalfOfUploader: !!this.rebaseOnBehalfOfUploader?.checked,
     };
     fireNoBubbleNoCompose(this, 'confirm-rebase', detail);
     this.text = '';
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts
index 2644d81..79d8647 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts
@@ -9,6 +9,7 @@
 import {
   pressKey,
   queryAndAssert,
+  stubFlags,
   stubRestApi,
   waitUntil,
 } from '../../../test/test-utils';
@@ -22,6 +23,7 @@
   let element: GrConfirmRebaseDialog;
 
   setup(async () => {
+    stubFlags('isEnabled').returns(true);
     element = await fixture(
       html`<gr-confirm-rebase-dialog></gr-confirm-rebase-dialog>`
     );
@@ -78,7 +80,13 @@
             >
             </gr-autocomplete>
           </div>
-          <div class="rebaseAllowConflicts">
+          <div class="rebaseCheckbox">
+            <input id="rebaseOnBehalfOfUploader" type="checkbox" checked="" />
+            <label for="rebaseOnBehalfOfUploader">
+              Rebase on behalf of uploader
+            </label>
+          </div>
+          <div class="rebaseCheckbox">
             <input id="rebaseAllowConflicts" type="checkbox" />
             <label for="rebaseAllowConflicts">
               Allow rebase with conflicts
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.ts
index 3a88eaf..a20135b 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.ts
@@ -9,6 +9,7 @@
   queryAndAssert,
   stubRestApi,
   waitEventLoop,
+  waitUntil,
 } from '../../../test/test-utils';
 
 import {GrReplyDialog} from './gr-reply-dialog';
@@ -23,6 +24,7 @@
 import {GrButton} from '../../shared/gr-button/gr-button';
 import {testResolver} from '../../../test/common-test-setup';
 import {pluginLoaderToken} from '../../shared/gr-js-api-interface/gr-plugin-loader';
+import {GrComment} from '../../shared/gr-comment/gr-comment';
 
 suite('gr-reply-dialog-it tests', () => {
   let element: GrReplyDialog;
@@ -96,10 +98,15 @@
   });
 
   test('lgtm plugin', async () => {
+    const attachStub = sinon.stub();
+    const callbackStub = sinon.stub();
     window.Gerrit.install(
       plugin => {
         const replyApi = plugin.changeReply();
+        const hook = plugin.hook('reply-text');
+        hook.onAttached(attachStub);
         replyApi.addReplyTextChangedCallback(text => {
+          callbackStub(text);
           const label = 'Code-Review';
           const labelValue = replyApi.getLabelValue(label);
           if (labelValue && labelValue === ' 0' && text.indexOf('LGTM') === 0) {
@@ -114,16 +121,27 @@
     setupElement(element);
     const pluginLoader = testResolver(pluginLoaderToken);
     pluginLoader.loadPlugins([]);
-    await pluginLoader.awaitPluginsLoaded();
-    await waitEventLoop();
-    await waitEventLoop();
+    // This may seem a bit weird, but we have to somehow make sure that the
+    // event listener is actually installed, and apparently a `gr-comment` is
+    // attached twice inside the 'reply-text' endpoint. Could not find a better
+    // way to make sure that the callback is ready to receive events.
+    await waitUntil(() => attachStub.callCount === 2);
+
+    const comment = queryAndAssert<GrComment>(
+      element,
+      'gr-comment#patchsetLevelComment'
+    );
+    comment.messageText = 'LGTM';
+
+    await waitUntil(() => callbackStub.calledWith('LGTM'));
+
     const labelScoreRows = queryAndAssert(
       element.getLabelScores(),
       'gr-label-score-row[name="Code-Review"]'
     );
     const selectedBtn = queryAndAssert(
       labelScoreRows,
-      'gr-button[data-value="+1"]'
+      'gr-button[data-value="+1"].iron-selected'
     );
     assert.isOk(selectedBtn);
   });
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
index 893eeb9..38acf80 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
@@ -914,6 +914,8 @@
         }}
         @comment-text-changed=${(e: ValueChangedEvent<string>) => {
           this.patchsetLevelDraftMessage = e.detail.value;
+          // See `addReplyTextChangedCallback` in `ChangeReplyPluginApi`.
+          fire(e.currentTarget as HTMLElement, 'value-changed', e.detail);
         }}
         .messagePlaceholder=${this.messagePlaceholder}
         hide-header
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
index 12b19c7..cf555eb 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
@@ -39,6 +39,7 @@
 } from '../../../models/views/change';
 import {userModelToken} from '../../../models/user/user-model';
 import {storageServiceToken} from '../../../services/storage/gr-storage_impl';
+import {isDarkTheme} from '../../../utils/theme-util';
 
 const RESTORED_MESSAGE = 'Content restored from a previous edit.';
 const SAVING_MESSAGE = 'Saving changes...';
@@ -88,6 +89,8 @@
   // private but used in test
   @state() latestPatchsetNumber?: RevisionPatchSetNum;
 
+  @state() private darkMode = false;
+
   private readonly restApiService = getAppContext().restApiService;
 
   private readonly reporting = getAppContext().reportingService;
@@ -132,6 +135,13 @@
       () => this.getChangeModel().latestPatchNumWithEdit$,
       x => (this.latestPatchsetNumber = x)
     );
+    subscribe(
+      this,
+      () => this.getUserModel().preferenceTheme$,
+      theme => {
+        this.darkMode = isDarkTheme(theme);
+      }
+    );
     this.shortcuts.addLocal({key: 's', modifiers: [Modifier.CTRL_KEY]}, () =>
       this.handleSaveShortcut()
     );
@@ -284,6 +294,10 @@
             name="lineNum"
             .value=${this.viewState?.editView?.lineNum}
           ></gr-endpoint-param>
+          <gr-endpoint-param
+            name="darkMode"
+            .value=${this.darkMode}
+          ></gr-endpoint-param>
           <gr-default-editor
             id="file"
             .fileContent=${this.newContent}
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts
index 0a36a05..a2c92c5 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts
@@ -117,6 +117,7 @@
             <gr-endpoint-param name="prefs"> </gr-endpoint-param>
             <gr-endpoint-param name="fileType"> </gr-endpoint-param>
             <gr-endpoint-param name="lineNum"> </gr-endpoint-param>
+            <gr-endpoint-param name="darkMode"> </gr-endpoint-param>
             <gr-default-editor id="file"> </gr-default-editor>
           </gr-endpoint-decorator>
         </div>
diff --git a/polygerrit-ui/app/services/flags/flags.ts b/polygerrit-ui/app/services/flags/flags.ts
index 7488e79..5c7bf2d 100644
--- a/polygerrit-ui/app/services/flags/flags.ts
+++ b/polygerrit-ui/app/services/flags/flags.ts
@@ -19,4 +19,5 @@
   PUSH_NOTIFICATIONS_DEVELOPER = 'UiFeature__push_notifications_developer',
   PUSH_NOTIFICATIONS = 'UiFeature__push_notifications',
   SUGGEST_EDIT = 'UiFeature__suggest_edit',
+  REBASE_ON_BEHALF_OF_UPLOADER = 'UiFeature__rebase_on_behalf_of_uploader',
 }