Allow watching specific branches or any other search query
Any valid search string is now a valid filter expression or a
watched project. The only operator not supported here is the
is:watched operator, because that creates a recursive call that
would never succeed.
The change turned out far bigger than it should be due to the request
scope requirement for the query builder. We had to rearrange a lot
of code to ensure we always have the request scope available in order
to construct a query builder and execute the filter expressions.
Bug: issue 492
Change-Id: I199d9b215e000c049279cd8e86e7a36386fee0fb
Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java
index b4fc55d..e219074 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java
@@ -48,7 +48,7 @@
void myProjectWatch(AsyncCallback<List<AccountProjectWatchInfo>> callback);
@SignInRequired
- void addProjectWatch(String projectName,
+ void addProjectWatch(String projectName, String filter,
AsyncCallback<AccountProjectWatchInfo> callback);
@SignInRequired
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 40fe731..c556770 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
@@ -32,6 +32,7 @@
String removeReviewer();
String removeReviewerCell();
String addSshKeyPanel();
+ String addWatchPanel();
String approvalCategoryList();
String approvalTable();
String approvalhint();
@@ -180,4 +181,5 @@
String useridentity();
String usernameField();
String version();
+ String watchedProjectFilter();
}
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 e161c14..3756bed 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
@@ -80,6 +80,9 @@
String buttonWatchProject();
String defaultProjectName();
+ String defaultFilter();
+ String watchedProjectName();
+ String watchedProjectFilter();
String watchedProjectColumnEmailNotifications();
String watchedProjectColumnNewChanges();
String watchedProjectColumnAllComments();
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 b74add4..29ee14a 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
@@ -61,6 +61,9 @@
buttonWatchProject = Watch
defaultProjectName = Project Name
+defaultFilter = branch:name, or other search expression
+watchedProjectName = Project Name
+watchedProjectFilter = Only If
watchedProjectColumnEmailNotifications = Email Notifications
watchedProjectColumnNewChanges = New Changes
watchedProjectColumnAllComments = All Comments
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchedProjectsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchedProjectsScreen.java
index eb8a8f1..28099bf 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchedProjectsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchedProjectsScreen.java
@@ -38,8 +38,11 @@
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
+import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
import com.google.gwtexpui.globalkey.client.NpTextBox;
import com.google.gwtjsonrpc.client.VoidResult;
@@ -51,7 +54,9 @@
private WatchTable watches;
private Button addNew;
+ private NpTextBox nameBox;
private SuggestBox nameTxt;
+ private NpTextBox filterTxt;
private Button delSel;
private boolean submitOnSelection;
@@ -60,32 +65,30 @@
super.onInitUI();
{
- final FlowPanel fp = new FlowPanel();
-
- final NpTextBox box = new NpTextBox();
- nameTxt = new SuggestBox(new ProjectNameSuggestOracle(), box);
- box.setVisibleLength(50);
- box.setText(Util.C.defaultProjectName());
- box.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
- box.addFocusHandler(new FocusHandler() {
+ nameBox = new NpTextBox();
+ nameTxt = new SuggestBox(new ProjectNameSuggestOracle(), nameBox);
+ nameBox.setVisibleLength(50);
+ nameBox.setText(Util.C.defaultProjectName());
+ nameBox.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
+ nameBox.addFocusHandler(new FocusHandler() {
@Override
public void onFocus(FocusEvent event) {
- if (Util.C.defaultProjectName().equals(box.getText())) {
- box.setText("");
- box.removeStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
+ if (Util.C.defaultProjectName().equals(nameBox.getText())) {
+ nameBox.setText("");
+ nameBox.removeStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
}
}
});
- box.addBlurHandler(new BlurHandler() {
+ nameBox.addBlurHandler(new BlurHandler() {
@Override
public void onBlur(BlurEvent event) {
- if ("".equals(box.getText())) {
- box.setText(Util.C.defaultProjectName());
- box.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
+ if ("".equals(nameBox.getText())) {
+ nameBox.setText(Util.C.defaultProjectName());
+ nameBox.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
}
}
});
- box.addKeyPressHandler(new KeyPressHandler() {
+ nameBox.addKeyPressHandler(new KeyPressHandler() {
@Override
public void onKeyPress(KeyPressEvent event) {
submitOnSelection = false;
@@ -108,7 +111,37 @@
}
}
});
- fp.add(nameTxt);
+
+ filterTxt = new NpTextBox();
+ filterTxt.setVisibleLength(50);
+ filterTxt.setText(Util.C.defaultFilter());
+ filterTxt.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
+ filterTxt.addFocusHandler(new FocusHandler() {
+ @Override
+ public void onFocus(FocusEvent event) {
+ if (Util.C.defaultFilter().equals(filterTxt.getText())) {
+ filterTxt.setText("");
+ filterTxt.removeStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
+ }
+ }
+ });
+ filterTxt.addBlurHandler(new BlurHandler() {
+ @Override
+ public void onBlur(BlurEvent event) {
+ if ("".equals(filterTxt.getText())) {
+ filterTxt.setText(Util.C.defaultFilter());
+ filterTxt.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
+ }
+ }
+ });
+ filterTxt.addKeyPressHandler(new KeyPressHandler() {
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ if (event.getCharCode() == KeyCodes.KEY_ENTER) {
+ doAddNew();
+ }
+ }
+ });
addNew = new Button(Util.C.buttonWatchProject());
addNew.addClickHandler(new ClickHandler() {
@@ -117,24 +150,40 @@
doAddNew();
}
});
+
+ final Grid grid = new Grid(2, 2);
+ grid.setStyleName(Gerrit.RESOURCES.css().infoBlock());
+ grid.setText(0, 0, Util.C.watchedProjectName());
+ grid.setWidget(0, 1, nameTxt);
+
+ grid.setText(1, 0, Util.C.watchedProjectFilter());
+ grid.setWidget(1, 1, filterTxt);
+
+ final CellFormatter fmt = grid.getCellFormatter();
+ fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().topmost());
+ fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().topmost());
+ fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().header());
+ fmt.addStyleName(1, 0, Gerrit.RESOURCES.css().header());
+ fmt.addStyleName(1, 0, Gerrit.RESOURCES.css().bottomheader());
+
+ final FlowPanel fp = new FlowPanel();
+ fp.setStyleName(Gerrit.RESOURCES.css().addWatchPanel());
+ fp.add(grid);
fp.add(addNew);
add(fp);
}
watches = new WatchTable();
add(watches);
- {
- final FlowPanel fp = new FlowPanel();
- delSel = new Button(Util.C.buttonDeleteSshKey());
- delSel.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- watches.deleteChecked();
- }
- });
- fp.add(delSel);
- add(fp);
- }
+
+ delSel = new Button(Util.C.buttonDeleteSshKey());
+ delSel.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(final ClickEvent event) {
+ watches.deleteChecked();
+ }
+ });
+ add(delSel);
}
void doAddNew() {
@@ -144,11 +193,23 @@
return;
}
+ String filter = filterTxt.getText();
+ if (filter == null || filter.isEmpty()
+ || filter.equals(Util.C.defaultFilter())) {
+ filter = null;
+ }
+
addNew.setEnabled(false);
- Util.ACCOUNT_SVC.addProjectWatch(projectName,
+ nameBox.setEnabled(false);
+ filterTxt.setEnabled(false);
+
+ Util.ACCOUNT_SVC.addProjectWatch(projectName, filter,
new GerritCallback<AccountProjectWatchInfo>() {
public void onSuccess(final AccountProjectWatchInfo result) {
addNew.setEnabled(true);
+ nameBox.setEnabled(true);
+ filterTxt.setEnabled(true);
+
nameTxt.setText("");
watches.insertWatch(result);
}
@@ -156,6 +217,9 @@
@Override
public void onFailure(final Throwable caught) {
addNew.setEnabled(true);
+ nameBox.setEnabled(true);
+ filterTxt.setEnabled(true);
+
super.onFailure(caught);
}
});
@@ -177,8 +241,7 @@
WatchTable() {
table.setWidth("");
table.insertRow(1);
- table.setText(0, 2, com.google.gerrit.client.changes.Util.C
- .changeTableColumnProject());
+ table.setText(0, 2, Util.C.watchedProjectName());
table.setText(0, 3, Util.C.watchedProjectColumnEmailNotifications());
final FlexCellFormatter fmt = table.getFlexCellFormatter();
@@ -253,8 +316,16 @@
}
void populate(final int row, final AccountProjectWatchInfo k) {
+ final FlowPanel fp = new FlowPanel();
+ fp.add(new ProjectLink(k.getProject().getNameKey(), Status.NEW));
+ if (k.getWatch().getFilter() != null) {
+ Label filter = new Label(k.getWatch().getFilter());
+ filter.setStyleName(Gerrit.RESOURCES.css().watchedProjectFilter());
+ fp.add(filter);
+ }
+
table.setWidget(row, 1, new CheckBox());
- table.setWidget(row, 2, new ProjectLink(k.getProject().getNameKey(), Status.NEW));
+ table.setWidget(row, 2, fp);
{
final CheckBox notifyNewChanges = new CheckBox();
notifyNewChanges.addClickHandler(new ClickHandler() {
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 2b2fbcc..d44aa78 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
@@ -1029,6 +1029,15 @@
font-weight: bold;
}
+.addWatchPanel {
+ margin-top: 10px;
+ padding: 5px 5px 5px 5px;
+}
+.watchedProjectFilter {
+ margin-left: 1em;
+ color: grey;
+}
+
.addSshKeyPanel {
margin-top: 10px;
background-color: trimColor;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java
index c01d65e..332e262 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java
@@ -89,7 +89,7 @@
private final ChangeControl.Factory changeControlFactory;
private final AccountInfoCacheFactory.Factory accountInfoCacheFactory;
- private final Provider<ChangeQueryBuilder> queryBuilder;
+ private final ChangeQueryBuilder.Factory queryBuilder;
private final Provider<ChangeQueryRewriter> queryRewriter;
@Inject
@@ -97,7 +97,7 @@
final Provider<CurrentUser> currentUser,
final ChangeControl.Factory changeControlFactory,
final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
- final Provider<ChangeQueryBuilder> queryBuilder,
+ final ChangeQueryBuilder.Factory queryBuilder,
final Provider<ChangeQueryRewriter> queryRewriter) {
super(schema, currentUser);
this.currentUser = currentUser;
@@ -144,10 +144,8 @@
final int limit, final String key, final Comparator<Change> cmp)
throws OrmException, InvalidQueryException {
try {
- final ChangeQueryBuilder builder = queryBuilder.get();
- final Predicate<ChangeData> visibleToMe =
- builder.visibleto(currentUser.get());
-
+ final ChangeQueryBuilder builder = queryBuilder.create(currentUser.get());
+ final Predicate<ChangeData> visibleToMe = builder.is_visible();
Predicate<ChangeData> q = builder.parse(query);
q = Predicate.and(q, //
cmp == QUERY_PREV //
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
index aa2e95b..dd1866c 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
@@ -17,6 +17,7 @@
import com.google.gerrit.common.data.AccountProjectWatchInfo;
import com.google.gerrit.common.data.AccountService;
import com.google.gerrit.common.data.AgreementInfo;
+import com.google.gerrit.common.errors.InvalidQueryException;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
import com.google.gerrit.reviewdb.Account;
@@ -29,8 +30,11 @@
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult;
+import com.google.gwtorm.client.OrmDuplicateKeyException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -47,18 +51,21 @@
private final AccountCache accountCache;
private final ProjectControl.Factory projectControlFactory;
private final AgreementInfoFactory.Factory agreementInfoFactory;
+ private final ChangeQueryBuilder.Factory queryBuilder;
@Inject
AccountServiceImpl(final Provider<ReviewDb> schema,
final Provider<IdentifiedUser> identifiedUser,
final AccountCache accountCache,
final ProjectControl.Factory projectControlFactory,
- final AgreementInfoFactory.Factory agreementInfoFactory) {
+ final AgreementInfoFactory.Factory agreementInfoFactory,
+ final ChangeQueryBuilder.Factory queryBuilder) {
super(schema, identifiedUser);
this.currentUser = identifiedUser;
this.accountCache = accountCache;
this.projectControlFactory = projectControlFactory;
this.agreementInfoFactory = agreementInfoFactory;
+ this.queryBuilder = queryBuilder;
}
public void myAccount(final AsyncCallback<Account> callback) {
@@ -137,19 +144,31 @@
});
}
- public void addProjectWatch(final String projectName,
+ public void addProjectWatch(final String projectName, final String filter,
final AsyncCallback<AccountProjectWatchInfo> callback) {
run(callback, new Action<AccountProjectWatchInfo>() {
public AccountProjectWatchInfo run(ReviewDb db) throws OrmException,
- NoSuchProjectException {
+ NoSuchProjectException, InvalidQueryException {
final Project.NameKey nameKey = new Project.NameKey(projectName);
final ProjectControl ctl = projectControlFactory.validateFor(nameKey);
- final AccountProjectWatch watch =
- new AccountProjectWatch(
- new AccountProjectWatch.Key(((IdentifiedUser) ctl
- .getCurrentUser()).getAccountId(), nameKey));
- db.accountProjectWatches().insert(Collections.singleton(watch));
+ if (filter != null) {
+ try {
+ queryBuilder.create(currentUser.get()).parse(filter);
+ } catch (QueryParseException badFilter) {
+ throw new InvalidQueryException(badFilter.getMessage(), filter);
+ }
+ }
+
+ AccountProjectWatch watch =
+ new AccountProjectWatch(new AccountProjectWatch.Key(
+ ((IdentifiedUser) ctl.getCurrentUser()).getAccountId(),
+ nameKey, filter));
+ try {
+ db.accountProjectWatches().insert(Collections.singleton(watch));
+ } catch (OrmDuplicateKeyException alreadyHave) {
+ watch = db.accountProjectWatches().get(watch.getKey());
+ }
return new AccountProjectWatchInfo(watch, ctl.getProject());
}
});
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChange.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChange.java
index 5f0851e..4a4d9d1 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChange.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChange.java
@@ -127,7 +127,6 @@
// Email the reviewers
final AbandonedSender cm = abandonedSenderFactory.create(change);
cm.setFrom(currentUser.getAccountId());
- cm.setReviewDb(db);
cm.setChangeMessage(cmsg);
cm.send();
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/SubmitAction.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/SubmitAction.java
index 8e64469..4ab9072 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/SubmitAction.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/SubmitAction.java
@@ -23,6 +23,7 @@
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.MergeQueue;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.CanSubmitResult;
@@ -44,8 +45,8 @@
private final FunctionState.Factory functionState;
private final IdentifiedUser user;
private final ChangeDetailFactory.Factory changeDetailFactory;
- @Inject
- private ChangeControl.Factory changeControlFactory;
+ private final ChangeControl.Factory changeControlFactory;
+ private final MergeOp.Factory opFactory;
private final PatchSet.Id patchSetId;
@@ -53,13 +54,17 @@
SubmitAction(final ReviewDb db, final MergeQueue mq, final ApprovalTypes at,
final FunctionState.Factory fs, final IdentifiedUser user,
final ChangeDetailFactory.Factory changeDetailFactory,
+ final ChangeControl.Factory changeControlFactory,
+ final MergeOp.Factory opFactory,
@Assisted final PatchSet.Id patchSetId) {
this.db = db;
this.merger = mq;
this.approvalTypes = at;
this.functionState = fs;
this.user = user;
+ this.changeControlFactory = changeControlFactory;
this.changeDetailFactory = changeDetailFactory;
+ this.opFactory = opFactory;
this.patchSetId = patchSetId;
}
@@ -76,7 +81,7 @@
CanSubmitResult err =
changeControl.canSubmit(patchSetId, db, approvalTypes, functionState);
if (err == CanSubmitResult.OK) {
- ChangeUtil.submit(patchSetId, user, db, merger);
+ ChangeUtil.submit(opFactory, patchSetId, user, db, merger);
return changeDetailFactory.create(changeId).call();
} else {
throw new IllegalStateException(err.getMessage());
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/AddReviewer.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/AddReviewer.java
index d04f4ef..ae36388 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/AddReviewer.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/AddReviewer.java
@@ -130,7 +130,6 @@
final AddReviewerSender cm;
cm = addReviewerSenderFactory.create(control.getChange());
cm.setFrom(currentUser.getAccountId());
- cm.setReviewDb(db);
cm.addReviewers(added);
cm.send();
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatch.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatch.java
index 5d1565b..52bef2b 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatch.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatch.java
@@ -16,9 +16,12 @@
import com.google.gwtorm.client.Column;
import com.google.gwtorm.client.CompoundKey;
+import com.google.gwtorm.client.StringKey;
/** An {@link Account} interested in a {@link Project}. */
public final class AccountProjectWatch {
+ public static final String FILTER_ALL = "*";
+
public static class Key extends CompoundKey<Account.Id> {
private static final long serialVersionUID = 1L;
@@ -28,14 +31,19 @@
@Column(id = 2)
protected Project.NameKey projectName;
+ @Column(id = 3)
+ protected Filter filter;
+
protected Key() {
accountId = new Account.Id();
projectName = new Project.NameKey();
+ filter = new Filter();
}
- public Key(final Account.Id a, final Project.NameKey g) {
+ public Key(Account.Id a, Project.NameKey g, String f) {
accountId = a;
projectName = g;
+ filter = new Filter(f);
}
@Override
@@ -45,7 +53,31 @@
@Override
public com.google.gwtorm.client.Key<?>[] members() {
- return new com.google.gwtorm.client.Key<?>[] {projectName};
+ return new com.google.gwtorm.client.Key<?>[] {projectName, filter};
+ }
+ }
+
+ public static class Filter extends StringKey<com.google.gwtorm.client.Key<?>> {
+ private static final long serialVersionUID = 1L;
+
+ @Column(id = 1)
+ protected String filter;
+
+ protected Filter() {
+ }
+
+ public Filter(String f) {
+ filter = f != null && !f.isEmpty() ? f : FILTER_ALL;
+ }
+
+ @Override
+ public String get() {
+ return filter;
+ }
+
+ @Override
+ protected void set(String newValue) {
+ filter = newValue;
}
}
@@ -83,6 +115,10 @@
return key.projectName;
}
+ public String getFilter() {
+ return FILTER_ALL.equals(key.filter.get()) ? null : key.filter.get();
+ }
+
public boolean isNotifyNewChanges() {
return notifyNewChanges;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java
index ff6f43d..1bd2066 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java
@@ -15,12 +15,13 @@
package com.google.gerrit.server;
import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
-import com.google.gerrit.reviewdb.Project.NameKey;
import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import java.util.Collection;
import java.util.Collections;
import java.util.Set;
@@ -43,7 +44,7 @@
}
@Override
- public Set<NameKey> getWatchedProjects() {
+ public Collection<AccountProjectWatch> getNotificationFilters() {
return Collections.emptySet();
}
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 7f562c9..36fc2ff 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
@@ -23,6 +23,7 @@
import com.google.gerrit.reviewdb.TrackingId;
import com.google.gerrit.server.config.TrackingFooter;
import com.google.gerrit.server.config.TrackingFooters;
+import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.MergeQueue;
import com.google.gwtorm.client.AtomicUpdate;
import com.google.gwtorm.client.OrmConcurrencyException;
@@ -135,8 +136,8 @@
db.trackingIds().delete(toDelete);
}
- public static void submit(PatchSet.Id patchSetId, IdentifiedUser user, ReviewDb db, MergeQueue merger)
- throws OrmException {
+ public static void submit(MergeOp.Factory opFactory, PatchSet.Id patchSetId,
+ IdentifiedUser user, ReviewDb db, MergeQueue merger) throws OrmException {
final Change.Id changeId = patchSetId.getParentKey();
final PatchSetApproval approval = createSubmitApproval(patchSetId, user, db);
@@ -154,7 +155,7 @@
});
if (change.getStatus() == Change.Status.SUBMITTED) {
- merger.merge(change.getDest());
+ merger.merge(opFactory, change.getDest());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
index 207d5cc..751d215 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
@@ -15,11 +15,12 @@
package com.google.gerrit.server;
import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
-import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.servlet.RequestScoped;
+import java.util.Collection;
import java.util.Set;
/**
@@ -60,8 +61,8 @@
/** Set of changes starred by this user. */
public abstract Set<Change.Id> getStarredChanges();
- /** Set of project that are watched by this user */
- public abstract Set<Project.NameKey> getWatchedProjects();
+ /** Filters selecting changes the user wants to monitor. */
+ public abstract Collection<AccountProjectWatch> getNotificationFilters();
/** Is the user a non-interactive user? */
public boolean isBatchUser() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
index a5c780e..78cbed3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
@@ -19,7 +19,6 @@
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
-import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.StarredChange;
import com.google.gerrit.server.account.AccountCache;
@@ -43,9 +42,11 @@
import java.net.MalformedURLException;
import java.net.SocketAddress;
import java.net.URL;
+import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
import java.util.TimeZone;
@@ -145,7 +146,7 @@
private Set<String> emailAddresses;
private Set<AccountGroup.Id> effectiveGroups;
private Set<Change.Id> starredChanges;
- private Set<Project.NameKey> watchedProjects;
+ private Collection<AccountProjectWatch> notificationFilters;
private IdentifiedUser(final AccessPath accessPath,
final AuthConfig authConfig, final Provider<String> canonicalUrl,
@@ -237,24 +238,22 @@
}
@Override
- public Set<Project.NameKey> getWatchedProjects() {
- if (watchedProjects == null) {
+ public Collection<AccountProjectWatch> getNotificationFilters() {
+ if (notificationFilters == null) {
if (dbProvider == null) {
throw new OutOfScopeException("Not in request scoped user");
}
- final Set<Project.NameKey> h = new HashSet<Project.NameKey>();
+ List<AccountProjectWatch> r;
try {
- for (AccountProjectWatch projectWatch : dbProvider.get()
- .accountProjectWatches().byAccount(getAccountId())) {
- h.add(projectWatch.getProjectNameKey());
- }
+ r = dbProvider.get().accountProjectWatches() //
+ .byAccount(getAccountId()).toList();
} catch (OrmException e) {
- log.warn("Cannot query project watches of a user", e);
+ log.warn("Cannot query notification filters of a user", e);
+ r = Collections.emptyList();
}
- watchedProjects = Collections.unmodifiableSet(h);
+ notificationFilters = Collections.unmodifiableList(r);
}
-
- return watchedProjects;
+ return notificationFilters;
}
public PersonIdent newRefLogIdent() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java
index d44643b..e422b19 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server;
import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Project.NameKey;
import com.google.gerrit.server.config.AuthConfig;
@@ -22,6 +23,7 @@
import com.google.inject.assistedinject.Assisted;
import java.net.SocketAddress;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -59,7 +61,7 @@
}
@Override
- public Set<NameKey> getWatchedProjects() {
+ public Collection<AccountProjectWatch> getNotificationFilters() {
return Collections.emptySet();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ReplicationUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/ReplicationUser.java
index b21df8a..3293324 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ReplicationUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ReplicationUser.java
@@ -15,12 +15,14 @@
package com.google.gerrit.server;
import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Project.NameKey;
import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -73,7 +75,7 @@
}
@Override
- public Set<NameKey> getWatchedProjects() {
+ public Collection<AccountProjectWatch> getNotificationFilters() {
return Collections.emptySet();
}
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 697f045..38a9272 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
@@ -132,7 +132,6 @@
factory(PushAllProjectsOp.Factory.class);
bind(MergeQueue.class).to(ChangeMergeQueue.class).in(SINGLETON);
- factory(MergeOp.Factory.class);
factory(ReloadSubmitQueueOp.Factory.class);
bind(FromAddressGenerator.class).toProvider(
@@ -142,12 +141,6 @@
bind(PatchSetInfoFactory.class);
bind(IdentifiedUser.GenericFactory.class).in(SINGLETON);
factory(FunctionState.Factory.class);
-
- factory(AbandonedSender.Factory.class);
- factory(CommentSender.Factory.class);
- factory(MergedSender.Factory.class);
- factory(MergeFailSender.Factory.class);
- factory(RegisterNewEmailSender.Factory.class);
factory(ReplicationUser.Factory.class);
install(new LifecycleModule() {
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 1125b05..5a75995 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
@@ -21,9 +21,15 @@
import com.google.gerrit.server.RequestCleanup;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.account.GroupControl;
+import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.ReceiveCommits;
+import com.google.gerrit.server.mail.AbandonedSender;
import com.google.gerrit.server.mail.AddReviewerSender;
+import com.google.gerrit.server.mail.CommentSender;
import com.google.gerrit.server.mail.CreateChangeSender;
+import com.google.gerrit.server.mail.MergeFailSender;
+import com.google.gerrit.server.mail.MergedSender;
+import com.google.gerrit.server.mail.RegisterNewEmailSender;
import com.google.gerrit.server.mail.ReplacePatchSetSender;
import com.google.gerrit.server.patch.PublishComments;
import com.google.gerrit.server.project.ChangeControl;
@@ -41,14 +47,15 @@
RequestScoped.class);
bind(IdentifiedUser.RequestFactory.class).in(SINGLETON);
bind(AccountResolver.class);
- bind(ChangeQueryBuilder.class);
bind(ChangeQueryRewriter.class);
bind(ChangeControl.Factory.class).in(SINGLETON);
bind(GroupControl.Factory.class).in(SINGLETON);
bind(ProjectControl.Factory.class).in(SINGLETON);
+ factory(ChangeQueryBuilder.Factory.class);
factory(ReceiveCommits.Factory.class);
+ factory(MergeOp.Factory.class);
// Not really per-request, but dammit, I don't know where else to
// easily park this stuff.
@@ -57,5 +64,10 @@
factory(CreateChangeSender.Factory.class);
factory(PublishComments.Factory.class);
factory(ReplacePatchSetSender.Factory.class);
+ factory(AbandonedSender.Factory.class);
+ factory(CommentSender.Factory.class);
+ factory(MergedSender.Factory.class);
+ factory(MergeFailSender.Factory.class);
+ factory(RegisterNewEmailSender.Factory.class);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeMergeQueue.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeMergeQueue.java
index d6c8f4a..a7a969f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeMergeQueue.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeMergeQueue.java
@@ -19,12 +19,30 @@
import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.RemotePeer;
+import com.google.gerrit.server.RequestCleanup;
+import com.google.gerrit.server.config.GerritRequestModule;
+import com.google.gerrit.server.ssh.SshInfo;
+import com.google.inject.AbstractModule;
import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.OutOfScopeException;
+import com.google.inject.Provider;
+import com.google.inject.Scope;
+import com.google.inject.servlet.RequestScoped;
+
+import com.jcraft.jsch.HostKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.net.SocketAddress;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -38,18 +56,47 @@
new HashMap<Branch.NameKey, RecheckJob>();
private final WorkQueue workQueue;
- private final MergeOp.Factory opFactory;
+ private final Provider<MergeOp.Factory> bgFactory;
@Inject
- ChangeMergeQueue(final WorkQueue wq, final MergeOp.Factory of) {
+ ChangeMergeQueue(final WorkQueue wq, Injector parent) {
workQueue = wq;
- opFactory = of;
+
+ Injector child = parent.createChildInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bindScope(RequestScoped.class, MyScope.REQUEST);
+ install(new GerritRequestModule());
+
+ bind(CurrentUser.class).to(IdentifiedUser.class);
+ bind(IdentifiedUser.class).toProvider(new Provider<IdentifiedUser>() {
+ @Override
+ public IdentifiedUser get() {
+ throw new OutOfScopeException("No user on merge thread");
+ }
+ });
+ bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
+ new Provider<SocketAddress>() {
+ @Override
+ public SocketAddress get() {
+ throw new OutOfScopeException("No remote peer on merge thread");
+ }
+ });
+ bind(SshInfo.class).toInstance(new SshInfo() {
+ @Override
+ public List<HostKey> getHostKeys() {
+ return Collections.emptyList();
+ }
+ });
+ }
+ });
+ bgFactory = child.getProvider(MergeOp.Factory.class);
}
@Override
- public void merge(final Branch.NameKey branch) {
+ public void merge(MergeOp.Factory mof, Branch.NameKey branch) {
if (start(branch)) {
- mergeImpl(branch);
+ mergeImpl(mof, branch);
}
}
@@ -127,7 +174,7 @@
e.needMerge = false;
}
- private void mergeImpl(final Branch.NameKey branch) {
+ private void mergeImpl(MergeOp.Factory opFactory, Branch.NameKey branch) {
try {
opFactory.create(branch).merge();
} catch (Throwable e) {
@@ -137,6 +184,26 @@
}
}
+ private void mergeImpl(Branch.NameKey branch) {
+ try {
+ MyScope ctx = new MyScope();
+ MyScope old = MyScope.set(ctx);
+ try {
+ try {
+ bgFactory.get().create(branch).merge();
+ } finally {
+ ctx.cleanup.run();
+ }
+ } finally {
+ MyScope.set(old);
+ }
+ } catch (Throwable e) {
+ log.error("Merge attempt for " + branch + " failed", e);
+ } finally {
+ finish(branch);
+ }
+ }
+
private synchronized void recheck(final RecheckJob e) {
final long remainingDelay = e.recheckAt - System.currentTimeMillis();
if (MILLISECONDS.convert(10, SECONDS) < remainingDelay) {
@@ -194,4 +261,65 @@
return "recheck " + project.get() + " " + dest.getShortName();
}
}
+
+ private static class MyScope {
+ private static final ThreadLocal<MyScope> current =
+ new ThreadLocal<MyScope>();
+
+ private static MyScope getContext() {
+ final MyScope ctx = current.get();
+ if (ctx == null) {
+ throw new OutOfScopeException("Not in command/request");
+ }
+ return ctx;
+ }
+
+ static MyScope set(MyScope ctx) {
+ MyScope old = current.get();
+ current.set(ctx);
+ return old;
+ }
+
+ static final Scope REQUEST = new Scope() {
+ public <T> Provider<T> scope(final Key<T> key, final Provider<T> creator) {
+ return new Provider<T>() {
+ public T get() {
+ return getContext().get(key, creator);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s[%s]", creator, REQUEST);
+ }
+ };
+ }
+
+ @Override
+ public String toString() {
+ return "MergeQueue.REQUEST";
+ }
+ };
+
+ private static final Key<RequestCleanup> RC_KEY =
+ Key.get(RequestCleanup.class);
+
+ private final RequestCleanup cleanup;
+ private final Map<Key<?>, Object> map;
+
+ MyScope() {
+ cleanup = new RequestCleanup();
+ map = new HashMap<Key<?>, Object>();
+ map.put(RC_KEY, cleanup);
+ }
+
+ synchronized <T> T get(Key<T> key, Provider<T> creator) {
+ @SuppressWarnings("unchecked")
+ T t = (T) map.get(key);
+ if (t == null) {
+ t = creator.get();
+ map.put(key, t);
+ }
+ return t;
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index 5280e0c..66e8d61 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -1179,7 +1179,6 @@
if (submitter != null) {
cm.setFrom(submitter.getAccountId());
}
- cm.setReviewDb(schema);
cm.setPatchSet(schema.patchSets().get(c.currentPatchSetId()));
cm.send();
} catch (OrmException e) {
@@ -1241,7 +1240,6 @@
cm.setFrom(submitter.getAccountId());
}
}
- cm.setReviewDb(schema);
cm.setPatchSet(schema.patchSets().get(c.currentPatchSetId()));
cm.setChangeMessage(msg);
cm.send();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeQueue.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeQueue.java
index abb0ab8..39017eb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeQueue.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeQueue.java
@@ -19,7 +19,7 @@
import java.util.concurrent.TimeUnit;
public interface MergeQueue {
- void merge(Branch.NameKey branch);
+ void merge(MergeOp.Factory mof, Branch.NameKey branch);
void schedule(Branch.NameKey branch);
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 be357cf..fbcdc09 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
@@ -931,7 +931,6 @@
cm = createChangeSenderFactory.create(change);
cm.setFrom(me);
cm.setPatchSet(ps, info);
- cm.setReviewDb(db);
cm.addReviewers(reviewers);
cm.addExtraCC(cc);
cm.send();
@@ -1234,7 +1233,6 @@
cm.setFrom(me);
cm.setPatchSet(ps, result.info);
cm.setChangeMessage(result.msg);
- cm.setReviewDb(db);
cm.addReviewers(reviewers);
cm.addExtraCC(cc);
cm.addReviewers(oldReviewers);
@@ -1583,7 +1581,6 @@
try {
final MergedSender cm = mergedSenderFactory.create(result.change);
cm.setFrom(currentUser.getAccountId());
- cm.setReviewDb(db);
cm.setPatchSet(result.patchSet, result.info);
cm.setDest(new Branch.NameKey(project.getNameKey(),
result.mergedIntoRef));
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 bba69c8..99f8028 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
@@ -25,8 +25,8 @@
}
@Inject
- public AbandonedSender(@Assisted Change c) {
- super(c, "abandon");
+ public AbandonedSender(EmailArguments ea, @Assisted Change c) {
+ super(ea, c, "abandon");
}
@Override
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 c9a6d98..be62ba0 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
@@ -15,6 +15,7 @@
package com.google.gerrit.server.mail;
import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.server.ssh.SshInfo;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -25,8 +26,9 @@
}
@Inject
- public AddReviewerSender(@Assisted Change c) {
- super(c);
+ public AddReviewerSender(EmailArguments ea, SshInfo sshInfo,
+ @Assisted Change c) {
+ super(ea, sshInfo, c);
}
@Override
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 a010ac1..358c1cc 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
@@ -38,8 +38,8 @@
private List<PatchLineComment> inlineComments = Collections.emptyList();
@Inject
- public CommentSender(@Assisted Change c) {
- super(c, "comment");
+ public CommentSender(EmailArguments ea, @Assisted Change c) {
+ super(ea, c, "comment");
}
public void setPatchLineComments(final List<PatchLineComment> plc) {
@@ -124,7 +124,7 @@
private Repository getRepository() {
try {
- return server.openRepository(projectName);
+ return args.server.openRepository(projectName);
} catch (RepositoryNotFoundException e) {
return null;
}
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 e3b65b8..ea57cde 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
@@ -19,7 +19,7 @@
import com.google.gerrit.reviewdb.AccountGroupMember;
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
-import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.ssh.SshInfo;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -34,8 +34,9 @@
}
@Inject
- public CreateChangeSender(@Assisted Change c) {
- super(c);
+ public CreateChangeSender(EmailArguments ea, SshInfo sshInfo,
+ @Assisted Change c) {
+ super(ea, sshInfo, c);
}
@Override
@@ -46,38 +47,32 @@
}
private void bccWatchers() {
- if (db != null) {
- try {
- // BCC anyone else who has interest in this project's changes
- //
- final ProjectState ps = getProjectState();
- if (ps != null) {
- // Try to mark interested owners with a TO and not a BCC line.
- //
- final Set<Account.Id> owners = new HashSet<Account.Id>();
- for (AccountGroup.Id g : getProjectOwners()) {
- for (AccountGroupMember m : db.accountGroupMembers().byGroup(g)) {
- owners.add(m.getAccountId());
- }
- }
+ try {
+ // Try to mark interested owners with a TO and not a BCC line.
+ //
+ final Set<Account.Id> owners = new HashSet<Account.Id>();
+ for (AccountGroup.Id g : getProjectOwners()) {
+ for (AccountGroupMember m : args.db.get().accountGroupMembers()
+ .byGroup(g)) {
+ owners.add(m.getAccountId());
+ }
+ }
- // BCC anyone who has interest in this project's changes
- //
- for (final AccountProjectWatch w : getProjectWatches()) {
- if (w.isNotifyNewChanges()) {
- if (owners.contains(w.getAccountId())) {
- add(RecipientType.TO, w.getAccountId());
- } else {
- add(RecipientType.BCC, w.getAccountId());
- }
- }
+ // BCC anyone who has interest in this project's changes
+ //
+ for (final AccountProjectWatch w : getWatches()) {
+ if (w.isNotifyNewChanges()) {
+ if (owners.contains(w.getAccountId())) {
+ add(RecipientType.TO, w.getAccountId());
+ } else {
+ add(RecipientType.BCC, w.getAccountId());
}
}
- } catch (OrmException err) {
- // Just don't CC everyone. Better to send a partial message to those
- // we already have queued up then to fail deliver entirely to people
- // who have a lower interest in the change.
}
+ } catch (OrmException err) {
+ // Just don't CC everyone. Better to send a partial message to those
+ // we already have queued up then to fail deliver entirely to people
+ // who have a lower interest in the change.
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
new file mode 100644
index 0000000..8a7ffd6
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
@@ -0,0 +1,75 @@
+// Copyright (C) 2010 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.Project;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.IdentifiedUser.GenericFactory;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.config.WildProjectName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder;
+import com.google.gerrit.server.query.change.ChangeQueryRewriter;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import javax.annotation.Nullable;
+
+class EmailArguments {
+ final GitRepositoryManager server;
+ final ProjectCache projectCache;
+ final AccountCache accountCache;
+ final PatchListCache patchListCache;
+ final FromAddressGenerator fromAddressGenerator;
+ final EmailSender emailSender;
+ final PatchSetInfoFactory patchSetInfoFactory;
+ final IdentifiedUser.GenericFactory identifiedUserFactory;
+ final Provider<String> urlProvider;
+ final Project.NameKey wildProject;
+
+ final ChangeQueryBuilder.Factory queryBuilder;
+ final Provider<ChangeQueryRewriter> queryRewriter;
+ final Provider<ReviewDb> db;
+
+ @Inject
+ EmailArguments(GitRepositoryManager server, ProjectCache projectCache,
+ AccountCache accountCache, PatchListCache patchListCache,
+ FromAddressGenerator fromAddressGenerator, EmailSender emailSender,
+ PatchSetInfoFactory patchSetInfoFactory,
+ GenericFactory identifiedUserFactory,
+ @CanonicalWebUrl @Nullable Provider<String> urlProvider,
+ @WildProjectName Project.NameKey wildProject,
+ ChangeQueryBuilder.Factory queryBuilder,
+ Provider<ChangeQueryRewriter> queryRewriter, Provider<ReviewDb> db) {
+ this.server = server;
+ this.projectCache = projectCache;
+ this.accountCache = accountCache;
+ this.patchListCache = patchListCache;
+ this.fromAddressGenerator = fromAddressGenerator;
+ this.emailSender = emailSender;
+ this.patchSetInfoFactory = patchSetInfoFactory;
+ this.identifiedUserFactory = identifiedUserFactory;
+ this.urlProvider = urlProvider;
+ this.wildProject = wildProject;
+ this.queryBuilder = queryBuilder;
+ this.queryRewriter = queryRewriter;
+ this.db = db;
+ }
+}
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 11a541b..1395d78 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
@@ -25,8 +25,8 @@
}
@Inject
- public MergeFailSender(@Assisted Change c) {
- super(c, "comment");
+ public MergeFailSender(EmailArguments ea, @Assisted Change c) {
+ super(ea, c, "comment");
}
@Override
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 c467bf8..ee31c7e 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
@@ -37,15 +37,14 @@
public MergedSender create(Change change);
}
+ private final ApprovalTypes approvalTypes;
private Branch.NameKey dest;
@Inject
- private ApprovalTypes approvalTypes;
-
- @Inject
- public MergedSender(@Assisted Change c) {
- super(c, "merged");
+ public MergedSender(EmailArguments ea, ApprovalTypes at, @Assisted Change c) {
+ super(ea, c, "merged");
dest = c.getDest();
+ approvalTypes = at;
}
public void setDest(final Branch.NameKey key) {
@@ -78,7 +77,7 @@
}
private void formatApprovals() {
- if (db != null && patchSet != null) {
+ if (patchSet != null) {
try {
final Map<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>> pos =
new HashMap<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>>();
@@ -86,8 +85,8 @@
final Map<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>> neg =
new HashMap<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>>();
- for (PatchSetApproval ca : db.patchSetApprovals().byPatchSet(
- patchSet.getId())) {
+ for (PatchSetApproval ca : args.db.get().patchSetApprovals()
+ .byPatchSet(patchSet.getId())) {
if (ca.getValue() > 0) {
insert(pos, ca);
} else if (ca.getValue() < 0) {
@@ -157,23 +156,18 @@
}
private void bccWatchesNotifySubmittedChanges() {
- if (db != null) {
- try {
- // BCC anyone else who has interest in this project's changes
- //
- final ProjectState ps = getProjectState();
- if (ps != null) {
- for (final AccountProjectWatch w : getProjectWatches()) {
- if (w.isNotifySubmittedChanges()) {
- add(RecipientType.BCC, w.getAccountId());
- }
- }
+ try {
+ // BCC anyone else who has interest in this project's changes
+ //
+ for (final AccountProjectWatch w : getWatches()) {
+ if (w.isNotifySubmittedChanges()) {
+ add(RecipientType.BCC, w.getAccountId());
}
- } catch (OrmException err) {
- // Just don't CC everyone. Better to send a partial message to those
- // we already have queued up then to fail deliver entirely to people
- // who have a lower interest in the change.
}
+ } catch (OrmException err) {
+ // Just don't CC everyone. Better to send a partial message to those
+ // we already have queued up then to fail deliver entirely to people
+ // who have a lower interest in the change.
}
}
}
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 6c35f0e..459fc4d 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
@@ -17,7 +17,6 @@
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.server.ssh.SshInfo;
-import com.google.inject.Inject;
import com.jcraft.jsch.HostKey;
@@ -29,14 +28,13 @@
/** Sends an email alerting a user to a new change for them to review. */
public abstract class NewChangeSender extends OutgoingEmail {
- @Inject
- private SshInfo sshInfo;
-
+ private final SshInfo sshInfo;
private final Set<Account.Id> reviewers = new HashSet<Account.Id>();
private final Set<Account.Id> extraCC = new HashSet<Account.Id>();
- protected NewChangeSender(Change c) {
- super(c, "newchange");
+ protected NewChangeSender(EmailArguments ea, SshInfo sshInfo, Change c) {
+ super(ea, c, "newchange");
+ this.sshInfo = sshInfo;
}
public void addReviewers(final Collection<Account.Id> cc) {
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 6d042ea..3e05c5f 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
@@ -22,27 +22,20 @@
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.PatchSetInfo;
-import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.StarredChange;
import com.google.gerrit.reviewdb.UserIdentity;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.config.CanonicalWebUrl;
-import com.google.gerrit.server.config.WildProjectName;
-import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.mail.EmailHeader.AddressList;
import com.google.gerrit.server.patch.PatchList;
-import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListEntry;
-import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
-import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gwtorm.client.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
import org.eclipse.jgit.util.SystemReader;
import org.slf4j.Logger;
@@ -63,8 +56,6 @@
import java.util.Set;
import java.util.TreeSet;
-import javax.annotation.Nullable;
-
/** Sends an email to one or more interested parties. */
public abstract class OutgoingEmail {
private static final Logger log = LoggerFactory.getLogger(OutgoingEmail.class);
@@ -83,55 +74,24 @@
private StringBuilder body;
private boolean inFooter;
+ protected final EmailArguments args;
protected Account.Id fromId;
protected PatchSet patchSet;
protected PatchSetInfo patchSetInfo;
protected ChangeMessage changeMessage;
- protected ReviewDb db;
-
- @Inject
- protected GitRepositoryManager server;
-
- @Inject
- private ProjectCache projectCache;
-
- @Inject
- private AccountCache accountCache;
-
- @Inject
- private PatchListCache patchListCache;
-
- @Inject
- private FromAddressGenerator fromAddressGenerator;
-
- @Inject
- private EmailSender emailSender;
-
- @Inject
- private PatchSetInfoFactory patchSetInfoFactory;
-
- @Inject
- private IdentifiedUser.GenericFactory identifiedUserFactory;
-
- @Inject
- @CanonicalWebUrl
- @Nullable
- private Provider<String> urlProvider;
-
- @Inject
- @WildProjectName
- private Project.NameKey wildProject;
private ProjectState projectState;
+ private ChangeData changeData;
- protected OutgoingEmail(final Change c, final String mc) {
+ protected OutgoingEmail(EmailArguments ea, final Change c, final String mc) {
+ args = ea;
change = c;
messageClass = mc;
headers = new LinkedHashMap<String, EmailHeader>();
}
- protected OutgoingEmail(final String mc) {
- this(null, mc);
+ protected OutgoingEmail(EmailArguments ea, final String mc) {
+ this(ea, null, mc);
}
public void setFrom(final Account.Id id) {
@@ -151,17 +111,13 @@
changeMessage = cm;
}
- public void setReviewDb(final ReviewDb d) {
- db = d;
- }
-
/**
* Format and enqueue the message for delivery.
*
* @throws EmailException
*/
public void send() throws EmailException {
- if (!emailSender.isEnabled()) {
+ if (!args.emailSender.isEnabled()) {
// Server has explicitly disabled email sending.
//
return;
@@ -171,7 +127,7 @@
format();
if (shouldSendMessage()) {
if (fromId != null) {
- final Account fromUser = accountCache.get(fromId).getAccount();
+ final Account fromUser = args.accountCache.get(fromId).getAccount();
if (fromUser.getGeneralPreferences().isCopySelfOnEmails()) {
// If we are impersonating a user, make sure they receive a CC of
@@ -226,24 +182,22 @@
appendText("Gerrit-Branch: " + change.getDest().getShortName() + "\n");
appendText("Gerrit-Owner: " + getNameEmailFor(change.getOwner()) + "\n");
- if (db != null) {
- try {
- HashSet<Account.Id> reviewers = new HashSet<Account.Id>();
- for (PatchSetApproval p : db.patchSetApprovals().byChange(
- change.getId())) {
- reviewers.add(p.getAccountId());
- }
-
- TreeSet<String> names = new TreeSet<String>();
- for (Account.Id who : reviewers) {
- names.add(getNameEmailFor(who));
- }
-
- for (String name : names) {
- appendText("Gerrit-Reviewer: " + name + "\n");
- }
- } catch (OrmException e) {
+ try {
+ HashSet<Account.Id> reviewers = new HashSet<Account.Id>();
+ for (PatchSetApproval p : args.db.get().patchSetApprovals().byChange(
+ change.getId())) {
+ reviewers.add(p.getAccountId());
}
+
+ TreeSet<String> names = new TreeSet<String>();
+ for (Account.Id who : reviewers) {
+ names.add(getNameEmailFor(who));
+ }
+
+ for (String name : names) {
+ appendText("Gerrit-Reviewer: " + name + "\n");
+ }
+ } catch (OrmException e) {
}
}
@@ -259,7 +213,7 @@
setHeader("Message-ID", rndid.toString());
}
- emailSender.send(smtpFromAddress, smtpRcptTo, headers, body.toString());
+ args.emailSender.send(smtpFromAddress, smtpRcptTo, headers, body.toString());
}
}
@@ -268,16 +222,18 @@
/** Setup the message headers and envelope (TO, CC, BCC). */
protected void init() {
- if (change != null && projectCache != null) {
- projectState = projectCache.get(change.getProject());
+ if (change != null && args.projectCache != null) {
+ changeData = new ChangeData(change);
+ projectState = args.projectCache.get(change.getProject());
projectName =
projectState != null ? projectState.getProject().getName() : null;
} else {
+ changeData = null;
projectState = null;
projectName = null;
}
- smtpFromAddress = fromAddressGenerator.from(fromId);
+ smtpFromAddress = args.fromAddressGenerator.from(fromId);
if (changeMessage != null && changeMessage.getWrittenOn() != null) {
setHeader("Date", new Date(changeMessage.getWrittenOn().getTime()));
} else {
@@ -313,8 +269,8 @@
body = new StringBuilder();
inFooter = false;
- if (fromId != null && fromAddressGenerator.isGenericAddress(fromId)) {
- final Account account = accountCache.get(fromId).getAccount();
+ if (fromId != null && args.fromAddressGenerator.isGenericAddress(fromId)) {
+ final Account account = args.accountCache.get(fromId).getAccount();
final String name = account.getFullName();
final String email = account.getPreferredEmail();
@@ -331,10 +287,10 @@
}
}
- if (change != null && db != null) {
+ if (change != null) {
if (patchSet == null) {
try {
- patchSet = db.patchSets().get(change.currentPatchSetId());
+ patchSet = args.db.get().patchSets().get(change.currentPatchSetId());
} catch (OrmException err) {
patchSet = null;
}
@@ -342,7 +298,7 @@
if (patchSet != null && patchSetInfo == null) {
try {
- patchSetInfo = patchSetInfoFactory.get(patchSet.getId());
+ patchSetInfo = args.patchSetInfoFactory.get(patchSet.getId());
} catch (PatchSetInfoNotAvailableException err) {
patchSetInfo = null;
}
@@ -439,7 +395,7 @@
}
protected String getGerritUrl() {
- return urlProvider.get();
+ return args.urlProvider.get();
}
protected String getChangeMessageThreadId() {
@@ -521,7 +477,7 @@
/** Get the patch list corresponding to this patch set. */
protected PatchList getPatchList() {
if (patchSet != null) {
- return patchListCache.get(change, patchSet);
+ return args.patchListCache.get(change, patchSet);
}
return null;
}
@@ -532,7 +488,7 @@
return "Anonymous Coward";
}
- final Account userAccount = accountCache.get(accountId).getAccount();
+ final Account userAccount = args.accountCache.get(accountId).getAccount();
String name = userAccount.getFullName();
if (name == null) {
name = userAccount.getPreferredEmail();
@@ -544,7 +500,7 @@
}
private String getNameEmailFor(Account.Id accountId) {
- AccountState who = accountCache.get(accountId);
+ AccountState who = args.accountCache.get(accountId);
String name = who.getAccount().getFullName();
String email = who.getAccount().getPreferredEmail();
@@ -594,7 +550,7 @@
protected Set<AccountGroup.Id> getProjectOwners() {
final ProjectState r;
- r = projectCache.get(change.getProject());
+ r = args.projectCache.get(change.getProject());
return r != null ? r.getOwners() : Collections.<AccountGroup.Id> emptySet();
}
@@ -625,62 +581,82 @@
/** BCC any user who has starred this change. */
protected void bccStarredBy() {
- if (db != null) {
- try {
- // BCC anyone who has starred this change.
- //
- for (StarredChange w : db.starredChanges().byChange(change.getId())) {
- add(RecipientType.BCC, w.getAccountId());
- }
- } catch (OrmException err) {
- // Just don't BCC everyone. Better to send a partial message to those
- // we already have queued up then to fail deliver entirely to people
- // who have a lower interest in the change.
+ try {
+ // BCC anyone who has starred this change.
+ //
+ for (StarredChange w : args.db.get().starredChanges().byChange(
+ change.getId())) {
+ add(RecipientType.BCC, w.getAccountId());
}
+ } catch (OrmException err) {
+ // Just don't BCC everyone. Better to send a partial message to those
+ // we already have queued up then to fail deliver entirely to people
+ // who have a lower interest in the change.
}
}
/** BCC any user who has set "notify all comments" on this project. */
protected void bccWatchesNotifyAllComments() {
- if (db != null) {
- try {
- // BCC anyone else who has interest in this project's changes
- //
- final ProjectState ps = getProjectState();
- if (ps != null) {
- for (final AccountProjectWatch w : getProjectWatches()) {
- if (w.isNotifyAllComments()) {
- add(RecipientType.BCC, w.getAccountId());
- }
- }
+ try {
+ // BCC anyone else who has interest in this project's changes
+ //
+ for (final AccountProjectWatch w : getWatches()) {
+ if (w.isNotifyAllComments()) {
+ add(RecipientType.BCC, w.getAccountId());
}
- } catch (OrmException err) {
- // Just don't CC everyone. Better to send a partial message to those
- // we already have queued up then to fail deliver entirely to people
- // who have a lower interest in the change.
}
+ } catch (OrmException err) {
+ // Just don't CC everyone. Better to send a partial message to those
+ // we already have queued up then to fail deliver entirely to people
+ // who have a lower interest in the change.
}
}
- /** Returns all watches that are relevant for this project */
- final protected Set<AccountProjectWatch> getProjectWatches() throws OrmException {
- final Set<AccountProjectWatch> projectWatches = new HashSet<AccountProjectWatch>();
- final Set<Account.Id> projectWatchers = new HashSet<Account.Id>();
- final ProjectState ps = getProjectState();
- if (ps != null) {
- for (final AccountProjectWatch w : db.accountProjectWatches().byProject(ps.getProject().getNameKey())) {
- projectWatches.add(w);
- projectWatchers.add(w.getAccountId());
- }
+ /** Returns all watches that are relevant */
+ protected final List<AccountProjectWatch> getWatches() throws OrmException {
+ if (changeData == null) {
+ return Collections.emptyList();
}
- for (final AccountProjectWatch w : db.accountProjectWatches().byProject(wildProject)) {
+
+ List<AccountProjectWatch> matching = new ArrayList<AccountProjectWatch>();
+ Set<Account.Id> projectWatchers = new HashSet<Account.Id>();
+
+ for (AccountProjectWatch w : args.db.get().accountProjectWatches()
+ .byProject(change.getProject())) {
+ projectWatchers.add(w.getAccountId());
+ add(matching, w);
+ }
+
+ for (AccountProjectWatch w : args.db.get().accountProjectWatches()
+ .byProject(args.wildProject)) {
if (!projectWatchers.contains(w.getAccountId())) {
- // the all projects watch settings are only relevant if the user did not configure
- // any specific rules for the concrete project
- projectWatches.add(w);
+ add(matching, w);
}
}
- return Collections.unmodifiableSet(projectWatches);
+
+ return Collections.unmodifiableList(matching);
+ }
+
+ @SuppressWarnings("unchecked")
+ private void add(List<AccountProjectWatch> matching, AccountProjectWatch w)
+ throws OrmException {
+ IdentifiedUser user =
+ args.identifiedUserFactory.create(args.db, w.getAccountId());
+ ChangeQueryBuilder qb = args.queryBuilder.create(user);
+ Predicate<ChangeData> p = qb.is_visible();
+ if (w.getFilter() != null) {
+ try {
+ p = Predicate.and(qb.parse(w.getFilter()), p);
+ p = args.queryRewriter.get().rewrite(p);
+ if (p.match(changeData)) {
+ matching.add(w);
+ }
+ } catch (QueryParseException e) {
+ // Ignore broken filter expressions.
+ }
+ } else if (p.match(changeData)) {
+ matching.add(w);
+ }
}
/** Any user who has published comments on this change. */
@@ -694,19 +670,17 @@
}
private void ccApprovals(final boolean includeZero) {
- if (db != null) {
- try {
- // CC anyone else who has posted an approval mark on this change
- //
- for (PatchSetApproval ap : db.patchSetApprovals().byChange(
- change.getId())) {
- if (!includeZero && ap.getValue() == 0) {
- continue;
- }
- add(RecipientType.CC, ap.getAccountId());
+ try {
+ // CC anyone else who has posted an approval mark on this change
+ //
+ for (PatchSetApproval ap : args.db.get().patchSetApprovals().byChange(
+ change.getId())) {
+ if (!includeZero && ap.getValue() == 0) {
+ continue;
}
- } catch (OrmException err) {
+ add(RecipientType.CC, ap.getAccountId());
}
+ } catch (OrmException err) {
}
}
@@ -721,14 +695,14 @@
private boolean isVisibleTo(final Account.Id to) {
return projectState == null
|| change == null
- || projectState.controlFor(identifiedUserFactory.create(to))
+ || projectState.controlFor(args.identifiedUserFactory.create(to))
.controlFor(change).isVisible();
}
/** Schedule delivery of this message to the given account. */
protected void add(final RecipientType rt, final Address addr) {
if (addr != null && addr.email != null && addr.email.length() > 0) {
- if (emailSender.canEmail(addr.email)) {
+ if (args.emailSender.canEmail(addr.email)) {
smtpRcptTo.add(addr);
switch (rt) {
case TO:
@@ -745,7 +719,7 @@
}
private Address toAddress(final Account.Id id) {
- final Account a = accountCache.get(id).getAccount();
+ final Account a = args.accountCache.get(id).getAccount();
final String e = a.getPreferredEmail();
if (e == null) {
return null;
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 d22cc59..0007efe 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
@@ -28,14 +28,14 @@
public RegisterNewEmailSender create(String address);
}
+ private final AuthConfig authConfig;
private final String addr;
@Inject
- private AuthConfig authConfig;
-
- @Inject
- public RegisterNewEmailSender(@Assisted final String address) {
- super("registernewemail");
+ public RegisterNewEmailSender(EmailArguments ea, AuthConfig ac,
+ @Assisted final String address) {
+ super(ea, "registernewemail");
+ authConfig = ac;
addr = address;
}
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 70ed4f5..91cd400 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
@@ -34,15 +34,14 @@
public ReplacePatchSetSender create(Change change);
}
- @Inject
- private SshInfo sshInfo;
-
private final Set<Account.Id> reviewers = new HashSet<Account.Id>();
private final Set<Account.Id> extraCC = new HashSet<Account.Id>();
+ private final SshInfo sshInfo;
@Inject
- public ReplacePatchSetSender(@Assisted Change c) {
- super(c, "newpatchset");
+ public ReplacePatchSetSender(EmailArguments ea, SshInfo si, @Assisted Change c) {
+ super(ea, c, "newpatchset");
+ sshInfo = si;
}
public void addReviewers(final Collection<Account.Id> cc) {
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 99e9565..9163dc3 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
@@ -18,8 +18,8 @@
/** Alert a user to a reply to a change, usually commentary made during review. */
public abstract class ReplyToChangeSender extends OutgoingEmail {
- protected ReplyToChangeSender(Change c, String mc) {
- super(c, mc);
+ protected ReplyToChangeSender(EmailArguments ea, Change c, String mc) {
+ super(ea, c, mc);
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PublishComments.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PublishComments.java
index 6fdb06e..f3e2890 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PublishComments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PublishComments.java
@@ -284,7 +284,6 @@
cm.setPatchSet(patchSet, patchSetInfoFactory.get(patchSetId));
cm.setChangeMessage(message);
cm.setPatchLineComments(drafts);
- cm.setReviewDb(db);
cm.send();
} catch (EmailException e) {
log.error("Cannot send comments by email for patch set " + patchSetId, e);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java
index 8e006f3..a0aeb58 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java
@@ -26,8 +26,6 @@
import static com.google.gerrit.server.query.QueryParser.SINGLE_WORD;
import static com.google.gerrit.server.query.QueryParser.VARIABLE_ASSIGN;
-import com.google.gerrit.server.query.change.ChangeData;
-
import org.antlr.runtime.tree.Tree;
import java.lang.annotation.ElementType;
@@ -244,6 +242,29 @@
*
* @param p the predicate to find.
* @param clazz type of the predicate instance.
+ * @return the predicate, null if not found.
+ */
+ @SuppressWarnings("unchecked")
+ public <P extends Predicate<T>> P find(Predicate<T> p, Class<P> clazz) {
+ if (clazz.isAssignableFrom(p.getClass())) {
+ return (P) p;
+ }
+
+ for (Predicate<T> c : p.getChildren()) {
+ P r = find(c, clazz);
+ if (r != null) {
+ return r;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Locate a predicate in the predicate tree.
+ *
+ * @param p the predicate to find.
+ * @param clazz type of the predicate instance.
* @param name name of the operator.
* @return the predicate, null if not found.
*/
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 9be4bf1..b704c1a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -35,7 +35,7 @@
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import com.google.inject.servlet.RequestScoped;
+import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
@@ -46,7 +46,6 @@
/**
* Parses a query string meant to be applied to change objects.
*/
-@RequestScoped
public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
private static final Pattern PAT_LEGACY_ID = Pattern.compile("^[1-9][0-9]*$");
private static final Pattern PAT_CHANGE_ID =
@@ -86,55 +85,67 @@
new QueryBuilder.Definition<ChangeData, ChangeQueryBuilder>(
ChangeQueryBuilder.class);
- private final Provider<ReviewDb> dbProvider;
- private final Provider<CurrentUser> currentUser;
- private final IdentifiedUser.GenericFactory userFactory;
- private final ChangeControl.Factory changeControlFactory;
- private final AccountResolver accountResolver;
- private final GroupCache groupCache;
- private final AuthConfig authConfig;
- private final ApprovalTypes approvalTypes;
- private final Project.NameKey wildProjectName;
+ static class Arguments {
+ final Provider<ReviewDb> dbProvider;
+ final Provider<ChangeQueryRewriter> rewriter;
+ final IdentifiedUser.GenericFactory userFactory;
+ final ChangeControl.Factory changeControlFactory;
+ final AccountResolver accountResolver;
+ final GroupCache groupCache;
+ final AuthConfig authConfig;
+ final ApprovalTypes approvalTypes;
+ final Project.NameKey wildProjectName;
- @Inject
- ChangeQueryBuilder(Provider<ReviewDb> dbProvider,
- Provider<CurrentUser> currentUser,
- IdentifiedUser.GenericFactory userFactory,
- ChangeControl.Factory changeControlFactory,
- AccountResolver accountResolver, GroupCache groupCache,
- AuthConfig authConfig, ApprovalTypes approvalTypes,
- @WildProjectName Project.NameKey wildProjectName) {
- super(mydef);
- this.dbProvider = dbProvider;
- this.currentUser = currentUser;
- this.userFactory = userFactory;
- this.changeControlFactory = changeControlFactory;
- this.accountResolver = accountResolver;
- this.groupCache = groupCache;
- this.authConfig = authConfig;
- this.approvalTypes = approvalTypes;
- this.wildProjectName = wildProjectName;
+ @Inject
+ Arguments(Provider<ReviewDb> dbProvider,
+ Provider<ChangeQueryRewriter> rewriter,
+ IdentifiedUser.GenericFactory userFactory,
+ ChangeControl.Factory changeControlFactory,
+ AccountResolver accountResolver, GroupCache groupCache,
+ AuthConfig authConfig, ApprovalTypes approvalTypes,
+ @WildProjectName Project.NameKey wildProjectName) {
+ this.dbProvider = dbProvider;
+ this.rewriter = rewriter;
+ this.userFactory = userFactory;
+ this.changeControlFactory = changeControlFactory;
+ this.accountResolver = accountResolver;
+ this.groupCache = groupCache;
+ this.authConfig = authConfig;
+ this.approvalTypes = approvalTypes;
+ this.wildProjectName = wildProjectName;
+ }
}
- Provider<ReviewDb> getReviewDbProvider() {
- return dbProvider;
+ public interface Factory {
+ ChangeQueryBuilder create(CurrentUser user);
+ }
+
+ private final Arguments args;
+ private final CurrentUser currentUser;
+
+ @Inject
+ ChangeQueryBuilder(Arguments args, @Assisted CurrentUser currentUser) {
+ super(mydef);
+ this.args = args;
+ this.currentUser = currentUser;
}
@Operator
public Predicate<ChangeData> age(String value) {
- return new AgePredicate(dbProvider, value);
+ return new AgePredicate(args.dbProvider, value);
}
@Operator
public Predicate<ChangeData> change(String query) {
if (PAT_LEGACY_ID.matcher(query).matches()) {
- return new LegacyChangeIdPredicate(dbProvider, Change.Id.parse(query));
+ return new LegacyChangeIdPredicate(args.dbProvider, Change.Id
+ .parse(query));
} else if (PAT_CHANGE_ID.matcher(query).matches()) {
if (query.charAt(0) == 'i') {
query = "I" + query.substring(1);
}
- return new ChangeIdPredicate(dbProvider, query);
+ return new ChangeIdPredicate(args.dbProvider, query);
}
throw new IllegalArgumentException();
@@ -146,30 +157,30 @@
return status_open();
} else if ("closed".equals(statusName)) {
- return ChangeStatusPredicate.closed(dbProvider);
+ return ChangeStatusPredicate.closed(args.dbProvider);
} else if ("reviewed".equalsIgnoreCase(statusName)) {
- return new IsReviewedPredicate(dbProvider);
+ return new IsReviewedPredicate(args.dbProvider);
} else {
- return new ChangeStatusPredicate(dbProvider, statusName);
+ return new ChangeStatusPredicate(args.dbProvider, statusName);
}
}
public Predicate<ChangeData> status_open() {
- return ChangeStatusPredicate.open(dbProvider);
+ return ChangeStatusPredicate.open(args.dbProvider);
}
@Operator
public Predicate<ChangeData> has(String value) {
if ("star".equalsIgnoreCase(value)) {
- return new IsStarredByPredicate(dbProvider, currentUser.get());
+ return new IsStarredByPredicate(args.dbProvider, currentUser);
}
if ("draft".equalsIgnoreCase(value)) {
- if (currentUser.get() instanceof IdentifiedUser) {
- return new HasDraftByPredicate(dbProvider,
- ((IdentifiedUser) currentUser.get()).getAccountId());
+ if (currentUser instanceof IdentifiedUser) {
+ return new HasDraftByPredicate(args.dbProvider,
+ ((IdentifiedUser) currentUser).getAccountId());
}
}
@@ -179,21 +190,19 @@
@Operator
public Predicate<ChangeData> is(String value) {
if ("starred".equalsIgnoreCase(value)) {
- return new IsStarredByPredicate(dbProvider, currentUser.get());
+ return new IsStarredByPredicate(args.dbProvider, currentUser);
}
if ("watched".equalsIgnoreCase(value)) {
- return new IsWatchedByPredicate(dbProvider, wildProjectName, //
- currentUser.get());
+ return new IsWatchedByPredicate(args, currentUser);
}
if ("visible".equalsIgnoreCase(value)) {
- return new IsVisibleToPredicate(dbProvider, changeControlFactory,
- currentUser.get());
+ return is_visible();
}
if ("reviewed".equalsIgnoreCase(value)) {
- return new IsReviewedPredicate(dbProvider);
+ return new IsReviewedPredicate(args.dbProvider);
}
try {
@@ -207,121 +216,129 @@
@Operator
public Predicate<ChangeData> commit(String id) {
- return new CommitPredicate(dbProvider, AbbreviatedObjectId.fromString(id));
+ return new CommitPredicate(args.dbProvider, AbbreviatedObjectId
+ .fromString(id));
}
@Operator
public Predicate<ChangeData> project(String name) {
- return new ProjectPredicate(dbProvider, name);
+ return new ProjectPredicate(args.dbProvider, name);
}
@Operator
public Predicate<ChangeData> branch(String name) {
- return new BranchPredicate(dbProvider, name);
+ return new BranchPredicate(args.dbProvider, name);
}
@Operator
public Predicate<ChangeData> topic(String name) {
- return new TopicPredicate(dbProvider, name);
+ return new TopicPredicate(args.dbProvider, name);
}
@Operator
public Predicate<ChangeData> ref(String ref) {
- return new RefPredicate(dbProvider, ref);
+ return new RefPredicate(args.dbProvider, ref);
}
@Operator
public Predicate<ChangeData> label(String name) {
- return new LabelPredicate(dbProvider, approvalTypes, name);
+ return new LabelPredicate(args.dbProvider, args.approvalTypes, name);
}
@Operator
public Predicate<ChangeData> starredby(String who)
throws QueryParseException, OrmException {
- Account account = accountResolver.find(who);
+ Account account = args.accountResolver.find(who);
if (account == null) {
throw error("User " + who + " not found");
}
- return new IsStarredByPredicate(dbProvider, //
- userFactory.create(dbProvider, account.getId()));
+ return new IsStarredByPredicate(args.dbProvider, //
+ args.userFactory.create(args.dbProvider, account.getId()));
}
@Operator
public Predicate<ChangeData> watchedby(String who)
throws QueryParseException, OrmException {
- Account account = accountResolver.find(who);
+ Account account = args.accountResolver.find(who);
if (account == null) {
throw error("User " + who + " not found");
}
- return new IsWatchedByPredicate(dbProvider, wildProjectName, //
- userFactory.create(dbProvider, account.getId()));
+ return new IsWatchedByPredicate(args, args.userFactory.create(
+ args.dbProvider, account.getId()));
}
@Operator
public Predicate<ChangeData> draftby(String who) throws QueryParseException,
OrmException {
- Account account = accountResolver.find(who);
+ Account account = args.accountResolver.find(who);
if (account == null) {
throw error("User " + who + " not found");
}
- return new HasDraftByPredicate(dbProvider, account.getId());
+ return new HasDraftByPredicate(args.dbProvider, account.getId());
}
@Operator
public Predicate<ChangeData> visibleto(String who)
throws QueryParseException, OrmException {
- Account account = accountResolver.find(who);
+ Account account = args.accountResolver.find(who);
if (account != null) {
- return visibleto(userFactory.create(dbProvider, account.getId()));
+ return visibleto(args.userFactory
+ .create(args.dbProvider, account.getId()));
}
// If its not an account, maybe its a group?
//
- AccountGroup g = groupCache.get(new AccountGroup.NameKey(who));
+ AccountGroup g = args.groupCache.get(new AccountGroup.NameKey(who));
if (g != null) {
- return visibleto(new SingleGroupUser(authConfig, g.getId()));
+ return visibleto(new SingleGroupUser(args.authConfig, g.getId()));
}
Collection<AccountGroup> matches =
- groupCache.get(new AccountGroup.ExternalNameKey(who));
+ args.groupCache.get(new AccountGroup.ExternalNameKey(who));
if (matches != null && !matches.isEmpty()) {
HashSet<AccountGroup.Id> ids = new HashSet<AccountGroup.Id>();
for (AccountGroup group : matches) {
ids.add(group.getId());
}
- return visibleto(new SingleGroupUser(authConfig, ids));
+ return visibleto(new SingleGroupUser(args.authConfig, ids));
}
throw error("No user or group matches \"" + who + "\".");
}
public Predicate<ChangeData> visibleto(CurrentUser user) {
- return new IsVisibleToPredicate(dbProvider, changeControlFactory, user);
+ return new IsVisibleToPredicate(args.dbProvider, //
+ args.changeControlFactory, //
+ user);
+ }
+
+ public Predicate<ChangeData> is_visible() {
+ return visibleto(currentUser);
}
@Operator
public Predicate<ChangeData> owner(String who) throws QueryParseException,
OrmException {
- Account account = accountResolver.find(who);
+ Account account = args.accountResolver.find(who);
if (account == null) {
throw error("User " + who + " not found");
}
- return new OwnerPredicate(dbProvider, account.getId());
+ return new OwnerPredicate(args.dbProvider, account.getId());
}
@Operator
public Predicate<ChangeData> reviewer(String nameOrEmail)
throws QueryParseException, OrmException {
- Account account = accountResolver.find(nameOrEmail);
+ Account account = args.accountResolver.find(nameOrEmail);
if (account == null) {
throw error("Reviewer " + nameOrEmail + " not found");
}
- return new ReviewerPredicate(dbProvider, account.getId());
+ return new ReviewerPredicate(args.dbProvider, account.getId());
}
@Operator
public Predicate<ChangeData> tr(String trackingId) {
- return new TrackingIdPredicate(dbProvider, trackingId);
+ return new TrackingIdPredicate(args.dbProvider, trackingId);
}
@Operator
@@ -350,12 +367,12 @@
@Operator
public Predicate<ChangeData> sortkey_after(String sortKey) {
- return new SortKeyPredicate.After(dbProvider, sortKey);
+ return new SortKeyPredicate.After(args.dbProvider, sortKey);
}
@Operator
public Predicate<ChangeData> sortkey_before(String sortKey) {
- return new SortKeyPredicate.Before(dbProvider, sortKey);
+ return new SortKeyPredicate.Before(args.dbProvider, sortKey);
}
@Operator
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
index c25a753..ee09239 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
@@ -18,7 +18,6 @@
import com.google.gerrit.reviewdb.ChangeAccess;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.query.IntPredicate;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryRewriter;
@@ -29,18 +28,18 @@
import com.google.inject.OutOfScopeException;
import com.google.inject.Provider;
import com.google.inject.name.Named;
-import com.google.inject.servlet.RequestScoped;
import java.util.Collection;
-@RequestScoped
public class ChangeQueryRewriter extends QueryRewriter<ChangeData> {
private static final QueryRewriter.Definition<ChangeData, ChangeQueryRewriter> mydef =
new QueryRewriter.Definition<ChangeData, ChangeQueryRewriter>(
ChangeQueryRewriter.class, new ChangeQueryBuilder(
- new InvalidProvider<ReviewDb>(),
- new InvalidProvider<CurrentUser>(), //
- null, null, null, null, null, null, null));
+ new ChangeQueryBuilder.Arguments( //
+ new InvalidProvider<ReviewDb>(), //
+ new InvalidProvider<ChangeQueryRewriter>(), //
+ null, null, null, null, null, null, null),
+ null));
private final Provider<ReviewDb> dbProvider;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
index 429b07b..870be73 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
@@ -14,18 +14,20 @@
package com.google.gerrit.server.query.change;
+import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gerrit.reviewdb.Project.NameKey;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.config.WildProjectName;
import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
import com.google.gwtorm.client.OrmException;
-import com.google.inject.Provider;
-import java.util.Set;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
class IsWatchedByPredicate extends OperatorPredicate<ChangeData> {
private static String describe(CurrentUser user) {
@@ -35,32 +37,81 @@
return user.toString();
}
- private final Provider<ReviewDb> db;
- private final Project.NameKey wildProject;
+ private final ChangeQueryBuilder.Arguments args;
private final CurrentUser user;
- IsWatchedByPredicate(Provider<ReviewDb> db,
- @WildProjectName Project.NameKey wildProject, CurrentUser user) {
+ private Map<Project.NameKey, List<Predicate<ChangeData>>> rules;
+
+ IsWatchedByPredicate(ChangeQueryBuilder.Arguments args, CurrentUser user) {
super(ChangeQueryBuilder.FIELD_WATCHEDBY, describe(user));
- this.db = db;
- this.wildProject = wildProject;
+ this.args = args;
this.user = user;
}
@Override
public boolean match(final ChangeData cd) throws OrmException {
- Set<NameKey> watched = user.getWatchedProjects();
- if (watched.contains(wildProject)) {
- return true;
+ if (rules == null) {
+ ChangeQueryBuilder builder = new ChangeQueryBuilder(args, user);
+ rules = new HashMap<Project.NameKey, List<Predicate<ChangeData>>>();
+ for (AccountProjectWatch w : user.getNotificationFilters()) {
+ List<Predicate<ChangeData>> list = rules.get(w.getProjectNameKey());
+ if (list == null) {
+ list = new ArrayList<Predicate<ChangeData>>(4);
+ rules.put(w.getProjectNameKey(), list);
+ }
+
+ Predicate<ChangeData> p = compile(builder, w);
+ if (p != null) {
+ list.add(p);
+ }
+ }
}
- Change change = cd.change(db);
+ if (rules.isEmpty()) {
+ return false;
+ }
+
+ Change change = cd.change(args.dbProvider);
if (change == null) {
return false;
}
Project.NameKey project = change.getDest().getParentKey();
- return watched.contains(project);
+ List<Predicate<ChangeData>> list = rules.get(project);
+ if (list == null) {
+ list = rules.get(args.wildProjectName);
+ }
+ if (list != null) {
+ for (Predicate<ChangeData> p : list) {
+ if (p.match(cd)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Predicate<ChangeData> compile(ChangeQueryBuilder builder,
+ AccountProjectWatch w) {
+ Predicate<ChangeData> p = builder.is_visible();
+ if (w.getFilter() != null) {
+ try {
+ p = Predicate.and(builder.parse(w.getFilter()), p);
+ if (builder.find(p, IsWatchedByPredicate.class) != null) {
+ // If the query is going to infinite loop, assume it
+ // will never match and return null. Yes this test
+ // prevents you from having a filter that matches what
+ // another user is filtering on. :-)
+ //
+ return null;
+ }
+ p = args.rewriter.get().rewrite(p);
+ } catch (QueryParseException e) {
+ return null;
+ }
+ }
+ return p;
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
index 3065fd1..62d3bbd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
@@ -60,7 +60,6 @@
private final SimpleDateFormat sdf =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss zzz");
- private final CurrentUser currentUser;
private final EventFactory eventFactory;
private final ChangeQueryBuilder queryBuilder;
private final ChangeQueryRewriter queryRewriter;
@@ -75,12 +74,11 @@
private PrintWriter out;
@Inject
- QueryProcessor(CurrentUser currentUser, EventFactory eventFactory,
- ChangeQueryBuilder queryBuilder, ChangeQueryRewriter queryRewriter,
- Provider<ReviewDb> db) {
- this.currentUser = currentUser;
+ QueryProcessor(EventFactory eventFactory,
+ ChangeQueryBuilder.Factory queryBuilder, CurrentUser currentUser,
+ ChangeQueryRewriter queryRewriter, Provider<ReviewDb> db) {
this.eventFactory = eventFactory;
- this.queryBuilder = queryBuilder;
+ this.queryBuilder = queryBuilder.create(currentUser);
this.queryRewriter = queryRewriter;
this.db = db;
}
@@ -107,9 +105,7 @@
final QueryStats stats = new QueryStats();
stats.runTimeMilliseconds = System.currentTimeMillis();
- final Predicate<ChangeData> visibleToMe =
- queryBuilder.visibleto(currentUser);
-
+ final Predicate<ChangeData> visibleToMe = queryBuilder.is_visible();
Predicate<ChangeData> s = compileQuery(queryString, visibleToMe);
List<ChangeData> results = new ArrayList<ChangeData>();
HashSet<Change.Id> want = new HashSet<Change.Id>();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SingleGroupUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SingleGroupUser.java
index 36e04d9..f8ab33f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SingleGroupUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SingleGroupUser.java
@@ -15,12 +15,14 @@
package com.google.gerrit.server.query.change;
import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.AuthConfig;
+import java.util.Collection;
import java.util.Collections;
import java.util.Set;
@@ -47,7 +49,7 @@
}
@Override
- public Set<Project.NameKey> getWatchedProjects() {
+ public Collection<AccountProjectWatch> getNotificationFilters() {
return Collections.emptySet();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
index d2638b3..2f8d4ca 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -32,7 +32,7 @@
/** A version of the database schema. */
public abstract class SchemaVersion {
/** The current schema version. */
- private static final Class<? extends SchemaVersion> C = Schema_39.class;
+ private static final Class<? extends SchemaVersion> C = Schema_40.class;
public static class Module extends AbstractModule {
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_40.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_40.java
new file mode 100644
index 0000000..7d3e4f5
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_40.java
@@ -0,0 +1,70 @@
+// Copyright (C) 2010 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.schema;
+
+import com.google.gerrit.reviewdb.AccountProjectWatch;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.gwtorm.schema.sql.DialectH2;
+import com.google.gwtorm.schema.sql.DialectMySQL;
+import com.google.gwtorm.schema.sql.DialectPostgreSQL;
+import com.google.gwtorm.schema.sql.SqlDialect;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.sql.SQLException;
+import java.sql.Statement;
+
+public class Schema_40 extends SchemaVersion {
+ @Inject
+ Schema_40(Provider<Schema_39> prior) {
+ super(prior);
+ }
+
+ @Override
+ protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException,
+ OrmException {
+ // Set to "*" the filter field of the previously watched projects
+ //
+ Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
+ try {
+ stmt.execute("UPDATE account_project_watches" //
+ + " SET filter = '" + AccountProjectWatch.FILTER_ALL + "'" //
+ + " WHERE filter IS NULL OR filter = ''");
+
+ // Set the new primary key
+ //
+ final SqlDialect dialect = ((JdbcSchema) db).getDialect();
+ if (dialect instanceof DialectPostgreSQL) {
+ stmt.execute("ALTER TABLE account_project_watches "
+ + "DROP CONSTRAINT account_project_watches_pkey");
+ stmt.execute("ALTER TABLE account_project_watches "
+ + "ADD PRIMARY KEY (account_id, project_name, filter)");
+
+ } else if ((dialect instanceof DialectH2)
+ || (dialect instanceof DialectMySQL)) {
+ stmt.execute("ALTER TABLE account_project_watches DROP PRIMARY KEY");
+ stmt.execute("ALTER TABLE account_project_watches "
+ + "ADD PRIMARY KEY (account_id, project_name, filter)");
+
+ } else {
+ throw new OrmException("Unsupported dialect " + dialect);
+ }
+ } finally {
+ stmt.close();
+ }
+ }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index 4bc3686..a0b9d25 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -25,6 +25,7 @@
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.MergeQueue;
import com.google.gerrit.server.patch.PublishComments;
import com.google.gerrit.server.project.CanSubmitResult;
@@ -96,6 +97,9 @@
private MergeQueue merger;
@Inject
+ private MergeOp.Factory opFactory;
+
+ @Inject
private ApprovalTypes approvalTypes;
@Inject
@@ -166,7 +170,7 @@
changeControl.canSubmit(patchSetId, db, approvalTypes,
functionStateFactory);
if (result == CanSubmitResult.OK) {
- ChangeUtil.submit(patchSetId, currentUser, db, merger);
+ ChangeUtil.submit(opFactory, patchSetId, currentUser, db, merger);
} else {
throw error(result.getMessage());
}