Merge "Add support for cherry-picking even with merge conflicts"
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
index 6b6e90b..278875e 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
@@ -32,6 +32,7 @@
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 <link rel="import" href="../gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.html">
 <link rel="import" href="../gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.html">
+<link rel="import" href="../gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.html">
 <link rel="import" href="../gr-confirm-move-dialog/gr-confirm-move-dialog.html">
 <link rel="import" href="../gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.html">
 <link rel="import" href="../gr-confirm-revert-dialog/gr-confirm-revert-dialog.html">
@@ -187,6 +188,15 @@
           on-cancel="_handleConfirmDialogCancel"
           project="[[change.project]]"
           hidden></gr-confirm-cherrypick-dialog>
+      <gr-confirm-cherrypick-conflict-dialog id="confirmCherrypickConflict"
+          class="confirmDialog"
+          change-status="[[changeStatus]]"
+          commit-message="[[commitMessage]]"
+          commit-num="[[commitNum]]"
+          on-confirm="_handleCherrypickConflictConfirm"
+          on-cancel="_handleConfirmDialogCancel"
+          project="[[change.project]]"
+          hidden></gr-confirm-cherrypick-conflict-dialog>
       <gr-confirm-move-dialog id="confirmMove"
           class="confirmDialog"
           on-confirm="_handleMoveConfirm"
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index 368b11b..3e5c4f7 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -991,6 +991,14 @@
     },
 
     _handleCherrypickConfirm() {
+      this._handleCherryPickRestApi(false);
+    },
+
+    _handleCherrypickConflictConfirm() {
+      this._handleCherryPickRestApi(true);
+    },
+
+    _handleCherryPickRestApi(conflicts) {
       const el = this.$.confirmCherrypick;
       if (!el.branch) {
         // TODO(davido): Fix error handling
@@ -1011,6 +1019,7 @@
             destination: el.branch,
             base: el.baseCommit ? el.baseCommit : null,
             message: el.message,
+            allow_conflicts: conflicts,
           }
       );
     },
@@ -1113,8 +1122,9 @@
     _fireAction(endpoint, action, revAction, opt_payload) {
       const cleanupFn =
           this._setLoadingOnButtonWithKey(action.__type, action.__key);
-      this._send(action.method, opt_payload, endpoint, revAction, cleanupFn)
-          .then(this._handleResponse.bind(this, action));
+
+      this._send(action.method, opt_payload, endpoint, revAction, cleanupFn,
+          action).then(this._handleResponse.bind(this, action));
     },
 
     _showActionDialog(dialog) {
@@ -1171,7 +1181,14 @@
       });
     },
 
