summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAnatol Pomazau <anatol@google.com>2010-08-04 11:28:50 -0700
committerShawn O. Pearce <sop@google.com>2010-08-19 19:46:15 -0700
commit607570ecaa2985ba34f43298d65d26865532c12d (patch)
treefc0691beebab189dae61fac60f4ee3dc815e7bb1
parentef1b0f15ef95b3f1c69d4902118cbadddb666e5d (diff)
Implement 'Restore Change' feature.
Now it is possible to restore status of abandoned changes to 'NEW'. It helps in a situation when a change has been abandoned accidently. Bug: issue 312 Change-Id: Iba61dcf82a9b5ee5b78cd529b041f92a042a9611 [cherry-picked from 32002457744e9e0c17b258fcfea5dd6d30732950] Change-Id: Icc6dddb63689250c869189639af0d72ed8837336
-rw-r--r--Documentation/cmd-stream-events.txt10
-rw-r--r--Documentation/config-hooks.txt9
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java9
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeManageService.java4
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java6
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties6
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java20
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/RestoreChangeDialog.java133
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java1
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeManageServiceImpl.java10
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeModule.java1
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RestoreChange.java132
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java31
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeRestoreEvent.java23
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java5
15 files changed, 399 insertions, 1 deletions
diff --git a/Documentation/cmd-stream-events.txt b/Documentation/cmd-stream-events.txt
index 0b536fc476..aae908a2f7 100644
--- a/Documentation/cmd-stream-events.txt
+++ b/Documentation/cmd-stream-events.txt
@@ -70,6 +70,16 @@ patchset:: link:json.html#patchset[patchset attribute]
abandoner:: link:json.html#account[account attribute]
+Change Restored
+^^^^^^^^^^^^^^^^
+type:: "change-restored"
+
+change:: link:json.html#change[change attribute]
+
+patchset:: link:json.html#patchset[patchset attribute]
+
+restorer:: link:json.html#account[account attribute]
+
Change Merged
^^^^^^^^^^^^^
type:: "change-merged"
diff --git a/Documentation/config-hooks.txt b/Documentation/config-hooks.txt
index 4dd7531c51..95fc7bf0bb 100644
--- a/Documentation/config-hooks.txt
+++ b/Documentation/config-hooks.txt
@@ -57,6 +57,15 @@ Called whenever a change has been abandoned.
change-abandoned --change <change id> --change-url <change url> --project <project name> --branch <branch> --abandoner <abandoner> --reason <reason>
====
+change-restored
+~~~~~~~~~~~~~~~~
+
+Called whenever a change has been restored.
+
+====
+ change-restored --change <change id> --change-url <change url> --project <project name> --branch <branch> --restorer <restorer> --reason <reason>
+====
+
Configuration Settings
----------------------
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 80c1c31f28..cd992fdfe8 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
@@ -30,6 +30,7 @@ public class ChangeDetail {
protected AccountInfoCache accounts;
protected boolean allowsAnonymous;
protected boolean canAbandon;
+ protected boolean canRestore;
protected Change change;
protected boolean starred;
protected List<ChangeInfo> dependsOn;
@@ -69,6 +70,14 @@ public class ChangeDetail {
canAbandon = a;
}
+ public boolean canRestore() {
+ return canRestore;
+ }
+
+ public void setCanRestore(final boolean a) {
+ canRestore = 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 53946f2230..b61c35c1e4 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
@@ -29,4 +29,8 @@ public interface ChangeManageService extends RemoteJsonService {
@SignInRequired
void abandonChange(PatchSet.Id patchSetId, String message,
AsyncCallback<ChangeDetail> callback);
+
+ @SignInRequired
+ void restoreChange(PatchSet.Id patchSetId, String message,
+ AsyncCallback<ChangeDetail> callback);
}
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 c5901d6a15..e339d26af2 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
@@ -112,6 +112,12 @@ public interface ChangeConstants extends Constants {
String headingCoverMessage();
String headingPatchComments();
+ String buttonRestoreChangeBegin();
+ String restoreChangeTitle();
+ String buttonRestoreChangeCancel();
+ String headingRestoreMessage();
+ String buttonRestoreChangeSend();
+
String pagedChangeListPrev();
String pagedChangeListNext();
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 75f3ae28b1..ed6be476bb 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
@@ -82,6 +82,12 @@ buttonAbandonChangeCancel = Cancel
headingAbandonMessage = Abandon Message:
abandonChangeTitle = Code Review - Abandon Change
+buttonRestoreChangeBegin = Restore Change
+restoreChangeTitle = Code Review - Restore Change
+buttonRestoreChangeCancel = Cancel
+headingRestoreMessage = Restore Message:
+buttonRestoreChangeSend = Restore Change
+
buttonReview = Review
buttonPublishCommentsSend = Publish Comments
buttonPublishSubmitSend = Publish and Submit
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 e004f00d3c..6a57feb165 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
@@ -416,6 +416,26 @@ class PatchSetComplexDisclosurePanel extends ComplexDisclosurePanel implements O
});
actionsPanel.add(b);
}
+
+ if (changeDetail.canRestore()) {
+ final Button b = new Button(Util.C.buttonRestoreChangeBegin());
+ b.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(final ClickEvent event) {
+ new RestoreChangeDialog(patchSet.getId(),
+ new AsyncCallback<ChangeDetail>() {
+ public void onSuccess(ChangeDetail result) {
+ changeScreen.display(result);
+ }
+
+ public void onFailure(Throwable caught) {
+ b.setEnabled(true);
+ }
+ }).center();
+ }
+ });
+ actionsPanel.add(b);
+ }
}
private void populateDiffAllActions(final PatchSetDetail detail) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/RestoreChangeDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/RestoreChangeDialog.java
new file mode 100644
index 0000000000..e05c42afc6
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/RestoreChangeDialog.java
@@ -0,0 +1,133 @@
+// Copyright (C) 2009 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.client.changes;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.ui.SmallHeading;
+import com.google.gerrit.common.data.ChangeDetail;
+import com.google.gerrit.reviewdb.PatchSet;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwtexpui.globalkey.client.GlobalKey;
+import com.google.gwtexpui.globalkey.client.NpTextArea;
+import com.google.gwtexpui.user.client.AutoCenterDialogBox;
+
+public class RestoreChangeDialog extends AutoCenterDialogBox implements CloseHandler<PopupPanel>{
+ private final FlowPanel panel;
+ private final NpTextArea message;
+ private final Button sendButton;
+ private final Button cancelButton;
+ private final PatchSet.Id psid;
+ private final AsyncCallback<ChangeDetail> callback;
+
+ private boolean buttonClicked = false;
+
+ public RestoreChangeDialog(final PatchSet.Id psi,
+ final AsyncCallback<ChangeDetail> callback) {
+ super(/* auto hide */false, /* modal */true);
+ setGlassEnabled(true);
+
+ psid = psi;
+ this.callback = callback;
+ addStyleName(Gerrit.RESOURCES.css().abandonChangeDialog());
+ setText(Util.C.restoreChangeTitle());
+
+ panel = new FlowPanel();
+ add(panel);
+
+ panel.add(new SmallHeading(Util.C.headingRestoreMessage()));
+
+ final FlowPanel mwrap = new FlowPanel();
+ mwrap.setStyleName(Gerrit.RESOURCES.css().abandonMessage());
+ panel.add(mwrap);
+
+ message = new NpTextArea();
+ message.setCharacterWidth(60);
+ message.setVisibleLines(10);
+ DOM.setElementPropertyBoolean(message.getElement(), "spellcheck", true);
+ mwrap.add(message);
+
+ final FlowPanel buttonPanel = new FlowPanel();
+ panel.add(buttonPanel);
+
+ sendButton = new Button(Util.C.buttonRestoreChangeSend());
+ sendButton.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(final ClickEvent event) {
+ sendButton.setEnabled(false);
+ Util.MANAGE_SVC.restoreChange(psid, message.getText().trim(),
+ new GerritCallback<ChangeDetail>() {
+ @Override
+ public void onSuccess(ChangeDetail result) {
+ buttonClicked = true;
+ if (callback != null) {
+ callback.onSuccess(result);
+ }
+ hide();
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ sendButton.setEnabled(true);
+ super.onFailure(caught);
+ }
+ });
+ }
+ });
+ buttonPanel.add(sendButton);
+
+ cancelButton = new Button(Util.C.buttonRestoreChangeCancel());
+ DOM.setStyleAttribute(cancelButton.getElement(), "marginLeft", "300px");
+ cancelButton.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(final ClickEvent event) {
+ buttonClicked = true;
+ if (callback != null) {
+ callback.onFailure(null);
+ }
+ hide();
+ }
+ });
+ buttonPanel.add(cancelButton);
+
+ addCloseHandler(this);
+ }
+
+ @Override
+ public void center() {
+ super.center();
+ GlobalKey.dialog(this);
+ message.setFocus(true);
+ }
+
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event) {
+ if (!buttonClicked) {
+ // the dialog was closed without one of the buttons being pressed
+ // e.g. the user pressed ESC to close the dialog
+ if (callback != null) {
+ callback.onFailure(null);
+ }
+ }
+ }
+}
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 bcea11d992..954f0a233b 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
@@ -101,6 +101,7 @@ public class ChangeDetailFactory extends Handler<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));
loadPatchSets();
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 196d400de6..0eccc2c3fd 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
@@ -23,12 +23,15 @@ import com.google.inject.Inject;
class ChangeManageServiceImpl implements ChangeManageService {
private final SubmitAction.Factory submitAction;
private final AbandonChange.Factory abandonChangeFactory;
+ private final RestoreChange.Factory restoreChangeFactory;
@Inject
ChangeManageServiceImpl(final SubmitAction.Factory patchSetAction,
- final AbandonChange.Factory abandonChangeFactory) {
+ final AbandonChange.Factory abandonChangeFactory,
+ final RestoreChange.Factory restoreChangeFactory) {
this.submitAction = patchSetAction;
this.abandonChangeFactory = abandonChangeFactory;
+ this.restoreChangeFactory = restoreChangeFactory;
}
public void submit(final PatchSet.Id patchSetId,
@@ -40,4 +43,9 @@ class ChangeManageServiceImpl implements ChangeManageService {
final AsyncCallback<ChangeDetail> callback) {
abandonChangeFactory.create(patchSetId, message).to(callback);
}
+
+ public void restoreChange(final PatchSet.Id patchSetId, final String message,
+ final AsyncCallback<ChangeDetail> 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 8605de398c..85e2aef018 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
@@ -29,6 +29,7 @@ public class ChangeModule extends RpcServletModule {
@Override
protected void configure() {
factory(AbandonChange.Factory.class);
+ factory(RestoreChange.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/RestoreChange.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RestoreChange.java
new file mode 100644
index 0000000000..9639f1a2bb
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RestoreChange.java
@@ -0,0 +1,132 @@
+// Copyright (C) 2009 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.*;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.mail.AbandonedSender;
+import com.google.gerrit.server.mail.EmailException;
+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.AtomicUpdate;
+import com.google.gwtorm.client.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import javax.annotation.Nullable;
+import java.util.Collections;
+import java.util.List;
+
+class RestoreChange extends Handler<ChangeDetail> {
+ interface Factory {
+ RestoreChange create(PatchSet.Id patchSetId, String message);
+ }
+
+ private final ChangeControl.Factory changeControlFactory;
+ private final ReviewDb db;
+ private final IdentifiedUser currentUser;
+ private final AbandonedSender.Factory abandonedSenderFactory;
+ private final ChangeDetailFactory.Factory changeDetailFactory;
+
+ private final PatchSet.Id patchSetId;
+ @Nullable
+ private final String message;
+
+ private final ChangeHookRunner hooks;
+
+ @Inject
+ RestoreChange(final ChangeControl.Factory changeControlFactory,
+ final ReviewDb db, final IdentifiedUser currentUser,
+ final AbandonedSender.Factory abandonedSenderFactory,
+ final ChangeDetailFactory.Factory changeDetailFactory,
+ @Assisted final PatchSet.Id patchSetId,
+ @Assisted @Nullable final String message, final ChangeHookRunner hooks) {
+ this.changeControlFactory = changeControlFactory;
+ this.db = db;
+ this.currentUser = currentUser;
+ this.abandonedSenderFactory = abandonedSenderFactory;
+ this.changeDetailFactory = changeDetailFactory;
+
+ this.patchSetId = patchSetId;
+ this.message = message;
+ this.hooks = hooks;
+ }
+
+ @Override
+ public ChangeDetail call() throws NoSuchChangeException, OrmException,
+ EmailException, NoSuchEntityException, PatchSetInfoNotAvailableException {
+ final Change.Id changeId = patchSetId.getParentKey();
+ final ChangeControl control = changeControlFactory.validateFor(changeId);
+ if (!control.canRestore()) {
+ throw new NoSuchChangeException(changeId);
+ }
+ 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)), currentUser.getAccountId());
+ final StringBuilder msgBuf =
+ new StringBuilder("Patch Set " + patchSetId.get() + ": Restored");
+ if (message != null && message.length() > 0) {
+ msgBuf.append("\n\n");
+ msgBuf.append(message);
+ }
+ cmsg.setMessage(msgBuf.toString());
+
+ Change change = db.changes().atomicUpdate(changeId, new AtomicUpdate<Change>() {
+ @Override
+ public Change update(Change change) {
+ if (change.getStatus() == Change.Status.ABANDONED
+ && change.currentPatchSetId().equals(patchSetId)) {
+ change.setStatus(Change.Status.NEW);
+ ChangeUtil.updated(change);
+ return change;
+ } else {
+ return null;
+ }
+ }
+ });
+
+ if (change != null) {
+ db.changeMessages().insert(Collections.singleton(cmsg));
+
+ final List<PatchSetApproval> approvals =
+ db.patchSetApprovals().byChange(changeId).toList();
+ for (PatchSetApproval a : approvals) {
+ a.cache(change);
+ }
+ db.patchSetApprovals().update(approvals);
+
+ // Email the reviewers
+ final AbandonedSender cm = abandonedSenderFactory.create(change);
+ cm.setFrom(currentUser.getAccountId());
+ cm.setChangeMessage(cmsg);
+ cm.send();
+ }
+
+ hooks.doChangeRestoreHook(change, currentUser.getAccount(), message);
+
+ return changeDetailFactory.create(changeId).call();
+ }
+}
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 05bd28b9df..d29bc23964 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.events.ApprovalAttribute;
import com.google.gerrit.server.events.ChangeAbandonedEvent;
import com.google.gerrit.server.events.ChangeEvent;
import com.google.gerrit.server.events.ChangeMergedEvent;
+import com.google.gerrit.server.events.ChangeRestoreEvent;
import com.google.gerrit.server.events.CommentAddedEvent;
import com.google.gerrit.server.events.EventFactory;
import com.google.gerrit.server.events.PatchSetCreatedEvent;
@@ -92,6 +93,9 @@ public class ChangeHookRunner {
/** Filename of the change abandoned hook. */
private final File changeAbandonedHook;
+ /** Filename of the change abandoned hook. */
+ private final File changeRestoredHook;
+
/** Repository Manager. */
private final GitRepositoryManager repoManager;
@@ -136,6 +140,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());
+ changeRestoredHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "changeRestoredHook", "change-restored")).getPath());
}
public void addChangeListener(ChangeListener listener, IdentifiedUser user) {
@@ -304,6 +309,32 @@ public class ChangeHookRunner {
runHook(openRepository(change), changeAbandonedHook, args);
}
+ /**
+ * Fire the Change Restored Hook.
+ *
+ * @param change The change itself.
+ * @param account The gerrit user who restored the change.
+ * @param reason Reason for restoring the change.
+ */
+ public void doChangeRestoreHook(final Change change, final Account account, final String reason) {
+ final ChangeRestoreEvent event = new ChangeRestoreEvent();
+
+ event.change = eventFactory.asChangeAttribute(change);
+ event.restorer = 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, "--restorer", getDisplayName(account));
+ addArg(args, "--reason", reason == null ? "" : reason);
+
+ runHook(openRepository(change), changeRestoredHook, args);
+ }
+
private void fireEvent(final Change change, final ChangeEvent event) {
for (ChangeListenerHolder holder : listeners.values()) {
if (isVisibleTo(change, holder.user)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeRestoreEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeRestoreEvent.java
new file mode 100644
index 0000000000..1a2922bb33
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeRestoreEvent.java
@@ -0,0 +1,23 @@
+// 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.
+
+package com.google.gerrit.server.events;
+
+public class ChangeRestoreEvent extends ChangeEvent {
+ public final String type = "change-restored";
+ public ChangeAttribute change;
+ public PatchSetAttribute patchSet;
+ public AccountAttribute restorer;
+ public String reason;
+}
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 c9435d3151..3291f1ac3b 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
@@ -159,6 +159,11 @@ public class ChangeControl {
;
}
+ /** Can this user restore this change? */
+ public boolean canRestore() {
+ return canAbandon(); // Anyone who can abandon the change can restore it back
+ }
+
public short normalize(ApprovalCategory.Id category, short score) {
return getRefControl().normalize(category, score);
}