Pass notification settings through BatchUpdate's Context
Previously, any notification settings that a BatchUpdateOp wanted to respect in its postUpdate method needed to be resolved from the corresponding REST API input and plumbed through the Op constructor so they could be looked up as needed. Explicit can be good, but this verged into the territory of _too_ explicit. Instead, treat the notification settings as a property of the BatchUpdate. This is somewhat magical, but in this case the magic is justified. Conceptually, associating notification settings with the BatchUpdate makes sense: there is a single set of notification settings that is parsed at the top level from the input, and those settings should apply to *all* emails sent as a result of that BatchUpdate, regardless of the particulars of where they're sent from. Putting them in the Context allows us to treat them as a per-BatchUpdate singleton. We retain the ability to override the NotifyHandling enum on a per-*change* basis, rather than a per-*op* basis, to account for the reduced notifications from WIP changes. Without this change, there's always the possibility of having multiple Ops in the same BatchUpdate that send different emails with different notification settings. For example, we had special code in PostReview to ignore the notification settings embedded in the constituent AddReviewerInputs of a ReviewInput. After this change, we still ignore those settings (as the REST API docs specify), but we don't have to do anything special to do so: we just set the NotifyResolver.Result on the BatchUpdate to the one from the ReviewInput, completely ignoring the AddReviewerInputs. This change also has the effect of removing any logic around notification settings from ReviewerAdder; all the notification logic is in the caller. We still retain a separate mechanism for suppressing all emails from ReviewerAdder, so that PostReview can send only a single email. However, this is stored as a simple boolean, rather than passing around a whole NotifyResolver.Result. It's still not completely ideal, but considering everything else ReviewerAdder has to deal with, it's a small win. Plus, simplifying ReviewerAdder will pave the way for rewriting it in the near future. One downside here is that any check to set NotifyHandling based on properties of the change (e.g. the WIP bit) is racy, since the change was read outside of the BatchUpdate. The risk and consequences of such a race are low enough that the benefits described above still outweigh it. (And it's not like it's the only such race, even though we do try to keep them to a minimum.) This change should have minimal behavior changes. One exception is that moving around calls to NotifyResolver#resolve from the body of a try block to the top level of a REST API handler might result in a 400/422 for invalid input in notify_details rather than logging and silently not sending an email. Some endpoints already had this behavior, so making the failure explicit and consistent is considered a feature. Change-Id: If6f8a44f382f57b9cb10490f74c2b144e904ece8
This commit is contained in:
@@ -45,7 +45,6 @@ public class AbandonOp implements BatchUpdateOp {
|
|||||||
private final ChangeAbandoned changeAbandoned;
|
private final ChangeAbandoned changeAbandoned;
|
||||||
|
|
||||||
private final String msgTxt;
|
private final String msgTxt;
|
||||||
private final NotifyResolver.Result notify;
|
|
||||||
private final AccountState accountState;
|
private final AccountState accountState;
|
||||||
|
|
||||||
private Change change;
|
private Change change;
|
||||||
@@ -54,9 +53,7 @@ public class AbandonOp implements BatchUpdateOp {
|
|||||||
|
|
||||||
public interface Factory {
|
public interface Factory {
|
||||||
AbandonOp create(
|
AbandonOp create(
|
||||||
@Assisted @Nullable AccountState accountState,
|
@Assisted @Nullable AccountState accountState, @Assisted @Nullable String msgTxt);
|
||||||
@Assisted @Nullable String msgTxt,
|
|
||||||
@Assisted NotifyResolver.Result notify);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -66,8 +63,7 @@ public class AbandonOp implements BatchUpdateOp {
|
|||||||
PatchSetUtil psUtil,
|
PatchSetUtil psUtil,
|
||||||
ChangeAbandoned changeAbandoned,
|
ChangeAbandoned changeAbandoned,
|
||||||
@Assisted @Nullable AccountState accountState,
|
@Assisted @Nullable AccountState accountState,
|
||||||
@Assisted @Nullable String msgTxt,
|
@Assisted @Nullable String msgTxt) {
|
||||||
@Assisted NotifyResolver.Result notify) {
|
|
||||||
this.abandonedSenderFactory = abandonedSenderFactory;
|
this.abandonedSenderFactory = abandonedSenderFactory;
|
||||||
this.cmUtil = cmUtil;
|
this.cmUtil = cmUtil;
|
||||||
this.psUtil = psUtil;
|
this.psUtil = psUtil;
|
||||||
@@ -75,7 +71,6 @@ public class AbandonOp implements BatchUpdateOp {
|
|||||||
|
|
||||||
this.accountState = accountState;
|
this.accountState = accountState;
|
||||||
this.msgTxt = Strings.nullToEmpty(msgTxt);
|
this.msgTxt = Strings.nullToEmpty(msgTxt);
|
||||||
this.notify = notify;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -114,6 +109,7 @@ public class AbandonOp implements BatchUpdateOp {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postUpdate(Context ctx) throws OrmException {
|
public void postUpdate(Context ctx) throws OrmException {
|
||||||
|
NotifyResolver.Result notify = ctx.getNotify(change.getId());
|
||||||
try {
|
try {
|
||||||
ReplyToChangeSender cm = abandonedSenderFactory.create(ctx.getProject(), change.getId());
|
ReplyToChangeSender cm = abandonedSenderFactory.create(ctx.getProject(), change.getId());
|
||||||
if (accountState != null) {
|
if (accountState != null) {
|
||||||
|
|||||||
@@ -65,14 +65,10 @@ public class AddReviewersOp implements BatchUpdateOp {
|
|||||||
* @param accountIds account IDs to add.
|
* @param accountIds account IDs to add.
|
||||||
* @param addresses email addresses to add.
|
* @param addresses email addresses to add.
|
||||||
* @param state resulting reviewer state.
|
* @param state resulting reviewer state.
|
||||||
* @param notify notification handling.
|
|
||||||
* @return batch update operation.
|
* @return batch update operation.
|
||||||
*/
|
*/
|
||||||
AddReviewersOp create(
|
AddReviewersOp create(
|
||||||
Set<Account.Id> accountIds,
|
Set<Account.Id> accountIds, Collection<Address> addresses, ReviewerState state);
|
||||||
Collection<Address> addresses,
|
|
||||||
ReviewerState state,
|
|
||||||
NotifyResolver.Result notify);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@AutoValue
|
@AutoValue
|
||||||
@@ -112,7 +108,6 @@ public class AddReviewersOp implements BatchUpdateOp {
|
|||||||
private final Set<Account.Id> accountIds;
|
private final Set<Account.Id> accountIds;
|
||||||
private final Collection<Address> addresses;
|
private final Collection<Address> addresses;
|
||||||
private final ReviewerState state;
|
private final ReviewerState state;
|
||||||
private final NotifyResolver.Result notify;
|
|
||||||
|
|
||||||
// Unlike addedCCs, addedReviewers is a PatchSetApproval because the AddReviewerResult returned
|
// Unlike addedCCs, addedReviewers is a PatchSetApproval because the AddReviewerResult returned
|
||||||
// via the REST API is supposed to include vote information.
|
// via the REST API is supposed to include vote information.
|
||||||
@@ -121,6 +116,7 @@ public class AddReviewersOp implements BatchUpdateOp {
|
|||||||
private Collection<Account.Id> addedCCs = ImmutableList.of();
|
private Collection<Account.Id> addedCCs = ImmutableList.of();
|
||||||
private Collection<Address> addedCCsByEmail = ImmutableList.of();
|
private Collection<Address> addedCCsByEmail = ImmutableList.of();
|
||||||
|
|
||||||
|
private boolean sendEmail = true;
|
||||||
private Change change;
|
private Change change;
|
||||||
private PatchSet patchSet;
|
private PatchSet patchSet;
|
||||||
private Result opResult;
|
private Result opResult;
|
||||||
@@ -135,8 +131,7 @@ public class AddReviewersOp implements BatchUpdateOp {
|
|||||||
AddReviewersEmail addReviewersEmail,
|
AddReviewersEmail addReviewersEmail,
|
||||||
@Assisted Set<Account.Id> accountIds,
|
@Assisted Set<Account.Id> accountIds,
|
||||||
@Assisted Collection<Address> addresses,
|
@Assisted Collection<Address> addresses,
|
||||||
@Assisted ReviewerState state,
|
@Assisted ReviewerState state) {
|
||||||
@Assisted NotifyResolver.Result notify) {
|
|
||||||
checkArgument(state == REVIEWER || state == CC, "must be %s or %s: %s", REVIEWER, CC, state);
|
checkArgument(state == REVIEWER || state == CC, "must be %s or %s: %s", REVIEWER, CC, state);
|
||||||
this.approvalsUtil = approvalsUtil;
|
this.approvalsUtil = approvalsUtil;
|
||||||
this.psUtil = psUtil;
|
this.psUtil = psUtil;
|
||||||
@@ -148,7 +143,13 @@ public class AddReviewersOp implements BatchUpdateOp {
|
|||||||
this.accountIds = accountIds;
|
this.accountIds = accountIds;
|
||||||
this.addresses = addresses;
|
this.addresses = addresses;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.notify = notify;
|
}
|
||||||
|
|
||||||
|
// TODO(dborowitz): This mutable setter is ugly, but a) it's less ugly than adding boolean args
|
||||||
|
// all the way through the constructor stack, and b) this class is slated to be completely
|
||||||
|
// rewritten.
|
||||||
|
public void suppressEmail() {
|
||||||
|
this.sendEmail = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setPatchSet(PatchSet patchSet) {
|
void setPatchSet(PatchSet patchSet) {
|
||||||
@@ -237,14 +238,16 @@ public class AddReviewersOp implements BatchUpdateOp {
|
|||||||
.setAddedCCs(addedCCs)
|
.setAddedCCs(addedCCs)
|
||||||
.setAddedCCsByEmail(addedCCsByEmail)
|
.setAddedCCsByEmail(addedCCsByEmail)
|
||||||
.build();
|
.build();
|
||||||
addReviewersEmail.emailReviewers(
|
if (sendEmail) {
|
||||||
ctx.getUser().asIdentifiedUser(),
|
addReviewersEmail.emailReviewers(
|
||||||
change,
|
ctx.getUser().asIdentifiedUser(),
|
||||||
Lists.transform(addedReviewers, PatchSetApproval::getAccountId),
|
change,
|
||||||
addedCCs,
|
Lists.transform(addedReviewers, PatchSetApproval::getAccountId),
|
||||||
addedReviewersByEmail,
|
addedCCs,
|
||||||
addedCCsByEmail,
|
addedReviewersByEmail,
|
||||||
notify);
|
addedCCsByEmail,
|
||||||
|
ctx.getNotify(change.getId()));
|
||||||
|
}
|
||||||
if (!addedReviewers.isEmpty()) {
|
if (!addedReviewers.isEmpty()) {
|
||||||
List<AccountState> reviewers =
|
List<AccountState> reviewers =
|
||||||
addedReviewers
|
addedReviewers
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ public class BatchAbandon {
|
|||||||
}
|
}
|
||||||
AccountState accountState = user.isIdentifiedUser() ? user.asIdentifiedUser().state() : null;
|
AccountState accountState = user.isIdentifiedUser() ? user.asIdentifiedUser().state() : null;
|
||||||
try (BatchUpdate u = updateFactory.create(project, user, TimeUtil.nowTs())) {
|
try (BatchUpdate u = updateFactory.create(project, user, TimeUtil.nowTs())) {
|
||||||
|
u.setNotify(notify);
|
||||||
for (ChangeData change : changes) {
|
for (ChangeData change : changes) {
|
||||||
if (!project.equals(change.project())) {
|
if (!project.equals(change.project())) {
|
||||||
throw new ResourceConflictException(
|
throw new ResourceConflictException(
|
||||||
@@ -63,7 +64,7 @@ public class BatchAbandon {
|
|||||||
"Project name \"%s\" doesn't match \"%s\"",
|
"Project name \"%s\" doesn't match \"%s\"",
|
||||||
change.project().get(), project.get()));
|
change.project().get(), project.get()));
|
||||||
}
|
}
|
||||||
u.addOp(change.getId(), abandonOpFactory.create(accountState, msgTxt, notify));
|
u.addOp(change.getId(), abandonOpFactory.create(accountState, msgTxt));
|
||||||
}
|
}
|
||||||
u.execute();
|
u.execute();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,7 +120,6 @@ public class ChangeInserter implements InsertChangeOp {
|
|||||||
private boolean workInProgress;
|
private boolean workInProgress;
|
||||||
private List<String> groups = Collections.emptyList();
|
private List<String> groups = Collections.emptyList();
|
||||||
private boolean validate = true;
|
private boolean validate = true;
|
||||||
private NotifyResolver.Result notify = NotifyResolver.Result.all();
|
|
||||||
private Map<String, Short> approvals;
|
private Map<String, Short> approvals;
|
||||||
private RequestScopePropagator requestScopePropagator;
|
private RequestScopePropagator requestScopePropagator;
|
||||||
private boolean fireRevisionCreated;
|
private boolean fireRevisionCreated;
|
||||||
@@ -251,11 +250,6 @@ public class ChangeInserter implements InsertChangeOp {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChangeInserter setNotify(NotifyResolver.Result notify) {
|
|
||||||
this.notify = notify;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChangeInserter setReviewersAndCcs(
|
public ChangeInserter setReviewersAndCcs(
|
||||||
Iterable<Account.Id> reviewers, Iterable<Account.Id> ccs) {
|
Iterable<Account.Id> reviewers, Iterable<Account.Id> ccs) {
|
||||||
return setReviewersAndCcsAsStrings(
|
return setReviewersAndCcsAsStrings(
|
||||||
@@ -447,6 +441,7 @@ public class ChangeInserter implements InsertChangeOp {
|
|||||||
@Override
|
@Override
|
||||||
public void postUpdate(Context ctx) throws Exception {
|
public void postUpdate(Context ctx) throws Exception {
|
||||||
reviewerAdditions.postUpdate(ctx);
|
reviewerAdditions.postUpdate(ctx);
|
||||||
|
NotifyResolver.Result notify = ctx.getNotify(change.getId());
|
||||||
if (sendMail && notify.shouldNotify()) {
|
if (sendMail && notify.shouldNotify()) {
|
||||||
Runnable sender =
|
Runnable sender =
|
||||||
new Runnable() {
|
new Runnable() {
|
||||||
|
|||||||
@@ -531,12 +531,12 @@ public class ConsistencyChecker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bu.setNotify(NotifyResolver.Result.none());
|
||||||
bu.addOp(
|
bu.addOp(
|
||||||
notes.getChangeId(),
|
notes.getChangeId(),
|
||||||
inserter
|
inserter
|
||||||
.setValidate(false)
|
.setValidate(false)
|
||||||
.setFireRevisionCreated(false)
|
.setFireRevisionCreated(false)
|
||||||
.setNotify(NotifyResolver.Result.none())
|
|
||||||
.setAllowClosed(true)
|
.setAllowClosed(true)
|
||||||
.setMessage("Patch set for merged commit inserted by consistency checker"));
|
.setMessage("Patch set for merged commit inserted by consistency checker"));
|
||||||
bu.addOp(notes.getChangeId(), new FixMergedOp(notFound));
|
bu.addOp(notes.getChangeId(), new FixMergedOp(notFound));
|
||||||
|
|||||||
@@ -63,6 +63,10 @@ public class NotifyResolver {
|
|||||||
// TODO(dborowitz): Should be ImmutableSetMultimap.
|
// TODO(dborowitz): Should be ImmutableSetMultimap.
|
||||||
public abstract ImmutableListMultimap<RecipientType, Account.Id> accounts();
|
public abstract ImmutableListMultimap<RecipientType, Account.Id> accounts();
|
||||||
|
|
||||||
|
public Result withHandling(NotifyHandling notifyHandling) {
|
||||||
|
return create(notifyHandling, accounts());
|
||||||
|
}
|
||||||
|
|
||||||
public boolean shouldNotify() {
|
public boolean shouldNotify() {
|
||||||
return !accounts().isEmpty() || handling().compareTo(NotifyHandling.NONE) > 0;
|
return !accounts().isEmpty() || handling().compareTo(NotifyHandling.NONE) > 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,6 @@ public class PatchSetInserter implements BatchUpdateOp {
|
|||||||
private boolean checkAddPatchSetPermission = true;
|
private boolean checkAddPatchSetPermission = true;
|
||||||
private List<String> groups = Collections.emptyList();
|
private List<String> groups = Collections.emptyList();
|
||||||
private boolean fireRevisionCreated = true;
|
private boolean fireRevisionCreated = true;
|
||||||
private NotifyResolver.Result notify = NotifyResolver.Result.all();
|
|
||||||
private boolean allowClosed;
|
private boolean allowClosed;
|
||||||
|
|
||||||
// Fields set during some phase of BatchUpdate.Op.
|
// Fields set during some phase of BatchUpdate.Op.
|
||||||
@@ -165,11 +164,6 @@ public class PatchSetInserter implements BatchUpdateOp {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PatchSetInserter setNotify(NotifyResolver.Result notify) {
|
|
||||||
this.notify = requireNonNull(notify);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PatchSetInserter setAllowClosed(boolean allowClosed) {
|
public PatchSetInserter setAllowClosed(boolean allowClosed) {
|
||||||
this.allowClosed = allowClosed;
|
this.allowClosed = allowClosed;
|
||||||
return this;
|
return this;
|
||||||
@@ -218,7 +212,7 @@ public class PatchSetInserter implements BatchUpdateOp {
|
|||||||
psUtil.insert(
|
psUtil.insert(
|
||||||
ctx.getRevWalk(), ctx.getUpdate(psId), psId, commitId, newGroups, null, description);
|
ctx.getRevWalk(), ctx.getUpdate(psId), psId, commitId, newGroups, null, description);
|
||||||
|
|
||||||
if (notify.handling() != NotifyHandling.NONE) {
|
if (ctx.getNotify(change.getId()).handling() != NotifyHandling.NONE) {
|
||||||
oldReviewers = approvalsUtil.getReviewers(ctx.getNotes());
|
oldReviewers = approvalsUtil.getReviewers(ctx.getNotes());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,6 +241,7 @@ public class PatchSetInserter implements BatchUpdateOp {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postUpdate(Context ctx) throws OrmException {
|
public void postUpdate(Context ctx) throws OrmException {
|
||||||
|
NotifyResolver.Result notify = ctx.getNotify(change.getId());
|
||||||
if (notify.shouldNotify()) {
|
if (notify.shouldNotify()) {
|
||||||
try {
|
try {
|
||||||
ReplacePatchSetSender cm = replacePatchSetFactory.create(ctx.getProject(), change.getId());
|
ReplacePatchSetSender cm = replacePatchSetFactory.create(ctx.getProject(), change.getId());
|
||||||
|
|||||||
@@ -182,7 +182,6 @@ public class RebaseChangeOp implements BatchUpdateOp {
|
|||||||
patchSetInserterFactory
|
patchSetInserterFactory
|
||||||
.create(notes, rebasedPatchSetId, rebasedCommit)
|
.create(notes, rebasedPatchSetId, rebasedCommit)
|
||||||
.setDescription("Rebase")
|
.setDescription("Rebase")
|
||||||
.setNotify(NotifyResolver.Result.none())
|
|
||||||
.setFireRevisionCreated(fireRevisionCreated)
|
.setFireRevisionCreated(fireRevisionCreated)
|
||||||
.setCheckAddPatchSetPermission(checkAddPatchSetPermission)
|
.setCheckAddPatchSetPermission(checkAddPatchSetPermission)
|
||||||
.setValidate(validate);
|
.setValidate(validate);
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import com.google.gerrit.extensions.api.changes.ReviewerInfo;
|
|||||||
import com.google.gerrit.extensions.client.ReviewerState;
|
import com.google.gerrit.extensions.client.ReviewerState;
|
||||||
import com.google.gerrit.extensions.common.AccountInfo;
|
import com.google.gerrit.extensions.common.AccountInfo;
|
||||||
import com.google.gerrit.extensions.restapi.AuthException;
|
import com.google.gerrit.extensions.restapi.AuthException;
|
||||||
import com.google.gerrit.extensions.restapi.BadRequestException;
|
|
||||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
||||||
import com.google.gerrit.mail.Address;
|
import com.google.gerrit.mail.Address;
|
||||||
@@ -148,7 +147,6 @@ public class ReviewerAdder {
|
|||||||
private final AccountLoader.Factory accountLoaderFactory;
|
private final AccountLoader.Factory accountLoaderFactory;
|
||||||
private final Config cfg;
|
private final Config cfg;
|
||||||
private final ReviewerJson json;
|
private final ReviewerJson json;
|
||||||
private final NotifyResolver notifyResolver;
|
|
||||||
private final ProjectCache projectCache;
|
private final ProjectCache projectCache;
|
||||||
private final Provider<AnonymousUser> anonymousProvider;
|
private final Provider<AnonymousUser> anonymousProvider;
|
||||||
private final AddReviewersOp.Factory addReviewersOpFactory;
|
private final AddReviewersOp.Factory addReviewersOpFactory;
|
||||||
@@ -163,7 +161,6 @@ public class ReviewerAdder {
|
|||||||
AccountLoader.Factory accountLoaderFactory,
|
AccountLoader.Factory accountLoaderFactory,
|
||||||
@GerritServerConfig Config cfg,
|
@GerritServerConfig Config cfg,
|
||||||
ReviewerJson json,
|
ReviewerJson json,
|
||||||
NotifyResolver notifyResolver,
|
|
||||||
ProjectCache projectCache,
|
ProjectCache projectCache,
|
||||||
Provider<AnonymousUser> anonymousProvider,
|
Provider<AnonymousUser> anonymousProvider,
|
||||||
AddReviewersOp.Factory addReviewersOpFactory,
|
AddReviewersOp.Factory addReviewersOpFactory,
|
||||||
@@ -175,7 +172,6 @@ public class ReviewerAdder {
|
|||||||
this.accountLoaderFactory = accountLoaderFactory;
|
this.accountLoaderFactory = accountLoaderFactory;
|
||||||
this.cfg = cfg;
|
this.cfg = cfg;
|
||||||
this.json = json;
|
this.json = json;
|
||||||
this.notifyResolver = notifyResolver;
|
|
||||||
this.projectCache = projectCache;
|
this.projectCache = projectCache;
|
||||||
this.anonymousProvider = anonymousProvider;
|
this.anonymousProvider = anonymousProvider;
|
||||||
this.addReviewersOpFactory = addReviewersOpFactory;
|
this.addReviewersOpFactory = addReviewersOpFactory;
|
||||||
@@ -201,23 +197,17 @@ public class ReviewerAdder {
|
|||||||
ChangeNotes notes, CurrentUser user, AddReviewerInput input, boolean allowGroup)
|
ChangeNotes notes, CurrentUser user, AddReviewerInput input, boolean allowGroup)
|
||||||
throws OrmException, IOException, PermissionBackendException, ConfigInvalidException {
|
throws OrmException, IOException, PermissionBackendException, ConfigInvalidException {
|
||||||
requireNonNull(input.reviewer);
|
requireNonNull(input.reviewer);
|
||||||
NotifyResolver.Result notify;
|
|
||||||
try {
|
|
||||||
notify = resolveNotify(notes, input);
|
|
||||||
} catch (BadRequestException e) {
|
|
||||||
return fail(input, FailureType.OTHER, e.getMessage());
|
|
||||||
}
|
|
||||||
boolean confirmed = input.confirmed();
|
boolean confirmed = input.confirmed();
|
||||||
boolean allowByEmail =
|
boolean allowByEmail =
|
||||||
projectCache
|
projectCache
|
||||||
.checkedGet(notes.getProjectName())
|
.checkedGet(notes.getProjectName())
|
||||||
.is(BooleanProjectConfig.ENABLE_REVIEWER_BY_EMAIL);
|
.is(BooleanProjectConfig.ENABLE_REVIEWER_BY_EMAIL);
|
||||||
|
|
||||||
ReviewerAddition byAccountId = addByAccountId(input, notes, user, notify);
|
ReviewerAddition byAccountId = addByAccountId(input, notes, user);
|
||||||
|
|
||||||
ReviewerAddition wholeGroup = null;
|
ReviewerAddition wholeGroup = null;
|
||||||
if (!byAccountId.exactMatchFound) {
|
if (!byAccountId.exactMatchFound) {
|
||||||
wholeGroup = addWholeGroup(input, notes, user, notify, confirmed, allowGroup, allowByEmail);
|
wholeGroup = addWholeGroup(input, notes, user, confirmed, allowGroup, allowByEmail);
|
||||||
if (wholeGroup != null && wholeGroup.exactMatchFound) {
|
if (wholeGroup != null && wholeGroup.exactMatchFound) {
|
||||||
return wholeGroup;
|
return wholeGroup;
|
||||||
}
|
}
|
||||||
@@ -239,17 +229,7 @@ public class ReviewerAdder {
|
|||||||
return wholeGroup;
|
return wholeGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
return addByEmail(input, notes, user, notify);
|
return addByEmail(input, notes, user);
|
||||||
}
|
|
||||||
|
|
||||||
private NotifyResolver.Result resolveNotify(ChangeNotes notes, AddReviewerInput input)
|
|
||||||
throws BadRequestException, OrmException, ConfigInvalidException, IOException {
|
|
||||||
NotifyHandling notifyHandling = input.notify;
|
|
||||||
if (notifyHandling == null) {
|
|
||||||
notifyHandling =
|
|
||||||
notes.getChange().isWorkInProgress() ? NotifyHandling.NONE : NotifyHandling.ALL;
|
|
||||||
}
|
|
||||||
return notifyResolver.resolve(notifyHandling, input.notifyDetails);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReviewerAddition ccCurrentUser(CurrentUser user, RevisionResource revision) {
|
public ReviewerAddition ccCurrentUser(CurrentUser user, RevisionResource revision) {
|
||||||
@@ -259,13 +239,12 @@ public class ReviewerAdder {
|
|||||||
revision.getUser(),
|
revision.getUser(),
|
||||||
ImmutableSet.of(user.getAccountId()),
|
ImmutableSet.of(user.getAccountId()),
|
||||||
null,
|
null,
|
||||||
NotifyResolver.Result.none(),
|
|
||||||
true);
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private ReviewerAddition addByAccountId(
|
private ReviewerAddition addByAccountId(
|
||||||
AddReviewerInput input, ChangeNotes notes, CurrentUser user, NotifyResolver.Result notify)
|
AddReviewerInput input, ChangeNotes notes, CurrentUser user)
|
||||||
throws OrmException, PermissionBackendException, IOException, ConfigInvalidException {
|
throws OrmException, PermissionBackendException, IOException, ConfigInvalidException {
|
||||||
IdentifiedUser reviewerUser;
|
IdentifiedUser reviewerUser;
|
||||||
boolean exactMatchFound = false;
|
boolean exactMatchFound = false;
|
||||||
@@ -283,13 +262,7 @@ public class ReviewerAdder {
|
|||||||
|
|
||||||
if (isValidReviewer(notes.getChange().getDest(), reviewerUser.getAccount())) {
|
if (isValidReviewer(notes.getChange().getDest(), reviewerUser.getAccount())) {
|
||||||
return new ReviewerAddition(
|
return new ReviewerAddition(
|
||||||
input,
|
input, notes, user, ImmutableSet.of(reviewerUser.getAccountId()), null, exactMatchFound);
|
||||||
notes,
|
|
||||||
user,
|
|
||||||
ImmutableSet.of(reviewerUser.getAccountId()),
|
|
||||||
null,
|
|
||||||
notify,
|
|
||||||
exactMatchFound);
|
|
||||||
}
|
}
|
||||||
return fail(
|
return fail(
|
||||||
input,
|
input,
|
||||||
@@ -302,7 +275,6 @@ public class ReviewerAdder {
|
|||||||
AddReviewerInput input,
|
AddReviewerInput input,
|
||||||
ChangeNotes notes,
|
ChangeNotes notes,
|
||||||
CurrentUser user,
|
CurrentUser user,
|
||||||
NotifyResolver.Result notify,
|
|
||||||
boolean confirmed,
|
boolean confirmed,
|
||||||
boolean allowGroup,
|
boolean allowGroup,
|
||||||
boolean allowByEmail)
|
boolean allowByEmail)
|
||||||
@@ -374,12 +346,11 @@ public class ReviewerAdder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ReviewerAddition(input, notes, user, reviewers, null, notify, true);
|
return new ReviewerAddition(input, notes, user, reviewers, null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private ReviewerAddition addByEmail(
|
private ReviewerAddition addByEmail(AddReviewerInput input, ChangeNotes notes, CurrentUser user)
|
||||||
AddReviewerInput input, ChangeNotes notes, CurrentUser user, NotifyResolver.Result notify)
|
|
||||||
throws PermissionBackendException {
|
throws PermissionBackendException {
|
||||||
try {
|
try {
|
||||||
permissionBackend.user(anonymousProvider.get()).change(notes).check(ChangePermission.READ);
|
permissionBackend.user(anonymousProvider.get()).change(notes).check(ChangePermission.READ);
|
||||||
@@ -397,7 +368,7 @@ public class ReviewerAdder {
|
|||||||
FailureType.NOT_FOUND,
|
FailureType.NOT_FOUND,
|
||||||
MessageFormat.format(ChangeMessages.get().reviewerInvalid, input.reviewer));
|
MessageFormat.format(ChangeMessages.get().reviewerInvalid, input.reviewer));
|
||||||
}
|
}
|
||||||
return new ReviewerAddition(input, notes, user, null, ImmutableList.of(adr), notify, true);
|
return new ReviewerAddition(input, notes, user, null, ImmutableList.of(adr), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isValidReviewer(Branch.NameKey branch, Account member)
|
private boolean isValidReviewer(Branch.NameKey branch, Account member)
|
||||||
@@ -452,7 +423,6 @@ public class ReviewerAdder {
|
|||||||
CurrentUser caller,
|
CurrentUser caller,
|
||||||
@Nullable Iterable<Account.Id> reviewers,
|
@Nullable Iterable<Account.Id> reviewers,
|
||||||
@Nullable Iterable<Address> reviewersByEmail,
|
@Nullable Iterable<Address> reviewersByEmail,
|
||||||
NotifyResolver.Result notify,
|
|
||||||
boolean exactMatchFound) {
|
boolean exactMatchFound) {
|
||||||
checkArgument(
|
checkArgument(
|
||||||
reviewers != null || reviewersByEmail != null,
|
reviewers != null || reviewersByEmail != null,
|
||||||
@@ -467,7 +437,7 @@ public class ReviewerAdder {
|
|||||||
this.reviewersByEmail =
|
this.reviewersByEmail =
|
||||||
reviewersByEmail == null ? ImmutableSet.of() : ImmutableSet.copyOf(reviewersByEmail);
|
reviewersByEmail == null ? ImmutableSet.of() : ImmutableSet.copyOf(reviewersByEmail);
|
||||||
this.caller = caller.asIdentifiedUser();
|
this.caller = caller.asIdentifiedUser();
|
||||||
op = addReviewersOpFactory.create(this.reviewers, this.reviewersByEmail, state(), notify);
|
op = addReviewersOpFactory.create(this.reviewers, this.reviewersByEmail, state());
|
||||||
this.exactMatchFound = exactMatchFound;
|
this.exactMatchFound = exactMatchFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,7 +527,12 @@ public class ReviewerAdder {
|
|||||||
.collect(toImmutableList());
|
.collect(toImmutableList());
|
||||||
List<ReviewerAddition> additions = new ArrayList<>();
|
List<ReviewerAddition> additions = new ArrayList<>();
|
||||||
for (AddReviewerInput input : sorted) {
|
for (AddReviewerInput input : sorted) {
|
||||||
additions.add(prepare(notes, user, input, allowGroup));
|
ReviewerAddition addition = prepare(notes, user, input, allowGroup);
|
||||||
|
if (addition.op != null) {
|
||||||
|
// Assume any callers preparing a list of batch insertions are handling their own email.
|
||||||
|
addition.op.suppressEmail();
|
||||||
|
}
|
||||||
|
additions.add(addition);
|
||||||
}
|
}
|
||||||
return new ReviewerAdditionList(additions);
|
return new ReviewerAdditionList(additions);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.change;
|
package com.google.gerrit.server.change;
|
||||||
|
|
||||||
import com.google.common.base.MoreObjects;
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.gerrit.common.Nullable;
|
import com.google.gerrit.common.Nullable;
|
||||||
@@ -91,9 +90,9 @@ public class WorkInProgressOp implements BatchUpdateOp {
|
|||||||
private final PatchSetUtil psUtil;
|
private final PatchSetUtil psUtil;
|
||||||
private final boolean workInProgress;
|
private final boolean workInProgress;
|
||||||
private final Input in;
|
private final Input in;
|
||||||
private final NotifyResolver.Result notify;
|
|
||||||
private final WorkInProgressStateChanged stateChanged;
|
private final WorkInProgressStateChanged stateChanged;
|
||||||
|
|
||||||
|
private boolean sendEmail = true;
|
||||||
private Change change;
|
private Change change;
|
||||||
private ChangeNotes notes;
|
private ChangeNotes notes;
|
||||||
private PatchSet ps;
|
private PatchSet ps;
|
||||||
@@ -113,10 +112,10 @@ public class WorkInProgressOp implements BatchUpdateOp {
|
|||||||
this.stateChanged = stateChanged;
|
this.stateChanged = stateChanged;
|
||||||
this.workInProgress = workInProgress;
|
this.workInProgress = workInProgress;
|
||||||
this.in = in;
|
this.in = in;
|
||||||
notify =
|
}
|
||||||
NotifyResolver.Result.create(
|
|
||||||
MoreObjects.firstNonNull(
|
public void suppressEmail() {
|
||||||
in.notify, workInProgress ? NotifyHandling.NONE : NotifyHandling.ALL));
|
this.sendEmail = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -160,7 +159,10 @@ public class WorkInProgressOp implements BatchUpdateOp {
|
|||||||
@Override
|
@Override
|
||||||
public void postUpdate(Context ctx) {
|
public void postUpdate(Context ctx) {
|
||||||
stateChanged.fire(change, ps, ctx.getAccount(), ctx.getWhen());
|
stateChanged.fire(change, ps, ctx.getAccount(), ctx.getWhen());
|
||||||
if (workInProgress || notify.handling().compareTo(NotifyHandling.OWNER_REVIEWERS) < 0) {
|
NotifyResolver.Result notify = ctx.getNotify(change.getId());
|
||||||
|
if (workInProgress
|
||||||
|
|| notify.handling().compareTo(NotifyHandling.OWNER_REVIEWERS) < 0
|
||||||
|
|| !sendEmail) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
email
|
email
|
||||||
|
|||||||
@@ -169,8 +169,7 @@ public class ChangeEditUtil {
|
|||||||
|
|
||||||
RevCommit squashed = squashEdit(rw, oi, edit.getEditCommit(), basePatchSet);
|
RevCommit squashed = squashEdit(rw, oi, edit.getEditCommit(), basePatchSet);
|
||||||
PatchSet.Id psId = ChangeUtil.nextPatchSetId(repo, change.currentPatchSetId());
|
PatchSet.Id psId = ChangeUtil.nextPatchSetId(repo, change.currentPatchSetId());
|
||||||
PatchSetInserter inserter =
|
PatchSetInserter inserter = patchSetInserterFactory.create(notes, psId, squashed);
|
||||||
patchSetInserterFactory.create(notes, psId, squashed).setNotify(notify);
|
|
||||||
|
|
||||||
StringBuilder message =
|
StringBuilder message =
|
||||||
new StringBuilder("Patch Set ").append(inserter.getPatchSetId().get()).append(": ");
|
new StringBuilder("Patch Set ").append(inserter.getPatchSetId().get()).append(": ");
|
||||||
@@ -191,6 +190,7 @@ public class ChangeEditUtil {
|
|||||||
|
|
||||||
try (BatchUpdate bu = updateFactory.create(change.getProject(), user, TimeUtil.nowTs())) {
|
try (BatchUpdate bu = updateFactory.create(change.getProject(), user, TimeUtil.nowTs())) {
|
||||||
bu.setRepository(repo, rw, oi);
|
bu.setRepository(repo, rw, oi);
|
||||||
|
bu.setNotify(notify);
|
||||||
bu.addOp(change.getId(), inserter.setMessage(message.toString()));
|
bu.addOp(change.getId(), inserter.setMessage(message.toString()));
|
||||||
bu.addOp(
|
bu.addOp(
|
||||||
change.getId(),
|
change.getId(),
|
||||||
|
|||||||
@@ -682,7 +682,7 @@ class ReceiveCommits {
|
|||||||
// Update superproject gitlinks if required.
|
// Update superproject gitlinks if required.
|
||||||
if (!branches.isEmpty()) {
|
if (!branches.isEmpty()) {
|
||||||
try (MergeOpRepoManager orm = ormProvider.get()) {
|
try (MergeOpRepoManager orm = ormProvider.get()) {
|
||||||
orm.setContext(TimeUtil.nowTs(), user);
|
orm.setContext(TimeUtil.nowTs(), user, NotifyResolver.Result.none());
|
||||||
SubmoduleOp op = subOpFactory.create(branches, orm);
|
SubmoduleOp op = subOpFactory.create(branches, orm);
|
||||||
op.updateSuperProjects();
|
op.updateSuperProjects();
|
||||||
} catch (SubmoduleException e) {
|
} catch (SubmoduleException e) {
|
||||||
@@ -787,9 +787,15 @@ class ReceiveCommits {
|
|||||||
RevWalk rw = new RevWalk(reader)) {
|
RevWalk rw = new RevWalk(reader)) {
|
||||||
bu.setRepository(repo, rw, ins);
|
bu.setRepository(repo, rw, ins);
|
||||||
bu.setRefLogMessage("push");
|
bu.setRefLogMessage("push");
|
||||||
|
if (magicBranch != null) {
|
||||||
|
bu.setNotify(magicBranch.getNotifyForNewChange());
|
||||||
|
}
|
||||||
|
|
||||||
logger.atFine().log("Adding %d replace requests", newChanges.size());
|
logger.atFine().log("Adding %d replace requests", newChanges.size());
|
||||||
for (ReplaceRequest replace : replaceByChange.values()) {
|
for (ReplaceRequest replace : replaceByChange.values()) {
|
||||||
|
if (magicBranch != null) {
|
||||||
|
bu.setNotifyHandling(replace.ontoChange, magicBranch.getNotifyHandling(replace.notes));
|
||||||
|
}
|
||||||
replace.addOps(bu, replaceProgress);
|
replace.addOps(bu, replaceProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1578,31 +1584,25 @@ class ReceiveCommits {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NotifyResolver.Result getNotifyForNewChange() {
|
NotifyResolver.Result getNotifyForNewChange() {
|
||||||
return getNotifyImpl(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
NotifyResolver.Result getNotify(ChangeNotes notes) {
|
|
||||||
return getNotifyImpl(requireNonNull(notes));
|
|
||||||
}
|
|
||||||
|
|
||||||
private NotifyResolver.Result getNotifyImpl(@Nullable ChangeNotes notes) {
|
|
||||||
NotifyHandling notifyHandling = this.notifyHandling;
|
|
||||||
if (notifyHandling == null) {
|
|
||||||
if (workInProgress || (notes != null && !ready && notes.getChange().isWorkInProgress())) {
|
|
||||||
notifyHandling = NotifyHandling.OWNER;
|
|
||||||
} else {
|
|
||||||
notifyHandling = NotifyHandling.ALL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NotifyResolver.Result.create(
|
return NotifyResolver.Result.create(
|
||||||
notifyHandling,
|
firstNonNull(notifyHandling, workInProgress ? NotifyHandling.OWNER : NotifyHandling.ALL),
|
||||||
ImmutableListMultimap.<RecipientType, Account.Id>builder()
|
ImmutableListMultimap.<RecipientType, Account.Id>builder()
|
||||||
.putAll(RecipientType.TO, notifyTo)
|
.putAll(RecipientType.TO, notifyTo)
|
||||||
.putAll(RecipientType.CC, notifyCc)
|
.putAll(RecipientType.CC, notifyCc)
|
||||||
.putAll(RecipientType.BCC, notifyBcc)
|
.putAll(RecipientType.BCC, notifyBcc)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NotifyHandling getNotifyHandling(ChangeNotes notes) {
|
||||||
|
requireNonNull(notes);
|
||||||
|
if (notifyHandling != null) {
|
||||||
|
return notifyHandling;
|
||||||
|
}
|
||||||
|
if (workInProgress || (!ready && notes.getChange().isWorkInProgress())) {
|
||||||
|
return NotifyHandling.OWNER;
|
||||||
|
}
|
||||||
|
return NotifyHandling.ALL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2440,13 +2440,13 @@ class ReceiveCommits {
|
|||||||
msg.append("\n").append(magicBranch.message);
|
msg.append("\n").append(magicBranch.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bu.setNotify(magicBranch.getNotifyForNewChange());
|
||||||
bu.insertChange(
|
bu.insertChange(
|
||||||
ins.setReviewersAndCcsAsStrings(
|
ins.setReviewersAndCcsAsStrings(
|
||||||
magicBranch.getCombinedReviewers(fromFooters),
|
magicBranch.getCombinedReviewers(fromFooters),
|
||||||
magicBranch.getCombinedCcs(fromFooters))
|
magicBranch.getCombinedCcs(fromFooters))
|
||||||
.setApprovals(approvals)
|
.setApprovals(approvals)
|
||||||
.setMessage(msg.toString())
|
.setMessage(msg.toString())
|
||||||
.setNotify(magicBranch.getNotifyForNewChange())
|
|
||||||
.setRequestScopePropagator(requestScopePropagator)
|
.setRequestScopePropagator(requestScopePropagator)
|
||||||
.setSendMail(true)
|
.setSendMail(true)
|
||||||
.setPatchSetDescription(magicBranch.message));
|
.setPatchSetDescription(magicBranch.message));
|
||||||
|
|||||||
@@ -512,8 +512,7 @@ public class ReplaceOp implements BatchUpdateOp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NotifyResolver.Result notify =
|
NotifyResolver.Result notify = ctx.getNotify(notes.getChangeId());
|
||||||
magicBranch != null ? magicBranch.getNotify(notes) : NotifyResolver.Result.all();
|
|
||||||
if (shouldPublishComments()) {
|
if (shouldPublishComments()) {
|
||||||
emailCommentsFactory
|
emailCommentsFactory
|
||||||
.create(
|
.create(
|
||||||
@@ -554,9 +553,7 @@ public class ReplaceOp implements BatchUpdateOp {
|
|||||||
cm.setFrom(ctx.getAccount().getAccount().getId());
|
cm.setFrom(ctx.getAccount().getAccount().getId());
|
||||||
cm.setPatchSet(newPatchSet, info);
|
cm.setPatchSet(newPatchSet, info);
|
||||||
cm.setChangeMessage(msg.getMessage(), ctx.getWhen());
|
cm.setChangeMessage(msg.getMessage(), ctx.getWhen());
|
||||||
if (magicBranch != null) {
|
cm.setNotify(ctx.getNotify(notes.getChangeId()));
|
||||||
cm.setNotify(magicBranch.getNotify(notes));
|
|
||||||
}
|
|
||||||
cm.addReviewers(
|
cm.addReviewers(
|
||||||
Streams.concat(
|
Streams.concat(
|
||||||
oldRecipients.getReviewers().stream(),
|
oldRecipients.getReviewers().stream(),
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ import com.google.gerrit.server.account.AccountCache;
|
|||||||
import com.google.gerrit.server.account.AccountState;
|
import com.google.gerrit.server.account.AccountState;
|
||||||
import com.google.gerrit.server.account.Emails;
|
import com.google.gerrit.server.account.Emails;
|
||||||
import com.google.gerrit.server.change.EmailReviewComments;
|
import com.google.gerrit.server.change.EmailReviewComments;
|
||||||
import com.google.gerrit.server.change.NotifyResolver;
|
|
||||||
import com.google.gerrit.server.config.UrlFormatter;
|
import com.google.gerrit.server.config.UrlFormatter;
|
||||||
import com.google.gerrit.server.extensions.events.CommentAdded;
|
import com.google.gerrit.server.extensions.events.CommentAdded;
|
||||||
import com.google.gerrit.server.mail.MailFilter;
|
import com.google.gerrit.server.mail.MailFilter;
|
||||||
@@ -314,7 +313,7 @@ public class MailProcessor {
|
|||||||
// Send email notifications
|
// Send email notifications
|
||||||
outgoingMailFactory
|
outgoingMailFactory
|
||||||
.create(
|
.create(
|
||||||
NotifyResolver.Result.all(),
|
ctx.getNotify(notes.getChangeId()),
|
||||||
notes,
|
notes,
|
||||||
patchSet,
|
patchSet,
|
||||||
ctx.getUser().asIdentifiedUser(),
|
ctx.getUser().asIdentifiedUser(),
|
||||||
|
|||||||
@@ -120,8 +120,9 @@ public class Abandon extends RetryingRestModifyView<ChangeResource, AbandonInput
|
|||||||
NotifyResolver.Result notify)
|
NotifyResolver.Result notify)
|
||||||
throws RestApiException, UpdateException {
|
throws RestApiException, UpdateException {
|
||||||
AccountState accountState = user.isIdentifiedUser() ? user.asIdentifiedUser().state() : null;
|
AccountState accountState = user.isIdentifiedUser() ? user.asIdentifiedUser().state() : null;
|
||||||
AbandonOp op = abandonOpFactory.create(accountState, msgTxt, notify);
|
AbandonOp op = abandonOpFactory.create(accountState, msgTxt);
|
||||||
try (BatchUpdate u = updateFactory.create(notes.getProjectName(), user, TimeUtil.nowTs())) {
|
try (BatchUpdate u = updateFactory.create(notes.getProjectName(), user, TimeUtil.nowTs())) {
|
||||||
|
u.setNotify(notify);
|
||||||
u.addOp(notes.getChangeId(), op).execute();
|
u.addOp(notes.getChangeId(), op).execute();
|
||||||
}
|
}
|
||||||
return op.getChange();
|
return op.getChange();
|
||||||
|
|||||||
@@ -249,11 +249,12 @@ public class CherryPickChange {
|
|||||||
}
|
}
|
||||||
try (BatchUpdate bu = batchUpdateFactory.create(project, identifiedUser, now)) {
|
try (BatchUpdate bu = batchUpdateFactory.create(project, identifiedUser, now)) {
|
||||||
bu.setRepository(git, revWalk, oi);
|
bu.setRepository(git, revWalk, oi);
|
||||||
|
bu.setNotify(resolveNotify(input));
|
||||||
Change.Id changeId;
|
Change.Id changeId;
|
||||||
if (destChanges.size() == 1) {
|
if (destChanges.size() == 1) {
|
||||||
// The change key exists on the destination branch. The cherry pick
|
// The change key exists on the destination branch. The cherry pick
|
||||||
// will be added as a new patch set.
|
// will be added as a new patch set.
|
||||||
changeId = insertPatchSet(bu, git, destChanges.get(0).notes(), cherryPickCommit, input);
|
changeId = insertPatchSet(bu, git, destChanges.get(0).notes(), cherryPickCommit);
|
||||||
} else {
|
} else {
|
||||||
// Change key not found on destination branch. We can create a new
|
// Change key not found on destination branch. We can create a new
|
||||||
// change.
|
// change.
|
||||||
@@ -318,19 +319,12 @@ public class CherryPickChange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Change.Id insertPatchSet(
|
private Change.Id insertPatchSet(
|
||||||
BatchUpdate bu,
|
BatchUpdate bu, Repository git, ChangeNotes destNotes, CodeReviewCommit cherryPickCommit)
|
||||||
Repository git,
|
throws IOException {
|
||||||
ChangeNotes destNotes,
|
|
||||||
CodeReviewCommit cherryPickCommit,
|
|
||||||
CherryPickInput input)
|
|
||||||
throws IOException, OrmException, BadRequestException, ConfigInvalidException {
|
|
||||||
Change destChange = destNotes.getChange();
|
Change destChange = destNotes.getChange();
|
||||||
PatchSet.Id psId = ChangeUtil.nextPatchSetId(git, destChange.currentPatchSetId());
|
PatchSet.Id psId = ChangeUtil.nextPatchSetId(git, destChange.currentPatchSetId());
|
||||||
PatchSetInserter inserter = patchSetInserterFactory.create(destNotes, psId, cherryPickCommit);
|
PatchSetInserter inserter = patchSetInserterFactory.create(destNotes, psId, cherryPickCommit);
|
||||||
NotifyResolver.Result notify = resolveNotify(input);
|
inserter.setMessage("Uploaded patch set " + inserter.getPatchSetId().get() + ".");
|
||||||
inserter
|
|
||||||
.setMessage("Uploaded patch set " + inserter.getPatchSetId().get() + ".")
|
|
||||||
.setNotify(notify);
|
|
||||||
bu.addOp(destChange.getId(), inserter);
|
bu.addOp(destChange.getId(), inserter);
|
||||||
return destChange.getId();
|
return destChange.getId();
|
||||||
}
|
}
|
||||||
@@ -343,19 +337,17 @@ public class CherryPickChange {
|
|||||||
@Nullable Change sourceChange,
|
@Nullable Change sourceChange,
|
||||||
ObjectId sourceCommit,
|
ObjectId sourceCommit,
|
||||||
CherryPickInput input)
|
CherryPickInput input)
|
||||||
throws OrmException, IOException, BadRequestException, ConfigInvalidException {
|
throws OrmException, IOException {
|
||||||
Change.Id changeId = new Change.Id(seq.nextChangeId());
|
Change.Id changeId = new Change.Id(seq.nextChangeId());
|
||||||
ChangeInserter ins = changeInserterFactory.create(changeId, cherryPickCommit, refName);
|
ChangeInserter ins = changeInserterFactory.create(changeId, cherryPickCommit, refName);
|
||||||
Branch.NameKey sourceBranch = sourceChange == null ? null : sourceChange.getDest();
|
Branch.NameKey sourceBranch = sourceChange == null ? null : sourceChange.getDest();
|
||||||
NotifyResolver.Result notify = resolveNotify(input);
|
|
||||||
ins.setMessage(
|
ins.setMessage(
|
||||||
messageForDestinationChange(
|
messageForDestinationChange(
|
||||||
ins.getPatchSetId(), sourceBranch, sourceCommit, cherryPickCommit))
|
ins.getPatchSetId(), sourceBranch, sourceCommit, cherryPickCommit))
|
||||||
.setTopic(topic)
|
.setTopic(topic)
|
||||||
.setWorkInProgress(
|
.setWorkInProgress(
|
||||||
(sourceChange != null && sourceChange.isWorkInProgress())
|
(sourceChange != null && sourceChange.isWorkInProgress())
|
||||||
|| !cherryPickCommit.getFilesWithGitConflicts().isEmpty())
|
|| !cherryPickCommit.getFilesWithGitConflicts().isEmpty());
|
||||||
.setNotify(notify);
|
|
||||||
if (input.keepReviewers && sourceChange != null) {
|
if (input.keepReviewers && sourceChange != null) {
|
||||||
ReviewerSet reviewerSet =
|
ReviewerSet reviewerSet =
|
||||||
approvalsUtil.getReviewers(changeNotesFactory.createChecked(sourceChange));
|
approvalsUtil.getReviewers(changeNotesFactory.createChecked(sourceChange));
|
||||||
|
|||||||
@@ -305,12 +305,11 @@ public class CreateChange
|
|||||||
ins.setPrivate(input.isPrivate);
|
ins.setPrivate(input.isPrivate);
|
||||||
ins.setWorkInProgress(input.workInProgress);
|
ins.setWorkInProgress(input.workInProgress);
|
||||||
ins.setGroups(groups);
|
ins.setGroups(groups);
|
||||||
NotifyResolver.Result notify =
|
|
||||||
notifyResolver.resolve(
|
|
||||||
firstNonNull(input.notify, NotifyHandling.ALL), input.notifyDetails);
|
|
||||||
ins.setNotify(notify);
|
|
||||||
try (BatchUpdate bu = updateFactory.create(projectState.getNameKey(), me, now)) {
|
try (BatchUpdate bu = updateFactory.create(projectState.getNameKey(), me, now)) {
|
||||||
bu.setRepository(git, rw, oi);
|
bu.setRepository(git, rw, oi);
|
||||||
|
bu.setNotify(
|
||||||
|
notifyResolver.resolve(
|
||||||
|
firstNonNull(input.notify, NotifyHandling.ALL), input.notifyDetails));
|
||||||
bu.insertChange(ins);
|
bu.insertChange(ins);
|
||||||
bu.execute();
|
bu.execute();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,9 +183,9 @@ public class CreateMergePatchSet
|
|||||||
patchSetInserterFactory.create(rsrc.getNotes(), nextPsId, newCommit);
|
patchSetInserterFactory.create(rsrc.getNotes(), nextPsId, newCommit);
|
||||||
try (BatchUpdate bu = updateFactory.create(project, me, now)) {
|
try (BatchUpdate bu = updateFactory.create(project, me, now)) {
|
||||||
bu.setRepository(git, rw, oi);
|
bu.setRepository(git, rw, oi);
|
||||||
|
bu.setNotify(NotifyResolver.Result.none());
|
||||||
psInserter
|
psInserter
|
||||||
.setMessage("Uploaded patch set " + nextPsId.get() + ".")
|
.setMessage("Uploaded patch set " + nextPsId.get() + ".")
|
||||||
.setNotify(NotifyResolver.Result.none())
|
|
||||||
.setCheckAddPatchSetPermission(false);
|
.setCheckAddPatchSetPermission(false);
|
||||||
if (groups != null) {
|
if (groups != null) {
|
||||||
psInserter.setGroups(groups);
|
psInserter.setGroups(groups);
|
||||||
|
|||||||
@@ -15,8 +15,11 @@
|
|||||||
package com.google.gerrit.server.restapi.change;
|
package com.google.gerrit.server.restapi.change;
|
||||||
|
|
||||||
import com.google.gerrit.extensions.api.changes.DeleteReviewerInput;
|
import com.google.gerrit.extensions.api.changes.DeleteReviewerInput;
|
||||||
|
import com.google.gerrit.extensions.api.changes.NotifyHandling;
|
||||||
import com.google.gerrit.extensions.restapi.Response;
|
import com.google.gerrit.extensions.restapi.Response;
|
||||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
|
import com.google.gerrit.server.change.NotifyResolver;
|
||||||
import com.google.gerrit.server.change.ReviewerResource;
|
import com.google.gerrit.server.change.ReviewerResource;
|
||||||
import com.google.gerrit.server.update.BatchUpdate;
|
import com.google.gerrit.server.update.BatchUpdate;
|
||||||
import com.google.gerrit.server.update.BatchUpdateOp;
|
import com.google.gerrit.server.update.BatchUpdateOp;
|
||||||
@@ -57,6 +60,7 @@ public class DeleteReviewer
|
|||||||
rsrc.getChangeResource().getProject(),
|
rsrc.getChangeResource().getProject(),
|
||||||
rsrc.getChangeResource().getUser(),
|
rsrc.getChangeResource().getUser(),
|
||||||
TimeUtil.nowTs())) {
|
TimeUtil.nowTs())) {
|
||||||
|
bu.setNotify(getNotify(rsrc.getChange(), input));
|
||||||
BatchUpdateOp op;
|
BatchUpdateOp op;
|
||||||
if (rsrc.isByEmail()) {
|
if (rsrc.isByEmail()) {
|
||||||
op = deleteReviewerByEmailOpFactory.create(rsrc.getReviewerByEmail(), input);
|
op = deleteReviewerByEmailOpFactory.create(rsrc.getReviewerByEmail(), input);
|
||||||
@@ -68,4 +72,12 @@ public class DeleteReviewer
|
|||||||
}
|
}
|
||||||
return Response.none();
|
return Response.none();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static NotifyResolver.Result getNotify(Change change, DeleteReviewerInput input) {
|
||||||
|
NotifyHandling notifyHandling = input.notify;
|
||||||
|
if (notifyHandling == null) {
|
||||||
|
notifyHandling = change.isWorkInProgress() ? NotifyHandling.NONE : NotifyHandling.ALL;
|
||||||
|
}
|
||||||
|
return NotifyResolver.Result.create(notifyHandling);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,8 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.restapi.change;
|
package com.google.gerrit.server.restapi.change;
|
||||||
|
|
||||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
|
||||||
|
|
||||||
import com.google.common.flogger.FluentLogger;
|
import com.google.common.flogger.FluentLogger;
|
||||||
import com.google.gerrit.extensions.api.changes.DeleteReviewerInput;
|
import com.google.gerrit.extensions.api.changes.DeleteReviewerInput;
|
||||||
import com.google.gerrit.extensions.api.changes.NotifyHandling;
|
|
||||||
import com.google.gerrit.mail.Address;
|
import com.google.gerrit.mail.Address;
|
||||||
import com.google.gerrit.reviewdb.client.Change;
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
||||||
@@ -42,7 +39,6 @@ public class DeleteReviewerByEmailOp implements BatchUpdateOp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final DeleteReviewerSender.Factory deleteReviewerSenderFactory;
|
private final DeleteReviewerSender.Factory deleteReviewerSenderFactory;
|
||||||
private final NotifyResolver notifyResolver;
|
|
||||||
private final Address reviewer;
|
private final Address reviewer;
|
||||||
private final DeleteReviewerInput input;
|
private final DeleteReviewerInput input;
|
||||||
|
|
||||||
@@ -52,11 +48,9 @@ public class DeleteReviewerByEmailOp implements BatchUpdateOp {
|
|||||||
@Inject
|
@Inject
|
||||||
DeleteReviewerByEmailOp(
|
DeleteReviewerByEmailOp(
|
||||||
DeleteReviewerSender.Factory deleteReviewerSenderFactory,
|
DeleteReviewerSender.Factory deleteReviewerSenderFactory,
|
||||||
NotifyResolver notifyResolver,
|
|
||||||
@Assisted Address reviewer,
|
@Assisted Address reviewer,
|
||||||
@Assisted DeleteReviewerInput input) {
|
@Assisted DeleteReviewerInput input) {
|
||||||
this.deleteReviewerSenderFactory = deleteReviewerSenderFactory;
|
this.deleteReviewerSenderFactory = deleteReviewerSenderFactory;
|
||||||
this.notifyResolver = notifyResolver;
|
|
||||||
this.reviewer = reviewer;
|
this.reviewer = reviewer;
|
||||||
this.input = input;
|
this.input = input;
|
||||||
}
|
}
|
||||||
@@ -81,17 +75,8 @@ public class DeleteReviewerByEmailOp implements BatchUpdateOp {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postUpdate(Context ctx) {
|
public void postUpdate(Context ctx) {
|
||||||
if (input.notify == null) {
|
|
||||||
if (change.isWorkInProgress()) {
|
|
||||||
input.notify = NotifyHandling.NONE;
|
|
||||||
} else {
|
|
||||||
input.notify = NotifyHandling.ALL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
NotifyResolver.Result notify =
|
NotifyResolver.Result notify = ctx.getNotify(change.getId());
|
||||||
notifyResolver.resolve(
|
|
||||||
firstNonNull(input.notify, NotifyHandling.ALL), input.notifyDetails);
|
|
||||||
if (!notify.shouldNotify()) {
|
if (!notify.shouldNotify()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ public class DeleteReviewerOp implements BatchUpdateOp {
|
|||||||
private final ReviewerDeleted reviewerDeleted;
|
private final ReviewerDeleted reviewerDeleted;
|
||||||
private final Provider<IdentifiedUser> user;
|
private final Provider<IdentifiedUser> user;
|
||||||
private final DeleteReviewerSender.Factory deleteReviewerSenderFactory;
|
private final DeleteReviewerSender.Factory deleteReviewerSenderFactory;
|
||||||
private final NotifyResolver notifyResolver;
|
|
||||||
private final RemoveReviewerControl removeReviewerControl;
|
private final RemoveReviewerControl removeReviewerControl;
|
||||||
private final ProjectCache projectCache;
|
private final ProjectCache projectCache;
|
||||||
|
|
||||||
@@ -91,7 +90,6 @@ public class DeleteReviewerOp implements BatchUpdateOp {
|
|||||||
ReviewerDeleted reviewerDeleted,
|
ReviewerDeleted reviewerDeleted,
|
||||||
Provider<IdentifiedUser> user,
|
Provider<IdentifiedUser> user,
|
||||||
DeleteReviewerSender.Factory deleteReviewerSenderFactory,
|
DeleteReviewerSender.Factory deleteReviewerSenderFactory,
|
||||||
NotifyResolver notifyResolver,
|
|
||||||
RemoveReviewerControl removeReviewerControl,
|
RemoveReviewerControl removeReviewerControl,
|
||||||
ProjectCache projectCache,
|
ProjectCache projectCache,
|
||||||
@Assisted AccountState reviewerAccount,
|
@Assisted AccountState reviewerAccount,
|
||||||
@@ -103,7 +101,6 @@ public class DeleteReviewerOp implements BatchUpdateOp {
|
|||||||
this.reviewerDeleted = reviewerDeleted;
|
this.reviewerDeleted = reviewerDeleted;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.deleteReviewerSenderFactory = deleteReviewerSenderFactory;
|
this.deleteReviewerSenderFactory = deleteReviewerSenderFactory;
|
||||||
this.notifyResolver = notifyResolver;
|
|
||||||
this.removeReviewerControl = removeReviewerControl;
|
this.removeReviewerControl = removeReviewerControl;
|
||||||
this.projectCache = projectCache;
|
this.projectCache = projectCache;
|
||||||
this.reviewer = reviewerAccount;
|
this.reviewer = reviewerAccount;
|
||||||
@@ -170,15 +167,16 @@ public class DeleteReviewerOp implements BatchUpdateOp {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postUpdate(Context ctx) {
|
public void postUpdate(Context ctx) {
|
||||||
if (input.notify == null) {
|
NotifyResolver.Result notify = ctx.getNotify(currChange.getId());
|
||||||
if (currChange.isWorkInProgress()) {
|
if (input.notify == null
|
||||||
input.notify = oldApprovals.isEmpty() ? NotifyHandling.NONE : NotifyHandling.OWNER;
|
&& currChange.isWorkInProgress()
|
||||||
} else {
|
&& !oldApprovals.isEmpty()
|
||||||
input.notify = NotifyHandling.ALL;
|
&& notify.handling().compareTo(NotifyHandling.OWNER) < 0) {
|
||||||
}
|
// Override NotifyHandling from the context to notify owner if votes were removed on a WIP
|
||||||
|
// change.
|
||||||
|
notify = notify.withHandling(NotifyHandling.OWNER);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
NotifyResolver.Result notify = notifyResolver.resolve(input.notify, input.notifyDetails);
|
|
||||||
if (notify.shouldNotify()) {
|
if (notify.shouldNotify()) {
|
||||||
emailReviewers(ctx.getProject(), currChange, changeMessage, notify);
|
emailReviewers(ctx.getProject(), currChange, changeMessage, notify);
|
||||||
}
|
}
|
||||||
@@ -193,7 +191,7 @@ public class DeleteReviewerOp implements BatchUpdateOp {
|
|||||||
changeMessage.getMessage(),
|
changeMessage.getMessage(),
|
||||||
newApprovals,
|
newApprovals,
|
||||||
oldApprovals,
|
oldApprovals,
|
||||||
input.notify,
|
notify.handling(),
|
||||||
ctx.getWhen());
|
ctx.getWhen());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ import com.google.inject.Singleton;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class DeleteVote extends RetryingRestModifyView<VoteResource, DeleteVoteInput, Response<?>> {
|
public class DeleteVote extends RetryingRestModifyView<VoteResource, DeleteVoteInput, Response<?>> {
|
||||||
@@ -104,7 +105,7 @@ public class DeleteVote extends RetryingRestModifyView<VoteResource, DeleteVoteI
|
|||||||
@Override
|
@Override
|
||||||
protected Response<?> applyImpl(
|
protected Response<?> applyImpl(
|
||||||
BatchUpdate.Factory updateFactory, VoteResource rsrc, DeleteVoteInput input)
|
BatchUpdate.Factory updateFactory, VoteResource rsrc, DeleteVoteInput input)
|
||||||
throws RestApiException, UpdateException, IOException {
|
throws RestApiException, UpdateException, IOException, OrmException, ConfigInvalidException {
|
||||||
if (input == null) {
|
if (input == null) {
|
||||||
input = new DeleteVoteInput();
|
input = new DeleteVoteInput();
|
||||||
}
|
}
|
||||||
@@ -124,6 +125,9 @@ public class DeleteVote extends RetryingRestModifyView<VoteResource, DeleteVoteI
|
|||||||
try (BatchUpdate bu =
|
try (BatchUpdate bu =
|
||||||
updateFactory.create(
|
updateFactory.create(
|
||||||
change.getProject(), r.getChangeResource().getUser(), TimeUtil.nowTs())) {
|
change.getProject(), r.getChangeResource().getUser(), TimeUtil.nowTs())) {
|
||||||
|
bu.setNotify(
|
||||||
|
notifyResolver.resolve(
|
||||||
|
firstNonNull(input.notify, NotifyHandling.ALL), input.notifyDetails));
|
||||||
bu.addOp(
|
bu.addOp(
|
||||||
change.getId(),
|
change.getId(),
|
||||||
new Op(
|
new Op(
|
||||||
@@ -219,9 +223,7 @@ public class DeleteVote extends RetryingRestModifyView<VoteResource, DeleteVoteI
|
|||||||
|
|
||||||
IdentifiedUser user = ctx.getIdentifiedUser();
|
IdentifiedUser user = ctx.getIdentifiedUser();
|
||||||
try {
|
try {
|
||||||
NotifyResolver.Result notify =
|
NotifyResolver.Result notify = ctx.getNotify(change.getId());
|
||||||
notifyResolver.resolve(
|
|
||||||
firstNonNull(input.notify, NotifyHandling.ALL), input.notifyDetails);
|
|
||||||
if (notify.shouldNotify()) {
|
if (notify.shouldNotify()) {
|
||||||
ReplyToChangeSender cm = deleteVoteSenderFactory.create(ctx.getProject(), change.getId());
|
ReplyToChangeSender cm = deleteVoteSenderFactory.create(ctx.getProject(), change.getId());
|
||||||
cm.setFrom(user.getAccountId());
|
cm.setFrom(user.getAccountId());
|
||||||
|
|||||||
@@ -255,8 +255,6 @@ public class PostReview
|
|||||||
input.notify = defaultNotify(revision.getChange(), input);
|
input.notify = defaultNotify(revision.getChange(), input);
|
||||||
}
|
}
|
||||||
|
|
||||||
NotifyResolver.Result notify = notifyResolver.resolve(input.notify, input.notifyDetails);
|
|
||||||
|
|
||||||
Map<String, AddReviewerResult> reviewerJsonResults = null;
|
Map<String, AddReviewerResult> reviewerJsonResults = null;
|
||||||
List<ReviewerAddition> reviewerResults = Lists.newArrayList();
|
List<ReviewerAddition> reviewerResults = Lists.newArrayList();
|
||||||
boolean hasError = false;
|
boolean hasError = false;
|
||||||
@@ -264,12 +262,6 @@ public class PostReview
|
|||||||
if (input.reviewers != null) {
|
if (input.reviewers != null) {
|
||||||
reviewerJsonResults = Maps.newHashMap();
|
reviewerJsonResults = Maps.newHashMap();
|
||||||
for (AddReviewerInput reviewerInput : input.reviewers) {
|
for (AddReviewerInput reviewerInput : input.reviewers) {
|
||||||
// Prevent individual AddReviewersOps from sending one email each. Instead, we call
|
|
||||||
// batchEmailReviewers at the very end to send out a single email.
|
|
||||||
// TODO(dborowitz): I think this still sends out separate emails if any of input.reviewers
|
|
||||||
// specifies explicit accountsToNotify. Unclear whether that's a good thing.
|
|
||||||
reviewerInput.notify = NotifyHandling.NONE;
|
|
||||||
|
|
||||||
ReviewerAddition result =
|
ReviewerAddition result =
|
||||||
reviewerAdder.prepare(revision.getNotes(), revision.getUser(), reviewerInput, true);
|
reviewerAdder.prepare(revision.getNotes(), revision.getUser(), reviewerInput, true);
|
||||||
reviewerJsonResults.put(reviewerInput.reviewer, result.result);
|
reviewerJsonResults.put(reviewerInput.reviewer, result.result);
|
||||||
@@ -312,6 +304,7 @@ public class PostReview
|
|||||||
// updated set of reviewers. Also keep track of whether the user added
|
// updated set of reviewers. Also keep track of whether the user added
|
||||||
// themselves as a reviewer or to the CC list.
|
// themselves as a reviewer or to the CC list.
|
||||||
for (ReviewerAddition reviewerResult : reviewerResults) {
|
for (ReviewerAddition reviewerResult : reviewerResults) {
|
||||||
|
reviewerResult.op.suppressEmail(); // Send a single batch email below.
|
||||||
bu.addOp(revision.getChange().getId(), reviewerResult.op);
|
bu.addOp(revision.getChange().getId(), reviewerResult.op);
|
||||||
if (!ccOrReviewer && reviewerResult.result.reviewers != null) {
|
if (!ccOrReviewer && reviewerResult.result.reviewers != null) {
|
||||||
for (ReviewerInfo reviewerInfo : reviewerResult.result.reviewers) {
|
for (ReviewerInfo reviewerInfo : reviewerResult.result.reviewers) {
|
||||||
@@ -336,6 +329,7 @@ public class PostReview
|
|||||||
// isn't being explicitly added, and isn't voting on any label.
|
// isn't being explicitly added, and isn't voting on any label.
|
||||||
// Automatically CC them on this change so they receive replies.
|
// Automatically CC them on this change so they receive replies.
|
||||||
ReviewerAddition selfAddition = reviewerAdder.ccCurrentUser(revision.getUser(), revision);
|
ReviewerAddition selfAddition = reviewerAdder.ccCurrentUser(revision.getUser(), revision);
|
||||||
|
selfAddition.op.suppressEmail();
|
||||||
bu.addOp(revision.getChange().getId(), selfAddition.op);
|
bu.addOp(revision.getChange().getId(), selfAddition.op);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,19 +347,21 @@ public class PostReview
|
|||||||
output.ready = true;
|
output.ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Suppress notifications in WorkInProgressOp, we'll take care of
|
WorkInProgressOp wipOp =
|
||||||
// them in this endpoint.
|
workInProgressOpFactory.create(input.workInProgress, new WorkInProgressOp.Input());
|
||||||
WorkInProgressOp.Input wipIn = new WorkInProgressOp.Input();
|
wipOp.suppressEmail();
|
||||||
wipIn.notify = NotifyHandling.NONE;
|
bu.addOp(revision.getChange().getId(), wipOp);
|
||||||
bu.addOp(
|
|
||||||
revision.getChange().getId(),
|
|
||||||
workInProgressOpFactory.create(input.workInProgress, wipIn));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the review op.
|
// Add the review op.
|
||||||
bu.addOp(
|
bu.addOp(
|
||||||
revision.getChange().getId(),
|
revision.getChange().getId(),
|
||||||
new Op(projectState, revision.getPatchSet().getId(), input, notify));
|
new Op(projectState, revision.getPatchSet().getId(), input));
|
||||||
|
|
||||||
|
// Notify based on ReviewInput, ignoring the notify settings from any AddReviewerInputs.
|
||||||
|
NotifyResolver.Result notify =
|
||||||
|
notifyResolver.resolve(getNotifyHandling(input, output, revision), input.notifyDetails);
|
||||||
|
bu.setNotify(notify);
|
||||||
|
|
||||||
bu.execute();
|
bu.execute();
|
||||||
|
|
||||||
@@ -382,6 +378,17 @@ public class PostReview
|
|||||||
return Response.ok(output);
|
return Response.ok(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private NotifyHandling getNotifyHandling(
|
||||||
|
ReviewInput input, ReviewResult output, RevisionResource revision) {
|
||||||
|
if (input.notify != null) {
|
||||||
|
return input.notify;
|
||||||
|
}
|
||||||
|
if ((output.ready != null && output.ready) || !revision.getChange().isWorkInProgress()) {
|
||||||
|
return NotifyHandling.ALL;
|
||||||
|
}
|
||||||
|
return NotifyHandling.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
private NotifyHandling defaultNotify(Change c, ReviewInput in) {
|
private NotifyHandling defaultNotify(Change c, ReviewInput in) {
|
||||||
boolean workInProgress = c.isWorkInProgress();
|
boolean workInProgress = c.isWorkInProgress();
|
||||||
if (in.workInProgress) {
|
if (in.workInProgress) {
|
||||||
@@ -828,7 +835,6 @@ public class PostReview
|
|||||||
private final ProjectState projectState;
|
private final ProjectState projectState;
|
||||||
private final PatchSet.Id psId;
|
private final PatchSet.Id psId;
|
||||||
private final ReviewInput in;
|
private final ReviewInput in;
|
||||||
private final NotifyResolver.Result notify;
|
|
||||||
|
|
||||||
private IdentifiedUser user;
|
private IdentifiedUser user;
|
||||||
private ChangeNotes notes;
|
private ChangeNotes notes;
|
||||||
@@ -839,12 +845,10 @@ public class PostReview
|
|||||||
private Map<String, Short> approvals = new HashMap<>();
|
private Map<String, Short> approvals = new HashMap<>();
|
||||||
private Map<String, Short> oldApprovals = new HashMap<>();
|
private Map<String, Short> oldApprovals = new HashMap<>();
|
||||||
|
|
||||||
private Op(
|
private Op(ProjectState projectState, PatchSet.Id psId, ReviewInput in) {
|
||||||
ProjectState projectState, PatchSet.Id psId, ReviewInput in, NotifyResolver.Result notify) {
|
|
||||||
this.projectState = projectState;
|
this.projectState = projectState;
|
||||||
this.psId = psId;
|
this.psId = psId;
|
||||||
this.in = in;
|
this.in = in;
|
||||||
this.notify = requireNonNull(notify);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -867,6 +871,7 @@ public class PostReview
|
|||||||
if (message == null) {
|
if (message == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
NotifyResolver.Result notify = ctx.getNotify(notes.getChangeId());
|
||||||
if (notify.shouldNotify()) {
|
if (notify.shouldNotify()) {
|
||||||
email
|
email
|
||||||
.create(notify, notes, ps, user, message, comments, in.message, labelDelta)
|
.create(notify, notes, ps, user, message, comments, in.message, labelDelta)
|
||||||
|
|||||||
@@ -16,10 +16,12 @@ package com.google.gerrit.server.restapi.change;
|
|||||||
|
|
||||||
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
|
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
|
||||||
import com.google.gerrit.extensions.api.changes.AddReviewerResult;
|
import com.google.gerrit.extensions.api.changes.AddReviewerResult;
|
||||||
|
import com.google.gerrit.extensions.api.changes.NotifyHandling;
|
||||||
import com.google.gerrit.extensions.restapi.BadRequestException;
|
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
import com.google.gerrit.reviewdb.client.Change;
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
import com.google.gerrit.server.change.ChangeResource;
|
import com.google.gerrit.server.change.ChangeResource;
|
||||||
|
import com.google.gerrit.server.change.NotifyResolver;
|
||||||
import com.google.gerrit.server.change.ReviewerAdder;
|
import com.google.gerrit.server.change.ReviewerAdder;
|
||||||
import com.google.gerrit.server.change.ReviewerAdder.ReviewerAddition;
|
import com.google.gerrit.server.change.ReviewerAdder.ReviewerAddition;
|
||||||
import com.google.gerrit.server.change.ReviewerResource;
|
import com.google.gerrit.server.change.ReviewerResource;
|
||||||
@@ -42,13 +44,18 @@ public class PostReviewers
|
|||||||
ChangeResource, ReviewerResource, AddReviewerInput, AddReviewerResult> {
|
ChangeResource, ReviewerResource, AddReviewerInput, AddReviewerResult> {
|
||||||
|
|
||||||
private final ChangeData.Factory changeDataFactory;
|
private final ChangeData.Factory changeDataFactory;
|
||||||
|
private final NotifyResolver notifyResolver;
|
||||||
private final ReviewerAdder reviewerAdder;
|
private final ReviewerAdder reviewerAdder;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
PostReviewers(
|
PostReviewers(
|
||||||
ChangeData.Factory changeDataFactory, RetryHelper retryHelper, ReviewerAdder reviewerAdder) {
|
ChangeData.Factory changeDataFactory,
|
||||||
|
RetryHelper retryHelper,
|
||||||
|
NotifyResolver notifyResolver,
|
||||||
|
ReviewerAdder reviewerAdder) {
|
||||||
super(retryHelper);
|
super(retryHelper);
|
||||||
this.changeDataFactory = changeDataFactory;
|
this.changeDataFactory = changeDataFactory;
|
||||||
|
this.notifyResolver = notifyResolver;
|
||||||
this.reviewerAdder = reviewerAdder;
|
this.reviewerAdder = reviewerAdder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,6 +74,7 @@ public class PostReviewers
|
|||||||
}
|
}
|
||||||
try (BatchUpdate bu =
|
try (BatchUpdate bu =
|
||||||
updateFactory.create(rsrc.getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
|
updateFactory.create(rsrc.getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
|
||||||
|
bu.setNotify(resolveNotify(rsrc, input));
|
||||||
Change.Id id = rsrc.getChange().getId();
|
Change.Id id = rsrc.getChange().getId();
|
||||||
bu.addOp(id, addition.op);
|
bu.addOp(id, addition.op);
|
||||||
bu.execute();
|
bu.execute();
|
||||||
@@ -76,4 +84,14 @@ public class PostReviewers
|
|||||||
addition.gatherResults(changeDataFactory.create(rsrc.getProject(), rsrc.getId()));
|
addition.gatherResults(changeDataFactory.create(rsrc.getProject(), rsrc.getId()));
|
||||||
return addition.result;
|
return addition.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private NotifyResolver.Result resolveNotify(ChangeResource rsrc, AddReviewerInput input)
|
||||||
|
throws BadRequestException, OrmException, ConfigInvalidException, IOException {
|
||||||
|
NotifyHandling notifyHandling = input.notify;
|
||||||
|
if (notifyHandling == null) {
|
||||||
|
notifyHandling =
|
||||||
|
rsrc.getChange().isWorkInProgress() ? NotifyHandling.NONE : NotifyHandling.ALL;
|
||||||
|
}
|
||||||
|
return notifyResolver.resolve(notifyHandling, input.notifyDetails);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ public class PutAssignee extends RetryingRestModifyView<ChangeResource, Assignee
|
|||||||
bu.addOp(rsrc.getId(), op);
|
bu.addOp(rsrc.getId(), op);
|
||||||
|
|
||||||
ReviewerAddition reviewersAddition = addAssigneeAsCC(rsrc, input.assignee);
|
ReviewerAddition reviewersAddition = addAssigneeAsCC(rsrc, input.assignee);
|
||||||
|
reviewersAddition.op.suppressEmail();
|
||||||
bu.addOp(rsrc.getId(), reviewersAddition.op);
|
bu.addOp(rsrc.getId(), reviewersAddition.op);
|
||||||
|
|
||||||
bu.execute();
|
bu.execute();
|
||||||
|
|||||||
@@ -140,8 +140,7 @@ public class PutMessage
|
|||||||
inserter.setMessage(
|
inserter.setMessage(
|
||||||
String.format("Patch Set %s: Commit message was updated.", psId.getId()));
|
String.format("Patch Set %s: Commit message was updated.", psId.getId()));
|
||||||
inserter.setDescription("Edit commit message");
|
inserter.setDescription("Edit commit message");
|
||||||
NotifyResolver.Result notify = resolveNotify(input, resource);
|
bu.setNotify(resolveNotify(input, resource));
|
||||||
inserter.setNotify(notify);
|
|
||||||
bu.addOp(resource.getChange().getId(), inserter);
|
bu.addOp(resource.getChange().getId(), inserter);
|
||||||
bu.execute();
|
bu.execute();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import com.google.gerrit.server.ChangeUtil;
|
|||||||
import com.google.gerrit.server.PatchSetUtil;
|
import com.google.gerrit.server.PatchSetUtil;
|
||||||
import com.google.gerrit.server.change.ChangeJson;
|
import com.google.gerrit.server.change.ChangeJson;
|
||||||
import com.google.gerrit.server.change.ChangeResource;
|
import com.google.gerrit.server.change.ChangeResource;
|
||||||
|
import com.google.gerrit.server.change.NotifyResolver;
|
||||||
import com.google.gerrit.server.change.RebaseChangeOp;
|
import com.google.gerrit.server.change.RebaseChangeOp;
|
||||||
import com.google.gerrit.server.change.RebaseUtil;
|
import com.google.gerrit.server.change.RebaseUtil;
|
||||||
import com.google.gerrit.server.change.RebaseUtil.Base;
|
import com.google.gerrit.server.change.RebaseUtil.Base;
|
||||||
@@ -120,6 +121,8 @@ public class Rebase extends RetryingRestModifyView<RevisionResource, RebaseInput
|
|||||||
throw new ResourceConflictException(
|
throw new ResourceConflictException(
|
||||||
"cannot rebase merge commits or commit with no ancestor");
|
"cannot rebase merge commits or commit with no ancestor");
|
||||||
}
|
}
|
||||||
|
// TODO(dborowitz): Why no notification? This seems wrong; dig up blame.
|
||||||
|
bu.setNotify(NotifyResolver.Result.none());
|
||||||
bu.setRepository(repo, rw, oi);
|
bu.setRepository(repo, rw, oi);
|
||||||
bu.addOp(
|
bu.addOp(
|
||||||
change.getId(),
|
change.getId(),
|
||||||
|
|||||||
@@ -224,7 +224,6 @@ public class Revert extends RetryingRestModifyView<ChangeResource, RevertInput,
|
|||||||
.create(changeId, revertCommit, notes.getChange().getDest().get())
|
.create(changeId, revertCommit, notes.getChange().getDest().get())
|
||||||
.setTopic(changeToRevert.getTopic());
|
.setTopic(changeToRevert.getTopic());
|
||||||
ins.setMessage("Uploaded patch set 1.");
|
ins.setMessage("Uploaded patch set 1.");
|
||||||
ins.setNotify(notify);
|
|
||||||
|
|
||||||
ReviewerSet reviewerSet = approvalsUtil.getReviewers(notes);
|
ReviewerSet reviewerSet = approvalsUtil.getReviewers(notes);
|
||||||
|
|
||||||
@@ -239,8 +238,9 @@ public class Revert extends RetryingRestModifyView<ChangeResource, RevertInput,
|
|||||||
|
|
||||||
try (BatchUpdate bu = updateFactory.create(project, user, now)) {
|
try (BatchUpdate bu = updateFactory.create(project, user, now)) {
|
||||||
bu.setRepository(git, revWalk, oi);
|
bu.setRepository(git, revWalk, oi);
|
||||||
|
bu.setNotify(notify);
|
||||||
bu.insertChange(ins);
|
bu.insertChange(ins);
|
||||||
bu.addOp(changeId, new NotifyOp(changeToRevert, ins, notify));
|
bu.addOp(changeId, new NotifyOp(changeToRevert, ins));
|
||||||
bu.addOp(changeToRevert.getId(), new PostRevertedMessageOp(computedChangeId));
|
bu.addOp(changeToRevert.getId(), new PostRevertedMessageOp(computedChangeId));
|
||||||
bu.execute();
|
bu.execute();
|
||||||
}
|
}
|
||||||
@@ -275,12 +275,10 @@ public class Revert extends RetryingRestModifyView<ChangeResource, RevertInput,
|
|||||||
private class NotifyOp implements BatchUpdateOp {
|
private class NotifyOp implements BatchUpdateOp {
|
||||||
private final Change change;
|
private final Change change;
|
||||||
private final ChangeInserter ins;
|
private final ChangeInserter ins;
|
||||||
private final NotifyResolver.Result notify;
|
|
||||||
|
|
||||||
NotifyOp(Change change, ChangeInserter ins, NotifyResolver.Result notify) {
|
NotifyOp(Change change, ChangeInserter ins) {
|
||||||
this.change = change;
|
this.change = change;
|
||||||
this.ins = ins;
|
this.ins = ins;
|
||||||
this.notify = notify;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -289,7 +287,7 @@ public class Revert extends RetryingRestModifyView<ChangeResource, RevertInput,
|
|||||||
try {
|
try {
|
||||||
RevertedSender cm = revertedSenderFactory.create(ctx.getProject(), change.getId());
|
RevertedSender cm = revertedSenderFactory.create(ctx.getProject(), change.getId());
|
||||||
cm.setFrom(ctx.getAccountId());
|
cm.setFrom(ctx.getAccountId());
|
||||||
cm.setNotify(notify);
|
cm.setNotify(ctx.getNotify(change.getId()));
|
||||||
cm.send();
|
cm.send();
|
||||||
} catch (Exception err) {
|
} catch (Exception err) {
|
||||||
logger.atSevere().withCause(err).log(
|
logger.atSevere().withCause(err).log(
|
||||||
|
|||||||
@@ -14,9 +14,11 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.restapi.change;
|
package com.google.gerrit.server.restapi.change;
|
||||||
|
|
||||||
|
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||||
import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
|
import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
|
||||||
import static com.google.gerrit.extensions.conditions.BooleanCondition.or;
|
import static com.google.gerrit.extensions.conditions.BooleanCondition.or;
|
||||||
|
|
||||||
|
import com.google.gerrit.extensions.api.changes.NotifyHandling;
|
||||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||||
import com.google.gerrit.extensions.restapi.Response;
|
import com.google.gerrit.extensions.restapi.Response;
|
||||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
@@ -26,6 +28,7 @@ import com.google.gerrit.reviewdb.client.Change.Status;
|
|||||||
import com.google.gerrit.server.ChangeUtil;
|
import com.google.gerrit.server.ChangeUtil;
|
||||||
import com.google.gerrit.server.CurrentUser;
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.change.ChangeResource;
|
import com.google.gerrit.server.change.ChangeResource;
|
||||||
|
import com.google.gerrit.server.change.NotifyResolver;
|
||||||
import com.google.gerrit.server.change.WorkInProgressOp;
|
import com.google.gerrit.server.change.WorkInProgressOp;
|
||||||
import com.google.gerrit.server.change.WorkInProgressOp.Input;
|
import com.google.gerrit.server.change.WorkInProgressOp.Input;
|
||||||
import com.google.gerrit.server.permissions.GlobalPermission;
|
import com.google.gerrit.server.permissions.GlobalPermission;
|
||||||
@@ -77,6 +80,7 @@ public class SetReadyForReview extends RetryingRestModifyView<ChangeResource, In
|
|||||||
|
|
||||||
try (BatchUpdate bu =
|
try (BatchUpdate bu =
|
||||||
updateFactory.create(rsrc.getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
|
updateFactory.create(rsrc.getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
|
||||||
|
bu.setNotify(NotifyResolver.Result.create(firstNonNull(input.notify, NotifyHandling.ALL)));
|
||||||
bu.addOp(rsrc.getChange().getId(), opFactory.create(false, input));
|
bu.addOp(rsrc.getChange().getId(), opFactory.create(false, input));
|
||||||
bu.execute();
|
bu.execute();
|
||||||
return Response.ok("");
|
return Response.ok("");
|
||||||
|
|||||||
@@ -14,9 +14,11 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.restapi.change;
|
package com.google.gerrit.server.restapi.change;
|
||||||
|
|
||||||
|
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||||
import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
|
import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
|
||||||
import static com.google.gerrit.extensions.conditions.BooleanCondition.or;
|
import static com.google.gerrit.extensions.conditions.BooleanCondition.or;
|
||||||
|
|
||||||
|
import com.google.gerrit.extensions.api.changes.NotifyHandling;
|
||||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||||
import com.google.gerrit.extensions.restapi.Response;
|
import com.google.gerrit.extensions.restapi.Response;
|
||||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
@@ -26,6 +28,7 @@ import com.google.gerrit.reviewdb.client.Change.Status;
|
|||||||
import com.google.gerrit.server.ChangeUtil;
|
import com.google.gerrit.server.ChangeUtil;
|
||||||
import com.google.gerrit.server.CurrentUser;
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.change.ChangeResource;
|
import com.google.gerrit.server.change.ChangeResource;
|
||||||
|
import com.google.gerrit.server.change.NotifyResolver;
|
||||||
import com.google.gerrit.server.change.WorkInProgressOp;
|
import com.google.gerrit.server.change.WorkInProgressOp;
|
||||||
import com.google.gerrit.server.change.WorkInProgressOp.Input;
|
import com.google.gerrit.server.change.WorkInProgressOp.Input;
|
||||||
import com.google.gerrit.server.permissions.GlobalPermission;
|
import com.google.gerrit.server.permissions.GlobalPermission;
|
||||||
@@ -77,6 +80,7 @@ public class SetWorkInProgress extends RetryingRestModifyView<ChangeResource, In
|
|||||||
|
|
||||||
try (BatchUpdate bu =
|
try (BatchUpdate bu =
|
||||||
updateFactory.create(rsrc.getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
|
updateFactory.create(rsrc.getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
|
||||||
|
bu.setNotify(NotifyResolver.Result.create(firstNonNull(input.notify, NotifyHandling.NONE)));
|
||||||
bu.addOp(rsrc.getChange().getId(), opFactory.create(true, input));
|
bu.addOp(rsrc.getChange().getId(), opFactory.create(true, input));
|
||||||
bu.execute();
|
bu.execute();
|
||||||
return Response.ok("");
|
return Response.ok("");
|
||||||
|
|||||||
@@ -533,7 +533,7 @@ public class MergeOp implements AutoCloseable {
|
|||||||
orm.close();
|
orm.close();
|
||||||
}
|
}
|
||||||
orm = ormProvider.get();
|
orm = ormProvider.get();
|
||||||
orm.setContext(ts, caller);
|
orm.setContext(ts, caller, notify);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ChangeSet reloadChanges(ChangeSet changeSet) {
|
private ChangeSet reloadChanges(ChangeSet changeSet) {
|
||||||
@@ -676,7 +676,6 @@ public class MergeOp implements AutoCloseable {
|
|||||||
commitStatus,
|
commitStatus,
|
||||||
submissionId,
|
submissionId,
|
||||||
submitInput,
|
submitInput,
|
||||||
notify,
|
|
||||||
submoduleOp,
|
submoduleOp,
|
||||||
dryrun);
|
dryrun);
|
||||||
strategies.add(strategy);
|
strategies.add(strategy);
|
||||||
|
|||||||
@@ -15,12 +15,14 @@
|
|||||||
package com.google.gerrit.server.submit;
|
package com.google.gerrit.server.submit;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.gerrit.reviewdb.client.Branch;
|
import com.google.gerrit.reviewdb.client.Branch;
|
||||||
import com.google.gerrit.reviewdb.client.Project;
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
import com.google.gerrit.reviewdb.client.RefNames;
|
import com.google.gerrit.reviewdb.client.RefNames;
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
|
import com.google.gerrit.server.change.NotifyResolver;
|
||||||
import com.google.gerrit.server.git.CodeReviewCommit;
|
import com.google.gerrit.server.git.CodeReviewCommit;
|
||||||
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
|
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
|
||||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
@@ -110,6 +112,7 @@ public class MergeOpRepoManager implements AutoCloseable {
|
|||||||
batchUpdateFactory
|
batchUpdateFactory
|
||||||
.create(getProjectName(), caller, ts)
|
.create(getProjectName(), caller, ts)
|
||||||
.setRepository(repo, rw, ins)
|
.setRepository(repo, rw, ins)
|
||||||
|
.setNotify(notify)
|
||||||
.setOnSubmitValidators(onSubmitValidatorsFactory.create());
|
.setOnSubmitValidators(onSubmitValidatorsFactory.create());
|
||||||
}
|
}
|
||||||
return update;
|
return update;
|
||||||
@@ -158,6 +161,7 @@ public class MergeOpRepoManager implements AutoCloseable {
|
|||||||
|
|
||||||
private Timestamp ts;
|
private Timestamp ts;
|
||||||
private IdentifiedUser caller;
|
private IdentifiedUser caller;
|
||||||
|
private NotifyResolver.Result notify;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MergeOpRepoManager(
|
MergeOpRepoManager(
|
||||||
@@ -173,9 +177,10 @@ public class MergeOpRepoManager implements AutoCloseable {
|
|||||||
openRepos = new HashMap<>();
|
openRepos = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setContext(Timestamp ts, IdentifiedUser caller) {
|
public void setContext(Timestamp ts, IdentifiedUser caller, NotifyResolver.Result notify) {
|
||||||
this.ts = ts;
|
this.ts = requireNonNull(ts);
|
||||||
this.caller = caller;
|
this.caller = requireNonNull(caller);
|
||||||
|
this.notify = requireNonNull(notify);
|
||||||
}
|
}
|
||||||
|
|
||||||
public OpenRepo getRepo(Project.NameKey project) throws NoSuchProjectException, IOException {
|
public OpenRepo getRepo(Project.NameKey project) throws NoSuchProjectException, IOException {
|
||||||
@@ -200,7 +205,7 @@ public class MergeOpRepoManager implements AutoCloseable {
|
|||||||
throws NoSuchProjectException, IOException {
|
throws NoSuchProjectException, IOException {
|
||||||
List<BatchUpdate> updates = new ArrayList<>(projects.size());
|
List<BatchUpdate> updates = new ArrayList<>(projects.size());
|
||||||
for (Project.NameKey project : projects) {
|
for (Project.NameKey project : projects) {
|
||||||
updates.add(getRepo(project).getUpdate().setRefLogMessage("merged"));
|
updates.add(getRepo(project).getUpdate().setNotify(notify).setRefLogMessage("merged"));
|
||||||
}
|
}
|
||||||
return updates;
|
return updates;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import com.google.gerrit.server.IdentifiedUser;
|
|||||||
import com.google.gerrit.server.PatchSetUtil;
|
import com.google.gerrit.server.PatchSetUtil;
|
||||||
import com.google.gerrit.server.account.AccountCache;
|
import com.google.gerrit.server.account.AccountCache;
|
||||||
import com.google.gerrit.server.change.LabelNormalizer;
|
import com.google.gerrit.server.change.LabelNormalizer;
|
||||||
import com.google.gerrit.server.change.NotifyResolver;
|
|
||||||
import com.google.gerrit.server.change.RebaseChangeOp;
|
import com.google.gerrit.server.change.RebaseChangeOp;
|
||||||
import com.google.gerrit.server.change.TestSubmitInput;
|
import com.google.gerrit.server.change.TestSubmitInput;
|
||||||
import com.google.gerrit.server.extensions.events.ChangeMerged;
|
import com.google.gerrit.server.extensions.events.ChangeMerged;
|
||||||
@@ -92,7 +91,6 @@ public abstract class SubmitStrategy {
|
|||||||
Set<CodeReviewCommit> incoming,
|
Set<CodeReviewCommit> incoming,
|
||||||
RequestId submissionId,
|
RequestId submissionId,
|
||||||
SubmitInput submitInput,
|
SubmitInput submitInput,
|
||||||
NotifyResolver.Result notify,
|
|
||||||
SubmoduleOp submoduleOp,
|
SubmoduleOp submoduleOp,
|
||||||
boolean dryrun);
|
boolean dryrun);
|
||||||
}
|
}
|
||||||
@@ -124,7 +122,6 @@ public abstract class SubmitStrategy {
|
|||||||
final RequestId submissionId;
|
final RequestId submissionId;
|
||||||
final SubmitType submitType;
|
final SubmitType submitType;
|
||||||
final SubmitInput submitInput;
|
final SubmitInput submitInput;
|
||||||
final NotifyResolver.Result notify;
|
|
||||||
final SubmoduleOp submoduleOp;
|
final SubmoduleOp submoduleOp;
|
||||||
|
|
||||||
final ProjectState project;
|
final ProjectState project;
|
||||||
@@ -163,7 +160,6 @@ public abstract class SubmitStrategy {
|
|||||||
@Assisted RequestId submissionId,
|
@Assisted RequestId submissionId,
|
||||||
@Assisted SubmitType submitType,
|
@Assisted SubmitType submitType,
|
||||||
@Assisted SubmitInput submitInput,
|
@Assisted SubmitInput submitInput,
|
||||||
@Assisted NotifyResolver.Result notify,
|
|
||||||
@Assisted SubmoduleOp submoduleOp,
|
@Assisted SubmoduleOp submoduleOp,
|
||||||
@Assisted boolean dryrun) {
|
@Assisted boolean dryrun) {
|
||||||
this.accountCache = accountCache;
|
this.accountCache = accountCache;
|
||||||
@@ -192,7 +188,6 @@ public abstract class SubmitStrategy {
|
|||||||
this.submissionId = submissionId;
|
this.submissionId = submissionId;
|
||||||
this.submitType = submitType;
|
this.submitType = submitType;
|
||||||
this.submitInput = submitInput;
|
this.submitInput = submitInput;
|
||||||
this.notify = notify;
|
|
||||||
this.submoduleOp = submoduleOp;
|
this.submoduleOp = submoduleOp;
|
||||||
this.dryrun = dryrun;
|
this.dryrun = dryrun;
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import com.google.gerrit.extensions.api.changes.SubmitInput;
|
|||||||
import com.google.gerrit.extensions.client.SubmitType;
|
import com.google.gerrit.extensions.client.SubmitType;
|
||||||
import com.google.gerrit.reviewdb.client.Branch;
|
import com.google.gerrit.reviewdb.client.Branch;
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
import com.google.gerrit.server.change.NotifyResolver;
|
|
||||||
import com.google.gerrit.server.git.CodeReviewCommit;
|
import com.google.gerrit.server.git.CodeReviewCommit;
|
||||||
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
|
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
|
||||||
import com.google.gerrit.server.git.MergeTip;
|
import com.google.gerrit.server.git.MergeTip;
|
||||||
@@ -55,7 +54,6 @@ public class SubmitStrategyFactory {
|
|||||||
CommitStatus commitStatus,
|
CommitStatus commitStatus,
|
||||||
RequestId submissionId,
|
RequestId submissionId,
|
||||||
SubmitInput submitInput,
|
SubmitInput submitInput,
|
||||||
NotifyResolver.Result notify,
|
|
||||||
SubmoduleOp submoduleOp,
|
SubmoduleOp submoduleOp,
|
||||||
boolean dryrun)
|
boolean dryrun)
|
||||||
throws IntegrationException {
|
throws IntegrationException {
|
||||||
@@ -72,7 +70,6 @@ public class SubmitStrategyFactory {
|
|||||||
incoming,
|
incoming,
|
||||||
submissionId,
|
submissionId,
|
||||||
submitInput,
|
submitInput,
|
||||||
notify,
|
|
||||||
submoduleOp,
|
submoduleOp,
|
||||||
dryrun);
|
dryrun);
|
||||||
switch (submitType) {
|
switch (submitType) {
|
||||||
|
|||||||
@@ -501,7 +501,7 @@ abstract class SubmitStrategyOp implements BatchUpdateOp {
|
|||||||
// have failed fast in one of the other steps.
|
// have failed fast in one of the other steps.
|
||||||
try {
|
try {
|
||||||
args.mergedSenderFactory
|
args.mergedSenderFactory
|
||||||
.create(ctx.getProject(), getId(), submitter.getAccountId(), args.notify)
|
.create(ctx.getProject(), getId(), submitter.getAccountId(), ctx.getNotify(getId()))
|
||||||
.sendAsync();
|
.sendAsync();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.atSevere().withCause(e).log("Cannot email merged notification for %s", getId());
|
logger.atSevere().withCause(e).log("Cannot email merged notification for %s", getId());
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import com.google.common.collect.MultimapBuilder;
|
|||||||
import com.google.common.collect.Multiset;
|
import com.google.common.collect.Multiset;
|
||||||
import com.google.common.flogger.FluentLogger;
|
import com.google.common.flogger.FluentLogger;
|
||||||
import com.google.gerrit.common.Nullable;
|
import com.google.gerrit.common.Nullable;
|
||||||
|
import com.google.gerrit.extensions.api.changes.NotifyHandling;
|
||||||
import com.google.gerrit.extensions.config.FactoryModule;
|
import com.google.gerrit.extensions.config.FactoryModule;
|
||||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||||
@@ -40,6 +41,7 @@ import com.google.gerrit.reviewdb.client.Project;
|
|||||||
import com.google.gerrit.server.CurrentUser;
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.GerritPersonIdent;
|
import com.google.gerrit.server.GerritPersonIdent;
|
||||||
import com.google.gerrit.server.account.AccountState;
|
import com.google.gerrit.server.account.AccountState;
|
||||||
|
import com.google.gerrit.server.change.NotifyResolver;
|
||||||
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
|
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
|
||||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
import com.google.gerrit.server.git.validators.OnSubmitValidators;
|
import com.google.gerrit.server.git.validators.OnSubmitValidators;
|
||||||
@@ -229,6 +231,12 @@ public class BatchUpdate implements AutoCloseable {
|
|||||||
public CurrentUser getUser() {
|
public CurrentUser getUser() {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NotifyResolver.Result getNotify(Change.Id changeId) {
|
||||||
|
NotifyHandling notifyHandling = perChangeNotifyHandling.get(changeId);
|
||||||
|
return notifyHandling != null ? notify.withHandling(notifyHandling) : notify;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RepoContextImpl extends ContextImpl implements RepoContext {
|
private class RepoContextImpl extends ContextImpl implements RepoContext {
|
||||||
@@ -302,12 +310,14 @@ public class BatchUpdate implements AutoCloseable {
|
|||||||
MultimapBuilder.linkedHashKeys().arrayListValues().build();
|
MultimapBuilder.linkedHashKeys().arrayListValues().build();
|
||||||
private final Map<Change.Id, Change> newChanges = new HashMap<>();
|
private final Map<Change.Id, Change> newChanges = new HashMap<>();
|
||||||
private final List<RepoOnlyOp> repoOnlyOps = new ArrayList<>();
|
private final List<RepoOnlyOp> repoOnlyOps = new ArrayList<>();
|
||||||
|
private final Map<Change.Id, NotifyHandling> perChangeNotifyHandling = new HashMap<>();
|
||||||
|
|
||||||
private RepoView repoView;
|
private RepoView repoView;
|
||||||
private BatchRefUpdate batchRefUpdate;
|
private BatchRefUpdate batchRefUpdate;
|
||||||
private OnSubmitValidators onSubmitValidators;
|
private OnSubmitValidators onSubmitValidators;
|
||||||
private PushCertificate pushCert;
|
private PushCertificate pushCert;
|
||||||
private String refLogMessage;
|
private String refLogMessage;
|
||||||
|
private NotifyResolver.Result notify = NotifyResolver.Result.all();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
BatchUpdate(
|
BatchUpdate(
|
||||||
@@ -364,6 +374,32 @@ public class BatchUpdate implements AutoCloseable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the default notification settings for all changes in the batch.
|
||||||
|
*
|
||||||
|
* @param notify notification settings.
|
||||||
|
* @return this.
|
||||||
|
*/
|
||||||
|
public BatchUpdate setNotify(NotifyResolver.Result notify) {
|
||||||
|
this.notify = requireNonNull(notify);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override the {@link NotifyHandling} on a per-change basis.
|
||||||
|
*
|
||||||
|
* <p>Only the handling enum can be overridden; all changes share the same value for {@link
|
||||||
|
* NotifyResolver.Result#accounts()}.
|
||||||
|
*
|
||||||
|
* @param changeId change ID.
|
||||||
|
* @param notifyHandling notify handling.
|
||||||
|
* @return this.
|
||||||
|
*/
|
||||||
|
public BatchUpdate setNotifyHandling(Change.Id changeId, NotifyHandling notifyHandling) {
|
||||||
|
this.perChangeNotifyHandling.put(changeId, requireNonNull(notifyHandling));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a validation step for intended ref operations, which will be performed at the end of {@link
|
* Add a validation step for intended ref operations, which will be performed at the end of {@link
|
||||||
* RepoOnlyOp#updateRepo(RepoContext)} step.
|
* RepoOnlyOp#updateRepo(RepoContext)} step.
|
||||||
|
|||||||
@@ -17,10 +17,12 @@ package com.google.gerrit.server.update;
|
|||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
import com.google.gerrit.reviewdb.client.Account;
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
import com.google.gerrit.reviewdb.client.Project;
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
import com.google.gerrit.server.CurrentUser;
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
import com.google.gerrit.server.account.AccountState;
|
import com.google.gerrit.server.account.AccountState;
|
||||||
|
import com.google.gerrit.server.change.NotifyResolver;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
@@ -85,6 +87,18 @@ public interface Context {
|
|||||||
*/
|
*/
|
||||||
CurrentUser getUser();
|
CurrentUser getUser();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the notification settings configured by the caller.
|
||||||
|
*
|
||||||
|
* <p>If there are multiple changes in a batch, they may have different settings. For example, WIP
|
||||||
|
* changes may have reduced {@code NotifyHandling} levels, and may be in a batch with non-WIP
|
||||||
|
* changes.
|
||||||
|
*
|
||||||
|
* @param changeId change ID
|
||||||
|
* @return notification settings.
|
||||||
|
*/
|
||||||
|
NotifyResolver.Result getNotify(Change.Id changeId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the identified user performing the update.
|
* Get the identified user performing the update.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -749,11 +749,11 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
|
|||||||
ChangeInserter ins;
|
ChangeInserter ins;
|
||||||
try (BatchUpdate bu = newUpdate(owner.getId())) {
|
try (BatchUpdate bu = newUpdate(owner.getId())) {
|
||||||
RevCommit commit = patchSetCommit(new PatchSet.Id(id, 1));
|
RevCommit commit = patchSetCommit(new PatchSet.Id(id, 1));
|
||||||
|
bu.setNotify(NotifyResolver.Result.none());
|
||||||
ins =
|
ins =
|
||||||
changeInserterFactory
|
changeInserterFactory
|
||||||
.create(id, commit, dest)
|
.create(id, commit, dest)
|
||||||
.setValidate(false)
|
.setValidate(false)
|
||||||
.setNotify(NotifyResolver.Result.none())
|
|
||||||
.setFireRevisionCreated(false)
|
.setFireRevisionCreated(false)
|
||||||
.setSendMail(false);
|
.setSendMail(false);
|
||||||
bu.insertChange(ins).execute();
|
bu.insertChange(ins).execute();
|
||||||
@@ -773,12 +773,12 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
|
|||||||
private ChangeNotes incrementPatchSet(ChangeNotes notes, RevCommit commit) throws Exception {
|
private ChangeNotes incrementPatchSet(ChangeNotes notes, RevCommit commit) throws Exception {
|
||||||
PatchSetInserter ins;
|
PatchSetInserter ins;
|
||||||
try (BatchUpdate bu = newUpdate(notes.getChange().getOwner())) {
|
try (BatchUpdate bu = newUpdate(notes.getChange().getOwner())) {
|
||||||
|
bu.setNotify(NotifyResolver.Result.none());
|
||||||
ins =
|
ins =
|
||||||
patchSetInserterFactory
|
patchSetInserterFactory
|
||||||
.create(notes, nextPatchSetId(notes), commit)
|
.create(notes, nextPatchSetId(notes), commit)
|
||||||
.setValidate(false)
|
.setValidate(false)
|
||||||
.setFireRevisionCreated(false)
|
.setFireRevisionCreated(false);
|
||||||
.setNotify(NotifyResolver.Result.none());
|
|
||||||
bu.addOp(notes.getChangeId(), ins).execute();
|
bu.addOp(notes.getChangeId(), ins).execute();
|
||||||
}
|
}
|
||||||
return reload(notes);
|
return reload(notes);
|
||||||
|
|||||||
@@ -3176,7 +3176,6 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
|
|||||||
PatchSetInserter inserter =
|
PatchSetInserter inserter =
|
||||||
patchSetFactory
|
patchSetFactory
|
||||||
.create(changeNotesFactory.createChecked(c), new PatchSet.Id(c.getId(), n), commit)
|
.create(changeNotesFactory.createChecked(c), new PatchSet.Id(c.getId(), n), commit)
|
||||||
.setNotify(NotifyResolver.Result.none())
|
|
||||||
.setFireRevisionCreated(false)
|
.setFireRevisionCreated(false)
|
||||||
.setValidate(false);
|
.setValidate(false);
|
||||||
try (BatchUpdate bu = updateFactory.create(c.getProject(), user, TimeUtil.nowTs());
|
try (BatchUpdate bu = updateFactory.create(c.getProject(), user, TimeUtil.nowTs());
|
||||||
@@ -3184,6 +3183,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
|
|||||||
ObjectReader reader = oi.newReader();
|
ObjectReader reader = oi.newReader();
|
||||||
RevWalk rw = new RevWalk(reader)) {
|
RevWalk rw = new RevWalk(reader)) {
|
||||||
bu.setRepository(repo.getRepository(), rw, oi);
|
bu.setRepository(repo.getRepository(), rw, oi);
|
||||||
|
bu.setNotify(NotifyResolver.Result.none());
|
||||||
bu.addOp(c.getId(), inserter);
|
bu.addOp(c.getId(), inserter);
|
||||||
bu.execute();
|
bu.execute();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user