-    _handleResponseError(response) {
+    _handleResponseError(action, response, body) {
+      if (action && action.__key === RevisionActions.CHERRYPICK) {
+        if (response && response.status === 409 &&
+            body && !body.allow_conflicts) {
+          return this._showActionDialog(
+              this.$.confirmCherrypickConflict);
+        }
+      }
       return response.text().then(errText => {
         this.fire('show-error',
             {message: `Could not perform action: ${errText}`});
@@ -1187,13 +1204,12 @@
      * @param {string} actionEndpoint
      * @param {boolean} revisionAction
      * @param {?Function} cleanupFn
-     * @param {?Function=} opt_errorFn
+     * @param {!Object|undefined} action
      */
-    _send(method, payload, actionEndpoint, revisionAction, cleanupFn,
-        opt_errorFn) {
+    _send(method, payload, actionEndpoint, revisionAction, cleanupFn, action) {
       const handleError = response => {
         cleanupFn.call(this);
-        this._handleResponseError(response);
+        this._handleResponseError(action, response, payload);
       };
 
       return this.fetchChangeUpdates(this.change, this.$.restAPI)
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
index 1e63d53..1d83819 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -599,6 +599,38 @@
             destination: 'master',
             base: null,
             message: 'foo message',
+            allow_conflicts: false,
+          },
+        ]);
+      });
+
+      test('cherry pick even with conflicts', () => {
+        element._handleCherrypickTap();
+        const action = {
+          __key: 'cherrypick',
+          __type: 'revision',
+          __primary: false,
+          enabled: true,
+          label: 'Cherry pick',
+          method: 'POST',
+          title: 'Cherry pick change to a different branch',
+        };
+
+        element.$.confirmCherrypick.branch = 'master';
+
+        // Add attributes that are used to determine the message.
+        element.$.confirmCherrypick.commitMessage = 'foo message';
+        element.$.confirmCherrypick.changeStatus = 'OPEN';
+        element.$.confirmCherrypick.commitNum = '123';
+
+        element._handleCherrypickConflictConfirm();
+
+        assert.deepEqual(fireActionStub.lastCall.args, [
+          '/cherrypick', action, true, {
+            destination: 'master',
+            base: null,
+            message: 'foo message',
+            allow_conflicts: true,
           },
         ]);
       });
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.html
new file mode 100644
index 0000000..cd196ec
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.html
@@ -0,0 +1,51 @@
+<!--
+@license
+Copyright (C) 2018 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.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
+
+<dom-module id="gr-confirm-cherrypick-conflict-dialog">
+  <template>
+    <style include="shared-styles">
+      :host {
+        display: block;
+      }
+      :host([disabled]) {
+        opacity: .5;
+        pointer-events: none;
+      }
+      .main {
+        display: flex;
+        flex-direction: column;
+        width: 100%;
+      }
+    </style>
+    <gr-dialog
+        confirm-label="Continue"
+        on-confirm="_handleConfirmTap"
+        on-cancel="_handleCancelTap">
+      <div class="header" slot="header">Cherry Pick Conflict!</div>
+      <div class="main" slot="main">
+        <span>Cherry Pick failed! (merge conflicts)</span>
+
+        <span>Please select "Continue" to continue with conflicts or select "cancel" to close the dialog.</span>
+      </div>
+    </gr-dialog>
+  </template>
+  <script src="gr-confirm-cherrypick-conflict-dialog.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.js
new file mode 100644
index 0000000..355aa78
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.js
@@ -0,0 +1,45 @@
+/**
+ * @license
+ * Copyright (C) 2018 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.
+ */
+(function() {
+  'use strict';
+
+  Polymer({
+    is: 'gr-confirm-cherrypick-conflict-dialog',
+
+    /**
+     * Fired when the confirm button is pressed.
+     *
+     * @event confirm
+     */
+
+    /**
+     * Fired when the cancel button is pressed.
+     *
+     * @event cancel
+     */
+
+    _handleConfirmTap(e) {
+      e.preventDefault();
+      this.fire('confirm', null, {bubbles: false});
+    },
+
+    _handleCancelTap(e) {
+      e.preventDefault();
+      this.fire('cancel', null, {bubbles: false});
+    },
+  });
+})();
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog_test.html
new file mode 100644
index 0000000..77b102c
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog_test.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2018 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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-confirm-cherrypick-conflict-dialog</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="gr-confirm-cherrypick-conflict-dialog.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+  <template>
+    <gr-confirm-cherrypick-conflict-dialog></gr-confirm-cherrypick-conflict-dialog>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-confirm-cherrypick-conflict-dialog tests', () => {
+    let element;
+    let sandbox;
+
+    setup(() => {
+      sandbox = sinon.sandbox.create();
+      element = fixture('basic');
+    });
+
+    teardown(() => { sandbox.restore(); });
+
+    test('_handleConfirmTap', () => {
+      const confirmHandler = sandbox.stub();
+      element.addEventListener('confirm', confirmHandler);
+      sandbox.stub(element, '_handleConfirmTap');
+      element.$$('gr-dialog').fire('confirm');
+      assert.isTrue(confirmHandler.called);
+      assert.isTrue(element._handleConfirmTap.called);
+    });
+
+    test('_handleCancelTap', () => {
+      const cancelHandler = sandbox.stub();
+      element.addEventListener('cancel', cancelHandler);
+      sandbox.stub(element, '_handleCancelTap');
+      element.$$('gr-dialog').fire('cancel');
+      assert.isTrue(cancelHandler.called);
+      assert.isTrue(element._handleCancelTap.called);
+    });
+  });
+</script>
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index 3b91650..32bd396 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -71,6 +71,7 @@
     'change/gr-commit-info/gr-commit-info_test.html',
     'change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.html',
     'change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html',
+    'change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog_test.html',
     'change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.html',
     'change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html',
     'change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html',