use of com.google.gerrit.server.change.ReviewerModifier.ReviewerModification in project gerrit by GerritCodeReview.
the class PostReview method batchEmailReviewers.
private void batchEmailReviewers(CurrentUser user, Change change, List<ReviewerModification> reviewerModifications, NotifyResolver.Result notify) {
try (TraceContext.TraceTimer ignored = newTimer("batchEmailReviewers")) {
List<Account.Id> to = new ArrayList<>();
List<Account.Id> cc = new ArrayList<>();
List<Account.Id> removed = new ArrayList<>();
List<Address> toByEmail = new ArrayList<>();
List<Address> ccByEmail = new ArrayList<>();
List<Address> removedByEmail = new ArrayList<>();
for (ReviewerModification modification : reviewerModifications) {
Result reviewAdditionResult = modification.op.getResult();
if (modification.state() == ReviewerState.REVIEWER && (!reviewAdditionResult.addedReviewers().isEmpty() || !reviewAdditionResult.addedReviewersByEmail().isEmpty())) {
to.addAll(modification.reviewers.stream().map(Account::id).collect(toImmutableSet()));
toByEmail.addAll(modification.reviewersByEmail);
} else if (modification.state() == ReviewerState.CC && (!reviewAdditionResult.addedCCs().isEmpty() || !reviewAdditionResult.addedCCsByEmail().isEmpty())) {
cc.addAll(modification.reviewers.stream().map(Account::id).collect(toImmutableSet()));
ccByEmail.addAll(modification.reviewersByEmail);
} else if (modification.state() == ReviewerState.REMOVED && (reviewAdditionResult.deletedReviewer().isPresent() || reviewAdditionResult.deletedReviewerByEmail().isPresent())) {
reviewAdditionResult.deletedReviewer().ifPresent(d -> removed.add(d));
reviewAdditionResult.deletedReviewerByEmail().ifPresent(d -> removedByEmail.add(d));
}
}
modifyReviewersEmail.emailReviewersAsync(user.asIdentifiedUser(), change, to, cc, removed, toByEmail, ccByEmail, removedByEmail, notify);
}
}
use of com.google.gerrit.server.change.ReviewerModifier.ReviewerModification in project gerrit by GerritCodeReview.
the class ChangeInserter method updateChange.
@Override
public boolean updateChange(ChangeContext ctx) throws RestApiException, IOException, PermissionBackendException, ConfigInvalidException {
// Use defensive copy created by ChangeControl.
change = ctx.getChange();
patchSetInfo = patchSetInfoFactory.get(ctx.getRevWalk(), ctx.getRevWalk().parseCommit(commitId), psId);
ctx.getChange().setCurrentPatchSet(patchSetInfo);
ChangeUpdate update = ctx.getUpdate(psId);
update.setChangeId(change.getKey().get());
update.setSubjectForCommit("Create change");
update.setBranch(change.getDest().branch());
try {
update.setTopic(change.getTopic());
} catch (ValidationException ex) {
throw new BadRequestException(ex.getMessage());
}
update.setPsDescription(patchSetDescription);
update.setPrivate(isPrivate);
update.setWorkInProgress(workInProgress);
if (revertOf != null) {
update.setRevertOf(revertOf.get());
}
if (cherryPickOf != null) {
update.setCherryPickOf(cherryPickOf.getCommaSeparatedChangeAndPatchSetId());
}
List<String> newGroups = groups;
if (newGroups.isEmpty()) {
newGroups = GroupCollector.getDefaultGroups(commitId);
}
patchSet = psUtil.insert(ctx.getRevWalk(), update, psId, commitId, newGroups, pushCert, patchSetDescription);
/* TODO: fixStatusToMerged is used here because the tests
* (byStatusClosed() in AbstractQueryChangesTest)
* insert changes that are already merged,
* and setStatus may not be used to set the Status to merged
*
* is it possible to make the tests use the merge code path,
* instead of setting the status directly?
*/
if (change.getStatus() == Change.Status.MERGED) {
update.fixStatusToMerged(new SubmissionId(change));
} else {
update.setStatus(change.getStatus());
}
reviewerAdditions = reviewerModifier.prepare(ctx.getNotes(), ctx.getUser(), getReviewerInputs(), true);
Optional<ReviewerModification> reviewerError = reviewerAdditions.getFailures().stream().findFirst();
if (reviewerError.isPresent()) {
throw new UnprocessableEntityException(reviewerError.get().result.error);
}
reviewerAdditions.updateChange(ctx, patchSet);
LabelTypes labelTypes = projectState.getLabelTypes();
approvalsUtil.addApprovalsForNewPatchSet(update, labelTypes, patchSet, ctx.getUser(), approvals);
// TODO(dborowitz): Still necessary?
if (!approvals.isEmpty()) {
update.putReviewer(ctx.getAccountId(), REVIEWER);
}
if (message != null) {
changeMessage = cmUtil.setChangeMessage(update, message, ChangeMessagesUtil.uploadedPatchSetTag(workInProgress));
}
return true;
}
use of com.google.gerrit.server.change.ReviewerModifier.ReviewerModification in project gerrit by GerritCodeReview.
the class ReplaceOp method updateChange.
@Override
public boolean updateChange(ChangeContext ctx) throws RestApiException, IOException, PermissionBackendException, ConfigInvalidException, ValidationException {
notes = ctx.getNotes();
Change change = notes.getChange();
if (change == null || change.isClosed()) {
rejectMessage = CHANGE_IS_CLOSED;
return false;
}
if (groups.isEmpty()) {
PatchSet prevPs = psUtil.current(notes);
groups = prevPs != null ? prevPs.groups() : ImmutableList.of();
}
ChangeData cd = changeDataFactory.create(ctx.getNotes());
oldRecipients = getRecipientsFromReviewers(cd.reviewers());
ChangeUpdate update = ctx.getUpdate(patchSetId);
update.setSubjectForCommit("Create patch set " + patchSetId.get());
String reviewMessage = null;
String psDescription = null;
if (magicBranch != null) {
reviewMessage = magicBranch.message;
psDescription = magicBranch.message;
approvals.putAll(magicBranch.labels);
Set<String> hashtags = magicBranch.hashtags;
if (hashtags != null && !hashtags.isEmpty()) {
hashtags.addAll(notes.getHashtags());
update.setHashtags(hashtags);
}
if (magicBranch.topic != null && !magicBranch.topic.equals(ctx.getChange().getTopic())) {
try {
update.setTopic(magicBranch.topic);
} catch (ValidationException ex) {
throw new BadRequestException(ex.getMessage());
}
}
if (magicBranch.removePrivate) {
change.setPrivate(false);
update.setPrivate(false);
} else if (magicBranch.isPrivate) {
change.setPrivate(true);
update.setPrivate(true);
}
if (magicBranch.ready) {
change.setWorkInProgress(false);
change.setReviewStarted(true);
update.setWorkInProgress(false);
} else if (magicBranch.workInProgress) {
change.setWorkInProgress(true);
update.setWorkInProgress(true);
}
if (magicBranch.ignoreAttentionSet) {
update.ignoreFurtherAttentionSetUpdates();
}
}
newPatchSet = psUtil.insert(ctx.getRevWalk(), update, patchSetId, commitId, groups, pushCertificate != null ? pushCertificate.toTextWithSignature() : null, psDescription);
update.setPsDescription(psDescription);
MailRecipients fromFooters = getRecipientsFromFooters(accountResolver, commit.getFooterLines());
approvalsUtil.addApprovalsForNewPatchSet(update, projectState.getLabelTypes(), newPatchSet, ctx.getUser(), approvals);
reviewerAdditions = reviewerModifier.prepare(ctx.getNotes(), ctx.getUser(), getReviewerInputs(magicBranch, fromFooters, ctx.getChange(), info), true);
Optional<ReviewerModification> reviewerError = reviewerAdditions.getFailures().stream().findFirst();
if (reviewerError.isPresent()) {
throw new UnprocessableEntityException(reviewerError.get().result.error);
}
reviewerAdditions.updateChange(ctx, newPatchSet);
// reviewer which is needed in several other code paths.
if (magicBranch != null && !magicBranch.labels.isEmpty()) {
update.putReviewer(ctx.getAccountId(), REVIEWER);
}
approvalsUtil.persistCopiedApprovals(ctx.getNotes(), newPatchSet, ctx.getRevWalk(), ctx.getRepoView().getConfig(), update);
mailMessage = insertChangeMessage(update, ctx, reviewMessage);
if (mergedByPushOp == null) {
resetChange(ctx);
} else {
mergedByPushOp.setPatchSetProvider(Providers.of(newPatchSet)).updateChange(ctx);
}
return true;
}
use of com.google.gerrit.server.change.ReviewerModifier.ReviewerModification in project gerrit by GerritCodeReview.
the class PostReview method apply.
public Response<ReviewResult> apply(RevisionResource revision, ReviewInput input, Instant ts) throws RestApiException, UpdateException, IOException, PermissionBackendException, ConfigInvalidException, PatchListNotAvailableException {
// Respect timestamp, but truncate at change created-on time.
ts = Ordering.natural().max(ts, revision.getChange().getCreatedOn());
if (revision.getEdit().isPresent()) {
throw new ResourceConflictException("cannot post review on edit");
}
ProjectState projectState = projectCache.get(revision.getProject()).orElseThrow(illegalState(revision.getProject()));
LabelTypes labelTypes = projectState.getLabelTypes(revision.getNotes());
logger.atFine().log("strict label checking is %s", (strictLabels ? "enabled" : "disabled"));
metrics.draftHandling.increment(input.drafts == null ? "N/A" : input.drafts.name());
input.drafts = firstNonNull(input.drafts, DraftHandling.KEEP);
logger.atFine().log("draft handling = %s", input.drafts);
if (input.onBehalfOf != null) {
revision = onBehalfOf(revision, labelTypes, input);
}
if (input.labels != null) {
checkLabels(revision, labelTypes, input.labels);
}
if (input.comments != null) {
input.comments = cleanUpComments(input.comments);
checkComments(revision, input.comments);
}
if (input.draftIdsToPublish != null) {
checkDraftIds(revision, input.draftIdsToPublish, input.drafts);
}
if (input.robotComments != null) {
input.robotComments = cleanUpComments(input.robotComments);
checkRobotComments(revision, input.robotComments);
}
if (input.notify == null) {
input.notify = defaultNotify(revision.getChange(), input);
}
logger.atFine().log("notify handling = %s", input.notify);
Map<String, ReviewerResult> reviewerJsonResults = null;
List<ReviewerModification> reviewerResults = Lists.newArrayList();
boolean hasError = false;
boolean confirm = false;
if (input.reviewers != null) {
reviewerJsonResults = Maps.newHashMap();
for (ReviewerInput reviewerInput : input.reviewers) {
ReviewerModification result = reviewerModifier.prepare(revision.getNotes(), revision.getUser(), reviewerInput, true);
reviewerJsonResults.put(reviewerInput.reviewer, result.result);
if (result.result.error != null) {
logger.atFine().log("Adding %s as reviewer failed: %s", reviewerInput.reviewer, result.result.error);
hasError = true;
continue;
}
if (result.result.confirm != null) {
logger.atFine().log("Adding %s as reviewer requires confirmation", reviewerInput.reviewer);
confirm = true;
continue;
}
logger.atFine().log("Adding %s as reviewer was prepared", reviewerInput.reviewer);
reviewerResults.add(result);
}
}
ReviewResult output = new ReviewResult();
output.reviewers = reviewerJsonResults;
if (hasError || confirm) {
output.error = ERROR_ADDING_REVIEWER;
return Response.withStatusCode(SC_BAD_REQUEST, output);
}
output.labels = input.labels;
try (BatchUpdate bu = updateFactory.create(revision.getChange().getProject(), revision.getUser(), ts)) {
Account account = revision.getUser().asIdentifiedUser().getAccount();
boolean ccOrReviewer = false;
if (input.labels != null && !input.labels.isEmpty()) {
ccOrReviewer = input.labels.values().stream().anyMatch(v -> v != 0);
if (ccOrReviewer) {
logger.atFine().log("calling user is cc/reviewer on the change due to voting on a label");
}
}
if (!ccOrReviewer) {
// Check if user was already CCed or reviewing prior to this review.
ReviewerSet currentReviewers = approvalsUtil.getReviewers(revision.getChangeResource().getNotes());
ccOrReviewer = currentReviewers.all().contains(account.id());
if (ccOrReviewer) {
logger.atFine().log("calling user is already cc/reviewer on the change");
}
}
// Apply reviewer changes first. Revision emails should be sent to the
// updated set of reviewers. Also keep track of whether the user added
// themselves as a reviewer or to the CC list.
logger.atFine().log("adding reviewer additions");
for (ReviewerModification reviewerResult : reviewerResults) {
// Send a single batch email below.
reviewerResult.op.suppressEmail();
// Send events below, if possible as batch.
reviewerResult.op.suppressEvent();
bu.addOp(revision.getChange().getId(), reviewerResult.op);
if (!ccOrReviewer && reviewerResult.reviewers.contains(account)) {
logger.atFine().log("calling user is explicitly added as reviewer or CC");
ccOrReviewer = true;
}
}
if (!ccOrReviewer) {
// User posting this review isn't currently in the reviewer or CC list,
// isn't being explicitly added, and isn't voting on any label.
// Automatically CC them on this change so they receive replies.
logger.atFine().log("CCing calling user");
ReviewerModification selfAddition = reviewerModifier.ccCurrentUser(revision.getUser(), revision);
selfAddition.op.suppressEmail();
selfAddition.op.suppressEvent();
bu.addOp(revision.getChange().getId(), selfAddition.op);
}
// Add WorkInProgressOp if requested.
if ((input.ready || input.workInProgress) && didWorkInProgressChange(revision.getChange().isWorkInProgress(), input)) {
if (input.ready && input.workInProgress) {
output.error = ERROR_WIP_READY_MUTUALLY_EXCLUSIVE;
return Response.withStatusCode(SC_BAD_REQUEST, output);
}
revision.getChangeResource().permissions().check(ChangePermission.TOGGLE_WORK_IN_PROGRESS_STATE);
if (input.ready) {
output.ready = true;
}
logger.atFine().log("setting work-in-progress to %s", input.workInProgress);
WorkInProgressOp wipOp = workInProgressOpFactory.create(input.workInProgress, new WorkInProgressOp.Input());
wipOp.suppressEmail();
bu.addOp(revision.getChange().getId(), wipOp);
}
// Add the review op.
logger.atFine().log("posting review");
bu.addOp(revision.getChange().getId(), new Op(projectState, revision.getPatchSet().id(), input));
// Notify based on ReviewInput, ignoring the notify settings from any ReviewerInputs.
NotifyResolver.Result notify = notifyResolver.resolve(input.notify, input.notifyDetails);
bu.setNotify(notify);
// Adjust the attention set based on the input
replyAttentionSetUpdates.updateAttentionSet(bu, revision.getNotes(), input, revision.getUser());
bu.execute();
// Re-read change to take into account results of the update.
ChangeData cd = changeDataFactory.create(revision.getProject(), revision.getChange().getId());
for (ReviewerModification reviewerResult : reviewerResults) {
reviewerResult.gatherResults(cd);
}
// Sending emails and events from ReviewersOps was suppressed so we can send a single batch
// email/event here.
batchEmailReviewers(revision.getUser(), revision.getChange(), reviewerResults, notify);
batchReviewerEvents(revision.getUser(), cd, revision.getPatchSet(), reviewerResults, ts);
}
return Response.ok(output);
}
use of com.google.gerrit.server.change.ReviewerModifier.ReviewerModification in project gerrit by GerritCodeReview.
the class PostReview method batchReviewerEvents.
private void batchReviewerEvents(CurrentUser user, ChangeData cd, PatchSet patchSet, List<ReviewerModification> reviewerModifications, Instant when) {
List<AccountState> newlyAddedReviewers = new ArrayList<>();
// There are no events for CCs and reviewers added/deleted by email.
for (ReviewerModification modification : reviewerModifications) {
Result reviewerAdditionResult = modification.op.getResult();
if (modification.state() == ReviewerState.REVIEWER) {
newlyAddedReviewers.addAll(reviewerAdditionResult.addedReviewers().stream().map(psa -> psa.accountId()).map(accountId -> accountCache.get(accountId)).flatMap(Streams::stream).collect(toList()));
} else if (modification.state() == ReviewerState.REMOVED) {
// There is no batch event for reviewer removals, hence fire the event for each
// modification that deleted a reviewer immediately.
modification.op.sendEvent();
}
}
// Fire a batch event for all newly added reviewers.
reviewerAdded.fire(cd, patchSet, newlyAddedReviewers, user.asIdentifiedUser().state(), when);
}
Aggregations