ChangeScreen2: Show download commands defined by plugins

The download commands are returned as part of the FetchInfo of a
revision, but only if the download commands are requested by setting
the option DOWNLOAD_COMMANDS.

This allows to completely customize the download commands on a Gerrit
installation.

This change only effects the download commands that are shown on
ChangeScreen2.

For non-anonymous download commands the username is now again included
into the displayed download commands. Also the HTTP scheme and the
Anonymous HTTP scheme can be used at the same time.

With this change the patch download (base64 & zipped) is still
hard-coded in Gerrit core and does not come from a plugin.

Bug: issue 2116
Change-Id: I8fb21fdeb1a98548ce9027655e1b5e467ee2d27e
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
Edwin Kempin
2013-10-16 12:58:24 +02:00
committed by David Pursehouse
parent 78e868ef89
commit ea62148983
11 changed files with 278 additions and 275 deletions

View File

@@ -982,6 +982,20 @@ MyType(@PluginData java.io.File myDir) {
} }
---- ----
[[download-commands]]
Download Commands
-----------------
Gerrit offers commands for downloading changes using different
download schemes (e.g. for downloading via different network
protocols). Plugins can contribute download schemes and download
commands by implementing
`com.google.gerrit.extensions.config.DownloadScheme` and
`com.google.gerrit.extensions.config.DownloadCommand`.
The download schemes and download commands which are used most often
are provided by the Gerrit core plugin `download-commands`.
[[documentation]] [[documentation]]
Documentation Documentation
------------- -------------

View File

