Merge changes Id3047574,I70d0981e
* changes:
ChangeScreen2: provide commands for downloading a patch set
/accounts/self/preferences REST API
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
index 2de4608..06fad36 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
@@ -29,6 +29,10 @@
* accounts.
*/
public class AccountApi {
+ public static RestApi self() {
+ return new RestApi("/accounts/").view("self");
+ }
+
/** Retrieve the username */
public static void getUsername(String account, AsyncCallback<NativeString> cb) {
new RestApi("/accounts/").id(account).view("username").get(cb);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
index 44e4456..ddb2c7f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
@@ -117,6 +117,7 @@
private UpdateAvailableBar updateAvailable;
private boolean openReplyBox;
+ @UiField HTMLPanel headerLine;
@UiField Style style;
@UiField ToggleButton star;
@UiField Reload reload;
@@ -145,6 +146,7 @@
@UiField FileTable files;
@UiField FlowPanel history;
+ @UiField Button download;
@UiField Button reply;
@UiField Button expandAll;
@UiField Button collapseAll;
@@ -152,6 +154,7 @@
@UiField QuickApprove quickApprove;
private ReplyAction replyAction;
private EditMessageAction editMessageAction;
+ private DownloadAction downloadAction;
public ChangeScreen2(Change.Id changeId, String revision, boolean openReplyBox) {
this.changeId = changeId;
@@ -243,14 +246,22 @@
}
}
- private void initEditMessageAction() {
- NativeMap<ActionInfo> actions = changeInfo.revision(revision).actions();
+ private void renderDownload(ChangeInfo info, String revision) {
+ downloadAction = new DownloadAction(
+ info.legacy_id(),
+ info.project(),
+ info.revision(revision),
+ style, headerLine, download);
+ }
+
+ private void initEditMessageAction(ChangeInfo info, String revision) {
+ NativeMap<ActionInfo> actions = info.revision(revision).actions();
if (actions != null && actions.containsKey("message")) {
editMessage.setVisible(true);
editMessageAction = new EditMessageAction(
- changeInfo.legacy_id(),
+ info.legacy_id(),
revision,
- changeInfo.revision(revision).commit().message(),
+ info.revision(revision).commit().message(),
style,
editMessage,
reply);
@@ -324,6 +335,11 @@
StarredChanges.toggleStar(changeId, e.getValue());
}
+ @UiHandler("download")
+ void onDownload(ClickEvent e) {
+ downloadAction.show();
+ }
+
@UiHandler("revisionList")
void onChangeRevision(ChangeEvent e) {
int idx = revisionList.getSelectedIndex();
@@ -580,6 +596,7 @@
renderOwner(info);
renderReviewers(info);
renderActionTextDate(info);
+ renderDownload(info, revision);
renderRevisions(info);
renderHistory(info);
actions.display(info, revision);
@@ -598,7 +615,7 @@
quickApprove.set(info, revision);
if (Gerrit.isSignedIn()) {
- initEditMessageAction();
+ initEditMessageAction(info, revision);
replyAction = new ReplyAction(info, revision, style, reply);
if (topic.canEdit()) {
keysAction.add(new KeyCommand(0, 't', Util.C.keyEditTopic()) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml
index cea072b..ee0c74a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml
@@ -83,10 +83,27 @@
padding-left: 5px;
}
- .revisionList {
+ .popdown {
position: absolute;
top: 2px;
- right: 10px;
+ right: 0;
+ }
+ .popdown button {
+ cursor: pointer;
+ height: 25px;
+ border: none;
+ border-left: 1px solid #fff;
+ background-color: trimColor;
+ margin-right: 0;
+ padding-left: 0;
+ padding-right: 0;
+ }
+ .popdown button div {
+ padding-left: 6px;
+ padding-right: 6px;
+ }
+ .popdown button div:after {
+ content: " \25bc";
}
.headerTable {
@@ -201,7 +218,7 @@
</ui:style>
<g:HTMLPanel>
- <div class='{style.headerLine}'>
+ <g:HTMLPanel styleName='{style.headerLine}' ui:field='headerLine'>
<div class='{style.idBlock}'>
<c:StarIcon ui:field='star' styleName='{style.star}'/>
<div class='{style.idLine}'>
@@ -235,10 +252,16 @@
<div><ui:msg>Edit Message</ui:msg></div>
</g:Button>
</div>
- <div class='{style.revisionList}' ui:field='revisionParent'>
- <ui:msg>Revision <g:ListBox ui:field='revisionList'/></ui:msg>
+
+ <div class='{style.popdown}'>
+ <div ui:field='revisionParent' style='display: inline-block;'>
+ <ui:msg>Revision <g:ListBox ui:field='revisionList'/></ui:msg>
+ </div>
+ <g:Button ui:field='download' styleName=''>
+ <div><ui:msg>Download</ui:msg></div>
+ </g:Button>
</div>
- </div>
+ </g:HTMLPanel>
<table class='{style.headerTable}'>
<tr>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadAction.java
new file mode 100644
index 0000000..91ff99c
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadAction.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2013 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.client.change;
+
+import com.google.gerrit.client.changes.ChangeInfo.FetchInfo;
+import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwt.user.client.ui.Widget;
+
+class DownloadAction extends RightSidePopdownAction {
+ private final DownloadBox downloadBox;
+
+ DownloadAction(
+ Change.Id changeId,
+ String project,
+ RevisionInfo revision,
+ ChangeScreen2.Style style,
+ UIObject relativeTo,
+ Widget downloadButton) {
+ super(style, relativeTo, downloadButton);
+ this.downloadBox = new DownloadBox(
+ revision.has_fetch()
+ ? revision.fetch()
+ : NativeMap.<FetchInfo> create(),
+ revision.name(),
+ project,
+ new PatchSet.Id(changeId, revision._number()));
+ }
+
+ Widget getWidget() {
+ return downloadBox;
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java
new file mode 100644
index 0000000..9ee6e3e
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java
@@ -0,0 +1,241 @@
+// Copyright (C) 2013 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.client.change;
+
+import static com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme.REPO_DOWNLOAD;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.account.AccountApi;
+import com.google.gerrit.client.changes.ChangeInfo.FetchInfo;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.RestApi;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.AnchorElement;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwtexpui.clippy.client.CopyableLabel;
+
+class DownloadBox extends Composite {
+ interface Binder extends UiBinder<HTMLPanel, DownloadBox> {}
+ private static final Binder uiBinder = GWT.create(Binder.class);
+
+ private final NativeMap<FetchInfo> fetch;
+ private final String revision;
+ private final String project;
+ private final PatchSet.Id psId;
+
+ @UiField ListBox scheme;
+ @UiField CopyableLabel checkout;
+ @UiField CopyableLabel cherryPick;
+ @UiField CopyableLabel pull;
+ @UiField AnchorElement patchBase64;
+ @UiField AnchorElement patchZip;
+ @UiField Element repoSection;
+ @UiField CopyableLabel repoDownload;
+
+ DownloadBox(NativeMap<FetchInfo> fetch, String revision,
+ String project, PatchSet.Id psId) {
+ this.fetch = fetch;
+ this.revision = revision;
+ this.project = project;
+ this.psId = psId;
+ initWidget(uiBinder.createAndBindUi(this));
+ }
+
+ @Override
+ protected void onLoad() {
+ if (scheme.getItemCount() == 0) {
+ renderScheme(fetch);
+ }
+ }
+
+ @UiHandler("scheme")
+ void onScheme(ChangeEvent event) {
+ renderCommands();
+
+ if (Gerrit.isSignedIn()) {
+ saveScheme();
+ }
+ }
+
+ private void renderCommands() {
+ FetchInfo info = fetch.get(scheme.getValue(scheme.getSelectedIndex()));
+ checkout(info);
+ cherryPick(info);
+ pull(info);
+ patch(info);
+ repo(info);
+ }
+
+ private void checkout(FetchInfo info) {
+ checkout.setText(
+ "git fetch " + info.url() + " " + info.ref()
+ + " && git checkout FETCH_HEAD");
+ }
+
+ private void cherryPick(FetchInfo info) {
+ cherryPick.setText(
+ "git fetch " + info.url() + " " + info.ref()
+ + " && git cherry-pick FETCH_HEAD");
+ }
+
+ private void pull(FetchInfo info) {
+ pull.setText("git pull " + info.url() + " " + info.ref());
+ }
+
+ private void patch(FetchInfo info) {
+ String id = revision.substring(0, 7);
+ patchBase64.setInnerText(id + ".diff.base64");
+ patchBase64.setHref(new RestApi("/changes/")
+ .id(psId.getParentKey().get())
+ .view("revisions")
+ .id(revision)
+ .view("patch")
+ .addParameterTrue("download")
+ .url());
+
+ patchZip.setInnerText(id + ".diff.zip");
+ patchZip.setHref(new RestApi("/changes/")
+ .id(psId.getParentKey().get())
+ .view("revisions")
+ .id(revision)
+ .view("patch")
+ .addParameterTrue("zip")
+ .url());
+ }
+
+ private void repo(FetchInfo info) {
+ if (Gerrit.getConfig().getDownloadSchemes().contains(REPO_DOWNLOAD)) {
+ UIObject.setVisible(repoSection, true);
+ repoDownload.setText("repo download "
+ + project
+ + " " + psId.getParentKey().get() + "/" + psId.get());
+ }
+ }
+
+ private void renderScheme(NativeMap<FetchInfo> fetch) {
+ for (String id : fetch.keySet()) {
+ FetchInfo info = fetch.get(id);
+ String u = info.url();
+ int css = u.indexOf("://");
+ if (css > 0) {
+ int s = u.indexOf('/', css + 3);
+ if (s > 0) {
+ u = u.substring(0, s + 1);
+ }
+ }
+ scheme.addItem(u, id);
+ }
+
+ int select = 0;
+ String find = getUserPreference();
+ if (find != null) {
+ for (int i = 0; i < scheme.getItemCount(); i++) {
+ if (find.equals(scheme.getValue(i))) {
+ select = i;
+ break;
+ }
+ }
+ }
+
+ scheme.setSelectedIndex(select);
+ renderCommands();
+ }
+
+ private static String getUserPreference() {
+ if (Gerrit.isSignedIn()) {
+ DownloadScheme pref =
+ Gerrit.getUserAccount().getGeneralPreferences().getDownloadUrl();
+ if (pref != null) {
+ switch (pref) {
+ case ANON_GIT:
+ return "git";
+ case HTTP:
+ case ANON_HTTP:
+ return "http";
+ case SSH:
+ case ANON_SSH:
+ return "ssh";
+ default:
+ return null;
+ }
+ }
+ }
+ return null;
+ }
+
+ private void saveScheme() {
+ DownloadScheme scheme = getSelectedScheme();
+ AccountGeneralPreferences pref =
+ Gerrit.getUserAccount().getGeneralPreferences();
+
+ if (scheme != null && scheme != pref.getDownloadUrl()) {
+ pref.setDownloadUrl(scheme);
+ PreferenceInput in = PreferenceInput.create();
+ in.download_scheme(scheme);
+ AccountApi.self().view("preferences")
+ .post(in, new AsyncCallback<JavaScriptObject>() {
+ @Override
+ public void onSuccess(JavaScriptObject result) {
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ }
+ });
+ }
+ }
+
+ private DownloadScheme getSelectedScheme() {
+ String id = scheme.getValue(scheme.getSelectedIndex());
+ if ("git".equals(id)) {
+ return DownloadScheme.ANON_GIT;
+ } else if ("http".equals(id)) {
+ return DownloadScheme.HTTP;
+ } else if ("ssh".equals(id)) {
+ return DownloadScheme.SSH;
+ }
+ return null;
+ }
+
+ private static class PreferenceInput extends JavaScriptObject {
+ static PreferenceInput create() {
+ return createObject().cast();
+ }
+
+ final void download_scheme(DownloadScheme s) {
+ download_scheme0(s.name());
+ }
+
+ private final native void download_scheme0(String n) /*-{
+ this.download_scheme = n;
+ }-*/;
+
+ protected PreferenceInput() {
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.ui.xml
new file mode 100644
index 0000000..4490982
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.ui.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2013 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.
+-->
+<ui:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:c='urn:import:com.google.gwtexpui.clippy.client'>
+ <ui:with field='res' type='com.google.gerrit.client.change.Resources'/>
+ <ui:style>
+ @external .gwt-TextBox;
+
+ .downloadBox {
+ min-width: 580px;
+ margin: 5px;
+ }
+
+ .table {
+ border-spacing: 0;
+ width: 100%;
+ }
+ .table th {
+ text-align: left;
+ font-weight: normal;
+ white-space: nowrap;
+ max-height: 18px;
+ width: 80px;
+ padding-right: 5px;
+ }
+
+ .scheme {
+ float: right;
+ }
+
+ .clippy {
+ font-size: smaller;
+ font-family: monospace;
+ }
+ .clippy span {
+ width: 500px;
+ white-space: nowrap;
+ display: inline-block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ .clippy .gwt-TextBox {
+ padding: 0;
+ margin: 0;
+ border: 0;
+ max-height: 18px;
+ width: 500px;
+ }
+ .clippy div {
+ float: right;
+ }
+ </ui:style>
+ <g:HTMLPanel styleName='{style.downloadBox}'>
+ <table class='{style.table}'>
+ <tr>
+ <th><ui:msg>Checkout</ui:msg></th>
+ <td><c:CopyableLabel ui:field='checkout' styleName='{style.clippy}'/></td>
+ </tr>
+ <tr>
+ <th><ui:msg>Cherry Pick</ui:msg></th>
+ <td><c:CopyableLabel ui:field='cherryPick' styleName='{style.clippy}'/></td>
+ </tr>
+ <tr>
+ <th><ui:msg>Pull</ui:msg></th>
+ <td><c:CopyableLabel ui:field='pull' styleName='{style.clippy}'/></td>
+ </tr>
+ <tr>
+ <th><ui:msg>Patch File</ui:msg></th>
+ <td><a ui:field='patchZip'/> | <a ui:field='patchBase64'/></td>
+ </tr>
+ <tr ui:field='repoSection' style='display: NONE' aria-hidden='true'>
+ <th><ui:msg>repo</ui:msg></th>
+ <td><c:CopyableLabel ui:field='repoDownload' styleName='{style.clippy}'/></td>
+ </tr>
+ <tr>
+ <td colspan='2'>
+ <g:ListBox ui:field='scheme' styleName='{style.scheme}'/>
+ </td>
+ </tr>
+ </table>
+ </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RightSidePopdownAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RightSidePopdownAction.java
new file mode 100644
index 0000000..f73f418
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RightSidePopdownAction.java
@@ -0,0 +1,80 @@
+// Copyright (C) 2013 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.client.change;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwtexpui.globalkey.client.GlobalKey;
+import com.google.gwtexpui.user.client.PluginSafePopupPanel;
+
+abstract class RightSidePopdownAction {
+ private final ChangeScreen2.Style style;
+ private final Widget button;
+ private final UIObject relativeTo;
+ private PopupPanel popup;
+
+ RightSidePopdownAction(
+ ChangeScreen2.Style style,
+ UIObject relativeTo,
+ Widget button) {
+ this.style = style;
+ this.relativeTo = relativeTo;
+ this.button = button;
+ }
+
+ abstract Widget getWidget();
+
+ void show() {
+ if (popup != null) {
+ popup.hide();
+ return;
+ }
+
+ final PluginSafePopupPanel p = new PluginSafePopupPanel(true) {
+ @Override
+ public void setPopupPosition(int left, int top) {
+ top -= Document.get().getBodyOffsetTop();
+
+ int w = Window.getScrollLeft() + Window.getClientWidth();
+ int r = relativeTo.getAbsoluteLeft() + relativeTo.getOffsetWidth();
+ int right = w - r;
+ Style style = getElement().getStyle();
+ style.clearProperty("left");
+ style.setPropertyPx("right", right);
+ style.setPropertyPx("top", top);
+ }
+ };
+ p.setStyleName(style.replyBox());
+ p.addAutoHidePartner(button.getElement());
+ p.addCloseHandler(new CloseHandler<PopupPanel>() {
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event) {
+ if (popup == p) {
+ popup = null;
+ }
+ }
+ });
+ p.add(getWidget());
+ p.showRelativeTo(relativeTo);
+ GlobalKey.dialog(p);
+ popup = p;
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
index c0c5312..5b57b2b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
@@ -211,6 +211,9 @@
public final native boolean has_actions() /*-{ return this.hasOwnProperty('actions') }-*/;
public final native NativeMap<ActionInfo> actions() /*-{ return this.actions; }-*/;
+ public final native boolean has_fetch() /*-{ return this.hasOwnProperty('fetch') }-*/;
+ public final native NativeMap<FetchInfo> fetch() /*-{ return this.fetch; }-*/;
+
public static void sortRevisionInfoByNumber(JsArray<RevisionInfo> list) {
Collections.sort(Natives.asList(list), new Comparator<RevisionInfo>() {
@Override
@@ -224,6 +227,14 @@
}
}
+ public static class FetchInfo extends JavaScriptObject {
+ public final native String url() /*-{ return this.url }-*/;
+ public final native String ref() /*-{ return this.ref }-*/;
+
+ protected FetchInfo () {
+ }
+ }
+
public static class CommitInfo extends JavaScriptObject {
public final native String commit() /*-{ return this.commit; }-*/;
public final native JsArray<CommitInfo> parents() /*-{ return this.parents; }-*/;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
new file mode 100644
index 0000000..733c49d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
@@ -0,0 +1,81 @@
+// Copyright (C) 2013 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.server.account;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.CommentVisibilityStrategy;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DateFormat;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.TimeFormat;
+import com.google.gerrit.server.CurrentUser;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class GetPreferences implements RestReadView<AccountResource> {
+ private final Provider<CurrentUser> self;
+
+ @Inject
+ GetPreferences(Provider<CurrentUser> self) {
+ this.self = self;
+ }
+
+ @Override
+ public PreferenceInfo apply(AccountResource rsrc) throws AuthException {
+ if (self.get() != rsrc.getUser()
+ && !self.get().getCapabilities().canAdministrateServer()) {
+ throw new AuthException("restricted to administrator");
+ }
+ return new PreferenceInfo(rsrc.getUser().getAccount()
+ .getGeneralPreferences());
+ }
+
+ static class PreferenceInfo {
+ final String kind = "gerritcodereview#preferences";
+
+ short changesPerPage;
+ Boolean showSiteHeader;
+ Boolean useFlashClipboard;
+ DownloadScheme downloadScheme;
+ DownloadCommand downloadCommand;
+ Boolean copySelfOnEmail;
+ DateFormat dateFormat;
+ TimeFormat timeFormat;
+ Boolean reversePatchSetOrder;
+ Boolean showUsernameInReviewCategory;
+ Boolean relativeDateInChangeTable;
+ CommentVisibilityStrategy commentVisibilityStrategy;
+ DiffView diffView;
+
+ PreferenceInfo(AccountGeneralPreferences p) {
+ changesPerPage = p.getMaximumPageSize();
+ showSiteHeader = p.isShowSiteHeader() ? true : null;
+ useFlashClipboard = p.isUseFlashClipboard() ? true : null;
+ downloadScheme = p.getDownloadUrl();
+ downloadCommand = p.getDownloadCommand();
+ copySelfOnEmail = p.isCopySelfOnEmails() ? true : null;
+ dateFormat = p.getDateFormat();
+ timeFormat = p.getTimeFormat();
+ reversePatchSetOrder = p.isReversePatchSetOrder() ? true : null;
+ showUsernameInReviewCategory = p.isShowUsernameInReviewCategory() ? true : null;
+ relativeDateInChangeTable = p.isRelativeDateInChangeTable() ? true : null;
+ commentVisibilityStrategy = p.getCommentVisibilityStrategy();
+ diffView = p.getDiffView();
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
index fe3086a..79a0089 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
@@ -59,6 +59,8 @@
get(ACCOUNT_KIND, "avatar.change.url").to(GetAvatarChangeUrl.class);
child(ACCOUNT_KIND, "capabilities").to(Capabilities.class);
get(ACCOUNT_KIND, "groups").to(GetGroups.class);
+ get(ACCOUNT_KIND, "preferences").to(GetPreferences.class);
+ post(ACCOUNT_KIND, "preferences").to(SetPreferences.class);
get(ACCOUNT_KIND, "preferences.diff").to(GetDiffPreferences.class);
put(ACCOUNT_KIND, "preferences.diff").to(SetDiffPreferences.class);
get(CAPABILITY_KIND).to(GetCapabilities.CheckOne.class);
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
new file mode 100644
index 0000000..8e40b2e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
@@ -0,0 +1,139 @@
+// Copyright (C) 2013 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.server.account;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.CommentVisibilityStrategy;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DateFormat;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.TimeFormat;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.SetPreferences.Input;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.util.Collections;
+
+public class SetPreferences implements RestModifyView<AccountResource, Input> {
+ static class Input {
+ Short changesPerPage;
+ Boolean showSiteHeader;
+ Boolean useFlashClipboard;
+ DownloadScheme downloadScheme;
+ DownloadCommand downloadCommand;
+ Boolean copySelfOnEmail;
+ DateFormat dateFormat;
+ TimeFormat timeFormat;
+ Boolean reversePatchSetOrder;
+ Boolean showUsernameInReviewCategory;
+ Boolean relativeDateInChangeTable;
+ CommentVisibilityStrategy commentVisibilityStrategy;
+ DiffView diffView;
+ }
+
+ private final Provider<CurrentUser> self;
+ private final AccountCache cache;
+ private final ReviewDb db;
+
+ @Inject
+ SetPreferences(Provider<CurrentUser> self, AccountCache cache, ReviewDb db) {
+ this.self = self;
+ this.cache = cache;
+ this.db = db;
+ }
+
+ @Override
+ public GetPreferences.PreferenceInfo apply(AccountResource rsrc, Input i)
+ throws AuthException, ResourceNotFoundException, OrmException {
+ if (self.get() != rsrc.getUser()
+ && !self.get().getCapabilities().canAdministrateServer()) {
+ throw new AuthException("restricted to administrator");
+ }
+ if (i == null) {
+ i = new Input();
+ }
+
+ Account.Id accountId = rsrc.getUser().getAccountId();
+ AccountGeneralPreferences p;
+ db.accounts().beginTransaction(accountId);
+ try {
+ Account a = db.accounts().get(accountId);
+ if (a == null) {
+ throw new ResourceNotFoundException();
+ }
+
+ p = a.getGeneralPreferences();
+ if (p == null) {
+ p = new AccountGeneralPreferences();
+ a.setGeneralPreferences(p);
+ }
+
+ if (i.changesPerPage != null) {
+ p.setMaximumPageSize(i.changesPerPage);
+ }
+ if (i.showSiteHeader != null) {
+ p.setShowSiteHeader(i.showSiteHeader);
+ }
+ if (i.useFlashClipboard != null) {
+ p.setUseFlashClipboard(i.useFlashClipboard);
+ }
+ if (i.downloadScheme != null) {
+ p.setDownloadUrl(i.downloadScheme);
+ }
+ if (i.downloadCommand != null) {
+ p.setDownloadCommand(i.downloadCommand);
+ }
+ if (i.copySelfOnEmail != null) {
+ p.setCopySelfOnEmails(i.copySelfOnEmail);
+ }
+ if (i.dateFormat != null) {
+ p.setDateFormat(i.dateFormat);
+ }
+ if (i.timeFormat != null) {
+ p.setTimeFormat(i.timeFormat);
+ }
+ if (i.reversePatchSetOrder != null) {
+ p.setReversePatchSetOrder(i.reversePatchSetOrder);
+ }
+ if (i.showUsernameInReviewCategory != null) {
+ p.setShowUsernameInReviewCategory(i.showUsernameInReviewCategory);
+ }
+ if (i.relativeDateInChangeTable != null) {
+ p.setRelativeDateInChangeTable(i.relativeDateInChangeTable);
+ }
+ if (i.commentVisibilityStrategy != null) {
+ p.setCommentVisibilityStrategy(i.commentVisibilityStrategy);
+ }
+ if (i.diffView != null) {
+ p.setDiffView(i.diffView);
+ }
+
+ db.accounts().update(Collections.singleton(a));
+ db.commit();
+ cache.evict(accountId);
+ } finally {
+ db.rollback();
+ }
+ return new GetPreferences.PreferenceInfo(p);
+ }
+}