Merge changes from topic 'ssh-keys-in-git'

* changes:
  Don't add the same SSH key several times
  Store SSH keys in git
  Support deletion of SSH keys in extension API
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 0fae667..ae42477 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -1108,11 +1108,13 @@
     "changes_per_page": 25,
     "show_site_header": true,
     "use_flash_clipboard": true,
+    "download_command": "CHECKOUT",
     "date_format": "STD",
     "time_format": "HHMM_12",
+    "diff_view": "SIDE_BY_SIDE",
     "size_bar_in_change_table": true,
     "review_category_strategy": "ABBREV",
-    "diff_view": "SIDE_BY_SIDE",
+    "mute_common_path_prefixes": true,
     "my": [
       {
         "url": "#/dashboard/self",
@@ -1162,11 +1164,13 @@
     "changes_per_page": 50,
     "show_site_header": true,
     "use_flash_clipboard": true,
+    "download_command": "CHECKOUT",
     "date_format": "STD",
     "time_format": "HHMM_12",
     "size_bar_in_change_table": true,
     "review_category_strategy": "NAME",
     "diff_view": "SIDE_BY_SIDE",
+    "mute_common_path_prefixes": true,
     "my": [
       {
         "url": "#/dashboard/self",
@@ -1210,11 +1214,13 @@
     "changes_per_page": 50,
     "show_site_header": true,
     "use_flash_clipboard": true,
+    "download_command": "CHECKOUT",
     "date_format": "STD",
     "time_format": "HHMM_12",
     "size_bar_in_change_table": true,
     "review_category_strategy": "NAME",
     "diff_view": "SIDE_BY_SIDE",
+    "mute_common_path_prefixes": true,
     "my": [
       {
         "url": "#/dashboard/self",
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
index b70cabd..eb32e5a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
@@ -140,7 +140,7 @@
     }
   }
 
-  public static void storeUrlAliases(VersionedAccountPreferences prefs,
+  private static void storeUrlAliases(VersionedAccountPreferences prefs,
       Map<String, String> urlAliases) {
     if (urlAliases != null) {
       Config cfg = prefs.getConfig();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java
index 4c5ab65..bbf2299 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java
@@ -60,6 +60,8 @@
         || i.legacycidInChangeTable != null
         || i.muteCommonPathPrefixes != null
         || i.reviewCategoryStrategy != null
+        || i.signedOffBy != null
+        || i.urlAliases != null
         || i.emailStrategy != null) {
       throw new BadRequestException("unsupported option");
     }
diff --git a/lib/asciidoctor/java/AsciiDoctor.java b/lib/asciidoctor/java/AsciiDoctor.java
index 667f274..8e18feb1 100644
--- a/lib/asciidoctor/java/AsciiDoctor.java
+++ b/lib/asciidoctor/java/AsciiDoctor.java
@@ -151,6 +151,7 @@
       }
 
       File[] cssFiles = tmpdir.listFiles(new FilenameFilter() {
+        @Override
         public boolean accept(File dir, String name) {
           return name.endsWith(".css");
         }
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
index d94a828..1066625 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
@@ -16,7 +16,6 @@
 
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
 <link rel="import" href="../../../behaviors/keyboard-shortcut-behavior.html">
-<link rel="import" href="../../shared/gr-request/gr-request.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 
 <dom-module id="gr-file-list">
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
index ad2b925..71fee37 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
@@ -19,7 +19,6 @@
 <link rel="import" href="../../../bower_components/iron-selector/iron-selector.html">
 <link rel="import" href="../../../behaviors/rest-client-behavior.html">
 <link rel="import" href="../../shared/gr-button/gr-button.html">
-<link rel="import" href="../../shared/gr-request/gr-request.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 
 <dom-module id="gr-reply-dialog">
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index 1cd0ce2..47324ba 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -46,7 +46,6 @@
       permittedLabels: Object,
 
       _account: Object,
-      _xhrPromise: Object,  // Used for testing.
     },
 
     behaviors: [
@@ -140,26 +139,24 @@
         obj.message = this.draft;
       }
       this.disabled = true;
-      this._send(obj).then(function(req) {
-        this.fire('send', null, {bubbles: false});
-        this.draft = '';
+      this._saveReview(obj).then(function(response) {
         this.disabled = false;
-      }.bind(this)).catch(function(err) {
-        alert('Oops. Something went wrong. Check the console and bug the ' +
-            'PolyGerrit team for assistance.');
-        throw err;
+        if (!response.ok) {
+          alert('Oops. Something went wrong. Check the console and bug the ' +
+              'PolyGerrit team for assistance.');
+          return response.text().then(function(text) {
+            console.error(text);
+          });
+        }
+
+        this.draft = '';
+        this.fire('send', null, {bubbles: false});
       }.bind(this));
     },
 
-    _send: function(payload) {
-      var xhr = document.createElement('gr-request');
-      this._xhrPromise = xhr.send({
-        method: 'POST',
-        url: this.changeBaseURL(this.changeNum, this.patchNum) + '/review',
-        body: payload,
-      });
-
-      return this._xhrPromise;
+    _saveReview: function(review) {
+      return this.$.restAPI.saveChangeReview(this.changeNum, this.patchNum,
+          review);
     },
   });
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
index fb2de6a..bc850e3 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
@@ -34,11 +34,10 @@
 <script>
   suite('gr-reply-dialog tests', function() {
     var element;
-    var server;
 
     setup(function() {
-      stub('gr-reply-dialog', {
-        _getAccount: function() { return Promise.resolve({}); },
+      stub('gr-rest-api-interface', {
+        getAccount: function() { return Promise.resolve({}); },
       });
       element = fixture('basic');
       element.changeNum = 42;
@@ -76,31 +75,10 @@
         ]
       };
 
-      server = sinon.fakeServer.create();
-      server.respondWith(
-        'POST',
-        '/changes/42/revisions/1/review',
-        [
-          200,
-          {'Content-Type': 'application/json'},
-          ')]}\'\n' +
-          '{' +
-            '"labels": {' +
-              '"Code-Review": -1,' +
-              '"Verified": -1' +
-            '}' +
-          '}'
-        ]
-      );
-
       // Allow the elements created by dom-repeat to be stamped.
       flushAsynchronousOperations();
     });
 
-    teardown(function() {
-      server.restore();
-    });
-
     test('cancel event', function(done) {
       element.addEventListener('cancel', function() { done(); });
       MockInteractions.tap(element.$$('.cancel'));
@@ -122,32 +100,32 @@
             'iron-selector[data-label="Verified"] > ' +
             'gr-button[data-value="-1"]'));
 
+        var saveReviewStub = sinon.stub(element, '_saveReview',
+            function(review) {
+          assert.deepEqual(review, {
+            drafts: 'PUBLISH_ALL_REVISIONS',
+            labels: {
+              'Code-Review': -1,
+              'Verified': -1
+            },
+            message: 'I wholeheartedly disapprove'
+          });
+          return Promise.resolve({ok: true});
+        });
+
+        element.addEventListener('send', function() {
+          assert.isFalse(element.disabled,
+              'Element should be enabled when done sending reply.');
+          assert.equal(element.draft.length, 0);
+          saveReviewStub.restore();
+          done();
+        });
+
         // This is needed on non-Blink engines most likely due to the ways in
         // which the dom-repeat elements are stamped.
         flush(function() {
           MockInteractions.tap(element.$$('.send'));
           assert.isTrue(element.disabled);
-
-          server.respond();
-
-          element._xhrPromise.then(function(req) {
-            assert.isFalse(element.disabled,
-                'Element should be enabled when done sending reply.');
-            assert.equal(req.status, 200);
-            assert.equal(req.url, '/changes/42/revisions/1/review');
-            var reqObj = JSON.parse(req.xhr.requestBody);
-            assert.deepEqual(reqObj, {
-              drafts: 'PUBLISH_ALL_REVISIONS',
-              labels: {
-                'Code-Review': -1,
-                'Verified': -1
-              },
-              message: 'I wholeheartedly disapprove'
-            });
-            assert.equal(req.response.labels['Code-Review'], -1);
-            assert.equal(req.response.labels.Verified, -1);
-            done();
-          });
         });
       });
     });
diff --git a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.html b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.html
new file mode 100644
index 0000000..28e45a4
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.html
@@ -0,0 +1,58 @@
+<!--
+Copyright (C) 2016 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="../gr-button/gr-button.html">
+
+<dom-module id="gr-alert">
+  <template>
+    <style>
+      /**
+       * ALERT: DO NOT ADD TRANSITION PROPERTIES WITHOUT PROPERLY UNDERSTANDING
+       * HOW THEY ARE USED IN THE CODE.
+       */
+      :host([toast]) {
+        background-color: #333;
+        bottom: 1.25rem;
+        border-radius: 3px;
+        box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
+        color: #fff;
+        left: 1.25rem;
+        padding: 1em 1.5em;
+        position: fixed;
+        transform: translateY(5rem);
+        transition: transform var(--gr-alert-transition-duration, 80ms) ease-out;
+      }
+      :host([shown]) {
+        transform: translateY(0);
+      }
+      .action {
+        color: #a1c2fa;
+        font-weight: bold;
+        margin-left: 1em;
+        text-decoration: none;
+      }
+    </style>
+    [[text]]
+    <gr-button
+        link
+        class="action"
+        hidden$="[[!actionText]]"
+        on-tap="_handleActionTap">[[actionText]]</gr-button>
+  </template>
+  <script src="gr-alert.js"></script>
+</dom-module>
+
diff --git a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.js b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.js
new file mode 100644
index 0000000..ba481ec
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.js
@@ -0,0 +1,88 @@
+// Copyright (C) 2016 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-alert',
+
+    /**
+     * Fired when the action button is pressed.
+     *
+     * @event action
+     */
+
+    properties: {
+      text: String,
+      actionText: String,
+      shown: {
+        type: Boolean,
+        value: true,
+        readOnly: true,
+        reflectToAttribute: true,
+      },
+      toast: {
+        type: Boolean,
+        value: true,
+        reflectToAttribute: true,
+      },
+
+      _boundTransitionEndHandler: {
+        type: Function,
+        value: function() { return this._handleTransitionEnd.bind(this); },
+      },
+    },
+
+    attached: function() {
+      this.addEventListener('transitionend', this._boundTransitionEndHandler);
+    },
+
+    detached: function() {
+      this.removeEventListener('transitionend',
+          this._boundTransitionEndHandler);
+    },
+
+    show: function(text, opt_actionText) {
+      this.text = text;
+      this.actionText = opt_actionText;
+      document.body.appendChild(this);
+      this._setShown(true);
+    },
+
+    hide: function() {
+      this._setShown(false);
+      if (this._hasZeroTransitionDuration()) {
+        document.body.removeChild(this);
+      }
+    },
+
+    _hasZeroTransitionDuration: function() {
+      var style = window.getComputedStyle(this);
+      // transitionDuration is always given in seconds.
+      var duration = Math.round(parseFloat(style.transitionDuration) * 100);
+      return duration === 0;
+    },
+
+    _handleTransitionEnd: function(e) {
+      if (this.shown) { return; }
+
+      document.body.removeChild(this);
+    },
+
+    _handleActionTap: function(e) {
+      e.preventDefault();
+      this.fire('action', null, {bubbles: false});
+    },
+  });
+})();
diff --git a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.html b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.html
new file mode 100644
index 0000000..067ac5b
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2015 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-alert</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="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="gr-alert.html">
+
+<script>
+  suite('gr-alert tests', function() {
+    var element;
+
+    setup(function() {
+      element = document.createElement('gr-alert');
+    });
+
+    teardown(function() {
+      if (element.parentNode) {
+        element.parentNode.removeChild(element);
+      }
+    });
+
+    test('show/hide', function() {
+      assert.isNull(element.parentNode);
+      element.show();
+      assert.equal(element.parentNode, document.body);
+      element.customStyle['--gr-alert-transition-duration'] = '0ms';
+      element.updateStyles();
+      element.hide();
+      assert.isNull(element.parentNode);
+    });
+
+    test('action event', function(done) {
+      element.show();
+      element.addEventListener('action', function() {
+        done();
+      });
+      MockInteractions.tap(element.$$('.action'));
+    });
+
+  });
+</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index f23d50b..54ce15c 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -325,6 +325,12 @@
       return this.send(method, url, null, opt_errFn, opt_ctx);
     },
 
+    saveChangeReview: function(changeNum, patchNum, review, opt_errFn,
+        opt_ctx) {
+      var url = this.getChangeActionURL(changeNum, patchNum, '/review');
+      return this.send('POST', url, review, opt_errFn, opt_ctx);
+    },
+
     send: function(method, url, opt_body, opt_errFn, opt_ctx) {
       var headers = new Headers({
         'X-Gerrit-Auth': this._getCookie('XSRF_TOKEN'),
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index 4238f9a..dc12bf9 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -48,6 +48,7 @@
     '../elements/diff/gr-diff-preferences/gr-diff-preferences_test.html',
     '../elements/diff/gr-diff-view/gr-diff-view_test.html',
     '../elements/diff/gr-patch-range-select/gr-patch-range-select_test.html',
+    '../elements/shared/gr-alert/gr-alert_test.html',
     '../elements/shared/gr-account-label/gr-account-label_test.html',
     '../elements/shared/gr-account-link/gr-account-link_test.html',
     '../elements/shared/gr-avatar/gr-avatar_test.html',