@@ -171,6 +171,13 @@ default. Optional fields are:
* `ALL_REVISIONS`: describe all revisions, not just current. * `ALL_REVISIONS`: describe all revisions, not just current.
-- --
[[download_commands]]
--
* `DOWNLOAD_COMMANDS`: include the `commands` field in the
link:#fetch-info[FetchInfo] for revisions. Only valid when the
`CURRENT_REVISION` or `ALL_REVISIONS` option is selected.
--
[[draft_comments]] [[draft_comments]]
-- --
* `DRAFT_COMMENTS`: include the `has_draft_comments` field for * `DRAFT_COMMENTS`: include the `has_draft_comments` field for
@@ -233,7 +240,7 @@ default. Optional fields are:
.Request .Request
---- ----
GET /changes/?q=97&o=CURRENT_REVISION&o=CURRENT_COMMIT&o=CURRENT_FILES HTTP/1.0 GET /changes/?q=97&o=CURRENT_REVISION&o=CURRENT_COMMIT&o=CURRENT_FILES&o=DOWNLOAD_COMMANDS HTTP/1.0
---- ----
.Response .Response
@@ -267,11 +274,33 @@ default. Optional fields are:
"fetch": { "fetch": {
"git": { "git": {
"url": "git://localhost/gerrit", "url": "git://localhost/gerrit",
"ref": "refs/changes/97/97/1" "ref": "refs/changes/97/97/1",
"commands": {
"Checkout": "git fetch git://localhost/gerrit refs/changes/97/97/1 \u0026\u0026 git checkout FETCH_HEAD",
"Cherry-Pick": "git fetch git://localhost/gerrit refs/changes/97/97/1 \u0026\u0026 git cherry-pick FETCH_HEAD",
"Format-Patch": "git fetch git://localhost/gerrit refs/changes/97/97/1 \u0026\u0026 git format-patch -1 --stdout FETCH_HEAD",
"Pull": "git pull git://localhost/gerrit refs/changes/97/97/1"
}
}, },
"http": { "http": {
"url": "http://127.0.0.1:8080/gerrit", "url": "http://myuser@127.0.0.1:8080/gerrit",
"ref": "refs/changes/97/97/1" "ref": "refs/changes/97/97/1",
"commands": {
"Checkout": "git fetch http://myuser@127.0.0.1:8080/gerrit refs/changes/97/97/1 \u0026\u0026 git checkout FETCH_HEAD",
"Cherry-Pick": "git fetch http://myuser@127.0.0.1:8080/gerrit refs/changes/97/97/1 \u0026\u0026 git cherry-pick FETCH_HEAD",
"Format-Patch": "git fetch http://myuser@127.0.0.1:8080/gerrit refs/changes/97/97/1 \u0026\u0026 git format-patch -1 --stdout FETCH_HEAD",
"Pull": "git pull http://myuser@127.0.0.1:8080/gerrit refs/changes/97/97/1"
}
},
"ssh": {
"url": "ssh://myuser@*:29418/gerrit",
"ref": "refs/changes/97/97/1",
"commands": {
"Checkout": "git fetch ssh://myuser@*:29418/gerrit refs/changes/97/97/1 \u0026\u0026 git checkout FETCH_HEAD",
"Cherry-Pick": "git fetch ssh://myuser@*:29418/gerrit refs/changes/97/97/1 \u0026\u0026 git cherry-pick FETCH_HEAD",
"Format-Patch": "git fetch ssh://myuser@*:29418/gerrit refs/changes/97/97/1 \u0026\u0026 git format-patch -1 --stdout FETCH_HEAD",
"Pull": "git pull ssh://myuser@*:29418/gerrit refs/changes/97/97/1"
}
} }
}, },
"commit": { "commit": {
@@ -2949,11 +2978,15 @@ FetchInfo
The `FetchInfo` entity contains information about how to fetch a patch The `FetchInfo` entity contains information about how to fetch a patch
set via a certain protocol. set via a certain protocol.
[options="header",width="50%",cols="1,6"] [options="header",width="50%",cols="1,^1,5"]
|========================== |==========================
|Field Name |Description |Field Name ||Description
|`url` |The URL of the project. |`url` ||The URL of the project.
|`ref` |The ref of the patch set. |`ref` ||The ref of the patch set.
|`commands` |optional|
The download commands for this patch set as a map that maps the command
names to the commands. +
Only set if link:#download_commands[download commands] are requested.
|========================== |==========================
[[file-info]] [[file-info]]

View File

@@ -46,7 +46,10 @@ public enum ListChangesOption {
REVIEWED(11), REVIEWED(11),
/** Include draft comments for the caller. */ /** Include draft comments for the caller. */
DRAFT_COMMENTS(12); DRAFT_COMMENTS(12),
/** Include download commands for the caller. */
DOWNLOAD_COMMANDS(13);
private final int value; private final int value;

View File

@@ -92,6 +92,12 @@ public interface GerritCss extends CssResource {
String diffTextHunkHeader(); String diffTextHunkHeader();
String diffTextINSERT(); String diffTextINSERT();
String diffTextNoLF(); String diffTextNoLF();
String downloadBox();
String downloadBoxTable();
String downloadBoxTableCommandColumn();
String downloadBoxSpacer();
String downloadBoxScheme();
String downloadBoxCopyLabel();
String downloadLink(); String downloadLink();
String downloadLinkCopyLabel(); String downloadLinkCopyLabel();
String downloadLinkHeader(); String downloadLinkHeader();

View File

@@ -280,11 +280,8 @@ public class ChangeScreen2 extends Screen {
} }
private void initDownloadAction(ChangeInfo info, String revision) { private void initDownloadAction(ChangeInfo info, String revision) {
downloadAction = new DownloadAction( downloadAction =
info.legacy_id(), new DownloadAction(info, revision, style, headerLine, download);
info.project(),
info.revision(revision),
style, headerLine, download);
} }
private void initProjectLink(ChangeInfo info) { private void initProjectLink(ChangeInfo info) {

View File

@@ -14,10 +14,7 @@
package com.google.gerrit.client.change; package com.google.gerrit.client.change;
import com.google.gerrit.client.changes.ChangeInfo.FetchInfo; import com.google.gerrit.client.changes.ChangeInfo;
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.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.user.client.ui.UIObject; import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.Widget;
@@ -26,20 +23,15 @@ class DownloadAction extends RightSidePopdownAction {
private final DownloadBox downloadBox; private final DownloadBox downloadBox;
DownloadAction( DownloadAction(
Change.Id changeId, ChangeInfo info,
String project, String revision,
RevisionInfo revision,
ChangeScreen2.Style style, ChangeScreen2.Style style,
UIObject relativeTo, UIObject relativeTo,
Widget downloadButton) { Widget downloadButton) {
super(style, relativeTo, downloadButton); super(style, relativeTo, downloadButton);
this.downloadBox = new DownloadBox( this.downloadBox = new DownloadBox(info, revision,
revision.has_fetch() new PatchSet.Id(info.legacy_id(),
? revision.fetch() info.revision(revision)._number()));
: NativeMap.<FetchInfo> create(),
revision.name(),
project,
new PatchSet.Id(changeId, revision._number()));
} }
Widget getWidget() { Widget getWidget() {

View File

@@ -14,102 +14,107 @@
package com.google.gerrit.client.change; 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.Gerrit;
import com.google.gerrit.client.account.AccountApi; import com.google.gerrit.client.account.AccountApi;
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.ChangeInfo;
import com.google.gerrit.client.changes.ChangeInfo.FetchInfo; import com.google.gerrit.client.changes.ChangeInfo.FetchInfo;
import com.google.gerrit.client.changes.ChangeList;
import com.google.gerrit.client.rpc.NativeMap; import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.rpc.RestApi; import com.google.gerrit.client.rpc.RestApi;
import com.google.gerrit.common.changes.ListChangesOption;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences; import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme; import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
import com.google.gerrit.reviewdb.client.PatchSet; 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.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.event.dom.client.ChangeEvent;
import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.event.dom.client.ChangeHandler;
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.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.UIObject; import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwtexpui.clippy.client.CopyableLabel; import com.google.gwtexpui.clippy.client.CopyableLabel;
class DownloadBox extends Composite { import java.util.EnumSet;
interface Binder extends UiBinder<HTMLPanel, DownloadBox> {}
private static final Binder uiBinder = GWT.create(Binder.class);
private final NativeMap<FetchInfo> fetch; class DownloadBox extends VerticalPanel {
private final ChangeInfo change;
private final String revision; private final String revision;
private final String project;
private final PatchSet.Id psId; private final PatchSet.Id psId;
private final FlexTable commandTable;
private final ListBox scheme;
private NativeMap<FetchInfo> fetch;
@UiField ListBox scheme; DownloadBox(ChangeInfo change, String revision, PatchSet.Id psId) {
@UiField CopyableLabel checkout; this.change = change;
@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.revision = revision;
this.project = project;
this.psId = psId; this.psId = psId;
initWidget(uiBinder.createAndBindUi(this)); this.commandTable = new FlexTable();
this.scheme = new ListBox();
this.scheme.addChangeHandler(new ChangeHandler() {
@Override
public void onChange(ChangeEvent event) {
renderCommands();
if (Gerrit.isSignedIn()) {
saveScheme();
}
}
});
setStyleName(Gerrit.RESOURCES.css().downloadBox());
commandTable.setStyleName(Gerrit.RESOURCES.css().downloadBoxTable());
scheme.setStyleName(Gerrit.RESOURCES.css().downloadBoxScheme());
add(commandTable);
} }
@Override @Override
protected void onLoad() { protected void onLoad() {
if (scheme.getItemCount() == 0) { if (fetch == null) {
renderScheme(fetch); RestApi call = ChangeApi.detail(change.legacy_id().get());
} ChangeList.addOptions(call, EnumSet.of(
} revision.equals(change.current_revision())
? ListChangesOption.CURRENT_REVISION
: ListChangesOption.ALL_REVISIONS,
ListChangesOption.DOWNLOAD_COMMANDS));
call.get(new AsyncCallback<ChangeInfo>() {
@Override
public void onSuccess(ChangeInfo result) {
fetch = result.revision(revision).fetch();
renderScheme();
}
@UiHandler("scheme") @Override
void onScheme(ChangeEvent event) { public void onFailure(Throwable caught) {
renderCommands(); }
});
if (Gerrit.isSignedIn()) {
saveScheme();
} }
} }
private void renderCommands() { private void renderCommands() {
FetchInfo info = fetch.get(scheme.getValue(scheme.getSelectedIndex())); commandTable.removeAllRows();
checkout(info);
cherryPick(info); if (scheme.getItemCount() > 0) {
pull(info); FetchInfo fetchInfo =
patch(info); fetch.get(scheme.getValue(scheme.getSelectedIndex()));
repo(info); for (String commandName : Natives.keys(fetchInfo.commands())) {
CopyableLabel copyLabel =
new CopyableLabel(fetchInfo.command(commandName));
copyLabel.setStyleName(Gerrit.RESOURCES.css().downloadBoxCopyLabel());
insertCommand(commandName, copyLabel);
}
}
insertPatch();
insertCommand(null, scheme);
} }
private void checkout(FetchInfo info) { private void insertPatch() {
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); String id = revision.substring(0, 7);
patchBase64.setInnerText(id + ".diff.base64"); Anchor patchBase64 = new Anchor(id + ".diff.base64");
patchBase64.setHref(new RestApi("/changes/") patchBase64.setHref(new RestApi("/changes/")
.id(psId.getParentKey().get()) .id(psId.getParentKey().get())
.view("revisions") .view("revisions")
@@ -118,7 +123,7 @@ class DownloadBox extends Composite {
.addParameterTrue("download") .addParameterTrue("download")
.url()); .url());
patchZip.setInnerText(id + ".diff.zip"); Anchor patchZip = new Anchor(id + ".diff.zip");
patchZip.setHref(new RestApi("/changes/") patchZip.setHref(new RestApi("/changes/")
.id(psId.getParentKey().get()) .id(psId.getParentKey().get())
.view("revisions") .view("revisions")
@@ -126,45 +131,52 @@ class DownloadBox extends Composite {
.view("patch") .view("patch")
.addParameterTrue("zip") .addParameterTrue("zip")
.url()); .url());
HorizontalPanel p = new HorizontalPanel();
p.add(patchBase64);
InlineLabel spacer = new InlineLabel("|");
spacer.setStyleName(Gerrit.RESOURCES.css().downloadBoxSpacer());
p.add(spacer);
p.add(patchZip);
insertCommand("Patch-File", p);
} }
private void repo(FetchInfo info) { private void insertCommand(String commandName, Widget w) {
if (Gerrit.getConfig().getDownloadSchemes().contains(REPO_DOWNLOAD)) { int row = commandTable.getRowCount();
UIObject.setVisible(repoSection, true); commandTable.insertRow(row);
repoDownload.setText("repo download " commandTable.getCellFormatter().addStyleName(row, 0,
+ project Gerrit.RESOURCES.css().downloadBoxTableCommandColumn());
+ " " + psId.getParentKey().get() + "/" + psId.get()); if (commandName != null) {
commandTable.setText(row, 0, commandName);
}
if (w != null) {
commandTable.setWidget(row, 1, w);
} }
} }
private void renderScheme(NativeMap<FetchInfo> fetch) { private void renderScheme() {
for (String id : fetch.keySet()) { for (String id : fetch.keySet()) {
FetchInfo info = fetch.get(id); scheme.addItem(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);
} }
if (scheme.getItemCount() == 1) { if (scheme.getItemCount() == 0) {
scheme.setSelectedIndex(0);
scheme.setVisible(false); scheme.setVisible(false);
} else { } else {
int select = 0; if (scheme.getItemCount() == 1) {
String find = getUserPreference(); scheme.setSelectedIndex(0);
if (find != null) { scheme.setVisible(false);
for (int i = 0; i < scheme.getItemCount(); i++) { } else {
if (find.equals(scheme.getValue(i))) { int select = 0;
select = i; String find = getUserPreference();
break; if (find != null) {
for (int i = 0; i < scheme.getItemCount(); i++) {
if (find.equals(scheme.getValue(i))) {
select = i;
break;
}
} }
} }
scheme.setSelectedIndex(select);
} }
scheme.setSelectedIndex(select);
} }
renderCommands(); renderCommands();
} }
@@ -177,11 +189,14 @@ class DownloadBox extends Composite {
switch (pref) { switch (pref) {
case ANON_GIT: case ANON_GIT:
return "git"; return "git";
case HTTP:
case ANON_HTTP: case ANON_HTTP:
return "anonymous http";
case HTTP:
return "http"; return "http";
case SSH: case SSH:
return "ssh"; return "ssh";
case REPO_DOWNLOAD:
return "repo";
default: default:
return null; return null;
} }
@@ -216,10 +231,14 @@ class DownloadBox extends Composite {
String id = scheme.getValue(scheme.getSelectedIndex()); String id = scheme.getValue(scheme.getSelectedIndex());
if ("git".equals(id)) { if ("git".equals(id)) {
return DownloadScheme.ANON_GIT; return DownloadScheme.ANON_GIT;
} else if ("anonymous http".equals(id)) {
return DownloadScheme.ANON_HTTP;
} else if ("http".equals(id)) { } else if ("http".equals(id)) {
return DownloadScheme.HTTP; return DownloadScheme.HTTP;
} else if ("ssh".equals(id)) { } else if ("ssh".equals(id)) {
return DownloadScheme.SSH; return DownloadScheme.SSH;
} else if ("repo".equals(id)) {
return DownloadScheme.REPO_DOWNLOAD;
} }
return null; return null;
} }

View File

@@ -1,98 +0,0 @@
<?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>

View File

@@ -232,6 +232,8 @@ public class ChangeInfo extends JavaScriptObject {
public static class FetchInfo extends JavaScriptObject { public static class FetchInfo extends JavaScriptObject {
public final native String url() /*-{ return this.url }-*/; public final native String url() /*-{ return this.url }-*/;
public final native String ref() /*-{ return this.ref }-*/; public final native String ref() /*-{ return this.ref }-*/;
public final native NativeMap<NativeString> commands() /*-{ return this.commands }-*/;
public final native String command(String n) /*-{ return this.commands[n]; }-*/;
protected FetchInfo () { protected FetchInfo () {
} }

View File

@@ -1090,6 +1090,51 @@ td.removeReviewerCell {
padding-left: 4em; padding-left: 4em;
border-left: none; border-left: none;
} }
.downloadBox {
min-width: 580px;
margin: 5px;
}
.downloadBoxTable {
border-spacing: 0;
width: 100%;
}
.downloadBoxTableCommandColumn {
text-align: left;
font-weight: normal;
white-space: nowrap;
max-height: 18px;
width: 80px;
padding-right: 5px;
}
.downloadBoxSpacer {
margin-left: 5px;
margin-right: 5px;
}
.downloadBoxScheme {
float: right;
}
.downloadBoxCopyLabel {
font-size: smaller;
font-family: monospace;
}
.downloadBoxCopyLabel span {
width: 500px;
white-space: nowrap;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
}
.downloadBoxCopyLabel .gwt-TextBox {
padding: 0;
margin: 0;
border: 0;
max-height: 18px;
width: 500px;
}
.downloadBoxCopyLabel div {
float: right;
}
td.downloadLinkListCell { td.downloadLinkListCell {
padding: 0px; padding: 0px;
} }

View File

@@ -23,6 +23,7 @@ import static com.google.gerrit.common.changes.ListChangesOption.CURRENT_FILES;
import static com.google.gerrit.common.changes.ListChangesOption.CURRENT_REVISION; import static com.google.gerrit.common.changes.ListChangesOption.CURRENT_REVISION;
import static com.google.gerrit.common.changes.ListChangesOption.DETAILED_ACCOUNTS; import static com.google.gerrit.common.changes.ListChangesOption.DETAILED_ACCOUNTS;
import static com.google.gerrit.common.changes.ListChangesOption.DETAILED_LABELS; import static com.google.gerrit.common.changes.ListChangesOption.DETAILED_LABELS;
import static com.google.gerrit.common.changes.ListChangesOption.DOWNLOAD_COMMANDS;
import static com.google.gerrit.common.changes.ListChangesOption.DRAFT_COMMENTS; import static com.google.gerrit.common.changes.ListChangesOption.DRAFT_COMMENTS;
import static com.google.gerrit.common.changes.ListChangesOption.LABELS; import static com.google.gerrit.common.changes.ListChangesOption.LABELS;
import static com.google.gerrit.common.changes.ListChangesOption.MESSAGES; import static com.google.gerrit.common.changes.ListChangesOption.MESSAGES;
@@ -30,7 +31,6 @@ import static com.google.gerrit.common.changes.ListChangesOption.REVIEWED;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.common.collect.HashBasedTable; import com.google.common.collect.HashBasedTable;
import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@@ -50,6 +50,8 @@ import com.google.gerrit.common.data.LabelValue;
import com.google.gerrit.common.data.Permission; import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRange; import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.common.data.SubmitRecord; import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.extensions.config.DownloadCommand;
import com.google.gerrit.extensions.config.DownloadScheme;
import com.google.gerrit.extensions.registration.DynamicMap; import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestView; import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.restapi.Url; import com.google.gerrit.extensions.restapi.Url;
@@ -69,8 +71,6 @@ import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountInfo; import com.google.gerrit.server.account.AccountInfo;
import com.google.gerrit.server.actions.ActionInfo; import com.google.gerrit.server.actions.ActionInfo;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.extensions.webui.UiActions; import com.google.gerrit.server.extensions.webui.UiActions;
import com.google.gerrit.server.git.LabelNormalizer; import com.google.gerrit.server.git.LabelNormalizer;
import com.google.gerrit.server.patch.PatchListNotAvailableException; import com.google.gerrit.server.patch.PatchListNotAvailableException;
@@ -79,14 +79,11 @@ import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.ssh.SshAdvertisedAddresses;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet; import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -120,30 +117,6 @@ public class ChangeJson {
} }
}; };
@Singleton
static class Urls {
final String git;
final String http;
final String ssh;
@Inject
Urls(@GerritServerConfig Config cfg,
@SshAdvertisedAddresses List<String> sshAddresses) {
this.git = ensureSlash(cfg.getString("gerrit", null, "canonicalGitUrl"));
this.http = ensureSlash(cfg.getString("gerrit", null, "gitHttpUrl"));
this.ssh = !sshAddresses.isEmpty()
? ensureSlash("ssh://" + sshAddresses.get(0))
: null;
}
private static String ensureSlash(String in) {
if (in != null && !in.endsWith("/")) {
return in + "/";
}
return in;
}
}
private final Provider<ReviewDb> db; private final Provider<ReviewDb> db;
private final LabelNormalizer labelNormalizer; private final LabelNormalizer labelNormalizer;
private final Provider<CurrentUser> userProvider; private final Provider<CurrentUser> userProvider;
@@ -153,8 +126,8 @@ public class ChangeJson {
private final PatchSetInfoFactory patchSetInfoFactory; private final PatchSetInfoFactory patchSetInfoFactory;
private final FileInfoJson fileInfoJson; private final FileInfoJson fileInfoJson;
private final AccountInfo.Loader.Factory accountLoaderFactory; private final AccountInfo.Loader.Factory accountLoaderFactory;
private final Provider<String> urlProvider; private final DynamicMap<DownloadScheme> downloadSchemes;
private final Urls urls; private final DynamicMap<DownloadCommand> downloadCommands;
private final DynamicMap<RestView<ChangeResource>> changes; private final DynamicMap<RestView<ChangeResource>> changes;
private final Revisions revisions; private final Revisions revisions;
@@ -175,8 +148,8 @@ public class ChangeJson {
PatchSetInfoFactory psi, PatchSetInfoFactory psi,
FileInfoJson fileInfoJson, FileInfoJson fileInfoJson,
AccountInfo.Loader.Factory ailf, AccountInfo.Loader.Factory ailf,
@CanonicalWebUrl Provider<String> curl, DynamicMap<DownloadScheme> downloadSchemes,
Urls urls, DynamicMap<DownloadCommand> downloadCommands,
DynamicMap<RestView<ChangeResource>> changes, DynamicMap<RestView<ChangeResource>> changes,
Revisions revisions) { Revisions revisions) {
this.db = db; this.db = db;
@@ -188,8 +161,8 @@ public class ChangeJson {
this.patchSetInfoFactory = psi; this.patchSetInfoFactory = psi;
this.fileInfoJson = fileInfoJson; this.fileInfoJson = fileInfoJson;
this.accountLoaderFactory = ailf; this.accountLoaderFactory = ailf;
this.urlProvider = curl; this.downloadSchemes = downloadSchemes;
this.urls = urls; this.downloadCommands = downloadCommands;
this.changes = changes; this.changes = changes;
this.revisions = revisions; this.revisions = revisions;
@@ -872,28 +845,37 @@ public class ChangeJson {
private Map<String, FetchInfo> makeFetchMap(ChangeData cd, PatchSet in) private Map<String, FetchInfo> makeFetchMap(ChangeData cd, PatchSet in)
throws OrmException { throws OrmException {
Map<String, FetchInfo> r = Maps.newLinkedHashMap(); Map<String, FetchInfo> r = Maps.newLinkedHashMap();
String refName = in.getRefName();
ChangeControl ctl = control(cd); for (DynamicMap.Entry<DownloadScheme> e : downloadSchemes) {
if (ctl != null && ctl.forUser(anonymous).isPatchVisible(in, db.get())) { String schemeName = e.getExportName();
if (urls.git != null) { DownloadScheme scheme = e.getProvider().get();
r.put("git", new FetchInfo(urls.git if (!scheme.isEnabled()
+ cd.change(db).getProject().get(), refName)); || (scheme.isAuthRequired() && !userProvider.get().isIdentifiedUser())) {
continue;
} }
}
if (urls.http != null) { ChangeControl ctl = control(cd);
r.put("http", new FetchInfo(urls.http if (!scheme.isAuthRequired()
+ cd.change(db).getProject().get(), refName)); && !ctl.forUser(anonymous).isPatchVisible(in, db.get())) {
} else { continue;
String http = urlProvider.get(); }
if (!Strings.isNullOrEmpty(http)) {
r.put("http", new FetchInfo(http String projectName = ctl.getProject().getNameKey().get();
+ cd.change(db).getProject().get(), refName)); String url = scheme.getUrl(projectName);
String refName = in.getRefName();
FetchInfo fetchInfo = new FetchInfo(url, refName);
r.put(schemeName, fetchInfo);
if (has(DOWNLOAD_COMMANDS)) {
for (DynamicMap.Entry<DownloadCommand> e2 : downloadCommands) {
String commandName = e2.getExportName();
DownloadCommand command = e2.getProvider().get();
String c = command.getCommand(scheme, projectName, refName);
if (c != null) {
fetchInfo.addCommand(commandName, c);
}
}
} }
}
if (urls.ssh != null) {
r.put("ssh", new FetchInfo(
urls.ssh + cd.change(db).getProject().get(),
refName));
} }
return r; return r;
@@ -960,11 +942,19 @@ public class ChangeJson {
static class FetchInfo { static class FetchInfo {
String url; String url;
String ref; String ref;
Map<String, String> commands;
FetchInfo(String url, String ref) { FetchInfo(String url, String ref) {
this.url = url; this.url = url;
this.ref = ref; this.ref = ref;
} }
void addCommand(String name, String command) {
if (commands == null) {
commands = Maps.newTreeMap();
}
commands.put(name, command);
}
} }
static class GitPerson { static class GitPerson {