Merge "Fix style sheets for expanded context lines"
diff --git a/.gitignore b/.gitignore
index f318b65..465893d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,5 @@
/.settings/org.eclipse.jdt.core.prefs
/.settings/org.maven.ide.eclipse.prefs
/test_site
+/.idea
+/gerrit-parent.iml
diff --git a/Documentation/cmd-ban-commit.txt b/Documentation/cmd-ban-commit.txt
new file mode 100644
index 0000000..fb4a2ac
--- /dev/null
+++ b/Documentation/cmd-ban-commit.txt
@@ -0,0 +1,60 @@
+gerrit ban-commit
+=================
+
+NAME
+----
+gerrit ban-commit - Bans a commit from a project's repository.
+
+SYNOPSIS
+--------
+[verse]
+'ssh' -p <port> <host> 'gerrit ban-commit'
+ [--reason <REASON>]
+ <PROJECT>
+ <COMMIT> ...
+
+DESCRIPTION
+-----------
+Marks a commit as banned for the specified repository. If a commit is
+banned Gerrit rejects every push that includes this commit with
+link:error-contains-banned-commit.html[contains banned commit ...].
+
+[NOTE]
+This command just marks the commit as banned, but it does not remove
+the commit from the history of any central branch. This needs to be
+done manually.
+
+ACCESS
+------
+Caller must be owner of the project or be a member of the privileged
+'Administrators' group.
+
+SCRIPTING
+---------
+This command is intended to be used in scripts.
+
+OPTIONS
+-------
+<PROJECT>::
+ Required; name of the project for which the commit should be
+ banned.
+
+<COMMIT>::
+ Required; commit(s) that should be banned.
+
+--reason::
+ Reason for banning the commit.
+
+EXAMPLES
+--------
+Ban commit `421919d015c062fd28901fe144a78a555d0b5984` from project
+`myproject`:
+
+====
+ $ ssh -p 29418 review.example.com gerrit ban-commit myproject \
+ 421919d015c062fd28901fe144a78a555d0b5984
+====
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index b09c3b3..e7d59fb 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -54,6 +54,9 @@
'gerrit approve'::
'Deprecated alias for `gerrit review`.'
+link:cmd-ban-commit.html[gerrit ban-commit]::
+ Bans a commit from a project's repository.
+
link:cmd-ls-groups.html[gerrit ls-groups]::
List groups visible to the caller.
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 54f7752..e7cc9e4 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1898,6 +1898,12 @@
updated versions. If false, a server restart is required to change
any of these resources. Default is true, allowing automatic reloads.
+[[site.enableDeprecatedQuery]]site.enableDeprecatedQuery::
++
+If true the deprecated `/query` URL is available to return JSON
+and text results for changes. If false, the URL is disabled and
+returns 404 to clients. Default is true, enabling `/query`.
+
[[sshd]] Section sshd
~~~~~~~~~~~~~~~~~~~~~
diff --git a/gerrit-antlr/.gitignore b/gerrit-antlr/.gitignore
index 194bedc..fb047af 100644
--- a/gerrit-antlr/.gitignore
+++ b/gerrit-antlr/.gitignore
@@ -2,4 +2,5 @@
/.classpath
/.project
/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-antlr.iml
\ No newline at end of file
diff --git a/gerrit-antlr/pom.xml b/gerrit-antlr/pom.xml
index aa0d7fd..34cb46f 100644
--- a/gerrit-antlr/pom.xml
+++ b/gerrit-antlr/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.4-SNAPSHOT</version>
+ <version>2.5-SNAPSHOT</version>
</parent>
<artifactId>gerrit-antlr</artifactId>
diff --git a/gerrit-common/.gitignore b/gerrit-common/.gitignore
index 194bedc..759f12c 100644
--- a/gerrit-common/.gitignore
+++ b/gerrit-common/.gitignore
@@ -2,4 +2,5 @@
/.classpath
/.project
/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-common.iml
\ No newline at end of file
diff --git a/gerrit-common/pom.xml b/gerrit-common/pom.xml
index e7933ea..9b3fe5f 100644
--- a/gerrit-common/pom.xml
+++ b/gerrit-common/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.4-SNAPSHOT</version>
+ <version>2.5-SNAPSHOT</version>
</parent>
<artifactId>gerrit-common</artifactId>
diff --git a/gerrit-ehcache/.gitignore b/gerrit-ehcache/.gitignore
index 20251d4..fe190c9 100644
--- a/gerrit-ehcache/.gitignore
+++ b/gerrit-ehcache/.gitignore
@@ -3,3 +3,4 @@
/.project
/.settings/org.eclipse.m2e.core.prefs
/.settings/org.maven.ide.eclipse.prefs
+/gerrit-ehcache.iml
\ No newline at end of file
diff --git a/gerrit-ehcache/pom.xml b/gerrit-ehcache/pom.xml
index 839c52b0..f9117b9 100644
--- a/gerrit-ehcache/pom.xml
+++ b/gerrit-ehcache/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.4-SNAPSHOT</version>
+ <version>2.5-SNAPSHOT</version>
</parent>
<artifactId>gerrit-ehcache</artifactId>
diff --git a/gerrit-gwtdebug/.gitignore b/gerrit-gwtdebug/.gitignore
index 194bedc..4207862 100644
--- a/gerrit-gwtdebug/.gitignore
+++ b/gerrit-gwtdebug/.gitignore
@@ -2,4 +2,5 @@
/.classpath
/.project
/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-gwtdebug.iml
\ No newline at end of file
diff --git a/gerrit-gwtdebug/pom.xml b/gerrit-gwtdebug/pom.xml
index 734f645..01b93a6 100644
--- a/gerrit-gwtdebug/pom.xml
+++ b/gerrit-gwtdebug/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.4-SNAPSHOT</version>
+ <version>2.5-SNAPSHOT</version>
</parent>
<artifactId>gerrit-gwtdebug</artifactId>
diff --git a/gerrit-gwtui/.gitignore b/gerrit-gwtui/.gitignore
index 194bedc..53d46b3 100644
--- a/gerrit-gwtui/.gitignore
+++ b/gerrit-gwtui/.gitignore
@@ -2,4 +2,5 @@
/.classpath
/.project
/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-gwtui.iml
\ No newline at end of file
diff --git a/gerrit-gwtui/pom.xml b/gerrit-gwtui/pom.xml
index d6ac743..b3291d1 100644
--- a/gerrit-gwtui/pom.xml
+++ b/gerrit-gwtui/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.4-SNAPSHOT</version>
+ <version>2.5-SNAPSHOT</version>
</parent>
<artifactId>gerrit-gwtui</artifactId>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
index 3aee0e2..ffa76ed 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
@@ -58,6 +58,7 @@
import com.google.gerrit.client.auth.userpass.UserPassSignInDialog;
import com.google.gerrit.client.changes.AccountDashboardScreen;
import com.google.gerrit.client.changes.ChangeScreen;
+import com.google.gerrit.client.changes.CustomDashboardScreen;
import com.google.gerrit.client.changes.PatchTable;
import com.google.gerrit.client.changes.PublishCommentScreen;
import com.google.gerrit.client.changes.QueryScreen;
@@ -361,8 +362,18 @@
}
private static void dashboard(final String token) {
- Gerrit.display(token, //
- new AccountDashboardScreen(Account.Id.parse(skip(token))));
+ String rest = skip(token);
+ if (rest.matches("[0-9]+")) {
+ Gerrit.display(token, new AccountDashboardScreen(Account.Id.parse(rest)));
+ return;
+ }
+
+ if (rest.startsWith("?")) {
+ Gerrit.display(token, new CustomDashboardScreen(rest.substring(1)));
+ return;
+ }
+
+ Gerrit.display(token, new NotFoundScreen());
}
private static void change(final String token) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java
index 74a2678..13bba12 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java
@@ -25,6 +25,7 @@
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.user.client.PluginSafePopupPanel;
import com.google.gwtjsonrpc.client.RemoteJsonException;
@@ -94,6 +95,11 @@
body.add(message.toBlockWidget());
}
+ public ErrorDialog(final Widget w) {
+ this();
+ body.add(w);
+ }
+
/** Create a dialog box to nicely format an exception. */
public ErrorDialog(final Throwable what) {
this();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
index a8315d8..574f58e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
@@ -104,6 +104,7 @@
String errorDialogTitle();
String errorDialogButtons();
String errorDialogErrorType();
+ String errorDialogText();
String fileColumnHeader();
String fileLine();
String fileLineCONTEXT();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
index 9250ca3..d049ff6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
@@ -89,6 +89,7 @@
String initialRevision();
String buttonAddBranch();
String buttonDeleteBranch();
+ String branchDeletionOpenChanges();
String groupListPrev();
String groupListNext();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index 389ed2c..7e0edec 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -68,6 +68,8 @@
initialRevision = Initial Revision
buttonAddBranch = Create Branch
buttonDeleteBranch = Delete
+branchDeletionOpenChanges = The following branches were not deleted \
+because they have open changes:
groupListPrev = Previous group
groupListNext = Next group
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
index f3ecbb3..56d9417 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
@@ -16,16 +16,19 @@
import com.google.gerrit.client.ConfirmationCallback;
import com.google.gerrit.client.ConfirmationDialog;
+import com.google.gerrit.client.ErrorDialog;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.GitwebLink;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
+import com.google.gerrit.client.ui.BranchLink;
import com.google.gerrit.client.ui.FancyFlexTable;
import com.google.gerrit.client.ui.HintTextBox;
import com.google.gerrit.common.data.ListBranchesResult;
import com.google.gerrit.common.errors.InvalidNameException;
import com.google.gerrit.common.errors.InvalidRevisionException;
import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
@@ -41,6 +44,7 @@
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
import com.google.gwtjsonrpc.client.RemoteJsonException;
@@ -260,24 +264,52 @@
b.toSafeHtml(), new ConfirmationCallback() {
@Override
public void onOk() {
- Util.PROJECT_SVC.deleteBranch(getProjectKey(), ids,
- new GerritCallback<Set<Branch.NameKey>>() {
- public void onSuccess(final Set<Branch.NameKey> deleted) {
- for (int row = 1; row < table.getRowCount();) {
- final Branch k = getRowItem(row);
- if (k != null && deleted.contains(k.getNameKey())) {
- table.removeRow(row);
- } else {
- row++;
- }
- }
- }
- });
+ deleteBranches(ids);
}
});
confirmationDialog.center();
}
+ private void deleteBranches(final Set<Branch.NameKey> branchIds) {
+ Util.PROJECT_SVC.deleteBranch(getProjectKey(), branchIds,
+ new GerritCallback<Set<Branch.NameKey>>() {
+ public void onSuccess(final Set<Branch.NameKey> deleted) {
+ if (!deleted.isEmpty()) {
+ for (int row = 1; row < table.getRowCount();) {
+ final Branch k = getRowItem(row);
+ if (k != null && deleted.contains(k.getNameKey())) {
+ table.removeRow(row);
+ } else {
+ row++;
+ }
+ }
+ }
+
+ branchIds.removeAll(deleted);
+ if (!branchIds.isEmpty()) {
+ final VerticalPanel p = new VerticalPanel();
+ final ErrorDialog errorDialog = new ErrorDialog(p);
+ final Label l = new Label(Util.C.branchDeletionOpenChanges());
+ l.setStyleName(Gerrit.RESOURCES.css().errorDialogText());
+ p.add(l);
+ for (final Branch.NameKey branch : branchIds) {
+ final BranchLink link =
+ new BranchLink(branch.getParentKey(), Change.Status.NEW,
+ branch.get(), null) {
+ @Override
+ public void go() {
+ errorDialog.hide();
+ super.go();
+ };
+ };
+ p.add(link);
+ }
+ errorDialog.center();
+ }
+ }
+ });
+ }
+
void display(final List<Branch> result) {
canDelete = false;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
index 1d881b6..be892a1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
@@ -14,39 +14,46 @@
package com.google.gerrit.client.changes;
-import com.google.gerrit.client.FormatUtil;
import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.changes.ChangeTable.ApprovalViewType;
+import com.google.gerrit.client.NotFoundScreen;
+import com.google.gerrit.client.rpc.NativeList;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.Screen;
import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.common.data.AccountDashboardInfo;
-import com.google.gerrit.common.data.AccountInfo;
import com.google.gerrit.reviewdb.client.Account;
+import java.util.Collections;
+import java.util.Comparator;
public class AccountDashboardScreen extends Screen implements ChangeListScreen {
private final Account.Id ownerId;
- private ChangeTable table;
- private ChangeTable.Section byOwner;
- private ChangeTable.Section forReview;
- private ChangeTable.Section closed;
+ private final boolean mine;
+ private ChangeTable2 table;
+ private ChangeTable2.Section outgoing;
+ private ChangeTable2.Section incoming;
+ private ChangeTable2.Section closed;
public AccountDashboardScreen(final Account.Id id) {
ownerId = id;
+ mine = Gerrit.isSignedIn() && ownerId.equals(Gerrit.getUserAccount().getId());
}
@Override
protected void onInitUI() {
super.onInitUI();
- table = new ChangeTable(true);
+ table = new ChangeTable2();
table.addStyleName(Gerrit.RESOURCES.css().accountDashboard());
- byOwner = new ChangeTable.Section("", ApprovalViewType.STRONGEST, null);
- forReview = new ChangeTable.Section("", ApprovalViewType.USER, ownerId);
- closed = new ChangeTable.Section("", ApprovalViewType.STRONGEST, null);
- table.addSection(byOwner);
- table.addSection(forReview);
+ outgoing = new ChangeTable2.Section();
+ incoming = new ChangeTable2.Section();
+ closed = new ChangeTable2.Section();
+
+ outgoing.setTitleText(Util.C.outgoingReviews());
+ incoming.setTitleText(Util.C.incomingReviews());
+ closed.setTitleText(Util.C.recentlyClosed());
+
+ table.addSection(outgoing);
+ table.addSection(incoming);
table.addSection(closed);
add(table);
table.setSavePointerId(PageLinks.toAccountDashboard(ownerId));
@@ -55,13 +62,17 @@
@Override
protected void onLoad() {
super.onLoad();
- Util.LIST_SVC.forAccount(ownerId,
- new ScreenLoadCallback<AccountDashboardInfo>(this) {
+ String who = mine ? "self" : ownerId.toString();
+ ChangeList.query(
+ new ScreenLoadCallback<NativeList<ChangeList>>(this) {
@Override
- protected void preDisplay(final AccountDashboardInfo r) {
- display(r);
+ protected void preDisplay(NativeList<ChangeList> result) {
+ display(result);
}
- });
+ },
+ "is:open owner:" + who,
+ "is:open reviewer:" + who + " -owner:" + who,
+ "is:closed owner:" + who + " -age:1w limit:10");
}
@Override
@@ -70,20 +81,80 @@
table.setRegisterKeys(true);
}
- private void display(final AccountDashboardInfo r) {
- table.setAccountInfoCache(r.getAccounts());
+ private void display(NativeList<ChangeList> result) {
+ if (!mine && !hasChanges(result)) {
+ // When no results are returned and the data is not for the
+ // current user, the target user is presumed to not exist.
+ Gerrit.display(getToken(), new NotFoundScreen());
+ return;
+ }
- final AccountInfo o = r.getAccounts().get(r.getOwner());
- final String name = FormatUtil.name(o);
- setWindowTitle(name);
- setPageTitle(Util.M.accountDashboardTitle(name));
- byOwner.setTitleText(Util.M.changesStartedBy(name));
- forReview.setTitleText(Util.M.changesReviewableBy(name));
- closed.setTitleText(Util.C.changesRecentlyClosed());
+ ChangeList out = result.get(0);
+ ChangeList in = result.get(1);
+ ChangeList done = result.get(2);
- byOwner.display(r.getByOwner());
- forReview.display(r.getForReview());
- closed.display(r.getClosed());
+ if (mine) {
+ setWindowTitle(Util.C.myDashboardTitle());
+ setPageTitle(Util.C.myDashboardTitle());
+ } else {
+ // The server doesn't tell us who the dashboard is for. Try to guess
+ // by looking at a change started by the owner and extract the name.
+ String name = guessName(out);
+ if (name == null) {
+ name = guessName(done);
+ }
+ if (name != null) {
+ setWindowTitle(name);
+ setPageTitle(Util.M.accountDashboardTitle(name));
+ } else {
+ setWindowTitle(Util.C.unknownDashboardTitle());
+ setWindowTitle(Util.C.unknownDashboardTitle());
+ }
+ }
+
+ Collections.sort(out.asList(), compare());
+ Collections.sort(in.asList(), compare());
+
+ table.updateColumnsForLabels(out, in, done);
+ outgoing.display(out);
+ incoming.display(in);
+ closed.display(done);
table.finishDisplay();
}
+
+ private Comparator<ChangeInfo> compare() {
+ return new Comparator<ChangeInfo>() {
+ @Override
+ public int compare(ChangeInfo a, ChangeInfo b) {
+ int cmp = a.project().compareTo(b.project());
+ if (cmp != 0) return cmp;
+ cmp = a.branch().compareTo(b.branch());
+ if (cmp != 0) return cmp;
+
+ String at = a.topic() != null ? a.topic() : "";
+ String bt = b.topic() != null ? b.topic() : "";
+ cmp = at.compareTo(bt);
+ if (cmp != 0) return cmp;
+ return a._number() - b._number();
+ }
+ };
+ }
+
+ private boolean hasChanges(NativeList<ChangeList> result) {
+ for (ChangeList list : result.asList()) {
+ if (!list.isEmpty()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static String guessName(ChangeList list) {
+ for (ChangeInfo change : list.asList()) {
+ if (change.owner() != null && change.owner().name() != null) {
+ return change.owner().name();
+ }
+ }
+ return null;
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeCache.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeCache.java
index 27f76f6..5a9da1a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeCache.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeCache.java
@@ -38,7 +38,6 @@
private Change.Id changeId;
private ChangeDetailCache detail;
private ListenableValue<ChangeInfo> info;
- private StarCache starred;
protected ChangeCache(Change.Id chg) {
changeId = chg;
@@ -61,11 +60,4 @@
}
return info;
}
-
- public StarCache getStarCache() {
- if (starred == null) {
- starred = new StarCache(changeId);
- }
- return starred;
- }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
index 3372096..d42992f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
@@ -23,7 +23,11 @@
String statusLongAbandoned();
String statusLongDraft();
- String changesRecentlyClosed();
+ String myDashboardTitle();
+ String unknownDashboardTitle();
+ String incomingReviews();
+ String outgoingReviews();
+ String recentlyClosed();
String starredHeading();
String watchedHeading();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
index ad70674..8ceb74c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
@@ -7,7 +7,11 @@
starredHeading = Starred Changes
watchedHeading = Open Changes of Watched Projects
draftsHeading = Changes with unpublished drafts
-changesRecentlyClosed = Recently closed
+myDashboardTitle = My Reviews
+unknownDashboardTitle = Code Review Dashboard
+incomingReviews = Incoming reviews
+outgoingReviews = Outgoing reviews
+recentlyClosed = Recently closed
allOpenChanges = All open changes
allAbandonedChanges = All abandoned changes
allMergedChanges = All merged changes
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java
index bb28e11..9c19d50 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java
@@ -65,6 +65,7 @@
public static void setChangeDetail(ChangeDetail detail) {
Change.Id chgId = detail.getChange().getId();
ChangeCache.get(chgId).getChangeDetailCache().set(detail);
+ StarredChanges.fireChangeStarEvent(chgId, detail.isStarred());
}
private final Change.Id changeId;
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
new file mode 100644
index 0000000..adacade
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
@@ -0,0 +1,106 @@
+// Copyright (C) 2012 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.changes;
+
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwtjsonrpc.client.impl.ser.JavaSqlTimestamp_JsonSerializer;
+
+import java.sql.Timestamp;
+import java.util.Set;
+
+public class ChangeInfo extends JavaScriptObject {
+ public final Project.NameKey project_name_key() {
+ return new Project.NameKey(project());
+ }
+
+ public final Change.Id legacy_id() {
+ return new Change.Id(_number());
+ }
+
+ public final Timestamp updated() {
+ return JavaSqlTimestamp_JsonSerializer.parseTimestamp(updatedRaw());
+ }
+
+ public final String id_abbreviated() {
+ return new Change.Key(id()).abbreviate();
+ }
+
+ public final Change.Status status() {
+ return Change.Status.valueOf(statusRaw());
+ }
+
+ public final Set<String> labels() {
+ return Natives.keys(labels0());
+ }
+
+ public final native String project() /*-{ return this.project; }-*/;
+ public final native String branch() /*-{ return this.branch; }-*/;
+ public final native String topic() /*-{ return this.topic; }-*/;
+ public final native String id() /*-{ return this.id; }-*/;
+ private final native String statusRaw() /*-{ return this.status; }-*/;
+ public final native String subject() /*-{ return this.subject; }-*/;
+ public final native AccountInfo owner() /*-{ return this.owner; }-*/;
+ private final native String updatedRaw() /*-{ return this.updated; }-*/;
+ public final native boolean starred() /*-{ return this.starred ? true : false; }-*/;
+ public final native String _sortkey() /*-{ return this._sortkey; }-*/;
+ private final native JavaScriptObject labels0() /*-{ return this.labels; }-*/;
+ public final native LabelInfo label(String n) /*-{ return this.labels[n]; }-*/;
+ final native int _number() /*-{ return this._number; }-*/;
+ final native boolean _more_changes()
+ /*-{ return this._more_changes ? true : false; }-*/;
+
+ protected ChangeInfo() {
+ }
+
+ public static class AccountInfo extends JavaScriptObject {
+ public final native String name() /*-{ return this.name; }-*/;
+
+ protected AccountInfo() {
+ }
+ }
+
+ public static class LabelInfo extends JavaScriptObject {
+ public final SubmitRecord.Label.Status status() {
+ if (approved() != null) {
+ return SubmitRecord.Label.Status.OK;
+ } else if (rejected() != null) {
+ return SubmitRecord.Label.Status.REJECT;
+ } else {
+ return SubmitRecord.Label.Status.NEED;
+ }
+ }
+
+ public final native String name() /*-{ return this._name; }-*/;
+ public final native AccountInfo approved() /*-{ return this.approved; }-*/;
+ public final native AccountInfo rejected() /*-{ return this.rejected; }-*/;
+
+ public final native AccountInfo recommended() /*-{ return this.recommended; }-*/;
+ public final native AccountInfo disliked() /*-{ return this.disliked; }-*/;
+ final native short _value()
+ /*-{
+ if (this.value) return this.value;
+ if (this.recommended) return 1;
+ if (this.disliked) return -1;
+ return 0;
+ }-*/;
+
+ protected LabelInfo() {
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java
new file mode 100644
index 0000000..debe145
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java
@@ -0,0 +1,74 @@
+// Copyright (C) 2012 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.changes;
+
+import com.google.gerrit.client.rpc.NativeList;
+import com.google.gerrit.client.rpc.RestApi;
+import com.google.gwtjsonrpc.common.AsyncCallback;
+import com.google.gwtorm.client.KeyUtil;
+
+/** List of changes available from {@code /changes/}. */
+public class ChangeList extends NativeList<ChangeInfo> {
+ private static final String URI = "/changes/";
+
+ /** Run 2 or more queries in a single remote invocation. */
+ public static void query(
+ AsyncCallback<NativeList<ChangeList>> callback, String... queries) {
+ assert queries.length >= 2; // At least 2 is required for correct result.
+ RestApi call = new RestApi(URI);
+ for (String q : queries) {
+ call.addParameterRaw("q", KeyUtil.encode(q));
+ }
+ call.send(callback);
+ }
+
+ public static void prev(String query,
+ int limit, String sortkey,
+ AsyncCallback<ChangeList> callback) {
+ RestApi call = newQuery(query);
+ if (limit > 0) {
+ call.addParameter("n", limit);
+ }
+ if (!PagedSingleListScreen.MIN_SORTKEY.equals(sortkey)) {
+ call.addParameter("P", sortkey);
+ }
+ call.send(callback);
+ }
+
+ public static void next(String query,
+ int limit, String sortkey,
+ AsyncCallback<ChangeList> callback) {
+ RestApi call = newQuery(query);
+ if (limit > 0) {
+ call.addParameter("n", limit);
+ }
+ if (!PagedSingleListScreen.MAX_SORTKEY.equals(sortkey)) {
+ call.addParameter("N", sortkey);
+ }
+ call.send(callback);
+ }
+
+ private static RestApi newQuery(String query) {
+ RestApi call = new RestApi(URI);
+ // The server default is ?q=status:open so don't repeat it.
+ if (!"status:open".equals(query) && !"is:open".equals(query)) {
+ call.addParameterRaw("q", KeyUtil.encode(query));
+ }
+ return call;
+ }
+
+ protected ChangeList() {
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
index 41dc6c1..4bd0828 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
@@ -18,8 +18,6 @@
public interface ChangeMessages extends Messages {
String accountDashboardTitle(String fullName);
- String changesStartedBy(String fullName);
- String changesReviewableBy(String fullName);
String changesOpenInProject(String string);
String changesMergedInProject(String string);
String changesAbandonedInProject(String string);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
index 40088a1..2449613 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
@@ -1,6 +1,4 @@
accountDashboardTitle = Code Review Dashboard for {0}
-changesStartedBy = Started by {0}
-changesReviewableBy = Review Requests for {0}
changesOpenInProject = Open Changes In {0}
changesMergedInProject = Merged Changes In {0}
changesAbandonedInProject = Abandoned Changes In {0}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
index b054175..15c1150 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
@@ -28,9 +28,9 @@
import com.google.gerrit.common.data.ChangeInfo;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
@@ -42,7 +42,6 @@
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HorizontalPanel;
-import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
@@ -60,9 +59,7 @@
private final Change.Id changeId;
private final PatchSet.Id openPatchSetId;
private ChangeDetailCache detailCache;
- private StarCache starred;
- private Image starChange;
private ChangeDescriptionBlock descriptionBlock;
private ApprovalTable approvals;
@@ -155,8 +152,6 @@
detailCache = cache.getChangeDetailCache();
detailCache.addValueChangeHandler(this);
- starred = cache.getStarCache();
-
addStyleName(Gerrit.RESOURCES.css().changeScreen());
keysNavigation = new KeyCommandSet(Gerrit.C.sectionNavigation());
@@ -165,13 +160,13 @@
keysNavigation.add(new ExpandCollapseDependencySectionKeyCommand(0, 'd', Util.C.expandCollapseDependencies()));
if (Gerrit.isSignedIn()) {
- keysAction.add(starred.new KeyCommand(0, 's', Util.C.changeTableStar()));
+ StarredChanges.Icon star = StarredChanges.createIcon(changeId, false);
+ star.setStyleName(Gerrit.RESOURCES.css().changeScreenStarIcon());
+ setTitleWest(star);
+
+ keysAction.add(StarredChanges.newKeyCommand(star));
keysAction.add(new PublishCommentsKeyCommand(0, 'r', Util.C
.keyPublishComments()));
-
- starChange = starred.createStar();
- starChange.setStyleName(Gerrit.RESOURCES.css().changeScreenStarIcon());
- setTitleWest(starChange);
}
descriptionBlock = new ChangeDescriptionBlock();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
index 111e507..19a770e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
@@ -145,7 +145,7 @@
protected void onStarClick(final int row) {
final ChangeInfo c = getRowItem(row);
if (c != null && Gerrit.isSignedIn()) {
- ChangeCache.get(c.getId()).getStarCache().toggleStar();
+ ((StarredChanges.Icon) table.getWidget(row, C_STAR)).toggleStar();
}
}
@@ -198,7 +198,7 @@
final String idstr = c.getKey().abbreviate();
table.setWidget(row, C_ARROW, null);
if (Gerrit.isSignedIn()) {
- table.setWidget(row, C_STAR, cache.getStarCache().createStar());
+ table.setWidget(row, C_STAR, StarredChanges.createIcon(c.getId(), c.isStarred()));
}
table.setWidget(row, C_ID, new TableChangeLink(idstr, c));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
new file mode 100644
index 0000000..1372aa2
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
@@ -0,0 +1,403 @@
+// Copyright (C) 2008 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.changes;
+
+import static com.google.gerrit.client.FormatUtil.shortFormat;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
+import com.google.gerrit.client.ui.BranchLink;
+import com.google.gerrit.client.ui.ChangeLink;
+import com.google.gerrit.client.ui.NavigationTable;
+import com.google.gerrit.client.ui.NeedsSignInKeyCommand;
+import com.google.gerrit.client.ui.ProjectLink;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTMLTable.Cell;
+import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.InlineLabel;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class ChangeTable2 extends NavigationTable<ChangeInfo> {
+ private static final int C_STAR = 1;
+ private static final int C_ID = 2;
+ private static final int C_SUBJECT = 3;
+ private static final int C_OWNER = 4;
+ private static final int C_PROJECT = 5;
+ private static final int C_BRANCH = 6;
+ private static final int C_LAST_UPDATE = 7;
+ private static final int BASE_COLUMNS = 8;
+
+ private final List<Section> sections;
+ private int columns;
+ private List<String> labelNames;
+
+ public ChangeTable2() {
+ columns = BASE_COLUMNS;
+ labelNames = Collections.emptyList();
+
+ keysNavigation.add(new PrevKeyCommand(0, 'k', Util.C.changeTablePrev()));
+ keysNavigation.add(new NextKeyCommand(0, 'j', Util.C.changeTableNext()));
+ keysNavigation.add(new OpenKeyCommand(0, 'o', Util.C.changeTableOpen()));
+ keysNavigation.add(
+ new OpenKeyCommand(0, KeyCodes.KEY_ENTER, Util.C.changeTableOpen()));
+
+ if (Gerrit.isSignedIn()) {
+ keysAction.add(new StarKeyCommand(0, 's', Util.C.changeTableStar()));
+ }
+
+ sections = new ArrayList<Section>();
+ table.setText(0, C_STAR, "");
+ table.setText(0, C_ID, Util.C.changeTableColumnID());
+ table.setText(0, C_SUBJECT, Util.C.changeTableColumnSubject());
+ table.setText(0, C_OWNER, Util.C.changeTableColumnOwner());
+ table.setText(0, C_PROJECT, Util.C.changeTableColumnProject());
+ table.setText(0, C_BRANCH, Util.C.changeTableColumnBranch());
+ table.setText(0, C_LAST_UPDATE, Util.C.changeTableColumnLastUpdate());
+
+ final FlexCellFormatter fmt = table.getFlexCellFormatter();
+ fmt.addStyleName(0, C_STAR, Gerrit.RESOURCES.css().iconHeader());
+ fmt.addStyleName(0, C_ID, Gerrit.RESOURCES.css().cID());
+ for (int i = C_ID; i < columns; i++) {
+ fmt.addStyleName(0, i, Gerrit.RESOURCES.css().dataHeader());
+ }
+
+ table.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(final ClickEvent event) {
+ final Cell cell = table.getCellForEvent(event);
+ if (cell == null) {
+ return;
+ }
+ if (cell.getCellIndex() == C_STAR) {
+ // Don't do anything (handled by star itself).
+ } else if (cell.getCellIndex() == C_OWNER) {
+ // Don't do anything.
+ } else if (getRowItem(cell.getRowIndex()) != null) {
+ movePointerTo(cell.getRowIndex());
+ }
+ }
+ });
+ }
+
+ @Override
+ protected Object getRowItemKey(final ChangeInfo item) {
+ return item.legacy_id();
+ }
+
+ @Override
+ protected void onOpenRow(final int row) {
+ final ChangeInfo c = getRowItem(row);
+ final Change.Id id = c.legacy_id();
+ Gerrit.display(PageLinks.toChange(id), new ChangeScreen(id));
+ }
+
+ private void insertNoneRow(final int row) {
+ insertRow(row);
+ table.setText(row, 0, Util.C.changeTableNone());
+ final FlexCellFormatter fmt = table.getFlexCellFormatter();
+ fmt.setColSpan(row, 0, columns);
+ fmt.setStyleName(row, 0, Gerrit.RESOURCES.css().emptySection());
+ }
+
+ private void insertChangeRow(final int row) {
+ insertRow(row);
+ applyDataRowStyle(row);
+ }
+
+ @Override
+ protected void applyDataRowStyle(final int row) {
+ super.applyDataRowStyle(row);
+ final CellFormatter fmt = table.getCellFormatter();
+ fmt.addStyleName(row, C_STAR, Gerrit.RESOURCES.css().iconCell());
+ for (int i = C_ID; i < columns; i++) {
+ fmt.addStyleName(row, i, Gerrit.RESOURCES.css().dataCell());
+ }
+ fmt.addStyleName(row, C_ID, Gerrit.RESOURCES.css().cID());
+ fmt.addStyleName(row, C_SUBJECT, Gerrit.RESOURCES.css().cSUBJECT());
+ fmt.addStyleName(row, C_PROJECT, Gerrit.RESOURCES.css().cPROJECT());
+ fmt.addStyleName(row, C_BRANCH, Gerrit.RESOURCES.css().cPROJECT());
+ fmt.addStyleName(row, C_LAST_UPDATE, Gerrit.RESOURCES.css().cLastUpdate());
+ for (int i = BASE_COLUMNS; i < columns; i++) {
+ fmt.addStyleName(row, i, Gerrit.RESOURCES.css().cAPPROVAL());
+ }
+ }
+
+ public void updateColumnsForLabels(ChangeList... lists) {
+ labelNames = new ArrayList<String>();
+ for (ChangeList list : lists) {
+ for (int i = 0; i < list.size(); i++) {
+ for (String name : list.get(i).labels()) {
+ if (!labelNames.contains(name)) {
+ labelNames.add(name);
+ }
+ }
+ }
+ }
+ Collections.sort(labelNames);
+
+ if (BASE_COLUMNS + labelNames.size() < columns) {
+ int n = columns - (BASE_COLUMNS + labelNames.size());
+ for (int row = 0; row < table.getRowCount(); row++) {
+ table.removeCells(row, columns, n);
+ }
+ }
+ columns = BASE_COLUMNS + labelNames.size();
+
+ FlexCellFormatter fmt = table.getFlexCellFormatter();
+ for (int i = 0; i < labelNames.size(); i++) {
+ String name = labelNames.get(i);
+ int col = BASE_COLUMNS + i;
+
+ StringBuilder abbrev = new StringBuilder();
+ for (String t : name.split("-")) {
+ abbrev.append(t.substring(0, 1).toUpperCase());
+ }
+ table.setText(0, col, abbrev.toString());
+ table.getCellFormatter().getElement(0, col).setTitle(name);
+ fmt.addStyleName(0, col, Gerrit.RESOURCES.css().dataHeader());
+ }
+
+ for (Section s : sections) {
+ if (s.titleRow >= 0) {
+ fmt.setColSpan(s.titleRow, 0, columns);
+ }
+ }
+ }
+
+ private void populateChangeRow(final int row, final ChangeInfo c) {
+ if (Gerrit.isSignedIn()) {
+ table.setWidget(row, C_STAR, StarredChanges.createIcon(
+ c.legacy_id(),
+ c.starred()));
+ }
+ table.setWidget(row, C_ID, new TableChangeLink(c.id_abbreviated(), c));
+
+ String subject = c.subject();
+ if (subject.length() > 80) {
+ subject = subject.substring(0, 80);
+ }
+ Change.Status status = c.status();
+ if (status != Change.Status.NEW) {
+ subject += " (" + Util.toLongString(status) + ")";
+ }
+ table.setWidget(row, C_SUBJECT, new TableChangeLink(subject, c));
+
+ String owner = "";
+ if (c.owner() != null && c.owner().name() != null) {
+ owner = c.owner().name();
+ }
+ table.setText(row, C_OWNER, owner);
+
+ table.setWidget(
+ row, C_PROJECT, new ProjectLink(c.project_name_key(), c.status()));
+ table.setWidget(row, C_BRANCH, new BranchLink(c.project_name_key(), c
+ .status(), c.branch(), c.topic()));
+ table.setText(row, C_LAST_UPDATE, shortFormat(c.updated()));
+
+ boolean displayName = Gerrit.isSignedIn() && Gerrit.getUserAccount()
+ .getGeneralPreferences().isShowUsernameInReviewCategory();
+
+ CellFormatter fmt = table.getCellFormatter();
+ for (int idx = 0; idx < labelNames.size(); idx++) {
+ String name = labelNames.get(idx);
+ int col = BASE_COLUMNS + idx;
+
+ LabelInfo label = c.label(name);
+ if (label == null) {
+ table.clearCell(row, col);
+ continue;
+ }
+
+ String user;
+ if (label.rejected() != null) {
+ user = label.rejected().name();
+ if (displayName && user != null) {
+ FlowPanel panel = new FlowPanel();
+ panel.add(new Image(Gerrit.RESOURCES.redNot()));
+ panel.add(new InlineLabel(user));
+ table.setWidget(row, col, panel);
+ } else {
+ table.setWidget(row, col, new Image(Gerrit.RESOURCES.redNot()));
+ }
+ } else if (label.approved() != null) {
+ user = label.approved().name();
+ if (displayName && user != null) {
+ FlowPanel panel = new FlowPanel();
+ panel.add(new Image(Gerrit.RESOURCES.greenCheck()));
+ panel.add(new InlineLabel(user));
+ table.setWidget(row, col, panel);
+ } else {
+ table.setWidget(row, col, new Image(Gerrit.RESOURCES.greenCheck()));
+ }
+ } else if (label.disliked() != null) {
+ user = label.disliked().name();
+ String vstr = String.valueOf(label._value());
+ if (displayName && user != null) {
+ vstr = vstr + " " + user;
+ }
+ fmt.addStyleName(row, col, Gerrit.RESOURCES.css().negscore());
+ table.setText(row, col, vstr);
+ } else if (label.recommended() != null) {
+ user = label.recommended().name();
+ String vstr = "+" + label._value();
+ if (displayName && user != null) {
+ vstr = vstr + " " + user;
+ }
+ fmt.addStyleName(row, col, Gerrit.RESOURCES.css().posscore());
+ table.setText(row, col, vstr);
+ } else {
+ table.clearCell(row, col);
+ continue;
+ }
+ fmt.addStyleName(row, col, Gerrit.RESOURCES.css().singleLine());
+
+ if (!displayName && user != null) {
+ // Some web browsers ignore the embedded newline; some like it;
+ // so we include a space before the newline to accommodate both.
+ fmt.getElement(row, col).setTitle(name + " \nby " + user);
+ }
+ }
+
+ // TODO(sop): Highlight changes I haven't reviewed on my dashboard.
+ // final Element tr = DOM.getParent(fmt.getElement(row, 0));
+ // UIObject.setStyleName(tr, Gerrit.RESOURCES.css().needsReview(),
+ // !haveReview && highlightUnreviewed);
+
+ setRowItem(row, c);
+ }
+
+ public void addSection(final Section s) {
+ assert s.parent == null;
+
+ if (s.titleText != null) {
+ s.titleRow = table.getRowCount();
+ table.setText(s.titleRow, 0, s.titleText);
+ final FlexCellFormatter fmt = table.getFlexCellFormatter();
+ fmt.setColSpan(s.titleRow, 0, columns);
+ fmt.addStyleName(s.titleRow, 0, Gerrit.RESOURCES.css().sectionHeader());
+ } else {
+ s.titleRow = -1;
+ }
+
+ s.parent = this;
+ s.dataBegin = table.getRowCount();
+ insertNoneRow(s.dataBegin);
+ sections.add(s);
+ }
+
+ private int insertRow(final int beforeRow) {
+ for (final Section s : sections) {
+ if (beforeRow <= s.titleRow) {
+ s.titleRow++;
+ }
+ if (beforeRow < s.dataBegin) {
+ s.dataBegin++;
+ }
+ }
+ return table.insertRow(beforeRow);
+ }
+
+ private void removeRow(final int row) {
+ for (final Section s : sections) {
+ if (row < s.titleRow) {
+ s.titleRow--;
+ }
+ if (row < s.dataBegin) {
+ s.dataBegin--;
+ }
+ }
+ table.removeRow(row);
+ }
+
+ public class StarKeyCommand extends NeedsSignInKeyCommand {
+ public StarKeyCommand(int mask, char key, String help) {
+ super(mask, key, help);
+ }
+
+ @Override
+ public void onKeyPress(final KeyPressEvent event) {
+ int row = getCurrentRow();
+ ChangeInfo c = getRowItem(row);
+ if (c != null && Gerrit.isSignedIn()) {
+ ((StarredChanges.Icon) table.getWidget(row, C_STAR)).toggleStar();
+ }
+ }
+ }
+
+ private final class TableChangeLink extends ChangeLink {
+ private TableChangeLink(final String text, final ChangeInfo c) {
+ super(text, c.legacy_id());
+ }
+
+ @Override
+ public void go() {
+ movePointerTo(cid);
+ super.go();
+ }
+ }
+
+ public static class Section {
+ ChangeTable2 parent;
+ String titleText;
+ int titleRow = -1;
+ int dataBegin;
+ int rows;
+
+ public void setTitleText(final String text) {
+ titleText = text;
+ if (titleRow >= 0) {
+ parent.table.setText(titleRow, 0, titleText);
+ }
+ }
+
+ public void display(ChangeList changeList) {
+ final int sz = changeList != null ? changeList.size() : 0;
+ final boolean hadData = rows > 0;
+
+ if (hadData) {
+ while (sz < rows) {
+ parent.removeRow(dataBegin);
+ rows--;
+ }
+ } else {
+ parent.removeRow(dataBegin);
+ }
+
+ if (sz == 0) {
+ parent.insertNoneRow(dataBegin);
+ return;
+ }
+
+ while (rows < sz) {
+ parent.insertChangeRow(dataBegin + rows);
+ rows++;
+ }
+ for (int i = 0; i < sz; i++) {
+ parent.populateChangeRow(dataBegin + i, changeList.get(i));
+ }
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CustomDashboardScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CustomDashboardScreen.java
new file mode 100644
index 0000000..c9f1dc6
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CustomDashboardScreen.java
@@ -0,0 +1,112 @@
+// Copyright (C) 2012 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.changes;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.rpc.NativeList;
+import com.google.gerrit.client.rpc.ScreenLoadCallback;
+import com.google.gerrit.client.ui.Screen;
+import com.google.gwt.http.client.URL;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CustomDashboardScreen extends Screen implements ChangeListScreen {
+ private String title;
+ private List<String> titles;
+ private List<String> queries;
+ private ChangeTable2 table;
+ private List<ChangeTable2.Section> sections;
+
+ public CustomDashboardScreen(String params) {
+ titles = new ArrayList<String>();
+ queries = new ArrayList<String>();
+ for (String kvPair : params.split("[,;&]")) {
+ String[] kv = kvPair.split("=", 2);
+ if (kv.length != 2 || kv[0].isEmpty()) {
+ continue;
+ }
+
+ if ("title".equals(kv[0])) {
+ title = URL.decodeQueryString(kv[1]);
+ } else {
+ titles.add(URL.decodeQueryString(kv[0]));
+ queries.add(URL.decodeQueryString(kv[1]));
+ }
+ }
+ }
+
+ @Override
+ protected void onInitUI() {
+ super.onInitUI();
+
+ if (title != null) {
+ setWindowTitle(title);
+ setPageTitle(title);
+ }
+
+ table = new ChangeTable2();
+ table.addStyleName(Gerrit.RESOURCES.css().accountDashboard());
+
+ sections = new ArrayList<ChangeTable2.Section>();
+ for (String title : titles) {
+ ChangeTable2.Section s = new ChangeTable2.Section();
+ s.setTitleText(title);
+ table.addSection(s);
+ sections.add(s);
+ }
+ add(table);
+ }
+
+ @Override
+ protected void onLoad() {
+ super.onLoad();
+
+ if (queries.isEmpty()) {
+ display();
+ } else if (queries.size() == 1) {
+ ChangeList.next(queries.get(0),
+ 0, PagedSingleListScreen.MAX_SORTKEY,
+ new ScreenLoadCallback<ChangeList>(this) {
+ @Override
+ protected void preDisplay(ChangeList result) {
+ table.updateColumnsForLabels(result);
+ sections.get(0).display(result);
+ table.finishDisplay();
+ }
+ });
+ } else {
+ ChangeList.query(
+ new ScreenLoadCallback<NativeList<ChangeList>>(this) {
+ @Override
+ protected void preDisplay(NativeList<ChangeList> result) {
+ table.updateColumnsForLabels(
+ result.asList().toArray(new ChangeList[result.size()]));
+ for (int i = 0; i < result.size(); i++) {
+ sections.get(i).display(result.get(i));
+ }
+ table.finishDisplay();
+ }
+ },
+ queries.toArray(new String[queries.size()]));
+ }
+ }
+
+ @Override
+ public void registerKeys() {
+ super.registerKeys();
+ table.setRegisterKeys(true);
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PagedSingleListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PagedSingleListScreen.java
index 72300b2..23ce178 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PagedSingleListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PagedSingleListScreen.java
@@ -15,12 +15,9 @@
package com.google.gerrit.client.changes;
import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.changes.ChangeTable.ApprovalViewType;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.Hyperlink;
import com.google.gerrit.client.ui.Screen;
-import com.google.gerrit.common.data.ChangeInfo;
-import com.google.gerrit.common.data.SingleListChangeInfo;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.user.client.History;
@@ -28,19 +25,16 @@
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwtexpui.globalkey.client.KeyCommand;
-import java.util.List;
-
-
public abstract class PagedSingleListScreen extends Screen {
protected static final String MIN_SORTKEY = "";
protected static final String MAX_SORTKEY = "z";
protected final int pageSize;
- private ChangeTable table;
- private ChangeTable.Section section;
+ private ChangeTable2 table;
+ private ChangeTable2.Section section;
protected Hyperlink prev;
protected Hyperlink next;
- protected List<ChangeInfo> changes;
+ protected ChangeList changes;
protected final String anchorPrefix;
protected boolean useLoadPrev;
@@ -71,7 +65,7 @@
next = new Hyperlink(Util.C.pagedChangeListNext(), true, "");
next.setVisible(false);
- table = new ChangeTable(true) {
+ table = new ChangeTable2() {
{
keysNavigation.add(new DoLinkCommand(0, 'p', Util.C
.changeTablePagePrev(), prev));
@@ -79,8 +73,7 @@
.changeTablePageNext(), next));
}
};
- section = new ChangeTable.Section(null, ApprovalViewType.STRONGEST, null);
-
+ section = new ChangeTable2.Section();
table.addSection(section);
table.setSavePointerId(anchorPrefix);
add(table);
@@ -112,36 +105,34 @@
protected abstract void loadNext();
- protected AsyncCallback<SingleListChangeInfo> loadCallback() {
- return new ScreenLoadCallback<SingleListChangeInfo>(this) {
+ protected AsyncCallback<ChangeList> loadCallback() {
+ return new ScreenLoadCallback<ChangeList>(this) {
@Override
- protected void preDisplay(final SingleListChangeInfo result) {
+ protected void preDisplay(ChangeList result) {
display(result);
}
};
}
- protected void display(final SingleListChangeInfo result) {
- changes = result.getChanges();
-
+ protected void display(final ChangeList result) {
+ changes = result;
if (!changes.isEmpty()) {
final ChangeInfo f = changes.get(0);
final ChangeInfo l = changes.get(changes.size() - 1);
- prev.setTargetHistoryToken(anchorPrefix + ",p," + f.getSortKey());
- next.setTargetHistoryToken(anchorPrefix + ",n," + l.getSortKey());
+ prev.setTargetHistoryToken(anchorPrefix + ",p," + f._sortkey());
+ next.setTargetHistoryToken(anchorPrefix + ",n," + l._sortkey());
if (useLoadPrev) {
- prev.setVisible(!result.isAtEnd());
+ prev.setVisible(f._more_changes());
next.setVisible(!MIN_SORTKEY.equals(pos));
} else {
prev.setVisible(!MAX_SORTKEY.equals(pos));
- next.setVisible(!result.isAtEnd());
+ next.setVisible(l._more_changes());
}
}
-
- table.setAccountInfoCache(result.getAccounts());
- section.display(result.getChanges());
+ table.updateColumnsForLabels(result);
+ section.display(result);
table.finishDisplay();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/QueryScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/QueryScreen.java
index cf9c526..b94fcae 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/QueryScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/QueryScreen.java
@@ -17,13 +17,11 @@
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.common.data.ChangeInfo;
-import com.google.gerrit.common.data.SingleListChangeInfo;
+import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtorm.client.KeyUtil;
-
public class QueryScreen extends PagedSingleListScreen implements
ChangeListScreen {
public static QueryScreen forQuery(String query) {
@@ -49,13 +47,15 @@
}
@Override
- protected AsyncCallback<SingleListChangeInfo> loadCallback() {
- return new GerritCallback<SingleListChangeInfo>() {
- public final void onSuccess(final SingleListChangeInfo result) {
+ protected AsyncCallback<ChangeList> loadCallback() {
+ return new GerritCallback<ChangeList>() {
+ @Override
+ public final void onSuccess(ChangeList result) {
if (isAttached()) {
- if (result.getChanges().size() == 1 && isSingleQuery(query)) {
- final ChangeInfo c = result.getChanges().get(0);
- Gerrit.display(PageLinks.toChange(c), new ChangeScreen(c));
+ if (result.size() == 1 && isSingleQuery(query)) {
+ ChangeInfo c = result.get(0);
+ Change.Id id = c.legacy_id();
+ Gerrit.display(PageLinks.toChange(id), new ChangeScreen(id));
} else {
Gerrit.setQueryString(query);
display(result);
@@ -68,12 +68,12 @@
@Override
protected void loadPrev() {
- Util.LIST_SVC.allQueryPrev(query, pos, pageSize, loadCallback());
+ ChangeList.prev(query, pageSize, pos, loadCallback());
}
@Override
protected void loadNext() {
- Util.LIST_SVC.allQueryNext(query, pos, pageSize, loadCallback());
+ ChangeList.next(query, pageSize, pos, loadCallback());
}
private static boolean isSingleQuery(String query) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarCache.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarCache.java
deleted file mode 100644
index d7624a6..0000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarCache.java
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright (C) 2012 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.changes;
-
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.client.ui.NeedsSignInKeyCommand;
-import com.google.gerrit.common.data.ChangeDetail;
-import com.google.gerrit.common.data.ChangeInfo;
-import com.google.gerrit.common.data.ToggleStarRequest;
-import com.google.gerrit.reviewdb.client.Change;
-
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
-import com.google.gwt.event.logical.shared.ValueChangeEvent;
-import com.google.gwt.event.logical.shared.ValueChangeHandler;
-import com.google.gwt.event.shared.GwtEvent;
-import com.google.gwt.event.shared.HandlerManager;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.resources.client.ImageResource;
-import com.google.gwt.user.client.ui.Image;
-import com.google.gwtjsonrpc.common.VoidResult;
-
-public class StarCache implements HasValueChangeHandlers<Boolean> {
- public class KeyCommand extends NeedsSignInKeyCommand {
- public KeyCommand(int mask, char key, String help) {
- super(mask, key, help);
- }
-
- @Override
- public void onKeyPress(final KeyPressEvent event) {
- StarCache.this.toggleStar();
- }
- }
-
- ChangeCache cache;
-
- private HandlerManager manager = new HandlerManager(this);
-
- public StarCache(final Change.Id chg) {
- cache = ChangeCache.get(chg);
- }
-
- public boolean get() {
- ChangeDetail detail = cache.getChangeDetailCache().get();
- if (detail != null) {
- return detail.isStarred();
- }
- ChangeInfo info = cache.getChangeInfoCache().get();
- if (info != null) {
- return info.isStarred();
- }
- return false;
- }
-
- public void set(final boolean s) {
- if (Gerrit.isSignedIn() && s != get()) {
- final ToggleStarRequest req = new ToggleStarRequest();
- req.toggle(cache.getChangeId(), s);
-
- Util.LIST_SVC.toggleStars(req, new GerritCallback<VoidResult>() {
- public void onSuccess(final VoidResult result) {
- setStarred(s);
- fireEvent(new ValueChangeEvent<Boolean>(s){});
- }
- });
- }
- }
-
- private void setStarred(final boolean s) {
- ChangeDetail detail = cache.getChangeDetailCache().get();
- if (detail != null) {
- detail.setStarred(s);
- }
- ChangeInfo info = cache.getChangeInfoCache().get();
- if (info != null) {
- info.setStarred(s);
- }
- }
-
- public void toggleStar() {
- set(!get());
- }
-
- @SuppressWarnings("unchecked")
- public Image createStar() {
- final Image star = new Image(getResource());
- star.setVisible(Gerrit.isSignedIn());
-
- star.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- StarCache.this.toggleStar();
- }
- });
-
- @SuppressWarnings("rawtypes")
- ValueChangeHandler starUpdater = new ValueChangeHandler() {
- @Override
- public void onValueChange(ValueChangeEvent event) {
- star.setResource(StarCache.this.getResource());
- }
- };
-
- cache.getChangeDetailCache().addValueChangeHandler(starUpdater);
- cache.getChangeInfoCache().addValueChangeHandler(starUpdater);
-
- this.addValueChangeHandler(starUpdater);
-
- return star;
- }
-
- private ImageResource getResource() {
- return get() ? Gerrit.RESOURCES.starFilled() : Gerrit.RESOURCES.starOpen();
- }
-
- public void fireEvent(GwtEvent<?> event) {
- manager.fireEvent(event);
- }
-
- public HandlerRegistration addValueChangeHandler(
- ValueChangeHandler<Boolean> handler) {
- return manager.addHandler(ValueChangeEvent.getType(), handler);
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java
new file mode 100644
index 0000000..8b5aa1c
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java
@@ -0,0 +1,216 @@
+// Copyright (C) 2012 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.changes;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.common.data.ToggleStarRequest;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.shared.EventBus;
+import com.google.gwt.event.shared.SimpleEventBus;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwtexpui.globalkey.client.KeyCommand;
+import com.google.gwtjsonrpc.common.VoidResult;
+import com.google.web.bindery.event.shared.Event;
+import com.google.web.bindery.event.shared.HandlerRegistration;
+
+/** Supports the star icon displayed on changes and tracking the status. */
+public class StarredChanges {
+ private static final EventBus eventBus = new SimpleEventBus();
+ private static final Event.Type<ChangeStarHandler> TYPE =
+ new Event.Type<ChangeStarHandler>();
+
+ /** Handler that can receive notifications of a change's starred status. */
+ public static interface ChangeStarHandler {
+ public void onChangeStar(ChangeStarEvent event);
+ }
+
+ /** Event fired when a star changes status. The new status is reported. */
+ public static class ChangeStarEvent extends Event<ChangeStarHandler> {
+ private boolean starred;
+
+ public ChangeStarEvent(Change.Id source, boolean starred) {
+ setSource(source);
+ this.starred = starred;
+ }
+
+ public boolean isStarred() {
+ return starred;
+ }
+
+ @Override
+ public Type<ChangeStarHandler> getAssociatedType() {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(ChangeStarHandler handler) {
+ handler.onChangeStar(this);
+ }
+ }
+
+ /**
+ * Create a star icon for the given change, and current status. Returns null
+ * if the user is not signed in and cannot support starred changes.
+ */
+ public static Icon createIcon(Change.Id source, boolean starred) {
+ return Gerrit.isSignedIn() ? new Icon(source, starred) : null;
+ }
+
+ /** Make a key command that toggles the star for a change. */
+ public static KeyCommand newKeyCommand(final Icon icon) {
+ return new KeyCommand(0, 's', Util.C.changeTableStar()) {
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ icon.toggleStar();
+ }
+ };
+ }
+
+ /** Add a handler to listen for starred status to change. */
+ public static HandlerRegistration addHandler(
+ Change.Id source,
+ ChangeStarHandler handler) {
+ return eventBus.addHandlerToSource(TYPE, source, handler);
+ }
+
+ /**
+ * Broadcast the current starred value of a change to UI widgets. This does
+ * not RPC to the server and does not alter the starred status of a change.
+ */
+ public static void fireChangeStarEvent(Change.Id id, boolean starred) {
+ eventBus.fireEventFromSource(
+ new ChangeStarEvent(id, starred),
+ id);
+ }
+
+ /**
+ * Set the starred status of a change. This method broadcasts to all
+ * interested UI widgets and sends an RPC to the server to record the
+ * updated status.
+ */
+ public static void toggleStar(
+ final Change.Id changeId,
+ final boolean newValue) {
+ if (next == null) {
+ next = new ToggleStarRequest();
+ }
+ next.toggle(changeId, newValue);
+ fireChangeStarEvent(changeId, newValue);
+ if (!busy) {
+ start();
+ }
+ }
+
+ private static ToggleStarRequest next;
+ private static boolean busy;
+
+ private static void start() {
+ final ToggleStarRequest req = next;
+ next = null;
+ busy = true;
+
+ Util.LIST_SVC.toggleStars(req, new GerritCallback<VoidResult>() {
+ @Override
+ public void onSuccess(VoidResult result) {
+ if (next != null) {
+ start();
+ } else {
+ busy = false;
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ rollback(req);
+ if (next != null) {
+ rollback(next);
+ next = null;
+ }
+ busy = false;
+ super.onFailure(caught);
+ }
+ });
+ }
+
+ private static void rollback(ToggleStarRequest req) {
+ if (req.getAddSet() != null) {
+ for (Change.Id id : req.getAddSet()) {
+ fireChangeStarEvent(id, false);
+ }
+ }
+ if (req.getRemoveSet() != null) {
+ for (Change.Id id : req.getRemoveSet()) {
+ fireChangeStarEvent(id, true);
+ }
+ }
+ }
+
+ public static class Icon extends Image
+ implements ChangeStarHandler, ClickHandler {
+ private final Change.Id changeId;
+ private boolean starred;
+ private HandlerRegistration handler;
+
+ Icon(Change.Id changeId, boolean starred) {
+ super(resource(starred));
+ this.changeId = changeId;
+ this.starred = starred;
+ addClickHandler(this);
+ }
+
+ /**
+ * Toggles the state of the star, as if the user clicked on the image. This
+ * will broadcast the new star status to all interested UI widgets, and RPC
+ * to the server to store the changed value.
+ */
+ public void toggleStar() {
+ StarredChanges.toggleStar(changeId, !starred);
+ }
+
+ @Override
+ protected void onLoad() {
+ handler = StarredChanges.addHandler(changeId, this);
+ }
+
+ @Override
+ protected void onUnload() {
+ handler.removeHandler();
+ handler = null;
+ }
+
+ @Override
+ public void onChangeStar(ChangeStarEvent event) {
+ setResource(resource(event.isStarred()));
+ starred = event.isStarred();
+ }
+
+ @Override
+ public void onClick(ClickEvent event) {
+ toggleStar();
+ }
+
+ private static ImageResource resource(boolean starred) {
+ return starred ? Gerrit.RESOURCES.starFilled() : Gerrit.RESOURCES.starOpen();
+ }
+ }
+
+ private StarredChanges() {
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
index 87208c6..52c37c3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
@@ -375,6 +375,18 @@
width: 100%;
margin-top: 15px;
}
+.errorDialogText {
+ font-size: 15px;
+ font-family: verdana;
+}
+.errorDialog a,
+.errorDialog a:visited,
+.errorDialog a:hover {
+ color: white;
+ font-weight: bold;
+ font-size: 15px;
+ font-family: verdana;
+}
/** Screen **/
diff --git a/gerrit-httpd/.gitignore b/gerrit-httpd/.gitignore
index 194bedc..5bbeafd 100644
--- a/gerrit-httpd/.gitignore
+++ b/gerrit-httpd/.gitignore
@@ -2,4 +2,5 @@
/.classpath
/.project
/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-httpd.iml
\ No newline at end of file
diff --git a/gerrit-httpd/pom.xml b/gerrit-httpd/pom.xml
index a6374da..ceacb66 100644
--- a/gerrit-httpd/pom.xml
+++ b/gerrit-httpd/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.4-SNAPSHOT</version>
+ <version>2.5-SNAPSHOT</version>
</parent>
<artifactId>gerrit-httpd</artifactId>
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
index c299a71..9e69946 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
@@ -24,15 +24,21 @@
import com.google.gerrit.httpd.raw.StaticServlet;
import com.google.gerrit.httpd.raw.ToolServlet;
import com.google.gerrit.httpd.rpc.account.AccountCapabilitiesServlet;
+import com.google.gerrit.httpd.rpc.change.DeprecatedChangeQueryServlet;
+import com.google.gerrit.httpd.rpc.change.ListChangesServlet;
import com.google.gerrit.httpd.rpc.project.ListProjectsServlet;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gwtexpui.server.CacheControlFilter;
+import com.google.inject.Inject;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.internal.UniqueAnnotations;
import com.google.inject.servlet.ServletModule;
+import org.eclipse.jgit.lib.Config;
+
import java.io.IOException;
import javax.servlet.http.HttpServlet;
@@ -40,6 +46,21 @@
import javax.servlet.http.HttpServletResponse;
class UrlModule extends ServletModule {
+ static class UrlConfig {
+ private final boolean deprecatedQuery;
+
+ @Inject
+ UrlConfig(@GerritServerConfig Config cfg) {
+ deprecatedQuery = cfg.getBoolean("site", "enableDeprecatedQuery", true);
+ }
+ }
+
+ private final UrlConfig cfg;
+
+ UrlModule(UrlConfig cfg) {
+ this.cfg = cfg;
+ }
+
@Override
protected void configureServlets() {
filter("/*").through(Key.get(CacheControlFilter.class));
@@ -50,7 +71,6 @@
serve("/Gerrit/*").with(legacyGerritScreen());
serve("/cat/*").with(CatServlet.class);
serve("/logout").with(HttpLogoutServlet.class);
- serve("/query").with(ChangeQueryServlet.class);
serve("/signout").with(HttpLogoutServlet.class);
serve("/ssh_info").with(SshInfoServlet.class);
serve("/static/*").with(StaticServlet.class);
@@ -74,7 +94,12 @@
filter("/a/*").through(RequireIdentifiedUserFilter.class);
serveRegex("^/(?:a/)?accounts/self/capabilities$").with(AccountCapabilitiesServlet.class);
+ serveRegex("^/(?:a/)?changes/$").with(ListChangesServlet.class);
serveRegex("^/(?:a/)?projects/(.*)?$").with(ListProjectsServlet.class);
+
+ if (cfg.deprecatedQuery) {
+ serve("/query").with(DeprecatedChangeQueryServlet.class);
+ }
}
private Key<HttpServlet> notFound() {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
index 53dee84..0f223c9 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
@@ -52,14 +52,17 @@
public class WebModule extends FactoryModule {
private final AuthConfig authConfig;
+ private final UrlModule.UrlConfig urlConfig;
private final boolean wantSSL;
private final GitWebConfig gitWebConfig;
@Inject
WebModule(final AuthConfig authConfig,
+ final UrlModule.UrlConfig urlConfig,
@CanonicalWebUrl @Nullable final String canonicalUrl,
final Injector creatingInjector) {
this.authConfig = authConfig;
+ this.urlConfig = urlConfig;
this.wantSSL = canonicalUrl != null && canonicalUrl.startsWith("https:");
this.gitWebConfig =
@@ -117,7 +120,7 @@
throw new ProvisionException("Unsupported loginType: " + authConfig.getAuthType());
}
- install(new UrlModule());
+ install(new UrlModule(urlConfig));
install(new UiRpcModule());
install(new GerritRequestModule());
install(new GitOverHttpServlet.Module());
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ChangeQueryServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/change/DeprecatedChangeQueryServlet.java
similarity index 94%
rename from gerrit-httpd/src/main/java/com/google/gerrit/httpd/ChangeQueryServlet.java
rename to gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/change/DeprecatedChangeQueryServlet.java
index 9c4c598..cf443e7 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ChangeQueryServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/change/DeprecatedChangeQueryServlet.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.httpd;
+package com.google.gerrit.httpd.rpc.change;
import com.google.gerrit.server.query.change.QueryProcessor;
import com.google.gerrit.server.query.change.QueryProcessor.OutputFormat;
@@ -29,12 +29,12 @@
import javax.servlet.http.HttpServletResponse;
@Singleton
-public class ChangeQueryServlet extends HttpServlet {
+public class DeprecatedChangeQueryServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private final Provider<QueryProcessor> processor;
@Inject
- ChangeQueryServlet(Provider<QueryProcessor> processor) {
+ DeprecatedChangeQueryServlet(Provider<QueryProcessor> processor) {
this.processor = processor;
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/change/ListChangesServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/change/ListChangesServlet.java
new file mode 100644
index 0000000..91fc5b0
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/change/ListChangesServlet.java
@@ -0,0 +1,83 @@
+// Copyright (C) 2012 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.httpd.rpc.change;
+
+import com.google.gerrit.httpd.RestApiServlet;
+import com.google.gerrit.server.OutputFormat;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.query.change.ListChanges;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Singleton
+public class ListChangesServlet extends RestApiServlet {
+ private static final long serialVersionUID = 1L;
+ private static final Logger log = LoggerFactory.getLogger(ListChangesServlet.class);
+ private final ParameterParser paramParser;
+ private final Provider<ListChanges> factory;
+
+ @Inject
+ ListChangesServlet(ParameterParser paramParser, Provider<ListChanges> ls) {
+ this.paramParser = paramParser;
+ this.factory = ls;
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse res)
+ throws IOException {
+ ListChanges impl = factory.get();
+ if (acceptsJson(req)) {
+ impl.setFormat(OutputFormat.JSON_COMPACT);
+ }
+ if (paramParser.parse(impl, req, res)) {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ if (impl.getFormat().isJson()) {
+ buf.write(JSON_MAGIC);
+ }
+
+ Writer out = new BufferedWriter(new OutputStreamWriter(buf, "UTF-8"));
+ try {
+ impl.query(out);
+ } catch (QueryParseException e) {
+ res.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ sendText(req, res, e.getMessage());
+ return;
+ } catch (OrmException e) {
+ log.error("Error querying /changes/", e);
+ res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ out.flush();
+
+ res.setContentType(impl.getFormat().isJson() ? JSON_TYPE : "text/plain");
+ res.setCharacterEncoding("UTF-8");
+ send(req, res, buf.toByteArray());
+ }
+ }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
index 47a9395..ab266f3 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
@@ -134,7 +134,7 @@
detail.setCanEdit(control.getRefControl().canWrite());
if (detail.getChange().getStatus().isOpen()) {
- List<SubmitRecord> submitRecords = control.canSubmit(db, patch.getId());
+ List<SubmitRecord> submitRecords = control.canSubmit(db, patch);
for (SubmitRecord rec : submitRecords) {
if (rec.labels != null) {
for (SubmitRecord.Label lbl : rec.labels) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
index 638bfe3..183b5f6 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
@@ -83,7 +83,8 @@
final Change.Id changeId = patchSetId.getParentKey();
final ChangeControl control = changeControlFactory.validateFor(changeId);
change = control.getChange();
- patchSetInfo = infoFactory.get(db, patchSetId);
+ PatchSet patchSet = db.patchSets().get(patchSetId);
+ patchSetInfo = infoFactory.get(change, patchSet);
drafts = db.patchComments().draftByPatchSetAuthor(patchSetId, user.getAccountId()).toList();
aic.want(change.getOwner());
@@ -119,7 +120,7 @@
.toList();
boolean couldSubmit = false;
- List<SubmitRecord> submitRecords = control.canSubmit(db, patchSetId);
+ List<SubmitRecord> submitRecords = control.canSubmit(db, patchSet);
for (SubmitRecord rec : submitRecords) {
if (rec.status == SubmitRecord.Status.OK) {
couldSubmit = true;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java
index 073e2d7..a3a1c25 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java
@@ -18,11 +18,13 @@
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.ReplicationQueue;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
+import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -34,6 +36,7 @@
import java.io.IOException;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.Set;
class DeleteBranches extends Handler<Set<Branch.NameKey>> {
@@ -50,6 +53,7 @@
private final ReplicationQueue replication;
private final IdentifiedUser identifiedUser;
private final ChangeHooks hooks;
+ private final ReviewDb db;
private final Project.NameKey projectName;
private final Set<Branch.NameKey> toRemove;
@@ -60,6 +64,7 @@
final ReplicationQueue replication,
final IdentifiedUser identifiedUser,
final ChangeHooks hooks,
+ final ReviewDb db,
@Assisted Project.NameKey name, @Assisted Set<Branch.NameKey> toRemove) {
this.projectControlFactory = projectControlFactory;
@@ -67,6 +72,7 @@
this.replication = replication;
this.identifiedUser = identifiedUser;
this.hooks = hooks;
+ this.db = db;
this.projectName = name;
this.toRemove = toRemove;
@@ -74,17 +80,23 @@
@Override
public Set<Branch.NameKey> call() throws NoSuchProjectException,
- RepositoryNotFoundException {
+ RepositoryNotFoundException, OrmException {
final ProjectControl projectControl =
projectControlFactory.controlFor(projectName);
- for (Branch.NameKey k : toRemove) {
+ final Iterator<Branch.NameKey> branchIt = toRemove.iterator();
+ while (branchIt.hasNext()) {
+ final Branch.NameKey k = branchIt.next();
if (!projectName.equals(k.getParentKey())) {
throw new IllegalArgumentException("All keys must be from same project");
}
if (!projectControl.controlForRef(k).canDelete()) {
throw new IllegalStateException("Cannot delete " + k.getShortName());
}
+
+ if (db.changes().byBranchOpenAll(k).iterator().hasNext()) {
+ branchIt.remove();
+ }
}
final Set<Branch.NameKey> deleted = new HashSet<Branch.NameKey>();
diff --git a/gerrit-launcher/.gitignore b/gerrit-launcher/.gitignore
index 194bedc..980a6b1 100644
--- a/gerrit-launcher/.gitignore
+++ b/gerrit-launcher/.gitignore
@@ -2,4 +2,5 @@
/.classpath
/.project
/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-launcher.iml
\ No newline at end of file
diff --git a/gerrit-launcher/pom.xml b/gerrit-launcher/pom.xml
index 7d07652..e700351 100644
--- a/gerrit-launcher/pom.xml
+++ b/gerrit-launcher/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.4-SNAPSHOT</version>
+ <version>2.5-SNAPSHOT</version>
</parent>
<artifactId>gerrit-launcher</artifactId>
diff --git a/gerrit-main/.gitignore b/gerrit-main/.gitignore
index 194bedc..c847710 100644
--- a/gerrit-main/.gitignore
+++ b/gerrit-main/.gitignore
@@ -2,4 +2,5 @@
/.classpath
/.project
/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-main.iml
\ No newline at end of file
diff --git a/gerrit-main/pom.xml b/gerrit-main/pom.xml
index 9d8320c..bb2d763 100644
--- a/gerrit-main/pom.xml
+++ b/gerrit-main/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.4-SNAPSHOT</version>
+ <version>2.5-SNAPSHOT</version>
</parent>
<artifactId>gerrit-main</artifactId>
diff --git a/gerrit-openid/.gitignore b/gerrit-openid/.gitignore
index 194bedc..158faf1 100644
--- a/gerrit-openid/.gitignore
+++ b/gerrit-openid/.gitignore
@@ -2,4 +2,5 @@
/.classpath
/.project
/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-openid.iml
\ No newline at end of file
diff --git a/gerrit-openid/pom.xml b/gerrit-openid/pom.xml
index ed2625e..fa4ab95 100644
--- a/gerrit-openid/pom.xml
+++ b/gerrit-openid/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.4-SNAPSHOT</version>
+ <version>2.5-SNAPSHOT</version>
</parent>
<artifactId>gerrit-openid</artifactId>
diff --git a/gerrit-patch-commonsnet/.gitignore b/gerrit-patch-commonsnet/.gitignore
index 194bedc..121f8e90 100644
--- a/gerrit-patch-commonsnet/.gitignore
+++ b/gerrit-patch-commonsnet/.gitignore
@@ -2,4 +2,5 @@
/.classpath
/.project
/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-patch-commonsnet.iml
\ No newline at end of file
diff --git a/gerrit-patch-commonsnet/pom.xml b/gerrit-patch-commonsnet/pom.xml
index 75ee12e..f1a8b3e 100644
--- a/gerrit-patch-commonsnet/pom.xml
+++ b/gerrit-patch-commonsnet/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.4-SNAPSHOT</version>
+ <version>2.5-SNAPSHOT</version>
</parent>
<artifactId>gerrit-patch-commonsnet</artifactId>
diff --git a/gerrit-patch-jgit/.gitignore b/gerrit-patch-jgit/.gitignore
index 194bedc..7c4c433 100644
--- a/gerrit-patch-jgit/.gitignore
+++ b/gerrit-patch-jgit/.gitignore
@@ -2,4 +2,5 @@
/.classpath
/.project
/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-patch-jgit.iml
\ No newline at end of file
diff --git a/gerrit-patch-jgit/pom.xml b/gerrit-patch-jgit/pom.xml
index f8190f5..65223fb 100644
--- a/gerrit-patch-jgit/pom.xml
+++ b/gerrit-patch-jgit/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.4-SNAPSHOT</version>
+ <version>2.5-SNAPSHOT</version>
</parent>
<artifactId>gerrit-patch-jgit</artifactId>
diff --git a/gerrit-pgm/.gitignore b/gerrit-pgm/.gitignore
index 194bedc..dafe355 100644
--- a/gerrit-pgm/.gitignore
+++ b/gerrit-pgm/.gitignore
@@ -2,4 +2,5 @@
/.classpath
/.project
/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-pgm.iml
\ No newline at end of file
diff --git a/gerrit-pgm/pom.xml b/gerrit-pgm/pom.xml
index 1463d15..a015219 100644
--- a/gerrit-pgm/pom.xml
+++ b/gerrit-pgm/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.4-SNAPSHOT</version>
+ <version>2.5-SNAPSHOT</version>
</parent>
<artifactId>gerrit-pgm</artifactId>
diff --git a/gerrit-prettify/.gitignore b/gerrit-prettify/.gitignore
index 194bedc..8cf95ef 100644
--- a/gerrit-prettify/.gitignore
+++ b/gerrit-prettify/.gitignore
@@ -2,4 +2,5 @@
/.classpath
/.project
/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-prettify.iml
\ No newline at end of file
diff --git a/gerrit-prettify/pom.xml b/gerrit-prettify/pom.xml
index f5bd3d6..9354274 100644
--- a/gerrit-prettify/pom.xml
+++ b/gerrit-prettify/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.4-SNAPSHOT</version>
+ <version>2.5-SNAPSHOT</version>
</parent>
<artifactId>gerrit-prettify</artifactId>
diff --git a/gerrit-reviewdb/.gitignore b/gerrit-reviewdb/.gitignore
index 194bedc..812ddd0 100644
--- a/gerrit-reviewdb/.gitignore
+++ b/gerrit-reviewdb/.gitignore
@@ -2,4 +2,5 @@
/.classpath
/.project
/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-reviewdb.iml
\ No newline at end of file
diff --git a/gerrit-reviewdb/pom.xml b/gerrit-reviewdb/pom.xml
index 24d6a1b..f9fb49e 100644
--- a/gerrit-reviewdb/pom.xml
+++ b/gerrit-reviewdb/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.4-SNAPSHOT</version>
+ <version>2.5-SNAPSHOT</version>
</parent>
<artifactId>gerrit-reviewdb</artifactId>
diff --git a/gerrit-server/.gitignore b/gerrit-server/.gitignore
index 194bedc..9324efe 100644
--- a/gerrit-server/.gitignore
+++ b/gerrit-server/.gitignore
@@ -2,4 +2,5 @@
/.classpath
/.project
/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-server.iml
\ No newline at end of file
diff --git a/gerrit-server/pom.xml b/gerrit-server/pom.xml
index 58e43cf..f35608c 100644
--- a/gerrit-server/pom.xml
+++ b/gerrit-server/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.4-SNAPSHOT</version>
+ <version>2.5-SNAPSHOT</version>
</parent>
<artifactId>gerrit-server</artifactId>
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java b/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java
index 8ab9471..b9f476b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java
@@ -16,12 +16,16 @@
import static com.google.gerrit.rules.StoredValue.create;
+import com.google.common.collect.Maps;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.AnonymousUser;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
@@ -29,6 +33,7 @@
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.query.change.ChangeData;
import com.googlecode.prolog_cafe.lang.Prolog;
import com.googlecode.prolog_cafe.lang.SystemException;
@@ -37,21 +42,25 @@
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
+import java.util.Map;
+
public final class StoredValues {
public static final StoredValue<ReviewDb> REVIEW_DB = create(ReviewDb.class);
public static final StoredValue<Change> CHANGE = create(Change.class);
- public static final StoredValue<PatchSet.Id> PATCH_SET_ID = create(PatchSet.Id.class);
+ public static final StoredValue<ChangeData> CHANGE_DATA = create(ChangeData.class);
+ public static final StoredValue<PatchSet> PATCH_SET = create(PatchSet.class);
public static final StoredValue<ChangeControl> CHANGE_CONTROL = create(ChangeControl.class);
public static final StoredValue<PatchSetInfo> PATCH_SET_INFO = new StoredValue<PatchSetInfo>() {
@Override
public PatchSetInfo createValue(Prolog engine) {
- PatchSet.Id patchSetId = StoredValues.PATCH_SET_ID.get(engine);
+ Change change = StoredValues.CHANGE.get(engine);
+ PatchSet ps = StoredValues.PATCH_SET.get(engine);
PrologEnvironment env = (PrologEnvironment) engine.control;
PatchSetInfoFactory patchInfoFactory =
env.getInjector().getInstance(PatchSetInfoFactory.class);
try {
- return patchInfoFactory.get(REVIEW_DB.get(engine), patchSetId);
+ return patchInfoFactory.get(change, ps);
} catch (PatchSetInfoNotAvailableException e) {
throw new SystemException(e.getMessage());
}
@@ -102,6 +111,23 @@
}
};
+ public static final StoredValue<AnonymousUser> ANONYMOUS_USER =
+ new StoredValue<AnonymousUser>() {
+ @Override
+ protected AnonymousUser createValue(Prolog engine) {
+ PrologEnvironment env = (PrologEnvironment) engine.control;
+ return env.getInjector().getInstance(AnonymousUser.class);
+ }
+ };
+
+ public static final StoredValue<Map<Account.Id, IdentifiedUser>> USERS =
+ new StoredValue<Map<Account.Id, IdentifiedUser>>() {
+ @Override
+ protected Map<Account.Id, IdentifiedUser> createValue(Prolog engine) {
+ return Maps.newHashMap();
+ }
+ };
+
private StoredValues() {
}
}
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
index 6c216a8..e469c34 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
@@ -426,6 +426,56 @@
}
}
+ /**
+ * Unlink an authentication identity from an existing account.
+ *
+ * @param from account to unlink the identity from.
+ * @param who the identity to delete
+ * @return the result of unlinking the identity from the user.
+ * @throws AccountException the identity belongs to a different account, or it
+ * cannot be unlinked at this time.
+ */
+ public AuthResult unlink(final Account.Id from, AuthRequest who)
+ throws AccountException {
+ try {
+ final ReviewDb db = schema.open();
+ try {
+ who = realm.unlink(db, from, who);
+
+ final AccountExternalId.Key key = id(who);
+ AccountExternalId extId = db.accountExternalIds().get(key);
+ if (extId != null) {
+ if (!extId.getAccountId().equals(from)) {
+ throw new AccountException("Identity in use by another account");
+ }
+ db.accountExternalIds().delete(Collections.singleton(extId));
+
+ if (who.getEmailAddress() != null) {
+ final Account a = db.accounts().get(from);
+ if (a.getPreferredEmail() != null
+ && a.getPreferredEmail().equals(who.getEmailAddress())) {
+ a.setPreferredEmail(null);
+ db.accounts().update(Collections.singleton(a));
+ }
+ byEmailCache.evict(who.getEmailAddress());
+ byIdCache.evict(from);
+ }
+
+ } else {
+ throw new AccountException("Identity not found");
+ }
+
+ return new AuthResult(from, key, false);
+
+ } finally {
+ db.close();
+ }
+ } catch (OrmException e) {
+ throw new AccountException("Cannot unlink identity", e);
+ }
+ }
+
+
private static AccountExternalId.Key id(final AuthRequest who) {
return new AccountExternalId.Key(who.getExternalId());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
index 4f3392c..844e604 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
@@ -56,6 +56,11 @@
}
@Override
+ public AuthRequest unlink(ReviewDb db, Account.Id from, AuthRequest who) {
+ return who;
+ }
+
+ @Override
public void onCreateAccount(final AuthRequest who, final Account account) {
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java
index fc7c0be..2ebd0e5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java
@@ -29,6 +29,9 @@
public AuthRequest link(ReviewDb db, Account.Id to, AuthRequest who)
throws AccountException;
+ public AuthRequest unlink(ReviewDb db, Account.Id to, AuthRequest who)
+ throws AccountException;
+
public void onCreateAccount(AuthRequest who, Account account);
public GroupMembership groups(AccountState who);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
index e085d1e..910bf06 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
@@ -255,6 +255,11 @@
}
@Override
+ public AuthRequest unlink(ReviewDb db, Account.Id from, AuthRequest who) {
+ return who;
+ }
+
+ @Override
public void onCreateAccount(final AuthRequest who, final Account account) {
usernameCache.put(who.getLocalUser(), account.getId());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/Submit.java
index abd3582..6648c7b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/Submit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/Submit.java
@@ -80,7 +80,7 @@
throw new NoSuchChangeException(changeId);
}
- List<SubmitRecord> submitResult = control.canSubmit(db, patchSetId);
+ List<SubmitRecord> submitResult = control.canSubmit(db, patch);
if (submitResult.isEmpty()) {
throw new IllegalStateException(
"ChangeControl.canSubmit returned empty list");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
index e76249a..6068c50 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
@@ -16,27 +16,10 @@
import static org.eclipse.jgit.util.StringUtils.equalsIgnoreCase;
-import com.google.common.base.Function;
-import com.google.common.base.Predicates;
-import com.google.common.collect.Iterables;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroupName;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.OrmRuntimeException;
-import com.google.gwtorm.server.SchemaFactory;
-
import org.eclipse.jgit.lib.Config;
-import org.slf4j.Logger;
-
import java.lang.reflect.InvocationTargetException;
-import java.text.MessageFormat;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -303,83 +286,6 @@
}
}
- /**
- * Resolve groups from group names, via the database. Group names not found in
- * the database will be skipped.
- *
- * @param dbfactory database to resolve from.
- * @param groupNames group names to resolve.
- * @param log log for any warnings and errors.
- * @param groupNotFoundWarning formatted message to output to the log for each
- * group name which is not found in the database. <code>{0}</code> will
- * be replaced with the group name.
- * @return the actual groups resolved from the database. If no groups are
- * found, returns an empty {@code Set}, never {@code null}.
- */
- public static Set<AccountGroup.UUID> groupsFor(
- SchemaFactory<ReviewDb> dbfactory, String[] groupNames, Logger log,
- String groupNotFoundWarning) {
- final Set<AccountGroup.UUID> result = new HashSet<AccountGroup.UUID>();
- try {
- final ReviewDb db = dbfactory.open();
- try {
- List<AccountGroupName> groups = db.accountGroupNames().get(
- Iterables.transform(Arrays.asList(groupNames),
- new Function<String, AccountGroup.NameKey>() {
- @Override
- public AccountGroup.NameKey apply(String name) {
- return new AccountGroup.NameKey(name);
- }
- })).toList();
-
- Iterator<AccountGroup> ags = db.accountGroups().get(
- Iterables.transform(Iterables.filter(groups, Predicates.notNull()),
- new Function<AccountGroupName, AccountGroup.Id>() {
- @Override
- public AccountGroup.Id apply(AccountGroupName group) {
- return group.getId();
- }
- })).iterator();
-
- for (int i = 0; i < groupNames.length; i++) {
- if (groups.get(i) == null) {
- log.warn(MessageFormat.format(groupNotFoundWarning, groupNames[i]));
- continue;
- }
- AccountGroup ag = ags.next();
- if (ag == null) {
- log.warn(MessageFormat.format(groupNotFoundWarning, groupNames[i]));
- } else {
- result.add(ag.getGroupUUID());
- }
- }
- } finally {
- db.close();
- }
- } catch (OrmRuntimeException e) {
- log.error("Database error, cannot load groups", e);
- } catch (OrmException e) {
- log.error("Database error, cannot load groups", e);
- }
- return result;
- }
-
- /**
- * Resolve groups from group names, via the database. Group names not found in
- * the database will be skipped.
- *
- * @param dbfactory database to resolve from.
- * @param groupNames group names to resolve.
- * @param log log for any warnings and errors.
- * @return the actual groups resolved from the database. If no groups are
- * found, returns an empty {@code Set}, never {@code null}.
- */
- public static Set<AccountGroup.UUID> groupsFor(
- SchemaFactory<ReviewDb> dbfactory, String[] groupNames, Logger log) {
- return groupsFor(dbfactory, groupNames, log,
- "Group \"{0}\" not in database, skipping.");
- }
-
private static boolean match(final String a, final String... cases) {
for (final String b : cases) {
if (equalsIgnoreCase(a, b)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
index 944bbeb..f581adc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
@@ -35,6 +35,7 @@
import com.google.gerrit.server.changedetail.RestoreChange;
import com.google.gerrit.server.changedetail.Submit;
import com.google.gerrit.server.git.AsyncReceiveCommits;
+import com.google.gerrit.server.git.BanCommit;
import com.google.gerrit.server.git.CreateCodeReviewNotes;
import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.MetaDataUpdate;
@@ -117,5 +118,6 @@
factory(CreateProject.Factory.class);
factory(Submit.Factory.class);
factory(SuggestParentCandidates.Factory.class);
+ factory(BanCommit.Factory.class);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java
index 9992f18..c89f025 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java
@@ -15,8 +15,7 @@
package com.google.gerrit.server.config;
import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.server.SchemaFactory;
+import com.google.gerrit.server.account.GroupCache;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.Config;
@@ -25,9 +24,9 @@
public class GitReceivePackGroupsProvider extends GroupSetProvider {
@Inject
- public GitReceivePackGroupsProvider(@GerritServerConfig Config config,
- SchemaFactory<ReviewDb> db) {
- super(config, db, "receive", null, "allowGroup");
+ public GitReceivePackGroupsProvider(GroupCache gc,
+ @GerritServerConfig Config config) {
+ super(gc, config, "receive", null, "allowGroup");
// If no group was set, default to "registered users"
//
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java
index 76d8844..b5de742 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java
@@ -15,8 +15,7 @@
package com.google.gerrit.server.config;
import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.server.SchemaFactory;
+import com.google.gerrit.server.account.GroupCache;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.Config;
@@ -26,9 +25,9 @@
public class GitUploadPackGroupsProvider extends GroupSetProvider {
@Inject
- public GitUploadPackGroupsProvider(@GerritServerConfig Config config,
- SchemaFactory<ReviewDb> db) {
- super(config, db, "upload", null, "allowGroup");
+ public GitUploadPackGroupsProvider(GroupCache gc,
+ @GerritServerConfig Config config) {
+ super(gc, config, "upload", null, "allowGroup");
// If no group was set, default to "registered users" and "anonymous"
//
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java
index 15711af..3619cda 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java
@@ -14,12 +14,9 @@
package com.google.gerrit.server.config;
-import static com.google.gerrit.server.config.ConfigUtil.groupsFor;
-import static java.util.Collections.unmodifiableSet;
-
+import com.google.common.collect.ImmutableSet;
import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.server.SchemaFactory;
+import com.google.gerrit.server.account.GroupCache;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -37,10 +34,20 @@
protected Set<AccountGroup.UUID> groupIds;
@Inject
- protected GroupSetProvider(@GerritServerConfig Config config,
- SchemaFactory<ReviewDb> db, String section, String subsection, String name) {
+ protected GroupSetProvider(GroupCache groupCache,
+ @GerritServerConfig Config config, String section,
+ String subsection, String name) {
String[] groupNames = config.getStringList(section, subsection, name);
- groupIds = unmodifiableSet(groupsFor(db, groupNames, log));
+ ImmutableSet.Builder<AccountGroup.UUID> builder = ImmutableSet.builder();
+ for (String n : groupNames) {
+ AccountGroup g = groupCache.get(new AccountGroup.NameKey(n));
+ if (g != null) {
+ builder.add(g.getGroupUUID());
+ } else {
+ log.warn("Group \"{0}\" not in database, skipping.", n);
+ }
+ }
+ groupIds = builder.build();
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java
index b279086..7172b6f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java
@@ -14,8 +14,7 @@
package com.google.gerrit.server.config;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.server.SchemaFactory;
+import com.google.gerrit.server.account.GroupCache;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.Config;
@@ -33,8 +32,8 @@
*/
public class ProjectOwnerGroupsProvider extends GroupSetProvider {
@Inject
- public ProjectOwnerGroupsProvider(
- @GerritServerConfig final Config config, final SchemaFactory<ReviewDb> db) {
- super(config, db, "repository", "*", "ownerGroup");
+ public ProjectOwnerGroupsProvider(GroupCache gc,
+ @GerritServerConfig final Config config) {
+ super(gc, config, "repository", "*", "ownerGroup");
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java
new file mode 100644
index 0000000..c9c9753
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java
@@ -0,0 +1,273 @@
+// Copyright (C) 2012 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.git;
+
+import static com.google.gerrit.server.git.GitRepositoryManager.REF_REJECT_COMMITS;
+
+import com.google.gerrit.common.errors.PermissionDeniedException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.notes.Note;
+import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.notes.NoteMapMerger;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+import java.io.IOException;
+import java.util.List;
+
+public class BanCommit {
+
+ private static final int MAX_LOCK_FAILURE_CALLS = 10;
+ private static final int SLEEP_ON_LOCK_FAILURE_MS = 25;
+
+ public interface Factory {
+ BanCommit create();
+ }
+
+ private final Provider<CurrentUser> currentUser;
+ private final GitRepositoryManager repoManager;
+ private final AccountCache accountCache;
+ private final PersonIdent gerritIdent;
+
+ @Inject
+ BanCommit(final Provider<CurrentUser> currentUser,
+ final GitRepositoryManager repoManager, final AccountCache accountCache,
+ @GerritPersonIdent final PersonIdent gerritIdent) {
+ this.currentUser = currentUser;
+ this.repoManager = repoManager;
+ this.accountCache = accountCache;
+ this.gerritIdent = gerritIdent;
+ }
+
+ public BanCommitResult ban(final ProjectControl projectControl,
+ final List<ObjectId> commitsToBan, final String reason)
+ throws PermissionDeniedException, IOException,
+ IncompleteUserInfoException, InterruptedException, MergeException {
+ if (!projectControl.isOwner()) {
+ throw new PermissionDeniedException(
+ "No project owner: not permitted to ban commits");
+ }
+
+ final BanCommitResult result = new BanCommitResult();
+
+ final PersonIdent currentUserIdent = createPersonIdent();
+ final Repository repo =
+ repoManager.openRepository(projectControl.getProject().getNameKey());
+ try {
+ final RevWalk revWalk = new RevWalk(repo);
+ final ObjectInserter inserter = repo.newObjectInserter();
+ try {
+ NoteMap baseNoteMap = null;
+ RevCommit baseCommit = null;
+ final Ref notesBranch = repo.getRef(REF_REJECT_COMMITS);
+ if (notesBranch != null) {
+ baseCommit = revWalk.parseCommit(notesBranch.getObjectId());
+ baseNoteMap = NoteMap.read(revWalk.getObjectReader(), baseCommit);
+ }
+
+ final NoteMap ourNoteMap;
+ if (baseCommit != null) {
+ ourNoteMap = NoteMap.read(repo.newObjectReader(), baseCommit);
+ } else {
+ ourNoteMap = NoteMap.newEmptyMap();
+ }
+
+ for (final ObjectId commitToBan : commitsToBan) {
+ try {
+ revWalk.parseCommit(commitToBan);
+ } catch (MissingObjectException e) {
+ // ignore exception, also not existing commits can be banned
+ } catch (IncorrectObjectTypeException e) {
+ result.notACommit(commitToBan, e.getMessage());
+ continue;
+ }
+
+ final Note note = ourNoteMap.getNote(commitToBan);
+ if (note != null) {
+ result.commitAlreadyBanned(commitToBan);
+ continue;
+ }
+
+ final String noteContent = reason != null ? reason : "";
+ final ObjectId noteContentId =
+ inserter
+ .insert(Constants.OBJ_BLOB, noteContent.getBytes("UTF-8"));
+ ourNoteMap.set(commitToBan, noteContentId);
+ result.commitBanned(commitToBan);
+ }
+
+ if (result.getNewlyBannedCommits().isEmpty()) {
+ return result;
+ }
+
+ final ObjectId ourCommit =
+ commit(ourNoteMap, inserter, currentUserIdent, baseCommit, result,
+ reason);
+
+ updateRef(repo, revWalk, inserter, ourNoteMap, ourCommit, baseNoteMap,
+ baseCommit);
+ } finally {
+ revWalk.release();
+ inserter.release();
+ }
+ } finally {
+ repo.close();
+ }
+
+ return result;
+ }
+
+ private PersonIdent createPersonIdent() throws IncompleteUserInfoException {
+ final String userName = currentUser.get().getUserName();
+ final Account account = accountCache.getByUsername(userName).getAccount();
+ if (account.getFullName() == null) {
+ throw new IncompleteUserInfoException(userName, "full name");
+ }
+ if (account.getPreferredEmail() == null) {
+ throw new IncompleteUserInfoException(userName, "preferred email");
+ }
+ return new PersonIdent(account.getFullName(), account.getPreferredEmail());
+ }
+
+ private static ObjectId commit(final NoteMap noteMap,
+ final ObjectInserter inserter, final PersonIdent personIdent,
+ final ObjectId baseCommit, final BanCommitResult result,
+ final String reason) throws IOException {
+ final String commitMsg =
+ buildCommitMessage(result.getNewlyBannedCommits(), reason);
+ if (baseCommit != null) {
+ return createCommit(noteMap, inserter, personIdent, commitMsg, baseCommit);
+ } else {
+ return createCommit(noteMap, inserter, personIdent, commitMsg);
+ }
+ }
+
+ private static ObjectId createCommit(final NoteMap noteMap,
+ final ObjectInserter inserter, final PersonIdent personIdent,
+ final String message, final ObjectId... parents) throws IOException {
+ final CommitBuilder b = new CommitBuilder();
+ b.setTreeId(noteMap.writeTree(inserter));
+ b.setAuthor(personIdent);
+ b.setCommitter(personIdent);
+ if (parents.length > 0) {
+ b.setParentIds(parents);
+ }
+ b.setMessage(message);
+ final ObjectId commitId = inserter.insert(b);
+ inserter.flush();
+ return commitId;
+ }
+
+ private static String buildCommitMessage(final List<ObjectId> bannedCommits,
+ final String reason) {
+ final StringBuilder commitMsg = new StringBuilder();
+ commitMsg.append("Banning ");
+ commitMsg.append(bannedCommits.size());
+ commitMsg.append(" ");
+ commitMsg.append(bannedCommits.size() == 1 ? "commit" : "commits");
+ commitMsg.append("\n\n");
+ if (reason != null) {
+ commitMsg.append("Reason: ");
+ commitMsg.append(reason);
+ commitMsg.append("\n\n");
+ }
+ commitMsg.append("The following commits are banned:\n");
+ final StringBuilder commitList = new StringBuilder();
+ for (final ObjectId c : bannedCommits) {
+ if (commitList.length() > 0) {
+ commitList.append(",\n");
+ }
+ commitList.append(c.getName());
+ }
+ commitMsg.append(commitList);
+ return commitMsg.toString();
+ }
+
+ public void updateRef(final Repository repo, final RevWalk revWalk,
+ final ObjectInserter inserter, final NoteMap ourNoteMap,
+ final ObjectId oursCommit, final NoteMap baseNoteMap,
+ final ObjectId baseCommit) throws IOException, InterruptedException,
+ MissingObjectException, IncorrectObjectTypeException,
+ CorruptObjectException, MergeException {
+
+ int remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
+ RefUpdate refUpdate = createRefUpdate(repo, oursCommit, baseCommit);
+
+ for (;;) {
+ final Result result = refUpdate.update();
+
+ if (result == Result.LOCK_FAILURE) {
+ if (--remainingLockFailureCalls > 0) {
+ Thread.sleep(SLEEP_ON_LOCK_FAILURE_MS);
+ } else {
+ throw new MergeException("Failed to lock the ref: "
+ + REF_REJECT_COMMITS);
+ }
+
+ } else if (result == Result.REJECTED) {
+ final RevCommit theirsCommit =
+ revWalk.parseCommit(refUpdate.getOldObjectId());
+ final NoteMap theirNoteMap =
+ NoteMap.read(revWalk.getObjectReader(), theirsCommit);
+ final NoteMapMerger merger = new NoteMapMerger(repo);
+ final NoteMap merged =
+ merger.merge(baseNoteMap, ourNoteMap, theirNoteMap);
+ final ObjectId mergeCommit =
+ createCommit(merged, inserter, gerritIdent,
+ "Merged note commits\n", oursCommit, theirsCommit);
+ refUpdate = createRefUpdate(repo, mergeCommit, theirsCommit);
+ remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
+
+ } else if (result == Result.IO_FAILURE) {
+ throw new IOException(
+ "Couldn't create commit reject notes because of IO_FAILURE");
+ } else {
+ break;
+ }
+ }
+ }
+
+ private static RefUpdate createRefUpdate(final Repository repo,
+ final ObjectId newObjectId, final ObjectId expectedOldObjectId)
+ throws IOException {
+ RefUpdate refUpdate = repo.updateRef(REF_REJECT_COMMITS);
+ refUpdate.setNewObjectId(newObjectId);
+ if (expectedOldObjectId == null) {
+ refUpdate.setExpectedOldObjectId(ObjectId.zeroId());
+ } else {
+ refUpdate.setExpectedOldObjectId(expectedOldObjectId);
+ }
+ return refUpdate;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommitResult.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommitResult.java
new file mode 100644
index 0000000..1b48455
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommitResult.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2012 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.git;
+
+import org.eclipse.jgit.lib.ObjectId;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class BanCommitResult {
+
+ private final List<ObjectId> newlyBannedCommits = new LinkedList<ObjectId>();
+ private final List<ObjectId> alreadyBannedCommits = new LinkedList<ObjectId>();
+ private final List<ObjectId> ignoredObjectIds = new LinkedList<ObjectId>();
+
+ public BanCommitResult() {
+ }
+
+ public void commitBanned(final ObjectId commitId) {
+ newlyBannedCommits.add(commitId);
+ }
+
+ public void commitAlreadyBanned(final ObjectId commitId) {
+ alreadyBannedCommits.add(commitId);
+ }
+
+ public void notACommit(final ObjectId id, final String message) {
+ ignoredObjectIds.add(id);
+ }
+
+ public List<ObjectId> getNewlyBannedCommits() {
+ return newlyBannedCommits;
+ }
+
+ public List<ObjectId> getAlreadyBannedCommits() {
+ return alreadyBannedCommits;
+ }
+
+ public List<ObjectId> getIgnoredObjectIds() {
+ return ignoredObjectIds;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java
new file mode 100644
index 0000000..204d777
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2012 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.git;
+
+public class IncompleteUserInfoException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public IncompleteUserInfoException(final String userName, final String missingInfo) {
+ super("For the user \"" + userName + "\" " + missingInfo + " is not set.");
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeException.java
index 44becb5..1997c13 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeException.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeException.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.git;
/** Indicates the current branch's queue cannot be processed at this time. */
-class MergeException extends Exception {
+public class MergeException extends Exception {
private static final long serialVersionUID = 1L;
MergeException(final String msg) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java
index 5c6cd25..0868749 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java
@@ -343,10 +343,6 @@
if (config.isMirror()) {
for (final Ref ref : remote.values()) {
- if (noPerms && GitRepositoryManager.REF_CONFIG.equals(ref.getName())) {
- continue;
- }
-
if (!Constants.HEAD.equals(ref.getName())) {
final RefSpec spec = matchDst(ref.getName());
if (spec != null && !local.containsKey(spec.getSource())) {
@@ -360,16 +356,13 @@
} else {
for (final String src : delta) {
- if (noPerms && GitRepositoryManager.REF_CONFIG.equals(src)) {
- continue;
- }
-
final RefSpec spec = matchSrc(src);
if (spec != null) {
// If the ref still exists locally, send it, otherwise delete it.
//
Ref srcRef = local.get(src);
- if (srcRef != null) {
+ if (srcRef != null &&
+ !(noPerms && GitRepositoryManager.REF_CONFIG.equals(src))) {
send(cmds, spec, srcRef);
} else if (config.isMirror()) {
delete(cmds, spec);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java
index 5cf5f7a..5bff0ad 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java
@@ -14,13 +14,15 @@
package com.google.gerrit.server.git;
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.ReplicationUser;
+import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.account.ListGroupMembership;
-import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.FactoryModule;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.project.NoSuchProjectException;
@@ -99,11 +101,12 @@
private final SchemaFactory<ReviewDb> database;
private final ReplicationUser.Factory replicationUserFactory;
private final GitRepositoryManager gitRepositoryManager;
+ private final GroupCache groupCache;
@Inject
PushReplication(final Injector i, final WorkQueue wq, final SitePaths site,
final ReplicationUser.Factory ruf, final SchemaFactory<ReviewDb> db,
- final GitRepositoryManager grm)
+ final GitRepositoryManager grm, GroupCache gc)
throws ConfigInvalidException, IOException {
injector = i;
workQueue = wq;
@@ -111,6 +114,7 @@
replicationUserFactory = ruf;
gitRepositoryManager = grm;
configs = allConfigs(site);
+ groupCache = gc;
}
@Override
@@ -195,7 +199,6 @@
}
}
-
if (c.getPushRefSpecs().isEmpty()) {
RefSpec spec = new RefSpec();
spec = spec.setSourceDestination("refs/*", "refs/*");
@@ -204,7 +207,7 @@
}
r.add(new ReplicationConfig(injector, workQueue, c, cfg, database,
- replicationUserFactory, gitRepositoryManager));
+ replicationUserFactory, gitRepositoryManager, groupCache));
}
return Collections.unmodifiableList(r);
}
@@ -392,7 +395,8 @@
ReplicationConfig(final Injector injector, final WorkQueue workQueue,
final RemoteConfig rc, final Config cfg, SchemaFactory<ReviewDb> db,
final ReplicationUser.Factory replicationUserFactory,
- final GitRepositoryManager gitRepositoryManager) {
+ final GitRepositoryManager gitRepositoryManager,
+ GroupCache groupCache) {
remote = rc;
delay = Math.max(0, getInt(rc, cfg, "replicationdelay", 15));
@@ -406,8 +410,16 @@
cfg.getStringList("remote", rc.getName(), "authGroup");
final GroupMembership authGroups;
if (authGroupNames.length > 0) {
- authGroups = new ListGroupMembership(ConfigUtil.groupsFor(db, authGroupNames, //
- log, "Group \"{0}\" not in database, removing from authGroup"));
+ ImmutableSet.Builder<AccountGroup.UUID> builder = ImmutableSet.builder();
+ for (String name : authGroupNames) {
+ AccountGroup g = groupCache.get(new AccountGroup.NameKey(name));
+ if (g != null) {
+ builder.add(g.getGroupUUID());
+ } else {
+ log.warn("Group \"{0}\" not in database, removing from authGroup", name);
+ }
+ }
+ authGroups = new ListGroupMembership(builder.build());
} else {
authGroups = ReplicationUser.EVERYTHING_VISIBLE;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index 290b162..83877d0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -16,6 +16,8 @@
import static com.google.gerrit.server.git.MultiProgressMonitor.UNKNOWN;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.ListMultimap;
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.ApprovalType;
@@ -112,6 +114,34 @@
private static final FooterKey TESTED_BY = new FooterKey("Tested-by");
private static final FooterKey CHANGE_ID = new FooterKey("Change-Id");
+ private static final String COMMAND_REJECTION_MESSAGE_FOOTER =
+ "Please read the documentation and contact an administrator\n"
+ + "if you feel the configuration is incorrect";
+
+ private enum Error {
+ CONFIG_UPDATE("You are not allowed to perform this operation.\n"
+ + "Configuration changes can only be pushed by project owners\n"
+ + "who also have 'Push' rights on " + GitRepositoryManager.REF_CONFIG),
+ UPDATE("You are not allowed to perform this operation.\n"
+ + "To push into this reference you need 'Push' rights."),
+ DELETE("You need 'Push' rights with the 'Force Push'\n"
+ + "flag set to delete references."),
+ CODE_REVIEW("You need 'Push' rights to upload code review requests.\n"
+ + "Verify that you are pushing to the right branch."),
+ CREATE("You are not allowed to perform this operation.\n"
+ + "To create new references you need 'Create Reference' rights.");
+
+ private final String value;
+
+ Error(String value) {
+ this.value = value;
+ }
+
+ public String get() {
+ return value;
+ }
+ }
+
interface Factory {
ReceiveCommits create(ProjectControl projectControl, Repository repository);
}
@@ -215,6 +245,7 @@
private final SubmoduleOp.Factory subOpFactory;
private final List<Message> messages = new ArrayList<Message>();
+ private ListMultimap<Error, String> errors = LinkedListMultimap.create();
private Task newProgress;
private Task replaceProgress;
private Task closeProgress;
@@ -438,6 +469,14 @@
doReplaces();
replaceProgress.end();
+ if (!errors.isEmpty()) {
+ for (Error error : errors.keySet()) {
+ rp.sendMessage(buildError(error, errors.get(error)));
+ }
+ rp.sendMessage(String.format("User: %s", displayName(currentUser)));
+ rp.sendMessage(COMMAND_REJECTION_MESSAGE_FOOTER);
+ }
+
for (final ReceiveCommand c : commands) {
if (c.getResult() == Result.OK) {
switch (c.getType()) {
@@ -502,6 +541,30 @@
}
}
+ private String buildError(Error error, List<String> branches) {
+ StringBuilder sb = new StringBuilder();
+ if (branches.size() == 1) {
+ sb.append("Branch ").append(branches.get(0)).append(":\n");
+ sb.append(error.get());
+ return sb.toString();
+ }
+ sb.append("Branches");
+ String delim = " ";
+ for (String branch : branches) {
+ sb.append(delim).append(branch);
+ delim = ", ";
+ }
+ return sb.append(":\n").append(error.get()).toString();
+ }
+
+ private static String displayName(IdentifiedUser user) {
+ String displayName = user.getUserName();
+ if (displayName == null) {
+ displayName = user.getAccount().getPreferredEmail();
+ }
+ return displayName;
+ }
+
private Account.Id toAccountId(final String nameOrEmail) throws OrmException,
NoSuchAccountException {
final Account a = accountResolver.findByNameOrEmail(nameOrEmail);
@@ -630,6 +693,7 @@
validateNewCommits(ctl, cmd);
cmd.execute(rp);
} else {
+ errors.put(Error.CREATE, ctl.getRefName());
reject(cmd, "can not create new references");
}
}
@@ -644,6 +708,11 @@
validateNewCommits(ctl, cmd);
cmd.execute(rp);
} else {
+ if (GitRepositoryManager.REF_CONFIG.equals(ctl.getRefName())) {
+ errors.put(Error.CONFIG_UPDATE, GitRepositoryManager.REF_CONFIG);
+ } else {
+ errors.put(Error.UPDATE, ctl.getRefName());
+ }
reject(cmd, "can not update the reference as a fast forward");
}
}
@@ -672,7 +741,12 @@
if (ctl.canDelete()) {
cmd.execute(rp);
} else {
- reject(cmd, "can not delete references");
+ if (GitRepositoryManager.REF_CONFIG.equals(ctl.getRefName())) {
+ reject(cmd, "cannot delete project configuration");
+ } else {
+ errors.put(Error.DELETE, ctl.getRefName());
+ reject(cmd, "can not delete references");
+ }
}
}
@@ -769,7 +843,8 @@
destBranchName.substring(0, split));
destBranchCtl = projectControl.controlForRef(destBranch);
if (!destBranchCtl.canUpload()) {
- reject(cmd, "can not upload a change to this reference");
+ errors.put(Error.CODE_REVIEW, cmd.getRefName());
+ reject(cmd, "can not upload review");
return;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
index f59dcc3..2619e00 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
@@ -18,7 +18,6 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetInfo;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.client.UserIdentity;
import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -29,6 +28,7 @@
import com.google.inject.Singleton;
import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
@@ -68,31 +68,39 @@
}
public PatchSetInfo get(ReviewDb db, PatchSet.Id patchSetId)
- throws PatchSetInfoNotAvailableException {
- Repository repo = null;
+ throws PatchSetInfoNotAvailableException {
try {
final PatchSet patchSet = db.patchSets().get(patchSetId);
final Change change = db.changes().get(patchSet.getId().getParentKey());
- final Project.NameKey projectKey = change.getProject();
- repo = repoManager.openRepository(projectKey);
+ return get(change, patchSet);
+ } catch (OrmException e) {
+ throw new PatchSetInfoNotAvailableException(e);
+ }
+ }
+
+ public PatchSetInfo get(Change change, PatchSet patchSet)
+ throws PatchSetInfoNotAvailableException {
+ Repository repo;
+ try {
+ repo = repoManager.openRepository(change.getProject());
+ } catch (RepositoryNotFoundException e) {
+ throw new PatchSetInfoNotAvailableException(e);
+ }
+ try {
final RevWalk rw = new RevWalk(repo);
try {
final RevCommit src =
rw.parseCommit(ObjectId.fromString(patchSet.getRevision().get()));
- PatchSetInfo info = get(src, patchSetId);
+ PatchSetInfo info = get(src, patchSet.getId());
info.setParents(toParentInfos(src.getParents(), rw));
return info;
} finally {
rw.release();
}
- } catch (OrmException e) {
- throw new PatchSetInfoNotAvailableException(e);
} catch (IOException e) {
throw new PatchSetInfoNotAvailableException(e);
} finally {
- if (repo != null) {
- repo.close();
- }
+ repo.close();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
index 53301b9..7652bed 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
@@ -26,10 +26,11 @@
import com.google.gerrit.rules.StoredValues;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.util.Providers;
import com.googlecode.prolog_cafe.compiler.CompileException;
import com.googlecode.prolog_cafe.lang.IntegerTerm;
@@ -49,6 +50,8 @@
import java.util.List;
import java.util.Set;
+import javax.annotation.Nullable;
+
/** Access control management for a user accessing a single change. */
public class ChangeControl {
@@ -161,7 +164,7 @@
/** Can this user see this change? */
public boolean isVisible(ReviewDb db) throws OrmException {
- if (change.getStatus() == Change.Status.DRAFT && !isDraftVisible(db)) {
+ if (change.getStatus() == Change.Status.DRAFT && !isDraftVisible(db, null)) {
return false;
}
return isRefVisible();
@@ -174,7 +177,7 @@
/** Can this user see the given patchset? */
public boolean isPatchVisible(PatchSet ps, ReviewDb db) throws OrmException {
- if (ps.isDraft() && !isDraftVisible(db)) {
+ if (ps.isDraft() && !isDraftVisible(db, null)) {
return false;
}
return isVisible(db);
@@ -235,10 +238,20 @@
/** Is this user a reviewer for the change? */
public boolean isReviewer(ReviewDb db) throws OrmException {
+ return isReviewer(db, null);
+ }
+
+ /** Is this user a reviewer for the change? */
+ public boolean isReviewer(ReviewDb db, @Nullable ChangeData cd)
+ throws OrmException {
if (getCurrentUser() instanceof IdentifiedUser) {
final IdentifiedUser user = (IdentifiedUser) getCurrentUser();
- ResultSet<PatchSetApproval> results =
- db.patchSetApprovals().byChange(change.getId());
+ Iterable<PatchSetApproval> results;
+ if (cd != null) {
+ results = cd.currentApprovals(Providers.of(db));
+ } else {
+ results = db.patchSetApprovals().byChange(change.getId());
+ }
for (PatchSetApproval approval : results) {
if (user.getAccountId().equals(approval.getAccountId())) {
return true;
@@ -278,34 +291,39 @@
return false;
}
- public List<SubmitRecord> canSubmit(ReviewDb db, PatchSet.Id patchSetId) {
+ public List<SubmitRecord> canSubmit(ReviewDb db, PatchSet patchSet) {
+ return canSubmit(db, patchSet, null, false);
+ }
+
+ public List<SubmitRecord> canSubmit(ReviewDb db, PatchSet patchSet,
+ @Nullable ChangeData cd, boolean fastEvalLabels) {
if (change.getStatus().isClosed()) {
SubmitRecord rec = new SubmitRecord();
rec.status = SubmitRecord.Status.CLOSED;
return Collections.singletonList(rec);
}
- if (!patchSetId.equals(change.currentPatchSetId())) {
- return ruleError("Patch set " + patchSetId + " is not current");
+ if (!patchSet.getId().equals(change.currentPatchSetId())) {
+ return ruleError("Patch set " + patchSet.getPatchSetId() + " is not current");
}
try {
- if (change.getStatus() == Change.Status.DRAFT){
- if (!isVisible(db)) {
- return ruleError("Patch set " + patchSetId + " not found");
+ if (change.getStatus() == Change.Status.DRAFT) {
+ if (!isDraftVisible(db, cd)) {
+ return ruleError("Patch set " + patchSet.getPatchSetId() + " not found");
} else {
return ruleError("Cannot submit draft changes");
}
}
- if (isDraftPatchSet(patchSetId, db)) {
- if (!isVisible(db)) {
- return ruleError("Patch set " + patchSetId + " not found");
+ if (patchSet.isDraft()) {
+ if (!isDraftVisible(db, cd)) {
+ return ruleError("Patch set " + patchSet.getPatchSetId() + " not found");
} else {
return ruleError("Cannot submit draft patch sets");
}
}
} catch (OrmException err) {
- return logRuleError("Cannot read patch set " + patchSetId, err);
+ return logRuleError("Cannot read patch set " + patchSet.getId(), err);
}
List<Term> results = new ArrayList<Term>();
@@ -323,7 +341,8 @@
try {
env.set(StoredValues.REVIEW_DB, db);
env.set(StoredValues.CHANGE, change);
- env.set(StoredValues.PATCH_SET_ID, patchSetId);
+ env.set(StoredValues.CHANGE_DATA, cd);
+ env.set(StoredValues.PATCH_SET, patchSet);
env.set(StoredValues.CHANGE_CONTROL, this);
submitRule = env.once(
@@ -334,6 +353,10 @@
+ getProject().getName());
}
+ if (fastEvalLabels) {
+ env.once("gerrit", "assume_range_from_label");
+ }
+
try {
for (Term[] template : env.all(
"gerrit", "can_submit",
@@ -372,6 +395,10 @@
parentEnv.once("gerrit", "locate_submit_filter", new VariableTerm());
if (filterRule != null) {
try {
+ if (fastEvalLabels) {
+ env.once("gerrit", "assume_range_from_label");
+ }
+
Term resultsTerm = toListTerm(results);
results.clear();
Term[] template = parentEnv.once(
@@ -515,16 +542,9 @@
}
}
- private boolean isDraftVisible(ReviewDb db) throws OrmException {
- return isOwner() || isReviewer(db);
- }
-
- private boolean isDraftPatchSet(PatchSet.Id id, ReviewDb db) throws OrmException {
- PatchSet ps = db.patchSets().get(id);
- if (ps == null) {
- throw new OrmException("Patch set " + id + " not found");
- }
- return ps.isDraft();
+ private boolean isDraftVisible(ReviewDb db, ChangeData cd)
+ throws OrmException {
+ return isOwner() || isReviewer(db, cd);
}
private static boolean isUser(Term who) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index 7a85b6f..db3470e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -14,11 +14,14 @@
package com.google.gerrit.server.query.change;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSet.Id;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.TrackingId;
@@ -28,7 +31,9 @@
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListEntry;
+import com.google.gerrit.server.project.ChangeControl;
import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
import com.google.inject.Provider;
import org.eclipse.jgit.lib.ObjectId;
@@ -46,9 +51,61 @@
import java.util.Map;
public class ChangeData {
+ public static void ensureChangeLoaded(
+ Provider<ReviewDb> db, List<ChangeData> changes) throws OrmException {
+ Map<Change.Id, ChangeData> missing = Maps.newHashMap();
+ for (ChangeData cd : changes) {
+ if (cd.change == null) {
+ missing.put(cd.getId(), cd);
+ }
+ }
+ if (!missing.isEmpty()) {
+ for (Change change : db.get().changes().get(missing.keySet())) {
+ missing.get(change.getId()).change = change;
+ }
+ }
+ }
+
+ public static void ensureCurrentPatchSetLoaded(
+ Provider<ReviewDb> db, List<ChangeData> changes) throws OrmException {
+ Map<PatchSet.Id, ChangeData> missing = Maps.newHashMap();
+ for (ChangeData cd : changes) {
+ if (cd.currentPatchSet == null && cd.patches == null) {
+ missing.put(cd.change(db).currentPatchSetId(), cd);
+ }
+ }
+ if (!missing.isEmpty()) {
+ for (PatchSet ps : db.get().patchSets().get(missing.keySet())) {
+ ChangeData cd = missing.get(ps.getId());
+ cd.currentPatchSet = ps;
+ cd.patches = Lists.newArrayList(ps);
+ }
+ }
+ }
+
+ public static void ensureCurrentApprovalsLoaded(
+ Provider<ReviewDb> db, List<ChangeData> changes) throws OrmException {
+ List<ResultSet<PatchSetApproval>> pending = Lists.newArrayList();
+ for (ChangeData cd : changes) {
+ if (cd.currentApprovals == null && cd.approvals == null) {
+ pending.add(db.get().patchSetApprovals()
+ .byPatchSet(cd.change(db).currentPatchSetId()));
+ }
+ }
+ if (!pending.isEmpty()) {
+ int idx = 0;
+ for (ChangeData cd : changes) {
+ if (cd.currentApprovals == null && cd.approvals == null) {
+ cd.currentApprovals = pending.get(idx++).toList();
+ }
+ }
+ }
+ }
+
private final Change.Id legacyId;
private Change change;
private String commitMessage;
+ private PatchSet currentPatchSet;
private Collection<PatchSet> patches;
private Collection<PatchSetApproval> approvals;
private Map<PatchSet.Id,Collection<PatchSetApproval>> approvalsMap;
@@ -57,6 +114,7 @@
private Collection<PatchLineComment> comments;
private Collection<TrackingId> trackingIds;
private CurrentUser visibleTo;
+ private ChangeControl changeControl;
private List<ChangeMessage> messages;
public ChangeData(final Change.Id id) {
@@ -125,8 +183,13 @@
return visibleTo == user;
}
- void cacheVisibleTo(CurrentUser user) {
- visibleTo = user;
+ ChangeControl changeControl() {
+ return changeControl;
+ }
+
+ void cacheVisibleTo(ChangeControl ctl) {
+ visibleTo = ctl.getCurrentUser();
+ changeControl = ctl;
}
public Change change(Provider<ReviewDb> db) throws OrmException {
@@ -137,16 +200,19 @@
}
public PatchSet currentPatchSet(Provider<ReviewDb> db) throws OrmException {
- Change c = change(db);
- if (c == null) {
- return null;
- }
- for (PatchSet p : patches(db)) {
- if (p.getId().equals(c.currentPatchSetId())) {
- return p;
+ if (currentPatchSet == null) {
+ Change c = change(db);
+ if (c == null) {
+ return null;
+ }
+ for (PatchSet p : patches(db)) {
+ if (p.getId().equals(c.currentPatchSetId())) {
+ currentPatchSet = p;
+ return p;
+ }
}
}
- return null;
+ return currentPatchSet;
}
public Collection<PatchSetApproval> currentApprovals(Provider<ReviewDb> db)
@@ -155,24 +221,21 @@
Change c = change(db);
if (c == null) {
currentApprovals = Collections.emptyList();
+ } else if (approvals != null) {
+ Map<Id, Collection<PatchSetApproval>> map = approvalsMap(db);
+ currentApprovals = map.get(c.currentPatchSetId());
+ if (currentApprovals == null) {
+ currentApprovals = Collections.emptyList();
+ map.put(c.currentPatchSetId(), currentApprovals);
+ }
} else {
- currentApprovals = approvalsFor(db, c.currentPatchSetId());
+ currentApprovals = db.get().patchSetApprovals()
+ .byPatchSet(c.currentPatchSetId()).toList();
}
}
return currentApprovals;
}
- public Collection<PatchSetApproval> approvalsFor(Provider<ReviewDb> db,
- PatchSet.Id psId) throws OrmException {
- List<PatchSetApproval> r = new ArrayList<PatchSetApproval>();
- for (PatchSetApproval p : approvals(db)) {
- if (p.getPatchSetId().equals(psId)) {
- r.add(p);
- }
- }
- return r;
- }
-
public String commitMessage(GitRepositoryManager repoManager,
Provider<ReviewDb> db) throws IOException, OrmException {
if (commitMessage == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java
index 413e6c4..b73465a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java
@@ -55,15 +55,18 @@
}
try {
Change c = cd.change(db);
- if (c != null && changeControl.controlFor(c, user).isVisible(db.get())) {
- cd.cacheVisibleTo(user);
- return true;
- } else {
+ if (c == null) {
return false;
}
+
+ ChangeControl cc = changeControl.controlFor(c, user);
+ if (cc.isVisible(db.get())) {
+ cd.cacheVisibleTo(cc);
+ return true;
+ }
} catch (NoSuchChangeException e) {
- return false;
}
+ return false;
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java
new file mode 100644
index 0000000..6f9094a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java
@@ -0,0 +1,318 @@
+// Copyright (C) 2012 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.query.change;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.gerrit.common.data.ApprovalType;
+import com.google.gerrit.common.data.ApprovalTypes;
+import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.OutputFormat;
+import com.google.gerrit.server.events.AccountAttribute;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gson.reflect.TypeToken;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.kohsuke.args4j.Option;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.sql.Timestamp;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public class ListChanges {
+ private final QueryProcessor imp;
+ private final Provider<ReviewDb> db;
+ private final ApprovalTypes approvalTypes;
+ private final CurrentUser user;
+ private final ChangeControl.Factory changeControlFactory;
+ private boolean reverse;
+ private Map<Account.Id, AccountAttribute> accounts;
+
+ @Option(name = "--format", metaVar = "FMT", usage = "Output display format")
+ private OutputFormat format = OutputFormat.TEXT;
+
+ @Option(name = "--query", aliases = {"-q"}, metaVar = "QUERY", multiValued = true, usage = "Query string")
+ private List<String> queries;
+
+ @Option(name = "--limit", aliases = {"-n"}, metaVar = "CNT", usage = "Maximum number of results to return")
+ void setLimit(int limit) {
+ imp.setLimit(limit);
+ }
+
+ @Option(name = "-P", metaVar = "SORTKEY", usage = "Previous changes before SORTKEY")
+ void setSortKeyAfter(String key) {
+ // Querying for the prior page of changes requires sortkey_after predicate.
+ // Changes are shown most recent->least recent. The previous page of
+ // results contains changes that were updated after the given key.
+ imp.setSortkeyAfter(key);
+ reverse = true;
+ }
+
+ @Option(name = "-N", metaVar = "SORTKEY", usage = "Next changes after SORTKEY")
+ void setSortKeyBefore(String key) {
+ // Querying for the next page of changes requires sortkey_before predicate.
+ // Changes are shown most recent->least recent. The next page contains
+ // changes that were updated before the given key.
+ imp.setSortkeyBefore(key);
+ }
+
+ @Inject
+ ListChanges(QueryProcessor qp,
+ Provider<ReviewDb> db,
+ ApprovalTypes at,
+ CurrentUser u,
+ ChangeControl.Factory cf) {
+ this.imp = qp;
+ this.db = db;
+ this.approvalTypes = at;
+ this.user = u;
+ this.changeControlFactory = cf;
+
+ accounts = Maps.newHashMap();
+ }
+
+ public OutputFormat getFormat() {
+ return format;
+ }
+
+ public ListChanges setFormat(OutputFormat fmt) {
+ this.format = fmt;
+ return this;
+ }
+
+ public void query(Writer out)
+ throws OrmException, QueryParseException, IOException {
+ if (imp.isDisabled()) {
+ throw new QueryParseException("query disabled");
+ }
+ if (queries == null || queries.isEmpty()) {
+ queries = Collections.singletonList("status:open");
+ } else if (queries.size() > 10) {
+ // Hard-code a default maximum number of queries to prevent
+ // users from submitting too much to the server in a single call.
+ throw new QueryParseException("limit of 10 queries");
+ }
+
+ List<List<ChangeInfo>> res = Lists.newArrayListWithCapacity(queries.size());
+ for (String query : queries) {
+ List<ChangeData> changes = imp.queryChanges(query);
+ boolean moreChanges = imp.getLimit() > 0 && changes.size() > imp.getLimit();
+ if (moreChanges) {
+ if (reverse) {
+ changes = changes.subList(1, changes.size());
+ } else {
+ changes = changes.subList(0, imp.getLimit());
+ }
+ }
+ ChangeData.ensureChangeLoaded(db, changes);
+ ChangeData.ensureCurrentPatchSetLoaded(db, changes);
+ ChangeData.ensureCurrentApprovalsLoaded(db, changes);
+
+ List<ChangeInfo> info = Lists.newArrayListWithCapacity(changes.size());
+ for (ChangeData cd : changes) {
+ info.add(toChangeInfo(cd));
+ }
+ if (moreChanges && !info.isEmpty()) {
+ if (reverse) {
+ info.get(0)._moreChanges = true;
+ } else {
+ info.get(info.size() - 1)._moreChanges = true;
+ }
+ }
+ res.add(info);
+ }
+
+ if (!accounts.isEmpty()) {
+ for (Account account : db.get().accounts().get(accounts.keySet())) {
+ AccountAttribute a = accounts.get(account.getId());
+ a.name = Strings.emptyToNull(account.getFullName());
+ }
+ }
+
+ if (format.isJson()) {
+ format.newGson().toJson(
+ res.size() == 1 ? res.get(0) : res,
+ new TypeToken<List<ChangeInfo>>() {}.getType(),
+ out);
+ out.write('\n');
+ } else {
+ boolean firstQuery = true;
+ for (List<ChangeInfo> info : res) {
+ if (firstQuery) {
+ firstQuery = false;
+ } else {
+ out.write('\n');
+ }
+ for (ChangeInfo c : info) {
+ String id = new Change.Key(c.id).abbreviate();
+ String subject = c.subject;
+ if (subject.length() + id.length() > 80) {
+ subject = subject.substring(0, 80 - id.length());
+ }
+ out.write(id);
+ out.write(' ');
+ out.write(subject.replace('\n', ' '));
+ out.write('\n');
+ }
+ }
+ }
+ }
+
+ private ChangeInfo toChangeInfo(ChangeData cd) throws OrmException {
+ ChangeInfo out = new ChangeInfo();
+ Change in = cd.change(db);
+ out.project = in.getProject().get();
+ out.branch = in.getDest().getShortName();
+ out.topic = in.getTopic();
+ out.id = in.getKey().get();
+ out.subject = in.getSubject();
+ out.status = in.getStatus();
+ out.owner = asAccountAttribute(in.getOwner());
+ out.created = in.getCreatedOn();
+ out.updated = in.getLastUpdatedOn();
+ out._number = in.getId().get();
+ out._sortkey = in.getSortKey();
+ out.starred = user.getStarredChanges().contains(in.getId()) ? true : null;
+ out.labels = labelsFor(cd);
+ return out;
+ }
+
+ private AccountAttribute asAccountAttribute(Account.Id user) {
+ AccountAttribute a = accounts.get(user);
+ if (a == null) {
+ a = new AccountAttribute();
+ accounts.put(user, a);
+ }
+ return a;
+ }
+
+ private Map<String, LabelInfo> labelsFor(ChangeData cd) throws OrmException {
+ Change in = cd.change(db);
+ ChangeControl ctl = cd.changeControl();
+ if (ctl == null || ctl.getCurrentUser() != user) {
+ try {
+ ctl = changeControlFactory.controlFor(in);
+ } catch (NoSuchChangeException e) {
+ return null;
+ }
+ }
+
+ PatchSet ps = cd.currentPatchSet(db);
+ Map<String, LabelInfo> labels = Maps.newLinkedHashMap();
+ for (SubmitRecord rec : ctl.canSubmit(db.get(), ps, cd, true)) {
+ if (rec.labels == null) {
+ continue;
+ }
+ for (SubmitRecord.Label r : rec.labels) {
+ LabelInfo p = labels.get(r.label);
+ if (p == null || p._status.compareTo(r.status) < 0) {
+ LabelInfo n = new LabelInfo();
+ n._status = r.status;
+ switch (r.status) {
+ case OK:
+ n.approved = asAccountAttribute(r.appliedBy);
+ break;
+ case REJECT:
+ n.rejected = asAccountAttribute(r.appliedBy);
+ break;
+ }
+ labels.put(r.label, n);
+ }
+ }
+ }
+
+ Collection<PatchSetApproval> approvals = null;
+ for (Map.Entry<String, LabelInfo> e : labels.entrySet()) {
+ if (e.getValue().approved != null || e.getValue().rejected != null) {
+ continue;
+ }
+
+ ApprovalType type = approvalTypes.byLabel(e.getKey());
+ if (type == null || type.getMin() == null || type.getMax() == null) {
+ // Unknown or misconfigured type can't have intermediate scores.
+ continue;
+ }
+
+ short min = type.getMin().getValue();
+ short max = type.getMax().getValue();
+ if (-1 <= min && max <= 1) {
+ // Types with a range of -1..+1 can't have intermediate scores.
+ continue;
+ }
+
+ if (approvals == null) {
+ approvals = cd.currentApprovals(db);
+ }
+ for (PatchSetApproval psa : approvals) {
+ short val = psa.getValue();
+ if (val != 0 && min < val && val < max
+ && psa.getCategoryId().equals(type.getCategory().getId())) {
+ if (0 < val) {
+ e.getValue().recommended = asAccountAttribute(psa.getAccountId());
+ e.getValue().value = val != 1 ? val : null;
+ } else {
+ e.getValue().disliked = asAccountAttribute(psa.getAccountId());
+ e.getValue().value = val != -1 ? val : null;
+ }
+ }
+ }
+ }
+ return labels;
+ }
+
+ static class ChangeInfo {
+ String project;
+ String branch;
+ String topic;
+ String id;
+ String subject;
+ Change.Status status;
+ Timestamp created;
+ Timestamp updated;
+ Boolean starred;
+
+ String _sortkey;
+ int _number;
+
+ AccountAttribute owner;
+ Map<String, LabelInfo> labels;
+ Boolean _moreChanges;
+ }
+
+ static class LabelInfo {
+ transient SubmitRecord.Label.Status _status;
+ AccountAttribute approved;
+ AccountAttribute rejected;
+
+ AccountAttribute recommended;
+ AccountAttribute disliked;
+ Short value;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
index a2fa7fe..76945f4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
@@ -55,6 +55,30 @@
private static final Logger log =
LoggerFactory.getLogger(QueryProcessor.class);
+ private final Comparator<ChangeData> cmpAfter =
+ new Comparator<ChangeData>() {
+ @Override
+ public int compare(ChangeData a, ChangeData b) {
+ try {
+ return a.change(db).getSortKey().compareTo(b.change(db).getSortKey());
+ } catch (OrmException e) {
+ return 0;
+ }
+ }
+ };
+
+ private final Comparator<ChangeData> cmpBefore =
+ new Comparator<ChangeData>() {
+ @Override
+ public int compare(ChangeData a, ChangeData b) {
+ try {
+ return b.change(db).getSortKey().compareTo(a.change(db).getSortKey());
+ } catch (OrmException e) {
+ return 0;
+ }
+ }
+ };
+
public static enum OutputFormat {
TEXT, JSON;
}
@@ -71,6 +95,9 @@
private final int maxLimit;
private OutputFormat outputFormat = OutputFormat.TEXT;
+ private int limit;
+ private String sortkeyAfter;
+ private String sortkeyBefore;
private boolean includePatchSets;
private boolean includeCurrentPatchSet;
private boolean includeApprovals;
@@ -97,6 +124,22 @@
.getMax();
}
+ int getLimit() {
+ return limit;
+ }
+
+ void setLimit(int n) {
+ limit = n;
+ }
+
+ void setSortkeyAfter(String sortkey) {
+ sortkeyAfter = sortkey;
+ }
+
+ void setSortkeyBefore(String sortkey) {
+ sortkeyBefore = sortkey;
+ }
+
public void setIncludePatchSets(boolean on) {
includePatchSets = on;
}
@@ -146,6 +189,14 @@
this.outputFormat = fmt;
}
+ /**
+ * Query for changes that match the query string.
+ * <p>
+ * If a limit was specified using {@link #setLimit(int)} this method may
+ * return up to {@code limit + 1} results, allowing the caller to determine if
+ * there are more than {@code limit} matches and suggest to its own caller
+ * that the query could be retried with {@link #setSortkeyBefore(String)}.
+ */
public List<ChangeData> queryChanges(final String queryString)
throws OrmException, QueryParseException {
final Predicate<ChangeData> visibleToMe = queryBuilder.is_visible();
@@ -175,19 +226,14 @@
}
}
- Collections.sort(results, new Comparator<ChangeData>() {
- @Override
- public int compare(ChangeData a, ChangeData b) {
- return b.getChange().getSortKey().compareTo(
- a.getChange().getSortKey());
- }
- });
-
+ Collections.sort(results, sortkeyAfter != null ? cmpAfter : cmpBefore);
int limit = limit(s);
if (limit < results.size()) {
results = results.subList(0, limit);
}
-
+ if (sortkeyAfter != null) {
+ Collections.reverse(results);
+ }
return results;
}
@@ -196,7 +242,7 @@
new BufferedWriter( //
new OutputStreamWriter(outputStream, "UTF-8")));
try {
- if (maxLimit <= 0) {
+ if (isDisabled()) {
ErrorMessage m = new ErrorMessage();
m.message = "query disabled";
show(m);
@@ -233,7 +279,7 @@
if (current != null) {
c.currentPatchSet = eventFactory.asPatchSetAttribute(current);
eventFactory.addApprovals(c.currentPatchSet, //
- d.approvalsFor(db, current.getId()));
+ d.currentApprovals(db));
if (includeFiles) {
eventFactory.addPatchSetFileNames(c.currentPatchSet,
@@ -283,8 +329,13 @@
}
}
+ boolean isDisabled() {
+ return maxLimit <= 0;
+ }
+
private int limit(Predicate<ChangeData> s) {
- return queryBuilder.hasLimit(s) ? queryBuilder.getLimit(s) : maxLimit;
+ int n = queryBuilder.hasLimit(s) ? queryBuilder.getLimit(s) : maxLimit;
+ return limit > 0 ? Math.min(n, limit) + 1 : n;
}
@SuppressWarnings("unchecked")
@@ -293,9 +344,17 @@
Predicate<ChangeData> q = queryBuilder.parse(queryString);
if (!queryBuilder.hasSortKey(q)) {
- q = Predicate.and(q, queryBuilder.sortkey_before("z"));
+ if (sortkeyBefore != null) {
+ q = Predicate.and(q, queryBuilder.sortkey_before(sortkeyBefore));
+ } else if (sortkeyAfter != null) {
+ q = Predicate.and(q, queryBuilder.sortkey_after(sortkeyAfter));
+ } else {
+ q = Predicate.and(q, queryBuilder.sortkey_before("z"));
+ }
}
- q = Predicate.and(q, queryBuilder.limit(maxLimit), visibleToMe);
+ q = Predicate.and(q,
+ queryBuilder.limit(limit > 0 ? Math.min(limit, maxLimit) + 1 : maxLimit),
+ visibleToMe);
Predicate<ChangeData> s = queryRewriter.rewrite(q);
if (!(s instanceof ChangeDataSource)) {
@@ -303,7 +362,7 @@
}
if (!(s instanceof ChangeDataSource)) {
- throw new QueryParseException("cannot execute query: " + s);
+ throw new QueryParseException("invalid query: " + s);
}
return s;
diff --git a/gerrit-server/src/main/java/gerrit/PRED__load_commit_labels_1.java b/gerrit-server/src/main/java/gerrit/PRED__load_commit_labels_1.java
index c760426..a0bb820 100644
--- a/gerrit-server/src/main/java/gerrit/PRED__load_commit_labels_1.java
+++ b/gerrit-server/src/main/java/gerrit/PRED__load_commit_labels_1.java
@@ -9,7 +9,9 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.rules.PrologEnvironment;
import com.google.gerrit.rules.StoredValues;
+import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
+import com.google.inject.util.Providers;
import com.googlecode.prolog_cafe.lang.IntegerTerm;
import com.googlecode.prolog_cafe.lang.JavaException;
@@ -44,10 +46,18 @@
try {
PrologEnvironment env = (PrologEnvironment) engine.control;
ReviewDb db = StoredValues.REVIEW_DB.get(engine);
- PatchSet.Id patchSetId = StoredValues.PATCH_SET_ID.get(engine);
+ PatchSet patchSet = StoredValues.PATCH_SET.get(engine);
+ ChangeData cd = StoredValues.CHANGE_DATA.getOrNull(engine);
ApprovalTypes types = env.getInjector().getInstance(ApprovalTypes.class);
- for (PatchSetApproval a : db.patchSetApprovals().byPatchSet(patchSetId)) {
+ Iterable<PatchSetApproval> approvals;
+ if (cd != null) {
+ approvals = cd.currentApprovals(Providers.of(db));
+ } else {
+ approvals = db.patchSetApprovals().byPatchSet(patchSet.getId());
+ }
+
+ for (PatchSetApproval a : approvals) {
if (a.getValue() == 0) {
continue;
}
diff --git a/gerrit-server/src/main/java/gerrit/PRED_current_user_2.java b/gerrit-server/src/main/java/gerrit/PRED_current_user_2.java
index 0a15608..1359de1 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_current_user_2.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_current_user_2.java
@@ -20,15 +20,12 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.rules.PrologEnvironment;
import com.google.gerrit.rules.StoredValues;
-import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.inject.Provider;
+import com.google.inject.util.Providers;
-import com.googlecode.prolog_cafe.lang.HashtableOfTerm;
import com.googlecode.prolog_cafe.lang.IllegalTypeException;
import com.googlecode.prolog_cafe.lang.IntegerTerm;
-import com.googlecode.prolog_cafe.lang.InternalException;
import com.googlecode.prolog_cafe.lang.JavaObjectTerm;
import com.googlecode.prolog_cafe.lang.Operation;
import com.googlecode.prolog_cafe.lang.PInstantiationException;
@@ -39,6 +36,8 @@
import com.googlecode.prolog_cafe.lang.SymbolTerm;
import com.googlecode.prolog_cafe.lang.Term;
+import java.util.Map;
+
/**
* Loads a CurrentUser object for a user identity.
* <p>
@@ -53,7 +52,6 @@
private static final long serialVersionUID = 1L;
private static final SymbolTerm user = intern("user", 1);
private static final SymbolTerm anonymous = intern("anonymous");
- private static final SymbolTerm current_user = intern("current_user");
PRED_current_user_2(Term a1, Term a2, Operation n) {
arg1 = a1;
@@ -71,24 +69,14 @@
throw new PInstantiationException(this, 1);
}
- HashtableOfTerm userHash = userHash(engine);
- Term userTerm = userHash.get(a1);
- if (userTerm != null && userTerm.isJavaObject()) {
- if (!(((JavaObjectTerm) userTerm).object() instanceof CurrentUser)) {
- userTerm = createUser(engine, a1, userHash);
- }
- } else {
- userTerm = createUser(engine, a1, userHash);
- }
-
- if (!a2.unify(userTerm, engine.trail)) {
+ if (!a2.unify(createUser(engine, a1), engine.trail)) {
return engine.fail();
}
return cont;
}
- public Term createUser(Prolog engine, Term key, HashtableOfTerm userHash) {
+ public Term createUser(Prolog engine, Term key) {
if (!key.isStructure()
|| key.arity() != 1
|| !((StructureTerm) key).functor().equals(user)) {
@@ -98,54 +86,30 @@
Term idTerm = key.arg(0);
CurrentUser user;
if (idTerm.isInteger()) {
+ Map<Account.Id, IdentifiedUser> cache = StoredValues.USERS.get(engine);
Account.Id accountId = new Account.Id(((IntegerTerm) idTerm).intValue());
-
- final ReviewDb db = StoredValues.REVIEW_DB.getOrNull(engine);
- IdentifiedUser.GenericFactory userFactory = userFactory(engine);
- if (db != null) {
- user = userFactory.create(new Provider<ReviewDb>() {
- public ReviewDb get() {
- return db;
- }
- }, accountId);
- } else {
- user = userFactory.create(accountId);
+ user = cache.get(accountId);
+ if (user == null) {
+ ReviewDb db = StoredValues.REVIEW_DB.getOrNull(engine);
+ IdentifiedUser.GenericFactory userFactory = userFactory(engine);
+ IdentifiedUser who;
+ if (db != null) {
+ who = userFactory.create(Providers.of(db), accountId);
+ } else {
+ who = userFactory.create(accountId);
+ }
+ cache.put(accountId, who);
+ user = who;
}
-
} else if (idTerm.equals(anonymous)) {
- user = anonymousUser(engine);
+ user = StoredValues.ANONYMOUS_USER.get(engine);
} else {
throw new IllegalTypeException(this, 1, "user(int)", key);
}
- Term userTerm = new JavaObjectTerm(user);
- userHash.put(key, userTerm);
- return userTerm;
- }
-
- private static HashtableOfTerm userHash(Prolog engine) {
- Term userHash = engine.getHashManager().get(current_user);
- if (userHash == null) {
- HashtableOfTerm users = new HashtableOfTerm();
- engine.getHashManager().put(current_user, new JavaObjectTerm(userHash));
- return users;
- }
-
- if (userHash.isJavaObject()) {
- Object obj = ((JavaObjectTerm) userHash).object();
- if (obj instanceof HashtableOfTerm) {
- return (HashtableOfTerm) obj;
- }
- }
-
- throw new InternalException(current_user + " is not HashtableOfTerm");
- }
-
- private static AnonymousUser anonymousUser(Prolog engine) {
- PrologEnvironment env = (PrologEnvironment) engine.control;
- return env.getInjector().getInstance(AnonymousUser.class);
+ return new JavaObjectTerm(user);
}
private static IdentifiedUser.GenericFactory userFactory(Prolog engine) {
diff --git a/gerrit-server/src/main/prolog/gerrit_common.pl b/gerrit-server/src/main/prolog/gerrit_common.pl
index 3313162..5acc831 100644
--- a/gerrit-server/src/main/prolog/gerrit_common.pl
+++ b/gerrit-server/src/main/prolog/gerrit_common.pl
@@ -25,8 +25,7 @@
%% predicate that needs to obtain it.
%%
init :-
- define_hash(commit_labels),
- define_hash(current_user).
+ define_hash(commit_labels).
define_hash(A) :- hash_exists(A), !, hash_clear(A).
define_hash(A) :- atom(A), !, new_hash(_, [alias(A)]).
@@ -98,6 +97,10 @@
%% Lookup the range allowed to be used.
%%
user_label_range(Label, Who, Min, Max) :-
+ hash_get(commit_labels, '$fast_range', true), !,
+ atom(Label),
+ assume_range_from_label(Label, Who, Min, Max).
+user_label_range(Label, Who, Min, Max) :-
Who = user(_), !,
atom(Label),
current_user(Who, User),
@@ -106,6 +109,14 @@
clause(user:test_grant(Label, test_user(Name), range(Min, Max)), _)
.
+assume_range_from_label :-
+ hash_put(commit_labels, '$fast_range', true).
+
+assume_range_from_label(Label, Who, Min, Max) :-
+ commit_label(label(Label, Value), Who), !,
+ Min = Value, Max = Value.
+assume_range_from_label(_, _, 0, 0).
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.vm
index f2f0fc76..1eb6842 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.vm
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.vm
@@ -31,7 +31,8 @@
## The ChangeFooter.vm template will determine the contents of the footer
## text that will be appended to ALL emails related to changes.
##
---
+#set ($SPACE = " ")
+--$SPACE
#if ($email.changeUrl)
To view, visit $email.changeUrl
#set ($notblank = 1)
diff --git a/gerrit-sshd/.gitignore b/gerrit-sshd/.gitignore
index 194bedc..8deb9bd 100644
--- a/gerrit-sshd/.gitignore
+++ b/gerrit-sshd/.gitignore
@@ -2,4 +2,5 @@
/.classpath
/.project
/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-sshd.iml
\ No newline at end of file
diff --git a/gerrit-sshd/pom.xml b/gerrit-sshd/pom.xml
index 55e8725..1c197a0 100644
--- a/gerrit-sshd/pom.xml
+++ b/gerrit-sshd/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.4-SNAPSHOT</version>
+ <version>2.5-SNAPSHOT</version>
</parent>
<artifactId>gerrit-sshd</artifactId>
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
index 54b0bb5..558707b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
@@ -36,6 +36,7 @@
import com.google.gerrit.sshd.args4j.AccountGroupIdHandler;
import com.google.gerrit.sshd.args4j.AccountGroupUUIDHandler;
import com.google.gerrit.sshd.args4j.AccountIdHandler;
+import com.google.gerrit.sshd.args4j.ObjectIdHandler;
import com.google.gerrit.sshd.args4j.PatchSetIdHandler;
import com.google.gerrit.sshd.args4j.ProjectControlHandler;
import com.google.gerrit.sshd.args4j.SocketAddressHandler;
@@ -48,6 +49,7 @@
import org.apache.sshd.common.KeyPairProvider;
import org.apache.sshd.server.CommandFactory;
import org.apache.sshd.server.PublickeyAuthenticator;
+import org.eclipse.jgit.lib.ObjectId;
import org.kohsuke.args4j.spi.OptionHandler;
import java.net.SocketAddress;
@@ -118,6 +120,7 @@
registerOptionHandler(Account.Id.class, AccountIdHandler.class);
registerOptionHandler(AccountGroup.Id.class, AccountGroupIdHandler.class);
registerOptionHandler(AccountGroup.UUID.class, AccountGroupUUIDHandler.class);
+ registerOptionHandler(ObjectId.class, ObjectIdHandler.class);
registerOptionHandler(PatchSet.Id.class, PatchSetIdHandler.class);
registerOptionHandler(ProjectControl.class, ProjectControlHandler.class);
registerOptionHandler(SocketAddress.class, SocketAddressHandler.class);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ObjectIdHandler.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ObjectIdHandler.java
new file mode 100644
index 0000000..adb5ad6
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ObjectIdHandler.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2012 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.sshd.args4j;
+
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.CmdLineParser;
+import org.kohsuke.args4j.OptionDef;
+import org.kohsuke.args4j.spi.OptionHandler;
+import org.kohsuke.args4j.spi.Parameters;
+import org.kohsuke.args4j.spi.Setter;
+
+public class ObjectIdHandler extends OptionHandler<ObjectId> {
+
+ @Inject
+ public ObjectIdHandler(@Assisted final CmdLineParser parser,
+ @Assisted final OptionDef option, @Assisted final Setter<ObjectId> setter) {
+ super(parser, option, setter);
+ }
+
+ @Override
+ public int parseArguments(Parameters params) throws CmdLineException {
+ final String n = params.getParameter(0);
+ setter.addValue(ObjectId.fromString(n));
+ return 1;
+ }
+
+ @Override
+ public String getDefaultMetaVariable() {
+ return "COMMIT";
+ }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
new file mode 100644
index 0000000..fd58221
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
@@ -0,0 +1,118 @@
+// Copyright (C) 2012 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.sshd.commands;
+
+import com.google.gerrit.common.errors.PermissionDeniedException;
+import com.google.gerrit.server.git.BanCommit;
+import com.google.gerrit.server.git.BanCommitResult;
+import com.google.gerrit.server.git.IncompleteUserInfoException;
+import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.sshd.BaseCommand;
+import com.google.inject.Inject;
+
+import org.apache.sshd.server.Environment;
+import org.eclipse.jgit.lib.ObjectId;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+public class BanCommitCommand extends BaseCommand {
+
+ @Option(name = "--reason", aliases = {"-r"}, metaVar = "REASON", usage = "reason for banning the commit")
+ private String reason;
+
+ @Argument(index = 0, required = true, metaVar = "PROJECT",
+ usage = "name of the project for which the commit should be banned")
+ private ProjectControl projectControl;
+
+ @Argument(index = 1, required = true, multiValued = true, metaVar = "COMMIT",
+ usage = "commit(s) that should be banned")
+ private List<ObjectId> commitsToBan = new ArrayList<ObjectId>();
+
+ @Inject
+ private BanCommit.Factory banCommitFactory;
+
+ @Override
+ public void start(final Environment env) throws IOException {
+ startThread(new CommandRunnable() {
+ @Override
+ public void run() throws Exception {
+ parseCommandLine();
+ BanCommitCommand.this.display();
+ }
+ });
+ }
+
+ private void display() throws Failure {
+ try {
+ final BanCommitResult result =
+ banCommitFactory.create().ban(projectControl, commitsToBan, reason);
+
+ final PrintWriter stdout = toPrintWriter(out);
+ try {
+ final List<ObjectId> newlyBannedCommits =
+ result.getNewlyBannedCommits();
+ if (!newlyBannedCommits.isEmpty()) {
+ stdout.print("The following commits were banned:\n");
+ printCommits(stdout, newlyBannedCommits);
+ }
+
+ final List<ObjectId> alreadyBannedCommits =
+ result.getAlreadyBannedCommits();
+ if (!alreadyBannedCommits.isEmpty()) {
+ stdout.print("The following commits were already banned:\n");
+ printCommits(stdout, alreadyBannedCommits);
+ }
+
+ final List<ObjectId> ignoredIds = result.getIgnoredObjectIds();
+ if (!ignoredIds.isEmpty()) {
+ stdout.print("The following ids do not represent commits"
+ + " and were ignored:\n");
+ printCommits(stdout, ignoredIds);
+ }
+ } finally {
+ stdout.flush();
+ }
+ } catch (PermissionDeniedException e) {
+ throw die(e);
+ } catch (IOException e) {
+ throw die(e);
+ } catch (IncompleteUserInfoException e) {
+ throw die(e);
+ } catch (MergeException e) {
+ throw die(e);
+ } catch (InterruptedException e) {
+ throw die(e);
+ }
+ }
+
+ private static void printCommits(final PrintWriter stdout,
+ final List<ObjectId> commits) {
+ boolean first = true;
+ for (final ObjectId c : commits) {
+ if (!first) {
+ stdout.print(",\n");
+ }
+ stdout.print(c.getName());
+ first = false;
+ }
+ stdout.print("\n\n");
+ }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
index 16461b6..4d7c93e 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
@@ -35,6 +35,7 @@
// SlaveCommandModule.
command(gerrit).toProvider(new DispatchCommandProvider(gerrit));
+ command(gerrit, "ban-commit").to(BanCommitCommand.class);
command(gerrit, "flush-caches").to(FlushCaches.class);
command(gerrit, "ls-projects").to(ListProjectsCommand.class);
command(gerrit, "ls-groups").to(ListGroupsCommand.class);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Replicate.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Replicate.java
index bc4e0bb..d56d1cd 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Replicate.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Replicate.java
@@ -73,7 +73,7 @@
private void schedule() throws Failure {
if (all && projectNames.size() > 0) {
- throw new Failure(1, "error: cannot combine --all and PROJECT");
+ throw new UnloggedFailure(1, "error: cannot combine --all and PROJECT");
}
if (!replication.isEnabled()) {
@@ -89,7 +89,7 @@
if (projectCache.get(key) != null) {
replication.scheduleFullSync(key, urlMatch);
} else {
- throw new Failure(1, "error: '" + name + "': not a Gerrit project");
+ throw new UnloggedFailure(1, "error: '" + name + "': not a Gerrit project");
}
}
}
diff --git a/gerrit-util-cli/.gitignore b/gerrit-util-cli/.gitignore
index 194bedc..35069e7 100644
--- a/gerrit-util-cli/.gitignore
+++ b/gerrit-util-cli/.gitignore
@@ -2,4 +2,5 @@
/.classpath
/.project
/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-util-cli.iml
\ No newline at end of file
diff --git a/gerrit-util-cli/pom.xml b/gerrit-util-cli/pom.xml
index 4ecbda4..4886d09 100644
--- a/gerrit-util-cli/pom.xml
+++ b/gerrit-util-cli/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.4-SNAPSHOT</version>
+ <version>2.5-SNAPSHOT</version>
</parent>
<artifactId>gerrit-util-cli</artifactId>
diff --git a/gerrit-util-ssl/.gitignore b/gerrit-util-ssl/.gitignore
index 194bedc..e552ad5 100644
--- a/gerrit-util-ssl/.gitignore
+++ b/gerrit-util-ssl/.gitignore
@@ -2,4 +2,5 @@
/.classpath
/.project
/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-util-ssl.iml
\ No newline at end of file
diff --git a/gerrit-util-ssl/pom.xml b/gerrit-util-ssl/pom.xml
index 2e49d47..beedb8f 100644
--- a/gerrit-util-ssl/pom.xml
+++ b/gerrit-util-ssl/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.4-SNAPSHOT</version>
+ <version>2.5-SNAPSHOT</version>
</parent>
<artifactId>gerrit-util-ssl</artifactId>
diff --git a/gerrit-war/.gitignore b/gerrit-war/.gitignore
index 194bedc..dc8c7ad 100644
--- a/gerrit-war/.gitignore
+++ b/gerrit-war/.gitignore
@@ -2,4 +2,5 @@
/.classpath
/.project
/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-war.iml
\ No newline at end of file
diff --git a/gerrit-war/pom.xml b/gerrit-war/pom.xml
index 733d976a..1f3750e 100644
--- a/gerrit-war/pom.xml
+++ b/gerrit-war/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.4-SNAPSHOT</version>
+ <version>2.5-SNAPSHOT</version>
</parent>
<artifactId>gerrit-war</artifactId>
diff --git a/gerrit-war/src/main/webapp/WEB-INF/extra/jetty7/gerrit.xml b/gerrit-war/src/main/webapp/WEB-INF/extra/jetty7/gerrit.xml
index 117bf61..3ae9440 100644
--- a/gerrit-war/src/main/webapp/WEB-INF/extra/jetty7/gerrit.xml
+++ b/gerrit-war/src/main/webapp/WEB-INF/extra/jetty7/gerrit.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.eclipse.org/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
<!--
Jetty configuration to place "gerrit.war" into the root context,
diff --git a/gerrit-war/src/main/webapp/WEB-INF/extra/jetty7/jetty_sslproxy.xml b/gerrit-war/src/main/webapp/WEB-INF/extra/jetty7/jetty_sslproxy.xml
index 652acad..59cc040 100644
--- a/gerrit-war/src/main/webapp/WEB-INF/extra/jetty7/jetty_sslproxy.xml
+++ b/gerrit-war/src/main/webapp/WEB-INF/extra/jetty7/jetty_sslproxy.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.eclipse.org/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
<!--
Jetty configuration to correctly handle SSL/HTTPS traffic when
diff --git a/pom.xml b/pom.xml
index 377b212..8c14a87 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
<packaging>pom</packaging>
- <version>2.4-SNAPSHOT</version>
+ <version>2.5-SNAPSHOT</version>
<name>Gerrit Code Review - Parent</name>
<url>http://code.google.com/p/gerrit/</url>