Merge "Allow group includes to be by UUID instead of group ID"
diff --git a/Documentation/config-cla.txt b/Documentation/config-cla.txt
index e63486f..6404d4e 100644
--- a/Documentation/config-cla.txt
+++ b/Documentation/config-cla.txt
@@ -1,5 +1,5 @@
Gerrit Code Review - Contributor Agreements
-============================================
+===========================================
Users can be required to sign one or more contributor agreements before
being able to submit a change in a project.
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index 2fafdab..006ad26 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -70,13 +70,13 @@
For security issues it is important that they are only announced
*after* fixed versions for all relevant releases have been published.
-Because of this `security-fix` releases can't be prepared in the public
+Because of this, `security-fix` releases can't be prepared in the public
`gerrit` project.
`security-fix` releases are prepared in the `gerrit-security-fixes`
project which is only readable by the Gerrit Maintainers. Only after
-a `security-fix` release has been published the commits/tags done in
-the `gerrit-security-fixes` project will be taken over into the public
+a `security-fix` release has been published will the commits/tags made in
+the `gerrit-security-fixes` project be taken over into the public
`gerrit` project.
diff --git a/Documentation/rest-api.txt b/Documentation/rest-api.txt
index 9aaac0e..7e77044 100644
--- a/Documentation/rest-api.txt
+++ b/Documentation/rest-api.txt
@@ -243,6 +243,78 @@
}
----
+[[groups]]
+/groups/ (List Groups)
+~~~~~~~~~~~~~~~~~~~~~~
+Lists the groups accessible by the caller. This is the same as
+using the link:cmd-ls-groups.html[ls-groups] command over SSH,
+and accepts the same options as query parameters.
+
+----
+ GET /groups/ HTTP/1.0
+
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json;charset=UTF-8
+
+ )]}'
+ {
+ "Administrators": {
+ "kind": "gerritcodereview#group",
+ "id": "uuid-6a1e70e1a88782771a91808c8af9bbb7a9871389",
+ "uuid": "6a1e70e1a88782771a91808c8af9bbb7a9871389",
+ "group_id": 1,
+ "description": "Gerrit Site Administrators",
+ "is_visible_to_all": false,
+ "owner_uuid": "6a1e70e1a88782771a91808c8af9bbb7a9871389"
+ },
+ "Anonymous Users": {
+ "kind": "gerritcodereview#group",
+ "id": "uuid-global%3AAnonymous-Users",
+ "uuid": "global:Anonymous-Users",
+ "group_id": 2,
+ "description": "Any user, signed-in or not",
+ "is_visible_to_all": false,
+ "owner_uuid": "6a1e70e1a88782771a91808c8af9bbb7a9871389"
+ },
+ "MyProject_Committers": {
+ "kind": "gerritcodereview#group",
+ "id": "uuid-834ec36dd5e0ed21a2ff5d7e2255da082d63bbd7",
+ "uuid": "834ec36dd5e0ed21a2ff5d7e2255da082d63bbd7",
+ "group_id": 6,
+ "is_visible_to_all": true,
+ "owner_uuid": "834ec36dd5e0ed21a2ff5d7e2255da082d63bbd7"
+ },
+ "Non-Interactive Users": {
+ "kind": "gerritcodereview#group",
+ "id": "uuid-5057f3cbd3519d6ab69364429a89ffdffba50f73",
+ "uuid": "5057f3cbd3519d6ab69364429a89ffdffba50f73",
+ "group_id": 4,
+ "description": "Users who perform batch actions on Gerrit",
+ "is_visible_to_all": false,
+ "owner_uuid": "6a1e70e1a88782771a91808c8af9bbb7a9871389"
+ },
+ "Project Owners": {
+ "kind": "gerritcodereview#group",
+ "id": "uuid-global%3AProject-Owners",
+ "uuid": "global:Project-Owners",
+ "group_id": 5,
+ "description": "Any owner of the project",
+ "is_visible_to_all": false,
+ "owner_uuid": "6a1e70e1a88782771a91808c8af9bbb7a9871389"
+ },
+ "Registered Users": {
+ "kind": "gerritcodereview#group",
+ "id": "uuid-global%3ARegistered-Users",
+ "uuid": "global:Registered-Users",
+ "group_id": 3,
+ "description": "Any signed-in user",
+ "is_visible_to_all": false,
+ "owner_uuid": "6a1e70e1a88782771a91808c8af9bbb7a9871389"
+ }
+ }
+----
+
[[changes]]
/changes/ (Query Changes)
~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/Documentation/user-notify.txt b/Documentation/user-notify.txt
index fc5e49b..0e832dd 100644
--- a/Documentation/user-notify.txt
+++ b/Documentation/user-notify.txt
@@ -18,9 +18,9 @@
`All-Projects` can be watched to watch all projects that
are visible to the user.
-Change search expressions can be used to filter change notifications
-to specific subsets, for example `branch:master` to only see changes
-proposed for the master branch.
+link:user-search.html[Change search expressions] can be used to filter
+change notifications to specific subsets, for example `branch:master`
+to only see changes proposed for the master branch.
Project Level Settings
----------------------
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java
index aa212f9..c2d03ab 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java
@@ -18,7 +18,6 @@
import com.google.gerrit.common.auth.SignInRequired;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountSshKey;
import com.google.gerrit.reviewdb.client.ContactInformation;
import com.google.gwtjsonrpc.common.AsyncCallback;
@@ -61,9 +60,6 @@
@SignInRequired
void myExternalIds(AsyncCallback<List<AccountExternalId>> callback);
- @SignInRequired
- void myGroups(AsyncCallback<List<AccountGroup>> callback);
-
@Audit
@SignInRequired
void deleteExternalIds(Set<AccountExternalId.Key> keys,
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupAdminService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupAdminService.java
index 452e2cd..96a80cc 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupAdminService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupAdminService.java
@@ -31,10 +31,6 @@
public interface GroupAdminService extends RemoteJsonService {
@Audit
@SignInRequired
- void visibleGroups(AsyncCallback<GroupList> callback);
-
- @Audit
- @SignInRequired
void createGroup(String newName, AsyncCallback<AccountGroup.Id> callback);
@Audit
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupList.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupList.java
deleted file mode 100644
index b3095cd..0000000
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupList.java
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (C) 2011 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.common.data;
-
-import com.google.gerrit.reviewdb.client.AccountGroup;
-
-import java.util.List;
-
-public class GroupList {
- protected List<AccountGroup> groups;
- protected boolean canCreateGroup;
-
- protected GroupList() {
- }
-
- public GroupList(final List<AccountGroup> groups, final boolean canCreateGroup) {
- this.groups = groups;
- this.canCreateGroup = canCreateGroup;
- }
-
- public List<AccountGroup> getGroups() {
- return groups;
- }
-
- public void setGroups(List<AccountGroup> groups) {
- this.groups = groups;
- }
-
- public boolean isCanCreateGroup() {
- return canCreateGroup;
- }
-
- public void setCanCreateGroup(boolean set) {
- canCreateGroup = set;
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailException.java b/gerrit-common/src/main/java/com/google/gerrit/common/errors/EmailException.java
similarity index 83%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailException.java
rename to gerrit-common/src/main/java/com/google/gerrit/common/errors/EmailException.java
index a33cb63..635335d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailException.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/errors/EmailException.java
@@ -12,16 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.mail;
+package com.google.gerrit.common.errors;
public class EmailException extends Exception {
private static final long serialVersionUID = 1L;
+ public static final String MESSAGE = "Mail Error: ";
+
public EmailException(String msg) {
- super(msg);
+ super(MESSAGE + msg);
}
public EmailException(String msg, Throwable why) {
- super(msg, why);
+ super(MESSAGE + msg, why);
}
}
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 1b95038..243e9ff 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
@@ -147,6 +147,7 @@
String leftMostCell();
String lineHeader();
String lineNumber();
+ String link();
String linkMenuBar();
String linkMenuItemNotLast();
String menuBarUserName();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
index 7032196..2afac1c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
@@ -111,6 +111,7 @@
String buttonCancel();
String titleRegisterNewEmail();
String descRegisterNewEmail();
+ String errorDialogTitleRegisterNewEmail();
String newAgreement();
String agreementStatus();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
index ea35c66..8d57096 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
@@ -119,6 +119,7 @@
descRegisterNewEmail = \
<p>A confirmation link will be sent by email to this address.</p>\
<p>You must click on the link to complete the registration and make the address available for selection.</p>
+errorDialogTitleRegisterNewEmail = Email Registration Failed
newAgreement = New Contributor Agreement
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java
index 9ac35f0..cba2f0b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java
@@ -19,6 +19,7 @@
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.OnEditEnabler;
import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Account.FieldName;
import com.google.gerrit.reviewdb.client.AccountExternalId;
@@ -300,7 +301,15 @@
public void onFailure(final Throwable caught) {
inEmail.setEnabled(true);
register.setEnabled(true);
- super.onFailure(caught);
+ if (caught.getMessage().startsWith(EmailException.MESSAGE)) {
+ final ErrorDialog d =
+ new ErrorDialog(caught.getMessage().substring(
+ EmailException.MESSAGE.length()));
+ d.setText(Util.C.errorDialogTitleRegisterNewEmail());
+ d.center();
+ } else {
+ super.onFailure(caught);
+ }
}
});
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGroupsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGroupsScreen.java
index 6cb749d..8b3bf56 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGroupsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGroupsScreen.java
@@ -15,10 +15,8 @@
package com.google.gerrit.client.account;
import com.google.gerrit.client.admin.GroupTable;
+import com.google.gerrit.client.groups.GroupMap;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-
-import java.util.List;
public class MyGroupsScreen extends SettingsScreen {
private GroupTable groups;
@@ -33,11 +31,11 @@
@Override
protected void onLoad() {
super.onLoad();
- Util.ACCOUNT_SEC.myGroups(new ScreenLoadCallback<List<AccountGroup>>(this) {
+ GroupMap.my(new ScreenLoadCallback<GroupMap>(this) {
@Override
- public void preDisplay(final List<AccountGroup> result) {
+ protected void preDisplay(final GroupMap result) {
groups.display(result);
- }
- });
+ groups.finishDisplay();
+ }});
}
}
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 d278e1f..d0a3329 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
@@ -97,6 +97,7 @@
String groupItemHelp();
String groupListTitle();
+ String groupFilter();
String createGroupTitle();
String groupTabGeneral();
String groupTabMembers();
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 765ed09..7d6859c 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
@@ -77,6 +77,7 @@
groupItemHelp = group
groupListTitle = Groups
+groupFilter = Filter
createGroupTitle = Create Group
groupTabGeneral = General
groupTabMembers = Members
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java
index dae0a03..555d023 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java
@@ -235,8 +235,22 @@
new GerritCallback<VoidResult>() {
@Override
public void onSuccess(VoidResult result) {
+ String nameWithoutSuffix = projectName;
+ if (nameWithoutSuffix.endsWith(".git")) {
+ // Be nice and drop the trailing ".git" suffix, which we never
+ // keep in our database, but clients might mistakenly provide
+ // anyway.
+ //
+ nameWithoutSuffix = nameWithoutSuffix.substring(0, //
+ nameWithoutSuffix.length() - 4);
+ while (nameWithoutSuffix.endsWith("/")) {
+ nameWithoutSuffix = nameWithoutSuffix.substring(//
+ 0, nameWithoutSuffix.length() - 1);
+ }
+ }
+
History.newItem(Dispatcher.toProjectAdmin(new Project.NameKey(
- projectName), ProjectScreen.INFO));
+ nameWithoutSuffix), ProjectScreen.INFO));
}
@Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
index 3157d97..8c54b58 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
@@ -14,36 +14,82 @@
package com.google.gerrit.client.admin;
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.groups.GroupMap;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.AccountScreen;
+import com.google.gerrit.client.ui.FilteredUserInterface;
+import com.google.gerrit.client.ui.IgnoreOutdatedFilterResultsCallbackWrapper;
import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.common.data.GroupList;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwtexpui.globalkey.client.NpTextBox;
-public class GroupListScreen extends AccountScreen {
+public class GroupListScreen extends AccountScreen implements FilteredUserInterface {
private GroupTable groups;
+ private NpTextBox filterTxt;
+ private String subname;
@Override
protected void onLoad() {
super.onLoad();
- Util.GROUP_SVC
- .visibleGroups(new ScreenLoadCallback<GroupList>(this) {
- @Override
- protected void preDisplay(GroupList result) {
- groups.display(result.getGroups());
- groups.finishDisplay();
- }
- });
+ refresh();
+ }
+
+ private void refresh() {
+ GroupMap.match(subname,
+ new IgnoreOutdatedFilterResultsCallbackWrapper<GroupMap>(this,
+ new ScreenLoadCallback<GroupMap>(this) {
+ @Override
+ protected void preDisplay(final GroupMap result) {
+ groups.display(result, subname);
+ groups.finishDisplay();
+ }
+ }));
+ }
+
+ @Override
+ public String getCurrentFilter() {
+ return subname;
}
@Override
protected void onInitUI() {
super.onInitUI();
setPageTitle(Util.C.groupListTitle());
+ initPageHeader();
groups = new GroupTable(true /* hyperlink to admin */, PageLinks.ADMIN_GROUPS);
add(groups);
}
+ private void initPageHeader() {
+ final HorizontalPanel hp = new HorizontalPanel();
+ hp.setStyleName(Gerrit.RESOURCES.css().projectFilterPanel());
+ final Label filterLabel = new Label(Util.C.projectFilter());
+ filterLabel.setStyleName(Gerrit.RESOURCES.css().projectFilterLabel());
+ hp.add(filterLabel);
+ filterTxt = new NpTextBox();
+ filterTxt.setValue(subname);
+ filterTxt.addKeyUpHandler(new KeyUpHandler() {
+ @Override
+ public void onKeyUp(KeyUpEvent event) {
+ subname = filterTxt.getValue();
+ refresh();
+ }
+ });
+ hp.add(filterTxt);
+ add(hp);
+ }
+
+ @Override
+ public void onShowView() {
+ super.onShowView();
+ filterTxt.setFocus(true);
+ }
+
@Override
public void registerKeys() {
super.registerKeys();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupTable.java
index 3116916..6a16f1a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupTable.java
@@ -16,9 +16,10 @@
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.ui.Hyperlink;
+import com.google.gerrit.client.groups.GroupInfo;
+import com.google.gerrit.client.groups.GroupMap;
+import com.google.gerrit.client.ui.HighlightingInlineHyperlink;
import com.google.gerrit.client.ui.NavigationTable;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.History;
@@ -26,10 +27,12 @@
import com.google.gwt.user.client.ui.HTMLTable.Cell;
import com.google.gwt.user.client.ui.Image;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
-public class GroupTable extends NavigationTable<AccountGroup> {
+public class GroupTable extends NavigationTable<GroupInfo> {
private static final int NUM_COLS = 3;
private final boolean enableLink;
@@ -64,35 +67,46 @@
}
@Override
- protected Object getRowItemKey(final AccountGroup item) {
- return item.getId();
+ protected Object getRowItemKey(final GroupInfo item) {
+ return item.getGroupId();
}
@Override
protected void onOpenRow(final int row) {
- History.newItem(Dispatcher.toGroup(getRowItem(row).getId()));
+ History.newItem(Dispatcher.toGroup(getRowItem(row).getGroupId()));
}
- public void display(final List<AccountGroup> result) {
+ public void display(final GroupMap groups) {
+ display(groups, null);
+ }
+
+ public void display(final GroupMap groups, final String toHighlight) {
while (1 < table.getRowCount())
table.removeRow(table.getRowCount() - 1);
- for(AccountGroup group : result) {
+ List<GroupInfo> list = groups.values().asList();
+ Collections.sort(list, new Comparator<GroupInfo>() {
+ @Override
+ public int compare(GroupInfo a, GroupInfo b) {
+ return a.name().compareTo(b.name());
+ }
+ });
+ for(GroupInfo group : list) {
final int row = table.getRowCount();
table.insertRow(row);
applyDataRowStyle(row);
- populate(row, group);
+ populate(row, group, toHighlight);
}
}
- void populate(final int row, final AccountGroup k) {
+ void populate(final int row, final GroupInfo k, final String toHighlight) {
if (enableLink) {
- table.setWidget(row, 1, new Hyperlink(k.getName(),
- Dispatcher.toGroup(k.getId())));
+ table.setWidget(row, 1, new HighlightingInlineHyperlink(k.name(),
+ Dispatcher.toGroup(k.getGroupId()), toHighlight));
} else {
- table.setText(row, 1, k.getName());
+ table.setText(row, 1, k.name());
}
- table.setText(row, 2, k.getDescription());
+ table.setText(row, 2, k.description());
if (k.isVisibleToAll()) {
table.setWidget(row, 3, new Image(Gerrit.RESOURCES.greenCheck()));
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
index f702820..4795bda 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
@@ -20,7 +20,9 @@
import com.google.gerrit.client.projects.ProjectInfo;
import com.google.gerrit.client.projects.ProjectMap;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
+import com.google.gerrit.client.ui.FilteredUserInterface;
import com.google.gerrit.client.ui.HighlightingInlineHyperlink;
+import com.google.gerrit.client.ui.IgnoreOutdatedFilterResultsCallbackWrapper;
import com.google.gerrit.client.ui.ProjectSearchLink;
import com.google.gerrit.client.ui.ProjectsTable;
import com.google.gerrit.client.ui.Screen;
@@ -34,7 +36,7 @@
import com.google.gwt.user.client.ui.Label;
import com.google.gwtexpui.globalkey.client.NpTextBox;
-public class ProjectListScreen extends Screen {
+public class ProjectListScreen extends Screen implements FilteredUserInterface {
private ProjectsTable projects;
private NpTextBox filterTxt;
private String subname;
@@ -46,25 +48,19 @@
}
private void refresh() {
- final String mySubname = subname;
- ProjectMap.match(subname, new ScreenLoadCallback<ProjectMap>(this) {
- @Override
- protected void preDisplay(final ProjectMap result) {
- if ((mySubname == null && subname == null)
- || (mySubname != null && mySubname.equals(subname))) {
- display(result);
- }
- // Else ignore the result, the user has already changed subname and
- // the result is not relevant anymore. If multiple RPC's are fired
- // the results may come back out-of-order and a non-relevant result
- // could overwrite the correct result if not ignored.
- }
- });
+ ProjectMap.match(subname,
+ new IgnoreOutdatedFilterResultsCallbackWrapper<ProjectMap>(this,
+ new ScreenLoadCallback<ProjectMap>(this) {
+ @Override
+ protected void preDisplay(final ProjectMap result) {
+ projects.display(result);
+ }
+ }));
}
- private void display(final ProjectMap result) {
- projects.display(result);
- projects.finishDisplay();
+ @Override
+ public String getCurrentFilter() {
+ return subname;
}
@Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
index 3891edd..058e5d0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
@@ -383,6 +383,7 @@
remove.setTitle(Util.M.removeReviewer( //
FormatUtil.name(accountCache.get(ad.getAccount()))));
remove.setStyleName(Gerrit.RESOURCES.css().removeReviewer());
+ remove.addStyleName(Gerrit.RESOURCES.css().link());
remove.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
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 e3f7c97..2fd9643 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
@@ -92,6 +92,7 @@
String changeInfoBlockProject();
String changeInfoBlockBranch();
String changeInfoBlockTopic();
+ String changeInfoBlockTopicAlterTopicToolTip();
String changeInfoBlockUploaded();
String changeInfoBlockUpdated();
String changeInfoBlockStatus();
@@ -132,6 +133,7 @@
String revertChangeTitle();
String headingEditCommitMessage();
+ String editCommitMessageToolTip();
String titleEditCommitMessage();
String buttonAbandonChangeBegin();
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 c236b18..816b623 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
@@ -69,6 +69,7 @@
changeInfoBlockProject = Project
changeInfoBlockBranch = Branch
changeInfoBlockTopic = Topic
+changeInfoBlockTopicAlterTopicToolTip = Edit Topic
changeInfoBlockUploaded = Uploaded
changeInfoBlockUpdated = Updated
changeInfoBlockStatus = Status
@@ -117,6 +118,7 @@
revertChangeTitle = Code Review - Revert Merged Change
headingEditCommitMessage = Commit Message
+editCommitMessageToolTip = Edit Commit Message
titleEditCommitMessage = Create New Patch Set
buttonRestoreChangeBegin = Restore Change
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java
index f891990..5468f1a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java
@@ -156,6 +156,8 @@
if (changeDetail.canEditTopicName()) {
final Image edit = new Image(Gerrit.RESOURCES.edit());
+ edit.addStyleName(Gerrit.RESOURCES.css().link());
+ edit.setTitle(Util.C.changeInfoBlockTopicAlterTopicToolTip());
edit.addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) {
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 3dcd5d9..509f34c 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
@@ -98,14 +98,6 @@
public ChangeScreen(final Change.Id toShow) {
changeId = toShow;
openPatchSetId = null;
-
- // If we have any diff stored, make sure they are applicable to the
- // current change, discard them otherwise.
- //
- if (currentChangeId != null && !currentChangeId.equals(toShow)) {
- diffBaseId = null;
- }
- currentChangeId = toShow;
}
public ChangeScreen(final PatchSet.Id toShow) {
@@ -271,6 +263,7 @@
private void display(final ChangeDetail detail) {
displayTitle(detail.getChange().getKey(), detail.getChange().getSubject());
+ discardDiffBaseIfNotApplicable(detail.getChange().getId());
if (Status.MERGED == detail.getChange().getStatus()) {
includedInPanel.setVisible(true);
@@ -291,6 +284,7 @@
neededBy.display(detail.getNeededBy());
approvals.display(detail);
+ patchesList.clear();
if (detail.getCurrentPatchSetDetail().getInfo().getParents().size() > 1) {
patchesList.addItem(Util.C.autoMerge());
} else {
@@ -354,6 +348,13 @@
patchSetsBlock.setRegisterKeys(true);
}
+ private static void discardDiffBaseIfNotApplicable(final Change.Id toShow) {
+ if (currentChangeId != null && !currentChangeId.equals(toShow)) {
+ diffBaseId = null;
+ }
+ currentChangeId = toShow;
+ }
+
private void addComments(final ChangeDetail detail) {
comments.clear();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java
index e983b87..8d20f0c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java
@@ -123,6 +123,8 @@
false));
if (canEditCommitMessage) {
final Image edit = new Image(Gerrit.RESOURCES.edit());
+ edit.setTitle(Util.C.editCommitMessageToolTip());
+ edit.addStyleName(Gerrit.RESOURCES.css().link());
edit.addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) {
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 b1a780d..5d52a07 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
@@ -106,6 +106,9 @@
padding: 0.2em 0.2em 0.2em 0.5em;
}
+.link {
+ cursor: pointer;
+}
/** MenuScreen **/
.menuScreenMenuBar {
@@ -957,7 +960,6 @@
.changeInfoTopicPanel img {
float: right;
- cursor: pointer;
}
.changeInfoTopicPanel a {
@@ -1156,6 +1158,8 @@
margin-left: 1em;
margin-right: 5em;
font-weight: bold;
+ font-size: medium;
+ font-family: Arial Unicode;
}
/** Patch History Table **/
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupInfo.java
new file mode 100644
index 0000000..5fed85d
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupInfo.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.groups;
+
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class GroupInfo extends JavaScriptObject {
+ public final AccountGroup.Id getGroupId() {
+ return new AccountGroup.Id(groupId());
+ }
+
+ public final native int groupId() /*-{ return this.group_id; }-*/;
+ public final native String name() /*-{ return this.name; }-*/;
+ public final native String uuid() /*-{ return this.uuid; }-*/;
+ public final native String description() /*-{ return this.description; }-*/;
+ public final native boolean isVisibleToAll() /*-{ return this['is_visible_to_all'] ? true : false; }-*/;
+ public final native String ownerUuid() /*-{ return this.owner_uuid; }-*/;
+
+ protected GroupInfo() {
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupMap.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupMap.java
new file mode 100644
index 0000000..e3a984f0
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupMap.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.groups;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.RestApi;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+/** Groups available from {@code /groups/}. */
+public class GroupMap extends NativeMap<GroupInfo> {
+ public static void all(AsyncCallback<GroupMap> callback) {
+ new RestApi("/groups/")
+ .get(NativeMap.copyKeysIntoChildren(callback));
+ }
+
+ public static void my(AsyncCallback<GroupMap> callback) {
+ new RestApi("/groups/")
+ .addParameter("user", Gerrit.getUserAccount().getId().get())
+ .get(NativeMap.copyKeysIntoChildren(callback));
+ }
+
+ public static void match(String match, AsyncCallback<GroupMap> cb) {
+ if (match == null || "".equals(match)) {
+ all(cb);
+ } else {
+ new RestApi("/groups/")
+ .addParameter("m", match)
+ .get(NativeMap.copyKeysIntoChildren(cb));
+ }
+ }
+
+ protected GroupMap() {
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java
index 3f71e76..8e4583a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java
@@ -58,6 +58,7 @@
import com.google.gwtexpui.globalkey.client.GlobalKey;
import com.google.gwtexpui.globalkey.client.KeyCommand;
import com.google.gwtexpui.globalkey.client.KeyCommandSet;
+import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
import java.util.ArrayList;
import java.util.List;
@@ -148,6 +149,7 @@
// Prepare icons.
iconA = new Image(Gerrit.RESOURCES.addFileComment());
iconA.setTitle(PatchUtil.C.addFileCommentToolTip());
+ iconA.addStyleName(Gerrit.RESOURCES.css().link());
iconA.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
@@ -156,6 +158,7 @@
});
iconB = new Image(Gerrit.RESOURCES.addFileComment());
iconB.setTitle(PatchUtil.C.addFileCommentToolTip());
+ iconB.addStyleName(Gerrit.RESOURCES.css().link());
iconB.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
@@ -235,6 +238,27 @@
render(s, d);
}
+ protected boolean hasDifferences(final PatchScript script) {
+ // True if there are differences between the two patch sets
+ boolean hasEdits = !script.getEdits().isEmpty();
+ // True if this change is a mode change or a pure rename/copy
+ boolean hasMeta = !script.getPatchHeader().isEmpty();
+
+ return hasEdits || hasMeta;
+ }
+
+ protected void appendNoDifferences(SafeHtmlBuilder m) {
+ m.openTr();
+ m.openTd();
+ m.setAttribute("colspan", 5);
+ m.openDiv();
+ m.addStyleName(Gerrit.RESOURCES.css().patchNoDifference());
+ m.append(PatchUtil.C.noDifference());
+ m.closeDiv();
+ m.closeTd();
+ m.closeTr();
+ }
+
protected SparseHtmlFile getSparseHtmlFileA(PatchScript s) {
AccountDiffPreference dp = new AccountDiffPreference(s.getDiffPrefs());
dp.setShowWhitespaceErrors(false);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java
index 36eae80..1dd7c6a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java
@@ -39,7 +39,6 @@
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.Label;
import com.google.gwtexpui.globalkey.client.GlobalKey;
import com.google.gwtexpui.globalkey.client.KeyCommand;
import com.google.gwtexpui.globalkey.client.KeyCommandSet;
@@ -103,7 +102,6 @@
private HistoryTable historyTable;
private FlowPanel topPanel;
private FlowPanel contentPanel;
- private Label noDifference;
private AbstractPatchContentTable contentTable;
private CommitMessageBlock commitMessageBlock;
private NavLinks topNav;
@@ -250,10 +248,6 @@
topPanel = new FlowPanel();
add(topPanel);
- noDifference = new Label(PatchUtil.C.noDifference());
- noDifference.setStyleName(Gerrit.RESOURCES.css().patchNoDifference());
- noDifference.setVisible(false);
-
contentTable = createContentTable();
contentTable.fileList = fileList;
@@ -269,7 +263,6 @@
contentPanel.setStyleName(Gerrit.RESOURCES.css().unifiedTable());
}
- contentPanel.add(noDifference);
contentPanel.add(contentTable);
add(contentPanel);
add(bottomNav);
@@ -433,7 +426,6 @@
// True if this change is a mode change or a pure rename/copy
boolean hasMeta = !script.getPatchHeader().isEmpty();
- boolean hasDifferences = hasEdits || hasMeta;
boolean pureMetaChange = !hasEdits && hasMeta;
if (contentTable instanceof SideBySideTable && pureMetaChange && !contentTable.isDisplayBinary) {
@@ -449,12 +441,11 @@
setToken(Dispatcher.toPatchUnified(idSideA, patchKey));
}
- if (hasDifferences) {
- contentTable.display(patchKey, idSideA, idSideB, script, patchSetDetail);
- contentTable.display(script.getCommentDetail(), script.isExpandAllComments());
- contentTable.finishDisplay();
- }
- showPatch(hasDifferences);
+ contentTable.display(patchKey, idSideA, idSideB, script, patchSetDetail);
+ contentTable.display(script.getCommentDetail(), script.isExpandAllComments());
+ contentTable.finishDisplay();
+ contentTable.setRegisterKeys(isCurrentView());
+
settingsPanel.setEnableSmallFileFeatures(!script.isHugeFile());
settingsPanel.setEnableIntralineDifference(script.hasIntralineDifference());
settingsPanel.setEnabled(true);
@@ -496,12 +487,6 @@
}
}
- private void showPatch(final boolean showPatch) {
- noDifference.setVisible(!showPatch);
- contentTable.setVisible(showPatch);
- contentTable.setRegisterKeys(isCurrentView() && showPatch);
- }
-
public void setTopView(TopView tv) {
topView = tv;
topPanel.clear();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
index 4ce402e..d57830a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
@@ -105,84 +105,86 @@
lines.add(null);
}
- int lastA = 0;
- int lastB = 0;
- final boolean ignoreWS = script.isIgnoreWhitespace();
- a = getSparseHtmlFileA(script);
- b = getSparseHtmlFileB(script);
- final boolean intraline =
- script.getDiffPrefs().isIntralineDifference()
- && script.hasIntralineDifference();
- for (final EditList.Hunk hunk : script.getHunks()) {
- if (!hunk.isStartOfFile()) {
- appendSkipLine(nc, hunk.getCurB() - lastB);
- lines.add(new SkippedLine(lastA, lastB, hunk.getCurB() - lastB));
- }
+ if (hasDifferences(script)) {
+ int lastA = 0;
+ int lastB = 0;
+ final boolean ignoreWS = script.isIgnoreWhitespace();
+ a = getSparseHtmlFileA(script);
+ b = getSparseHtmlFileB(script);
+ final boolean intraline =
+ script.getDiffPrefs().isIntralineDifference()
+ && script.hasIntralineDifference();
+ for (final EditList.Hunk hunk : script.getHunks()) {
+ if (!hunk.isStartOfFile()) {
+ appendSkipLine(nc, hunk.getCurB() - lastB);
+ lines.add(new SkippedLine(lastA, lastB, hunk.getCurB() - lastB));
+ }
- while (hunk.next()) {
- if (hunk.isContextLine()) {
- openLine(nc);
- final SafeHtml ctx = a.getSafeHtmlLine(hunk.getCurA());
- appendLineNumber(nc, hunk.getCurA(), false);
- appendLineText(nc, CONTEXT, ctx, false, false);
- if (ignoreWS && b.contains(hunk.getCurB())) {
- appendLineText(nc, CONTEXT, b, hunk.getCurB(), false);
- } else {
- appendLineText(nc, CONTEXT, ctx, false, false);
- }
- appendLineNumber(nc, hunk.getCurB(), true);
- closeLine(nc);
- hunk.incBoth();
- lines.add(new PatchLine(CONTEXT, hunk.getCurA(), hunk.getCurB()));
-
- } else if (hunk.isModifiedLine()) {
- final boolean del = hunk.isDeletedA();
- final boolean ins = hunk.isInsertedB();
- final boolean full =
- intraline && hunk.getCurEdit().getType() != Edit.Type.REPLACE;
- openLine(nc);
-
- if (del) {
+ while (hunk.next()) {
+ if (hunk.isContextLine()) {
+ openLine(nc);
+ final SafeHtml ctx = a.getSafeHtmlLine(hunk.getCurA());
appendLineNumber(nc, hunk.getCurA(), false);
- appendLineText(nc, DELETE, a, hunk.getCurA(), full);
- hunk.incA();
- } else if (hunk.getCurEdit().getType() == Edit.Type.REPLACE) {
- appendLineNumber(nc, false);
- appendLineNone(nc, DELETE);
- } else {
- appendLineNumber(nc, false);
- appendLineNone(nc, CONTEXT);
- }
-
- if (ins) {
- appendLineText(nc, INSERT, b, hunk.getCurB(), full);
+ appendLineText(nc, CONTEXT, ctx, false, false);
+ if (ignoreWS && b.contains(hunk.getCurB())) {
+ appendLineText(nc, CONTEXT, b, hunk.getCurB(), false);
+ } else {
+ appendLineText(nc, CONTEXT, ctx, false, false);
+ }
appendLineNumber(nc, hunk.getCurB(), true);
- hunk.incB();
- } else if (hunk.getCurEdit().getType() == Edit.Type.REPLACE) {
- appendLineNone(nc, INSERT);
- appendLineNumber(nc, true);
- } else {
- appendLineNone(nc, CONTEXT);
- appendLineNumber(nc, true);
- }
+ closeLine(nc);
+ hunk.incBoth();
+ lines.add(new PatchLine(CONTEXT, hunk.getCurA(), hunk.getCurB()));
- closeLine(nc);
+ } else if (hunk.isModifiedLine()) {
+ final boolean del = hunk.isDeletedA();
+ final boolean ins = hunk.isInsertedB();
+ final boolean full =
+ intraline && hunk.getCurEdit().getType() != Edit.Type.REPLACE;
+ openLine(nc);
- if (del && ins) {
- lines.add(new PatchLine(REPLACE, hunk.getCurA(), hunk.getCurB()));
- } else if (del) {
- lines.add(new PatchLine(DELETE, hunk.getCurA(), -1));
- } else if (ins) {
- lines.add(new PatchLine(INSERT, -1, hunk.getCurB()));
+ if (del) {
+ appendLineNumber(nc, hunk.getCurA(), false);
+ appendLineText(nc, DELETE, a, hunk.getCurA(), full);
+ hunk.incA();
+ } else if (hunk.getCurEdit().getType() == Edit.Type.REPLACE) {
+ appendLineNumber(nc, false);
+ appendLineNone(nc, DELETE);
+ } else {
+ appendLineNumber(nc, false);
+ appendLineNone(nc, CONTEXT);
+ }
+
+ if (ins) {
+ appendLineText(nc, INSERT, b, hunk.getCurB(), full);
+ appendLineNumber(nc, hunk.getCurB(), true);
+ hunk.incB();
+ } else if (hunk.getCurEdit().getType() == Edit.Type.REPLACE) {
+ appendLineNone(nc, INSERT);
+ appendLineNumber(nc, true);
+ } else {
+ appendLineNone(nc, CONTEXT);
+ appendLineNumber(nc, true);
+ }
+
+ closeLine(nc);
+
+ if (del && ins) {
+ lines.add(new PatchLine(REPLACE, hunk.getCurA(), hunk.getCurB()));
+ } else if (del) {
+ lines.add(new PatchLine(DELETE, hunk.getCurA(), -1));
+ } else if (ins) {
+ lines.add(new PatchLine(INSERT, -1, hunk.getCurB()));
+ }
}
}
+ lastA = hunk.getCurA();
+ lastB = hunk.getCurB();
}
- lastA = hunk.getCurA();
- lastB = hunk.getCurB();
- }
- if (lastB != b.size()) {
- appendSkipLine(nc, b.size() - lastB);
- lines.add(new SkippedLine(lastA, lastB, b.size() - lastB));
+ if (lastB != b.size()) {
+ appendSkipLine(nc, b.size() - lastB);
+ lines.add(new SkippedLine(lastA, lastB, b.size() - lastB));
+ }
}
}else{
// Display the patch header for binary
@@ -190,14 +192,19 @@
appendFileHeader(nc, line);
}
}
+ if (!hasDifferences(script)) {
+ appendNoDifferences(nc);
+ }
resetHtml(nc);
populateTableHeader(script, detail);
- initScript(script);
- if (!isDisplayBinary) {
- for (int row = 0; row < lines.size(); row++) {
- setRowItem(row, lines.get(row));
- if (lines.get(row) instanceof SkippedLine) {
- createSkipLine(row, (SkippedLine) lines.get(row));
+ if (hasDifferences(script)) {
+ initScript(script);
+ if (!isDisplayBinary) {
+ for (int row = 0; row < lines.size(); row++) {
+ setRowItem(row, lines.get(row));
+ if (lines.get(row) instanceof SkippedLine) {
+ createSkipLine(row, (SkippedLine) lines.get(row));
+ }
}
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java
index c143acc..479ded7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java
@@ -266,76 +266,83 @@
nc.closeTr();
}
- final boolean syntaxHighlighting =
- script.getDiffPrefs().isSyntaxHighlighting();
- for (final EditList.Hunk hunk : script.getHunks()) {
- appendHunkHeader(nc, hunk);
- while (hunk.next()) {
- if (hunk.isContextLine()) {
- openLine(nc);
- appendLineNumberForSideA(nc, hunk.getCurA());
- appendLineNumberForSideB(nc, hunk.getCurB());
- appendLineText(nc, false, CONTEXT, a, hunk.getCurA());
- closeLine(nc);
- hunk.incBoth();
- lines.add(new PatchLine(CONTEXT, hunk.getCurA(), hunk.getCurB()));
+ if (hasDifferences(script)) {
+ final boolean syntaxHighlighting =
+ script.getDiffPrefs().isSyntaxHighlighting();
+ for (final EditList.Hunk hunk : script.getHunks()) {
+ appendHunkHeader(nc, hunk);
+ while (hunk.next()) {
+ if (hunk.isContextLine()) {
+ openLine(nc);
+ appendLineNumberForSideA(nc, hunk.getCurA());
+ appendLineNumberForSideB(nc, hunk.getCurB());
+ appendLineText(nc, false, CONTEXT, a, hunk.getCurA());
+ closeLine(nc);
+ hunk.incBoth();
+ lines.add(new PatchLine(CONTEXT, hunk.getCurA(), hunk.getCurB()));
- } else if (hunk.isDeletedA()) {
- openLine(nc);
- appendLineNumberForSideA(nc, hunk.getCurA());
- padLineNumberForSideB(nc);
- appendLineText(nc, syntaxHighlighting, DELETE, a, hunk.getCurA());
- closeLine(nc);
- hunk.incA();
- lines.add(new PatchLine(DELETE, hunk.getCurA(), -1));
- if (a.size() == hunk.getCurA()
- && script.getA().isMissingNewlineAtEnd()) {
- appendNoLF(nc);
- }
+ } else if (hunk.isDeletedA()) {
+ openLine(nc);
+ appendLineNumberForSideA(nc, hunk.getCurA());
+ padLineNumberForSideB(nc);
+ appendLineText(nc, syntaxHighlighting, DELETE, a, hunk.getCurA());
+ closeLine(nc);
+ hunk.incA();
+ lines.add(new PatchLine(DELETE, hunk.getCurA(), -1));
+ if (a.size() == hunk.getCurA()
+ && script.getA().isMissingNewlineAtEnd()) {
+ appendNoLF(nc);
+ }
- } else if (hunk.isInsertedB()) {
- openLine(nc);
- padLineNumberForSideA(nc);
- appendLineNumberForSideB(nc, hunk.getCurB());
- appendLineText(nc, syntaxHighlighting, INSERT, b, hunk.getCurB());
- closeLine(nc);
- hunk.incB();
- lines.add(new PatchLine(INSERT, -1, hunk.getCurB()));
- if (b.size() == hunk.getCurB()
- && script.getB().isMissingNewlineAtEnd()) {
- appendNoLF(nc);
+ } else if (hunk.isInsertedB()) {
+ openLine(nc);
+ padLineNumberForSideA(nc);
+ appendLineNumberForSideB(nc, hunk.getCurB());
+ appendLineText(nc, syntaxHighlighting, INSERT, b, hunk.getCurB());
+ closeLine(nc);
+ hunk.incB();
+ lines.add(new PatchLine(INSERT, -1, hunk.getCurB()));
+ if (b.size() == hunk.getCurB()
+ && script.getB().isMissingNewlineAtEnd()) {
+ appendNoLF(nc);
+ }
}
}
}
}
}
+ if (!hasDifferences(script)) {
+ appendNoDifferences(nc);
+ }
resetHtml(nc);
populateTableHeader(script, detail);
- initScript(script);
- if (!isDisplayBinary) {
- int row = script.getPatchHeader().size();
- final CellFormatter fmt = table.getCellFormatter();
- final Iterator<PatchLine> iLine = lines.iterator();
- while (iLine.hasNext()) {
- final PatchLine l = iLine.next();
- final String n;
- switch (l.getType()) {
- case CONTEXT:
- n = Gerrit.RESOURCES.css().diffTextCONTEXT();
- break;
- case DELETE:
- n = Gerrit.RESOURCES.css().diffTextDELETE();
- break;
- case INSERT:
- n = Gerrit.RESOURCES.css().diffTextINSERT();
- break;
- default:
- continue;
+ if (hasDifferences(script)) {
+ initScript(script);
+ if (!isDisplayBinary) {
+ int row = script.getPatchHeader().size();
+ final CellFormatter fmt = table.getCellFormatter();
+ final Iterator<PatchLine> iLine = lines.iterator();
+ while (iLine.hasNext()) {
+ final PatchLine l = iLine.next();
+ final String n;
+ switch (l.getType()) {
+ case CONTEXT:
+ n = Gerrit.RESOURCES.css().diffTextCONTEXT();
+ break;
+ case DELETE:
+ n = Gerrit.RESOURCES.css().diffTextDELETE();
+ break;
+ case INSERT:
+ n = Gerrit.RESOURCES.css().diffTextINSERT();
+ break;
+ default:
+ continue;
+ }
+ while (!fmt.getStyleName(row, PC).contains(n)) {
+ row++;
+ }
+ setRowItem(row++, l);
}
- while (!fmt.getStyleName(row, PC).contains(n)) {
- row++;
- }
- setRowItem(row++, l);
}
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FilteredUserInterface.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FilteredUserInterface.java
new file mode 100644
index 0000000..02244d0
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FilteredUserInterface.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.ui;
+
+public interface FilteredUserInterface {
+ /**
+ * Return the value by which the user interface is currently filtered.
+ *
+ * @return value by which the user interface is currently filtered,
+ * <code>null</code> or empty String if currently no filter is applied
+ */
+ public String getCurrentFilter();
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/IgnoreOutdatedFilterResultsCallbackWrapper.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/IgnoreOutdatedFilterResultsCallbackWrapper.java
new file mode 100644
index 0000000..c9cadcc
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/IgnoreOutdatedFilterResultsCallbackWrapper.java
@@ -0,0 +1,50 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.ui;
+
+import com.google.gerrit.client.rpc.GerritCallback;
+
+/**
+ * GerritCallback to be used on user interfaces that allow filtering to handle
+ * RPC's that request filtering. The user may change the filter quickly so that
+ * a response may be outdated when the client receives it. In this case the
+ * response must be ignored because the responses to RCP's may come out-of-order
+ * and an outdated response would overwrite the correct result which was
+ * received before.
+ */
+public class IgnoreOutdatedFilterResultsCallbackWrapper<T> extends GerritCallback<T> {
+ private final FilteredUserInterface filteredUI;
+ private final String myFilter;
+ private final GerritCallback<T> cb;
+
+ public IgnoreOutdatedFilterResultsCallbackWrapper(
+ final FilteredUserInterface filteredUI, final GerritCallback<T> cb) {
+ this.filteredUI = filteredUI;
+ this.myFilter = filteredUI.getCurrentFilter();
+ this.cb = cb;
+ }
+
+ @Override
+ public void onSuccess(final T result) {
+ if ((myFilter == null && filteredUI.getCurrentFilter() == null)
+ || (myFilter != null && myFilter.equals(filteredUI.getCurrentFilter()))) {
+ cb.onSuccess(result);
+ }
+ // Else ignore the result, the user has already changed the filter
+ // and the result is not relevant anymore. If multiple RPC's are
+ // fired the results may come back out-of-order and a non-relevant
+ // result could overwrite the correct result if not ignored.
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java
index 9f0d6af..cac7667 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java
@@ -36,7 +36,7 @@
import com.google.gwtexpui.user.client.PluginSafeDialogBox;
/** It creates a popup containing all the projects. */
-public class ProjectListPopup {
+public class ProjectListPopup implements FilteredUserInterface {
private HighlightingProjectsTable projectsTab;
private PluginSafeDialogBox popup;
private NpTextBox filterTxt;
@@ -174,27 +174,22 @@
}
protected void populateProjects() {
- final String mySubname = subname;
- ProjectMap.match(subname, new GerritCallback<ProjectMap>() {
- @Override
- public void onSuccess(final ProjectMap result) {
- if ((mySubname == null && subname == null)
- || (mySubname != null && mySubname.equals(subname))) {
- display(result);
- }
- // Else ignore the result, the user has already changed subname and
- // the result is not relevant anymore. If multiple RPC's are fired
- // the results may come back out-of-order and a non-relevant result
- // could overwrite the correct result if not ignored.
- }
- });
+ ProjectMap.match(subname,
+ new IgnoreOutdatedFilterResultsCallbackWrapper<ProjectMap>(this,
+ new GerritCallback<ProjectMap>() {
+ @Override
+ public void onSuccess(final ProjectMap result) {
+ projectsTab.display(result, subname);
+ if (firstPopupLoad) { // Display was delayed until table was loaded
+ firstPopupLoad = false;
+ displayPopup();
+ }
+ }
+ }));
}
- private void display(final ProjectMap result) {
- projectsTab.display(result, subname);
- if (firstPopupLoad) { // Display was delayed until table was loaded
- firstPopupLoad = false;
- displayPopup();
- }
+ @Override
+ public String getCurrentFilter() {
+ return subname;
}
}
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 5667996..8d9d354 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
@@ -27,6 +27,7 @@
import com.google.gerrit.httpd.rpc.account.AccountsRestApiServlet;
import com.google.gerrit.httpd.rpc.change.ChangesRestApiServlet;
import com.google.gerrit.httpd.rpc.change.DeprecatedChangeQueryServlet;
+import com.google.gerrit.httpd.rpc.group.GroupsRestApiServlet;
import com.google.gerrit.httpd.rpc.project.ProjectsRestApiServlet;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
@@ -97,6 +98,7 @@
filter("/a/*").through(RequireIdentifiedUserFilter.class);
serveRegex("^/(?:a/)?accounts/(.*)$").with(AccountsRestApiServlet.class);
serveRegex("^/(?:a/)?changes/(.*)$").with(ChangesRestApiServlet.class);
+ serveRegex("^/(?:a/)?groups/(.*)?$").with(GroupsRestApiServlet.class);
serveRegex("^/(?:a/)?projects/(.*)?$").with(ProjectsRestApiServlet.class);
if (cfg.deprecatedQuery) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountModule.java
index 957f339..745c7ba 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountModule.java
@@ -34,10 +34,8 @@
factory(DeleteExternalIds.Factory.class);
factory(ExternalIdDetailFactory.Factory.class);
factory(GroupDetailHandler.Factory.class);
- factory(MyGroupsFactory.Factory.class);
factory(RegisterNewEmailSender.Factory.class);
factory(RenameGroup.Factory.class);
- factory(VisibleGroupsHandler.Factory.class);
}
});
rpc(AccountSecurityImpl.class);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
index b62a10b..07d7690 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
@@ -18,10 +18,10 @@
import com.google.gerrit.common.data.AccountSecurity;
import com.google.gerrit.common.data.ContributorAgreement;
import com.google.gerrit.common.errors.ContactInformationStoreException;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.common.errors.InvalidSshKeyException;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.common.errors.NoSuchEntityException;
-import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.client.Account;
@@ -47,7 +47,6 @@
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.contact.ContactStore;
-import com.google.gerrit.server.mail.EmailException;
import com.google.gerrit.server.mail.EmailTokenVerifier;
import com.google.gerrit.server.mail.RegisterNewEmailSender;
import com.google.gerrit.server.project.ProjectCache;
@@ -86,7 +85,6 @@
private final ChangeUserName.CurrentUser changeUserNameFactory;
private final DeleteExternalIds.Factory deleteExternalIdsFactory;
private final ExternalIdDetailFactory.Factory externalIdDetailFactory;
- private final MyGroupsFactory.Factory myGroupsFactory;
private final ChangeHooks hooks;
private final GroupCache groupCache;
@@ -104,7 +102,6 @@
final ChangeUserName.CurrentUser changeUserNameFactory,
final DeleteExternalIds.Factory deleteExternalIdsFactory,
final ExternalIdDetailFactory.Factory externalIdDetailFactory,
- final MyGroupsFactory.Factory myGroupsFactory,
final ChangeHooks hooks, final GroupCache groupCache) {
super(schema, currentUser);
contactStore = cs;
@@ -126,7 +123,6 @@
this.changeUserNameFactory = changeUserNameFactory;
this.deleteExternalIdsFactory = deleteExternalIdsFactory;
this.externalIdDetailFactory = externalIdDetailFactory;
- this.myGroupsFactory = myGroupsFactory;
this.hooks = hooks;
this.groupCache = groupCache;
}
@@ -211,16 +207,6 @@
externalIdDetailFactory.create().to(callback);
}
- @Override
- public void myGroups(final AsyncCallback<List<AccountGroup>> callback) {
- run(callback, new Action<List<AccountGroup>>() {
- public List<AccountGroup> run(final ReviewDb db) throws OrmException,
- NoSuchGroupException, Failure {
- return myGroupsFactory.create().call();
- }
- });
- }
-
public void deleteExternalIds(final Set<AccountExternalId.Key> keys,
final AsyncCallback<Set<AccountExternalId.Key>> callback) {
deleteExternalIdsFactory.create(keys).to(callback);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java
index 67a23f0..1651c2d 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java
@@ -16,7 +16,6 @@
import com.google.gerrit.common.data.GroupAdminService;
import com.google.gerrit.common.data.GroupDetail;
-import com.google.gerrit.common.data.GroupList;
import com.google.gerrit.common.data.GroupOptions;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.errors.InactiveAccountException;
@@ -69,7 +68,6 @@
private final CreateGroup.Factory createGroupFactory;
private final RenameGroup.Factory renameGroupFactory;
private final GroupDetailHandler.Factory groupDetailFactory;
- private final VisibleGroupsHandler.Factory visibleGroupsFactory;
@Inject
GroupAdminServiceImpl(final Provider<ReviewDb> schema,
@@ -84,8 +82,7 @@
final GroupControl.Factory groupControlFactory,
final CreateGroup.Factory createGroupFactory,
final RenameGroup.Factory renameGroupFactory,
- final GroupDetailHandler.Factory groupDetailFactory,
- final VisibleGroupsHandler.Factory visibleGroupsFactory) {
+ final GroupDetailHandler.Factory groupDetailFactory) {
super(schema, currentUser);
this.accountCache = accountCache;
this.groupIncludeCache = groupIncludeCache;
@@ -98,11 +95,6 @@
this.createGroupFactory = createGroupFactory;
this.renameGroupFactory = renameGroupFactory;
this.groupDetailFactory = groupDetailFactory;
- this.visibleGroupsFactory = visibleGroupsFactory;
- }
-
- public void visibleGroups(final AsyncCallback<GroupList> callback) {
- visibleGroupsFactory.create().to(callback);
}
public void createGroup(final String newName,
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/MyGroupsFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/MyGroupsFactory.java
deleted file mode 100644
index 33ce371..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/MyGroupsFactory.java
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (C) 2009 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.account;
-
-import com.google.gerrit.common.errors.NoSuchGroupException;
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.VisibleGroups;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-
-import java.util.List;
-
-class MyGroupsFactory extends Handler<List<AccountGroup>> {
- interface Factory {
- MyGroupsFactory create();
- }
-
- private final VisibleGroups.Factory visibleGroupsFactory;
- private final IdentifiedUser user;
-
- @Inject
- MyGroupsFactory(final VisibleGroups.Factory visibleGroupsFactory, final IdentifiedUser user) {
- this.visibleGroupsFactory = visibleGroupsFactory;
- this.user = user;
- }
-
- @Override
- public List<AccountGroup> call() throws OrmException, NoSuchGroupException {
- final VisibleGroups visibleGroups = visibleGroupsFactory.create();
- return visibleGroups.get(user).getGroups();
- }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/VisibleGroupsHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/VisibleGroupsHandler.java
deleted file mode 100644
index 54f91f7..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/VisibleGroupsHandler.java
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (C) 2011 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.account;
-
-import com.google.gerrit.common.data.GroupList;
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.server.account.VisibleGroups;
-import com.google.inject.Inject;
-
-public class VisibleGroupsHandler extends Handler<GroupList> {
-
- interface Factory {
- VisibleGroupsHandler create();
- }
-
- private final VisibleGroups.Factory visibleGroupsFactory;
-
- @Inject
- VisibleGroupsHandler(final VisibleGroups.Factory visibleGroupsFactory) {
- this.visibleGroupsFactory = visibleGroupsFactory;
- }
-
- @Override
- public GroupList call() throws Exception {
- return visibleGroupsFactory.create().get();
- }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/EditCommitMessageHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/EditCommitMessageHandler.java
index c3e5d4f..c38bda7 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/EditCommitMessageHandler.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/EditCommitMessageHandler.java
@@ -16,6 +16,7 @@
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.data.ChangeDetail;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.client.Change;
@@ -27,19 +28,22 @@
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.mail.CommitMessageEditedSender;
-import com.google.gerrit.server.mail.EmailException;
+import com.google.gerrit.server.git.validators.CommitValidators;
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.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.ssh.NoSshInfo;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
import java.io.IOException;
@@ -63,6 +67,7 @@
private final String message;
private final ChangeHooks hooks;
+ private final CommitValidators.Factory commitValidatorsFactory;
private final GitRepositoryManager gitManager;
private final PatchSetInfoFactory patchSetInfoFactory;
@@ -76,6 +81,7 @@
final CommitMessageEditedSender.Factory commitMessageEditedSenderFactory,
@Assisted final PatchSet.Id patchSetId,
@Assisted @Nullable final String message, final ChangeHooks hooks,
+ final CommitValidators.Factory commitValidatorsFactory,
final GitRepositoryManager gitManager,
final PatchSetInfoFactory patchSetInfoFactory,
final GitReferenceUpdated replication,
@@ -89,6 +95,7 @@
this.patchSetId = patchSetId;
this.message = message;
this.hooks = hooks;
+ this.commitValidatorsFactory = commitValidatorsFactory;
this.gitManager = gitManager;
this.patchSetInfoFactory = patchSetInfoFactory;
@@ -109,10 +116,22 @@
"Not allowed to add new Patch Sets to: " + changeId.toString());
}
- ChangeUtil.editCommitMessage(patchSetId, currentUser, message, db,
- commitMessageEditedSenderFactory, hooks, gitManager, patchSetInfoFactory,
- replication, myIdent);
+ final Repository git;
+ try {
+ git = gitManager.openRepository(db.changes().get(changeId).getProject());
+ } catch (RepositoryNotFoundException e) {
+ throw new NoSuchChangeException(changeId, e);
+ }
+ try {
+ CommitValidators commitValidators =
+ commitValidatorsFactory.create(control.getRefControl(), new NoSshInfo(), git);
- return changeDetailFactory.create(changeId).call();
+ ChangeUtil.editCommitMessage(patchSetId, control.getRefControl(), commitValidators, currentUser, message, db,
+ commitMessageEditedSenderFactory, hooks, git, patchSetInfoFactory, replication, myIdent);
+
+ return changeDetailFactory.create(changeId).call();
+ } finally {
+ git.close();
+ }
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChangeHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChangeHandler.java
index 465ba42..b47c8f2 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChangeHandler.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChangeHandler.java
@@ -15,12 +15,12 @@
package com.google.gerrit.httpd.rpc.changedetail;
import com.google.gerrit.common.data.ChangeDetail;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.changedetail.RebaseChange;
-import com.google.gerrit.server.mail.EmailException;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/group/GroupsRestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/group/GroupsRestApiServlet.java
new file mode 100644
index 0000000..04dc747
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/group/GroupsRestApiServlet.java
@@ -0,0 +1,32 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.httpd.rpc.group;
+
+import com.google.gerrit.httpd.restapi.RestApiServlet;
+import com.google.gerrit.server.group.GroupsCollection;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+@Singleton
+public class GroupsRestApiServlet extends RestApiServlet {
+ private static final long serialVersionUID = 1L;
+
+ @Inject
+ GroupsRestApiServlet(RestApiServlet.Globals globals,
+ Provider<GroupsCollection> groups) {
+ super(globals, groups);
+ }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
index 7b3b8e7..ea95e96 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
@@ -121,6 +121,9 @@
if (pc.isOwner()) {
local.add(section);
ownerOf.add(name);
+
+ } else if (metaConfigControl.isVisible()) {
+ local.add(section);
}
} else if (RefConfigSection.isValid(name)) {
diff --git a/gerrit-package-plugins/pom.xml b/gerrit-package-plugins/pom.xml
index 9f11bb3..5a4cdbb 100644
--- a/gerrit-package-plugins/pom.xml
+++ b/gerrit-package-plugins/pom.xml
@@ -43,7 +43,7 @@
<dependency>
<groupId>com.googlesource.gerrit.plugins.replication</groupId>
<artifactId>replication</artifactId>
- <version>1.0-rc0</version>
+ <version>1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/mysql_nextval.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/mysql_nextval.sql
deleted file mode 100644
index 2479010..0000000
--- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/mysql_nextval.sql
+++ /dev/null
@@ -1,14 +0,0 @@
--- Gerrit 2 : MySQL
---
-delimiter //
-
-CREATE FUNCTION nextval_account_id ()
- RETURNS BIGINT
- LANGUAGE SQL
- NOT DETERMINISTIC
- MODIFIES SQL DATA
-BEGIN
- INSERT INTO account_id (s) VALUES (NULL);
- RETURN LAST_INSERT_ID();
-END;
-//
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
index fda2907..ede8e74 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
@@ -16,6 +16,7 @@
import com.google.common.base.CharMatcher;
import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.PatchSet;
@@ -26,16 +27,19 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.config.TrackingFooter;
import com.google.gerrit.server.config.TrackingFooters;
+import com.google.gerrit.server.events.CommitReceivedEvent;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MergeOp;
+import com.google.gerrit.server.git.validators.CommitValidationException;
+import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.mail.CommitMessageEditedSender;
-import com.google.gerrit.server.mail.EmailException;
import com.google.gerrit.server.mail.RevertedSender;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.util.IdGenerator;
import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmConcurrencyException;
@@ -43,7 +47,6 @@
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
@@ -54,6 +57,7 @@
import org.eclipse.jgit.revwalk.FooterLine;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.util.Base64;
import org.eclipse.jgit.util.ChangeIdUtil;
import org.eclipse.jgit.util.NB;
@@ -187,14 +191,17 @@
db.patchSetAncestors().insert(toInsert);
}
- public static Change.Id revert(final PatchSet.Id patchSetId,
- final IdentifiedUser user, final String message, final ReviewDb db,
+ public static Change.Id revert(final RefControl refControl,
+ final PatchSet.Id patchSetId, final IdentifiedUser user,
+ final CommitValidators commitValidators,
+ final String message, final ReviewDb db,
final RevertedSender.Factory revertedSenderFactory,
- final ChangeHooks hooks, GitRepositoryManager gitManager,
+ final ChangeHooks hooks, Repository git,
final PatchSetInfoFactory patchSetInfoFactory,
- final GitReferenceUpdated replication, PersonIdent myIdent)
- throws NoSuchChangeException, EmailException, OrmException,
- MissingObjectException, IncorrectObjectTypeException, IOException {
+ final GitReferenceUpdated replication, PersonIdent myIdent,
+ String canonicalWebUrl) throws NoSuchChangeException, EmailException,
+ OrmException, MissingObjectException, IncorrectObjectTypeException,
+ IOException, InvalidChangeOperationException {
final Change.Id changeId = patchSetId.getParentKey();
final PatchSet patch = db.patchSets().get(patchSetId);
if (patch == null) {
@@ -202,13 +209,6 @@
}
final Change changeToRevert = db.changes().get(changeId);
- final Repository git;
- try {
- git = gitManager.openRepository(changeToRevert.getProject());
- } catch (RepositoryNotFoundException e) {
- throw new NoSuchChangeException(changeId, e);
- }
-
final RevWalk revWalk = new RevWalk(git);
try {
RevCommit commitToRevert =
@@ -255,6 +255,18 @@
ps.setUploader(change.getOwner());
ps.setRevision(new RevId(revertCommit.name()));
+ CommitReceivedEvent commitReceivedEvent =
+ new CommitReceivedEvent(new ReceiveCommand(ObjectId.zeroId(),
+ revertCommit.getId(), ps.getRefName()), refControl
+ .getProjectControl().getProject(), refControl.getRefName(),
+ revertCommit, user);
+
+ try {
+ commitValidators.validateForRevertCommits(commitReceivedEvent);
+ } catch (CommitValidationException e) {
+ throw new InvalidChangeOperationException(e.getMessage());
+ }
+
change.setCurrentPatchSet(patchSetInfoFactory.get(revertCommit, ps.getId()));
ChangeUtil.updated(change);
@@ -300,14 +312,14 @@
return change.getId();
} finally {
revWalk.release();
- git.close();
}
}
public static Change.Id editCommitMessage(final PatchSet.Id patchSetId,
+ final RefControl refControl, CommitValidators commitValidators,
final IdentifiedUser user, final String message, final ReviewDb db,
final CommitMessageEditedSender.Factory commitMessageEditedSenderFactory,
- final ChangeHooks hooks, GitRepositoryManager gitManager,
+ final ChangeHooks hooks, Repository git,
final PatchSetInfoFactory patchSetInfoFactory,
final GitReferenceUpdated replication, PersonIdent myIdent)
throws NoSuchChangeException, EmailException, OrmException,
@@ -323,128 +335,129 @@
throw new InvalidChangeOperationException("The commit message cannot be empty");
}
- final Repository git;
+ final RevWalk revWalk = new RevWalk(git);
try {
- git = gitManager.openRepository(db.changes().get(changeId).getProject());
- } catch (RepositoryNotFoundException e) {
- throw new NoSuchChangeException(changeId, e);
- }
+ RevCommit commit =
+ revWalk.parseCommit(ObjectId.fromString(patch.getRevision().get()));
+ if (commit.getFullMessage().equals(message)) {
+ throw new InvalidChangeOperationException("New commit message cannot be same as existing commit message");
+ }
- try {
- final RevWalk revWalk = new RevWalk(git);
+ Date now = myIdent.getWhen();
+ Change change = db.changes().get(changeId);
+ PersonIdent authorIdent =
+ user.newCommitterIdent(now, myIdent.getTimeZone());
+
+ CommitBuilder commitBuilder = new CommitBuilder();
+ commitBuilder.setTreeId(commit.getTree());
+ commitBuilder.setParentIds(commit.getParents());
+ commitBuilder.setAuthor(commit.getAuthorIdent());
+ commitBuilder.setCommitter(authorIdent);
+ commitBuilder.setMessage(message);
+
+ RevCommit newCommit;
+ final ObjectInserter oi = git.newObjectInserter();
try {
- RevCommit commit =
- revWalk.parseCommit(ObjectId.fromString(patch.getRevision().get()));
- if (commit.getFullMessage().equals(message)) {
- throw new InvalidChangeOperationException("New commit message cannot be same as existing commit message");
+ ObjectId id = oi.insert(commitBuilder);
+ oi.flush();
+ newCommit = revWalk.parseCommit(id);
+ } finally {
+ oi.release();
+ }
+
+ final PatchSet originalPS = db.patchSets().get(patchSetId);
+ PatchSet.Id id = nextPatchSetId(git, change.currentPatchSetId());
+ final PatchSet newPatchSet = new PatchSet(id);
+ newPatchSet.setCreatedOn(new Timestamp(now.getTime()));
+ newPatchSet.setUploader(user.getAccountId());
+ newPatchSet.setRevision(new RevId(newCommit.name()));
+ newPatchSet.setDraft(originalPS.isDraft());
+
+ final PatchSetInfo info =
+ patchSetInfoFactory.get(newCommit, newPatchSet.getId());
+
+ CommitReceivedEvent commitReceivedEvent =
+ new CommitReceivedEvent(new ReceiveCommand(ObjectId.zeroId(),
+ newCommit.getId(), newPatchSet.getRefName()), refControl
+ .getProjectControl().getProject(), refControl.getRefName(),
+ newCommit, user);
+
+ try {
+ commitValidators.validateForReceiveCommits(commitReceivedEvent);
+ } catch (CommitValidationException e) {
+ throw new InvalidChangeOperationException(e.getMessage());
+ }
+
+ final RefUpdate ru = git.updateRef(newPatchSet.getRefName());
+ ru.setExpectedOldObjectId(ObjectId.zeroId());
+ ru.setNewObjectId(newCommit);
+ ru.disableRefLog();
+ if (ru.update(revWalk) != RefUpdate.Result.NEW) {
+ throw new IOException(String.format(
+ "Failed to create ref %s in %s: %s", newPatchSet.getRefName(),
+ change.getDest().getParentKey().get(), ru.getResult()));
+ }
+ replication.fire(change.getProject(), ru.getName());
+
+ db.changes().beginTransaction(change.getId());
+ try {
+ Change updatedChange = db.changes().get(change.getId());
+ if (updatedChange != null && updatedChange.getStatus().isOpen()) {
+ change = updatedChange;
+ } else {
+ throw new InvalidChangeOperationException(String.format(
+ "Change %s is closed", change.getId()));
}
- Date now = myIdent.getWhen();
- Change change = db.changes().get(changeId);
- PersonIdent authorIdent =
- user.newCommitterIdent(now, myIdent.getTimeZone());
-
- CommitBuilder commitBuilder = new CommitBuilder();
- commitBuilder.setTreeId(commit.getTree());
- commitBuilder.setParentIds(commit.getParents());
- commitBuilder.setAuthor(commit.getAuthorIdent());
- commitBuilder.setCommitter(authorIdent);
- commitBuilder.setMessage(message);
-
- RevCommit newCommit;
- final ObjectInserter oi = git.newObjectInserter();
- try {
- ObjectId id = oi.insert(commitBuilder);
- oi.flush();
- newCommit = revWalk.parseCommit(id);
- } finally {
- oi.release();
- }
-
- final PatchSet originalPS = db.patchSets().get(patchSetId);
- PatchSet.Id id = nextPatchSetId(git, change.currentPatchSetId());
- final PatchSet newPatchSet = new PatchSet(id);
- newPatchSet.setCreatedOn(new Timestamp(now.getTime()));
- newPatchSet.setUploader(user.getAccountId());
- newPatchSet.setRevision(new RevId(newCommit.name()));
- newPatchSet.setDraft(originalPS.isDraft());
-
- final PatchSetInfo info =
- patchSetInfoFactory.get(newCommit, newPatchSet.getId());
-
- final RefUpdate ru = git.updateRef(newPatchSet.getRefName());
- ru.setExpectedOldObjectId(ObjectId.zeroId());
- ru.setNewObjectId(newCommit);
- ru.disableRefLog();
- if (ru.update(revWalk) != RefUpdate.Result.NEW) {
- throw new IOException(String.format(
- "Failed to create ref %s in %s: %s", newPatchSet.getRefName(),
- change.getDest().getParentKey().get(), ru.getResult()));
- }
- replication.fire(change.getProject(), ru.getName());
-
- db.changes().beginTransaction(change.getId());
- try {
- Change updatedChange = db.changes().get(change.getId());
- if (updatedChange != null && updatedChange.getStatus().isOpen()) {
- change = updatedChange;
- } else {
- throw new InvalidChangeOperationException(String.format(
- "Change %s is closed", change.getId()));
- }
-
- ChangeUtil.insertAncestors(db, newPatchSet.getId(), commit);
- db.patchSets().insert(Collections.singleton(newPatchSet));
- updatedChange =
- db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
- @Override
- public Change update(Change change) {
- if (change.getStatus().isClosed()) {
- return null;
- }
- if (!change.currentPatchSetId().equals(patchSetId)) {
- return null;
- }
- if (change.getStatus() != Change.Status.DRAFT) {
- change.setStatus(Change.Status.NEW);
- }
- change.setLastSha1MergeTested(null);
- change.setCurrentPatchSet(info);
- ChangeUtil.updated(change);
- return change;
+ ChangeUtil.insertAncestors(db, newPatchSet.getId(), commit);
+ db.patchSets().insert(Collections.singleton(newPatchSet));
+ updatedChange =
+ db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
+ @Override
+ public Change update(Change change) {
+ if (change.getStatus().isClosed()) {
+ return null;
}
- });
- if (updatedChange != null) {
- change = updatedChange;
- } else {
- throw new InvalidChangeOperationException(String.format(
- "Change %s was modified", change.getId()));
- }
+ if (!change.currentPatchSetId().equals(patchSetId)) {
+ return null;
+ }
+ if (change.getStatus() != Change.Status.DRAFT) {
+ change.setStatus(Change.Status.NEW);
+ }
+ change.setLastSha1MergeTested(null);
+ change.setCurrentPatchSet(info);
+ ChangeUtil.updated(change);
+ return change;
+ }
+ });
+ if (updatedChange != null) {
+ change = updatedChange;
+ } else {
+ throw new InvalidChangeOperationException(String.format(
+ "Change %s was modified", change.getId()));
+ }
- final ChangeMessage cmsg =
- new ChangeMessage(new ChangeMessage.Key(changeId,
- ChangeUtil.messageUUID(db)), user.getAccountId(), patchSetId);
- final String msg = "Patch Set " + newPatchSet.getPatchSetId() + ": Commit message was updated";
- cmsg.setMessage(msg);
- db.changeMessages().insert(Collections.singleton(cmsg));
- db.commit();
+ final ChangeMessage cmsg =
+ new ChangeMessage(new ChangeMessage.Key(changeId,
+ ChangeUtil.messageUUID(db)), user.getAccountId(), patchSetId);
+ final String msg = "Patch Set " + newPatchSet.getPatchSetId() + ": Commit message was updated";
+ cmsg.setMessage(msg);
+ db.changeMessages().insert(Collections.singleton(cmsg));
+ db.commit();
final CommitMessageEditedSender cm = commitMessageEditedSenderFactory.create(change);
cm.setFrom(user.getAccountId());
cm.setChangeMessage(cmsg);
cm.send();
- } finally {
- db.rollback();
- }
-
- hooks.doPatchsetCreatedHook(change, newPatchSet, db);
-
- return change.getId();
} finally {
- revWalk.release();
+ db.rollback();
}
+
+ hooks.doPatchsetCreatedHook(change, newPatchSet, db);
+
+ return change.getId();
} finally {
- git.close();
+ revWalk.release();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java
index 9141566..06a3eba 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java
@@ -84,6 +84,10 @@
this(who, GroupDescriptions.forAccountGroup(ag));
}
+ public GroupDescription.Basic getGroup() {
+ return group;
+ }
+
public CurrentUser getCurrentUser() {
return user;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/VisibleGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/VisibleGroups.java
index d3b2c83..54f0733 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/VisibleGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/VisibleGroups.java
@@ -14,9 +14,9 @@
package com.google.gerrit.server.account;
+import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
-import com.google.gerrit.common.data.GroupList;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -28,6 +28,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -43,6 +44,7 @@
private boolean onlyVisibleToAll;
private AccountGroup.Type groupType;
+ private String match;
@Inject
VisibleGroups(final Provider<IdentifiedUser> currentUser,
@@ -61,11 +63,15 @@
this.groupType = groupType;
}
- public GroupList get() {
- return createGroupList(filterGroups(groupCache.all()));
+ public void setMatch(final String match) {
+ this.match = match;
}
- public GroupList get(final Collection<ProjectControl> projects)
+ public List<AccountGroup> get() {
+ return filterGroups(groupCache.all());
+ }
+
+ public List<AccountGroup> get(final Collection<ProjectControl> projects)
throws NoSuchGroupException {
Map<AccountGroup.UUID, AccountGroup> groups = Maps.newHashMap();
for (final ProjectControl projectControl : projects) {
@@ -78,7 +84,7 @@
groups.put(group.getGroupUUID(), group);
}
}
- return createGroupList(filterGroups(groups.values()));
+ return filterGroups(groups.values());
}
/**
@@ -87,7 +93,7 @@
* groups.
* @See GroupMembership#getKnownGroups()
*/
- public GroupList get(final IdentifiedUser user) throws NoSuchGroupException {
+ public List<AccountGroup> get(final IdentifiedUser user) throws NoSuchGroupException {
if (identifiedUser.get().getAccountId().equals(user.getAccountId())
|| identifiedUser.get().getCapabilities().canAdministrateServer()) {
Set<AccountGroup.UUID> mine = user.getEffectiveGroups().getKnownGroups();
@@ -98,7 +104,7 @@
groups.put(groupId, group);
}
}
- return createGroupList(filterGroups(groups.values()));
+ return filterGroups(groups.values());
} else {
throw new NoSuchGroupException("Groups of user '" + user.getAccountId()
+ "' are not visible.");
@@ -110,6 +116,12 @@
final boolean isAdmin =
identifiedUser.get().getCapabilities().canAdministrateServer();
for (final AccountGroup group : groups) {
+ if (!Strings.isNullOrEmpty(match)) {
+ if (!group.getName().toLowerCase(Locale.US)
+ .contains(match.toLowerCase(Locale.US))) {
+ continue;
+ }
+ }
if (!isAdmin) {
final GroupControl c = groupControlFactory.controlFor(group);
if (!c.isVisible()) {
@@ -125,9 +137,4 @@
Collections.sort(filteredGroups, new GroupComparator());
return filteredGroups;
}
-
- private GroupList createGroupList(final List<AccountGroup> groups) {
- return new GroupList(groups, identifiedUser.get()
- .getCapabilities().canCreateGroup());
- }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
index efd4980..11a9edd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
@@ -22,6 +22,7 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.change.PostReview.NotifyHandling;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.mail.CommentSender;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
@@ -46,6 +47,7 @@
interface Factory {
EmailReviewComments create(
+ NotifyHandling notify,
Change change,
PatchSet patchSet,
Account.Id authorId,
@@ -59,6 +61,7 @@
private final SchemaFactory<ReviewDb> schemaFactory;
private final ThreadLocalRequestContext requestContext;
+ private final PostReview.NotifyHandling notify;
private final Change change;
private final PatchSet patchSet;
private final Account.Id authorId;
@@ -73,6 +76,7 @@
CommentSender.Factory commentSenderFactory,
SchemaFactory<ReviewDb> schemaFactory,
ThreadLocalRequestContext requestContext,
+ @Assisted NotifyHandling notify,
@Assisted Change change,
@Assisted PatchSet patchSet,
@Assisted Account.Id authorId,
@@ -83,6 +87,7 @@
this.commentSenderFactory = commentSenderFactory;
this.schemaFactory = schemaFactory;
this.requestContext = requestContext;
+ this.notify = notify;
this.change = change;
this.patchSet = patchSet;
this.authorId = authorId;
@@ -122,7 +127,7 @@
}
});
- CommentSender cm = commentSenderFactory.create(change);
+ CommentSender cm = commentSenderFactory.create(notify, change);
cm.setFrom(authorId);
cm.setPatchSet(patchSet, patchSetInfoFactory.get(change, patchSet));
cm.setChangeMessage(message);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index d165a4a..1a9d1f2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -76,12 +76,19 @@
* described in this input request.
*/
public DraftHandling drafts = DraftHandling.DELETE;
+
+ /** Who to send email notifications to after review is stored. */
+ public NotifyHandling notify = NotifyHandling.ALL;
}
public static enum DraftHandling {
DELETE, PUBLISH, KEEP;
}
+ public static enum NotifyHandling {
+ NONE, OWNER, OWNER_REVIEWERS, ALL;
+ }
+
static class Comment {
String id;
GetDraft.Side side;
@@ -132,6 +139,9 @@
if (input.comments != null) {
checkComments(input.comments);
}
+ if (input.notify == null) {
+ input.notify = NotifyHandling.NONE;
+ }
db.changes().beginTransaction(revision.getChange().getId());
try {
@@ -151,8 +161,9 @@
db.rollback();
}
- if (message != null) {
+ if (input.notify.compareTo(NotifyHandling.NONE) > 0 && message != null) {
email.create(
+ input.notify,
change,
revision.getPatchSet(),
revision.getAuthorId(),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
index de7cfd4..3614b37 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
@@ -17,6 +17,7 @@
import com.google.common.base.Strings;
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.reviewdb.client.Change;
@@ -26,25 +27,34 @@
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.Revert.Input;
+import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.mail.RevertedSender;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.ssh.NoSshInfo;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+
+import javax.annotation.Nullable;
public class Revert implements RestModifyView<ChangeResource, Input> {
private final ChangeHooks hooks;
private final RevertedSender.Factory revertedSenderFactory;
+ private final CommitValidators.Factory commitValidatorsFactory;
private final Provider<ReviewDb> dbProvider;
private final ChangeJson json;
private final GitRepositoryManager gitManager;
private final PersonIdent myIdent;
private final PatchSetInfoFactory patchSetInfoFactory;
private final GitReferenceUpdated replication;
+ private final String canonicalWebUrl;
public static class Input {
public String message;
@@ -53,20 +63,24 @@
@Inject
Revert(ChangeHooks hooks,
RevertedSender.Factory revertedSenderFactory,
+ final CommitValidators.Factory commitValidatorsFactory,
Provider<ReviewDb> dbProvider,
ChangeJson json,
GitRepositoryManager gitManager,
final PatchSetInfoFactory patchSetInfoFactory,
final GitReferenceUpdated replication,
- @GerritPersonIdent final PersonIdent myIdent) {
+ @GerritPersonIdent final PersonIdent myIdent,
+ @CanonicalWebUrl @Nullable final String canonicalWebUrl) {
this.hooks = hooks;
this.revertedSenderFactory = revertedSenderFactory;
+ this.commitValidatorsFactory = commitValidatorsFactory;
this.dbProvider = dbProvider;
this.json = json;
this.gitManager = gitManager;
this.myIdent = myIdent;
this.replication = replication;
this.patchSetInfoFactory = patchSetInfoFactory;
+ this.canonicalWebUrl = canonicalWebUrl;
}
@Override
@@ -75,8 +89,7 @@
}
@Override
- public Object apply(ChangeResource req, Input input)
- throws Exception {
+ public Object apply(ChangeResource req, Input input) throws Exception {
ChangeControl control = req.getControl();
Change change = req.getChange();
if (!control.canAddPatchSet()) {
@@ -85,14 +98,25 @@
throw new ResourceConflictException("change is " + status(change));
}
- Change.Id revertedChangeId = ChangeUtil.revert(
- change.currentPatchSetId(),
- (IdentifiedUser) control.getCurrentUser(),
- Strings.emptyToNull(input.message),
- dbProvider.get(),
- revertedSenderFactory, hooks, gitManager,
- patchSetInfoFactory, replication, myIdent);
- return json.format(revertedChangeId);
+ final Repository git = gitManager.openRepository(control.getProject().getNameKey());
+ try {
+ CommitValidators commitValidators =
+ commitValidatorsFactory.create(control.getRefControl(), new NoSshInfo(), git);
+
+ Change.Id revertedChangeId =
+ ChangeUtil.revert(control.getRefControl(), change.currentPatchSetId(),
+ (IdentifiedUser) control.getCurrentUser(),
+ commitValidators,
+ Strings.emptyToNull(input.message), dbProvider.get(),
+ revertedSenderFactory, hooks, git, patchSetInfoFactory,
+ replication, myIdent, canonicalWebUrl);
+
+ return json.format(revertedChangeId);
+ } catch (InvalidChangeOperationException e) {
+ throw new BadRequestException(e.getMessage());
+ } finally {
+ git.close();
+ }
}
private static String status(Change change) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java
index a71e12e..1cf0bd2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java
@@ -15,12 +15,28 @@
package com.google.gerrit.server.changedetail;
+import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromApprovals;
+import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromFooters;
+
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.data.ReviewResult;
+import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountResolver;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.mail.CreateChangeSender;
+import com.google.gerrit.server.mail.MailUtil.MailRecipients;
+import com.google.gerrit.server.mail.ReplacePatchSetSender;
+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.project.NoSuchChangeException;
import com.google.gwtorm.server.AtomicUpdate;
@@ -28,9 +44,22 @@
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.FooterLine;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
import java.util.concurrent.Callable;
public class PublishDraft implements Callable<ReviewResult> {
+ private static final Logger log =
+ LoggerFactory.getLogger(PublishDraft.class);
public interface Factory {
PublishDraft create(PatchSet.Id patchSetId);
@@ -39,22 +68,41 @@
private final ChangeControl.Factory changeControlFactory;
private final ReviewDb db;
private final ChangeHooks hooks;
+ private final GitRepositoryManager repoManager;
+ private final PatchSetInfoFactory patchSetInfoFactory;
+ private final ApprovalsUtil approvalsUtil;
+ private final AccountResolver accountResolver;
+ private final CreateChangeSender.Factory createChangeSenderFactory;
+ private final ReplacePatchSetSender.Factory replacePatchSetFactory;
private final PatchSet.Id patchSetId;
@Inject
- PublishDraft(ChangeControl.Factory changeControlFactory,
- ReviewDb db, @Assisted final PatchSet.Id patchSetId,
- final ChangeHooks hooks) {
+ PublishDraft(final ChangeControl.Factory changeControlFactory,
+ final ReviewDb db, final ChangeHooks hooks,
+ final GitRepositoryManager repoManager,
+ final PatchSetInfoFactory patchSetInfoFactory,
+ final ApprovalsUtil approvalsUtil,
+ final AccountResolver accountResolver,
+ final CreateChangeSender.Factory createChangeSenderFactory,
+ final ReplacePatchSetSender.Factory replacePatchSetFactory,
+ @Assisted final PatchSet.Id patchSetId) {
this.changeControlFactory = changeControlFactory;
this.db = db;
this.hooks = hooks;
+ this.repoManager = repoManager;
+ this.patchSetInfoFactory = patchSetInfoFactory;
+ this.approvalsUtil = approvalsUtil;
+ this.accountResolver = accountResolver;
+ this.createChangeSenderFactory = createChangeSenderFactory;
+ this.replacePatchSetFactory = replacePatchSetFactory;
this.patchSetId = patchSetId;
}
@Override
- public ReviewResult call() throws NoSuchChangeException, OrmException {
+ public ReviewResult call() throws NoSuchChangeException, OrmException,
+ IOException, PatchSetInfoNotAvailableException {
final ReviewResult result = new ReviewResult();
final Change.Id changeId = patchSetId.getParentKey();
@@ -74,7 +122,7 @@
result.addError(new ReviewResult.Error(
ReviewResult.Error.Type.PUBLISH_NOT_PERMITTED));
} else {
- final PatchSet updatedPatch = db.patchSets().atomicUpdate(patchSetId,
+ final PatchSet updatedPatchSet = db.patchSets().atomicUpdate(patchSetId,
new AtomicUpdate<PatchSet>() {
@Override
public PatchSet update(PatchSet patchset) {
@@ -95,11 +143,76 @@
}
});
- if (!updatedPatch.isDraft() || updatedChange.getStatus() == Change.Status.NEW) {
- hooks.doDraftPublishedHook(updatedChange, updatedPatch, db);
+ if (!updatedPatchSet.isDraft() || updatedChange.getStatus() == Change.Status.NEW) {
+ hooks.doDraftPublishedHook(updatedChange, updatedPatchSet, db);
+
+ sendNotifications(control.getChange().getStatus() == Change.Status.DRAFT,
+ (IdentifiedUser) control.getCurrentUser(), updatedChange, updatedPatchSet);
}
}
return result;
}
+
+ private void sendNotifications(final boolean newChange,
+ final IdentifiedUser currentUser, final Change updatedChange,
+ final PatchSet updatedPatchSet) throws OrmException, IOException,
+ PatchSetInfoNotAvailableException {
+ final Repository git = repoManager.openRepository(updatedChange.getProject());
+ try {
+ final RevWalk revWalk = new RevWalk(git);
+ final RevCommit commit;
+ try {
+ commit = revWalk.parseCommit(ObjectId.fromString(updatedPatchSet.getRevision().get()));
+ } finally {
+ revWalk.release();
+ }
+ final PatchSetInfo info = patchSetInfoFactory.get(commit, updatedPatchSet.getId());
+ final List<FooterLine> footerLines = commit.getFooterLines();
+ final Account.Id me = currentUser.getAccountId();
+ final MailRecipients recipients =
+ getRecipientsFromFooters(accountResolver, updatedPatchSet, footerLines);
+ recipients.remove(me);
+
+ if (newChange) {
+ approvalsUtil.addReviewers(db, updatedChange, updatedPatchSet, info,
+ recipients.getReviewers(), Collections.<Account.Id> emptySet());
+ try {
+ CreateChangeSender cm = createChangeSenderFactory.create(updatedChange);
+ cm.setFrom(me);
+ cm.setPatchSet(updatedPatchSet, info);
+ cm.addReviewers(recipients.getReviewers());
+ cm.addExtraCC(recipients.getCcOnly());
+ cm.send();
+ } catch (Exception e) {
+ log.error("Cannot send email for new change " + updatedChange.getId(), e);
+ }
+ } else {
+ final List<PatchSetApproval> patchSetApprovals =
+ db.patchSetApprovals().byChange(updatedChange.getId()).toList();
+ final MailRecipients oldRecipients =
+ getRecipientsFromApprovals(patchSetApprovals);
+ approvalsUtil.addReviewers(db, updatedChange, updatedPatchSet, info,
+ recipients.getReviewers(), oldRecipients.getAll());
+ final ChangeMessage msg =
+ new ChangeMessage(new ChangeMessage.Key(updatedChange.getId(),
+ ChangeUtil.messageUUID(db)), me,
+ updatedPatchSet.getCreatedOn(), updatedPatchSet.getId());
+ msg.setMessage("Uploaded patch set " + updatedPatchSet.getPatchSetId() + ".");
+ try {
+ ReplacePatchSetSender cm = replacePatchSetFactory.create(updatedChange);
+ cm.setFrom(me);
+ cm.setPatchSet(updatedPatchSet, info);
+ cm.setChangeMessage(msg);
+ cm.addReviewers(recipients.getReviewers());
+ cm.addExtraCC(recipients.getCcOnly());
+ cm.send();
+ } catch (Exception e) {
+ log.error("Cannot send email for new patch set " + updatedPatchSet.getId(), e);
+ }
+ }
+ } finally {
+ git.close();
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
index f613d05..0c1bef3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
@@ -16,6 +16,7 @@
import com.google.common.collect.Sets;
import com.google.gerrit.common.ChangeHookRunner;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
@@ -33,7 +34,6 @@
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MergeUtil;
-import com.google.gerrit.server.mail.EmailException;
import com.google.gerrit.server.mail.RebasedPatchSetSender;
import com.google.gerrit.server.mail.ReplacePatchSetSender;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
@@ -95,6 +95,8 @@
/**
* Rebases the change of the given patch set.
*
+ * It is verified that the current user is allowed to do the rebase.
+ *
* If the patch set has no dependency to an open change, then the change is
* rebased on the tip of the destination branch.
*
@@ -122,6 +124,11 @@
final Change.Id changeId = patchSetId.getParentKey();
final ChangeControl changeControl =
changeControlFactory.validateFor(changeId);
+ if (!changeControl.canRebase()) {
+ throw new InvalidChangeOperationException(
+ "Cannot rebase: New patch sets are not allowed to be added to change: "
+ + changeId.toString());
+ }
final Change change = changeControl.getChange();
Repository git = null;
RevWalk rw = null;
@@ -297,14 +304,6 @@
OrmException, IOException, InvalidChangeOperationException,
PathConflictException {
Change change = chg;
- final ChangeControl changeControl =
- changeControlFactory.validateFor(change);
- if (!changeControl.canRebase()) {
- throw new InvalidChangeOperationException(
- "Cannot rebase: New patch sets are not allowed to be added to change: "
- + change.getId().toString());
- }
-
final PatchSet originalPatchSet = db.patchSets().get(patchSetId);
final RevCommit rebasedCommit;
@@ -380,7 +379,7 @@
new ChangeMessage(new ChangeMessage.Key(change.getId(),
ChangeUtil.messageUUID(db)), uploader, patchSetId);
cmsg.setMessage("Patch Set " + change.currentPatchSetId().get()
- + ": Patch Set " + patchSetId.get() + " was rebased onto the latest head");
+ + ": Patch Set " + patchSetId.get() + " was rebased");
db.changeMessages().insert(Collections.singleton(cmsg));
db.commit();
} finally {
@@ -421,7 +420,7 @@
if (merger.getResultTreeId() == null) {
throw new PathConflictException(
- "The rebase failed since conflicts occured during the merge.");
+ "The change could not be rebased due to a path conflict during merge.");
}
final CommitBuilder cb = new CommitBuilder();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index cc754de..ff8a5eb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -49,6 +49,7 @@
import com.google.gerrit.server.account.InternalGroupBackend;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.account.UniversalGroupBackend;
+import com.google.gerrit.server.account.VisibleGroups;
import com.google.gerrit.server.auth.AuthBackend;
import com.google.gerrit.server.auth.InternalAuthBackend;
import com.google.gerrit.server.auth.UniversalAuthBackend;
@@ -65,6 +66,7 @@
import com.google.gerrit.server.git.TagCache;
import com.google.gerrit.server.git.TransferConfig;
import com.google.gerrit.server.git.validators.CommitValidationListener;
+import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.mail.EmailModule;
import com.google.gerrit.server.mail.FromAddressGenerator;
import com.google.gerrit.server.mail.FromAddressGeneratorProvider;
@@ -151,6 +153,7 @@
factory(CapabilityControl.Factory.class);
factory(ChangeQueryBuilder.Factory.class);
factory(GroupInfoCacheFactory.Factory.class);
+ factory(VisibleGroups.Factory.class);
factory(InternalUser.Factory.class);
factory(ProjectNode.Factory.class);
factory(ProjectState.Factory.class);
@@ -194,6 +197,7 @@
install(new AuditModule());
install(new com.google.gerrit.server.account.Module());
install(new com.google.gerrit.server.change.Module());
+ install(new com.google.gerrit.server.group.Module());
install(new com.google.gerrit.server.project.Module());
bind(GitReferenceUpdated.class);
@@ -203,6 +207,7 @@
DynamicSet.setOf(binder(), NewProjectCreatedListener.class);
DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(ChangeCache.class);
DynamicSet.setOf(binder(), CommitValidationListener.class);
+ factory(CommitValidators.Factory.class);
bind(AnonymousUser.class);
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 9d72bf6..2784f30 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
@@ -24,7 +24,6 @@
import com.google.gerrit.server.account.GroupMembers;
import com.google.gerrit.server.account.PerformCreateGroup;
import com.google.gerrit.server.account.PerformRenameGroup;
-import com.google.gerrit.server.account.VisibleGroups;
import com.google.gerrit.server.changedetail.DeleteDraftPatchSet;
import com.google.gerrit.server.changedetail.PublishDraft;
import com.google.gerrit.server.git.AsyncReceiveCommits;
@@ -85,7 +84,6 @@
factory(MergeFailSender.Factory.class);
factory(PerformCreateGroup.Factory.class);
factory(PerformRenameGroup.Factory.class);
- factory(VisibleGroups.Factory.class);
factory(GroupDetailFactory.Factory.class);
factory(GroupMembers.Factory.class);
factory(CreateProject.Factory.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/CherryPick.java
index d0c4742..4ef75c3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/CherryPick.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/CherryPick.java
@@ -23,6 +23,7 @@
import static com.google.gerrit.server.git.MergeUtil.mergeOneCommit;
import static com.google.gerrit.server.git.MergeUtil.getApprovalsForCommit;
+import com.google.common.collect.Lists;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
@@ -185,10 +186,11 @@
n.change.setCurrentPatchSet(patchSetInfoFactory.get(newCommit, ps.getId()));
args.db.changes().update(Collections.singletonList(n.change));
+ final List<PatchSetApproval> approvals = Lists.newArrayList();
for (PatchSetApproval a : getApprovalsForCommit(args.db, n)) {
- args.db.patchSetApprovals().insert(
- Collections.singleton(new PatchSetApproval(ps.getId(), a)));
+ approvals.add(new PatchSetApproval(ps.getId(), a));
}
+ args.db.patchSetApprovals().insert(approvals);
final RefUpdate ru = args.repo.updateRef(ps.getRefName());
ru.setExpectedOldObjectId(ObjectId.zeroId());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java
index 7dbba04..5a39aae 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java
@@ -28,7 +28,7 @@
ALREADY_MERGED(""),
/** */
- PATH_CONFLICT("Your change could not be merged due to a path conflict.\n"
+ PATH_CONFLICT("The change could not be merged due to a path conflict.\n"
+ "\n"
+ "Please rebase the change locally and upload the rebased commit for review."),
@@ -45,7 +45,7 @@
NO_SUBMIT_TYPE(""),
/** */
- CRISS_CROSS_MERGE("Your change requires a recursive merge to resolve.\n"
+ CRISS_CROSS_MERGE("The change requires a recursive merge to resolve.\n"
+ "\n"
+ "Please merge (or rebase) the change locally and upload the resolution for review."),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
index 44cb9a7..e23e849 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
@@ -177,10 +177,7 @@
// It doesn't exist under any of the standard permutations
// of the repository name, so prefer the standard bare name.
//
- String n = name.get();
- if (!n.endsWith(Constants.DOT_GIT_EXT)) {
- n = n + Constants.DOT_GIT_EXT;
- }
+ String n = name.get() + Constants.DOT_GIT_EXT;
loc = FileKey.exact(new File(basePath, n), FS.DETECTED);
}
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 23593e5..9200db4 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.reviewdb.client.Change.INITIAL_PATCH_SET_ID;
import static com.google.gerrit.server.git.MultiProgressMonitor.UNKNOWN;
+import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromApprovals;
+import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromFooters;
import static org.eclipse.jgit.lib.Constants.R_HEADS;
import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK;
@@ -37,11 +39,8 @@
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.gerrit.common.ChangeHookRunner.HookResult;
import com.google.gerrit.common.ChangeHooks;
-import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.Capable;
import com.google.gerrit.common.data.PermissionRule;
-import com.google.gerrit.common.errors.NoSuchAccountException;
-import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
@@ -65,9 +64,10 @@
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.MultiProgressMonitor.Task;
import com.google.gerrit.server.git.validators.CommitValidationException;
-import com.google.gerrit.server.git.validators.CommitValidationListener;
import com.google.gerrit.server.git.validators.CommitValidationMessage;
+import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.mail.CreateChangeSender;
+import com.google.gerrit.server.mail.MailUtil.MailRecipients;
import com.google.gerrit.server.mail.MergedSender;
import com.google.gerrit.server.mail.ReplacePatchSetSender;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
@@ -86,8 +86,6 @@
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
-import com.jcraft.jsch.HostKey;
-
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
@@ -113,13 +111,10 @@
import org.eclipse.jgit.transport.ReceiveCommand.Result;
import org.eclipse.jgit.transport.ReceivePack;
import org.eclipse.jgit.transport.UploadPack;
-import org.eclipse.jgit.util.SystemReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
@@ -143,8 +138,6 @@
private static final Pattern NEW_PATCHSET =
Pattern.compile("^refs/changes/(?:[0-9][0-9]/)?([1-9][0-9]*)(?:/new)?$");
- private static final FooterKey REVIEWED_BY = new FooterKey("Reviewed-by");
- 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 =
@@ -251,7 +244,7 @@
private final GitRepositoryManager repoManager;
private final ProjectCache projectCache;
private final String canonicalWebUrl;
- private final PersonIdent gerritIdent;
+ private final CommitValidators.Factory commitValidatorsFactory;
private final TrackingFooters trackingFooters;
private final TagCache tagCache;
private final WorkQueue workQueue;
@@ -290,7 +283,6 @@
private Task commandProgress;
private MessageSender messageSender;
private BatchRefUpdate batch;
- private final DynamicSet<CommitValidationListener> commitValidators;
@Inject
ReceiveCommits(final ReviewDb db,
@@ -307,6 +299,7 @@
final GitRepositoryManager repoManager,
final TagCache tagCache,
final ChangeCache changeCache,
+ final CommitValidators.Factory commitValidatorsFactory,
@CanonicalWebUrl @Nullable final String canonicalWebUrl,
@GerritPersonIdent final PersonIdent gerritIdent,
final TrackingFooters trackingFooters,
@@ -314,7 +307,6 @@
@ChangeUpdateExecutor ListeningExecutorService changeUpdateExector,
final RequestScopePropagator requestScopePropagator,
final SshInfo sshInfo,
- final DynamicSet<CommitValidationListener> commitValidationListeners,
final AllProjectsName allProjectsName,
@Assisted final ProjectControl projectControl,
@Assisted final Repository repo,
@@ -333,9 +325,9 @@
this.projectCache = projectCache;
this.repoManager = repoManager;
this.canonicalWebUrl = canonicalWebUrl;
- this.gerritIdent = gerritIdent;
this.trackingFooters = trackingFooters;
this.tagCache = tagCache;
+ this.commitValidatorsFactory = commitValidatorsFactory;
this.workQueue = workQueue;
this.changeUpdateExector = changeUpdateExector;
this.requestScopePropagator = requestScopePropagator;
@@ -349,7 +341,6 @@
this.rejectCommits = loadRejectCommitsMap();
this.subOpFactory = subOpFactory;
- this.commitValidators = commitValidationListeners;
this.messageSender = new ReceivePackMessageSender();
@@ -688,7 +679,7 @@
reject(newChange, "internal server error");
log.error(String.format(
"Only %d of %d new change refs created in %s; aborting",
- okToInsert, newChanges.size(), project.getName()));
+ okToInsert, replaceCount + newChanges.size(), project.getName()));
return;
}
@@ -741,16 +732,6 @@
return displayName;
}
- private Account.Id toAccountId(final String nameOrEmail) throws OrmException,
- NoSuchAccountException {
- final Account a = accountResolver.findByNameOrEmail(nameOrEmail);
- if (a == null) {
- throw new NoSuchAccountException("\"" + nameOrEmail
- + "\" is not registered");
- }
- return a.getId();
- }
-
private void parseCommands(final Collection<ReceiveCommand> commands) {
for (final ReceiveCommand cmd : commands) {
if (cmd.getResult() != NOT_ATTEMPTED) {
@@ -1203,6 +1184,7 @@
//
continue;
}
+
if (!validCommit(destBranchCtl, newChange, c)) {
// Not a change the user can propose? Abort as early as possible.
//
@@ -1385,26 +1367,10 @@
private void insertChange(ReviewDb db) throws OrmException {
final Account.Id me = currentUser.getAccountId();
- final Set<Account.Id> reviewers = new HashSet<Account.Id>(reviewerId);
- final Set<Account.Id> cc = new HashSet<Account.Id>(ccId);
final List<FooterLine> footerLines = commit.getFooterLines();
- for (final FooterLine footerLine : footerLines) {
- try {
- if (ps.isDraft()) {
- continue;
- }
- if (isReviewer(footerLine)) {
- reviewers.add(toAccountId(footerLine.getValue().trim()));
- } else if (footerLine.matches(FooterKey.CC)) {
- cc.add(toAccountId(footerLine.getValue().trim()));
- }
- } catch (NoSuchAccountException e) {
- continue;
- }
- }
- reviewers.remove(me);
- cc.remove(me);
- cc.removeAll(reviewers);
+ final MailRecipients recipients = new MailRecipients(reviewerId, ccId);
+ recipients.add(getRecipientsFromFooters(accountResolver, ps, footerLines));
+ recipients.remove(me);
db.changes().beginTransaction(change.getId());
try {
@@ -1413,7 +1379,7 @@
db.changes().insert(Collections.singleton(change));
ChangeUtil.updateTrackingIds(db, change, trackingFooters, footerLines);
approvalsUtil.addReviewers(db, change, ps, info,
- reviewers, Collections.<Account.Id> emptySet());
+ recipients.getReviewers(), Collections.<Account.Id> emptySet());
db.commit();
} finally {
db.rollback();
@@ -1431,8 +1397,8 @@
createChangeSenderFactory.create(change);
cm.setFrom(me);
cm.setPatchSet(ps, info);
- cm.addReviewers(reviewers);
- cm.addExtraCC(cc);
+ cm.addReviewers(recipients.getReviewers());
+ cm.addExtraCC(recipients.getCcOnly());
cm.send();
} catch (Exception e) {
log.error("Cannot send email for new change " + change.getId(), e);
@@ -1447,13 +1413,6 @@
}
}
- private static boolean isReviewer(final FooterLine candidateFooterLine) {
- return candidateFooterLine.matches(FooterKey.SIGNED_OFF_BY)
- || candidateFooterLine.matches(FooterKey.ACKED_BY)
- || candidateFooterLine.matches(REVIEWED_BY)
- || candidateFooterLine.matches(TESTED_BY);
- }
-
private void preparePatchSetsForReplace() {
try {
readChangesForReplace();
@@ -1568,6 +1527,7 @@
}
rp.getRevWalk().parseBody(newCommit);
+
if (!validCommit(changeCtl.getRefControl(), inputCommand, newCommit)) {
return false;
}
@@ -1699,26 +1659,10 @@
PatchSet.Id insertPatchSet(ReviewDb db) throws OrmException {
final Account.Id me = currentUser.getAccountId();
- final Set<Account.Id> reviewers = new HashSet<Account.Id>(reviewerId);
- final Set<Account.Id> cc = new HashSet<Account.Id>(ccId);
final List<FooterLine> footerLines = newCommit.getFooterLines();
- for (final FooterLine footerLine : footerLines) {
- try {
- if (isReviewer(footerLine)) {
- reviewers.add(toAccountId(footerLine.getValue().trim()));
- } else if (footerLine.matches(FooterKey.CC)) {
- cc.add(toAccountId(footerLine.getValue().trim()));
- }
- } catch (NoSuchAccountException e) {
- continue;
- }
- }
- reviewers.remove(me);
- cc.remove(me);
- cc.removeAll(reviewers);
-
- final Set<Account.Id> oldReviewers = new HashSet<Account.Id>();
- final Set<Account.Id> oldCC = new HashSet<Account.Id>();
+ final MailRecipients recipients = new MailRecipients(reviewerId, ccId);
+ recipients.add(getRecipientsFromFooters(accountResolver, newPatchSet, footerLines));
+ recipients.remove(me);
db.changes().beginTransaction(change.getId());
try {
@@ -1738,22 +1682,11 @@
List<PatchSetApproval> patchSetApprovals =
approvalsUtil.copyVetosToPatchSet(db, newPatchSet.getId());
-
- final Set<Account.Id> haveApprovals = new HashSet<Account.Id>();
- oldReviewers.clear();
- oldCC.clear();
-
- for (PatchSetApproval a : patchSetApprovals) {
- haveApprovals.add(a.getAccountId());
- if (a.getValue() != 0) {
- oldReviewers.add(a.getAccountId());
- } else {
- oldCC.add(a.getAccountId());
- }
- }
-
+ final MailRecipients oldRecipients =
+ getRecipientsFromApprovals(patchSetApprovals);
approvalsUtil.addReviewers(db, change, newPatchSet, info,
- reviewers, haveApprovals);
+ recipients.getReviewers(), oldRecipients.getAll());
+ recipients.add(oldRecipients);
msg =
new ChangeMessage(new ChangeMessage.Key(change.getId(), ChangeUtil
@@ -1812,6 +1745,9 @@
markChangeMergedByPush(db, this);
}
+ if (cmd.getResult() == NOT_ATTEMPTED) {
+ cmd.execute(rp);
+ }
replication.fire(project.getNameKey(), newPatchSet.getRefName());
hooks.doPatchsetCreatedHook(change, newPatchSet, db);
if (mergedIntoRef != null) {
@@ -1828,10 +1764,8 @@
cm.setFrom(me);
cm.setPatchSet(newPatchSet, info);
cm.setChangeMessage(msg);
- cm.addReviewers(reviewers);
- cm.addExtraCC(cc);
- cm.addReviewers(oldReviewers);
- cm.addExtraCC(oldCC);
+ cm.addReviewers(recipients.getReviewers());
+ cm.addExtraCC(recipients.getCcOnly());
cm.send();
} catch (Exception e) {
log.error("Cannot send email for new patch set " + newPatchSet.getId(), e);
@@ -1950,279 +1884,22 @@
private boolean validCommit(final RefControl ctl, final ReceiveCommand cmd,
final RevCommit c) throws MissingObjectException, IOException {
- rp.getRevWalk().parseBody(c);
- final PersonIdent committer = c.getCommitterIdent();
- final PersonIdent author = c.getAuthorIdent();
- // Require permission to upload merges.
- if (c.getParentCount() > 1 && !ctl.canUploadMerges()) {
- reject(cmd, "you are not allowed to upload merges");
+ CommitReceivedEvent receiveEvent =
+ new CommitReceivedEvent(cmd, project, ctl.getRefName(), c, currentUser);
+ CommitValidators commitValidators =
+ commitValidatorsFactory.create(ctl, sshInfo, repo);
+
+ try {
+ messages.addAll(commitValidators.validateForReceiveCommits(receiveEvent));
+ } catch (CommitValidationException e) {
+ messages.addAll(e.getMessages());
+ reject(cmd, e.getMessage());
return false;
}
-
- // Don't allow the user to amend a merge created by Gerrit Code Review.
- // This seems to happen all too often, due to users not paying any
- // attention to what they are doing.
- //
- if (c.getParentCount() > 1
- && author.getName().equals(gerritIdent.getName())
- && author.getEmailAddress().equals(gerritIdent.getEmailAddress())
- && !ctl.canForgeGerritServerIdentity()) {
- reject(cmd, "do not amend merges not made by you");
- return false;
- }
-
- // Require that author matches the uploader.
- //
- if (!currentUser.getEmailAddresses().contains(author.getEmailAddress())
- && !ctl.canForgeAuthor()) {
- sendInvalidEmailError(c, "author", author);
- reject(cmd, "invalid author");
- return false;
- }
-
- // Require that committer matches the uploader.
- //
- if (!currentUser.getEmailAddresses().contains(committer.getEmailAddress())
- && !ctl.canForgeCommitter()) {
- sendInvalidEmailError(c, "committer", committer);
- reject(cmd, "invalid committer");
- return false;
- }
-
- if (projectControl.getProjectState().isUseSignedOffBy()) {
- // If the project wants Signed-off-by / Acked-by lines, verify we
- // have them for the blamable parties involved on this change.
- //
- boolean sboAuthor = false, sboCommitter = false, sboMe = false;
- for (final FooterLine footer : c.getFooterLines()) {
- if (footer.matches(FooterKey.SIGNED_OFF_BY)) {
- final String e = footer.getEmailAddress();
- if (e != null) {
- sboAuthor |= author.getEmailAddress().equals(e);
- sboCommitter |= committer.getEmailAddress().equals(e);
- sboMe |= currentUser.getEmailAddresses().contains(e);
- }
- }
- }
- if (!sboAuthor && !sboCommitter && !sboMe && !ctl.canForgeCommitter()) {
- reject(cmd, "not Signed-off-by author/committer/uploader in commit message footer");
- return false;
- }
- }
-
- final List<String> idList = c.getFooterLines(CHANGE_ID);
- if (MagicBranch.isMagicBranch(cmd.getRefName()) || NEW_PATCHSET.matcher(cmd.getRefName()).matches()) {
- if (idList.isEmpty()) {
- if (projectControl.getProjectState().isRequireChangeID()) {
- String errMsg = "missing Change-Id in commit message footer";
- reject(cmd, errMsg);
- addMessage(getFixedCommitMsgWithChangeId(errMsg, c));
- return false;
- }
- } else if (idList.size() > 1) {
- reject(cmd, "multiple Change-Id lines in commit message footer");
- return false;
- } else {
- final String v = idList.get(idList.size() - 1).trim();
- if (!v.matches("^I[0-9a-f]{8,}.*$")) {
- final String errMsg =
- "missing or invalid Change-Id line format in commit message footer";
- reject(cmd, errMsg);
- addMessage(getFixedCommitMsgWithChangeId(errMsg, c));
- return false;
- }
- }
- }
-
- // Check for banned commits to prevent them from entering the tree again.
- if (rejectCommits.contains(c)) {
- reject(cmd, "contains banned commit " + c.getName());
- return false;
- }
-
- // If this is the special project configuration branch, validate the config.
- if (GitRepositoryManager.REF_CONFIG.equals(ctl.getRefName())) {
- try {
- ProjectConfig cfg = new ProjectConfig(project.getNameKey());
- cfg.load(repo, cmd.getNewId());
- if (!cfg.getValidationErrors().isEmpty()) {
- addError("Invalid project configuration:");
- for (ValidationError err : cfg.getValidationErrors()) {
- addError(" " + err.getMessage());
- }
- reject(cmd, "invalid project configuration");
- log.error("User " + currentUser.getUserName()
- + " tried to push invalid project configuration "
- + cmd.getNewId().name() + " for " + project.getName());
- return false;
- }
- } catch (Exception e) {
- reject(cmd, "invalid project configuration");
- log.error("User " + currentUser.getUserName()
- + " tried to push invalid project configuration "
- + cmd.getNewId().name() + " for " + project.getName(), e);
- return false;
- }
- }
-
- // Execute commit validation plugins
- for (CommitValidationListener validator : commitValidators) {
- try {
- messages.addAll(validator.onCommitReceived(new CommitReceivedEvent(
- cmd, project, ctl.getRefName(), c, currentUser)));
- } catch (CommitValidationException error) {
- messages.addAll(error.getMessages());
- reject(cmd, error.getMessage());
- return false;
- }
- }
-
return true;
}
- /**
- * Get the Gerrit hostname.
- * @return the hostname from the canonical URL if it is configured,
- * otherwise whatever the OS says the hostname is.
- */
- private String getGerritHost() {
- String host;
- if (canonicalWebUrl != null) {
- try {
- host = new URL(canonicalWebUrl).getHost();
- } catch (MalformedURLException e) {
- host = SystemReader.getInstance().getHostname();
- }
- } else {
- host = SystemReader.getInstance().getHostname();
- }
- return host;
- }
-
- /**
- * Get the Gerrit URL.
- * @return the canonical URL (with any trailing slash removed) if it is
- * configured, otherwise fall back to "http://hostname" where hostname is
- * the value returned by {@link #getGerritHost()}.
- */
- private String getGerritUrl() {
- if (canonicalWebUrl != null) {
- if (canonicalWebUrl.endsWith("/")) {
- return canonicalWebUrl.substring(0, canonicalWebUrl.lastIndexOf("/"));
- }
- return canonicalWebUrl;
- } else {
- return "http://" + getGerritHost();
- }
- }
-
- /**
- * Get the text with instructions for installing the commit-msg hook, specific
- * to the server hostname and transport protocol.
- * @return commit-msg hook installation instructions as a String.
- */
- private String getCommitMessageHookInstallationHint() {
- final List<HostKey> hostKeys = sshInfo.getHostKeys();
-
- // If there are no SSH keys, the commit-msg hook must be installed via HTTP(S)
- if (hostKeys.isEmpty()) {
- String p = ".git/hooks/commit-msg";
- return String.format(
- " curl -o %s %s/tools/hooks/commit-msg ; chmod +x %s",
- p, getGerritUrl(), p);
- }
-
- // SSH keys exist, so the hook can be installed with scp.
- String sshHost;
- int sshPort;
- String host = hostKeys.get(0).getHost();
- int c = host.lastIndexOf(':');
- if (0 <= c) {
- if (host.startsWith("*:")) {
- sshHost = getGerritHost();
- } else {
- sshHost = host.substring(0, c);
- }
- sshPort = Integer.parseInt(host.substring(c+1));
- } else {
- sshHost = host;
- sshPort = 22;
- }
-
- return String.format(
- " scp -p -P %d %s@%s:hooks/commit-msg .git/hooks/",
- sshPort, currentUser.getUserName(), sshHost);
- }
-
- private String getFixedCommitMsgWithChangeId(String errMsg, RevCommit c) {
- // We handle 3 cases:
- // 1. No change id in the commit message at all.
- // 2. change id last in the commit message but missing empty line to create the footer.
- // 3. there is a change-id somewhere in the commit message, but we ignore it.
- final String changeId = "Change-Id:";
- StringBuilder sb = new StringBuilder();
- sb.append("ERROR: ").append(errMsg);
- sb.append('\n');
- sb.append("Suggestion for commit message:\n");
-
- if (c.getFullMessage().indexOf(changeId)==-1) {
- sb.append(c.getFullMessage());
- sb.append('\n');
- sb.append(changeId).append(" I").append(c.name());
- } else {
- String lines[] = c.getFullMessage().trim().split("\n");
- String lastLine = lines.length > 0 ? lines[lines.length - 1] : "";
-
- if (lastLine.indexOf(changeId)==0) {
- for (int i = 0; i < lines.length - 1; i++) {
- sb.append(lines[i]);
- sb.append('\n');
- }
-
- sb.append('\n');
- sb.append(lastLine);
- } else {
- sb.append(c.getFullMessage());
- sb.append('\n');
- sb.append(changeId).append(" I").append(c.name());
- sb.append('\n');
- sb.append("Hint: A potential Change-Id was found, but it was not in the footer of the commit message.");
- }
- }
- sb.append('\n');
- sb.append('\n');
- sb.append("Hint: To automatically insert Change-Id, install the hook:\n");
- sb.append(getCommitMessageHookInstallationHint()).append('\n');
- sb.append('\n');
-
- return sb.toString();
- }
-
- private void sendInvalidEmailError(RevCommit c, String type, PersonIdent who) {
- StringBuilder sb = new StringBuilder();
- sb.append("\n");
- sb.append("ERROR: In commit " + c.name() + "\n");
- sb.append("ERROR: " + type + " email address " + who.getEmailAddress() + "\n");
- sb.append("ERROR: does not match your user account.\n");
- sb.append("ERROR:\n");
- if (currentUser.getEmailAddresses().isEmpty()) {
- sb.append("ERROR: You have not registered any email addresses.\n");
- } else {
- sb.append("ERROR: The following addresses are currently registered:\n");
- for (String address : currentUser.getEmailAddresses()) {
- sb.append("ERROR: " + address + "\n");
- }
- }
- sb.append("ERROR:\n");
- if (canonicalWebUrl != null) {
- sb.append("ERROR: To register an email address, please visit:\n");
- sb.append("ERROR: " + canonicalWebUrl + "#" + PageLinks.SETTINGS_CONTACT + "\n");
- }
- sb.append("\n");
- addMessage(sb.toString());
- }
-
private void warnMalformedMessage(RevCommit c) {
ObjectReader reader = rp.getRevWalk().getObjectReader();
if (65 < c.getShortMessage().length()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
new file mode 100644
index 0000000..a53548b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -0,0 +1,576 @@
+// 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.validators;
+
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.events.CommitReceivedEvent;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.git.ValidationError;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.RefControl;
+import com.google.gerrit.server.ssh.SshInfo;
+import com.google.gerrit.server.util.MagicBranch;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import com.jcraft.jsch.HostKey;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.FooterKey;
+import org.eclipse.jgit.revwalk.FooterLine;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.util.SystemReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nullable;
+
+public class CommitValidators {
+ private static final Logger log = LoggerFactory
+ .getLogger(CommitValidators.class);
+
+ private static final FooterKey CHANGE_ID = new FooterKey("Change-Id");
+
+ private static final Pattern NEW_PATCHSET = Pattern
+ .compile("^refs/changes/(?:[0-9][0-9])?(/[1-9][0-9]*){1,2}(?:/new)?$");
+
+ public interface Factory {
+ CommitValidators create(RefControl refControl, SshInfo sshInfo,
+ Repository repo);
+ }
+
+ private final PersonIdent gerritIdent;
+ private final RefControl refControl;
+ private final String canonicalWebUrl;
+ private final SshInfo sshInfo;
+ private final Repository repo;
+ private final DynamicSet<CommitValidationListener> commitValidationListeners;
+
+ @Inject
+ CommitValidators(@GerritPersonIdent final PersonIdent gerritIdent,
+ @CanonicalWebUrl @Nullable final String canonicalWebUrl,
+ final DynamicSet<CommitValidationListener> commitValidationListeners,
+ @Assisted final SshInfo sshInfo,
+ @Assisted final Repository repo, @Assisted final RefControl refControl) {
+ this.gerritIdent = gerritIdent;
+ this.refControl = refControl;
+ this.canonicalWebUrl = canonicalWebUrl;
+ this.sshInfo = sshInfo;
+ this.repo = repo;
+ this.commitValidationListeners = commitValidationListeners;
+ }
+
+ public List<CommitValidationMessage> validateForReceiveCommits(
+ CommitReceivedEvent receiveEvent) throws CommitValidationException {
+
+ List<CommitValidationListener> validators =
+ new LinkedList<CommitValidationListener>();
+
+ validators.add(new UploadMergesPermissionValidator(refControl));
+ validators.add(new AmendedGerritMergeCommitValidationListener(
+ refControl, gerritIdent));
+ validators.add(new AuthorUploaderValidator(refControl, canonicalWebUrl));
+ validators.add(new CommitterUploaderValidator(refControl, canonicalWebUrl));
+ validators.add(new SignedOffByValidator(refControl, canonicalWebUrl));
+ validators.add(new ChangeIdValidator(refControl, canonicalWebUrl, sshInfo));
+ validators.add(new ConfigValidator(refControl, repo));
+ validators.add(new PluginCommitValidationListener(commitValidationListeners));
+
+ List<CommitValidationMessage> messages =
+ new LinkedList<CommitValidationMessage>();
+
+ try {
+ for (CommitValidationListener commitValidator : validators) {
+ messages.addAll(commitValidator.onCommitReceived(receiveEvent));
+ }
+ } catch (CommitValidationException e) {
+ // Keep the old messages (and their order) in case of an exception
+ messages.addAll(e.getMessages());
+ throw new CommitValidationException(e.getMessage(), messages);
+ }
+ return messages;
+ }
+
+ public List<CommitValidationMessage> validateForRevertCommits(
+ CommitReceivedEvent receiveEvent) throws CommitValidationException {
+
+ List<CommitValidationListener> validators =
+ new LinkedList<CommitValidationListener>();
+
+ validators.add(new UploadMergesPermissionValidator(refControl));
+ validators.add(new AmendedGerritMergeCommitValidationListener(
+ refControl, gerritIdent));
+ validators.add(new AuthorUploaderValidator(refControl, canonicalWebUrl));
+ validators.add(new SignedOffByValidator(refControl, canonicalWebUrl));
+ validators.add(new ChangeIdValidator(refControl, canonicalWebUrl, sshInfo));
+ validators.add(new ConfigValidator(refControl, repo));
+ validators.add(new PluginCommitValidationListener(commitValidationListeners));
+
+ List<CommitValidationMessage> messages =
+ new LinkedList<CommitValidationMessage>();
+
+ try {
+ for (CommitValidationListener commitValidator : validators) {
+ messages.addAll(commitValidator.onCommitReceived(receiveEvent));
+ }
+ } catch (CommitValidationException e) {
+ // Keep the old messages (and their order) in case of an exception
+ messages.addAll(e.getMessages());
+ throw new CommitValidationException(e.getMessage(), messages);
+ }
+ return messages;
+ }
+
+ public static class ChangeIdValidator implements CommitValidationListener {
+ private final RefControl refControl;
+ private final String canonicalWebUrl;
+ private final SshInfo sshInfo;
+
+ public ChangeIdValidator(RefControl refControl, String canonicalWebUrl,
+ SshInfo sshInfo) {
+ this.refControl = refControl;
+ this.canonicalWebUrl = canonicalWebUrl;
+ this.sshInfo = sshInfo;
+ }
+
+ @Override
+ public List<CommitValidationMessage> onCommitReceived(
+ CommitReceivedEvent receiveEvent) throws CommitValidationException {
+
+ final ProjectControl projectControl = refControl.getProjectControl();
+ IdentifiedUser currentUser = (IdentifiedUser) refControl.getCurrentUser();
+ final List<String> idList = receiveEvent.commit.getFooterLines(CHANGE_ID);
+
+ if (MagicBranch.isMagicBranch(receiveEvent.command.getRefName())
+ || NEW_PATCHSET.matcher(receiveEvent.command.getRefName()).matches()) {
+ List<CommitValidationMessage> messages =
+ new LinkedList<CommitValidationMessage>();
+
+ if (idList.isEmpty()) {
+ if (projectControl.getProjectState().isRequireChangeID()) {
+ String errMsg = "missing Change-Id in commit message footer";
+ messages.add(getFixedCommitMsgWithChangeId(errMsg, receiveEvent.commit,
+ currentUser, canonicalWebUrl, sshInfo));
+ throw new CommitValidationException(errMsg, messages);
+ }
+ } else if (idList.size() > 1) {
+ throw new CommitValidationException(
+ "multiple Change-Id lines in commit message footer", messages);
+ } else {
+ final String v = idList.get(idList.size() - 1).trim();
+ if (!v.matches("^I[0-9a-f]{8,}.*$")) {
+ final String errMsg =
+ "missing or invalid Change-Id line format in commit message footer";
+ messages.add(getFixedCommitMsgWithChangeId(errMsg, receiveEvent.commit,
+ currentUser, canonicalWebUrl, sshInfo));
+ throw new CommitValidationException(errMsg, messages);
+ }
+ }
+ }
+ return Collections.<CommitValidationMessage>emptyList();
+ }
+ }
+
+ /**
+ * If this is the special project configuration branch, validate the config.
+ */
+ public static class ConfigValidator implements CommitValidationListener {
+ private final RefControl refControl;
+ private final Repository repo;
+
+ public ConfigValidator(RefControl refControl, Repository repo) {
+ this.refControl = refControl;
+ this.repo = repo;
+ }
+
+ @Override
+ public List<CommitValidationMessage> onCommitReceived(
+ CommitReceivedEvent receiveEvent) throws CommitValidationException {
+ IdentifiedUser currentUser = (IdentifiedUser) refControl.getCurrentUser();
+
+ if (GitRepositoryManager.REF_CONFIG.equals(refControl.getRefName())) {
+ List<CommitValidationMessage> messages =
+ new LinkedList<CommitValidationMessage>();
+
+ try {
+ ProjectConfig cfg =
+ new ProjectConfig(receiveEvent.project.getNameKey());
+ cfg.load(repo, receiveEvent.command.getNewId());
+ if (!cfg.getValidationErrors().isEmpty()) {
+ addError("Invalid project configuration:", messages);
+ for (ValidationError err : cfg.getValidationErrors()) {
+ addError(" " + err.getMessage(), messages);
+ }
+ throw new ConfigInvalidException("invalid project configuration");
+ }
+ } catch (Exception e) {
+ log.error("User " + currentUser.getUserName()
+ + " tried to push invalid project configuration "
+ + receiveEvent.command.getNewId().name() + " for "
+ + receiveEvent.project.getName(), e);
+ throw new CommitValidationException("invalid project configuration",
+ messages);
+ }
+ }
+ return Collections.<CommitValidationMessage>emptyList();
+ }
+ }
+
+ /** Require permission to upload merges. */
+ public static class UploadMergesPermissionValidator implements
+ CommitValidationListener {
+ private final RefControl refControl;
+
+ public UploadMergesPermissionValidator(RefControl refControl) {
+ this.refControl = refControl;
+ }
+
+ @Override
+ public List<CommitValidationMessage> onCommitReceived(
+ CommitReceivedEvent receiveEvent) throws CommitValidationException {
+ if (receiveEvent.commit.getParentCount() > 1
+ && !refControl.canUploadMerges()) {
+ throw new CommitValidationException("you are not allowed to upload merges");
+ }
+ return Collections.<CommitValidationMessage>emptyList();
+ }
+ }
+
+ /** Execute commit validation plug-ins */
+ public static class PluginCommitValidationListener implements
+ CommitValidationListener {
+ private final DynamicSet<CommitValidationListener> commitValidationListeners;
+
+ public PluginCommitValidationListener(
+ final DynamicSet<CommitValidationListener> commitValidationListeners) {
+ this.commitValidationListeners = commitValidationListeners;
+ }
+
+ @Override
+ public List<CommitValidationMessage> onCommitReceived(
+ CommitReceivedEvent receiveEvent) throws CommitValidationException {
+ List<CommitValidationMessage> messages =
+ new LinkedList<CommitValidationMessage>();
+
+ for (CommitValidationListener validator : commitValidationListeners) {
+ try {
+ messages.addAll(validator.onCommitReceived(receiveEvent));
+ } catch (CommitValidationException e) {
+ messages.addAll(e.getMessages());
+ throw new CommitValidationException(e.getMessage(), messages);
+ }
+ }
+ return messages;
+ }
+ }
+
+ public static class SignedOffByValidator implements CommitValidationListener {
+ private final RefControl refControl;
+
+ public SignedOffByValidator(RefControl refControl, String canonicalWebUrl) {
+ this.refControl = refControl;
+ }
+
+ @Override
+ public List<CommitValidationMessage> onCommitReceived(
+ CommitReceivedEvent receiveEvent) throws CommitValidationException {
+ IdentifiedUser currentUser = (IdentifiedUser) refControl.getCurrentUser();
+ final PersonIdent committer = receiveEvent.commit.getCommitterIdent();
+ final PersonIdent author = receiveEvent.commit.getAuthorIdent();
+ final ProjectControl projectControl = refControl.getProjectControl();
+
+ if (projectControl.getProjectState().isUseSignedOffBy()) {
+ boolean sboAuthor = false, sboCommitter = false, sboMe = false;
+ for (final FooterLine footer : receiveEvent.commit.getFooterLines()) {
+ if (footer.matches(FooterKey.SIGNED_OFF_BY)) {
+ final String e = footer.getEmailAddress();
+ if (e != null) {
+ sboAuthor |= author.getEmailAddress().equals(e);
+ sboCommitter |= committer.getEmailAddress().equals(e);
+ sboMe |= currentUser.getEmailAddresses().contains(e);
+ }
+ }
+ }
+ if (!sboAuthor && !sboCommitter && !sboMe
+ && !refControl.canForgeCommitter()) {
+ throw new CommitValidationException(
+ "not Signed-off-by author/committer/uploader in commit message footer");
+ }
+ }
+ return Collections.<CommitValidationMessage>emptyList();
+ }
+ }
+
+ /** Require that author matches the uploader. */
+ public static class AuthorUploaderValidator implements
+ CommitValidationListener {
+ private final RefControl refControl;
+ private final String canonicalWebUrl;
+
+ public AuthorUploaderValidator(RefControl refControl, String canonicalWebUrl) {
+ this.refControl = refControl;
+ this.canonicalWebUrl = canonicalWebUrl;
+ }
+
+ @Override
+ public List<CommitValidationMessage> onCommitReceived(
+ CommitReceivedEvent receiveEvent) throws CommitValidationException {
+ IdentifiedUser currentUser = (IdentifiedUser) refControl.getCurrentUser();
+ final PersonIdent author = receiveEvent.commit.getAuthorIdent();
+
+ if (!currentUser.getEmailAddresses().contains(author.getEmailAddress())
+ && !refControl.canForgeAuthor()) {
+ List<CommitValidationMessage> messages =
+ new LinkedList<CommitValidationMessage>();
+
+ messages.add(getInvalidEmailError(receiveEvent.commit, "author", author,
+ currentUser, canonicalWebUrl));
+ throw new CommitValidationException("invalid author", messages);
+ }
+ return Collections.<CommitValidationMessage>emptyList();
+ }
+ }
+
+ /** Require that committer matches the uploader. */
+ public static class CommitterUploaderValidator implements
+ CommitValidationListener {
+ private final RefControl refControl;
+ private final String canonicalWebUrl;
+
+ public CommitterUploaderValidator(RefControl refControl,
+ String canonicalWebUrl) {
+ this.refControl = refControl;
+ this.canonicalWebUrl = canonicalWebUrl;
+ }
+
+ @Override
+ public List<CommitValidationMessage> onCommitReceived(
+ CommitReceivedEvent receiveEvent) throws CommitValidationException {
+ IdentifiedUser currentUser = (IdentifiedUser) refControl.getCurrentUser();
+ final PersonIdent committer = receiveEvent.commit.getCommitterIdent();
+ if (!currentUser.getEmailAddresses()
+ .contains(committer.getEmailAddress())
+ && !refControl.canForgeCommitter()) {
+ List<CommitValidationMessage> messages =
+ new LinkedList<CommitValidationMessage>();
+ messages.add(getInvalidEmailError(receiveEvent.commit, "committer", committer,
+ currentUser, canonicalWebUrl));
+ throw new CommitValidationException("invalid committer", messages);
+ }
+ return Collections.<CommitValidationMessage>emptyList();
+ }
+ }
+
+ /**
+ * Don't allow the user to amend a merge created by Gerrit Code Review. This
+ * seems to happen all too often, due to users not paying any attention to
+ * what they are doing.
+ */
+ public static class AmendedGerritMergeCommitValidationListener implements
+ CommitValidationListener {
+ private final PersonIdent gerritIdent;
+ private final RefControl refControl;
+
+ public AmendedGerritMergeCommitValidationListener(
+ final RefControl refControl, final PersonIdent gerritIdent) {
+ this.refControl = refControl;
+ this.gerritIdent = gerritIdent;
+ }
+
+ @Override
+ public List<CommitValidationMessage> onCommitReceived(
+ CommitReceivedEvent receiveEvent) throws CommitValidationException {
+ final PersonIdent author = receiveEvent.commit.getAuthorIdent();
+
+ if (receiveEvent.commit.getParentCount() > 1
+ && author.getName().equals(gerritIdent.getName())
+ && author.getEmailAddress().equals(gerritIdent.getEmailAddress())
+ && !refControl.canForgeGerritServerIdentity()) {
+ throw new CommitValidationException("do not amend merges not made by you");
+ }
+ return Collections.<CommitValidationMessage>emptyList();
+ }
+ }
+
+ private static CommitValidationMessage getInvalidEmailError(RevCommit c, String type,
+ PersonIdent who, IdentifiedUser currentUser, String canonicalWebUrl) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("\n");
+ sb.append("ERROR: In commit " + c.name() + "\n");
+ sb.append("ERROR: " + type + " email address " + who.getEmailAddress()
+ + "\n");
+ sb.append("ERROR: does not match your user account.\n");
+ sb.append("ERROR:\n");
+ if (currentUser.getEmailAddresses().isEmpty()) {
+ sb.append("ERROR: You have not registered any email addresses.\n");
+ } else {
+ sb.append("ERROR: The following addresses are currently registered:\n");
+ for (String address : currentUser.getEmailAddresses()) {
+ sb.append("ERROR: " + address + "\n");
+ }
+ }
+ sb.append("ERROR:\n");
+ if (canonicalWebUrl != null) {
+ sb.append("ERROR: To register an email address, please visit:\n");
+ sb.append("ERROR: " + canonicalWebUrl + "#" + PageLinks.SETTINGS_CONTACT
+ + "\n");
+ }
+ sb.append("\n");
+ return new CommitValidationMessage(sb.toString(), false);
+ }
+
+ /**
+ * We handle 3 cases:
+ * 1. No change id in the commit message at all.
+ * 2. Change id last in the commit message but missing empty line to create the footer.
+ * 3. There is a change-id somewhere in the commit message, but we ignore it.
+ *
+ * @return The fixed up commit message
+ */
+ private static CommitValidationMessage getFixedCommitMsgWithChangeId(final String errMsg,
+ final RevCommit c, final IdentifiedUser currentUser,
+ String canonicalWebUrl, final SshInfo sshInfo) {
+ final String changeId = "Change-Id:";
+ StringBuilder sb = new StringBuilder();
+ sb.append("ERROR: ").append(errMsg);
+ sb.append('\n');
+ sb.append("Suggestion for commit message:\n");
+
+ if (c.getFullMessage().indexOf(changeId) == -1) {
+ sb.append(c.getFullMessage());
+ sb.append('\n');
+ sb.append(changeId).append(" I").append(c.name());
+ } else {
+ String lines[] = c.getFullMessage().trim().split("\n");
+ String lastLine = lines.length > 0 ? lines[lines.length - 1] : "";
+
+ if (lastLine.indexOf(changeId) == 0) {
+ for (int i = 0; i < lines.length - 1; i++) {
+ sb.append(lines[i]);
+ sb.append('\n');
+ }
+
+ sb.append('\n');
+ sb.append(lastLine);
+ } else {
+ sb.append(c.getFullMessage());
+ sb.append('\n');
+ sb.append(changeId).append(" I").append(c.name());
+ sb.append('\n');
+ sb.append("Hint: A potential Change-Id was found, but it was not in the footer of the commit message.");
+ }
+ }
+ sb.append('\n');
+ sb.append('\n');
+ sb.append("Hint: To automatically insert Change-Id, install the hook:\n");
+ sb.append(getCommitMessageHookInstallationHint(currentUser,
+ canonicalWebUrl, sshInfo)).append('\n');
+ sb.append('\n');
+
+ return new CommitValidationMessage(sb.toString(), false);
+ }
+
+ private static String getCommitMessageHookInstallationHint(
+ final IdentifiedUser currentUser, String canonicalWebUrl,
+ final SshInfo sshInfo) {
+ final List<HostKey> hostKeys = sshInfo.getHostKeys();
+
+ // If there are no SSH keys, the commit-msg hook must be installed via
+ // HTTP(S)
+ if (hostKeys.isEmpty()) {
+ String p = ".git/hooks/commit-msg";
+ return String.format(
+ " curl -o %s %s/tools/hooks/commit-msg ; chmod +x %s", p,
+ getGerritUrl(canonicalWebUrl), p);
+ }
+
+ // SSH keys exist, so the hook can be installed with scp.
+ String sshHost;
+ int sshPort;
+ String host = hostKeys.get(0).getHost();
+ int c = host.lastIndexOf(':');
+ if (0 <= c) {
+ if (host.startsWith("*:")) {
+ sshHost = getGerritHost(canonicalWebUrl);
+ } else {
+ sshHost = host.substring(0, c);
+ }
+ sshPort = Integer.parseInt(host.substring(c + 1));
+ } else {
+ sshHost = host;
+ sshPort = 22;
+ }
+
+ return String.format(" scp -p -P %d %s@%s:hooks/commit-msg .git/hooks/",
+ sshPort, currentUser.getUserName(), sshHost);
+ }
+
+ /**
+ * Get the Gerrit URL.
+ *
+ * @return the canonical URL (with any trailing slash removed) if it is
+ * configured, otherwise fall back to "http://hostname" where hostname
+ * is the value returned by {@link #getGerritHost()}.
+ */
+ private static String getGerritUrl(String canonicalWebUrl) {
+ if (canonicalWebUrl != null) {
+ if (canonicalWebUrl.endsWith("/")) {
+ return canonicalWebUrl.substring(0, canonicalWebUrl.lastIndexOf("/"));
+ }
+ return canonicalWebUrl;
+ } else {
+ return "http://" + getGerritHost(canonicalWebUrl);
+ }
+ }
+
+ /**
+ * Get the Gerrit hostname.
+ *
+ * @return the hostname from the canonical URL if it is configured, otherwise
+ * whatever the OS says the hostname is.
+ */
+ private static String getGerritHost(String canonicalWebUrl) {
+ String host;
+ if (canonicalWebUrl != null) {
+ try {
+ host = new URL(canonicalWebUrl).getHost();
+ } catch (MalformedURLException e) {
+ host = SystemReader.getInstance().getHostname();
+ }
+ } else {
+ host = SystemReader.getInstance().getHostname();
+ }
+ return host;
+ }
+
+ private static void addError(String error,
+ List<CommitValidationMessage> messages) {
+ messages.add(new CommitValidationMessage(error, true));
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/GetGroup.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/GetGroup.java
new file mode 100644
index 0000000..f7c7fb8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/GetGroup.java
@@ -0,0 +1,59 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.group;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.util.Url;
+
+class GetGroup implements RestReadView<GroupResource> {
+
+ @Override
+ public Object apply(GroupResource resource) throws AuthException,
+ BadRequestException, ResourceConflictException, Exception {
+ GroupDescription.Basic group = resource.getControl().getGroup();
+ GroupInfo info = new GroupInfo();
+ info.name = resource.getName();
+ info.uuid = resource.getGroupUUID().get();
+ info.isVisibleToAll = group.isVisibleToAll();
+ if (group instanceof GroupDescription.Internal) {
+ final AccountGroup internalGroup =
+ ((GroupDescription.Internal) group).getAccountGroup();
+ info.description = Strings.emptyToNull(internalGroup.getDescription());
+ info.ownerUuid = internalGroup.getOwnerGroupUUID().get();
+ }
+ info.finish();
+ return info;
+ }
+
+ static class GroupInfo {
+ final String kind = "gerritcodereview#group";
+ String id;
+ String name;
+ String uuid;
+ String description;
+ boolean isVisibleToAll;
+ String ownerUuid;
+
+ void finish() {
+ id = Url.encode(GroupsCollection.UUID_PREFIX + uuid);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupResource.java
new file mode 100644
index 0000000..e7ec85c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupResource.java
@@ -0,0 +1,44 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.group;
+
+import com.google.gerrit.extensions.restapi.RestResource;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.account.GroupControl;
+import com.google.inject.TypeLiteral;
+
+public class GroupResource implements RestResource {
+ public static final TypeLiteral<RestView<GroupResource>> GROUP_KIND =
+ new TypeLiteral<RestView<GroupResource>>() {};
+
+ private final GroupControl control;
+
+ GroupResource(GroupControl control) {
+ this.control = control;
+ }
+
+ public String getName() {
+ return control.getGroup().getName();
+ }
+
+ public AccountGroup.UUID getGroupUUID() {
+ return control.getGroup().getGroupUUID();
+ }
+
+ public GroupControl getControl() {
+ return control;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupsCollection.java
new file mode 100644
index 0000000..c085521
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupsCollection.java
@@ -0,0 +1,103 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.group;
+
+import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestCollection;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.AnonymousUser;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.GroupControl;
+import com.google.gerrit.server.util.Url;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class GroupsCollection implements
+ RestCollection<TopLevelResource, GroupResource> {
+ public final static String UUID_PREFIX = "uuid-";
+
+ private final DynamicMap<RestView<GroupResource>> views;
+ private final Provider<ListGroups> list;
+ private final GroupControl.Factory groupControlFactory;
+ private final Provider<CurrentUser> self;
+
+ @Inject
+ GroupsCollection(final DynamicMap<RestView<GroupResource>> views,
+ final Provider<ListGroups> list,
+ final GroupControl.Factory groupControlFactory,
+ final Provider<CurrentUser> self) {
+ this.views = views;
+ this.list = list;
+ this.groupControlFactory = groupControlFactory;
+ this.self = self;
+ }
+
+ @Override
+ public RestView<TopLevelResource> list() throws ResourceNotFoundException,
+ AuthException {
+ final CurrentUser user = self.get();
+ if (user instanceof AnonymousUser) {
+ throw new AuthException("Authentication required");
+ } else if(!(user instanceof IdentifiedUser)) {
+ throw new ResourceNotFoundException();
+ }
+
+ return list.get();
+ }
+
+ @Override
+ public GroupResource parse(TopLevelResource parent, String id)
+ throws ResourceNotFoundException, Exception {
+ final CurrentUser user = self.get();
+ if (user instanceof AnonymousUser) {
+ throw new AuthException("Authentication required");
+ } else if(!(user instanceof IdentifiedUser)) {
+ throw new ResourceNotFoundException(id);
+ }
+
+ final String decodedId = Url.decode(id);
+ final GroupControl ctl;
+ try {
+ if (decodedId.startsWith(UUID_PREFIX)) {
+ final String uuid = decodedId.substring(UUID_PREFIX.length());
+ ctl = groupControlFactory.controlFor(new AccountGroup.UUID(uuid));
+ } else {
+ try {
+ ctl = groupControlFactory.controlFor(
+ new AccountGroup.Id(Integer.parseInt(decodedId)));
+ } catch (NumberFormatException e) {
+ throw new ResourceNotFoundException(id);
+ }
+ }
+ } catch (NoSuchGroupException e) {
+ throw new ResourceNotFoundException(id);
+ }
+ if (!ctl.isVisible() && !ctl.isOwner()) {
+ throw new ResourceNotFoundException(id);
+ }
+ return new GroupResource(ctl);
+ }
+
+ @Override
+ public DynamicMap<RestView<GroupResource>> views() {
+ return views;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
new file mode 100644
index 0000000..ec03f3a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
@@ -0,0 +1,186 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.group;
+
+import com.google.common.collect.Maps;
+import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.OutputFormat;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.VisibleGroups;
+import com.google.gerrit.server.ioutil.ColumnFormatter;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.util.Url;
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+import com.google.gwtorm.client.KeyUtil;
+import com.google.inject.Inject;
+
+import org.kohsuke.args4j.Option;
+
+import java.io.BufferedWriter;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/** List groups visible to the calling user. */
+public class ListGroups implements RestReadView<TopLevelResource> {
+
+ private final GroupCache groupCache;
+ private final VisibleGroups.Factory visibleGroupsFactory;
+ private final IdentifiedUser.GenericFactory userFactory;
+
+ @Option(name = "--project", aliases = {"-p"},
+ usage = "projects for which the groups should be listed")
+ private final List<ProjectControl> projects = new ArrayList<ProjectControl>();
+
+ @Option(name = "--visible-to-all", usage = "to list only groups that are visible to all registered users")
+ private boolean visibleToAll;
+
+ @Option(name = "--type", usage = "type of group")
+ private AccountGroup.Type groupType;
+
+ @Option(name = "--user", aliases = {"-u"},
+ usage = "user for which the groups should be listed")
+ private Account.Id user;
+
+ @Option(name = "--verbose", aliases = {"-v"},
+ usage = "verbose output format with tab-separated columns for the " +
+ "group name, UUID, description, type, owner group name, " +
+ "owner group UUID, and whether the group is visible to all")
+ private boolean verboseOutput;
+
+ @Option(name = "-m", metaVar = "MATCH", usage = "match group substring")
+ private String matchSubstring;
+
+ @Inject
+ protected ListGroups(final GroupCache groupCache,
+ final VisibleGroups.Factory visibleGroupsFactory,
+ final IdentifiedUser.GenericFactory userFactory) {
+ this.groupCache = groupCache;
+ this.visibleGroupsFactory = visibleGroupsFactory;
+ this.userFactory = userFactory;
+ }
+
+ public Account.Id getUser() {
+ return user;
+ }
+
+ public List<ProjectControl> getProjects() {
+ return projects;
+ }
+
+ @Override
+ public Object apply(TopLevelResource resource) throws AuthException,
+ BadRequestException, ResourceConflictException, Exception {
+ return display(null);
+ }
+
+ public JsonElement display(OutputStream displayOutputStream)
+ throws NoSuchGroupException {
+ PrintWriter stdout = null;
+ if (displayOutputStream != null) {
+ try {
+ stdout = new PrintWriter(new BufferedWriter(
+ new OutputStreamWriter(displayOutputStream, "UTF-8")));
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("JVM lacks UTF-8 encoding", e);
+ }
+ }
+
+ try {
+ final VisibleGroups visibleGroups = visibleGroupsFactory.create();
+ visibleGroups.setOnlyVisibleToAll(visibleToAll);
+ visibleGroups.setGroupType(groupType);
+ visibleGroups.setMatch(matchSubstring);
+ final List<AccountGroup> groupList;
+ if (!projects.isEmpty()) {
+ groupList = visibleGroups.get(projects);
+ } else if (user != null) {
+ groupList = visibleGroups.get(userFactory.create(user));
+ } else {
+ groupList = visibleGroups.get();
+ }
+
+ if (stdout == null) {
+ final Map<String, GroupInfo> output = Maps.newTreeMap();
+ for (final AccountGroup g : groupList) {
+ final GroupInfo info = new GroupInfo();
+ info.name = g.getName();
+ info.groupId = g.getId().get();
+ info.setUuid(g.getGroupUUID());
+ info.description = g.getDescription();
+ info.isVisibleToAll = g.isVisibleToAll();
+ info.ownerUuid = g.getOwnerGroupUUID().get();
+ output.put(info.name, info);
+ }
+ return OutputFormat.JSON.newGson().toJsonTree(output,
+ new TypeToken<Map<String, GroupInfo>>() {}.getType());
+ } else {
+ final ColumnFormatter formatter = new ColumnFormatter(stdout, '\t');
+ for (final AccountGroup g : groupList) {
+ formatter.addColumn(g.getName());
+ if (verboseOutput) {
+ formatter.addColumn(KeyUtil.decode(g.getGroupUUID().toString()));
+ formatter.addColumn(
+ g.getDescription() != null ? g.getDescription() : "");
+ formatter.addColumn(g.getType().toString());
+ final AccountGroup owningGroup =
+ groupCache.get(g.getOwnerGroupUUID());
+ formatter.addColumn(
+ owningGroup != null ? owningGroup.getName() : "n/a");
+ formatter.addColumn(KeyUtil.decode(g.getOwnerGroupUUID().toString()));
+ formatter.addColumn(Boolean.toString(g.isVisibleToAll()));
+ }
+ formatter.nextLine();
+ }
+ formatter.finish();
+ return null;
+ }
+ } finally {
+ if (stdout != null) {
+ stdout.flush();
+ }
+ }
+ }
+
+ static class GroupInfo {
+ final String kind = "gerritcodereview#group";
+
+ transient String name;
+ String id;
+ String uuid;
+ int groupId;
+ String description;
+ boolean isVisibleToAll;
+ String ownerUuid;
+
+ void setUuid(AccountGroup.UUID u) {
+ uuid = u.get();
+ id = Url.encode(GroupsCollection.UUID_PREFIX + uuid);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/Module.java
new file mode 100644
index 0000000..58f1506
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/Module.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.group;
+
+import static com.google.gerrit.server.group.GroupResource.GROUP_KIND;
+
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.RestApiModule;
+
+public class Module extends RestApiModule {
+ @Override
+ protected void configure() {
+ bind(GroupsCollection.class);
+
+ DynamicMap.mapOf(binder(), GROUP_KIND);
+
+ get(GROUP_KIND).to(GetGroup.class);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java
index b6ad8d8..90db3d7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.mail;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.server.config.AnonymousCowardName;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddReviewerSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddReviewerSender.java
index f7ace27..661737b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddReviewerSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddReviewerSender.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.mail;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.ssh.SshInfo;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
index 1274016..2fcdb2c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
@@ -18,6 +18,7 @@
import com.google.common.collect.Sets;
import com.google.gerrit.common.data.GroupDescriptions;
import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupIncludeByUuid;
@@ -65,7 +66,7 @@
import java.util.TreeSet;
/** Sends an email to one or more interested parties. */
-public abstract class ChangeEmail extends OutgoingEmail {
+public abstract class ChangeEmail extends NotificationEmail {
private static final Logger log = LoggerFactory.getLogger(ChangeEmail.class);
protected final Change change;
@@ -80,7 +81,7 @@
protected ChangeEmail(EmailArguments ea, final String anonymousCowardName,
final Change c, final String mc) {
- super(ea, anonymousCowardName, mc);
+ super(ea, anonymousCowardName, mc, c.getProject(), c.getDest());
change = c;
changeData = new ChangeData(change);
emailOnlyAuthors = false;
@@ -361,8 +362,8 @@
for (AccountProjectWatch w : args.db.get().accountProjectWatches()
.byProject(change.getProject())) {
- projectWatchers.add(w.getAccountId());
if (w.isNotify(type)) {
+ projectWatchers.add(w.getAccountId());
add(matching, w);
}
}
@@ -576,10 +577,7 @@
velocityContext.put("change", change);
velocityContext.put("changeId", change.getKey());
velocityContext.put("coverLetter", getCoverLetter());
- velocityContext.put("branch", change.getDest());
velocityContext.put("fromName", getNameFor(fromId));
- velocityContext.put("projectName", //
- projectState != null ? projectState.getProject().getName() : null);
velocityContext.put("patchSet", patchSet);
velocityContext.put("patchSetInfo", patchSetInfo);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
index 2d0046c..656a412 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
@@ -14,10 +14,12 @@
package com.google.gerrit.server.mail;
+import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
+import com.google.gerrit.server.change.PostReview.NotifyHandling;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.patch.PatchFile;
import com.google.gerrit.server.patch.PatchList;
@@ -37,15 +39,19 @@
/** Send comments, after the author of them hit used Publish Comments in the UI. */
public class CommentSender extends ReplyToChangeSender {
public static interface Factory {
- public CommentSender create(Change change);
+ public CommentSender create(NotifyHandling notify, Change change);
}
+ private final NotifyHandling notify;
private List<PatchLineComment> inlineComments = Collections.emptyList();
@Inject
public CommentSender(EmailArguments ea,
- @AnonymousCowardName String anonymousCowardName, @Assisted Change c) {
+ @AnonymousCowardName String anonymousCowardName,
+ @Assisted NotifyHandling notify,
+ @Assisted Change c) {
super(ea, anonymousCowardName, c, "comment");
+ this.notify = notify;
}
public void setPatchLineComments(final List<PatchLineComment> plc) {
@@ -67,9 +73,13 @@
protected void init() throws EmailException {
super.init();
- ccAllApprovals();
- bccStarredBy();
- includeWatchers(NotifyType.ALL_COMMENTS);
+ if (notify.compareTo(NotifyHandling.OWNER_REVIEWERS) >= 0) {
+ ccAllApprovals();
+ }
+ if (notify.compareTo(NotifyHandling.ALL) >= 0) {
+ bccStarredBy();
+ includeWatchers(NotifyType.ALL_COMMENTS);
+ }
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommitMessageEditedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommitMessageEditedSender.java
index de3cb65..4e378f5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommitMessageEditedSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommitMessageEditedSender.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.mail;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.ssh.SshInfo;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
index 20c2e15..1619c51 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.mail;
import com.google.common.collect.Iterables;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Change;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSender.java
index 9c4f4c4..a7a1028 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSender.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.mail;
+import com.google.gerrit.common.errors.EmailException;
+
import java.util.Collection;
import java.util.Map;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java
new file mode 100644
index 0000000..f22c6e4
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java
@@ -0,0 +1,131 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.mail;
+
+import com.google.gerrit.common.errors.NoSuchAccountException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.server.account.AccountResolver;
+import com.google.gwtorm.server.OrmException;
+
+import org.eclipse.jgit.revwalk.FooterKey;
+import org.eclipse.jgit.revwalk.FooterLine;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class MailUtil {
+ private static final FooterKey REVIEWED_BY = new FooterKey("Reviewed-by");
+ private static final FooterKey TESTED_BY = new FooterKey("Tested-by");
+
+ public static MailRecipients getRecipientsFromFooters(
+ final AccountResolver accountResolver, final PatchSet ps,
+ final List<FooterLine> footerLines) throws OrmException {
+ final MailRecipients recipients = new MailRecipients();
+ if (!ps.isDraft()) {
+ for (final FooterLine footerLine : footerLines) {
+ try {
+ if (isReviewer(footerLine)) {
+ recipients.reviewers.add(toAccountId(accountResolver, footerLine
+ .getValue().trim()));
+ } else if (footerLine.matches(FooterKey.CC)) {
+ recipients.cc.add(toAccountId(accountResolver, footerLine
+ .getValue().trim()));
+ }
+ } catch (NoSuchAccountException e) {
+ continue;
+ }
+ }
+ }
+ return recipients;
+ }
+
+ public static MailRecipients getRecipientsFromApprovals(
+ final List<PatchSetApproval> approvals) {
+ final MailRecipients recipients = new MailRecipients();
+ for (PatchSetApproval a : approvals) {
+ if (a.getValue() != 0) {
+ recipients.reviewers.add(a.getAccountId());
+ } else {
+ recipients.cc.add(a.getAccountId());
+ }
+ }
+ return recipients;
+ }
+
+ private static Account.Id toAccountId(final AccountResolver accountResolver,
+ final String nameOrEmail) throws OrmException, NoSuchAccountException {
+ final Account a = accountResolver.findByNameOrEmail(nameOrEmail);
+ if (a == null) {
+ throw new NoSuchAccountException("\"" + nameOrEmail
+ + "\" is not registered");
+ }
+ return a.getId();
+ }
+
+ private static boolean isReviewer(final FooterLine candidateFooterLine) {
+ return candidateFooterLine.matches(FooterKey.SIGNED_OFF_BY)
+ || candidateFooterLine.matches(FooterKey.ACKED_BY)
+ || candidateFooterLine.matches(REVIEWED_BY)
+ || candidateFooterLine.matches(TESTED_BY);
+ }
+
+ public static class MailRecipients {
+ private final Set<Account.Id> reviewers;
+ private final Set<Account.Id> cc;
+
+ public MailRecipients() {
+ this.reviewers = new HashSet<Account.Id>();
+ this.cc = new HashSet<Account.Id>();
+ }
+
+ public MailRecipients(final Set<Account.Id> reviewers,
+ final Set<Account.Id> cc) {
+ this.reviewers = new HashSet<Account.Id>(reviewers);
+ this.cc = new HashSet<Account.Id>(cc);
+ }
+
+ public void add(final MailRecipients recipients) {
+ reviewers.addAll(recipients.reviewers);
+ cc.addAll(recipients.cc);
+ }
+
+ public void remove(final Account.Id toRemove) {
+ reviewers.remove(toRemove);
+ cc.remove(toRemove);
+ }
+
+ public Set<Account.Id> getReviewers() {
+ return Collections.unmodifiableSet(reviewers);
+ }
+
+ public Set<Account.Id> getCcOnly() {
+ final Set<Account.Id> cc = new HashSet<Account.Id>(this.cc);
+ cc.removeAll(reviewers);
+ return Collections.unmodifiableSet(cc);
+ }
+
+ public Set<Account.Id> getAll() {
+ final Set<Account.Id> all =
+ new HashSet<Account.Id>(reviewers.size() + cc.size());
+ all.addAll(reviewers);
+ all.addAll(cc);
+ return Collections.unmodifiableSet(all);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergeFailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergeFailSender.java
index b695fb4..9612ee2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergeFailSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergeFailSender.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.mail;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.inject.Inject;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
index db56176..08d3451 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
@@ -16,6 +16,7 @@
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.ApprovalCategory;
import com.google.gerrit.reviewdb.client.ApprovalCategoryValue;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java
index 2e459d4..bb62bac 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.mail;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.ssh.SshInfo;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/NotificationEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/NotificationEmail.java
new file mode 100644
index 0000000..b0d5862
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/NotificationEmail.java
@@ -0,0 +1,41 @@
+// 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.mail;
+
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Project;
+
+/**
+ * Common class for notifications that are related to a project and branch
+ */
+public abstract class NotificationEmail extends OutgoingEmail {
+ protected Project.NameKey project;
+ protected Branch.NameKey branch;
+
+ protected NotificationEmail(EmailArguments ea, String anonymousCowardName,
+ String mc, Project.NameKey project, Branch.NameKey branch) {
+ super(ea, anonymousCowardName, mc);
+
+ this.project = project;
+ this.branch = branch;
+ }
+
+ @Override
+ protected void setupVelocityContext() {
+ super.setupVelocityContext();
+ velocityContext.put("projectName", project.get());
+ velocityContext.put("branch", branch);
+ }
+}
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
index 187b8a7..9bb087d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.mail;
import com.google.common.collect.Sets;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.UserIdentity;
import com.google.gerrit.server.account.AccountState;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RebasedPatchSetSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RebasedPatchSetSender.java
index 8fc8238..49e08b1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RebasedPatchSetSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RebasedPatchSetSender.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.mail;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.ssh.SshInfo;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java
index 17fe9c6..923fead 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.mail;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.inject.Inject;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java
index 9705b8a..14c4fca 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.mail;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.config.AnonymousCowardName;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java
index 2a77d39..c9540c7e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.mail;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.Change;
/** Alert a user to a reply to a change, usually commentary made during review. */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RestoredSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RestoredSender.java
index 55884e9..f72af26 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RestoredSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RestoredSender.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.mail;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.server.config.AnonymousCowardName;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RevertedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RevertedSender.java
index d62e82a..8b9a6d7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RevertedSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RevertedSender.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.mail;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.server.config.AnonymousCowardName;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java
index f681710..bd897b2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.mail;
import com.google.gerrit.common.Version;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.AbstractModule;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/CleanupHandle.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/CleanupHandle.java
index 7018a3b..50efb68 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/CleanupHandle.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/CleanupHandle.java
@@ -39,7 +39,9 @@
} catch (IOException err) {
}
if (!tmpFile.delete() && tmpFile.exists()) {
- PluginLoader.log.warn("Cannot delete " + tmpFile.getAbsolutePath());
+ PluginLoader.log.warn("Cannot delete " + tmpFile.getAbsolutePath()
+ + ", retrying to delete it on termination of the virtual machine");
+ tmpFile.deleteOnExit();
} else {
PluginLoader.log.info("Cleaned plugin " + tmpFile.getName());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
index 3502b00..4e565d3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
@@ -306,6 +306,10 @@
dropRemovedDisabledPlugins(jars);
for (File jar : jars) {
+ if (jar.getName().endsWith(".disabled")) {
+ continue;
+ }
+
String name = nameOf(jar);
FileSnapshot brokenTime = broken.get(name);
if (brokenTime != null && !brokenTime.isModified(jar)) {
@@ -447,9 +451,10 @@
URL[] urls = {tmp.toURI().toURL()};
ClassLoader parentLoader = parentFor(type);
ClassLoader pluginLoader = new URLClassLoader(urls, parentLoader);
- cleanupHandles.put(
- new CleanupHandle(tmp, jarFile, pluginLoader, cleanupQueue),
- Boolean.TRUE);
+ final CleanupHandle cleanupHandle =
+ new CleanupHandle(tmp, jarFile, pluginLoader, cleanupQueue);
+ cleanupHandle.enqueue();
+ cleanupHandles.put(cleanupHandle, Boolean.TRUE);
Class<? extends Module> sysModule = load(sysName, pluginLoader);
Class<? extends Module> sshModule = load(sshName, pluginLoader);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
index 3dbd7b7..06bc099 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
@@ -205,8 +205,6 @@
projectCache.onCreateProject(createProjectArgs.getProject());
repoManager.setProjectDescription(createProjectArgs.getProject(),
createProjectArgs.projectDescription);
- referenceUpdated.fire(createProjectArgs.getProject(),
- GitRepositoryManager.REF_CONFIG);
}
private void validateParameters() throws ProjectCreationFailedException {
@@ -216,11 +214,18 @@
}
if (createProjectArgs.getProjectName().endsWith(Constants.DOT_GIT_EXT)) {
- createProjectArgs.setProjectName(createProjectArgs.getProjectName()
- .substring(
- 0,
- createProjectArgs.getProjectName().length()
- - Constants.DOT_GIT_EXT.length()));
+ String nameWithoutSuffix = createProjectArgs.getProjectName();
+ // Be nice and drop the trailing ".git" suffix, which we never keep
+ // in our database, but clients might mistakenly provide anyway.
+ //
+ nameWithoutSuffix = nameWithoutSuffix.substring(0, //
+ nameWithoutSuffix.length() - Constants.DOT_GIT_EXT.length());
+ while (nameWithoutSuffix.endsWith("/")) {
+ nameWithoutSuffix =
+ nameWithoutSuffix.substring(0, nameWithoutSuffix.length() - 1);
+ }
+
+ createProjectArgs.setProjectName(nameWithoutSuffix);
}
if (!currentUser.getCapabilities().canCreateProject()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/BaseDataSourceType.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/BaseDataSourceType.java
index 1d12fea..e72c3e9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/BaseDataSourceType.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/BaseDataSourceType.java
@@ -42,11 +42,6 @@
return getScriptRunner("index_generic.sql");
}
- @Override
- public ScriptRunner getNextValScript() throws IOException {
- return ScriptRunner.NOOP;
- }
-
protected static final ScriptRunner getScriptRunner(String path) throws IOException {
if (path == null) {
return ScriptRunner.NOOP;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceType.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceType.java
index 513ef67..14cb780 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceType.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceType.java
@@ -33,12 +33,4 @@
* @throws IOException
*/
public ScriptRunner getIndexScript() throws IOException;
-
- /**
- * Return a ScriptRunner that runs the nextVal script. Must not return
- * <code>null</code>, but may return a ScriptRunner that does nothing.
- *
- * @throws IOException
- */
- public ScriptRunner getNextValScript() throws IOException;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/MySql.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/MySql.java
index ed7aadf..8c46732 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/MySql.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/MySql.java
@@ -23,8 +23,6 @@
import org.eclipse.jgit.lib.Config;
-import java.io.IOException;
-
class MySql extends BaseDataSourceType {
private Config cfg;
@@ -51,9 +49,4 @@
public boolean usePool() {
return false;
}
-
- @Override
- public ScriptRunner getNextValScript() throws IOException {
- return getScriptRunner("mysql_nextval.sql");
- }
}
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
index 0ca5d90..1742f48 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
@@ -119,7 +119,6 @@
}
dataSourceType.getIndexScript().run(db);
- dataSourceType.getNextValScript().run(db);
}
private AccountGroup newGroup(ReviewDb c, String name, AccountGroup.UUID uuid)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ssh/NoSshInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/ssh/NoSshInfo.java
index 519b922..6ceec2d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ssh/NoSshInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ssh/NoSshInfo.java
@@ -19,7 +19,7 @@
import java.util.Collections;
import java.util.List;
-class NoSshInfo implements SshInfo {
+public class NoSshInfo implements SshInfo {
@Override
public List<HostKey> getHostKeys() {
return Collections.emptyList();
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
index f8856f2..412ea70 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
@@ -14,93 +14,27 @@
package com.google.gerrit.sshd.commands;
-import com.google.gerrit.common.data.GroupList;
-import com.google.gerrit.common.errors.NoSuchGroupException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.GroupCache;
-import com.google.gerrit.server.account.VisibleGroups;
-import com.google.gerrit.server.ioutil.ColumnFormatter;
-import com.google.gerrit.server.project.ProjectControl;
-import com.google.gerrit.sshd.SshCommand;
-import com.google.gwtorm.client.KeyUtil;
+import com.google.gerrit.server.group.ListGroups;
+import com.google.gerrit.sshd.BaseCommand;
import com.google.inject.Inject;
-import org.kohsuke.args4j.Option;
+import org.apache.sshd.server.Environment;
-import java.util.ArrayList;
-import java.util.List;
-
-public class ListGroupsCommand extends SshCommand {
+public class ListGroupsCommand extends BaseCommand {
@Inject
- private GroupCache groupCache;
-
- @Inject
- private VisibleGroups.Factory visibleGroupsFactory;
-
- @Inject
- private IdentifiedUser.GenericFactory userFactory;
-
- @Option(name = "--project", aliases = {"-p"},
- usage = "projects for which the groups should be listed")
- private final List<ProjectControl> projects = new ArrayList<ProjectControl>();
-
- @Option(name = "--visible-to-all", usage = "to list only groups that are visible to all registered users")
- private boolean visibleToAll;
-
- @Option(name = "--type", usage = "type of group")
- private AccountGroup.Type groupType;
-
- @Option(name = "--user", aliases = {"-u"},
- usage = "user for which the groups should be listed")
- private Account.Id user;
-
- @Option(name = "--verbose", aliases = {"-v"},
- usage = "verbose output format with tab-separated columns for the " +
- "group name, UUID, description, type, owner group name, " +
- "owner group UUID, and whether the group is visible to all")
- private boolean verboseOutput;
+ private ListGroups impl;
@Override
- protected void run() throws Failure {
- try {
- if (user != null && !projects.isEmpty()) {
- throw new UnloggedFailure(1, "fatal: --user and --project options are not compatible.");
- }
-
- final VisibleGroups visibleGroups = visibleGroupsFactory.create();
- visibleGroups.setOnlyVisibleToAll(visibleToAll);
- visibleGroups.setGroupType(groupType);
- final GroupList groupList;
- if (!projects.isEmpty()) {
- groupList = visibleGroups.get(projects);
- } else if (user != null) {
- groupList = visibleGroups.get(userFactory.create(user));
- } else {
- groupList = visibleGroups.get();
- }
-
- final ColumnFormatter formatter = new ColumnFormatter(stdout, '\t');
- for (final AccountGroup g : groupList.getGroups()) {
- formatter.addColumn(g.getName());
- if (verboseOutput) {
- formatter.addColumn(KeyUtil.decode(g.getGroupUUID().toString()));
- formatter.addColumn(
- g.getDescription() != null ? g.getDescription() : "");
- formatter.addColumn(g.getType().toString());
- final AccountGroup owningGroup =
- groupCache.get(g.getOwnerGroupUUID());
- formatter.addColumn(
- owningGroup != null ? owningGroup.getName() : "n/a");
- formatter.addColumn(KeyUtil.decode(g.getOwnerGroupUUID().toString()));
- formatter.addColumn(Boolean.toString(g.isVisibleToAll()));
+ public void start(final Environment env) {
+ startThread(new CommandRunnable() {
+ @Override
+ public void run() throws Exception {
+ parseCommandLine(impl);
+ if (impl.getUser() != null && !impl.getProjects().isEmpty()) {
+ throw new UnloggedFailure(1, "fatal: --user and --project options are not compatible.");
}
- formatter.nextLine();
+ impl.display(out);
}
- formatter.finish();
- } catch (NoSuchGroupException e) {
- throw die(e);
- }
+ });
}
}