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:
parent
14760b7c0e
commit
0f42fc05a4
@ -48,7 +48,7 @@ public interface AccountService extends RemoteJsonService {
|
||||
void myProjectWatch(AsyncCallback<List<AccountProjectWatchInfo>> callback);
|
||||
|
||||
@SignInRequired
|
||||
void addProjectWatch(String projectName,
|
||||
void addProjectWatch(String projectName, String filter,
|
||||
AsyncCallback<AccountProjectWatchInfo> callback);
|
||||
|
||||
@SignInRequired
|
||||
|
@ -32,6 +32,7 @@ public interface GerritCss extends CssResource {
|
||||
String removeReviewer();
|
||||
String removeReviewerCell();
|
||||
String addSshKeyPanel();
|
||||
String addWatchPanel();
|
||||
String approvalCategoryList();
|
||||
String approvalTable();
|
||||
String approvalhint();
|
||||
@ -180,4 +181,5 @@ public interface GerritCss extends CssResource {
|
||||
String useridentity();
|
||||
String usernameField();
|
||||
String version();
|
||||
String watchedProjectFilter();
|
||||
}
|
||||
|
@ -80,6 +80,9 @@ public interface AccountConstants extends Constants {
|
||||
|
||||
String buttonWatchProject();
|
||||
String defaultProjectName();
|
||||
String defaultFilter();
|
||||
String watchedProjectName();
|
||||
String watchedProjectFilter();
|
||||
String watchedProjectColumnEmailNotifications();
|
||||
String watchedProjectColumnNewChanges();
|
||||
String watchedProjectColumnAllComments();
|
||||
|
@ -61,6 +61,9 @@ sshJavaAppletNotAvailable = Open Key Unavailable: Java not enabled
|
||||
|
||||
buttonWatchProject = Watch
|
||||
defaultProjectName = Project Name
|
||||
defaultFilter = branch:name, or other search expression
|
||||
watchedProjectName = Project Name
|
||||
watchedProjectFilter = Only If
|
||||
watchedProjectColumnEmailNotifications = Email Notifications
|
||||
watchedProjectColumnNewChanges = New Changes
|
||||
watchedProjectColumnAllComments = All Comments
|
||||
|
@ -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.CheckBox;
|
||||
import com.google.gwt.user.client.ui.FlowPanel;
|
||||
import com.google.gwt.user.client.ui.Grid;
|
||||
import com.google.gwt.user.client.ui.Label;
|
||||
import com.google.gwt.user.client.ui.SuggestBox;
|
||||
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
|
||||
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
|
||||
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
|
||||
import com.google.gwtexpui.globalkey.client.NpTextBox;
|
||||
import com.google.gwtjsonrpc.client.VoidResult;
|
||||
@ -51,7 +54,9 @@ public class MyWatchedProjectsScreen extends SettingsScreen {
|
||||
private WatchTable watches;
|
||||
|
||||
private Button addNew;
|
||||
private NpTextBox nameBox;
|
||||
private SuggestBox nameTxt;
|
||||
private NpTextBox filterTxt;
|
||||
private Button delSel;
|
||||
private boolean submitOnSelection;
|
||||
|
||||
@ -60,32 +65,30 @@ public class MyWatchedProjectsScreen extends SettingsScreen {
|
||||
super.onInitUI();
|
||||
|
||||
{
|
||||
final FlowPanel fp = new FlowPanel();
|
||||
|
||||
final NpTextBox box = new NpTextBox();
|
||||
nameTxt = new SuggestBox(new ProjectNameSuggestOracle(), box);
|
||||
box.setVisibleLength(50);
|
||||
box.setText(Util.C.defaultProjectName());
|
||||
box.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
|
||||
box.addFocusHandler(new FocusHandler() {
|
||||
nameBox = new NpTextBox();
|
||||
nameTxt = new SuggestBox(new ProjectNameSuggestOracle(), nameBox);
|
||||
nameBox.setVisibleLength(50);
|
||||
nameBox.setText(Util.C.defaultProjectName());
|
||||
nameBox.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
|
||||
nameBox.addFocusHandler(new FocusHandler() {
|
||||
@Override
|
||||
public void onFocus(FocusEvent event) {
|
||||
if (Util.C.defaultProjectName().equals(box.getText())) {
|
||||
box.setText("");
|
||||
box.removeStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
|
||||
if (Util.C.defaultProjectName().equals(nameBox.getText())) {
|
||||
nameBox.setText("");
|
||||
nameBox.removeStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
|
||||
}
|
||||
}
|
||||
});
|
||||
box.addBlurHandler(new BlurHandler() {
|
||||
nameBox.addBlurHandler(new BlurHandler() {
|
||||
@Override
|
||||
public void onBlur(BlurEvent event) {
|
||||
if ("".equals(box.getText())) {
|
||||
box.setText(Util.C.defaultProjectName());
|
||||
box.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
|
||||
if ("".equals(nameBox.getText())) {
|
||||
nameBox.setText(Util.C.defaultProjectName());
|
||||
nameBox.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
|
||||
}
|
||||
}
|
||||
});
|
||||
box.addKeyPressHandler(new KeyPressHandler() {
|
||||
nameBox.addKeyPressHandler(new KeyPressHandler() {
|
||||
@Override
|
||||
public void onKeyPress(KeyPressEvent event) {
|
||||
submitOnSelection = false;
|
||||
@ -108,7 +111,37 @@ 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.addClickHandler(new ClickHandler() {
|
||||
@ -117,24 +150,40 @@ public class MyWatchedProjectsScreen extends SettingsScreen {
|
||||
doAddNew();
|
||||
}
|
||||
});
|
||||
|
||||
final Grid grid = new Grid(2, 2);
|
||||
grid.setStyleName(Gerrit.RESOURCES.css().infoBlock());
|
||||
grid.setText(0, 0, Util.C.watchedProjectName());
|
||||
grid.setWidget(0, 1, nameTxt);
|
||||
|
||||
grid.setText(1, 0, Util.C.watchedProjectFilter());
|
||||
grid.setWidget(1, 1, filterTxt);
|
||||
|
||||
final CellFormatter fmt = grid.getCellFormatter();
|
||||
fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().topmost());
|
||||
fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().topmost());
|
||||
fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().header());
|
||||
fmt.addStyleName(1, 0, Gerrit.RESOURCES.css().header());
|
||||
fmt.addStyleName(1, 0, Gerrit.RESOURCES.css().bottomheader());
|
||||
|
||||
final FlowPanel fp = new FlowPanel();
|
||||
fp.setStyleName(Gerrit.RESOURCES.css().addWatchPanel());
|
||||
fp.add(grid);
|
||||
fp.add(addNew);
|
||||
add(fp);
|
||||
}
|
||||
|
||||
watches = new WatchTable();
|
||||
add(watches);
|
||||
{
|
||||
final FlowPanel fp = new FlowPanel();
|
||||
delSel = new Button(Util.C.buttonDeleteSshKey());
|
||||
delSel.addClickHandler(new ClickHandler() {
|
||||
@Override
|
||||
public void onClick(final ClickEvent event) {
|
||||
watches.deleteChecked();
|
||||
}
|
||||
});
|
||||
fp.add(delSel);
|
||||
add(fp);
|
||||
}
|
||||
|
||||
delSel = new Button(Util.C.buttonDeleteSshKey());
|
||||
delSel.addClickHandler(new ClickHandler() {
|
||||
@Override
|
||||
public void onClick(final ClickEvent event) {
|
||||
watches.deleteChecked();
|
||||
}
|
||||
});
|
||||
add(delSel);
|
||||
}
|
||||
|
||||
void doAddNew() {
|
||||
@ -144,11 +193,23 @@ public class MyWatchedProjectsScreen extends SettingsScreen {
|
||||
return;
|
||||
}
|
||||
|
||||
String filter = filterTxt.getText();
|
||||
if (filter == null || filter.isEmpty()
|
||||
|| filter.equals(Util.C.defaultFilter())) {
|
||||
filter = null;
|
||||
}
|
||||
|
||||
addNew.setEnabled(false);
|
||||
Util.ACCOUNT_SVC.addProjectWatch(projectName,
|
||||
nameBox.setEnabled(false);
|
||||
filterTxt.setEnabled(false);
|
||||
|
||||
Util.ACCOUNT_SVC.addProjectWatch(projectName, filter,
|
||||
new GerritCallback<AccountProjectWatchInfo>() {
|
||||
public void onSuccess(final AccountProjectWatchInfo result) {
|
||||
addNew.setEnabled(true);
|
||||
nameBox.setEnabled(true);
|
||||
filterTxt.setEnabled(true);
|
||||
|
||||
nameTxt.setText("");
|
||||
watches.insertWatch(result);
|
||||
}
|
||||
@ -156,6 +217,9 @@ public class MyWatchedProjectsScreen extends SettingsScreen {
|
||||
@Override
|
||||
public void onFailure(final Throwable caught) {
|
||||
addNew.setEnabled(true);
|
||||
nameBox.setEnabled(true);
|
||||
filterTxt.setEnabled(true);
|
||||
|
||||
super.onFailure(caught);
|
||||
}
|
||||
});
|
||||
@ -177,8 +241,7 @@ public class MyWatchedProjectsScreen extends SettingsScreen {
|
||||
WatchTable() {
|
||||
table.setWidth("");
|
||||
table.insertRow(1);
|
||||
table.setText(0, 2, com.google.gerrit.client.changes.Util.C
|
||||
.changeTableColumnProject());
|
||||
table.setText(0, 2, Util.C.watchedProjectName());
|
||||
table.setText(0, 3, Util.C.watchedProjectColumnEmailNotifications());
|
||||
|
||||
final FlexCellFormatter fmt = table.getFlexCellFormatter();
|
||||
@ -253,8 +316,16 @@ public class MyWatchedProjectsScreen extends SettingsScreen {
|
||||
}
|
||||
|
||||
void populate(final int row, final AccountProjectWatchInfo k) {
|
||||
final FlowPanel fp = new FlowPanel();
|
||||
fp.add(new ProjectLink(k.getProject().getNameKey(), Status.NEW));
|
||||
if (k.getWatch().getFilter() != null) {
|
||||
Label filter = new Label(k.getWatch().getFilter());
|
||||
filter.setStyleName(Gerrit.RESOURCES.css().watchedProjectFilter());
|
||||
fp.add(filter);
|
||||
}
|
||||
|
||||
table.setWidget(row, 1, new CheckBox());
|
||||
table.setWidget(row, 2, new ProjectLink(k.getProject().getNameKey(), Status.NEW));
|
||||
table.setWidget(row, 2, fp);
|
||||
{
|
||||
final CheckBox notifyNewChanges = new CheckBox();
|
||||
notifyNewChanges.addClickHandler(new ClickHandler() {
|
||||
|
@ -1029,6 +1029,15 @@ a:hover.downloadLink {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.addWatchPanel {
|
||||
margin-top: 10px;
|
||||
padding: 5px 5px 5px 5px;
|
||||
}
|
||||
.watchedProjectFilter {
|
||||
margin-left: 1em;
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.addSshKeyPanel {
|
||||
margin-top: 10px;
|
||||
background-color: trimColor;
|
||||
|
@ -89,7 +89,7 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements
|
||||
private final ChangeControl.Factory changeControlFactory;
|
||||
private final AccountInfoCacheFactory.Factory accountInfoCacheFactory;
|
||||
|
||||
private final Provider<ChangeQueryBuilder> queryBuilder;
|
||||
private final ChangeQueryBuilder.Factory queryBuilder;
|
||||
private final Provider<ChangeQueryRewriter> queryRewriter;
|
||||
|
||||
@Inject
|
||||
@ -97,7 +97,7 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements
|
||||
final Provider<CurrentUser> currentUser,
|
||||
final ChangeControl.Factory changeControlFactory,
|
||||
final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
|
||||
final Provider<ChangeQueryBuilder> queryBuilder,
|
||||
final ChangeQueryBuilder.Factory queryBuilder,
|
||||
final Provider<ChangeQueryRewriter> queryRewriter) {
|
||||
super(schema, currentUser);
|
||||
this.currentUser = currentUser;
|
||||
@ -144,10 +144,8 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements
|
||||
final int limit, final String key, final Comparator<Change> cmp)
|
||||
throws OrmException, InvalidQueryException {
|
||||
try {
|
||||
final ChangeQueryBuilder builder = queryBuilder.get();
|
||||
final Predicate<ChangeData> visibleToMe =
|
||||
builder.visibleto(currentUser.get());
|
||||
|
||||
final ChangeQueryBuilder builder = queryBuilder.create(currentUser.get());
|
||||
final Predicate<ChangeData> visibleToMe = builder.is_visible();
|
||||
Predicate<ChangeData> q = builder.parse(query);
|
||||
q = Predicate.and(q, //
|
||||
cmp == QUERY_PREV //
|
||||
|
@ -17,6 +17,7 @@ package com.google.gerrit.httpd.rpc.account;
|
||||
import com.google.gerrit.common.data.AccountProjectWatchInfo;
|
||||
import com.google.gerrit.common.data.AccountService;
|
||||
import com.google.gerrit.common.data.AgreementInfo;
|
||||
import com.google.gerrit.common.errors.InvalidQueryException;
|
||||
import com.google.gerrit.common.errors.NoSuchEntityException;
|
||||
import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
|
||||
import com.google.gerrit.reviewdb.Account;
|
||||
@ -29,8 +30,11 @@ import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.account.AccountCache;
|
||||
import com.google.gerrit.server.project.NoSuchProjectException;
|
||||
import com.google.gerrit.server.project.ProjectControl;
|
||||
import com.google.gerrit.server.query.QueryParseException;
|
||||
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
import com.google.gwtjsonrpc.client.VoidResult;
|
||||
import com.google.gwtorm.client.OrmDuplicateKeyException;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
@ -47,18 +51,21 @@ class AccountServiceImpl extends BaseServiceImplementation implements
|
||||
private final AccountCache accountCache;
|
||||
private final ProjectControl.Factory projectControlFactory;
|
||||
private final AgreementInfoFactory.Factory agreementInfoFactory;
|
||||
private final ChangeQueryBuilder.Factory queryBuilder;
|
||||
|
||||
@Inject
|
||||
AccountServiceImpl(final Provider<ReviewDb> schema,
|
||||
final Provider<IdentifiedUser> identifiedUser,
|
||||
final AccountCache accountCache,
|
||||
final ProjectControl.Factory projectControlFactory,
|
||||
final AgreementInfoFactory.Factory agreementInfoFactory) {
|
||||
final AgreementInfoFactory.Factory agreementInfoFactory,
|
||||
final ChangeQueryBuilder.Factory queryBuilder) {
|
||||
super(schema, identifiedUser);
|
||||
this.currentUser = identifiedUser;
|
||||
this.accountCache = accountCache;
|
||||
this.projectControlFactory = projectControlFactory;
|
||||
this.agreementInfoFactory = agreementInfoFactory;
|
||||
this.queryBuilder = queryBuilder;
|
||||
}
|
||||
|
||||
public void myAccount(final AsyncCallback<Account> callback) {
|
||||
@ -137,19 +144,31 @@ class AccountServiceImpl extends BaseServiceImplementation implements
|
||||
});
|
||||
}
|
||||
|
||||
public void addProjectWatch(final String projectName,
|
||||
public void addProjectWatch(final String projectName, final String filter,
|
||||
final AsyncCallback<AccountProjectWatchInfo> callback) {
|
||||
run(callback, new Action<AccountProjectWatchInfo>() {
|
||||
public AccountProjectWatchInfo run(ReviewDb db) throws OrmException,
|
||||
NoSuchProjectException {
|
||||
NoSuchProjectException, InvalidQueryException {
|
||||
final Project.NameKey nameKey = new Project.NameKey(projectName);
|
||||
final ProjectControl ctl = projectControlFactory.validateFor(nameKey);
|
||||
|
||||
final AccountProjectWatch watch =
|
||||
new AccountProjectWatch(
|
||||
new AccountProjectWatch.Key(((IdentifiedUser) ctl
|
||||
.getCurrentUser()).getAccountId(), nameKey));
|
||||
db.accountProjectWatches().insert(Collections.singleton(watch));
|
||||
if (filter != null) {
|
||||
try {
|
||||
queryBuilder.create(currentUser.get()).parse(filter);
|
||||
} catch (QueryParseException badFilter) {
|
||||
throw new InvalidQueryException(badFilter.getMessage(), filter);
|
||||
}
|
||||
}
|
||||
|
||||
AccountProjectWatch watch =
|
||||
new AccountProjectWatch(new AccountProjectWatch.Key(
|
||||
((IdentifiedUser) ctl.getCurrentUser()).getAccountId(),
|
||||
nameKey, filter));
|
||||
try {
|
||||
db.accountProjectWatches().insert(Collections.singleton(watch));
|
||||
} catch (OrmDuplicateKeyException alreadyHave) {
|
||||
watch = db.accountProjectWatches().get(watch.getKey());
|
||||
}
|
||||
return new AccountProjectWatchInfo(watch, ctl.getProject());
|
||||
}
|
||||
});
|
||||
|
@ -127,7 +127,6 @@ class AbandonChange extends Handler<ChangeDetail> {
|
||||
// Email the reviewers
|
||||
final AbandonedSender cm = abandonedSenderFactory.create(change);
|
||||
cm.setFrom(currentUser.getAccountId());
|
||||
cm.setReviewDb(db);
|
||||
cm.setChangeMessage(cmsg);
|
||||
cm.send();
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import com.google.gerrit.reviewdb.PatchSet;
|
||||
import com.google.gerrit.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.server.ChangeUtil;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.git.MergeOp;
|
||||
import com.google.gerrit.server.git.MergeQueue;
|
||||
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
|
||||
import com.google.gerrit.server.project.CanSubmitResult;
|
||||
@ -44,8 +45,8 @@ class SubmitAction extends Handler<ChangeDetail> {
|
||||
private final FunctionState.Factory functionState;
|
||||
private final IdentifiedUser user;
|
||||
private final ChangeDetailFactory.Factory changeDetailFactory;
|
||||
@Inject
|
||||
private ChangeControl.Factory changeControlFactory;
|
||||
private final ChangeControl.Factory changeControlFactory;
|
||||
private final MergeOp.Factory opFactory;
|
||||
|
||||
private final PatchSet.Id patchSetId;
|
||||
|
||||
@ -53,13 +54,17 @@ class SubmitAction extends Handler<ChangeDetail> {
|
||||
SubmitAction(final ReviewDb db, final MergeQueue mq, final ApprovalTypes at,
|
||||
final FunctionState.Factory fs, final IdentifiedUser user,
|
||||
final ChangeDetailFactory.Factory changeDetailFactory,
|
||||
final ChangeControl.Factory changeControlFactory,
|
||||
final MergeOp.Factory opFactory,
|
||||
@Assisted final PatchSet.Id patchSetId) {
|
||||
this.db = db;
|
||||
this.merger = mq;
|
||||
this.approvalTypes = at;
|
||||
this.functionState = fs;
|
||||
this.user = user;
|
||||
this.changeControlFactory = changeControlFactory;
|
||||
this.changeDetailFactory = changeDetailFactory;
|
||||
this.opFactory = opFactory;
|
||||
|
||||
this.patchSetId = patchSetId;
|
||||
}
|
||||
@ -76,7 +81,7 @@ class SubmitAction extends Handler<ChangeDetail> {
|
||||
CanSubmitResult err =
|
||||
changeControl.canSubmit(patchSetId, db, approvalTypes, functionState);
|
||||
if (err == CanSubmitResult.OK) {
|
||||
ChangeUtil.submit(patchSetId, user, db, merger);
|
||||
ChangeUtil.submit(opFactory, patchSetId, user, db, merger);
|
||||
return changeDetailFactory.create(changeId).call();
|
||||
} else {
|
||||
throw new IllegalStateException(err.getMessage());
|
||||
|
@ -130,7 +130,6 @@ class AddReviewer extends Handler<ReviewerResult> {
|
||||
final AddReviewerSender cm;
|
||||
cm = addReviewerSenderFactory.create(control.getChange());
|
||||
cm.setFrom(currentUser.getAccountId());
|
||||
cm.setReviewDb(db);
|
||||
cm.addReviewers(added);
|
||||
cm.send();
|
||||
|
||||
|
@ -16,9 +16,12 @@ package com.google.gerrit.reviewdb;
|
||||
|
||||
import com.google.gwtorm.client.Column;
|
||||
import com.google.gwtorm.client.CompoundKey;
|
||||
import com.google.gwtorm.client.StringKey;
|
||||
|
||||
/** An {@link Account} interested in a {@link Project}. */
|
||||
public final class AccountProjectWatch {
|
||||
public static final String FILTER_ALL = "*";
|
||||
|
||||
public static class Key extends CompoundKey<Account.Id> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ -28,14 +31,19 @@ public final class AccountProjectWatch {
|
||||
@Column(id = 2)
|
||||
protected Project.NameKey projectName;
|
||||
|
||||
@Column(id = 3)
|
||||
protected Filter filter;
|
||||
|
||||
protected Key() {
|
||||
accountId = new Account.Id();
|
||||
projectName = new Project.NameKey();
|
||||
filter = new Filter();
|
||||
}
|
||||
|
||||
public Key(final Account.Id a, final Project.NameKey g) {
|
||||
public Key(Account.Id a, Project.NameKey g, String f) {
|
||||
accountId = a;
|
||||
projectName = g;
|
||||
filter = new Filter(f);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -45,7 +53,31 @@ public final class AccountProjectWatch {
|
||||
|
||||
@Override
|
||||
public com.google.gwtorm.client.Key<?>[] members() {
|
||||
return new com.google.gwtorm.client.Key<?>[] {projectName};
|
||||
return new com.google.gwtorm.client.Key<?>[] {projectName, filter};
|
||||
}
|
||||
}
|
||||
|
||||
public static class Filter extends StringKey<com.google.gwtorm.client.Key<?>> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Column(id = 1)
|
||||
protected String filter;
|
||||
|
||||
protected Filter() {
|
||||
}
|
||||
|
||||
public Filter(String f) {
|
||||
filter = f != null && !f.isEmpty() ? f : FILTER_ALL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get() {
|
||||
return filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void set(String newValue) {
|
||||
filter = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,6 +115,10 @@ public final class AccountProjectWatch {
|
||||
return key.projectName;
|
||||
}
|
||||
|
||||
public String getFilter() {
|
||||
return FILTER_ALL.equals(key.filter.get()) ? null : key.filter.get();
|
||||
}
|
||||
|
||||
public boolean isNotifyNewChanges() {
|
||||
return notifyNewChanges;
|
||||
}
|
||||
|
@ -15,12 +15,13 @@
|
||||
package com.google.gerrit.server;
|
||||
|
||||
import com.google.gerrit.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.reviewdb.AccountProjectWatch;
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.reviewdb.Project.NameKey;
|
||||
import com.google.gerrit.server.config.AuthConfig;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
@ -43,7 +44,7 @@ public class AnonymousUser extends CurrentUser {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<NameKey> getWatchedProjects() {
|
||||
public Collection<AccountProjectWatch> getNotificationFilters() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@ import com.google.gerrit.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.reviewdb.TrackingId;
|
||||
import com.google.gerrit.server.config.TrackingFooter;
|
||||
import com.google.gerrit.server.config.TrackingFooters;
|
||||
import com.google.gerrit.server.git.MergeOp;
|
||||
import com.google.gerrit.server.git.MergeQueue;
|
||||
import com.google.gwtorm.client.AtomicUpdate;
|
||||
import com.google.gwtorm.client.OrmConcurrencyException;
|
||||
@ -135,8 +136,8 @@ public class ChangeUtil {
|
||||
db.trackingIds().delete(toDelete);
|
||||
}
|
||||
|
||||
public static void submit(PatchSet.Id patchSetId, IdentifiedUser user, ReviewDb db, MergeQueue merger)
|
||||
throws OrmException {
|
||||
public static void submit(MergeOp.Factory opFactory, PatchSet.Id patchSetId,
|
||||
IdentifiedUser user, ReviewDb db, MergeQueue merger) throws OrmException {
|
||||
final Change.Id changeId = patchSetId.getParentKey();
|
||||
final PatchSetApproval approval = createSubmitApproval(patchSetId, user, db);
|
||||
|
||||
@ -154,7 +155,7 @@ public class ChangeUtil {
|
||||
});
|
||||
|
||||
if (change.getStatus() == Change.Status.SUBMITTED) {
|
||||
merger.merge(change.getDest());
|
||||
merger.merge(opFactory, change.getDest());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,11 +15,12 @@
|
||||
package com.google.gerrit.server;
|
||||
|
||||
import com.google.gerrit.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.reviewdb.AccountProjectWatch;
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.reviewdb.Project;
|
||||
import com.google.gerrit.server.config.AuthConfig;
|
||||
import com.google.inject.servlet.RequestScoped;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -60,8 +61,8 @@ public abstract class CurrentUser {
|
||||
/** Set of changes starred by this user. */
|
||||
public abstract Set<Change.Id> getStarredChanges();
|
||||
|
||||
/** Set of project that are watched by this user */
|
||||
public abstract Set<Project.NameKey> getWatchedProjects();
|
||||
/** Filters selecting changes the user wants to monitor. */
|
||||
public abstract Collection<AccountProjectWatch> getNotificationFilters();
|
||||
|
||||
/** Is the user a non-interactive user? */
|
||||
public boolean isBatchUser() {
|
||||
|
@ -19,7 +19,6 @@ import com.google.gerrit.reviewdb.AccountDiffPreference;
|
||||
import com.google.gerrit.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.reviewdb.AccountProjectWatch;
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.reviewdb.Project;
|
||||
import com.google.gerrit.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.reviewdb.StarredChange;
|
||||
import com.google.gerrit.server.account.AccountCache;
|
||||
@ -43,9 +42,11 @@ import java.net.InetSocketAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
|
||||
@ -145,7 +146,7 @@ public class IdentifiedUser extends CurrentUser {
|
||||
private Set<String> emailAddresses;
|
||||
private Set<AccountGroup.Id> effectiveGroups;
|
||||
private Set<Change.Id> starredChanges;
|
||||
private Set<Project.NameKey> watchedProjects;
|
||||
private Collection<AccountProjectWatch> notificationFilters;
|
||||
|
||||
private IdentifiedUser(final AccessPath accessPath,
|
||||
final AuthConfig authConfig, final Provider<String> canonicalUrl,
|
||||
@ -237,24 +238,22 @@ public class IdentifiedUser extends CurrentUser {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Project.NameKey> getWatchedProjects() {
|
||||
if (watchedProjects == null) {
|
||||
public Collection<AccountProjectWatch> getNotificationFilters() {
|
||||
if (notificationFilters == null) {
|
||||
if (dbProvider == null) {
|
||||
throw new OutOfScopeException("Not in request scoped user");
|
||||
}
|
||||
final Set<Project.NameKey> h = new HashSet<Project.NameKey>();
|
||||
List<AccountProjectWatch> r;
|
||||
try {
|
||||
for (AccountProjectWatch projectWatch : dbProvider.get()
|
||||
.accountProjectWatches().byAccount(getAccountId())) {
|
||||
h.add(projectWatch.getProjectNameKey());
|
||||
}
|
||||
r = dbProvider.get().accountProjectWatches() //
|
||||
.byAccount(getAccountId()).toList();
|
||||
} catch (OrmException e) {
|
||||
log.warn("Cannot query project watches of a user", e);
|
||||
log.warn("Cannot query notification filters of a user", e);
|
||||
r = Collections.emptyList();
|
||||
}
|
||||
watchedProjects = Collections.unmodifiableSet(h);
|
||||
notificationFilters = Collections.unmodifiableList(r);
|
||||
}
|
||||
|
||||
return watchedProjects;
|
||||
return notificationFilters;
|
||||
}
|
||||
|
||||
public PersonIdent newRefLogIdent() {
|
||||
|
@ -15,6 +15,7 @@
|
||||
package com.google.gerrit.server;
|
||||
|
||||
import com.google.gerrit.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.reviewdb.AccountProjectWatch;
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.reviewdb.Project.NameKey;
|
||||
import com.google.gerrit.server.config.AuthConfig;
|
||||
@ -22,6 +23,7 @@ import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@ -59,7 +61,7 @@ public class PeerDaemonUser extends CurrentUser {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<NameKey> getWatchedProjects() {
|
||||
public Collection<AccountProjectWatch> getNotificationFilters() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
|
@ -15,12 +15,14 @@
|
||||
package com.google.gerrit.server;
|
||||
|
||||
import com.google.gerrit.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.reviewdb.AccountProjectWatch;
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.reviewdb.Project.NameKey;
|
||||
import com.google.gerrit.server.config.AuthConfig;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@ -73,7 +75,7 @@ public class ReplicationUser extends CurrentUser {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<NameKey> getWatchedProjects() {
|
||||
public Collection<AccountProjectWatch> getNotificationFilters() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
|
@ -132,7 +132,6 @@ public class GerritGlobalModule extends FactoryModule {
|
||||
factory(PushAllProjectsOp.Factory.class);
|
||||
|
||||
bind(MergeQueue.class).to(ChangeMergeQueue.class).in(SINGLETON);
|
||||
factory(MergeOp.Factory.class);
|
||||
factory(ReloadSubmitQueueOp.Factory.class);
|
||||
|
||||
bind(FromAddressGenerator.class).toProvider(
|
||||
@ -142,12 +141,6 @@ public class GerritGlobalModule extends FactoryModule {
|
||||
bind(PatchSetInfoFactory.class);
|
||||
bind(IdentifiedUser.GenericFactory.class).in(SINGLETON);
|
||||
factory(FunctionState.Factory.class);
|
||||
|
||||
factory(AbandonedSender.Factory.class);
|
||||
factory(CommentSender.Factory.class);
|
||||
factory(MergedSender.Factory.class);
|
||||
factory(MergeFailSender.Factory.class);
|
||||
factory(RegisterNewEmailSender.Factory.class);
|
||||
factory(ReplicationUser.Factory.class);
|
||||
|
||||
install(new LifecycleModule() {
|
||||
|
@ -21,9 +21,15 @@ import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.RequestCleanup;
|
||||
import com.google.gerrit.server.account.AccountResolver;
|
||||
import com.google.gerrit.server.account.GroupControl;
|
||||
import com.google.gerrit.server.git.MergeOp;
|
||||
import com.google.gerrit.server.git.ReceiveCommits;
|
||||
import com.google.gerrit.server.mail.AbandonedSender;
|
||||
import com.google.gerrit.server.mail.AddReviewerSender;
|
||||
import com.google.gerrit.server.mail.CommentSender;
|
||||
import com.google.gerrit.server.mail.CreateChangeSender;
|
||||
import com.google.gerrit.server.mail.MergeFailSender;
|
||||
import com.google.gerrit.server.mail.MergedSender;
|
||||
import com.google.gerrit.server.mail.RegisterNewEmailSender;
|
||||
import com.google.gerrit.server.mail.ReplacePatchSetSender;
|
||||
import com.google.gerrit.server.patch.PublishComments;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
@ -41,14 +47,15 @@ public class GerritRequestModule extends FactoryModule {
|
||||
RequestScoped.class);
|
||||
bind(IdentifiedUser.RequestFactory.class).in(SINGLETON);
|
||||
bind(AccountResolver.class);
|
||||
bind(ChangeQueryBuilder.class);
|
||||
bind(ChangeQueryRewriter.class);
|
||||
|
||||
bind(ChangeControl.Factory.class).in(SINGLETON);
|
||||
bind(GroupControl.Factory.class).in(SINGLETON);
|
||||
bind(ProjectControl.Factory.class).in(SINGLETON);
|
||||
|
||||
factory(ChangeQueryBuilder.Factory.class);
|
||||
factory(ReceiveCommits.Factory.class);
|
||||
factory(MergeOp.Factory.class);
|
||||
|
||||
// Not really per-request, but dammit, I don't know where else to
|
||||
// easily park this stuff.
|
||||
@ -57,5 +64,10 @@ public class GerritRequestModule extends FactoryModule {
|
||||
factory(CreateChangeSender.Factory.class);
|
||||
factory(PublishComments.Factory.class);
|
||||
factory(ReplacePatchSetSender.Factory.class);
|
||||
factory(AbandonedSender.Factory.class);
|
||||
factory(CommentSender.Factory.class);
|
||||
factory(MergedSender.Factory.class);
|
||||
factory(MergeFailSender.Factory.class);
|
||||
factory(RegisterNewEmailSender.Factory.class);
|
||||
}
|
||||
}
|
||||
|
@ -19,12 +19,30 @@ import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import com.google.gerrit.reviewdb.Branch;
|
||||
import com.google.gerrit.reviewdb.Project;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.RemotePeer;
|
||||
import com.google.gerrit.server.RequestCleanup;
|
||||
import com.google.gerrit.server.config.GerritRequestModule;
|
||||
import com.google.gerrit.server.ssh.SshInfo;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.OutOfScopeException;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Scope;
|
||||
import com.google.inject.servlet.RequestScoped;
|
||||
|
||||
import com.jcraft.jsch.HostKey;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -38,18 +56,47 @@ public class ChangeMergeQueue implements MergeQueue {
|
||||
new HashMap<Branch.NameKey, RecheckJob>();
|
||||
|
||||
private final WorkQueue workQueue;
|
||||
private final MergeOp.Factory opFactory;
|
||||
private final Provider<MergeOp.Factory> bgFactory;
|
||||
|
||||
@Inject
|
||||
ChangeMergeQueue(final WorkQueue wq, final MergeOp.Factory of) {
|
||||
ChangeMergeQueue(final WorkQueue wq, Injector parent) {
|
||||
workQueue = wq;
|
||||
opFactory = of;
|
||||
|
||||
Injector child = parent.createChildInjector(new AbstractModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
bindScope(RequestScoped.class, MyScope.REQUEST);
|
||||
install(new GerritRequestModule());
|
||||
|
||||
bind(CurrentUser.class).to(IdentifiedUser.class);
|
||||
bind(IdentifiedUser.class).toProvider(new Provider<IdentifiedUser>() {
|
||||
@Override
|
||||
public IdentifiedUser get() {
|
||||
throw new OutOfScopeException("No user on merge thread");
|
||||
}
|
||||
});
|
||||
bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
|
||||
new Provider<SocketAddress>() {
|
||||
@Override
|
||||
public SocketAddress get() {
|
||||
throw new OutOfScopeException("No remote peer on merge thread");
|
||||
}
|
||||
});
|
||||
bind(SshInfo.class).toInstance(new SshInfo() {
|
||||
@Override
|
||||
public List<HostKey> getHostKeys() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
bgFactory = child.getProvider(MergeOp.Factory.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void merge(final Branch.NameKey branch) {
|
||||
public void merge(MergeOp.Factory mof, Branch.NameKey branch) {
|
||||
if (start(branch)) {
|
||||
mergeImpl(branch);
|
||||
mergeImpl(mof, branch);
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,7 +174,7 @@ public class ChangeMergeQueue implements MergeQueue {
|
||||
e.needMerge = false;
|
||||
}
|
||||
|
||||
private void mergeImpl(final Branch.NameKey branch) {
|
||||
private void mergeImpl(MergeOp.Factory opFactory, Branch.NameKey branch) {
|
||||
try {
|
||||
opFactory.create(branch).merge();
|
||||
} catch (Throwable e) {
|
||||
@ -137,6 +184,26 @@ 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) {
|
||||
final long remainingDelay = e.recheckAt - System.currentTimeMillis();
|
||||
if (MILLISECONDS.convert(10, SECONDS) < remainingDelay) {
|
||||
@ -194,4 +261,65 @@ public class ChangeMergeQueue implements MergeQueue {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1179,7 +1179,6 @@ public class MergeOp {
|
||||
if (submitter != null) {
|
||||
cm.setFrom(submitter.getAccountId());
|
||||
}
|
||||
cm.setReviewDb(schema);
|
||||
cm.setPatchSet(schema.patchSets().get(c.currentPatchSetId()));
|
||||
cm.send();
|
||||
} catch (OrmException e) {
|
||||
@ -1241,7 +1240,6 @@ public class MergeOp {
|
||||
cm.setFrom(submitter.getAccountId());
|
||||
}
|
||||
}
|
||||
cm.setReviewDb(schema);
|
||||
cm.setPatchSet(schema.patchSets().get(c.currentPatchSetId()));
|
||||
cm.setChangeMessage(msg);
|
||||
cm.send();
|
||||
|
@ -19,7 +19,7 @@ import com.google.gerrit.reviewdb.Branch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public interface MergeQueue {
|
||||
void merge(Branch.NameKey branch);
|
||||
void merge(MergeOp.Factory mof, Branch.NameKey branch);
|
||||
|
||||
void schedule(Branch.NameKey branch);
|
||||
|
||||
|
@ -931,7 +931,6 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
||||
cm = createChangeSenderFactory.create(change);
|
||||
cm.setFrom(me);
|
||||
cm.setPatchSet(ps, info);
|
||||
cm.setReviewDb(db);
|
||||
cm.addReviewers(reviewers);
|
||||
cm.addExtraCC(cc);
|
||||
cm.send();
|
||||
@ -1234,7 +1233,6 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
||||
cm.setFrom(me);
|
||||
cm.setPatchSet(ps, result.info);
|
||||
cm.setChangeMessage(result.msg);
|
||||
cm.setReviewDb(db);
|
||||
cm.addReviewers(reviewers);
|
||||
cm.addExtraCC(cc);
|
||||
cm.addReviewers(oldReviewers);
|
||||
@ -1583,7 +1581,6 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
||||
try {
|
||||
final MergedSender cm = mergedSenderFactory.create(result.change);
|
||||
cm.setFrom(currentUser.getAccountId());
|
||||
cm.setReviewDb(db);
|
||||
cm.setPatchSet(result.patchSet, result.info);
|
||||
cm.setDest(new Branch.NameKey(project.getNameKey(),
|
||||
result.mergedIntoRef));
|
||||
|
@ -25,8 +25,8 @@ public class AbandonedSender extends ReplyToChangeSender {
|
||||
}
|
||||
|
||||
@Inject
|
||||
public AbandonedSender(@Assisted Change c) {
|
||||
super(c, "abandon");
|
||||
public AbandonedSender(EmailArguments ea, @Assisted Change c) {
|
||||
super(ea, c, "abandon");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -15,6 +15,7 @@
|
||||
package com.google.gerrit.server.mail;
|
||||
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.server.ssh.SshInfo;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
@ -25,8 +26,9 @@ public class AddReviewerSender extends NewChangeSender {
|
||||
}
|
||||
|
||||
@Inject
|
||||
public AddReviewerSender(@Assisted Change c) {
|
||||
super(c);
|
||||
public AddReviewerSender(EmailArguments ea, SshInfo sshInfo,
|
||||
@Assisted Change c) {
|
||||
super(ea, sshInfo, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -38,8 +38,8 @@ public class CommentSender extends ReplyToChangeSender {
|
||||
private List<PatchLineComment> inlineComments = Collections.emptyList();
|
||||
|
||||
@Inject
|
||||
public CommentSender(@Assisted Change c) {
|
||||
super(c, "comment");
|
||||
public CommentSender(EmailArguments ea, @Assisted Change c) {
|
||||
super(ea, c, "comment");
|
||||
}
|
||||
|
||||
public void setPatchLineComments(final List<PatchLineComment> plc) {
|
||||
@ -124,7 +124,7 @@ public class CommentSender extends ReplyToChangeSender {
|
||||
|
||||
private Repository getRepository() {
|
||||
try {
|
||||
return server.openRepository(projectName);
|
||||
return args.server.openRepository(projectName);
|
||||
} catch (RepositoryNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import com.google.gerrit.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.reviewdb.AccountGroupMember;
|
||||
import com.google.gerrit.reviewdb.AccountProjectWatch;
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.server.project.ProjectState;
|
||||
import com.google.gerrit.server.ssh.SshInfo;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
@ -34,8 +34,9 @@ public class CreateChangeSender extends NewChangeSender {
|
||||
}
|
||||
|
||||
@Inject
|
||||
public CreateChangeSender(@Assisted Change c) {
|
||||
super(c);
|
||||
public CreateChangeSender(EmailArguments ea, SshInfo sshInfo,
|
||||
@Assisted Change c) {
|
||||
super(ea, sshInfo, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -46,38 +47,32 @@ public class CreateChangeSender extends NewChangeSender {
|
||||
}
|
||||
|
||||
private void bccWatchers() {
|
||||
if (db != null) {
|
||||
try {
|
||||
// BCC anyone else who has interest in this project's changes
|
||||
//
|
||||
final ProjectState ps = getProjectState();
|
||||
if (ps != null) {
|
||||
// Try to mark interested owners with a TO and not a BCC line.
|
||||
//
|
||||
final Set<Account.Id> owners = new HashSet<Account.Id>();
|
||||
for (AccountGroup.Id g : getProjectOwners()) {
|
||||
for (AccountGroupMember m : db.accountGroupMembers().byGroup(g)) {
|
||||
owners.add(m.getAccountId());
|
||||
}
|
||||
}
|
||||
try {
|
||||
// Try to mark interested owners with a TO and not a BCC line.
|
||||
//
|
||||
final Set<Account.Id> owners = new HashSet<Account.Id>();
|
||||
for (AccountGroup.Id g : getProjectOwners()) {
|
||||
for (AccountGroupMember m : args.db.get().accountGroupMembers()
|
||||
.byGroup(g)) {
|
||||
owners.add(m.getAccountId());
|
||||
}
|
||||
}
|
||||
|
||||
// BCC anyone who has interest in this project's changes
|
||||
//
|
||||
for (final AccountProjectWatch w : getProjectWatches()) {
|
||||
if (w.isNotifyNewChanges()) {
|
||||
if (owners.contains(w.getAccountId())) {
|
||||
add(RecipientType.TO, w.getAccountId());
|
||||
} else {
|
||||
add(RecipientType.BCC, w.getAccountId());
|
||||
}
|
||||
}
|
||||
// BCC anyone who has interest in this project's changes
|
||||
//
|
||||
for (final AccountProjectWatch w : getWatches()) {
|
||||
if (w.isNotifyNewChanges()) {
|
||||
if (owners.contains(w.getAccountId())) {
|
||||
add(RecipientType.TO, w.getAccountId());
|
||||
} else {
|
||||
add(RecipientType.BCC, w.getAccountId());
|
||||
}
|
||||
}
|
||||
} catch (OrmException err) {
|
||||
// Just don't CC everyone. Better to send a partial message to those
|
||||
// we already have queued up then to fail deliver entirely to people
|
||||
// who have a lower interest in the change.
|
||||
}
|
||||
} catch (OrmException err) {
|
||||
// Just don't CC everyone. Better to send a partial message to those
|
||||
// we already have queued up then to fail deliver entirely to people
|
||||
// who have a lower interest in the change.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -25,8 +25,8 @@ public class MergeFailSender extends ReplyToChangeSender {
|
||||
}
|
||||
|
||||
@Inject
|
||||
public MergeFailSender(@Assisted Change c) {
|
||||
super(c, "comment");
|
||||
public MergeFailSender(EmailArguments ea, @Assisted Change c) {
|
||||
super(ea, c, "comment");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -37,15 +37,14 @@ public class MergedSender extends ReplyToChangeSender {
|
||||
public MergedSender create(Change change);
|
||||
}
|
||||
|
||||
private final ApprovalTypes approvalTypes;
|
||||
private Branch.NameKey dest;
|
||||
|
||||
@Inject
|
||||
private ApprovalTypes approvalTypes;
|
||||
|
||||
@Inject
|
||||
public MergedSender(@Assisted Change c) {
|
||||
super(c, "merged");
|
||||
public MergedSender(EmailArguments ea, ApprovalTypes at, @Assisted Change c) {
|
||||
super(ea, c, "merged");
|
||||
dest = c.getDest();
|
||||
approvalTypes = at;
|
||||
}
|
||||
|
||||
public void setDest(final Branch.NameKey key) {
|
||||
@ -78,7 +77,7 @@ public class MergedSender extends ReplyToChangeSender {
|
||||
}
|
||||
|
||||
private void formatApprovals() {
|
||||
if (db != null && patchSet != null) {
|
||||
if (patchSet != null) {
|
||||
try {
|
||||
final Map<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>> pos =
|
||||
new HashMap<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>>();
|
||||
@ -86,8 +85,8 @@ public class MergedSender extends ReplyToChangeSender {
|
||||
final Map<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>> neg =
|
||||
new HashMap<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>>();
|
||||
|
||||
for (PatchSetApproval ca : db.patchSetApprovals().byPatchSet(
|
||||
patchSet.getId())) {
|
||||
for (PatchSetApproval ca : args.db.get().patchSetApprovals()
|
||||
.byPatchSet(patchSet.getId())) {
|
||||
if (ca.getValue() > 0) {
|
||||
insert(pos, ca);
|
||||
} else if (ca.getValue() < 0) {
|
||||
@ -157,23 +156,18 @@ public class MergedSender extends ReplyToChangeSender {
|
||||
}
|
||||
|
||||
private void bccWatchesNotifySubmittedChanges() {
|
||||
if (db != null) {
|
||||
try {
|
||||
// BCC anyone else who has interest in this project's changes
|
||||
//
|
||||
final ProjectState ps = getProjectState();
|
||||
if (ps != null) {
|
||||
for (final AccountProjectWatch w : getProjectWatches()) {
|
||||
if (w.isNotifySubmittedChanges()) {
|
||||
add(RecipientType.BCC, w.getAccountId());
|
||||
}
|
||||
}
|
||||
try {
|
||||
// BCC anyone else who has interest in this project's changes
|
||||
//
|
||||
for (final AccountProjectWatch w : getWatches()) {
|
||||
if (w.isNotifySubmittedChanges()) {
|
||||
add(RecipientType.BCC, w.getAccountId());
|
||||
}
|
||||
} catch (OrmException err) {
|
||||
// Just don't CC everyone. Better to send a partial message to those
|
||||
// we already have queued up then to fail deliver entirely to people
|
||||
// who have a lower interest in the change.
|
||||
}
|
||||
} catch (OrmException err) {
|
||||
// Just don't CC everyone. Better to send a partial message to those
|
||||
// we already have queued up then to fail deliver entirely to people
|
||||
// who have a lower interest in the change.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ package com.google.gerrit.server.mail;
|
||||
import com.google.gerrit.reviewdb.Account;
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.server.ssh.SshInfo;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import com.jcraft.jsch.HostKey;
|
||||
|
||||
@ -29,14 +28,13 @@ import java.util.Set;
|
||||
|
||||
/** Sends an email alerting a user to a new change for them to review. */
|
||||
public abstract class NewChangeSender extends OutgoingEmail {
|
||||
@Inject
|
||||
private SshInfo sshInfo;
|
||||
|
||||
private final SshInfo sshInfo;
|
||||
private final Set<Account.Id> reviewers = new HashSet<Account.Id>();
|
||||
private final Set<Account.Id> extraCC = new HashSet<Account.Id>();
|
||||
|
||||
protected NewChangeSender(Change c) {
|
||||
super(c, "newchange");
|
||||
protected NewChangeSender(EmailArguments ea, SshInfo sshInfo, Change c) {
|
||||
super(ea, c, "newchange");
|
||||
this.sshInfo = sshInfo;
|
||||
}
|
||||
|
||||
public void addReviewers(final Collection<Account.Id> cc) {
|
||||
|
@ -22,27 +22,20 @@ import com.google.gerrit.reviewdb.ChangeMessage;
|
||||
import com.google.gerrit.reviewdb.PatchSet;
|
||||
import com.google.gerrit.reviewdb.PatchSetApproval;
|
||||
import com.google.gerrit.reviewdb.PatchSetInfo;
|
||||
import com.google.gerrit.reviewdb.Project;
|
||||
import com.google.gerrit.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.reviewdb.StarredChange;
|
||||
import com.google.gerrit.reviewdb.UserIdentity;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.account.AccountCache;
|
||||
import com.google.gerrit.server.account.AccountState;
|
||||
import com.google.gerrit.server.config.CanonicalWebUrl;
|
||||
import com.google.gerrit.server.config.WildProjectName;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.mail.EmailHeader.AddressList;
|
||||
import com.google.gerrit.server.patch.PatchList;
|
||||
import com.google.gerrit.server.patch.PatchListCache;
|
||||
import com.google.gerrit.server.patch.PatchListEntry;
|
||||
import com.google.gerrit.server.patch.PatchSetInfoFactory;
|
||||
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
|
||||
import com.google.gerrit.server.project.ProjectCache;
|
||||
import com.google.gerrit.server.project.ProjectState;
|
||||
import com.google.gerrit.server.query.Predicate;
|
||||
import com.google.gerrit.server.query.QueryParseException;
|
||||
import com.google.gerrit.server.query.change.ChangeData;
|
||||
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
import org.eclipse.jgit.util.SystemReader;
|
||||
import org.slf4j.Logger;
|
||||
@ -63,8 +56,6 @@ import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Sends an email to one or more interested parties. */
|
||||
public abstract class OutgoingEmail {
|
||||
private static final Logger log = LoggerFactory.getLogger(OutgoingEmail.class);
|
||||
@ -83,55 +74,24 @@ public abstract class OutgoingEmail {
|
||||
private StringBuilder body;
|
||||
private boolean inFooter;
|
||||
|
||||
protected final EmailArguments args;
|
||||
protected Account.Id fromId;
|
||||
protected PatchSet patchSet;
|
||||
protected PatchSetInfo patchSetInfo;
|
||||
protected ChangeMessage changeMessage;
|
||||
protected ReviewDb db;
|
||||
|
||||
@Inject
|
||||
protected GitRepositoryManager server;
|
||||
|
||||
@Inject
|
||||
private ProjectCache projectCache;
|
||||
|
||||
@Inject
|
||||
private AccountCache accountCache;
|
||||
|
||||
@Inject
|
||||
private PatchListCache patchListCache;
|
||||
|
||||
@Inject
|
||||
private FromAddressGenerator fromAddressGenerator;
|
||||
|
||||
@Inject
|
||||
private EmailSender emailSender;
|
||||
|
||||
@Inject
|
||||
private PatchSetInfoFactory patchSetInfoFactory;
|
||||
|
||||
@Inject
|
||||
private IdentifiedUser.GenericFactory identifiedUserFactory;
|
||||
|
||||
@Inject
|
||||
@CanonicalWebUrl
|
||||
@Nullable
|
||||
private Provider<String> urlProvider;
|
||||
|
||||
@Inject
|
||||
@WildProjectName
|
||||
private Project.NameKey wildProject;
|
||||
|
||||
private ProjectState projectState;
|
||||
private ChangeData changeData;
|
||||
|
||||
protected OutgoingEmail(final Change c, final String mc) {
|
||||
protected OutgoingEmail(EmailArguments ea, final Change c, final String mc) {
|
||||
args = ea;
|
||||
change = c;
|
||||
messageClass = mc;
|
||||
headers = new LinkedHashMap<String, EmailHeader>();
|
||||
}
|
||||
|
||||
protected OutgoingEmail(final String mc) {
|
||||
this(null, mc);
|
||||
protected OutgoingEmail(EmailArguments ea, final String mc) {
|
||||
this(ea, null, mc);
|
||||
}
|
||||
|
||||
public void setFrom(final Account.Id id) {
|
||||
@ -151,17 +111,13 @@ public abstract class OutgoingEmail {
|
||||
changeMessage = cm;
|
||||
}
|
||||
|
||||
public void setReviewDb(final ReviewDb d) {
|
||||
db = d;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format and enqueue the message for delivery.
|
||||
*
|
||||
* @throws EmailException
|
||||
*/
|
||||
public void send() throws EmailException {
|
||||
if (!emailSender.isEnabled()) {
|
||||
if (!args.emailSender.isEnabled()) {
|
||||
// Server has explicitly disabled email sending.
|
||||
//
|
||||
return;
|
||||
@ -171,7 +127,7 @@ public abstract class OutgoingEmail {
|
||||
format();
|
||||
if (shouldSendMessage()) {
|
||||
if (fromId != null) {
|
||||
final Account fromUser = accountCache.get(fromId).getAccount();
|
||||
final Account fromUser = args.accountCache.get(fromId).getAccount();
|
||||
|
||||
if (fromUser.getGeneralPreferences().isCopySelfOnEmails()) {
|
||||
// If we are impersonating a user, make sure they receive a CC of
|
||||
@ -226,24 +182,22 @@ public abstract class OutgoingEmail {
|
||||
appendText("Gerrit-Branch: " + change.getDest().getShortName() + "\n");
|
||||
appendText("Gerrit-Owner: " + getNameEmailFor(change.getOwner()) + "\n");
|
||||
|
||||
if (db != null) {
|
||||
try {
|
||||
HashSet<Account.Id> reviewers = new HashSet<Account.Id>();
|
||||
for (PatchSetApproval p : db.patchSetApprovals().byChange(
|
||||
change.getId())) {
|
||||
reviewers.add(p.getAccountId());
|
||||
}
|
||||
|
||||
TreeSet<String> names = new TreeSet<String>();
|
||||
for (Account.Id who : reviewers) {
|
||||
names.add(getNameEmailFor(who));
|
||||
}
|
||||
|
||||
for (String name : names) {
|
||||
appendText("Gerrit-Reviewer: " + name + "\n");
|
||||
}
|
||||
} catch (OrmException e) {
|
||||
try {
|
||||
HashSet<Account.Id> reviewers = new HashSet<Account.Id>();
|
||||
for (PatchSetApproval p : args.db.get().patchSetApprovals().byChange(
|
||||
change.getId())) {
|
||||
reviewers.add(p.getAccountId());
|
||||
}
|
||||
|
||||
TreeSet<String> names = new TreeSet<String>();
|
||||
for (Account.Id who : reviewers) {
|
||||
names.add(getNameEmailFor(who));
|
||||
}
|
||||
|
||||
for (String name : names) {
|
||||
appendText("Gerrit-Reviewer: " + name + "\n");
|
||||
}
|
||||
} catch (OrmException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -259,7 +213,7 @@ public abstract class OutgoingEmail {
|
||||
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). */
|
||||
protected void init() {
|
||||
if (change != null && projectCache != null) {
|
||||
projectState = projectCache.get(change.getProject());
|
||||
if (change != null && args.projectCache != null) {
|
||||
changeData = new ChangeData(change);
|
||||
projectState = args.projectCache.get(change.getProject());
|
||||
projectName =
|
||||
projectState != null ? projectState.getProject().getName() : null;
|
||||
} else {
|
||||
changeData = null;
|
||||
projectState = null;
|
||||
projectName = null;
|
||||
}
|
||||
|
||||
smtpFromAddress = fromAddressGenerator.from(fromId);
|
||||
smtpFromAddress = args.fromAddressGenerator.from(fromId);
|
||||
if (changeMessage != null && changeMessage.getWrittenOn() != null) {
|
||||
setHeader("Date", new Date(changeMessage.getWrittenOn().getTime()));
|
||||
} else {
|
||||
@ -313,8 +269,8 @@ public abstract class OutgoingEmail {
|
||||
body = new StringBuilder();
|
||||
inFooter = false;
|
||||
|
||||
if (fromId != null && fromAddressGenerator.isGenericAddress(fromId)) {
|
||||
final Account account = accountCache.get(fromId).getAccount();
|
||||
if (fromId != null && args.fromAddressGenerator.isGenericAddress(fromId)) {
|
||||
final Account account = args.accountCache.get(fromId).getAccount();
|
||||
final String name = account.getFullName();
|
||||
final String email = account.getPreferredEmail();
|
||||
|
||||
@ -331,10 +287,10 @@ public abstract class OutgoingEmail {
|
||||
}
|
||||
}
|
||||
|
||||
if (change != null && db != null) {
|
||||
if (change != null) {
|
||||
if (patchSet == null) {
|
||||
try {
|
||||
patchSet = db.patchSets().get(change.currentPatchSetId());
|
||||
patchSet = args.db.get().patchSets().get(change.currentPatchSetId());
|
||||
} catch (OrmException err) {
|
||||
patchSet = null;
|
||||
}
|
||||
@ -342,7 +298,7 @@ public abstract class OutgoingEmail {
|
||||
|
||||
if (patchSet != null && patchSetInfo == null) {
|
||||
try {
|
||||
patchSetInfo = patchSetInfoFactory.get(patchSet.getId());
|
||||
patchSetInfo = args.patchSetInfoFactory.get(patchSet.getId());
|
||||
} catch (PatchSetInfoNotAvailableException err) {
|
||||
patchSetInfo = null;
|
||||
}
|
||||
@ -439,7 +395,7 @@ public abstract class OutgoingEmail {
|
||||
}
|
||||
|
||||
protected String getGerritUrl() {
|
||||
return urlProvider.get();
|
||||
return args.urlProvider.get();
|
||||
}
|
||||
|
||||
protected String getChangeMessageThreadId() {
|
||||
@ -521,7 +477,7 @@ public abstract class OutgoingEmail {
|
||||
/** Get the patch list corresponding to this patch set. */
|
||||
protected PatchList getPatchList() {
|
||||
if (patchSet != null) {
|
||||
return patchListCache.get(change, patchSet);
|
||||
return args.patchListCache.get(change, patchSet);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -532,7 +488,7 @@ public abstract class OutgoingEmail {
|
||||
return "Anonymous Coward";
|
||||
}
|
||||
|
||||
final Account userAccount = accountCache.get(accountId).getAccount();
|
||||
final Account userAccount = args.accountCache.get(accountId).getAccount();
|
||||
String name = userAccount.getFullName();
|
||||
if (name == null) {
|
||||
name = userAccount.getPreferredEmail();
|
||||
@ -544,7 +500,7 @@ public abstract class OutgoingEmail {
|
||||
}
|
||||
|
||||
private String getNameEmailFor(Account.Id accountId) {
|
||||
AccountState who = accountCache.get(accountId);
|
||||
AccountState who = args.accountCache.get(accountId);
|
||||
String name = who.getAccount().getFullName();
|
||||
String email = who.getAccount().getPreferredEmail();
|
||||
|
||||
@ -594,7 +550,7 @@ public abstract class OutgoingEmail {
|
||||
protected Set<AccountGroup.Id> getProjectOwners() {
|
||||
final ProjectState r;
|
||||
|
||||
r = projectCache.get(change.getProject());
|
||||
r = args.projectCache.get(change.getProject());
|
||||
return r != null ? r.getOwners() : Collections.<AccountGroup.Id> emptySet();
|
||||
}
|
||||
|
||||
@ -625,62 +581,82 @@ public abstract class OutgoingEmail {
|
||||
|
||||
/** BCC any user who has starred this change. */
|
||||
protected void bccStarredBy() {
|
||||
if (db != null) {
|
||||
try {
|
||||
// BCC anyone who has starred this change.
|
||||
//
|
||||
for (StarredChange w : db.starredChanges().byChange(change.getId())) {
|
||||
add(RecipientType.BCC, w.getAccountId());
|
||||
}
|
||||
} catch (OrmException err) {
|
||||
// Just don't BCC everyone. Better to send a partial message to those
|
||||
// we already have queued up then to fail deliver entirely to people
|
||||
// who have a lower interest in the change.
|
||||
try {
|
||||
// BCC anyone who has starred this change.
|
||||
//
|
||||
for (StarredChange w : args.db.get().starredChanges().byChange(
|
||||
change.getId())) {
|
||||
add(RecipientType.BCC, w.getAccountId());
|
||||
}
|
||||
} catch (OrmException err) {
|
||||
// Just don't BCC everyone. Better to send a partial message to those
|
||||
// we already have queued up then to fail deliver entirely to people
|
||||
// who have a lower interest in the change.
|
||||
}
|
||||
}
|
||||
|
||||
/** BCC any user who has set "notify all comments" on this project. */
|
||||
protected void bccWatchesNotifyAllComments() {
|
||||
if (db != null) {
|
||||
try {
|
||||
// BCC anyone else who has interest in this project's changes
|
||||
//
|
||||
final ProjectState ps = getProjectState();
|
||||
if (ps != null) {
|
||||
for (final AccountProjectWatch w : getProjectWatches()) {
|
||||
if (w.isNotifyAllComments()) {
|
||||
add(RecipientType.BCC, w.getAccountId());
|
||||
}
|
||||
}
|
||||
try {
|
||||
// BCC anyone else who has interest in this project's changes
|
||||
//
|
||||
for (final AccountProjectWatch w : getWatches()) {
|
||||
if (w.isNotifyAllComments()) {
|
||||
add(RecipientType.BCC, w.getAccountId());
|
||||
}
|
||||
} catch (OrmException err) {
|
||||
// Just don't CC everyone. Better to send a partial message to those
|
||||
// we already have queued up then to fail deliver entirely to people
|
||||
// who have a lower interest in the change.
|
||||
}
|
||||
} catch (OrmException err) {
|
||||
// Just don't CC everyone. Better to send a partial message to those
|
||||
// we already have queued up then to fail deliver entirely to people
|
||||
// who have a lower interest in the change.
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns all watches that are relevant for this project */
|
||||
final protected Set<AccountProjectWatch> getProjectWatches() throws OrmException {
|
||||
final Set<AccountProjectWatch> projectWatches = new HashSet<AccountProjectWatch>();
|
||||
final Set<Account.Id> projectWatchers = new HashSet<Account.Id>();
|
||||
final ProjectState ps = getProjectState();
|
||||
if (ps != null) {
|
||||
for (final AccountProjectWatch w : db.accountProjectWatches().byProject(ps.getProject().getNameKey())) {
|
||||
projectWatches.add(w);
|
||||
projectWatchers.add(w.getAccountId());
|
||||
}
|
||||
/** Returns all watches that are relevant */
|
||||
protected final List<AccountProjectWatch> getWatches() throws OrmException {
|
||||
if (changeData == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
for (final AccountProjectWatch w : db.accountProjectWatches().byProject(wildProject)) {
|
||||
|
||||
List<AccountProjectWatch> matching = new ArrayList<AccountProjectWatch>();
|
||||
Set<Account.Id> projectWatchers = new HashSet<Account.Id>();
|
||||
|
||||
for (AccountProjectWatch w : args.db.get().accountProjectWatches()
|
||||
.byProject(change.getProject())) {
|
||||
projectWatchers.add(w.getAccountId());
|
||||
add(matching, w);
|
||||
}
|
||||
|
||||
for (AccountProjectWatch w : args.db.get().accountProjectWatches()
|
||||
.byProject(args.wildProject)) {
|
||||
if (!projectWatchers.contains(w.getAccountId())) {
|
||||
// the all projects watch settings are only relevant if the user did not configure
|
||||
// any specific rules for the concrete project
|
||||
projectWatches.add(w);
|
||||
add(matching, w);
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableSet(projectWatches);
|
||||
|
||||
return Collections.unmodifiableList(matching);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void add(List<AccountProjectWatch> matching, AccountProjectWatch w)
|
||||
throws OrmException {
|
||||
IdentifiedUser user =
|
||||
args.identifiedUserFactory.create(args.db, w.getAccountId());
|
||||
ChangeQueryBuilder qb = args.queryBuilder.create(user);
|
||||
Predicate<ChangeData> p = qb.is_visible();
|
||||
if (w.getFilter() != null) {
|
||||
try {
|
||||
p = Predicate.and(qb.parse(w.getFilter()), p);
|
||||
p = args.queryRewriter.get().rewrite(p);
|
||||
if (p.match(changeData)) {
|
||||
matching.add(w);
|
||||
}
|
||||
} catch (QueryParseException e) {
|
||||
// Ignore broken filter expressions.
|
||||
}
|
||||
} else if (p.match(changeData)) {
|
||||
matching.add(w);
|
||||
}
|
||||
}
|
||||
|
||||
/** Any user who has published comments on this change. */
|
||||
@ -694,19 +670,17 @@ public abstract class OutgoingEmail {
|
||||
}
|
||||
|
||||
private void ccApprovals(final boolean includeZero) {
|
||||
if (db != null) {
|
||||
try {
|
||||
// CC anyone else who has posted an approval mark on this change
|
||||
//
|
||||
for (PatchSetApproval ap : db.patchSetApprovals().byChange(
|
||||
change.getId())) {
|
||||
if (!includeZero && ap.getValue() == 0) {
|
||||
continue;
|
||||
}
|
||||
add(RecipientType.CC, ap.getAccountId());
|
||||
try {
|
||||
// CC anyone else who has posted an approval mark on this change
|
||||
//
|
||||
for (PatchSetApproval ap : args.db.get().patchSetApprovals().byChange(
|
||||
change.getId())) {
|
||||
if (!includeZero && ap.getValue() == 0) {
|
||||
continue;
|
||||
}
|
||||
} catch (OrmException err) {
|
||||
add(RecipientType.CC, ap.getAccountId());
|
||||
}
|
||||
} catch (OrmException err) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -721,14 +695,14 @@ public abstract class OutgoingEmail {
|
||||
private boolean isVisibleTo(final Account.Id to) {
|
||||
return projectState == null
|
||||
|| change == null
|
||||
|| projectState.controlFor(identifiedUserFactory.create(to))
|
||||
|| projectState.controlFor(args.identifiedUserFactory.create(to))
|
||||
.controlFor(change).isVisible();
|
||||
}
|
||||
|
||||
/** Schedule delivery of this message to the given account. */
|
||||
protected void add(final RecipientType rt, final Address addr) {
|
||||
if (addr != null && addr.email != null && addr.email.length() > 0) {
|
||||
if (emailSender.canEmail(addr.email)) {
|
||||
if (args.emailSender.canEmail(addr.email)) {
|
||||
smtpRcptTo.add(addr);
|
||||
switch (rt) {
|
||||
case TO:
|
||||
@ -745,7 +719,7 @@ public abstract class OutgoingEmail {
|
||||
}
|
||||
|
||||
private Address toAddress(final Account.Id id) {
|
||||
final Account a = accountCache.get(id).getAccount();
|
||||
final Account a = args.accountCache.get(id).getAccount();
|
||||
final String e = a.getPreferredEmail();
|
||||
if (e == null) {
|
||||
return null;
|
||||
|
@ -28,14 +28,14 @@ public class RegisterNewEmailSender extends OutgoingEmail {
|
||||
public RegisterNewEmailSender create(String address);
|
||||
}
|
||||
|
||||
private final AuthConfig authConfig;
|
||||
private final String addr;
|
||||
|
||||
@Inject
|
||||
private AuthConfig authConfig;
|
||||
|
||||
@Inject
|
||||
public RegisterNewEmailSender(@Assisted final String address) {
|
||||
super("registernewemail");
|
||||
public RegisterNewEmailSender(EmailArguments ea, AuthConfig ac,
|
||||
@Assisted final String address) {
|
||||
super(ea, "registernewemail");
|
||||
authConfig = ac;
|
||||
addr = address;
|
||||
}
|
||||
|
||||
|
@ -34,15 +34,14 @@ public class ReplacePatchSetSender extends ReplyToChangeSender {
|
||||
public ReplacePatchSetSender create(Change change);
|
||||
}
|
||||
|
||||
@Inject
|
||||
private SshInfo sshInfo;
|
||||
|
||||
private final Set<Account.Id> reviewers = new HashSet<Account.Id>();
|
||||
private final Set<Account.Id> extraCC = new HashSet<Account.Id>();
|
||||
private final SshInfo sshInfo;
|
||||
|
||||
@Inject
|
||||
public ReplacePatchSetSender(@Assisted Change c) {
|
||||
super(c, "newpatchset");
|
||||
public ReplacePatchSetSender(EmailArguments ea, SshInfo si, @Assisted Change c) {
|
||||
super(ea, c, "newpatchset");
|
||||
sshInfo = si;
|
||||
}
|
||||
|
||||
public void addReviewers(final Collection<Account.Id> cc) {
|
||||
|
@ -18,8 +18,8 @@ import com.google.gerrit.reviewdb.Change;
|
||||
|
||||
/** Alert a user to a reply to a change, usually commentary made during review. */
|
||||
public abstract class ReplyToChangeSender extends OutgoingEmail {
|
||||
protected ReplyToChangeSender(Change c, String mc) {
|
||||
super(c, mc);
|
||||
protected ReplyToChangeSender(EmailArguments ea, Change c, String mc) {
|
||||
super(ea, c, mc);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -284,7 +284,6 @@ public class PublishComments implements Callable<VoidResult> {
|
||||
cm.setPatchSet(patchSet, patchSetInfoFactory.get(patchSetId));
|
||||
cm.setChangeMessage(message);
|
||||
cm.setPatchLineComments(drafts);
|
||||
cm.setReviewDb(db);
|
||||
cm.send();
|
||||
} catch (EmailException e) {
|
||||
log.error("Cannot send comments by email for patch set " + patchSetId, e);
|
||||
|
@ -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.VARIABLE_ASSIGN;
|
||||
|
||||
import com.google.gerrit.server.query.change.ChangeData;
|
||||
|
||||
import org.antlr.runtime.tree.Tree;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
@ -239,6 +237,29 @@ public abstract class QueryBuilder<T> {
|
||||
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.
|
||||
*
|
||||
|
@ -35,7 +35,7 @@ import com.google.gerrit.server.query.QueryParseException;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.servlet.RequestScoped;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
import org.eclipse.jgit.lib.AbbreviatedObjectId;
|
||||
|
||||
@ -46,7 +46,6 @@ import java.util.regex.Pattern;
|
||||
/**
|
||||
* Parses a query string meant to be applied to change objects.
|
||||
*/
|
||||
@RequestScoped
|
||||
public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
private static final Pattern PAT_LEGACY_ID = Pattern.compile("^[1-9][0-9]*$");
|
||||
private static final Pattern PAT_CHANGE_ID =
|
||||
@ -86,55 +85,67 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
new QueryBuilder.Definition<ChangeData, ChangeQueryBuilder>(
|
||||
ChangeQueryBuilder.class);
|
||||
|
||||
private final Provider<ReviewDb> dbProvider;
|
||||
private final Provider<CurrentUser> currentUser;
|
||||
private final IdentifiedUser.GenericFactory userFactory;
|
||||
private final ChangeControl.Factory changeControlFactory;
|
||||
private final AccountResolver accountResolver;
|
||||
private final GroupCache groupCache;
|
||||
private final AuthConfig authConfig;
|
||||
private final ApprovalTypes approvalTypes;
|
||||
private final Project.NameKey wildProjectName;
|
||||
static class Arguments {
|
||||
final Provider<ReviewDb> dbProvider;
|
||||
final Provider<ChangeQueryRewriter> rewriter;
|
||||
final IdentifiedUser.GenericFactory userFactory;
|
||||
final ChangeControl.Factory changeControlFactory;
|
||||
final AccountResolver accountResolver;
|
||||
final GroupCache groupCache;
|
||||
final AuthConfig authConfig;
|
||||
final ApprovalTypes approvalTypes;
|
||||
final Project.NameKey wildProjectName;
|
||||
|
||||
@Inject
|
||||
ChangeQueryBuilder(Provider<ReviewDb> dbProvider,
|
||||
Provider<CurrentUser> currentUser,
|
||||
IdentifiedUser.GenericFactory userFactory,
|
||||
ChangeControl.Factory changeControlFactory,
|
||||
AccountResolver accountResolver, GroupCache groupCache,
|
||||
AuthConfig authConfig, ApprovalTypes approvalTypes,
|
||||
@WildProjectName Project.NameKey wildProjectName) {
|
||||
super(mydef);
|
||||
this.dbProvider = dbProvider;
|
||||
this.currentUser = currentUser;
|
||||
this.userFactory = userFactory;
|
||||
this.changeControlFactory = changeControlFactory;
|
||||
this.accountResolver = accountResolver;
|
||||
this.groupCache = groupCache;
|
||||
this.authConfig = authConfig;
|
||||
this.approvalTypes = approvalTypes;
|
||||
this.wildProjectName = wildProjectName;
|
||||
@Inject
|
||||
Arguments(Provider<ReviewDb> dbProvider,
|
||||
Provider<ChangeQueryRewriter> rewriter,
|
||||
IdentifiedUser.GenericFactory userFactory,
|
||||
ChangeControl.Factory changeControlFactory,
|
||||
AccountResolver accountResolver, GroupCache groupCache,
|
||||
AuthConfig authConfig, ApprovalTypes approvalTypes,
|
||||
@WildProjectName Project.NameKey wildProjectName) {
|
||||
this.dbProvider = dbProvider;
|
||||
this.rewriter = rewriter;
|
||||
this.userFactory = userFactory;
|
||||
this.changeControlFactory = changeControlFactory;
|
||||
this.accountResolver = accountResolver;
|
||||
this.groupCache = groupCache;
|
||||
this.authConfig = authConfig;
|
||||
this.approvalTypes = approvalTypes;
|
||||
this.wildProjectName = wildProjectName;
|
||||
}
|
||||
}
|
||||
|
||||
Provider<ReviewDb> getReviewDbProvider() {
|
||||
return dbProvider;
|
||||
public interface Factory {
|
||||
ChangeQueryBuilder create(CurrentUser user);
|
||||
}
|
||||
|
||||
private final Arguments args;
|
||||
private final CurrentUser currentUser;
|
||||
|
||||
@Inject
|
||||
ChangeQueryBuilder(Arguments args, @Assisted CurrentUser currentUser) {
|
||||
super(mydef);
|
||||
this.args = args;
|
||||
this.currentUser = currentUser;
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> age(String value) {
|
||||
return new AgePredicate(dbProvider, value);
|
||||
return new AgePredicate(args.dbProvider, value);
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> change(String query) {
|
||||
if (PAT_LEGACY_ID.matcher(query).matches()) {
|
||||
return new LegacyChangeIdPredicate(dbProvider, Change.Id.parse(query));
|
||||
return new LegacyChangeIdPredicate(args.dbProvider, Change.Id
|
||||
.parse(query));
|
||||
|
||||
} else if (PAT_CHANGE_ID.matcher(query).matches()) {
|
||||
if (query.charAt(0) == 'i') {
|
||||
query = "I" + query.substring(1);
|
||||
}
|
||||
return new ChangeIdPredicate(dbProvider, query);
|
||||
return new ChangeIdPredicate(args.dbProvider, query);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException();
|
||||
@ -146,30 +157,30 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
return status_open();
|
||||
|
||||
} else if ("closed".equals(statusName)) {
|
||||
return ChangeStatusPredicate.closed(dbProvider);
|
||||
return ChangeStatusPredicate.closed(args.dbProvider);
|
||||
|
||||
} else if ("reviewed".equalsIgnoreCase(statusName)) {
|
||||
return new IsReviewedPredicate(dbProvider);
|
||||
return new IsReviewedPredicate(args.dbProvider);
|
||||
|
||||
} else {
|
||||
return new ChangeStatusPredicate(dbProvider, statusName);
|
||||
return new ChangeStatusPredicate(args.dbProvider, statusName);
|
||||
}
|
||||
}
|
||||
|
||||
public Predicate<ChangeData> status_open() {
|
||||
return ChangeStatusPredicate.open(dbProvider);
|
||||
return ChangeStatusPredicate.open(args.dbProvider);
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> has(String value) {
|
||||
if ("star".equalsIgnoreCase(value)) {
|
||||
return new IsStarredByPredicate(dbProvider, currentUser.get());
|
||||
return new IsStarredByPredicate(args.dbProvider, currentUser);
|
||||
}
|
||||
|
||||
if ("draft".equalsIgnoreCase(value)) {
|
||||
if (currentUser.get() instanceof IdentifiedUser) {
|
||||
return new HasDraftByPredicate(dbProvider,
|
||||
((IdentifiedUser) currentUser.get()).getAccountId());
|
||||
if (currentUser instanceof IdentifiedUser) {
|
||||
return new HasDraftByPredicate(args.dbProvider,
|
||||
((IdentifiedUser) currentUser).getAccountId());
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,21 +190,19 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
@Operator
|
||||
public Predicate<ChangeData> is(String value) {
|
||||
if ("starred".equalsIgnoreCase(value)) {
|
||||
return new IsStarredByPredicate(dbProvider, currentUser.get());
|
||||
return new IsStarredByPredicate(args.dbProvider, currentUser);
|
||||
}
|
||||
|
||||
if ("watched".equalsIgnoreCase(value)) {
|
||||
return new IsWatchedByPredicate(dbProvider, wildProjectName, //
|
||||
currentUser.get());
|
||||
return new IsWatchedByPredicate(args, currentUser);
|
||||
}
|
||||
|
||||
if ("visible".equalsIgnoreCase(value)) {
|
||||
return new IsVisibleToPredicate(dbProvider, changeControlFactory,
|
||||
currentUser.get());
|
||||
return is_visible();
|
||||
}
|
||||
|
||||
if ("reviewed".equalsIgnoreCase(value)) {
|
||||
return new IsReviewedPredicate(dbProvider);
|
||||
return new IsReviewedPredicate(args.dbProvider);
|
||||
}
|
||||
|
||||
try {
|
||||
@ -207,121 +216,129 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> commit(String id) {
|
||||
return new CommitPredicate(dbProvider, AbbreviatedObjectId.fromString(id));
|
||||
return new CommitPredicate(args.dbProvider, AbbreviatedObjectId
|
||||
.fromString(id));
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> project(String name) {
|
||||
return new ProjectPredicate(dbProvider, name);
|
||||
return new ProjectPredicate(args.dbProvider, name);
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> branch(String name) {
|
||||
return new BranchPredicate(dbProvider, name);
|
||||
return new BranchPredicate(args.dbProvider, name);
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> topic(String name) {
|
||||
return new TopicPredicate(dbProvider, name);
|
||||
return new TopicPredicate(args.dbProvider, name);
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> ref(String ref) {
|
||||
return new RefPredicate(dbProvider, ref);
|
||||
return new RefPredicate(args.dbProvider, ref);
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> label(String name) {
|
||||
return new LabelPredicate(dbProvider, approvalTypes, name);
|
||||
return new LabelPredicate(args.dbProvider, args.approvalTypes, name);
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> starredby(String who)
|
||||
throws QueryParseException, OrmException {
|
||||
Account account = accountResolver.find(who);
|
||||
Account account = args.accountResolver.find(who);
|
||||
if (account == null) {
|
||||
throw error("User " + who + " not found");
|
||||
}
|
||||
return new IsStarredByPredicate(dbProvider, //
|
||||
userFactory.create(dbProvider, account.getId()));
|
||||
return new IsStarredByPredicate(args.dbProvider, //
|
||||
args.userFactory.create(args.dbProvider, account.getId()));
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> watchedby(String who)
|
||||
throws QueryParseException, OrmException {
|
||||
Account account = accountResolver.find(who);
|
||||
Account account = args.accountResolver.find(who);
|
||||
if (account == null) {
|
||||
throw error("User " + who + " not found");
|
||||
}
|
||||
return new IsWatchedByPredicate(dbProvider, wildProjectName, //
|
||||
userFactory.create(dbProvider, account.getId()));
|
||||
return new IsWatchedByPredicate(args, args.userFactory.create(
|
||||
args.dbProvider, account.getId()));
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> draftby(String who) throws QueryParseException,
|
||||
OrmException {
|
||||
Account account = accountResolver.find(who);
|
||||
Account account = args.accountResolver.find(who);
|
||||
if (account == null) {
|
||||
throw error("User " + who + " not found");
|
||||
}
|
||||
return new HasDraftByPredicate(dbProvider, account.getId());
|
||||
return new HasDraftByPredicate(args.dbProvider, account.getId());
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> visibleto(String who)
|
||||
throws QueryParseException, OrmException {
|
||||
Account account = accountResolver.find(who);
|
||||
Account account = args.accountResolver.find(who);
|
||||
if (account != null) {
|
||||
return visibleto(userFactory.create(dbProvider, account.getId()));
|
||||
return visibleto(args.userFactory
|
||||
.create(args.dbProvider, account.getId()));
|
||||
}
|
||||
|
||||
// If its not an account, maybe its a group?
|
||||
//
|
||||
AccountGroup g = groupCache.get(new AccountGroup.NameKey(who));
|
||||
AccountGroup g = args.groupCache.get(new AccountGroup.NameKey(who));
|
||||
if (g != null) {
|
||||
return visibleto(new SingleGroupUser(authConfig, g.getId()));
|
||||
return visibleto(new SingleGroupUser(args.authConfig, g.getId()));
|
||||
}
|
||||
|
||||
Collection<AccountGroup> matches =
|
||||
groupCache.get(new AccountGroup.ExternalNameKey(who));
|
||||
args.groupCache.get(new AccountGroup.ExternalNameKey(who));
|
||||
if (matches != null && !matches.isEmpty()) {
|
||||
HashSet<AccountGroup.Id> ids = new HashSet<AccountGroup.Id>();
|
||||
for (AccountGroup group : matches) {
|
||||
ids.add(group.getId());
|
||||
}
|
||||
return visibleto(new SingleGroupUser(authConfig, ids));
|
||||
return visibleto(new SingleGroupUser(args.authConfig, ids));
|
||||
}
|
||||
|
||||
throw error("No user or group matches \"" + who + "\".");
|
||||
}
|
||||
|
||||
public Predicate<ChangeData> visibleto(CurrentUser user) {
|
||||
return new IsVisibleToPredicate(dbProvider, changeControlFactory, user);
|
||||
return new IsVisibleToPredicate(args.dbProvider, //
|
||||
args.changeControlFactory, //
|
||||
user);
|
||||
}
|
||||
|
||||
public Predicate<ChangeData> is_visible() {
|
||||
return visibleto(currentUser);
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> owner(String who) throws QueryParseException,
|
||||
OrmException {
|
||||
Account account = accountResolver.find(who);
|
||||
Account account = args.accountResolver.find(who);
|
||||
if (account == null) {
|
||||
throw error("User " + who + " not found");
|
||||
}
|
||||
return new OwnerPredicate(dbProvider, account.getId());
|
||||
return new OwnerPredicate(args.dbProvider, account.getId());
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> reviewer(String nameOrEmail)
|
||||
throws QueryParseException, OrmException {
|
||||
Account account = accountResolver.find(nameOrEmail);
|
||||
Account account = args.accountResolver.find(nameOrEmail);
|
||||
if (account == null) {
|
||||
throw error("Reviewer " + nameOrEmail + " not found");
|
||||
}
|
||||
return new ReviewerPredicate(dbProvider, account.getId());
|
||||
return new ReviewerPredicate(args.dbProvider, account.getId());
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> tr(String trackingId) {
|
||||
return new TrackingIdPredicate(dbProvider, trackingId);
|
||||
return new TrackingIdPredicate(args.dbProvider, trackingId);
|
||||
}
|
||||
|
||||
@Operator
|
||||
@ -350,12 +367,12 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> sortkey_after(String sortKey) {
|
||||
return new SortKeyPredicate.After(dbProvider, sortKey);
|
||||
return new SortKeyPredicate.After(args.dbProvider, sortKey);
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> sortkey_before(String sortKey) {
|
||||
return new SortKeyPredicate.Before(dbProvider, sortKey);
|
||||
return new SortKeyPredicate.Before(args.dbProvider, sortKey);
|
||||
}
|
||||
|
||||
@Operator
|
||||
|
@ -18,7 +18,6 @@ import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.reviewdb.ChangeAccess;
|
||||
import com.google.gerrit.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.server.ChangeUtil;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.query.IntPredicate;
|
||||
import com.google.gerrit.server.query.Predicate;
|
||||
import com.google.gerrit.server.query.QueryRewriter;
|
||||
@ -29,18 +28,18 @@ import com.google.inject.Inject;
|
||||
import com.google.inject.OutOfScopeException;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.name.Named;
|
||||
import com.google.inject.servlet.RequestScoped;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@RequestScoped
|
||||
public class ChangeQueryRewriter extends QueryRewriter<ChangeData> {
|
||||
private static final QueryRewriter.Definition<ChangeData, ChangeQueryRewriter> mydef =
|
||||
new QueryRewriter.Definition<ChangeData, ChangeQueryRewriter>(
|
||||
ChangeQueryRewriter.class, new ChangeQueryBuilder(
|
||||
new InvalidProvider<ReviewDb>(),
|
||||
new InvalidProvider<CurrentUser>(), //
|
||||
null, null, null, null, null, null, null));
|
||||
new ChangeQueryBuilder.Arguments( //
|
||||
new InvalidProvider<ReviewDb>(), //
|
||||
new InvalidProvider<ChangeQueryRewriter>(), //
|
||||
null, null, null, null, null, null, null),
|
||||
null));
|
||||
|
||||
private final Provider<ReviewDb> dbProvider;
|
||||
|
||||
|
@ -14,18 +14,20 @@
|
||||
|
||||
package com.google.gerrit.server.query.change;
|
||||
|
||||
import com.google.gerrit.reviewdb.AccountProjectWatch;
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.reviewdb.Project;
|
||||
import com.google.gerrit.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.reviewdb.Project.NameKey;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.config.WildProjectName;
|
||||
import com.google.gerrit.server.query.OperatorPredicate;
|
||||
import com.google.gerrit.server.query.Predicate;
|
||||
import com.google.gerrit.server.query.QueryParseException;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class IsWatchedByPredicate extends OperatorPredicate<ChangeData> {
|
||||
private static String describe(CurrentUser user) {
|
||||
@ -35,32 +37,81 @@ class IsWatchedByPredicate extends OperatorPredicate<ChangeData> {
|
||||
return user.toString();
|
||||
}
|
||||
|
||||
private final Provider<ReviewDb> db;
|
||||
private final Project.NameKey wildProject;
|
||||
private final ChangeQueryBuilder.Arguments args;
|
||||
private final CurrentUser user;
|
||||
|
||||
IsWatchedByPredicate(Provider<ReviewDb> db,
|
||||
@WildProjectName Project.NameKey wildProject, CurrentUser user) {
|
||||
private Map<Project.NameKey, List<Predicate<ChangeData>>> rules;
|
||||
|
||||
IsWatchedByPredicate(ChangeQueryBuilder.Arguments args, CurrentUser user) {
|
||||
super(ChangeQueryBuilder.FIELD_WATCHEDBY, describe(user));
|
||||
this.db = db;
|
||||
this.wildProject = wildProject;
|
||||
this.args = args;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(final ChangeData cd) throws OrmException {
|
||||
Set<NameKey> watched = user.getWatchedProjects();
|
||||
if (watched.contains(wildProject)) {
|
||||
return true;
|
||||
if (rules == null) {
|
||||
ChangeQueryBuilder builder = new ChangeQueryBuilder(args, user);
|
||||
rules = new HashMap<Project.NameKey, List<Predicate<ChangeData>>>();
|
||||
for (AccountProjectWatch w : user.getNotificationFilters()) {
|
||||
List<Predicate<ChangeData>> list = rules.get(w.getProjectNameKey());
|
||||
if (list == null) {
|
||||
list = new ArrayList<Predicate<ChangeData>>(4);
|
||||
rules.put(w.getProjectNameKey(), list);
|
||||
}
|
||||
|
||||
Predicate<ChangeData> p = compile(builder, w);
|
||||
if (p != null) {
|
||||
list.add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Change change = cd.change(db);
|
||||
if (rules.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Change change = cd.change(args.dbProvider);
|
||||
if (change == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Project.NameKey project = change.getDest().getParentKey();
|
||||
return watched.contains(project);
|
||||
List<Predicate<ChangeData>> list = rules.get(project);
|
||||
if (list == null) {
|
||||
list = rules.get(args.wildProjectName);
|
||||
}
|
||||
if (list != null) {
|
||||
for (Predicate<ChangeData> p : list) {
|
||||
if (p.match(cd)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Predicate<ChangeData> compile(ChangeQueryBuilder builder,
|
||||
AccountProjectWatch w) {
|
||||
Predicate<ChangeData> p = builder.is_visible();
|
||||
if (w.getFilter() != null) {
|
||||
try {
|
||||
p = Predicate.and(builder.parse(w.getFilter()), p);
|
||||
if (builder.find(p, IsWatchedByPredicate.class) != null) {
|
||||
// If the query is going to infinite loop, assume it
|
||||
// will never match and return null. Yes this test
|
||||
// prevents you from having a filter that matches what
|
||||
// another user is filtering on. :-)
|
||||
//
|
||||
return null;
|
||||
}
|
||||
p = args.rewriter.get().rewrite(p);
|
||||
} catch (QueryParseException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -60,7 +60,6 @@ public class QueryProcessor {
|
||||
private final SimpleDateFormat sdf =
|
||||
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss zzz");
|
||||
|
||||
private final CurrentUser currentUser;
|
||||
private final EventFactory eventFactory;
|
||||
private final ChangeQueryBuilder queryBuilder;
|
||||
private final ChangeQueryRewriter queryRewriter;
|
||||
@ -75,12 +74,11 @@ public class QueryProcessor {
|
||||
private PrintWriter out;
|
||||
|
||||
@Inject
|
||||
QueryProcessor(CurrentUser currentUser, EventFactory eventFactory,
|
||||
ChangeQueryBuilder queryBuilder, ChangeQueryRewriter queryRewriter,
|
||||
Provider<ReviewDb> db) {
|
||||
this.currentUser = currentUser;
|
||||
QueryProcessor(EventFactory eventFactory,
|
||||
ChangeQueryBuilder.Factory queryBuilder, CurrentUser currentUser,
|
||||
ChangeQueryRewriter queryRewriter, Provider<ReviewDb> db) {
|
||||
this.eventFactory = eventFactory;
|
||||
this.queryBuilder = queryBuilder;
|
||||
this.queryBuilder = queryBuilder.create(currentUser);
|
||||
this.queryRewriter = queryRewriter;
|
||||
this.db = db;
|
||||
}
|
||||
@ -107,9 +105,7 @@ public class QueryProcessor {
|
||||
final QueryStats stats = new QueryStats();
|
||||
stats.runTimeMilliseconds = System.currentTimeMillis();
|
||||
|
||||
final Predicate<ChangeData> visibleToMe =
|
||||
queryBuilder.visibleto(currentUser);
|
||||
|
||||
final Predicate<ChangeData> visibleToMe = queryBuilder.is_visible();
|
||||
Predicate<ChangeData> s = compileQuery(queryString, visibleToMe);
|
||||
List<ChangeData> results = new ArrayList<ChangeData>();
|
||||
HashSet<Change.Id> want = new HashSet<Change.Id>();
|
||||
|
@ -15,12 +15,14 @@
|
||||
package com.google.gerrit.server.query.change;
|
||||
|
||||
import com.google.gerrit.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.reviewdb.AccountProjectWatch;
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.reviewdb.Project;
|
||||
import com.google.gerrit.server.AccessPath;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.config.AuthConfig;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
@ -47,7 +49,7 @@ final class SingleGroupUser extends CurrentUser {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Project.NameKey> getWatchedProjects() {
|
||||
public Collection<AccountProjectWatch> getNotificationFilters() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ import java.util.List;
|
||||
/** A version of the database schema. */
|
||||
public abstract class SchemaVersion {
|
||||
/** The current schema version. */
|
||||
private static final Class<? extends SchemaVersion> C = Schema_39.class;
|
||||
private static final Class<? extends SchemaVersion> C = Schema_40.class;
|
||||
|
||||
public static class Module extends AbstractModule {
|
||||
@Override
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ import com.google.gerrit.reviewdb.RevId;
|
||||
import com.google.gerrit.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.server.ChangeUtil;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.git.MergeOp;
|
||||
import com.google.gerrit.server.git.MergeQueue;
|
||||
import com.google.gerrit.server.patch.PublishComments;
|
||||
import com.google.gerrit.server.project.CanSubmitResult;
|
||||
@ -95,6 +96,9 @@ public class ReviewCommand extends BaseCommand {
|
||||
@Inject
|
||||
private MergeQueue merger;
|
||||
|
||||
@Inject
|
||||
private MergeOp.Factory opFactory;
|
||||
|
||||
@Inject
|
||||
private ApprovalTypes approvalTypes;
|
||||
|
||||
@ -166,7 +170,7 @@ public class ReviewCommand extends BaseCommand {
|
||||
changeControl.canSubmit(patchSetId, db, approvalTypes,
|
||||
functionStateFactory);
|
||||
if (result == CanSubmitResult.OK) {
|
||||
ChangeUtil.submit(patchSetId, currentUser, db, merger);
|
||||
ChangeUtil.submit(opFactory, patchSetId, currentUser, db, merger);
|
||||
} else {
|
||||
throw error(result.getMessage());
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user