diff options
Diffstat (limited to 'gerrit-server/src')
11 files changed, 444 insertions, 9 deletions
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 4f99a17550..3fa53e53ce 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 @@ -31,6 +31,7 @@ import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.SitePaths; import com.google.gerrit.server.events.ApprovalAttribute; import com.google.gerrit.server.events.ChangeAbandonedEvent; +import com.google.gerrit.server.events.ChangeDeferredEvent; import com.google.gerrit.server.events.ChangeEvent; import com.google.gerrit.server.events.ChangeMergedEvent; import com.google.gerrit.server.events.ChangeRestoreEvent; @@ -98,6 +99,9 @@ public class ChangeHookRunner { /** Filename of the change abandoned hook. */ private final File changeAbandonedHook; + /** Filename of the change deferred hook. */ + private final File changeDeferredHook; + /** Filename of the change abandoned hook. */ private final File changeRestoredHook; @@ -148,6 +152,7 @@ public class ChangeHookRunner { commentAddedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "commentAddedHook", "comment-added")).getPath()); changeMergedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "changeMergedHook", "change-merged")).getPath()); changeAbandonedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "changeAbandonedHook", "change-abandoned")).getPath()); + changeDeferredHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "changeDeferredHook", "change-deferred")).getPath()); changeRestoredHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "changeRestoredHook", "change-restored")).getPath()); refUpdatedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "refUpdatedHook", "ref-updated")).getPath()); } @@ -328,6 +333,32 @@ public class ChangeHookRunner { } /** + * Fire the Change Deferred Hook. + * + * @param change The change itself. + * @param account The gerrit user who deferred the change. + * @param reason Reason for deferring the change. + */ + public void doChangeDeferredHook(final Change change, final Account account, final String reason) { + final ChangeDeferredEvent event = new ChangeDeferredEvent(); + + event.change = eventFactory.asChangeAttribute(change); + event.deferrer = eventFactory.asAccountAttribute(account); + event.reason = reason; + fireEvent(change, event); + + final List<String> args = new ArrayList<String>(); + addArg(args, "--change", event.change.id); + addArg(args, "--change-url", event.change.url); + addArg(args, "--project", event.change.project); + addArg(args, "--branch", event.change.branch); + addArg(args, "--deferrer", getDisplayName(account)); + addArg(args, "--reason", reason == null ? "" : reason); + + runHook(openRepository(change), changeDeferredHook, args); + } + + /** * Fire the Change Restored Hook. * * @param change The change itself. diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java index cd837aa752..53eb9ab5c0 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java @@ -41,6 +41,7 @@ import com.google.gerrit.server.git.MergeQueue; import com.google.gerrit.server.git.ReplicationQueue; import com.google.gerrit.server.git.StagingUtil; import com.google.gerrit.server.mail.AbandonedSender; +import com.google.gerrit.server.mail.DeferredSender; import com.google.gerrit.server.mail.EmailException; import com.google.gerrit.server.mail.RestoredSender; import com.google.gerrit.server.mail.RevertedSender; @@ -260,7 +261,8 @@ public class ChangeUtil { new AtomicUpdate<Change>() { @Override public Change update(Change change) { - if (change.getStatus().isOpen() + if ((change.getStatus().isOpen() + || change.getStatus() == Change.Status.DEFERRED) && change.currentPatchSetId().equals(patchSetId)) { change.setStatus(Change.Status.ABANDONED); ChangeUtil.updated(change); @@ -298,6 +300,79 @@ public class ChangeUtil { hooks.doChangeAbandonedHook(updatedChange, user.getAccount(), message); } + public static void defer(final PatchSet.Id patchSetId, + final IdentifiedUser user, final String message, final ReviewDb db, + final DeferredSender.Factory senderFactory, + final ChangeHookRunner hooks) throws NoSuchChangeException, + InvalidChangeOperationException, EmailException, OrmException { + defer(patchSetId, user, message, db, senderFactory, hooks, true); + } + + public static void defer(final PatchSet.Id patchSetId, + final IdentifiedUser user, final String message, final ReviewDb db, + final DeferredSender.Factory senderFactory, + final ChangeHookRunner hooks, final boolean sendMail) throws NoSuchChangeException, + InvalidChangeOperationException, EmailException, OrmException { + final Change.Id changeId = patchSetId.getParentKey(); + final PatchSet patch = db.patchSets().get(patchSetId); + if (patch == null) { + throw new NoSuchChangeException(changeId); + } + + final ChangeMessage cmsg = + new ChangeMessage(new ChangeMessage.Key(changeId, ChangeUtil + .messageUUID(db)), user.getAccountId()); + final StringBuilder msgBuf = + new StringBuilder("Patch Set " + patchSetId.get() + ": Deferred"); + if (message != null && message.length() > 0) { + msgBuf.append("\n\n"); + msgBuf.append(message); + } + cmsg.setMessage(msgBuf.toString()); + + final Change updatedChange = db.changes().atomicUpdate(changeId, + new AtomicUpdate<Change>() { + @Override + public Change update(Change change) { + if ((change.getStatus().isOpen() + || change.getStatus() == Change.Status.ABANDONED) + && change.currentPatchSetId().equals(patchSetId)) { + change.setStatus(Change.Status.DEFERRED); + ChangeUtil.updated(change); + return change; + } else { + return null; + } + } + }); + + if (updatedChange == null) { + throw new InvalidChangeOperationException( + "Change is no longer open or patchset is not latest"); + } + + db.changeMessages().insert(Collections.singleton(cmsg)); + + final List<PatchSetApproval> approvals = + db.patchSetApprovals().byChange(changeId).toList(); + for (PatchSetApproval a : approvals) { + a.cache(updatedChange); + } + db.patchSetApprovals().update(approvals); + + if (senderFactory != null) { + // Email the reviewers + final DeferredSender cm = senderFactory.create(updatedChange); + cm.setFrom(user.getAccountId()); + cm.setChangeMessage(cmsg); + cm.send(); + } else { + log.error("Cannot send email when deferring a change."); + } + + hooks.doChangeDeferredHook(updatedChange, user.getAccount(), message); + } + public static void revert(final PatchSet.Id patchSetId, final IdentifiedUser user, final String message, final ReviewDb db, final RevertedSender.Factory revertedSenderFactory, @@ -460,7 +535,8 @@ public class ChangeUtil { new AtomicUpdate<Change>() { @Override public Change update(Change change) { - if (change.getStatus() == Change.Status.ABANDONED + if ((change.getStatus() == Change.Status.ABANDONED || + change.getStatus() == Change.Status.DEFERRED) && change.currentPatchSetId().equals(patchSetId)) { change.setStatus(Change.Status.NEW); ChangeUtil.updated(change); @@ -473,7 +549,7 @@ public class ChangeUtil { if (updatedChange == null) { throw new InvalidChangeOperationException( - "Change is not abandoned or patchset is not latest"); + "Change is not abandoned/deferred or patchset is not latest"); } db.changeMessages().insert(Collections.singleton(cmsg)); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/TopicUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/TopicUtil.java index 4e1b6360d1..deb67d826b 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/TopicUtil.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/TopicUtil.java @@ -45,6 +45,7 @@ import com.google.gerrit.server.project.NoSuchRefException; import com.google.gerrit.server.project.NoSuchTopicException; import com.google.gerrit.server.project.TopicControl; import com.google.gerrit.server.mail.AbandonedSender; +import com.google.gerrit.server.mail.DeferredSender; import com.google.gerrit.server.mail.AddReviewerSender; import com.google.gerrit.server.mail.EmailException; import com.google.gerrit.server.mail.RestoredSender; @@ -287,6 +288,79 @@ public static ChangeSetApproval createStagingApproval( } } + public static void defer(final ChangeSet.Id changeSetId, + final IdentifiedUser user, final String message, final ReviewDb db, + final DeferredSender.Factory deferredSenderFactory, + final ChangeHookRunner hooks) throws NoSuchTopicException, + NoSuchChangeException, InvalidChangeOperationException, + EmailException, OrmException { + final Topic.Id topicId = changeSetId.getParentKey(); + final ChangeSet changeSet = db.changeSets().get(changeSetId); + if (changeSet == null) { + throw new NoSuchTopicException(topicId); + } + + final TopicMessage tmsg = + new TopicMessage(new TopicMessage.Key(topicId, ChangeUtil + .messageUUID(db)), user.getAccountId()); + final StringBuilder msgBuf = + new StringBuilder("Change Set " + changeSetId.get() + ": Deferred"); + if (message != null && message.length() > 0) { + msgBuf.append("\n\n"); + msgBuf.append(message); + } + tmsg.setMessage(msgBuf.toString()); + + final Topic updatedTopic = db.topics().atomicUpdate(topicId, + new AtomicUpdate<Topic>() { + @Override + public Topic update(Topic topic) { + if (topic.getStatus().isOpen() + && topic.currentChangeSetId().equals(changeSetId)) { + topic.setStatus(Change.Status.DEFERRED); + TopicUtil.updated(topic); + return topic; + } else { + return null; + } + } + }); + + Change lastChange = null; + if (updatedTopic == null) { + throw new InvalidChangeOperationException( + "Topic is no longer open or changeset is not latest"); + } else { + // Defer the changes belonging to the Topic + // + List<Change> toDefer = db.changes().byTopicOpenAll(topicId).toList(); + for (Change c : toDefer) { + ChangeUtil.defer(c.currentPatchSetId(), user, message, db, + deferredSenderFactory, hooks, false); + } + lastChange = toDefer.get(toDefer.size() - 1); + } + + db.topicMessages().insert(Collections.singleton(tmsg)); + + final List<ChangeSetApproval> approvals = + db.changeSetApprovals().byTopic(topicId).toList(); + for (ChangeSetApproval a : approvals) { + a.cache(updatedTopic); + } + db.changeSetApprovals().update(approvals); + + // Email the reviewers + // TODO Topic support + // Meanwhile, sending mails in "behalf" of the last change of the topic + if (lastChange != null) { + final DeferredSender cm = deferredSenderFactory.create(lastChange); + cm.setFrom(user.getAccountId()); + cm.setTopicMessage(tmsg); + cm.send(); + } + } + public static void revert(final ChangeSet.Id changeSetId, final IdentifiedUser user, final String message, final ReviewDb db, final RevertedSender.Factory revertedSenderFactory, @@ -560,10 +634,11 @@ public static ChangeSetApproval createStagingApproval( final AbstractEntity.Status tStatus = t.getStatus(); if (t.getTopic().equals(topicName)) { if (tStatus.equals(AbstractEntity.Status.ABANDONED) || + tStatus.equals(AbstractEntity.Status.DEFERRED) || tStatus.equals(AbstractEntity.Status.MERGED)) continue; // If we don't have a mess in our DB, we must have only // one topic with the same String in a different status than - // MERGED or ABANDONED + // MERGED, ABANDONED or DEFERRED // else return t; } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java index b3edf0dd4e..8c79a46a0d 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java @@ -30,6 +30,7 @@ import com.google.gerrit.server.git.ReceiveCommits; import com.google.gerrit.server.git.StagingMergeDelegate; import com.google.gerrit.server.git.SubmitMergeDelegate; import com.google.gerrit.server.mail.AbandonedSender; +import com.google.gerrit.server.mail.DeferredSender; import com.google.gerrit.server.mail.AddReviewerSender; import com.google.gerrit.server.mail.BuildApprovedSender; import com.google.gerrit.server.mail.BuildRejectedSender; @@ -81,6 +82,7 @@ public class GerritRequestModule extends FactoryModule { factory(PublishTopicComments.Factory.class); factory(ReplacePatchSetSender.Factory.class); factory(AbandonedSender.Factory.class); + factory(DeferredSender.Factory.class); factory(RevertedSender.Factory.class); factory(RestoredSender.Factory.class); factory(CommentSender.Factory.class); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeDeferredEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeDeferredEvent.java new file mode 100644 index 0000000000..8edec816d1 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeDeferredEvent.java @@ -0,0 +1,24 @@ +// Copyright (C) 2010 The Android Open Source Project, +// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +// +// 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; + +public class ChangeDeferredEvent extends ChangeEvent { + public final String type = "change-deferred"; + public ChangeAttribute change; + public PatchSetAttribute patchSet; + public AccountAttribute deferrer; + public String reason; +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java index 893cca64dc..b5fc953486 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java @@ -955,6 +955,11 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook { reject(cmd, "change " + change.getId() + " closed"); return false; } + if (change.getStatus().equals(AbstractEntity.Status.DEFERRED) + && topic == null) { + reject(cmd, "change " + change.getId() + " closed"); + return false; + } Topic.Id topicId = topic != null ? topic.getId() : null; ChangeSet.Id csId = topic != null ? topic.currentChangeSetId() : null; @@ -986,6 +991,10 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook { reject(cmd, "change " + change.getId() + " closed"); return false; } + if (change.getStatus().equals(AbstractEntity.Status.DEFERRED) && !topic) { + reject(cmd, "change " + change.getId() + " closed"); + return false; + } if (toReplace.containsKey(change.getId())) { reject(cmd, "duplicate request"); return false; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeferredSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeferredSender.java new file mode 100644 index 0000000000..04a4a4520b --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeferredSender.java @@ -0,0 +1,46 @@ +// Copyright (C) 2009 The Android Open Source Project, +// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.mail; + +import com.google.gerrit.reviewdb.Change; +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; + +/** Send notice about a change being deferred by its owner. */ +public class DeferredSender extends ReplyToChangeSender { + public static interface Factory { + DeferredSender create(Change change); + } + + @Inject + public DeferredSender(EmailArguments ea, @Assisted Change c) { + super(ea, c, "defer"); + } + + @Override + protected void init() throws EmailException { + super.init(); + + ccAllApprovals(); + bccStarredBy(); + bccWatchesNotifyAllComments(); + } + + @Override + protected void formatChange() throws EmailException { + appendText(velocifyFile("Deferred.vm")); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java index c9652fef87..ce9023df04 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java @@ -166,9 +166,25 @@ public class ChangeControl { && (change.getStatus() != Change.Status.INTEGRATING); } + /** Can this user defer this change? */ + public boolean canDefer() { + if (change.getTopicId() != null) return false; + boolean userCan = isOwner() // owner (aka creator) of the change can defer + || getRefControl().isOwner() // branch owner can defer + || getProjectControl().isOwner() // project owner can defer + || getCurrentUser().isAdministrator() // site administers are god + ; + + // Cannot defer changes that are already processed by the continuous + // integration system. + return userCan + && (change.getStatus() != Change.Status.INTEGRATING); + } + /** Can this user restore this change? */ public boolean canRestore() { - return canAbandon(); // Anyone who can abandon the change can restore it back + // Anyone who can abandon or defer the change can restore it back + return canAbandon() || canDefer(); } /** All value ranges of any allowed label permission. */ diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/TopicControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/TopicControl.java index d9cf3a964f..ba55b44af9 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/TopicControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/TopicControl.java @@ -152,9 +152,19 @@ public class TopicControl { ; } + /** Can this user defer this topic? */ + public boolean canDefer() { + return isOwner() // owner (aka creator) of the change can defer + || getRefControl().isOwner() // branch owner can defer + || getProjectControl().isOwner() // project owner can defer + || getCurrentUser().isAdministrator() // site administers are god + ; + } + /** Can this user restore this topic? */ public boolean canRestore() { - return canAbandon(); // Anyone who can abandon the change can restore it back + // Anyone who can abandon or defer the change can restore it back + return canAbandon() || canDefer(); } /** All value ranges of any allowed label permission. */ diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java index b31bf655de..3c6df14a0b 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java @@ -76,7 +76,8 @@ public class ChangeQueryRewriter extends QueryRewriter<ChangeData> { @Rewrite("-status:merged") public Predicate<ChangeData> r00_notMerged() { return or(ChangeStatusPredicate.open(dbProvider), - new ChangeStatusPredicate(dbProvider, Change.Status.ABANDONED)); + new ChangeStatusPredicate(dbProvider, Change.Status.ABANDONED), + new ChangeStatusPredicate(dbProvider, Change.Status.DEFERRED)); } @SuppressWarnings("unchecked") @@ -89,6 +90,14 @@ public class ChangeQueryRewriter extends QueryRewriter<ChangeData> { @SuppressWarnings("unchecked") @NoCostComputation + @Rewrite("-status:deferred") + public Predicate<ChangeData> r00_notDeferred() { + return or(ChangeStatusPredicate.open(dbProvider), + new ChangeStatusPredicate(dbProvider, Change.Status.MERGED)); + } + + @SuppressWarnings("unchecked") + @NoCostComputation @Rewrite("sortkey_before:z A=(age:*)") public Predicate<ChangeData> r00_ageToSortKey(@Named("A") AgePredicate a) { String cut = ChangeUtil.sortKey(a.getCut(), Integer.MAX_VALUE); @@ -248,6 +257,50 @@ public class ChangeQueryRewriter extends QueryRewriter<ChangeData> { }; } + @Rewrite("status:deferred P=(project:*) S=(sortkey_after:*) L=(limit:*)") + public Predicate<ChangeData> r10_byProjectDeferredPrev( + @Named("P") final ProjectPredicate p, + @Named("S") final SortKeyPredicate.After s, + @Named("L") final IntPredicate<ChangeData> l) { + return new PaginatedSource(40000, s.getValue(), l.intValue()) { + @Override + ResultSet<Change> scan(ChangeAccess a, String key, int limit) + throws OrmException { + return a.byProjectClosedPrev(Change.Status.DEFERRED.getCode(), // + p.getValueKey(), key, limit); + } + + @Override + public boolean match(ChangeData cd) throws OrmException { + return cd.change(dbProvider).getStatus() == Change.Status.DEFERRED + && p.match(cd) // + && s.match(cd); + } + }; + } + + @Rewrite("status:deferred P=(project:*) S=(sortkey_before:*) L=(limit:*)") + public Predicate<ChangeData> r10_byProjectDeferredNext( + @Named("P") final ProjectPredicate p, + @Named("S") final SortKeyPredicate.Before s, + @Named("L") final IntPredicate<ChangeData> l) { + return new PaginatedSource(40000, s.getValue(), l.intValue()) { + @Override + ResultSet<Change> scan(ChangeAccess a, String key, int limit) + throws OrmException { + return a.byProjectClosedNext(Change.Status.DEFERRED.getCode(), // + p.getValueKey(), key, limit); + } + + @Override + public boolean match(ChangeData cd) throws OrmException { + return cd.change(dbProvider).getStatus() == Change.Status.DEFERRED + && p.match(cd) // + && s.match(cd); + } + }; + } + @Rewrite("status:open S=(sortkey_after:*) L=(limit:*)") public Predicate<ChangeData> r20_byOpenPrev( @Named("S") final SortKeyPredicate.After s, @@ -381,11 +434,59 @@ public class ChangeQueryRewriter extends QueryRewriter<ChangeData> { } @SuppressWarnings("unchecked") + @Rewrite("status:deferred S=(sortkey_after:*) L=(limit:*)") + public Predicate<ChangeData> r20_byDeferredPrev( + @Named("S") final SortKeyPredicate.After s, + @Named("L") final IntPredicate<ChangeData> l) { + return new PaginatedSource(50000, s.getValue(), l.intValue()) { + { + init("r20_byDeferredPrev", s, l); + } + + @Override + ResultSet<Change> scan(ChangeAccess a, String key, int limit) + throws OrmException { + return a.allClosedPrev(Change.Status.DEFERRED.getCode(), key, limit); + } + + @Override + public boolean match(ChangeData cd) throws OrmException { + return cd.change(dbProvider).getStatus() == Change.Status.DEFERRED + && s.match(cd); + } + }; + } + + @SuppressWarnings("unchecked") + @Rewrite("status:deferred S=(sortkey_before:*) L=(limit:*)") + public Predicate<ChangeData> r20_byDeferredNext( + @Named("S") final SortKeyPredicate.Before s, + @Named("L") final IntPredicate<ChangeData> l) { + return new PaginatedSource(50000, s.getValue(), l.intValue()) { + { + init("r20_byDeferredNext", s, l); + } + + @Override + ResultSet<Change> scan(ChangeAccess a, String key, int limit) + throws OrmException { + return a.allClosedNext(Change.Status.DEFERRED.getCode(), key, limit); + } + + @Override + public boolean match(ChangeData cd) throws OrmException { + return cd.change(dbProvider).getStatus() == Change.Status.DEFERRED + && s.match(cd); + } + }; + } + + @SuppressWarnings("unchecked") @Rewrite("status:closed S=(sortkey_after:*) L=(limit:*)") public Predicate<ChangeData> r20_byClosedPrev( @Named("S") final SortKeyPredicate.After s, @Named("L") final IntPredicate<ChangeData> l) { - return or(r20_byMergedPrev(s, l), r20_byAbandonedPrev(s, l)); + return or(r20_byMergedPrev(s, l), r20_byAbandonedPrev(s, l), r20_byDeferredPrev(s, l)); } @SuppressWarnings("unchecked") @@ -393,7 +494,7 @@ public class ChangeQueryRewriter extends QueryRewriter<ChangeData> { public Predicate<ChangeData> r20_byClosedNext( @Named("S") final SortKeyPredicate.Before s, @Named("L") final IntPredicate<ChangeData> l) { - return or(r20_byMergedNext(s, l), r20_byAbandonedNext(s, l)); + return or(r20_byMergedNext(s, l), r20_byAbandonedNext(s, l), r20_byDeferredNext(s, l)); } @SuppressWarnings("unchecked") diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Deferred.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Deferred.vm new file mode 100644 index 0000000000..25447313f3 --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Deferred.vm @@ -0,0 +1,45 @@ +## Copyright (C) 2010 The Android Open Source Project, +## Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +## +## 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 Deferred.vm template will determine the contents of the email related +## to a change being deferred. It is a ChangeEmail: see ChangeSubject.vm and +## ChangeFooter.vm. +## +$fromName has deferred this change. + +Change subject: $change.subject +...................................................................... + + +#if ($coverLetter) +$coverLetter + +#end |