diff --git a/Documentation/cmd-stream-events.txt b/Documentation/cmd-stream-events.txt index ee645c176b..c45bbdbbfb 100644 --- a/Documentation/cmd-stream-events.txt +++ b/Documentation/cmd-stream-events.txt @@ -247,6 +247,27 @@ reviewer:: link:json.html#account[account attribute] eventCreatedOn:: Time in seconds since the UNIX epoch when this event was created. +=== Reviewer Deleted + +Sent when a reviewer (with a vote) is removed from a change. + +type:: "reviewer-deleted" + +change:: link:json.html#change[change attribute] + +patchSet:: link:json.html#patchSet[patchSet attribute] + +reviewer:: link:json.html#account[account attribute] + +author:: link:json.html#account[account attribute] + +approvals:: All link:json.html#approval[approval attributes] removed. + +comment:: Review comment cover message. + +eventCreatedOn:: Time in seconds since the UNIX epoch when this event was +created. + === Topic Changed Sent when the topic of a change has been changed. diff --git a/Documentation/config-hooks.txt b/Documentation/config-hooks.txt index 5b1f5e3fe5..980c612224 100644 --- a/Documentation/config-hooks.txt +++ b/Documentation/config-hooks.txt @@ -126,6 +126,14 @@ Called whenever a reviewer is added to a change. reviewer-added --change --change-url --change-owner --project --branch --reviewer ==== +=== reviewer-deleted + +Called whenever a reviewer (with a vote) is removed from a change. + +==== + reviewer-deleted --change --change-url --change-owner --project --branch --reviewer [-- -- ...] +==== + === topic-changed Called whenever a change's topic is changed from the Web UI or via the REST API. diff --git a/Documentation/config-mail.txt b/Documentation/config-mail.txt index a97cdf257b..7aead9afb3 100644 --- a/Documentation/config-mail.txt +++ b/Documentation/config-mail.txt @@ -62,6 +62,12 @@ The `DeleteVote.vm` template will determine the contents of the email related to removing votes on changes. It is a `ChangeEmail`: see `ChangeSubject.vm` and `ChangeFooter.vm`. +=== DeleteReviewer.vm + +The `DeleteReiewer.vm` template will determine the contents of the email related +to a user removing a reviewer (with a vote) from a change. It is a +`ChangeEmail`: see `ChangeSubject.vm` and `ChangeFooter.vm`. + === Footer.vm The `Footer.vm` template will determine the contents of the footer text diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java index 9060bf0e47..7f86a8a0a6 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java @@ -105,6 +105,7 @@ public class SitePathInitializer { extractMailExample("ChangeSubject.vm"); extractMailExample("Comment.vm"); extractMailExample("CommentFooter.vm"); + extractMailExample("DeleteReviewer.vm"); extractMailExample("DeleteVote.vm"); extractMailExample("Footer.vm"); extractMailExample("Merged.vm"); diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java index b8d609de10..6d84599509 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java +++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java @@ -54,6 +54,7 @@ import com.google.gerrit.server.events.PatchSetCreatedEvent; import com.google.gerrit.server.events.ProjectCreatedEvent; import com.google.gerrit.server.events.RefUpdatedEvent; import com.google.gerrit.server.events.ReviewerAddedEvent; +import com.google.gerrit.server.events.ReviewerDeletedEvent; import com.google.gerrit.server.events.TopicChangedEvent; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.WorkQueue; @@ -183,6 +184,9 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener, /** Path of the reviewer added hook. */ private final Optional reviewerAddedHook; + /** Path of the reviewer deleted hook. */ + private final Optional reviewerDeletedHook; + /** Path of the topic changed hook. */ private final Optional topicChangedHook; @@ -269,6 +273,7 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener, changeRestoredHook = hook(config, hooksPath, "change-restored"); refUpdatedHook = hook(config, hooksPath, "ref-updated"); reviewerAddedHook = hook(config, hooksPath, "reviewer-added"); + reviewerDeletedHook = hook(config, hooksPath, "reviewer-deleted"); topicChangedHook = hook(config, hooksPath, "topic-changed"); claSignedHook = hook(config, hooksPath, "cla-signed"); refUpdateHook = hook(config, hooksPath, "ref-update"); @@ -699,6 +704,70 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener, runHook(change.getProject(), reviewerAddedHook, args); } + @Override + public void doReviewerDeletedHook(final Change change, Account account, + PatchSet patchSet, String comment, final Map approvals, + final Map oldApprovals, ReviewDb db) throws OrmException { + + ReviewerDeletedEvent event = new ReviewerDeletedEvent(change); + event.change = changeAttributeSupplier(change); + event.patchSet = patchSetAttributeSupplier(change, patchSet); + event.reviewer = accountAttributeSupplier(account); + event.comment = comment; + event.approvals = Suppliers.memoize( + new Supplier() { + @Override + public ApprovalAttribute[] get() { + LabelTypes labelTypes = projectCache.get( + change.getProject()).getLabelTypes(); + if (!approvals.isEmpty()) { + ApprovalAttribute[] r = new ApprovalAttribute[approvals.size()]; + int i = 0; + for (Map.Entry approval : approvals.entrySet()) { + r[i++] = getApprovalAttribute(labelTypes, approval, + oldApprovals); + } + return r; + } + return null; + } + }); + + dispatcher.get().postEvent(change, event, db); + + if (!reviewerDeletedHook.isPresent()) { + return; + } + + List args = new ArrayList<>(); + ChangeAttribute c = event.change.get(); + AccountState owner = accountCache.get(change.getOwner()); + + addArg(args, "--change", c.id); + addArg(args, "--change-url", c.url); + addArg(args, "--change-owner", getDisplayName(owner.getAccount())); + addArg(args, "--project", c.project); + addArg(args, "--branch", c.branch); + addArg(args, "--reviewer", getDisplayName(account)); + LabelTypes labelTypes = projectCache.get( + change.getProject()).getLabelTypes(); + // append votes that were removed + for (Map.Entry approval : approvals.entrySet()) { + LabelType lt = labelTypes.byLabel(approval.getKey()); + if (lt != null && approval.getValue() != null) { + addArg(args, "--" + lt.getName(), Short.toString(approval.getValue())); + if (oldApprovals != null && !oldApprovals.isEmpty()) { + Short oldValue = oldApprovals.get(approval.getKey()); + if (oldValue != null) { + addArg(args, "--" + lt.getName() + "-oldValue", + Short.toString(oldValue)); + } + } + } + } + runHook(change.getProject(), reviewerDeletedHook, args); + } + @Override public void doTopicChangedHook(Change change, Account account, String oldTopic, ReviewDb db) diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java index ee1de65fc5..9b02d18fce 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java +++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java @@ -155,7 +155,23 @@ public interface ChangeHooks { PatchSet patchSet, ReviewDb db) throws OrmException; /** - * Fire the Topic Changed Hook. + * Fire the Reviewer Deleted Hook + * + * @param change The change itself. + * @param account The reviewer that was removed. + * @param patchSet The patchset that the reviewer was removed from. + * @param comment The comment given. + * @param approvals Map of label IDs to scores. + * @param oldApprovals Map of label IDs to old approval scores + * @param db The review database. + * @throws OrmException + */ + void doReviewerDeletedHook(Change change, Account account, PatchSet patchSet, + String comment, Map approvals, + Map oldApprovals, ReviewDb db) throws OrmException; + + /** + * Fire the Topic Changed Hook * * @param change The change itself. * @param account The gerrit user who changed the topic. diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java b/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java index bec0b7e514..dbfa9799ca 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java +++ b/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java @@ -90,6 +90,12 @@ public final class DisabledChangeHooks implements ChangeHooks, EventDispatcher { ReviewDb db) { } + @Override + public void doReviewerDeletedHook(Change change, Account account, + PatchSet patchSet, String comment, Map approvals, + Map oldApprovals, ReviewDb db) { + } + @Override public void doTopicChangedHook(Change change, Account account, String oldTopic, ReviewDb db) { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java index dc3b26b523..ed0a6b844d 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java @@ -17,55 +17,84 @@ package com.google.gerrit.server.change; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import com.google.gerrit.common.ChangeHooks; import com.google.gerrit.common.TimeUtil; +import com.google.gerrit.common.data.LabelType; +import com.google.gerrit.common.data.LabelTypes; import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.extensions.restapi.Response; import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.extensions.restapi.RestModifyView; import com.google.gerrit.reviewdb.client.Account; +import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.ChangeMessage; import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.PatchSetApproval; +import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.ApprovalsUtil; import com.google.gerrit.server.ChangeMessagesUtil; import com.google.gerrit.server.ChangeUtil; import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.PatchSetUtil; import com.google.gerrit.server.change.DeleteReviewer.Input; import com.google.gerrit.server.git.BatchUpdate; import com.google.gerrit.server.git.BatchUpdate.ChangeContext; +import com.google.gerrit.server.git.BatchUpdate.Context; import com.google.gerrit.server.git.UpdateException; +import com.google.gerrit.server.mail.DeleteReviewerSender; import com.google.gerrit.server.notedb.ChangeUpdate; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Singleton public class DeleteReviewer implements RestModifyView { + private static final Logger log = LoggerFactory + .getLogger(DeleteReviewer.class); + public static class Input { } private final Provider dbProvider; private final ApprovalsUtil approvalsUtil; + private final PatchSetUtil psUtil; private final ChangeMessagesUtil cmUtil; private final BatchUpdate.Factory batchUpdateFactory; private final IdentifiedUser.GenericFactory userFactory; + private final ChangeHooks hooks; + private final Provider user; + private final DeleteReviewerSender.Factory deleteReviewerSenderFactory; @Inject DeleteReviewer(Provider dbProvider, ApprovalsUtil approvalsUtil, + PatchSetUtil psUtil, ChangeMessagesUtil cmUtil, BatchUpdate.Factory batchUpdateFactory, - IdentifiedUser.GenericFactory userFactory) { + IdentifiedUser.GenericFactory userFactory, + ChangeHooks hooks, + Provider user, + DeleteReviewerSender.Factory deleteReviewerSenderFactory) { this.dbProvider = dbProvider; this.approvalsUtil = approvalsUtil; + this.psUtil = psUtil; this.cmUtil = cmUtil; this.batchUpdateFactory = batchUpdateFactory; this.userFactory = userFactory; + this.hooks = hooks; + this.user = user; + this.deleteReviewerSenderFactory = deleteReviewerSenderFactory; } @Override @@ -74,7 +103,7 @@ public class DeleteReviewer implements RestModifyView { try (BatchUpdate bu = batchUpdateFactory.create(dbProvider.get(), rsrc.getChangeResource().getProject(), rsrc.getChangeResource().getUser(), TimeUtil.nowTs())) { - Op op = new Op(rsrc.getReviewerUser().getAccountId()); + Op op = new Op(rsrc.getReviewerUser().getAccount()); bu.addOp(rsrc.getChange().getId(), op); bu.execute(); } @@ -83,29 +112,44 @@ public class DeleteReviewer implements RestModifyView { } private class Op extends BatchUpdate.Op { - private final Account.Id reviewerId; + private final Account reviewer; + ChangeMessage changeMessage; + Change currChange; + PatchSet currPs; + List del = new ArrayList<>(); + Map newApprovals = new HashMap<>(); + Map oldApprovals = new HashMap<>(); - Op(Account.Id reviewerId) { - this.reviewerId = reviewerId; + Op(Account reviewerAccount) { + this.reviewer = reviewerAccount; } @Override public boolean updateChange(ChangeContext ctx) throws AuthException, ResourceNotFoundException, OrmException { - PatchSet.Id currPs = ctx.getChange().currentPatchSetId(); + Account.Id reviewerId = reviewer.getId(); + currChange = ctx.getChange(); + currPs = psUtil.current(dbProvider.get(), ctx.getNotes()); + + LabelTypes labelTypes = ctx.getControl().getLabelTypes(); + // removing a reviewer will remove all her votes + for (LabelType lt : labelTypes.getLabelTypes()) { + newApprovals.put(lt.getName(), (short) 0); + } + StringBuilder msg = new StringBuilder(); - List del = Lists.newArrayList(); for (PatchSetApproval a : approvals(ctx, reviewerId)) { if (ctx.getControl().canRemoveReviewer(a)) { del.add(a); - if (a.getPatchSetId().equals(currPs) - && a.getValue() != 0) { + if (a.getPatchSetId().equals(currPs.getId()) && a.getValue() != 0) { + oldApprovals.put(a.getLabel(), a.getValue()); if (msg.length() == 0) { - msg.append("Removed the following votes:\n\n"); + msg.append("Removed reviewer ").append(reviewer.getFullName()) + .append(" with the following votes:\n\n"); } - msg.append("* ") - .append(a.getLabel()).append(formatLabelValue(a.getValue())) - .append(" by ").append(userFactory.create(a.getAccountId()).getNameEmail()) + msg.append("* ").append(a.getLabel()) + .append(formatLabelValue(a.getValue())).append(" by ") + .append(userFactory.create(a.getAccountId()).getNameEmail()) .append("\n"); } } else { @@ -116,21 +160,37 @@ public class DeleteReviewer implements RestModifyView { throw new ResourceNotFoundException(); } ctx.getDb().patchSetApprovals().delete(del); - ChangeUpdate update = ctx.getUpdate(currPs); + ChangeUpdate update = ctx.getUpdate(currPs.getId()); update.removeReviewer(reviewerId); if (msg.length() > 0) { - ChangeMessage changeMessage = - new ChangeMessage(new ChangeMessage.Key(ctx.getChange().getId(), + changeMessage = new ChangeMessage( + new ChangeMessage.Key(currChange.getId(), ChangeUtil.messageUUID(ctx.getDb())), - ctx.getUser().getAccountId(), - ctx.getWhen(), currPs); + ctx.getUser().getAccountId(), ctx.getWhen(), currPs.getId()); changeMessage.setMessage(msg.toString()); cmUtil.addChangeMessage(ctx.getDb(), update, changeMessage); } + return true; } + @Override + public void postUpdate(Context ctx) { + if (changeMessage == null) { + return; + } + + emailReviewers(ctx.getProject(), currChange, del, changeMessage); + try { + hooks.doReviewerDeletedHook(currChange, reviewer, currPs, + changeMessage.getMessage(), newApprovals, oldApprovals, + dbProvider.get()); + } catch (OrmException e) { + log.warn("ChangeHook.doCommentAddedHook delivery failed", e); + } + } + private Iterable approvals(ChangeContext ctx, final Account.Id accountId) throws OrmException { return Iterables.filter( @@ -151,4 +211,29 @@ public class DeleteReviewer implements RestModifyView { } } } + + private void emailReviewers(Project.NameKey projectName, Change change, + List dels, ChangeMessage changeMessage) { + + // The user knows they removed themselves, don't bother emailing them. + List toMail = Lists.newArrayListWithCapacity(dels.size()); + Account.Id userId = user.get().getAccountId(); + for (PatchSetApproval psa : dels) { + if (!psa.getAccountId().equals(userId)) { + toMail.add(psa.getAccountId()); + } + } + if (!toMail.isEmpty()) { + try { + DeleteReviewerSender cm = + deleteReviewerSenderFactory.create(projectName, change.getId()); + cm.setFrom(userId); + cm.addReviewers(toMail); + cm.setChangeMessage(changeMessage); + cm.send(); + } catch (Exception err) { + log.error("Cannot email update for change " + change.getId(), err); + } + } + } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java index 1d2fc09685..b5b3b6adaa 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java @@ -109,6 +109,7 @@ import com.google.gerrit.server.index.change.ReindexAfterUpdate; import com.google.gerrit.server.mail.AddKeySender; import com.google.gerrit.server.mail.AddReviewerSender; import com.google.gerrit.server.mail.CreateChangeSender; +import com.google.gerrit.server.mail.DeleteReviewerSender; import com.google.gerrit.server.mail.EmailModule; import com.google.gerrit.server.mail.FromAddressGenerator; import com.google.gerrit.server.mail.FromAddressGeneratorProvider; @@ -205,6 +206,7 @@ public class GerritGlobalModule extends FactoryModule { factory(AccountInfoCacheFactory.Factory.class); factory(AddReviewerSender.Factory.class); + factory(DeleteReviewerSender.Factory.class); factory(AddKeySender.Factory.class); factory(BatchUpdate.Factory.class); factory(CapabilityControl.Factory.class); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/ReviewerDeletedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/ReviewerDeletedEvent.java new file mode 100644 index 0000000000..1b57906d01 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/ReviewerDeletedEvent.java @@ -0,0 +1,31 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.events; + +import com.google.common.base.Supplier; +import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.server.data.AccountAttribute; +import com.google.gerrit.server.data.ApprovalAttribute; + +public class ReviewerDeletedEvent extends PatchSetEvent { + static final String TYPE = "reviewer-deleted"; + public Supplier reviewer; + public Supplier approvals; + public String comment; + + public ReviewerDeletedEvent(Change change) { + super(TYPE, change); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeleteReviewerSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeleteReviewerSender.java new file mode 100644 index 0000000000..0225f71feb --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeleteReviewerSender.java @@ -0,0 +1,81 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.mail; + +import com.google.gerrit.common.errors.EmailException; +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.AccountProjectWatch.NotifyType; +import com.google.gwtorm.server.OrmException; +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** Let users know that a reviewer and possibly her review have + * been removed. */ +public class DeleteReviewerSender extends ReplyToChangeSender { + private final Set reviewers = new HashSet<>(); + + public static interface Factory extends + ReplyToChangeSender.Factory { + @Override + DeleteReviewerSender create(Project.NameKey project, Change.Id change); + } + + @Inject + public DeleteReviewerSender(EmailArguments ea, + @Assisted Project.NameKey project, + @Assisted Change.Id id) + throws OrmException { + super(ea, "deleteReviewer", newChangeData(ea, project, id)); + } + + public void addReviewers(Collection cc) { + reviewers.addAll(cc); + } + + @Override + protected void init() throws EmailException { + super.init(); + + ccAllApprovals(); + bccStarredBy(); + ccExistingReviewers(); + includeWatchers(NotifyType.ALL_COMMENTS); + add(RecipientType.TO, reviewers); + } + + @Override + protected void formatChange() throws EmailException { + appendText(velocifyFile("DeleteReviewer.vm")); + } + + public List getReviewerNames() { + if (reviewers.isEmpty()) { + return null; + } + List names = new ArrayList<>(); + for (Account.Id id : reviewers) { + names.add(getNameFor(id)); + } + return names; + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailModule.java index a9240cbde7..7ceb0ae08a 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailModule.java @@ -21,6 +21,7 @@ public class EmailModule extends FactoryModule { protected void configure() { factory(AbandonedSender.Factory.class); factory(CommentSender.Factory.class); + factory(DeleteReviewerSender.Factory.class); factory(DeleteVoteSender.Factory.class); factory(RestoredSender.Factory.class); factory(RevertedSender.Factory.class); diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewer.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewer.vm new file mode 100644 index 0000000000..619ba2a9c8 --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewer.vm @@ -0,0 +1,44 @@ +## Copyright (C) 2016 The Android Open Source Project +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## +## +## Template Type: +## ------------- +## This is a velocity mail template, see: http://velocity.apache.org and the +## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates. +## +## Template File Names and extensions: +## ---------------------------------- +## Gerrit will use templates ending in ".vm" but will ignore templates ending +## in ".vm.example". If a .vm template does not exist, the default internal +## gerrit template which is the same as the .vm.example will be used. If you +## want to override the default template, copy the .vm.example file to a .vm +## file and edit it appropriately. +## +## This Template: +## -------------- +## The DeleteReviewer.vm template will determine the contents of the email +## related to removal of a reviewer (and the reviewer's votes) from reviews. +## It is a ChangeEmail: see ChangeSubject.vm and ChangeFooter.vm. +## +$fromName has removed $email.joinStrings($email.reviewerNames, ', ') from this change. + +Change subject: $change.subject +...................................................................... + + +#if ($email.coverLetter) +$email.coverLetter + +#end