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>
This commit is contained in:
Shawn O. Pearce
2010-07-19 16:15:08 -07:00
parent 14760b7c0e
commit 0f42fc05a4
46 changed files with 908 additions and 434 deletions

View File

@@ -48,7 +48,7 @@ public interface AccountService extends RemoteJsonService {
void myProjectWatch(AsyncCallback<List<AccountProjectWatchInfo>> callback); void myProjectWatch(AsyncCallback<List<AccountProjectWatchInfo>> callback);
@SignInRequired @SignInRequired
void addProjectWatch(String projectName, void addProjectWatch(String projectName, String filter,
AsyncCallback<AccountProjectWatchInfo> callback); AsyncCallback<AccountProjectWatchInfo> callback);
@SignInRequired @SignInRequired

View File

@@ -32,6 +32,7 @@ public interface GerritCss extends CssResource {
String removeReviewer(); String removeReviewer();
String removeReviewerCell(); String removeReviewerCell();
String addSshKeyPanel(); String addSshKeyPanel();
String addWatchPanel();
String approvalCategoryList(); String approvalCategoryList();
String approvalTable(); String approvalTable();
String approvalhint(); String approvalhint();
@@ -180,4 +181,5 @@ public interface GerritCss extends CssResource {
String useridentity(); String useridentity();
String usernameField(); String usernameField();
String version(); String version();
String watchedProjectFilter();
} }

View File

@@ -80,6 +80,9 @@ public interface AccountConstants extends Constants {
String buttonWatchProject(); String buttonWatchProject();
String defaultProjectName(); String defaultProjectName();
String defaultFilter();
String watchedProjectName();
String watchedProjectFilter();
String watchedProjectColumnEmailNotifications(); String watchedProjectColumnEmailNotifications();
String watchedProjectColumnNewChanges(); String watchedProjectColumnNewChanges();
String watchedProjectColumnAllComments(); String watchedProjectColumnAllComments();

View File

@@ -61,6 +61,9 @@ sshJavaAppletNotAvailable = Open Key Unavailable: Java not enabled
buttonWatchProject = Watch buttonWatchProject = Watch
defaultProjectName = Project Name defaultProjectName = Project Name
defaultFilter = branch:name, or other search expression
watchedProjectName = Project Name
watchedProjectFilter = Only If
watchedProjectColumnEmailNotifications = Email Notifications watchedProjectColumnEmailNotifications = Email Notifications
watchedProjectColumnNewChanges = New Changes watchedProjectColumnNewChanges = New Changes
watchedProjectColumnAllComments = All Comments watchedProjectColumnAllComments = All Comments

View File

@@ -38,8 +38,11 @@ import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlowPanel; 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.SuggestBox;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter; 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.gwt.user.client.ui.SuggestOracle.Suggestion;
import com.google.gwtexpui.globalkey.client.NpTextBox; import com.google.gwtexpui.globalkey.client.NpTextBox;
import com.google.gwtjsonrpc.client.VoidResult; import com.google.gwtjsonrpc.client.VoidResult;
@@ -51,7 +54,9 @@ public class MyWatchedProjectsScreen extends SettingsScreen {
private WatchTable watches; private WatchTable watches;
private Button addNew; private Button addNew;
private NpTextBox nameBox;
private SuggestBox nameTxt; private SuggestBox nameTxt;
private NpTextBox filterTxt;
private Button delSel; private Button delSel;
private boolean submitOnSelection; private boolean submitOnSelection;
@@ -60,32 +65,30 @@ public class MyWatchedProjectsScreen extends SettingsScreen {
super.onInitUI(); super.onInitUI();
{ {
final FlowPanel fp = new FlowPanel(); nameBox = new NpTextBox();
nameTxt = new SuggestBox(new ProjectNameSuggestOracle(), nameBox);
final NpTextBox box = new NpTextBox(); nameBox.setVisibleLength(50);
nameTxt = new SuggestBox(new ProjectNameSuggestOracle(), box); nameBox.setText(Util.C.defaultProjectName());
box.setVisibleLength(50); nameBox.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
box.setText(Util.C.defaultProjectName()); nameBox.addFocusHandler(new FocusHandler() {
box.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
box.addFocusHandler(new FocusHandler() {
@Override @Override
public void onFocus(FocusEvent event) { public void onFocus(FocusEvent event) {
if (Util.C.defaultProjectName().equals(box.getText())) { if (Util.C.defaultProjectName().equals(nameBox.getText())) {
box.setText(""); nameBox.setText("");
box.removeStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint()); nameBox.removeStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
} }
} }
}); });
box.addBlurHandler(new BlurHandler() { nameBox.addBlurHandler(new BlurHandler() {
@Override @Override
public void onBlur(BlurEvent event) { public void onBlur(BlurEvent event) {
if ("".equals(box.getText())) { if ("".equals(nameBox.getText())) {
box.setText(Util.C.defaultProjectName()); nameBox.setText(Util.C.defaultProjectName());
box.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint()); nameBox.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
} }
} }
}); });
box.addKeyPressHandler(new KeyPressHandler() { nameBox.addKeyPressHandler(new KeyPressHandler() {
@Override @Override
public void onKeyPress(KeyPressEvent event) { public void onKeyPress(KeyPressEvent event) {
submitOnSelection = false; submitOnSelection = false;
@@ -108,7 +111,37 @@ public class MyWatchedProjectsScreen extends SettingsScreen {
} }
} }
}); });
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 = new Button(Util.C.buttonWatchProject());
addNew.addClickHandler(new ClickHandler() { addNew.addClickHandler(new ClickHandler() {
@@ -117,14 +150,32 @@ public class MyWatchedProjectsScreen extends SettingsScreen {
doAddNew(); 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); fp.add(addNew);
add(fp); add(fp);
} }
watches = new WatchTable(); watches = new WatchTable();
add(watches); add(watches);
{
final FlowPanel fp = new FlowPanel();
delSel = new Button(Util.C.buttonDeleteSshKey()); delSel = new Button(Util.C.buttonDeleteSshKey());
delSel.addClickHandler(new ClickHandler() { delSel.addClickHandler(new ClickHandler() {
@Override @Override
@@ -132,9 +183,7 @@ public class MyWatchedProjectsScreen extends SettingsScreen {
watches.deleteChecked(); watches.deleteChecked();
} }
}); });
fp.add(delSel); add(delSel);
add(fp);
}
} }
void doAddNew() { void doAddNew() {
@@ -144,11 +193,23 @@ public class MyWatchedProjectsScreen extends SettingsScreen {
return; return;
} }
String filter = filterTxt.getText();
if (filter == null || filter.isEmpty()
|| filter.equals(Util.C.defaultFilter())) {
filter = null;
}
addNew.setEnabled(false); addNew.setEnabled(false);
Util.ACCOUNT_SVC.addProjectWatch(projectName, nameBox.setEnabled(false);
filterTxt.setEnabled(false);
Util.ACCOUNT_SVC.addProjectWatch(projectName, filter,
new GerritCallback<AccountProjectWatchInfo>() { new GerritCallback<AccountProjectWatchInfo>() {
public void onSuccess(final AccountProjectWatchInfo result) { public void onSuccess(final AccountProjectWatchInfo result) {
addNew.setEnabled(true); addNew.setEnabled(true);
nameBox.setEnabled(true);
filterTxt.setEnabled(true);
nameTxt.setText(""); nameTxt.setText("");
watches.insertWatch(result); watches.insertWatch(result);
} }
@@ -156,6 +217,9 @@ public class MyWatchedProjectsScreen extends SettingsScreen {
@Override @Override
public void onFailure(final Throwable caught) { public void onFailure(final Throwable caught) {
addNew.setEnabled(true); addNew.setEnabled(true);
nameBox.setEnabled(true);
filterTxt.setEnabled(true);
super.onFailure(caught); super.onFailure(caught);
} }
}); });
@@ -177,8 +241,7 @@ public class MyWatchedProjectsScreen extends SettingsScreen {
WatchTable() { WatchTable() {
table.setWidth(""); table.setWidth("");
table.insertRow(1); table.insertRow(1);
table.setText(0, 2, com.google.gerrit.client.changes.Util.C table.setText(0, 2, Util.C.watchedProjectName());
.changeTableColumnProject());
table.setText(0, 3, Util.C.watchedProjectColumnEmailNotifications()); table.setText(0, 3, Util.C.watchedProjectColumnEmailNotifications());
final FlexCellFormatter fmt = table.getFlexCellFormatter(); final FlexCellFormatter fmt = table.getFlexCellFormatter();
@@ -253,8 +316,16 @@ public class MyWatchedProjectsScreen extends SettingsScreen {
} }
void populate(final int row, final AccountProjectWatchInfo k) { 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, 1, new CheckBox());
table.setWidget(row, 2, new ProjectLink(k.getProject().getNameKey(), Status.NEW)); table.setWidget(row, 2, fp);
{ {
final CheckBox notifyNewChanges = new CheckBox(); final CheckBox notifyNewChanges = new CheckBox();
notifyNewChanges.addClickHandler(new ClickHandler() { notifyNewChanges.addClickHandler(new ClickHandler() {

View File

@@ -1029,6 +1029,15 @@ a:hover.downloadLink {
font-weight: bold; font-weight: bold;
} }
.addWatchPanel {
margin-top: 10px;
padding: 5px 5px 5px 5px;
}
.watchedProjectFilter {
margin-left: 1em;
color: grey;
}
.addSshKeyPanel { .addSshKeyPanel {
margin-top: 10px; margin-top: 10px;
background-color: trimColor; background-color: trimColor;

View File

@@ -89,7 +89,7 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements
private final ChangeControl.Factory changeControlFactory; private final ChangeControl.Factory changeControlFactory;
private final AccountInfoCacheFactory.Factory accountInfoCacheFactory; private final AccountInfoCacheFactory.Factory accountInfoCacheFactory;
private final Provider<ChangeQueryBuilder> queryBuilder; private final ChangeQueryBuilder.Factory queryBuilder;
private final Provider<ChangeQueryRewriter> queryRewriter; private final Provider<ChangeQueryRewriter> queryRewriter;
@Inject @Inject
@@ -97,7 +97,7 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements
final Provider<CurrentUser> currentUser, final Provider<CurrentUser> currentUser,
final ChangeControl.Factory changeControlFactory, final ChangeControl.Factory changeControlFactory,
final AccountInfoCacheFactory.Factory accountInfoCacheFactory, final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
final Provider<ChangeQueryBuilder> queryBuilder, final ChangeQueryBuilder.Factory queryBuilder,
final Provider<ChangeQueryRewriter> queryRewriter) { final Provider<ChangeQueryRewriter> queryRewriter) {
super(schema, currentUser); super(schema, currentUser);
this.currentUser = currentUser; this.currentUser = currentUser;
@@ -144,10 +144,8 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements
final int limit, final String key, final Comparator<Change> cmp) final int limit, final String key, final Comparator<Change> cmp)
throws OrmException, InvalidQueryException { throws OrmException, InvalidQueryException {
try { try {
final ChangeQueryBuilder builder = queryBuilder.get(); final ChangeQueryBuilder builder = queryBuilder.create(currentUser.get());
final Predicate<ChangeData> visibleToMe = final Predicate<ChangeData> visibleToMe = builder.is_visible();
builder.visibleto(currentUser.get());
Predicate<ChangeData> q = builder.parse(query); Predicate<ChangeData> q = builder.parse(query);
q = Predicate.and(q, // q = Predicate.and(q, //
cmp == QUERY_PREV // cmp == QUERY_PREV //

View File

@@ -17,6 +17,7 @@ package com.google.gerrit.httpd.rpc.account;
import com.google.gerrit.common.data.AccountProjectWatchInfo; import com.google.gerrit.common.data.AccountProjectWatchInfo;
import com.google.gerrit.common.data.AccountService; import com.google.gerrit.common.data.AccountService;
import com.google.gerrit.common.data.AgreementInfo; 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.common.errors.NoSuchEntityException;
import com.google.gerrit.httpd.rpc.BaseServiceImplementation; import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
import com.google.gerrit.reviewdb.Account; import com.google.gerrit.reviewdb.Account;
@@ -29,8 +30,11 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.project.NoSuchProjectException; import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl; 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.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult; import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmDuplicateKeyException;
import com.google.gwtorm.client.OrmException; import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
@@ -47,18 +51,21 @@ class AccountServiceImpl extends BaseServiceImplementation implements
private final AccountCache accountCache; private final AccountCache accountCache;
private final ProjectControl.Factory projectControlFactory; private final ProjectControl.Factory projectControlFactory;
private final AgreementInfoFactory.Factory agreementInfoFactory; private final AgreementInfoFactory.Factory agreementInfoFactory;
private final ChangeQueryBuilder.Factory queryBuilder;
@Inject @Inject
AccountServiceImpl(final Provider<ReviewDb> schema, AccountServiceImpl(final Provider<ReviewDb> schema,
final Provider<IdentifiedUser> identifiedUser, final Provider<IdentifiedUser> identifiedUser,
final AccountCache accountCache, final AccountCache accountCache,
final ProjectControl.Factory projectControlFactory, final ProjectControl.Factory projectControlFactory,
final AgreementInfoFactory.Factory agreementInfoFactory) { final AgreementInfoFactory.Factory agreementInfoFactory,
final ChangeQueryBuilder.Factory queryBuilder) {
super(schema, identifiedUser); super(schema, identifiedUser);
this.currentUser = identifiedUser; this.currentUser = identifiedUser;
this.accountCache = accountCache; this.accountCache = accountCache;
this.projectControlFactory = projectControlFactory; this.projectControlFactory = projectControlFactory;
this.agreementInfoFactory = agreementInfoFactory; this.agreementInfoFactory = agreementInfoFactory;
this.queryBuilder = queryBuilder;
} }
public void myAccount(final AsyncCallback<Account> callback) { public void myAccount(final AsyncCallback<Account> callback) {
@@ -137,19 +144,31 @@ class AccountServiceImpl extends BaseServiceImplementation implements
}); });
} }
public void addProjectWatch(final String projectName, public void addProjectWatch(final String projectName, final String filter,
final AsyncCallback<AccountProjectWatchInfo> callback) { final AsyncCallback<AccountProjectWatchInfo> callback) {
run(callback, new Action<AccountProjectWatchInfo>() { run(callback, new Action<AccountProjectWatchInfo>() {
public AccountProjectWatchInfo run(ReviewDb db) throws OrmException, public AccountProjectWatchInfo run(ReviewDb db) throws OrmException,
NoSuchProjectException { NoSuchProjectException, InvalidQueryException {
final Project.NameKey nameKey = new Project.NameKey(projectName); final Project.NameKey nameKey = new Project.NameKey(projectName);
final ProjectControl ctl = projectControlFactory.validateFor(nameKey); final ProjectControl ctl = projectControlFactory.validateFor(nameKey);
final AccountProjectWatch watch = if (filter != null) {
new AccountProjectWatch( try {
new AccountProjectWatch.Key(((IdentifiedUser) ctl queryBuilder.create(currentUser.get()).parse(filter);
.getCurrentUser()).getAccountId(), nameKey)); } 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)); db.accountProjectWatches().insert(Collections.singleton(watch));
} catch (OrmDuplicateKeyException alreadyHave) {
watch = db.accountProjectWatches().get(watch.getKey());
}
return new AccountProjectWatchInfo(watch, ctl.getProject()); return new AccountProjectWatchInfo(watch, ctl.getProject());
} }
}); });

View File

@@ -127,7 +127,6 @@ class AbandonChange extends Handler<ChangeDetail> {
// Email the reviewers // Email the reviewers
final AbandonedSender cm = abandonedSenderFactory.create(change); final AbandonedSender cm = abandonedSenderFactory.create(change);
cm.setFrom(currentUser.getAccountId()); cm.setFrom(currentUser.getAccountId());
cm.setReviewDb(db);
cm.setChangeMessage(cmsg); cm.setChangeMessage(cmsg);
cm.send(); cm.send();
} }

View File

@@ -23,6 +23,7 @@ import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.ChangeUtil; import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser; 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.git.MergeQueue;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException; import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.CanSubmitResult; import com.google.gerrit.server.project.CanSubmitResult;
@@ -44,8 +45,8 @@ class SubmitAction extends Handler<ChangeDetail> {
private final FunctionState.Factory functionState; private final FunctionState.Factory functionState;
private final IdentifiedUser user; private final IdentifiedUser user;
private final ChangeDetailFactory.Factory changeDetailFactory; private final ChangeDetailFactory.Factory changeDetailFactory;
@Inject private final ChangeControl.Factory changeControlFactory;
private ChangeControl.Factory changeControlFactory; private final MergeOp.Factory opFactory;
private final PatchSet.Id patchSetId; private final PatchSet.Id patchSetId;
@@ -53,13 +54,17 @@ class SubmitAction extends Handler<ChangeDetail> {
SubmitAction(final ReviewDb db, final MergeQueue mq, final ApprovalTypes at, SubmitAction(final ReviewDb db, final MergeQueue mq, final ApprovalTypes at,
final FunctionState.Factory fs, final IdentifiedUser user, final FunctionState.Factory fs, final IdentifiedUser user,
final ChangeDetailFactory.Factory changeDetailFactory, final ChangeDetailFactory.Factory changeDetailFactory,
final ChangeControl.Factory changeControlFactory,
final MergeOp.Factory opFactory,
@Assisted final PatchSet.Id patchSetId) { @Assisted final PatchSet.Id patchSetId) {
this.db = db; this.db = db;
this.merger = mq; this.merger = mq;
this.approvalTypes = at; this.approvalTypes = at;
this.functionState = fs; this.functionState = fs;
this.user = user; this.user = user;
this.changeControlFactory = changeControlFactory;
this.changeDetailFactory = changeDetailFactory; this.changeDetailFactory = changeDetailFactory;
this.opFactory = opFactory;
this.patchSetId = patchSetId; this.patchSetId = patchSetId;
} }
@@ -76,7 +81,7 @@ class SubmitAction extends Handler<ChangeDetail> {
CanSubmitResult err = CanSubmitResult err =
changeControl.canSubmit(patchSetId, db, approvalTypes, functionState); changeControl.canSubmit(patchSetId, db, approvalTypes, functionState);
if (err == CanSubmitResult.OK) { if (err == CanSubmitResult.OK) {
ChangeUtil.submit(patchSetId, user, db, merger); ChangeUtil.submit(opFactory, patchSetId, user, db, merger);
return changeDetailFactory.create(changeId).call(); return changeDetailFactory.create(changeId).call();
} else { } else {
throw new IllegalStateException(err.getMessage()); throw new IllegalStateException(err.getMessage());

View File

@@ -130,7 +130,6 @@ class AddReviewer extends Handler<ReviewerResult> {
final AddReviewerSender cm; final AddReviewerSender cm;
cm = addReviewerSenderFactory.create(control.getChange()); cm = addReviewerSenderFactory.create(control.getChange());
cm.setFrom(currentUser.getAccountId()); cm.setFrom(currentUser.getAccountId());
cm.setReviewDb(db);
cm.addReviewers(added); cm.addReviewers(added);
cm.send(); cm.send();

View File

@@ -16,9 +16,12 @@ package com.google.gerrit.reviewdb;
import com.google.gwtorm.client.Column; import com.google.gwtorm.client.Column;
import com.google.gwtorm.client.CompoundKey; import com.google.gwtorm.client.CompoundKey;
import com.google.gwtorm.client.StringKey;
/** An {@link Account} interested in a {@link Project}. */ /** An {@link Account} interested in a {@link Project}. */
public final class AccountProjectWatch { public final class AccountProjectWatch {
public static final String FILTER_ALL = "*";
public static class Key extends CompoundKey<Account.Id> { public static class Key extends CompoundKey<Account.Id> {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@@ -28,14 +31,19 @@ public final class AccountProjectWatch {
@Column(id = 2) @Column(id = 2)
protected Project.NameKey projectName; protected Project.NameKey projectName;
@Column(id = 3)
protected Filter filter;
protected Key() { protected Key() {
accountId = new Account.Id(); accountId = new Account.Id();
projectName = new Project.NameKey(); 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; accountId = a;
projectName = g; projectName = g;
filter = new Filter(f);
} }
@Override @Override
@@ -45,7 +53,31 @@ public final class AccountProjectWatch {
@Override @Override
public com.google.gwtorm.client.Key<?>[] members() { 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 @@ public final class AccountProjectWatch {
return key.projectName; return key.projectName;
} }
public String getFilter() {
return FILTER_ALL.equals(key.filter.get()) ? null : key.filter.get();
}
public boolean isNotifyNewChanges() { public boolean isNotifyNewChanges() {
return notifyNewChanges; return notifyNewChanges;
} }

View File

@@ -15,12 +15,13 @@
package com.google.gerrit.server; package com.google.gerrit.server;
import com.google.gerrit.reviewdb.AccountGroup; import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Project.NameKey;
import com.google.gerrit.server.config.AuthConfig; import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
@@ -43,7 +44,7 @@ public class AnonymousUser extends CurrentUser {
} }
@Override @Override
public Set<NameKey> getWatchedProjects() { public Collection<AccountProjectWatch> getNotificationFilters() {
return Collections.emptySet(); return Collections.emptySet();
} }

View File

@@ -23,6 +23,7 @@ import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.TrackingId; import com.google.gerrit.reviewdb.TrackingId;
import com.google.gerrit.server.config.TrackingFooter; import com.google.gerrit.server.config.TrackingFooter;
import com.google.gerrit.server.config.TrackingFooters; import com.google.gerrit.server.config.TrackingFooters;
import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.MergeQueue; import com.google.gerrit.server.git.MergeQueue;
import com.google.gwtorm.client.AtomicUpdate; import com.google.gwtorm.client.AtomicUpdate;
import com.google.gwtorm.client.OrmConcurrencyException; import com.google.gwtorm.client.OrmConcurrencyException;
@@ -135,8 +136,8 @@ public class ChangeUtil {
db.trackingIds().delete(toDelete); db.trackingIds().delete(toDelete);
} }
public static void submit(PatchSet.Id patchSetId, IdentifiedUser user, ReviewDb db, MergeQueue merger) public static void submit(MergeOp.Factory opFactory, PatchSet.Id patchSetId,
throws OrmException { IdentifiedUser user, ReviewDb db, MergeQueue merger) throws OrmException {
final Change.Id changeId = patchSetId.getParentKey(); final Change.Id changeId = patchSetId.getParentKey();
final PatchSetApproval approval = createSubmitApproval(patchSetId, user, db); final PatchSetApproval approval = createSubmitApproval(patchSetId, user, db);
@@ -154,7 +155,7 @@ public class ChangeUtil {
}); });
if (change.getStatus() == Change.Status.SUBMITTED) { if (change.getStatus() == Change.Status.SUBMITTED) {
merger.merge(change.getDest()); merger.merge(opFactory, change.getDest());
} }
} }

View File

@@ -15,11 +15,12 @@
package com.google.gerrit.server; package com.google.gerrit.server;
import com.google.gerrit.reviewdb.AccountGroup; import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.server.config.AuthConfig; import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.servlet.RequestScoped; import com.google.inject.servlet.RequestScoped;
import java.util.Collection;
import java.util.Set; import java.util.Set;
/** /**
@@ -60,8 +61,8 @@ public abstract class CurrentUser {
/** Set of changes starred by this user. */ /** Set of changes starred by this user. */
public abstract Set<Change.Id> getStarredChanges(); public abstract Set<Change.Id> getStarredChanges();
/** Set of project that are watched by this user */ /** Filters selecting changes the user wants to monitor. */
public abstract Set<Project.NameKey> getWatchedProjects(); public abstract Collection<AccountProjectWatch> getNotificationFilters();
/** Is the user a non-interactive user? */ /** Is the user a non-interactive user? */
public boolean isBatchUser() { public boolean isBatchUser() {

View File

@@ -19,7 +19,6 @@ import com.google.gerrit.reviewdb.AccountDiffPreference;
import com.google.gerrit.reviewdb.AccountGroup; import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountProjectWatch; import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.StarredChange; import com.google.gerrit.reviewdb.StarredChange;
import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.account.AccountCache;
@@ -43,9 +42,11 @@ import java.net.InetSocketAddress;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.net.URL; import java.net.URL;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.TimeZone; import java.util.TimeZone;
@@ -145,7 +146,7 @@ public class IdentifiedUser extends CurrentUser {
private Set<String> emailAddresses; private Set<String> emailAddresses;
private Set<AccountGroup.Id> effectiveGroups; private Set<AccountGroup.Id> effectiveGroups;
private Set<Change.Id> starredChanges; private Set<Change.Id> starredChanges;
private Set<Project.NameKey> watchedProjects; private Collection<AccountProjectWatch> notificationFilters;
private IdentifiedUser(final AccessPath accessPath, private IdentifiedUser(final AccessPath accessPath,
final AuthConfig authConfig, final Provider<String> canonicalUrl, final AuthConfig authConfig, final Provider<String> canonicalUrl,
@@ -237,24 +238,22 @@ public class IdentifiedUser extends CurrentUser {
} }
@Override @Override
public Set<Project.NameKey> getWatchedProjects() { public Collection<AccountProjectWatch> getNotificationFilters() {
if (watchedProjects == null) { if (notificationFilters == null) {
if (dbProvider == null) { if (dbProvider == null) {
throw new OutOfScopeException("Not in request scoped user"); throw new OutOfScopeException("Not in request scoped user");
} }
final Set<Project.NameKey> h = new HashSet<Project.NameKey>(); List<AccountProjectWatch> r;
try { try {
for (AccountProjectWatch projectWatch : dbProvider.get() r = dbProvider.get().accountProjectWatches() //
.accountProjectWatches().byAccount(getAccountId())) { .byAccount(getAccountId()).toList();
h.add(projectWatch.getProjectNameKey());
}
} catch (OrmException e) { } 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 notificationFilters;
return watchedProjects;
} }
public PersonIdent newRefLogIdent() { public PersonIdent newRefLogIdent() {

View File

@@ -15,6 +15,7 @@
package com.google.gerrit.server; package com.google.gerrit.server;
import com.google.gerrit.reviewdb.AccountGroup; import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Project.NameKey; import com.google.gerrit.reviewdb.Project.NameKey;
import com.google.gerrit.server.config.AuthConfig; import com.google.gerrit.server.config.AuthConfig;
@@ -22,6 +23,7 @@ import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.Assisted;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@@ -59,7 +61,7 @@ public class PeerDaemonUser extends CurrentUser {
} }
@Override @Override
public Set<NameKey> getWatchedProjects() { public Collection<AccountProjectWatch> getNotificationFilters() {
return Collections.emptySet(); return Collections.emptySet();
} }

View File

@@ -15,12 +15,14 @@
package com.google.gerrit.server; package com.google.gerrit.server;
import com.google.gerrit.reviewdb.AccountGroup; import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Project.NameKey; import com.google.gerrit.reviewdb.Project.NameKey;
import com.google.gerrit.server.config.AuthConfig; import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.Assisted;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@@ -73,7 +75,7 @@ public class ReplicationUser extends CurrentUser {
} }
@Override @Override
public Set<NameKey> getWatchedProjects() { public Collection<AccountProjectWatch> getNotificationFilters() {
return Collections.emptySet(); return Collections.emptySet();
} }

View File

@@ -132,7 +132,6 @@ public class GerritGlobalModule extends FactoryModule {
factory(PushAllProjectsOp.Factory.class); factory(PushAllProjectsOp.Factory.class);
bind(MergeQueue.class).to(ChangeMergeQueue.class).in(SINGLETON); bind(MergeQueue.class).to(ChangeMergeQueue.class).in(SINGLETON);
factory(MergeOp.Factory.class);
factory(ReloadSubmitQueueOp.Factory.class); factory(ReloadSubmitQueueOp.Factory.class);
bind(FromAddressGenerator.class).toProvider( bind(FromAddressGenerator.class).toProvider(
@@ -142,12 +141,6 @@ public class GerritGlobalModule extends FactoryModule {
bind(PatchSetInfoFactory.class); bind(PatchSetInfoFactory.class);
bind(IdentifiedUser.GenericFactory.class).in(SINGLETON); bind(IdentifiedUser.GenericFactory.class).in(SINGLETON);
factory(FunctionState.Factory.class); 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); factory(ReplicationUser.Factory.class);
install(new LifecycleModule() { install(new LifecycleModule() {

View File

@@ -21,9 +21,15 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.RequestCleanup; import com.google.gerrit.server.RequestCleanup;
import com.google.gerrit.server.account.AccountResolver; import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.account.GroupControl; 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.git.ReceiveCommits;
import com.google.gerrit.server.mail.AbandonedSender;
import com.google.gerrit.server.mail.AddReviewerSender; 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.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.mail.ReplacePatchSetSender;
import com.google.gerrit.server.patch.PublishComments; import com.google.gerrit.server.patch.PublishComments;
import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.ChangeControl;
@@ -41,14 +47,15 @@ public class GerritRequestModule extends FactoryModule {
RequestScoped.class); RequestScoped.class);
bind(IdentifiedUser.RequestFactory.class).in(SINGLETON); bind(IdentifiedUser.RequestFactory.class).in(SINGLETON);
bind(AccountResolver.class); bind(AccountResolver.class);
bind(ChangeQueryBuilder.class);
bind(ChangeQueryRewriter.class); bind(ChangeQueryRewriter.class);
bind(ChangeControl.Factory.class).in(SINGLETON); bind(ChangeControl.Factory.class).in(SINGLETON);
bind(GroupControl.Factory.class).in(SINGLETON); bind(GroupControl.Factory.class).in(SINGLETON);
bind(ProjectControl.Factory.class).in(SINGLETON); bind(ProjectControl.Factory.class).in(SINGLETON);
factory(ChangeQueryBuilder.Factory.class);
factory(ReceiveCommits.Factory.class); factory(ReceiveCommits.Factory.class);
factory(MergeOp.Factory.class);
// Not really per-request, but dammit, I don't know where else to // Not really per-request, but dammit, I don't know where else to
// easily park this stuff. // easily park this stuff.
@@ -57,5 +64,10 @@ public class GerritRequestModule extends FactoryModule {
factory(CreateChangeSender.Factory.class); factory(CreateChangeSender.Factory.class);
factory(PublishComments.Factory.class); factory(PublishComments.Factory.class);
factory(ReplacePatchSetSender.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);
} }
} }

View File

@@ -19,12 +19,30 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.gerrit.reviewdb.Branch; import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Project; 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.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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.net.SocketAddress;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -38,18 +56,47 @@ public class ChangeMergeQueue implements MergeQueue {
new HashMap<Branch.NameKey, RecheckJob>(); new HashMap<Branch.NameKey, RecheckJob>();
private final WorkQueue workQueue; private final WorkQueue workQueue;
private final MergeOp.Factory opFactory; private final Provider<MergeOp.Factory> bgFactory;
@Inject @Inject
ChangeMergeQueue(final WorkQueue wq, final MergeOp.Factory of) { ChangeMergeQueue(final WorkQueue wq, Injector parent) {
workQueue = wq; 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 @Override
public void merge(final Branch.NameKey branch) { public void merge(MergeOp.Factory mof, Branch.NameKey branch) {
if (start(branch)) { if (start(branch)) {
mergeImpl(branch); mergeImpl(mof, branch);
} }
} }
@@ -127,7 +174,7 @@ public class ChangeMergeQueue implements MergeQueue {
e.needMerge = false; e.needMerge = false;
} }
private void mergeImpl(final Branch.NameKey branch) { private void mergeImpl(MergeOp.Factory opFactory, Branch.NameKey branch) {
try { try {
opFactory.create(branch).merge(); opFactory.create(branch).merge();
} catch (Throwable e) { } catch (Throwable e) {
@@ -137,6 +184,26 @@ public class ChangeMergeQueue implements MergeQueue {
} }
} }
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) { private synchronized void recheck(final RecheckJob e) {
final long remainingDelay = e.recheckAt - System.currentTimeMillis(); final long remainingDelay = e.recheckAt - System.currentTimeMillis();
if (MILLISECONDS.convert(10, SECONDS) < remainingDelay) { if (MILLISECONDS.convert(10, SECONDS) < remainingDelay) {
@@ -194,4 +261,65 @@ public class ChangeMergeQueue implements MergeQueue {
return "recheck " + project.get() + " " + dest.getShortName(); 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;
}
}
} }

View File

@@ -1179,7 +1179,6 @@ public class MergeOp {
if (submitter != null) { if (submitter != null) {
cm.setFrom(submitter.getAccountId()); cm.setFrom(submitter.getAccountId());
} }
cm.setReviewDb(schema);
cm.setPatchSet(schema.patchSets().get(c.currentPatchSetId())); cm.setPatchSet(schema.patchSets().get(c.currentPatchSetId()));
cm.send(); cm.send();
} catch (OrmException e) { } catch (OrmException e) {
@@ -1241,7 +1240,6 @@ public class MergeOp {
cm.setFrom(submitter.getAccountId()); cm.setFrom(submitter.getAccountId());
} }
} }
cm.setReviewDb(schema);
cm.setPatchSet(schema.patchSets().get(c.currentPatchSetId())); cm.setPatchSet(schema.patchSets().get(c.currentPatchSetId()));
cm.setChangeMessage(msg); cm.setChangeMessage(msg);
cm.send(); cm.send();

View File

@@ -19,7 +19,7 @@ import com.google.gerrit.reviewdb.Branch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public interface MergeQueue { public interface MergeQueue {
void merge(Branch.NameKey branch); void merge(MergeOp.Factory mof, Branch.NameKey branch);
void schedule(Branch.NameKey branch); void schedule(Branch.NameKey branch);

View File

@@ -931,7 +931,6 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
cm = createChangeSenderFactory.create(change); cm = createChangeSenderFactory.create(change);
cm.setFrom(me); cm.setFrom(me);
cm.setPatchSet(ps, info); cm.setPatchSet(ps, info);
cm.setReviewDb(db);
cm.addReviewers(reviewers); cm.addReviewers(reviewers);
cm.addExtraCC(cc); cm.addExtraCC(cc);
cm.send(); cm.send();
@@ -1234,7 +1233,6 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
cm.setFrom(me); cm.setFrom(me);
cm.setPatchSet(ps, result.info); cm.setPatchSet(ps, result.info);
cm.setChangeMessage(result.msg); cm.setChangeMessage(result.msg);
cm.setReviewDb(db);
cm.addReviewers(reviewers); cm.addReviewers(reviewers);
cm.addExtraCC(cc); cm.addExtraCC(cc);
cm.addReviewers(oldReviewers); cm.addReviewers(oldReviewers);
@@ -1583,7 +1581,6 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
try { try {
final MergedSender cm = mergedSenderFactory.create(result.change); final MergedSender cm = mergedSenderFactory.create(result.change);
cm.setFrom(currentUser.getAccountId()); cm.setFrom(currentUser.getAccountId());
cm.setReviewDb(db);
cm.setPatchSet(result.patchSet, result.info); cm.setPatchSet(result.patchSet, result.info);
cm.setDest(new Branch.NameKey(project.getNameKey(), cm.setDest(new Branch.NameKey(project.getNameKey(),
result.mergedIntoRef)); result.mergedIntoRef));

View File

@@ -25,8 +25,8 @@ public class AbandonedSender extends ReplyToChangeSender {
} }
@Inject @Inject
public AbandonedSender(@Assisted Change c) { public AbandonedSender(EmailArguments ea, @Assisted Change c) {
super(c, "abandon"); super(ea, c, "abandon");
} }
@Override @Override

View File

@@ -15,6 +15,7 @@
package com.google.gerrit.server.mail; package com.google.gerrit.server.mail;
import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.Assisted;
@@ -25,8 +26,9 @@ public class AddReviewerSender extends NewChangeSender {
} }
@Inject @Inject
public AddReviewerSender(@Assisted Change c) { public AddReviewerSender(EmailArguments ea, SshInfo sshInfo,
super(c); @Assisted Change c) {
super(ea, sshInfo, c);
} }
@Override @Override

View File

@@ -38,8 +38,8 @@ public class CommentSender extends ReplyToChangeSender {
private List<PatchLineComment> inlineComments = Collections.emptyList(); private List<PatchLineComment> inlineComments = Collections.emptyList();
@Inject @Inject
public CommentSender(@Assisted Change c) { public CommentSender(EmailArguments ea, @Assisted Change c) {
super(c, "comment"); super(ea, c, "comment");
} }
public void setPatchLineComments(final List<PatchLineComment> plc) { public void setPatchLineComments(final List<PatchLineComment> plc) {
@@ -124,7 +124,7 @@ public class CommentSender extends ReplyToChangeSender {
private Repository getRepository() { private Repository getRepository() {
try { try {
return server.openRepository(projectName); return args.server.openRepository(projectName);
} catch (RepositoryNotFoundException e) { } catch (RepositoryNotFoundException e) {
return null; return null;
} }

View File

@@ -19,7 +19,7 @@ import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountGroupMember; import com.google.gerrit.reviewdb.AccountGroupMember;
import com.google.gerrit.reviewdb.AccountProjectWatch; import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change; 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.gwtorm.client.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.Assisted;
@@ -34,8 +34,9 @@ public class CreateChangeSender extends NewChangeSender {
} }
@Inject @Inject
public CreateChangeSender(@Assisted Change c) { public CreateChangeSender(EmailArguments ea, SshInfo sshInfo,
super(c); @Assisted Change c) {
super(ea, sshInfo, c);
} }
@Override @Override
@@ -46,24 +47,20 @@ public class CreateChangeSender extends NewChangeSender {
} }
private void bccWatchers() { private void bccWatchers() {
if (db != null) {
try { 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. // Try to mark interested owners with a TO and not a BCC line.
// //
final Set<Account.Id> owners = new HashSet<Account.Id>(); final Set<Account.Id> owners = new HashSet<Account.Id>();
for (AccountGroup.Id g : getProjectOwners()) { for (AccountGroup.Id g : getProjectOwners()) {
for (AccountGroupMember m : db.accountGroupMembers().byGroup(g)) { for (AccountGroupMember m : args.db.get().accountGroupMembers()
.byGroup(g)) {
owners.add(m.getAccountId()); owners.add(m.getAccountId());
} }
} }
// BCC anyone who has interest in this project's changes // BCC anyone who has interest in this project's changes
// //
for (final AccountProjectWatch w : getProjectWatches()) { for (final AccountProjectWatch w : getWatches()) {
if (w.isNotifyNewChanges()) { if (w.isNotifyNewChanges()) {
if (owners.contains(w.getAccountId())) { if (owners.contains(w.getAccountId())) {
add(RecipientType.TO, w.getAccountId()); add(RecipientType.TO, w.getAccountId());
@@ -72,7 +69,6 @@ public class CreateChangeSender extends NewChangeSender {
} }
} }
} }
}
} catch (OrmException err) { } catch (OrmException err) {
// Just don't CC everyone. Better to send a partial message to those // 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 // we already have queued up then to fail deliver entirely to people
@@ -80,4 +76,3 @@ public class CreateChangeSender extends NewChangeSender {
} }
} }
} }
}

View File

@@ -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;
}
}

View File

@@ -25,8 +25,8 @@ public class MergeFailSender extends ReplyToChangeSender {
} }
@Inject @Inject
public MergeFailSender(@Assisted Change c) { public MergeFailSender(EmailArguments ea, @Assisted Change c) {
super(c, "comment"); super(ea, c, "comment");
} }
@Override @Override

View File

@@ -37,15 +37,14 @@ public class MergedSender extends ReplyToChangeSender {
public MergedSender create(Change change); public MergedSender create(Change change);
} }
private final ApprovalTypes approvalTypes;
private Branch.NameKey dest; private Branch.NameKey dest;
@Inject @Inject
private ApprovalTypes approvalTypes; public MergedSender(EmailArguments ea, ApprovalTypes at, @Assisted Change c) {
super(ea, c, "merged");
@Inject
public MergedSender(@Assisted Change c) {
super(c, "merged");
dest = c.getDest(); dest = c.getDest();
approvalTypes = at;
} }
public void setDest(final Branch.NameKey key) { public void setDest(final Branch.NameKey key) {
@@ -78,7 +77,7 @@ public class MergedSender extends ReplyToChangeSender {
} }
private void formatApprovals() { private void formatApprovals() {
if (db != null && patchSet != null) { if (patchSet != null) {
try { try {
final Map<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>> pos = final Map<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>> pos =
new HashMap<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>>(); new HashMap<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>>();
@@ -86,8 +85,8 @@ public class MergedSender extends ReplyToChangeSender {
final Map<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>> neg = final Map<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>> neg =
new HashMap<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>>(); new HashMap<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>>();
for (PatchSetApproval ca : db.patchSetApprovals().byPatchSet( for (PatchSetApproval ca : args.db.get().patchSetApprovals()
patchSet.getId())) { .byPatchSet(patchSet.getId())) {
if (ca.getValue() > 0) { if (ca.getValue() > 0) {
insert(pos, ca); insert(pos, ca);
} else if (ca.getValue() < 0) { } else if (ca.getValue() < 0) {
@@ -157,18 +156,14 @@ public class MergedSender extends ReplyToChangeSender {
} }
private void bccWatchesNotifySubmittedChanges() { private void bccWatchesNotifySubmittedChanges() {
if (db != null) {
try { try {
// BCC anyone else who has interest in this project's changes // BCC anyone else who has interest in this project's changes
// //
final ProjectState ps = getProjectState(); for (final AccountProjectWatch w : getWatches()) {
if (ps != null) {
for (final AccountProjectWatch w : getProjectWatches()) {
if (w.isNotifySubmittedChanges()) { if (w.isNotifySubmittedChanges()) {
add(RecipientType.BCC, w.getAccountId()); add(RecipientType.BCC, w.getAccountId());
} }
} }
}
} catch (OrmException err) { } catch (OrmException err) {
// Just don't CC everyone. Better to send a partial message to those // 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 // we already have queued up then to fail deliver entirely to people
@@ -176,4 +171,3 @@ public class MergedSender extends ReplyToChangeSender {
} }
} }
} }
}

View File

@@ -17,7 +17,6 @@ package com.google.gerrit.server.mail;
import com.google.gerrit.reviewdb.Account; import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.server.ssh.SshInfo; import com.google.gerrit.server.ssh.SshInfo;
import com.google.inject.Inject;
import com.jcraft.jsch.HostKey; import com.jcraft.jsch.HostKey;
@@ -29,14 +28,13 @@ import java.util.Set;
/** Sends an email alerting a user to a new change for them to review. */ /** Sends an email alerting a user to a new change for them to review. */
public abstract class NewChangeSender extends OutgoingEmail { public abstract class NewChangeSender extends OutgoingEmail {
@Inject private final SshInfo sshInfo;
private SshInfo sshInfo;
private final Set<Account.Id> reviewers = new HashSet<Account.Id>(); private final Set<Account.Id> reviewers = new HashSet<Account.Id>();
private final Set<Account.Id> extraCC = new HashSet<Account.Id>(); private final Set<Account.Id> extraCC = new HashSet<Account.Id>();
protected NewChangeSender(Change c) { protected NewChangeSender(EmailArguments ea, SshInfo sshInfo, Change c) {
super(c, "newchange"); super(ea, c, "newchange");
this.sshInfo = sshInfo;
} }
public void addReviewers(final Collection<Account.Id> cc) { public void addReviewers(final Collection<Account.Id> cc) {

View File

@@ -22,27 +22,20 @@ import com.google.gerrit.reviewdb.ChangeMessage;
import com.google.gerrit.reviewdb.PatchSet; import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval; import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.PatchSetInfo; 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.StarredChange;
import com.google.gerrit.reviewdb.UserIdentity; import com.google.gerrit.reviewdb.UserIdentity;
import com.google.gerrit.server.IdentifiedUser; 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.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.mail.EmailHeader.AddressList;
import com.google.gerrit.server.patch.PatchList; 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.PatchListEntry;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException; 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.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.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.eclipse.jgit.util.SystemReader; import org.eclipse.jgit.util.SystemReader;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -63,8 +56,6 @@ import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import javax.annotation.Nullable;
/** Sends an email to one or more interested parties. */ /** Sends an email to one or more interested parties. */
public abstract class OutgoingEmail { public abstract class OutgoingEmail {
private static final Logger log = LoggerFactory.getLogger(OutgoingEmail.class); private static final Logger log = LoggerFactory.getLogger(OutgoingEmail.class);
@@ -83,55 +74,24 @@ public abstract class OutgoingEmail {
private StringBuilder body; private StringBuilder body;
private boolean inFooter; private boolean inFooter;
protected final EmailArguments args;
protected Account.Id fromId; protected Account.Id fromId;
protected PatchSet patchSet; protected PatchSet patchSet;
protected PatchSetInfo patchSetInfo; protected PatchSetInfo patchSetInfo;
protected ChangeMessage changeMessage; 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 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; change = c;
messageClass = mc; messageClass = mc;
headers = new LinkedHashMap<String, EmailHeader>(); headers = new LinkedHashMap<String, EmailHeader>();
} }
protected OutgoingEmail(final String mc) { protected OutgoingEmail(EmailArguments ea, final String mc) {
this(null, mc); this(ea, null, mc);
} }
public void setFrom(final Account.Id id) { public void setFrom(final Account.Id id) {
@@ -151,17 +111,13 @@ public abstract class OutgoingEmail {
changeMessage = cm; changeMessage = cm;
} }
public void setReviewDb(final ReviewDb d) {
db = d;
}
/** /**
* Format and enqueue the message for delivery. * Format and enqueue the message for delivery.
* *
* @throws EmailException * @throws EmailException
*/ */
public void send() throws EmailException { public void send() throws EmailException {
if (!emailSender.isEnabled()) { if (!args.emailSender.isEnabled()) {
// Server has explicitly disabled email sending. // Server has explicitly disabled email sending.
// //
return; return;
@@ -171,7 +127,7 @@ public abstract class OutgoingEmail {
format(); format();
if (shouldSendMessage()) { if (shouldSendMessage()) {
if (fromId != null) { if (fromId != null) {
final Account fromUser = accountCache.get(fromId).getAccount(); final Account fromUser = args.accountCache.get(fromId).getAccount();
if (fromUser.getGeneralPreferences().isCopySelfOnEmails()) { if (fromUser.getGeneralPreferences().isCopySelfOnEmails()) {
// If we are impersonating a user, make sure they receive a CC of // If we are impersonating a user, make sure they receive a CC of
@@ -226,10 +182,9 @@ public abstract class OutgoingEmail {
appendText("Gerrit-Branch: " + change.getDest().getShortName() + "\n"); appendText("Gerrit-Branch: " + change.getDest().getShortName() + "\n");
appendText("Gerrit-Owner: " + getNameEmailFor(change.getOwner()) + "\n"); appendText("Gerrit-Owner: " + getNameEmailFor(change.getOwner()) + "\n");
if (db != null) {
try { try {
HashSet<Account.Id> reviewers = new HashSet<Account.Id>(); HashSet<Account.Id> reviewers = new HashSet<Account.Id>();
for (PatchSetApproval p : db.patchSetApprovals().byChange( for (PatchSetApproval p : args.db.get().patchSetApprovals().byChange(
change.getId())) { change.getId())) {
reviewers.add(p.getAccountId()); reviewers.add(p.getAccountId());
} }
@@ -245,7 +200,6 @@ public abstract class OutgoingEmail {
} catch (OrmException e) { } catch (OrmException e) {
} }
} }
}
if (headers.get("Message-ID").isEmpty()) { if (headers.get("Message-ID").isEmpty()) {
final StringBuilder rndid = new StringBuilder(); final StringBuilder rndid = new StringBuilder();
@@ -259,7 +213,7 @@ public abstract class OutgoingEmail {
setHeader("Message-ID", rndid.toString()); setHeader("Message-ID", rndid.toString());
} }
emailSender.send(smtpFromAddress, smtpRcptTo, headers, body.toString()); args.emailSender.send(smtpFromAddress, smtpRcptTo, headers, body.toString());
} }
} }
@@ -268,16 +222,18 @@ public abstract class OutgoingEmail {
/** Setup the message headers and envelope (TO, CC, BCC). */ /** Setup the message headers and envelope (TO, CC, BCC). */
protected void init() { protected void init() {
if (change != null && projectCache != null) { if (change != null && args.projectCache != null) {
projectState = projectCache.get(change.getProject()); changeData = new ChangeData(change);
projectState = args.projectCache.get(change.getProject());
projectName = projectName =
projectState != null ? projectState.getProject().getName() : null; projectState != null ? projectState.getProject().getName() : null;
} else { } else {
changeData = null;
projectState = null; projectState = null;
projectName = null; projectName = null;
} }
smtpFromAddress = fromAddressGenerator.from(fromId); smtpFromAddress = args.fromAddressGenerator.from(fromId);
if (changeMessage != null && changeMessage.getWrittenOn() != null) { if (changeMessage != null && changeMessage.getWrittenOn() != null) {
setHeader("Date", new Date(changeMessage.getWrittenOn().getTime())); setHeader("Date", new Date(changeMessage.getWrittenOn().getTime()));
} else { } else {
@@ -313,8 +269,8 @@ public abstract class OutgoingEmail {
body = new StringBuilder(); body = new StringBuilder();
inFooter = false; inFooter = false;
if (fromId != null && fromAddressGenerator.isGenericAddress(fromId)) { if (fromId != null && args.fromAddressGenerator.isGenericAddress(fromId)) {
final Account account = accountCache.get(fromId).getAccount(); final Account account = args.accountCache.get(fromId).getAccount();
final String name = account.getFullName(); final String name = account.getFullName();
final String email = account.getPreferredEmail(); final String email = account.getPreferredEmail();
@@ -331,10 +287,10 @@ public abstract class OutgoingEmail {
} }
} }
if (change != null && db != null) { if (change != null) {
if (patchSet == null) { if (patchSet == null) {
try { try {
patchSet = db.patchSets().get(change.currentPatchSetId()); patchSet = args.db.get().patchSets().get(change.currentPatchSetId());
} catch (OrmException err) { } catch (OrmException err) {
patchSet = null; patchSet = null;
} }
@@ -342,7 +298,7 @@ public abstract class OutgoingEmail {
if (patchSet != null && patchSetInfo == null) { if (patchSet != null && patchSetInfo == null) {
try { try {
patchSetInfo = patchSetInfoFactory.get(patchSet.getId()); patchSetInfo = args.patchSetInfoFactory.get(patchSet.getId());
} catch (PatchSetInfoNotAvailableException err) { } catch (PatchSetInfoNotAvailableException err) {
patchSetInfo = null; patchSetInfo = null;
} }
@@ -439,7 +395,7 @@ public abstract class OutgoingEmail {
} }
protected String getGerritUrl() { protected String getGerritUrl() {
return urlProvider.get(); return args.urlProvider.get();
} }
protected String getChangeMessageThreadId() { protected String getChangeMessageThreadId() {
@@ -521,7 +477,7 @@ public abstract class OutgoingEmail {
/** Get the patch list corresponding to this patch set. */ /** Get the patch list corresponding to this patch set. */
protected PatchList getPatchList() { protected PatchList getPatchList() {
if (patchSet != null) { if (patchSet != null) {
return patchListCache.get(change, patchSet); return args.patchListCache.get(change, patchSet);
} }
return null; return null;
} }
@@ -532,7 +488,7 @@ public abstract class OutgoingEmail {
return "Anonymous Coward"; return "Anonymous Coward";
} }
final Account userAccount = accountCache.get(accountId).getAccount(); final Account userAccount = args.accountCache.get(accountId).getAccount();
String name = userAccount.getFullName(); String name = userAccount.getFullName();
if (name == null) { if (name == null) {
name = userAccount.getPreferredEmail(); name = userAccount.getPreferredEmail();
@@ -544,7 +500,7 @@ public abstract class OutgoingEmail {
} }
private String getNameEmailFor(Account.Id accountId) { private String getNameEmailFor(Account.Id accountId) {
AccountState who = accountCache.get(accountId); AccountState who = args.accountCache.get(accountId);
String name = who.getAccount().getFullName(); String name = who.getAccount().getFullName();
String email = who.getAccount().getPreferredEmail(); String email = who.getAccount().getPreferredEmail();
@@ -594,7 +550,7 @@ public abstract class OutgoingEmail {
protected Set<AccountGroup.Id> getProjectOwners() { protected Set<AccountGroup.Id> getProjectOwners() {
final ProjectState r; final ProjectState r;
r = projectCache.get(change.getProject()); r = args.projectCache.get(change.getProject());
return r != null ? r.getOwners() : Collections.<AccountGroup.Id> emptySet(); return r != null ? r.getOwners() : Collections.<AccountGroup.Id> emptySet();
} }
@@ -625,11 +581,11 @@ public abstract class OutgoingEmail {
/** BCC any user who has starred this change. */ /** BCC any user who has starred this change. */
protected void bccStarredBy() { protected void bccStarredBy() {
if (db != null) {
try { try {
// BCC anyone who has starred this change. // BCC anyone who has starred this change.
// //
for (StarredChange w : db.starredChanges().byChange(change.getId())) { for (StarredChange w : args.db.get().starredChanges().byChange(
change.getId())) {
add(RecipientType.BCC, w.getAccountId()); add(RecipientType.BCC, w.getAccountId());
} }
} catch (OrmException err) { } catch (OrmException err) {
@@ -638,49 +594,69 @@ public abstract class OutgoingEmail {
// who have a lower interest in the change. // who have a lower interest in the change.
} }
} }
}
/** BCC any user who has set "notify all comments" on this project. */ /** BCC any user who has set "notify all comments" on this project. */
protected void bccWatchesNotifyAllComments() { protected void bccWatchesNotifyAllComments() {
if (db != null) {
try { try {
// BCC anyone else who has interest in this project's changes // BCC anyone else who has interest in this project's changes
// //
final ProjectState ps = getProjectState(); for (final AccountProjectWatch w : getWatches()) {
if (ps != null) {
for (final AccountProjectWatch w : getProjectWatches()) {
if (w.isNotifyAllComments()) { if (w.isNotifyAllComments()) {
add(RecipientType.BCC, w.getAccountId()); add(RecipientType.BCC, w.getAccountId());
} }
} }
}
} catch (OrmException err) { } catch (OrmException err) {
// Just don't CC everyone. Better to send a partial message to those // 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 // we already have queued up then to fail deliver entirely to people
// who have a lower interest in the change. // who have a lower interest in the change.
} }
} }
/** Returns all watches that are relevant */
protected final List<AccountProjectWatch> getWatches() throws OrmException {
if (changeData == null) {
return Collections.emptyList();
} }
/** Returns all watches that are relevant for this project */ List<AccountProjectWatch> matching = new ArrayList<AccountProjectWatch>();
final protected Set<AccountProjectWatch> getProjectWatches() throws OrmException { Set<Account.Id> projectWatchers = new HashSet<Account.Id>();
final Set<AccountProjectWatch> projectWatches = new HashSet<AccountProjectWatch>();
final Set<Account.Id> projectWatchers = new HashSet<Account.Id>(); for (AccountProjectWatch w : args.db.get().accountProjectWatches()
final ProjectState ps = getProjectState(); .byProject(change.getProject())) {
if (ps != null) {
for (final AccountProjectWatch w : db.accountProjectWatches().byProject(ps.getProject().getNameKey())) {
projectWatches.add(w);
projectWatchers.add(w.getAccountId()); projectWatchers.add(w.getAccountId());
add(matching, w);
} }
}
for (final AccountProjectWatch w : db.accountProjectWatches().byProject(wildProject)) { for (AccountProjectWatch w : args.db.get().accountProjectWatches()
.byProject(args.wildProject)) {
if (!projectWatchers.contains(w.getAccountId())) { if (!projectWatchers.contains(w.getAccountId())) {
// the all projects watch settings are only relevant if the user did not configure add(matching, w);
// any specific rules for the concrete project
projectWatches.add(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. */ /** Any user who has published comments on this change. */
@@ -694,11 +670,10 @@ public abstract class OutgoingEmail {
} }
private void ccApprovals(final boolean includeZero) { private void ccApprovals(final boolean includeZero) {
if (db != null) {
try { try {
// CC anyone else who has posted an approval mark on this change // CC anyone else who has posted an approval mark on this change
// //
for (PatchSetApproval ap : db.patchSetApprovals().byChange( for (PatchSetApproval ap : args.db.get().patchSetApprovals().byChange(
change.getId())) { change.getId())) {
if (!includeZero && ap.getValue() == 0) { if (!includeZero && ap.getValue() == 0) {
continue; continue;
@@ -708,7 +683,6 @@ public abstract class OutgoingEmail {
} catch (OrmException err) { } catch (OrmException err) {
} }
} }
}
/** Schedule delivery of this message to the given account. */ /** Schedule delivery of this message to the given account. */
protected void add(final RecipientType rt, final Account.Id to) { protected void add(final RecipientType rt, final Account.Id to) {
@@ -721,14 +695,14 @@ public abstract class OutgoingEmail {
private boolean isVisibleTo(final Account.Id to) { private boolean isVisibleTo(final Account.Id to) {
return projectState == null return projectState == null
|| change == null || change == null
|| projectState.controlFor(identifiedUserFactory.create(to)) || projectState.controlFor(args.identifiedUserFactory.create(to))
.controlFor(change).isVisible(); .controlFor(change).isVisible();
} }
/** Schedule delivery of this message to the given account. */ /** Schedule delivery of this message to the given account. */
protected void add(final RecipientType rt, final Address addr) { protected void add(final RecipientType rt, final Address addr) {
if (addr != null && addr.email != null && addr.email.length() > 0) { if (addr != null && addr.email != null && addr.email.length() > 0) {
if (emailSender.canEmail(addr.email)) { if (args.emailSender.canEmail(addr.email)) {
smtpRcptTo.add(addr); smtpRcptTo.add(addr);
switch (rt) { switch (rt) {
case TO: case TO:
@@ -745,7 +719,7 @@ public abstract class OutgoingEmail {
} }
private Address toAddress(final Account.Id id) { 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(); final String e = a.getPreferredEmail();
if (e == null) { if (e == null) {
return null; return null;

View File

@@ -28,14 +28,14 @@ public class RegisterNewEmailSender extends OutgoingEmail {
public RegisterNewEmailSender create(String address); public RegisterNewEmailSender create(String address);
} }
private final AuthConfig authConfig;
private final String addr; private final String addr;
@Inject @Inject
private AuthConfig authConfig; public RegisterNewEmailSender(EmailArguments ea, AuthConfig ac,
@Assisted final String address) {
@Inject super(ea, "registernewemail");
public RegisterNewEmailSender(@Assisted final String address) { authConfig = ac;
super("registernewemail");
addr = address; addr = address;
} }

View File

@@ -34,15 +34,14 @@ public class ReplacePatchSetSender extends ReplyToChangeSender {
public ReplacePatchSetSender create(Change change); public ReplacePatchSetSender create(Change change);
} }
@Inject
private SshInfo sshInfo;
private final Set<Account.Id> reviewers = new HashSet<Account.Id>(); private final Set<Account.Id> reviewers = new HashSet<Account.Id>();
private final Set<Account.Id> extraCC = new HashSet<Account.Id>(); private final Set<Account.Id> extraCC = new HashSet<Account.Id>();
private final SshInfo sshInfo;
@Inject @Inject
public ReplacePatchSetSender(@Assisted Change c) { public ReplacePatchSetSender(EmailArguments ea, SshInfo si, @Assisted Change c) {
super(c, "newpatchset"); super(ea, c, "newpatchset");
sshInfo = si;
} }
public void addReviewers(final Collection<Account.Id> cc) { public void addReviewers(final Collection<Account.Id> cc) {

View File

@@ -18,8 +18,8 @@ import com.google.gerrit.reviewdb.Change;
/** Alert a user to a reply to a change, usually commentary made during review. */ /** Alert a user to a reply to a change, usually commentary made during review. */
public abstract class ReplyToChangeSender extends OutgoingEmail { public abstract class ReplyToChangeSender extends OutgoingEmail {
protected ReplyToChangeSender(Change c, String mc) { protected ReplyToChangeSender(EmailArguments ea, Change c, String mc) {
super(c, mc); super(ea, c, mc);
} }
@Override @Override

View File

@@ -284,7 +284,6 @@ public class PublishComments implements Callable<VoidResult> {
cm.setPatchSet(patchSet, patchSetInfoFactory.get(patchSetId)); cm.setPatchSet(patchSet, patchSetInfoFactory.get(patchSetId));
cm.setChangeMessage(message); cm.setChangeMessage(message);
cm.setPatchLineComments(drafts); cm.setPatchLineComments(drafts);
cm.setReviewDb(db);
cm.send(); cm.send();
} catch (EmailException e) { } catch (EmailException e) {
log.error("Cannot send comments by email for patch set " + patchSetId, e); log.error("Cannot send comments by email for patch set " + patchSetId, e);

View File

@@ -26,8 +26,6 @@ import static com.google.gerrit.server.query.QueryParser.OR;
import static com.google.gerrit.server.query.QueryParser.SINGLE_WORD; import static com.google.gerrit.server.query.QueryParser.SINGLE_WORD;
import static com.google.gerrit.server.query.QueryParser.VARIABLE_ASSIGN; 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 org.antlr.runtime.tree.Tree;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
@@ -239,6 +237,29 @@ public abstract class QueryBuilder<T> {
throw error("Unsupported query:" + value); throw error("Unsupported query:" + value);
} }
/**
* Locate a predicate in the predicate tree.
*
* @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. * Locate a predicate in the predicate tree.
* *

View File

@@ -35,7 +35,7 @@ import com.google.gerrit.server.query.QueryParseException;
import com.google.gwtorm.client.OrmException; import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.servlet.RequestScoped; import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AbbreviatedObjectId;
@@ -46,7 +46,6 @@ import java.util.regex.Pattern;
/** /**
* Parses a query string meant to be applied to change objects. * Parses a query string meant to be applied to change objects.
*/ */
@RequestScoped
public class ChangeQueryBuilder extends QueryBuilder<ChangeData> { public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
private static final Pattern PAT_LEGACY_ID = Pattern.compile("^[1-9][0-9]*$"); private static final Pattern PAT_LEGACY_ID = Pattern.compile("^[1-9][0-9]*$");
private static final Pattern PAT_CHANGE_ID = private static final Pattern PAT_CHANGE_ID =
@@ -86,27 +85,27 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
new QueryBuilder.Definition<ChangeData, ChangeQueryBuilder>( new QueryBuilder.Definition<ChangeData, ChangeQueryBuilder>(
ChangeQueryBuilder.class); ChangeQueryBuilder.class);
private final Provider<ReviewDb> dbProvider; static class Arguments {
private final Provider<CurrentUser> currentUser; final Provider<ReviewDb> dbProvider;
private final IdentifiedUser.GenericFactory userFactory; final Provider<ChangeQueryRewriter> rewriter;
private final ChangeControl.Factory changeControlFactory; final IdentifiedUser.GenericFactory userFactory;
private final AccountResolver accountResolver; final ChangeControl.Factory changeControlFactory;
private final GroupCache groupCache; final AccountResolver accountResolver;
private final AuthConfig authConfig; final GroupCache groupCache;
private final ApprovalTypes approvalTypes; final AuthConfig authConfig;
private final Project.NameKey wildProjectName; final ApprovalTypes approvalTypes;
final Project.NameKey wildProjectName;
@Inject @Inject
ChangeQueryBuilder(Provider<ReviewDb> dbProvider, Arguments(Provider<ReviewDb> dbProvider,
Provider<CurrentUser> currentUser, Provider<ChangeQueryRewriter> rewriter,
IdentifiedUser.GenericFactory userFactory, IdentifiedUser.GenericFactory userFactory,
ChangeControl.Factory changeControlFactory, ChangeControl.Factory changeControlFactory,
AccountResolver accountResolver, GroupCache groupCache, AccountResolver accountResolver, GroupCache groupCache,
AuthConfig authConfig, ApprovalTypes approvalTypes, AuthConfig authConfig, ApprovalTypes approvalTypes,
@WildProjectName Project.NameKey wildProjectName) { @WildProjectName Project.NameKey wildProjectName) {
super(mydef);
this.dbProvider = dbProvider; this.dbProvider = dbProvider;
this.currentUser = currentUser; this.rewriter = rewriter;
this.userFactory = userFactory; this.userFactory = userFactory;
this.changeControlFactory = changeControlFactory; this.changeControlFactory = changeControlFactory;
this.accountResolver = accountResolver; this.accountResolver = accountResolver;
@@ -115,26 +114,38 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
this.approvalTypes = approvalTypes; this.approvalTypes = approvalTypes;
this.wildProjectName = wildProjectName; this.wildProjectName = wildProjectName;
} }
}
Provider<ReviewDb> getReviewDbProvider() { public interface Factory {
return dbProvider; 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 @Operator
public Predicate<ChangeData> age(String value) { public Predicate<ChangeData> age(String value) {
return new AgePredicate(dbProvider, value); return new AgePredicate(args.dbProvider, value);
} }
@Operator @Operator
public Predicate<ChangeData> change(String query) { public Predicate<ChangeData> change(String query) {
if (PAT_LEGACY_ID.matcher(query).matches()) { 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()) { } else if (PAT_CHANGE_ID.matcher(query).matches()) {
if (query.charAt(0) == 'i') { if (query.charAt(0) == 'i') {
query = "I" + query.substring(1); query = "I" + query.substring(1);
} }
return new ChangeIdPredicate(dbProvider, query); return new ChangeIdPredicate(args.dbProvider, query);
} }
throw new IllegalArgumentException(); throw new IllegalArgumentException();
@@ -146,30 +157,30 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
return status_open(); return status_open();
} else if ("closed".equals(statusName)) { } else if ("closed".equals(statusName)) {
return ChangeStatusPredicate.closed(dbProvider); return ChangeStatusPredicate.closed(args.dbProvider);
} else if ("reviewed".equalsIgnoreCase(statusName)) { } else if ("reviewed".equalsIgnoreCase(statusName)) {
return new IsReviewedPredicate(dbProvider); return new IsReviewedPredicate(args.dbProvider);
} else { } else {
return new ChangeStatusPredicate(dbProvider, statusName); return new ChangeStatusPredicate(args.dbProvider, statusName);
} }
} }
public Predicate<ChangeData> status_open() { public Predicate<ChangeData> status_open() {
return ChangeStatusPredicate.open(dbProvider); return ChangeStatusPredicate.open(args.dbProvider);
} }
@Operator @Operator
public Predicate<ChangeData> has(String value) { public Predicate<ChangeData> has(String value) {
if ("star".equalsIgnoreCase(value)) { if ("star".equalsIgnoreCase(value)) {
return new IsStarredByPredicate(dbProvider, currentUser.get()); return new IsStarredByPredicate(args.dbProvider, currentUser);
} }
if ("draft".equalsIgnoreCase(value)) { if ("draft".equalsIgnoreCase(value)) {
if (currentUser.get() instanceof IdentifiedUser) { if (currentUser instanceof IdentifiedUser) {
return new HasDraftByPredicate(dbProvider, return new HasDraftByPredicate(args.dbProvider,
((IdentifiedUser) currentUser.get()).getAccountId()); ((IdentifiedUser) currentUser).getAccountId());
} }
} }
@@ -179,21 +190,19 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
@Operator @Operator
public Predicate<ChangeData> is(String value) { public Predicate<ChangeData> is(String value) {
if ("starred".equalsIgnoreCase(value)) { if ("starred".equalsIgnoreCase(value)) {
return new IsStarredByPredicate(dbProvider, currentUser.get()); return new IsStarredByPredicate(args.dbProvider, currentUser);
} }
if ("watched".equalsIgnoreCase(value)) { if ("watched".equalsIgnoreCase(value)) {
return new IsWatchedByPredicate(dbProvider, wildProjectName, // return new IsWatchedByPredicate(args, currentUser);
currentUser.get());
} }
if ("visible".equalsIgnoreCase(value)) { if ("visible".equalsIgnoreCase(value)) {
return new IsVisibleToPredicate(dbProvider, changeControlFactory, return is_visible();
currentUser.get());
} }
if ("reviewed".equalsIgnoreCase(value)) { if ("reviewed".equalsIgnoreCase(value)) {
return new IsReviewedPredicate(dbProvider); return new IsReviewedPredicate(args.dbProvider);
} }
try { try {
@@ -207,121 +216,129 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
@Operator @Operator
public Predicate<ChangeData> commit(String id) { public Predicate<ChangeData> commit(String id) {
return new CommitPredicate(dbProvider, AbbreviatedObjectId.fromString(id)); return new CommitPredicate(args.dbProvider, AbbreviatedObjectId
.fromString(id));
} }
@Operator @Operator
public Predicate<ChangeData> project(String name) { public Predicate<ChangeData> project(String name) {
return new ProjectPredicate(dbProvider, name); return new ProjectPredicate(args.dbProvider, name);
} }
@Operator @Operator
public Predicate<ChangeData> branch(String name) { public Predicate<ChangeData> branch(String name) {
return new BranchPredicate(dbProvider, name); return new BranchPredicate(args.dbProvider, name);
} }
@Operator @Operator
public Predicate<ChangeData> topic(String name) { public Predicate<ChangeData> topic(String name) {
return new TopicPredicate(dbProvider, name); return new TopicPredicate(args.dbProvider, name);
} }
@Operator @Operator
public Predicate<ChangeData> ref(String ref) { public Predicate<ChangeData> ref(String ref) {
return new RefPredicate(dbProvider, ref); return new RefPredicate(args.dbProvider, ref);
} }
@Operator @Operator
public Predicate<ChangeData> label(String name) { public Predicate<ChangeData> label(String name) {
return new LabelPredicate(dbProvider, approvalTypes, name); return new LabelPredicate(args.dbProvider, args.approvalTypes, name);
} }
@Operator @Operator
public Predicate<ChangeData> starredby(String who) public Predicate<ChangeData> starredby(String who)
throws QueryParseException, OrmException { throws QueryParseException, OrmException {
Account account = accountResolver.find(who); Account account = args.accountResolver.find(who);
if (account == null) { if (account == null) {
throw error("User " + who + " not found"); throw error("User " + who + " not found");
} }
return new IsStarredByPredicate(dbProvider, // return new IsStarredByPredicate(args.dbProvider, //
userFactory.create(dbProvider, account.getId())); args.userFactory.create(args.dbProvider, account.getId()));
} }
@Operator @Operator
public Predicate<ChangeData> watchedby(String who) public Predicate<ChangeData> watchedby(String who)
throws QueryParseException, OrmException { throws QueryParseException, OrmException {
Account account = accountResolver.find(who); Account account = args.accountResolver.find(who);
if (account == null) { if (account == null) {
throw error("User " + who + " not found"); throw error("User " + who + " not found");
} }
return new IsWatchedByPredicate(dbProvider, wildProjectName, // return new IsWatchedByPredicate(args, args.userFactory.create(
userFactory.create(dbProvider, account.getId())); args.dbProvider, account.getId()));
} }
@Operator @Operator
public Predicate<ChangeData> draftby(String who) throws QueryParseException, public Predicate<ChangeData> draftby(String who) throws QueryParseException,
OrmException { OrmException {
Account account = accountResolver.find(who); Account account = args.accountResolver.find(who);
if (account == null) { if (account == null) {
throw error("User " + who + " not found"); throw error("User " + who + " not found");
} }
return new HasDraftByPredicate(dbProvider, account.getId()); return new HasDraftByPredicate(args.dbProvider, account.getId());
} }
@Operator @Operator
public Predicate<ChangeData> visibleto(String who) public Predicate<ChangeData> visibleto(String who)
throws QueryParseException, OrmException { throws QueryParseException, OrmException {
Account account = accountResolver.find(who); Account account = args.accountResolver.find(who);
if (account != null) { 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? // 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) { if (g != null) {
return visibleto(new SingleGroupUser(authConfig, g.getId())); return visibleto(new SingleGroupUser(args.authConfig, g.getId()));
} }
Collection<AccountGroup> matches = Collection<AccountGroup> matches =
groupCache.get(new AccountGroup.ExternalNameKey(who)); args.groupCache.get(new AccountGroup.ExternalNameKey(who));
if (matches != null && !matches.isEmpty()) { if (matches != null && !matches.isEmpty()) {
HashSet<AccountGroup.Id> ids = new HashSet<AccountGroup.Id>(); HashSet<AccountGroup.Id> ids = new HashSet<AccountGroup.Id>();
for (AccountGroup group : matches) { for (AccountGroup group : matches) {
ids.add(group.getId()); 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 + "\"."); throw error("No user or group matches \"" + who + "\".");
} }
public Predicate<ChangeData> visibleto(CurrentUser user) { 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 @Operator
public Predicate<ChangeData> owner(String who) throws QueryParseException, public Predicate<ChangeData> owner(String who) throws QueryParseException,
OrmException { OrmException {
Account account = accountResolver.find(who); Account account = args.accountResolver.find(who);
if (account == null) { if (account == null) {
throw error("User " + who + " not found"); throw error("User " + who + " not found");
} }
return new OwnerPredicate(dbProvider, account.getId()); return new OwnerPredicate(args.dbProvider, account.getId());
} }
@Operator @Operator
public Predicate<ChangeData> reviewer(String nameOrEmail) public Predicate<ChangeData> reviewer(String nameOrEmail)
throws QueryParseException, OrmException { throws QueryParseException, OrmException {
Account account = accountResolver.find(nameOrEmail); Account account = args.accountResolver.find(nameOrEmail);
if (account == null) { if (account == null) {
throw error("Reviewer " + nameOrEmail + " not found"); throw error("Reviewer " + nameOrEmail + " not found");
} }
return new ReviewerPredicate(dbProvider, account.getId()); return new ReviewerPredicate(args.dbProvider, account.getId());
} }
@Operator @Operator
public Predicate<ChangeData> tr(String trackingId) { public Predicate<ChangeData> tr(String trackingId) {
return new TrackingIdPredicate(dbProvider, trackingId); return new TrackingIdPredicate(args.dbProvider, trackingId);
} }
@Operator @Operator
@@ -350,12 +367,12 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
@Operator @Operator
public Predicate<ChangeData> sortkey_after(String sortKey) { public Predicate<ChangeData> sortkey_after(String sortKey) {
return new SortKeyPredicate.After(dbProvider, sortKey); return new SortKeyPredicate.After(args.dbProvider, sortKey);
} }
@Operator @Operator
public Predicate<ChangeData> sortkey_before(String sortKey) { public Predicate<ChangeData> sortkey_before(String sortKey) {
return new SortKeyPredicate.Before(dbProvider, sortKey); return new SortKeyPredicate.Before(args.dbProvider, sortKey);
} }
@Operator @Operator

View File

@@ -18,7 +18,6 @@ import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ChangeAccess; import com.google.gerrit.reviewdb.ChangeAccess;
import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.ChangeUtil; 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.IntPredicate;
import com.google.gerrit.server.query.Predicate; import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryRewriter; import com.google.gerrit.server.query.QueryRewriter;
@@ -29,18 +28,18 @@ import com.google.inject.Inject;
import com.google.inject.OutOfScopeException; import com.google.inject.OutOfScopeException;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.name.Named; import com.google.inject.name.Named;
import com.google.inject.servlet.RequestScoped;
import java.util.Collection; import java.util.Collection;
@RequestScoped
public class ChangeQueryRewriter extends QueryRewriter<ChangeData> { public class ChangeQueryRewriter extends QueryRewriter<ChangeData> {
private static final QueryRewriter.Definition<ChangeData, ChangeQueryRewriter> mydef = private static final QueryRewriter.Definition<ChangeData, ChangeQueryRewriter> mydef =
new QueryRewriter.Definition<ChangeData, ChangeQueryRewriter>( new QueryRewriter.Definition<ChangeData, ChangeQueryRewriter>(
ChangeQueryRewriter.class, new ChangeQueryBuilder( ChangeQueryRewriter.class, new ChangeQueryBuilder(
new InvalidProvider<ReviewDb>(), new ChangeQueryBuilder.Arguments( //
new InvalidProvider<CurrentUser>(), // new InvalidProvider<ReviewDb>(), //
null, null, null, null, null, null, null)); new InvalidProvider<ChangeQueryRewriter>(), //
null, null, null, null, null, null, null),
null));
private final Provider<ReviewDb> dbProvider; private final Provider<ReviewDb> dbProvider;

View File

@@ -14,18 +14,20 @@
package com.google.gerrit.server.query.change; package com.google.gerrit.server.query.change;
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Project; 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.CurrentUser;
import com.google.gerrit.server.IdentifiedUser; 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.OperatorPredicate;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gwtorm.client.OrmException; 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> { class IsWatchedByPredicate extends OperatorPredicate<ChangeData> {
private static String describe(CurrentUser user) { private static String describe(CurrentUser user) {
@@ -35,32 +37,81 @@ class IsWatchedByPredicate extends OperatorPredicate<ChangeData> {
return user.toString(); return user.toString();
} }
private final Provider<ReviewDb> db; private final ChangeQueryBuilder.Arguments args;
private final Project.NameKey wildProject;
private final CurrentUser user; private final CurrentUser user;
IsWatchedByPredicate(Provider<ReviewDb> db, private Map<Project.NameKey, List<Predicate<ChangeData>>> rules;
@WildProjectName Project.NameKey wildProject, CurrentUser user) {
IsWatchedByPredicate(ChangeQueryBuilder.Arguments args, CurrentUser user) {
super(ChangeQueryBuilder.FIELD_WATCHEDBY, describe(user)); super(ChangeQueryBuilder.FIELD_WATCHEDBY, describe(user));
this.db = db; this.args = args;
this.wildProject = wildProject;
this.user = user; this.user = user;
} }
@Override @Override
public boolean match(final ChangeData cd) throws OrmException { public boolean match(final ChangeData cd) throws OrmException {
Set<NameKey> watched = user.getWatchedProjects(); if (rules == null) {
if (watched.contains(wildProject)) { ChangeQueryBuilder builder = new ChangeQueryBuilder(args, user);
return true; 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);
} }
Change change = cd.change(db); Predicate<ChangeData> p = compile(builder, w);
if (p != null) {
list.add(p);
}
}
}
if (rules.isEmpty()) {
return false;
}
Change change = cd.change(args.dbProvider);
if (change == null) { if (change == null) {
return false; return false;
} }
Project.NameKey project = change.getDest().getParentKey(); 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 @Override

View File

@@ -60,7 +60,6 @@ public class QueryProcessor {
private final SimpleDateFormat sdf = private final SimpleDateFormat sdf =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss zzz"); new SimpleDateFormat("yyyy-MM-dd HH:mm:ss zzz");
private final CurrentUser currentUser;
private final EventFactory eventFactory; private final EventFactory eventFactory;
private final ChangeQueryBuilder queryBuilder; private final ChangeQueryBuilder queryBuilder;
private final ChangeQueryRewriter queryRewriter; private final ChangeQueryRewriter queryRewriter;
@@ -75,12 +74,11 @@ public class QueryProcessor {
private PrintWriter out; private PrintWriter out;
@Inject @Inject
QueryProcessor(CurrentUser currentUser, EventFactory eventFactory, QueryProcessor(EventFactory eventFactory,
ChangeQueryBuilder queryBuilder, ChangeQueryRewriter queryRewriter, ChangeQueryBuilder.Factory queryBuilder, CurrentUser currentUser,
Provider<ReviewDb> db) { ChangeQueryRewriter queryRewriter, Provider<ReviewDb> db) {
this.currentUser = currentUser;
this.eventFactory = eventFactory; this.eventFactory = eventFactory;
this.queryBuilder = queryBuilder; this.queryBuilder = queryBuilder.create(currentUser);
this.queryRewriter = queryRewriter; this.queryRewriter = queryRewriter;
this.db = db; this.db = db;
} }
@@ -107,9 +105,7 @@ public class QueryProcessor {
final QueryStats stats = new QueryStats(); final QueryStats stats = new QueryStats();
stats.runTimeMilliseconds = System.currentTimeMillis(); stats.runTimeMilliseconds = System.currentTimeMillis();
final Predicate<ChangeData> visibleToMe = final Predicate<ChangeData> visibleToMe = queryBuilder.is_visible();
queryBuilder.visibleto(currentUser);
Predicate<ChangeData> s = compileQuery(queryString, visibleToMe); Predicate<ChangeData> s = compileQuery(queryString, visibleToMe);
List<ChangeData> results = new ArrayList<ChangeData>(); List<ChangeData> results = new ArrayList<ChangeData>();
HashSet<Change.Id> want = new HashSet<Change.Id>(); HashSet<Change.Id> want = new HashSet<Change.Id>();

View File

@@ -15,12 +15,14 @@
package com.google.gerrit.server.query.change; package com.google.gerrit.server.query.change;
import com.google.gerrit.reviewdb.AccountGroup; import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Project; import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.server.AccessPath; import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.AuthConfig; import com.google.gerrit.server.config.AuthConfig;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
@@ -47,7 +49,7 @@ final class SingleGroupUser extends CurrentUser {
} }
@Override @Override
public Set<Project.NameKey> getWatchedProjects() { public Collection<AccountProjectWatch> getNotificationFilters() {
return Collections.emptySet(); return Collections.emptySet();
} }
} }

View File

@@ -32,7 +32,7 @@ import java.util.List;
/** A version of the database schema. */ /** A version of the database schema. */
public abstract class SchemaVersion { public abstract class SchemaVersion {
/** The current schema version. */ /** 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 { public static class Module extends AbstractModule {
@Override @Override

View File

@@ -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();
}
}
}

View File

@@ -25,6 +25,7 @@ import com.google.gerrit.reviewdb.RevId;
import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.ChangeUtil; import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser; 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.git.MergeQueue;
import com.google.gerrit.server.patch.PublishComments; import com.google.gerrit.server.patch.PublishComments;
import com.google.gerrit.server.project.CanSubmitResult; import com.google.gerrit.server.project.CanSubmitResult;
@@ -95,6 +96,9 @@ public class ReviewCommand extends BaseCommand {
@Inject @Inject
private MergeQueue merger; private MergeQueue merger;
@Inject
private MergeOp.Factory opFactory;
@Inject @Inject
private ApprovalTypes approvalTypes; private ApprovalTypes approvalTypes;
@@ -166,7 +170,7 @@ public class ReviewCommand extends BaseCommand {
changeControl.canSubmit(patchSetId, db, approvalTypes, changeControl.canSubmit(patchSetId, db, approvalTypes,
functionStateFactory); functionStateFactory);
if (result == CanSubmitResult.OK) { if (result == CanSubmitResult.OK) {
ChangeUtil.submit(patchSetId, currentUser, db, merger); ChangeUtil.submit(opFactory, patchSetId, currentUser, db, merger);
} else { } else {
throw error(result.getMessage()); throw error(result.getMessage());
} }