From 262629e6d3ed6ec296fc4c2fc0b4ce4e93c8cfb8 Mon Sep 17 00:00:00 2001 From: Gustaf Lundh Date: Wed, 18 May 2011 19:58:59 -0700 Subject: [PATCH] Adds a "revert change"-button to a submitted patchset. Change-Id: I409e656c88a7021f604c021ca3987d2e917c18d9 --- .../gerrit/common/data/ChangeDetail.java | 9 ++ .../common/data/ChangeManageService.java | 4 + .../com/google/gerrit/client/GerritCss.java | 2 + .../client/changes/ChangeConstants.java | 6 + .../client/changes/ChangeConstants.properties | 6 + .../gerrit/client/changes/ChangeMessages.java | 2 + .../client/changes/ChangeMessages.properties | 2 + .../changes/CommentedChangeActionDialog.java | 9 ++ .../PatchSetComplexDisclosurePanel.java | 20 +++ .../java/com/google/gerrit/client/gerrit.css | 25 ++++ .../rpc/changedetail/ChangeDetailFactory.java | 4 + .../changedetail/ChangeManageServiceImpl.java | 10 +- .../httpd/rpc/changedetail/ChangeModule.java | 1 + .../httpd/rpc/changedetail/RevertChange.java | 114 ++++++++++++++++ .../com/google/gerrit/server/ChangeUtil.java | 123 +++++++++++++++++- .../server/config/GerritRequestModule.java | 2 + .../gerrit/server/mail/RevertedSender.java | 45 +++++++ .../com/google/gerrit/server/mail/Reverted.vm | 44 +++++++ 18 files changed, 425 insertions(+), 3 deletions(-) create mode 100644 gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RevertChange.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/mail/RevertedSender.java create mode 100644 gerrit-server/src/main/resources/com/google/gerrit/server/mail/Reverted.vm diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java index cd992fdfe8..572fd7aad7 100644 --- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java +++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java @@ -31,6 +31,7 @@ public class ChangeDetail { protected boolean allowsAnonymous; protected boolean canAbandon; protected boolean canRestore; + protected boolean canRevert; protected Change change; protected boolean starred; protected List dependsOn; @@ -78,6 +79,14 @@ public class ChangeDetail { canRestore = a; } + public boolean canRevert() { + return canRevert; + } + + public void setCanRevert(boolean a) { + canRevert = a; + } + public Change getChange() { return change; } diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeManageService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeManageService.java index b61c35c1e4..cb38c3b802 100644 --- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeManageService.java +++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeManageService.java @@ -30,6 +30,10 @@ public interface ChangeManageService extends RemoteJsonService { void abandonChange(PatchSet.Id patchSetId, String message, AsyncCallback callback); + @SignInRequired + void revertChange(PatchSet.Id patchSetId, String message, + AsyncCallback callback); + @SignInRequired void restoreChange(PatchSet.Id patchSetId, String message, AsyncCallback callback); diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java index e3dbb01526..e988540d9f 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java @@ -20,6 +20,8 @@ public interface GerritCss extends CssResource { String greenCheckClass(); String abandonChangeDialog(); String abandonMessage(); + String revertChangeDialog(); + String revertMessage(); String accountContactOnFile(); String accountContactPrivacyDetails(); String accountDashboard(); diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java index abdaa15242..f87b5c28fe 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java @@ -100,6 +100,12 @@ public interface ChangeConstants extends Constants { String patchSetInfoCommitter(); String patchSetInfoDownload(); + String buttonRevertChangeBegin(); + String buttonRevertChangeSend(); + String buttonRevertChangeCancel(); + String headingRevertMessage(); + String revertChangeTitle(); + String buttonAbandonChangeBegin(); String buttonAbandonChangeSend(); String buttonAbandonChangeCancel(); diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties index 7cd0208f6b..5dc259c253 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties @@ -83,6 +83,12 @@ buttonAbandonChangeCancel = Cancel headingAbandonMessage = Abandon Message: abandonChangeTitle = Code Review - Abandon Change +buttonRevertChangeBegin = Revert Change +buttonRevertChangeSend = Revert Change +buttonRevertChangeCancel = Cancel +headingRevertMessage = Revert Commit Message: +revertChangeTitle = Code Review - Revert Merged Change + buttonRestoreChangeBegin = Restore Change restoreChangeTitle = Code Review - Restore Change buttonRestoreChangeCancel = Cancel diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java index 312cc106c0..cbf4a6bd23 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java @@ -24,6 +24,8 @@ public interface ChangeMessages extends Messages { String changesMergedInProject(String string); String changesAbandonedInProject(String string); + String revertChangeDefaultMessage(String commitMsg, String commitId); + String changeScreenTitleId(String changeId); String patchSetHeader(int id); String loadingPatchSet(int id); diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties index 625a9ecb8b..5982ad5d06 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties @@ -5,6 +5,8 @@ changesOpenInProject = Open Changes In {0} changesMergedInProject = Merged Changes In {0} changesAbandonedInProject = Abandoned Changes In {0} +revertChangeDefaultMessage = Revert \"{0}\"\n\nThis reverts commit {1} + changeScreenTitleId = Change {0} patchSetHeader = Patch Set {0} loadingPatchSet = Loading Patch Set {0} ... diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentedChangeActionDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentedChangeActionDialog.java index 5540b715bb..d725ac0800 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentedChangeActionDialog.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentedChangeActionDialog.java @@ -47,6 +47,14 @@ public abstract class CommentedChangeActionDialog extends AutoCenterDialogBox im final String dialogHeading, final String buttonSend, final String buttonCancel, final String dialogStyle, final String messageStyle) { + this(psi, callback, dialogTitle, dialogHeading, buttonSend, buttonCancel, dialogStyle, messageStyle, null); + } + + public CommentedChangeActionDialog(final PatchSet.Id psi, + final AsyncCallback callback, final String dialogTitle, + final String dialogHeading, final String buttonSend, + final String buttonCancel, final String dialogStyle, + final String messageStyle, final String defaultMessage) { super(/* auto hide */false, /* modal */true); setGlassEnabled(true); @@ -67,6 +75,7 @@ public abstract class CommentedChangeActionDialog extends AutoCenterDialogBox im message = new NpTextArea(); message.setCharacterWidth(60); message.setVisibleLines(10); + message.setText(defaultMessage); DOM.setElementPropertyBoolean(message.getElement(), "spellcheck", true); mwrap.add(message); diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java index e41d572a7f..a65c05d5d4 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java @@ -395,6 +395,26 @@ class PatchSetComplexDisclosurePanel extends ComplexDisclosurePanel implements O actionsPanel.add(b); } + if (changeDetail.canRevert()) { + final Button b = new Button(Util.C.buttonRevertChangeBegin()); + b.addClickHandler(new ClickHandler() { + @Override + public void onClick(final ClickEvent event) { + b.setEnabled(false); + new CommentedChangeActionDialog(patchSet.getId(), createCommentedCallback(b), + Util.C.revertChangeTitle(), Util.C.headingRevertMessage(), + Util.C.buttonRevertChangeSend(), Util.C.buttonRevertChangeCancel(), + Gerrit.RESOURCES.css().revertChangeDialog(), Gerrit.RESOURCES.css().revertMessage(), + Util.M.revertChangeDefaultMessage(detail.getInfo().getSubject(), detail.getPatchSet().getRevision().get())) { + public void onSend() { + Util.MANAGE_SVC.revertChange(getPatchSetId() , getMessageText(), createCallback()); + } + }.center(); + } + }); + actionsPanel.add(b); + } + if (changeDetail.canAbandon()) { final Button b = new Button(Util.C.buttonAbandonChangeBegin()); b.addClickHandler(new ClickHandler() { diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css index 319b90c064..84354f7bf0 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css @@ -1230,6 +1230,31 @@ a:hover.downloadLink { font-size: small; } +/** RevertChangeDialog **/ + +.revertChangeDialog .gwt-DisclosurePanel .header td { + font-weight: bold; + white-space: nowrap; +} + +.revertChangeDialog .smallHeading { + font-size: small; + font-weight: bold; + white-space: nowrap; +} +.revertChangeDialog .revertMessage { + margin-left: 10px; + background: trimColor; + padding: 5px 5px 5px 5px; +} +.revertChangeDialog .revertMessage textarea { + font-size: small; +} +.revertChangeDialog .gwt-Hyperlink { + white-space: nowrap; + font-size: small; +} + /** PatchBrowserPopup **/ .patchBrowserPopup { diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java index 14cb1e15ee..00d83e2d07 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java @@ -100,10 +100,14 @@ public class ChangeDetailFactory extends Handler { detail = new ChangeDetail(); detail.setChange(change); detail.setAllowsAnonymous(control.forAnonymousUser().isVisible()); + detail.setCanAbandon(change.getStatus().isOpen() && control.canAbandon()); detail.setCanRestore(change.getStatus() == Change.Status.ABANDONED && control.canRestore()); detail.setStarred(control.getCurrentUser().getStarredChanges().contains( changeId)); + + detail.setCanRevert(change.getStatus() == Change.Status.MERGED && control.canAddPatchSet()); + loadPatchSets(); loadMessages(); if (change.currentPatchSetId() != null) { diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeManageServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeManageServiceImpl.java index 0eccc2c3fd..9e04756edf 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeManageServiceImpl.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeManageServiceImpl.java @@ -24,14 +24,17 @@ class ChangeManageServiceImpl implements ChangeManageService { private final SubmitAction.Factory submitAction; private final AbandonChange.Factory abandonChangeFactory; private final RestoreChange.Factory restoreChangeFactory; + private final RevertChange.Factory revertChangeFactory; @Inject ChangeManageServiceImpl(final SubmitAction.Factory patchSetAction, final AbandonChange.Factory abandonChangeFactory, - final RestoreChange.Factory restoreChangeFactory) { + final RestoreChange.Factory restoreChangeFactory, + final RevertChange.Factory revertChangeFactory) { this.submitAction = patchSetAction; this.abandonChangeFactory = abandonChangeFactory; this.restoreChangeFactory = restoreChangeFactory; + this.revertChangeFactory = revertChangeFactory; } public void submit(final PatchSet.Id patchSetId, @@ -44,6 +47,11 @@ class ChangeManageServiceImpl implements ChangeManageService { abandonChangeFactory.create(patchSetId, message).to(callback); } + public void revertChange(final PatchSet.Id patchSetId, final String message, + final AsyncCallback callback) { + revertChangeFactory.create(patchSetId, message).to(callback); + } + public void restoreChange(final PatchSet.Id patchSetId, final String message, final AsyncCallback callback) { restoreChangeFactory.create(patchSetId, message).to(callback); diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeModule.java index 85e2aef018..95c438c15d 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeModule.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeModule.java @@ -30,6 +30,7 @@ public class ChangeModule extends RpcServletModule { protected void configure() { factory(AbandonChange.Factory.class); factory(RestoreChange.Factory.class); + factory(RevertChange.Factory.class); factory(ChangeDetailFactory.Factory.class); factory(IncludedInDetailFactory.Factory.class); factory(PatchSetDetailFactory.Factory.class); diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RevertChange.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RevertChange.java new file mode 100644 index 0000000000..3dfa0e3765 --- /dev/null +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RevertChange.java @@ -0,0 +1,114 @@ +// Copyright (C) 2011 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.httpd.rpc.changedetail; + +import com.google.gerrit.common.ChangeHookRunner; +import com.google.gerrit.common.data.ChangeDetail; +import com.google.gerrit.common.errors.NoSuchEntityException; +import com.google.gerrit.httpd.rpc.Handler; +import com.google.gerrit.reviewdb.Change; +import com.google.gerrit.reviewdb.PatchSet; +import com.google.gerrit.reviewdb.ReviewDb; +import com.google.gerrit.server.ChangeUtil; +import com.google.gerrit.server.GerritPersonIdent; +import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.git.GitRepositoryManager; +import com.google.gerrit.server.git.ReplicationQueue; +import com.google.gerrit.server.mail.EmailException; +import com.google.gerrit.server.mail.RevertedSender; +import com.google.gerrit.server.patch.PatchSetInfoFactory; +import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException; +import com.google.gerrit.server.project.ChangeControl; +import com.google.gerrit.server.project.NoSuchChangeException; +import com.google.gwtorm.client.OrmException; +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; + +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.PersonIdent; + +import java.io.IOException; + +import javax.annotation.Nullable; + +class RevertChange extends Handler { + interface Factory { + RevertChange create(PatchSet.Id patchSetId, String message); + } + + private final ChangeControl.Factory changeControlFactory; + private final ReviewDb db; + private final IdentifiedUser currentUser; + private final RevertedSender.Factory revertedSenderFactory; + private final ChangeDetailFactory.Factory changeDetailFactory; + private final ReplicationQueue replication; + + private final PatchSet.Id patchSetId; + @Nullable + private final String message; + + private final ChangeHookRunner hooks; + + private final GitRepositoryManager gitManager; + private final PatchSetInfoFactory patchSetInfoFactory; + + private final PersonIdent myIdent; + + @Inject + RevertChange(final ChangeControl.Factory changeControlFactory, + final ReviewDb db, final IdentifiedUser currentUser, + final RevertedSender.Factory revertedSenderFactory, + final ChangeDetailFactory.Factory changeDetailFactory, + @Assisted final PatchSet.Id patchSetId, + @Assisted @Nullable final String message, final ChangeHookRunner hooks, + final GitRepositoryManager gitManager, + final PatchSetInfoFactory patchSetInfoFactory, + final ReplicationQueue replication, + @GerritPersonIdent final PersonIdent myIdent) { + this.changeControlFactory = changeControlFactory; + this.db = db; + this.currentUser = currentUser; + this.revertedSenderFactory = revertedSenderFactory; + this.changeDetailFactory = changeDetailFactory; + + this.patchSetId = patchSetId; + this.message = message; + this.hooks = hooks; + this.gitManager = gitManager; + + this.patchSetInfoFactory = patchSetInfoFactory; + this.replication = replication; + this.myIdent = myIdent; + } + + @Override + public ChangeDetail call() throws NoSuchChangeException, OrmException, + EmailException, NoSuchEntityException, PatchSetInfoNotAvailableException, + MissingObjectException, IncorrectObjectTypeException, IOException { + + final Change.Id changeId = patchSetId.getParentKey(); + final ChangeControl control = changeControlFactory.validateFor(changeId); + if (!control.canAddPatchSet()) { + throw new NoSuchChangeException(changeId); + } + + ChangeUtil.revert(patchSetId, currentUser, message, db, + revertedSenderFactory, hooks, gitManager, patchSetInfoFactory, + replication, myIdent); + + return changeDetailFactory.create(changeId).call(); + } +} 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 11ab9e05cb..6886d2c85d 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 @@ -17,31 +17,48 @@ package com.google.gerrit.server; import static com.google.gerrit.reviewdb.ApprovalCategory.SUBMIT; import com.google.gerrit.common.ChangeHookRunner; -import com.google.gerrit.common.data.ChangeDetail; import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.ChangeMessage; -import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.PatchSet; import com.google.gerrit.reviewdb.PatchSetApproval; +import com.google.gerrit.reviewdb.PatchSetInfo; +import com.google.gerrit.reviewdb.RevId; import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.reviewdb.TrackingId; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.config.TrackingFooter; import com.google.gerrit.server.config.TrackingFooters; +import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.MergeOp; import com.google.gerrit.server.git.MergeQueue; +import com.google.gerrit.server.git.ReplicationQueue; +import com.google.gerrit.server.patch.PatchSetInfoFactory; +import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException; import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.mail.AbandonedSender; import com.google.gerrit.server.mail.EmailException; +import com.google.gerrit.server.mail.RevertedSender; import com.google.gwtorm.client.AtomicUpdate; import com.google.gwtorm.client.OrmConcurrencyException; import com.google.gwtorm.client.OrmException; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.FooterLine; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.util.Base64; import org.eclipse.jgit.util.NB; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -248,6 +265,108 @@ public class ChangeUtil { hooks.doChangeAbandonedHook(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, + final ChangeHookRunner hooks, GitRepositoryManager gitManager, + final PatchSetInfoFactory patchSetInfoFactory, + final ReplicationQueue replication, PersonIdent myIdent) + throws NoSuchChangeException, EmailException, OrmException, + MissingObjectException, IncorrectObjectTypeException, IOException, + PatchSetInfoNotAvailableException { + + final Change.Id changeId = patchSetId.getParentKey(); + final PatchSet patch = db.patchSets().get(patchSetId); + if (patch == null) { + throw new NoSuchChangeException(changeId); + } + + final Repository git; + try { + git = gitManager.openRepository(db.changes().get(changeId).getProject()); + } catch (RepositoryNotFoundException e) { + throw new NoSuchChangeException(changeId, e); + }; + + final RevWalk revWalk = new RevWalk(git); + try { + RevCommit commitToRevert = + revWalk.parseCommit(ObjectId.fromString(patch.getRevision().get())); + + PersonIdent authorIdent = + user.newCommitterIdent(myIdent.getWhen(), myIdent.getTimeZone()); + + RevCommit parentToCommitToRevert = commitToRevert.getParent(0); + revWalk.parseHeaders(parentToCommitToRevert); + + CommitBuilder revertCommit = new CommitBuilder(); + revertCommit.addParentId(commitToRevert); + revertCommit.setTreeId(parentToCommitToRevert.getTree()); + revertCommit.setAuthor(authorIdent); + revertCommit.setCommitter(myIdent); + revertCommit.setMessage(message); + + final ObjectInserter oi = git.newObjectInserter();; + ObjectId id; + try { + id = oi.insert(revertCommit); + oi.flush(); + } finally { + oi.release(); + } + + Change.Key changeKey = new Change.Key("I" + id.name()); + final Change change = + new Change(changeKey, new Change.Id(db.nextChangeId()), + user.getAccountId(), db.changes().get(changeId).getDest()); + change.nextPatchSetId(); + + final PatchSet ps = new PatchSet(change.currPatchSetId()); + ps.setCreatedOn(change.getCreatedOn()); + ps.setUploader(user.getAccountId()); + ps.setRevision(new RevId(id.getName())); + + db.patchSets().insert(Collections.singleton(ps)); + + final PatchSetInfo info = + patchSetInfoFactory.get(revWalk.parseCommit(id), ps.getId()); + change.setCurrentPatchSet(info); + ChangeUtil.updated(change); + db.changes().insert(Collections.singleton(change)); + + final RefUpdate ru = git.updateRef(ps.getRefName()); + ru.setNewObjectId(id); + ru.disableRefLog(); + if (ru.update(revWalk) != RefUpdate.Result.NEW) { + throw new IOException("Failed to create ref " + ps.getRefName() + + " in " + git.getDirectory() + ": " + ru.getResult()); + } + replication.scheduleUpdate(db.changes().get(changeId).getProject(), + ru.getName()); + + final ChangeMessage cmsg = + new ChangeMessage(new ChangeMessage.Key(changeId, + ChangeUtil.messageUUID(db)), user.getAccountId()); + final StringBuilder msgBuf = + new StringBuilder("Patch Set " + patchSetId.get() + ": Reverted"); + msgBuf.append("\n\n"); + msgBuf.append("This patchset was reverted in change: " + changeKey.get()); + + cmsg.setMessage(msgBuf.toString()); + db.changeMessages().insert(Collections.singleton(cmsg)); + + final RevertedSender cm = revertedSenderFactory.create(change); + cm.setFrom(user.getAccountId()); + cm.setChangeMessage(cmsg); + cm.send(); + + hooks.doPatchsetCreatedHook(change, ps); + } finally { + revWalk.release(); + git.close(); + } + } + public static void restore(final PatchSet.Id patchSetId, final IdentifiedUser user, final String message, final ReviewDb db, final AbandonedSender.Factory abandonedSenderFactory, 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 15e266546c..c3ee776329 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 @@ -32,6 +32,7 @@ import com.google.gerrit.server.mail.MergeFailSender; import com.google.gerrit.server.mail.MergedSender; import com.google.gerrit.server.mail.RegisterNewEmailSender; import com.google.gerrit.server.mail.ReplacePatchSetSender; +import com.google.gerrit.server.mail.RevertedSender; import com.google.gerrit.server.patch.PublishComments; import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.ProjectControl; @@ -66,6 +67,7 @@ public class GerritRequestModule extends FactoryModule { factory(PublishComments.Factory.class); factory(ReplacePatchSetSender.Factory.class); factory(AbandonedSender.Factory.class); + factory(RevertedSender.Factory.class); factory(CommentSender.Factory.class); factory(MergedSender.Factory.class); factory(MergeFailSender.Factory.class); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RevertedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RevertedSender.java new file mode 100644 index 0000000000..8ad993b957 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RevertedSender.java @@ -0,0 +1,45 @@ +// Copyright (C) 2011 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.mail; + +import com.google.gerrit.reviewdb.Change; +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; + +/** Send notice about a change being reverted. */ +public class RevertedSender extends ReplyToChangeSender { + public static interface Factory { + RevertedSender create(Change change); + } + + @Inject + public RevertedSender(EmailArguments ea, @Assisted Change c) { + super(ea, c, "revert"); + } + + @Override + protected void init() throws EmailException { + super.init(); + + ccAllApprovals(); + bccStarredBy(); + bccWatchesNotifyAllComments(); + } + + @Override + protected void formatChange() throws EmailException { + appendText(velocifyFile("Reverted.vm")); + } +} diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Reverted.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Reverted.vm new file mode 100644 index 0000000000..a2350d849d --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Reverted.vm @@ -0,0 +1,44 @@ +## Copyright (C) 2010 The Android Open Source Project +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## +## +## 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.exmaple file to a .vm +## file and edit it appropriately. +## +## This Template: +## -------------- +## The Reverted.vm template will determine the contents of the email related +## to a change being reverted. It is a ChangeEmail: see ChangeSubject.vm and +## ChangeFooter.vm. +## +$fromName has reverted this change. + +Change subject: $change.subject +...................................................................... + + +#if ($coverLetter) +$coverLetter + +#end