summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndroid Code Review <code-review@android.com>2009-03-09 12:54:40 -0700
committerAndroid Code Review <code-review@android.com>2009-03-09 12:54:40 -0700
commit1afce7ff1683d5879d2844963386d789d701c923 (patch)
treeaaf618d6425aa543d1fbcd49a0959b9d394712a2
parentdce0ee9ad37f3b96f955dfb781f83dde15e3f940 (diff)
parentf6a57ac50379a7e68c4af5bc2a491ace74ddc66b (diff)
Merge
-rw-r--r--src/main/java/com/google/gerrit/Gerrit.gwt.xml2
-rw-r--r--src/main/java/com/google/gerrit/client/changes/AbandonChangeDialog.java105
-rw-r--r--src/main/java/com/google/gerrit/client/changes/ChangeConstants.java7
-rw-r--r--src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties7
-rw-r--r--src/main/java/com/google/gerrit/client/changes/ChangeDetailServiceImpl.java27
-rw-r--r--src/main/java/com/google/gerrit/client/changes/PatchSetPanel.java22
-rw-r--r--src/main/java/com/google/gerrit/client/data/ChangeDetail.java9
-rw-r--r--src/main/java/com/google/gerrit/client/patches/PatchDetailService.java4
-rw-r--r--src/main/java/com/google/gerrit/public/gerrit3.cache.css (renamed from src/main/java/com/google/gerrit/public/gerrit2.cache.css)28
-rw-r--r--src/main/java/com/google/gerrit/server/ChangeMail.java35
-rw-r--r--src/main/java/com/google/gerrit/server/patch/PatchDetailServiceImpl.java93
11 files changed, 334 insertions, 5 deletions
diff --git a/src/main/java/com/google/gerrit/Gerrit.gwt.xml b/src/main/java/com/google/gerrit/Gerrit.gwt.xml
index 8d73d5cb44..2f03f9b62b 100644
--- a/src/main/java/com/google/gerrit/Gerrit.gwt.xml
+++ b/src/main/java/com/google/gerrit/Gerrit.gwt.xml
@@ -12,7 +12,7 @@
<entry-point class='com.google.gerrit.client.Gerrit'/>
- <stylesheet src='gerrit2.cache.css' />
+ <stylesheet src='gerrit3.cache.css' />
<servlet path='/Gerrit'
class='com.google.gerrit.server.HostPageServlet'/>
diff --git a/src/main/java/com/google/gerrit/client/changes/AbandonChangeDialog.java b/src/main/java/com/google/gerrit/client/changes/AbandonChangeDialog.java
new file mode 100644
index 0000000000..b0d419bfde
--- /dev/null
+++ b/src/main/java/com/google/gerrit/client/changes/AbandonChangeDialog.java
@@ -0,0 +1,105 @@
+// 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.patches.PatchUtil;
+import com.google.gerrit.client.reviewdb.PatchSet;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.ui.SmallHeading;
+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.ClickListener;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.TextArea;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwtexpui.user.client.AutoCenterDialogBox;
+import com.google.gwtjsonrpc.client.VoidResult;
+
+public class AbandonChangeDialog extends AutoCenterDialogBox {
+
+ private final FlowPanel panel;
+ private final TextArea message;
+ private final Button sendButton;
+ private final Button cancelButton;
+ private final PatchSet.Id psid;
+ private final AsyncCallback<?> appCallback;
+
+ public AbandonChangeDialog(final PatchSet.Id psi,
+ final AsyncCallback<?> callback) {
+ super(/* auto hide */true, /* modal */true);
+
+ psid = psi;
+ appCallback = callback;
+ addStyleName("gerrit-AbandonChangeDialog");
+ setText(Util.C.abandonChangeTitle());
+
+ panel = new FlowPanel();
+ add(panel);
+
+ panel.add(new SmallHeading(Util.C.headingAbandonMessage()));
+
+ final FlowPanel mwrap = new FlowPanel();
+ mwrap.setStyleName("gerrit-AbandonMessage");
+ panel.add(mwrap);
+
+ message = new TextArea();
+ message.setCharacterWidth(60);
+ message.setVisibleLines(10);
+ DOM.setElementPropertyBoolean(message.getElement(), "spellcheck", true);
+ mwrap.add(message);
+
+ final FlowPanel buttonPanel = new FlowPanel();
+ buttonPanel.setStyleName("gerrit-CommentEditor-Buttons");
+ panel.add(buttonPanel);
+
+ sendButton = new Button(Util.C.buttonAbandonChangeSend());
+ sendButton.addClickListener(new ClickListener() {
+ public void onClick(Widget sender) {
+ sendButton.setEnabled(false);
+ PatchUtil.DETAIL_SVC.abandonChange(psid, message.getText().trim(),
+ new GerritCallback<VoidResult>() {
+ public void onSuccess(VoidResult result) {
+ if (appCallback != null) {
+ appCallback.onSuccess(null);
+ }
+ hide();
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ sendButton.setEnabled(true);
+ super.onFailure(caught);
+ }
+ });
+ }
+ });
+ buttonPanel.add(sendButton);
+
+ cancelButton = new Button(Util.C.buttonAbandonChangeCancel());
+ cancelButton.addClickListener(new ClickListener() {
+ public void onClick(Widget sender) {
+ hide();
+ }
+ });
+ buttonPanel.add(cancelButton);
+ }
+
+ @Override
+ protected void onLoad() {
+ super.onLoad();
+ message.setFocus(true);
+ }
+}
diff --git a/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java b/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
index afc1555b9b..a69d85adc4 100644
--- a/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
+++ b/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
@@ -70,6 +70,13 @@ public interface ChangeConstants extends Constants {
String patchSetInfoAuthor();
String patchSetInfoCommitter();
String patchSetInfoDownload();
+
+ String buttonAbandonChangeBegin();
+ String buttonAbandonChangeSend();
+ String buttonAbandonChangeCancel();
+ String headingAbandonMessage();
+ String abandonChangeTitle();
+
String buttonPublishCommentsBegin();
String buttonPublishCommentsSend();
String buttonPublishCommentsCancel();
diff --git a/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties b/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
index 38546e923c..d0662c42e8 100644
--- a/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
+++ b/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
@@ -50,6 +50,13 @@ messageCollapseAll = Collapse All
patchSetInfoAuthor = Author
patchSetInfoCommitter = Committer
patchSetInfoDownload = Download
+
+buttonAbandonChangeBegin = Abandon Change
+buttonAbandonChangeSend = Abandon Change
+buttonAbandonChangeCancel = Cancel
+headingAbandonMessage = Abandon Message:
+abandonChangeTitle = Code Review - Abandon Change
+
buttonPublishCommentsBegin = Publish Comments
buttonPublishCommentsSend = Publish Comments
buttonPublishCommentsCancel = Cancel
diff --git a/src/main/java/com/google/gerrit/client/changes/ChangeDetailServiceImpl.java b/src/main/java/com/google/gerrit/client/changes/ChangeDetailServiceImpl.java
index 959ced2deb..52263ddb92 100644
--- a/src/main/java/com/google/gerrit/client/changes/ChangeDetailServiceImpl.java
+++ b/src/main/java/com/google/gerrit/client/changes/ChangeDetailServiceImpl.java
@@ -17,8 +17,11 @@ package com.google.gerrit.client.changes;
import com.google.gerrit.client.data.AccountInfoCacheFactory;
import com.google.gerrit.client.data.ChangeDetail;
import com.google.gerrit.client.data.PatchSetDetail;
+import com.google.gerrit.client.data.ProjectCache;
+import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.client.reviewdb.Change;
import com.google.gerrit.client.reviewdb.PatchSet;
+import com.google.gerrit.client.reviewdb.Project;
import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gerrit.client.rpc.BaseServiceImplementation;
import com.google.gerrit.client.rpc.Common;
@@ -32,14 +35,23 @@ public class ChangeDetailServiceImpl extends BaseServiceImplementation
final AsyncCallback<ChangeDetail> callback) {
run(callback, new Action<ChangeDetail>() {
public ChangeDetail run(final ReviewDb db) throws OrmException, Failure {
+ final Account.Id me = Common.getAccountId();
final Change change = db.changes().get(id);
if (change == null) {
throw new Failure(new NoSuchEntityException());
}
+ final PatchSet patch = db.patchSets().get(change.currentPatchSetId());
+ final ProjectCache.Entry projEnt =
+ Common.getProjectCache().get(change.getDest().getParentKey());
+ if (patch == null || projEnt == null) {
+ throw new Failure(new NoSuchEntityException());
+ }
+ final Project proj = projEnt.getProject();
assertCanRead(change);
final boolean anon;
- if (Common.getAccountId() == null) {
+ boolean canAbandon = false;
+ if (me == null) {
// Safe assumption, this wouldn't be allowed if it wasn't.
//
anon = true;
@@ -48,9 +60,20 @@ public class ChangeDetailServiceImpl extends BaseServiceImplementation
// we can that doesn't mean the anonymous user could.
//
anon = canRead(null, change.getDest().getParentKey());
+
+ // The change owner, current patchset uploader, Gerrit administrator,
+ // and project administrator can mark the change as abandoned.
+ //
+ canAbandon =
+ me.equals(change.getOwner())
+ || me.equals(patch.getUploader())
+ || Common.getGroupCache().isAdministrator(me)
+ || Common.getGroupCache().isInGroup(me,
+ proj.getOwnerGroupId());
}
final ChangeDetail d = new ChangeDetail();
- d.load(db, new AccountInfoCacheFactory(db), change, anon);
+
+ d.load(db, new AccountInfoCacheFactory(db), change, anon, canAbandon);
return d;
}
});
diff --git a/src/main/java/com/google/gerrit/client/changes/PatchSetPanel.java b/src/main/java/com/google/gerrit/client/changes/PatchSetPanel.java
index 0bf1c3fa4d..4295f27e76 100644
--- a/src/main/java/com/google/gerrit/client/changes/PatchSetPanel.java
+++ b/src/main/java/com/google/gerrit/client/changes/PatchSetPanel.java
@@ -33,6 +33,7 @@ import com.google.gerrit.client.rpc.Common;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.RefreshListener;
import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Composite;
@@ -161,6 +162,9 @@ class PatchSetPanel extends Composite implements DisclosureHandler {
if (Gerrit.isSignedIn() && changeDetail.isCurrentPatchSet(detail)) {
populateCommentAction();
populateActions(detail);
+ if (changeDetail.canAbandon()) {
+ populateAbandonAction();
+ }
}
body.add(patchTable);
}
@@ -311,6 +315,24 @@ class PatchSetPanel extends Composite implements DisclosureHandler {
}
}
+ private void populateAbandonAction() {
+ final Button b = new Button(Util.C.buttonAbandonChangeBegin());
+ b.addClickListener(new ClickListener() {
+ public void onClick(Widget sender) {
+ new AbandonChangeDialog(patchSet.getId(), new AsyncCallback<Object>() {
+ public void onSuccess(Object result) {
+ actionsPanel.remove(b);
+ fireOnSuggestRefresh();
+ }
+
+ public void onFailure(Throwable caught) {
+ }
+ }).center();
+ }
+ });
+ actionsPanel.add(b);
+ }
+
private void populateCommentAction() {
final Button b = new Button(Util.C.buttonPublishCommentsBegin());
b.addClickListener(new ClickListener() {
diff --git a/src/main/java/com/google/gerrit/client/data/ChangeDetail.java b/src/main/java/com/google/gerrit/client/data/ChangeDetail.java
index 036de1e977..9677789409 100644
--- a/src/main/java/com/google/gerrit/client/data/ChangeDetail.java
+++ b/src/main/java/com/google/gerrit/client/data/ChangeDetail.java
@@ -41,6 +41,7 @@ import java.util.Set;
public class ChangeDetail {
protected AccountInfoCache accounts;
protected boolean allowsAnonymous;
+ protected boolean canAbandon;
protected Change change;
protected List<ChangeInfo> dependsOn;
protected List<ChangeInfo> neededBy;
@@ -56,12 +57,14 @@ public class ChangeDetail {
}
public void load(final ReviewDb db, final AccountInfoCacheFactory acc,
- final Change c, final boolean allowAnon) throws OrmException {
+ final Change c, final boolean allowAnon, final boolean canAbdn)
+ throws OrmException {
change = c;
final Account.Id owner = change.getOwner();
acc.want(owner);
allowsAnonymous = allowAnon;
+ canAbandon = canAbdn;
patchSets = db.patchSets().byChange(change.getId()).toList();
messages = db.changeMessages().byChange(change.getId()).toList();
for (final ChangeMessage m : messages) {
@@ -177,6 +180,10 @@ public class ChangeDetail {
return allowsAnonymous;
}
+ public boolean canAbandon() {
+ return canAbandon;
+ }
+
public Change getChange() {
return change;
}
diff --git a/src/main/java/com/google/gerrit/client/patches/PatchDetailService.java b/src/main/java/com/google/gerrit/client/patches/PatchDetailService.java
index 7d00318975..04d506baba 100644
--- a/src/main/java/com/google/gerrit/client/patches/PatchDetailService.java
+++ b/src/main/java/com/google/gerrit/client/patches/PatchDetailService.java
@@ -54,4 +54,8 @@ public interface PatchDetailService extends RemoteJsonService {
@SignInRequired
void addReviewers(Change.Id id, List<String> reviewers,
AsyncCallback<VoidResult> callback);
+
+ @SignInRequired
+ void abandonChange(PatchSet.Id patchSetId, String message,
+ AsyncCallback<VoidResult> callback);
}
diff --git a/src/main/java/com/google/gerrit/public/gerrit2.cache.css b/src/main/java/com/google/gerrit/public/gerrit3.cache.css
index 69512fe775..66c735e6f2 100644
--- a/src/main/java/com/google/gerrit/public/gerrit2.cache.css
+++ b/src/main/java/com/google/gerrit/public/gerrit3.cache.css
@@ -288,7 +288,7 @@
border-collapse: separate;
border-spacing: 0;
}
-.gerrit-PatchContentTable td {
+.gerrit-PatchContentTable td {
font-size: 8pt;
font-family: monospace;
}
@@ -777,6 +777,32 @@
}
+/** AbandonChangeDialog **/
+
+.gerrit-AbandonChangeDialog .gwt-DisclosurePanel .header td {
+ font-weight: bold;
+ white-space: nowrap;
+}
+
+.gerrit-AbandonChangeDialog .gerrit-SmallHeading {
+ font-size: small;
+ font-weight: bold;
+ white-space: nowrap;
+}
+.gerrit-AbandonChangeDialog .gerrit-AbandonMessage {
+ margin-left: 10px;
+ background: #d4e9a9;
+ padding: 5px 5px 5px 5px;
+}
+.gerrit-AbandonChangeDialog .gerrit-AbandonMessage textarea {
+ font-size: small;
+}
+.gerrit-AbandonChangeDialog .gwt-Hyperlink {
+ white-space: nowrap;
+ font-size: small;
+}
+
+
/** OpenIdLoginPanel **/
.gerrit-OpenID-loginform {
margin-left: 10px;
diff --git a/src/main/java/com/google/gerrit/server/ChangeMail.java b/src/main/java/com/google/gerrit/server/ChangeMail.java
index 4c77fd9af5..b0515e96e3 100644
--- a/src/main/java/com/google/gerrit/server/ChangeMail.java
+++ b/src/main/java/com/google/gerrit/server/ChangeMail.java
@@ -295,6 +295,41 @@ public class ChangeMail {
}
}
+ public void sendAbandoned() throws MessagingException {
+ if (begin("abandon")) {
+ final Account a = Common.getAccountCache().get(fromId);
+ if (a == null || a.getFullName() == null || a.getFullName().length() == 0) {
+ body.append("A Gerrit user");
+ } else {
+ body.append(a.getFullName());
+ }
+
+ body.append(" has abandoned a change:\n\n");
+ body.append(change.getChangeId());
+ body.append(" - ");
+ body.append(change.getSubject());
+ body.append("\n\n");
+
+ if (message != null) {
+ body.append(message.getMessage().trim());
+ if (body.length() > 0) {
+ body.append("\n\n");
+ }
+ }
+
+ if (changeUrl() != null) {
+ openFooter();
+ body.append("To view visit ");
+ body.append(changeUrl());
+ body.append("\n");
+ }
+
+ initInReplyToChange();
+ commentTo();
+ send();
+ }
+ }
+
private void newChangeTo() throws MessagingException {
add(RecipientType.TO, reviewers);
add(RecipientType.CC, extraCC);
diff --git a/src/main/java/com/google/gerrit/server/patch/PatchDetailServiceImpl.java b/src/main/java/com/google/gerrit/server/patch/PatchDetailServiceImpl.java
index db835cef62..d95bc139dd 100644
--- a/src/main/java/com/google/gerrit/server/patch/PatchDetailServiceImpl.java
+++ b/src/main/java/com/google/gerrit/server/patch/PatchDetailServiceImpl.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.patch;
import com.google.gerrit.client.data.ApprovalType;
+import com.google.gerrit.client.data.ProjectCache;
import com.google.gerrit.client.data.SideBySidePatchDetail;
import com.google.gerrit.client.data.UnifiedPatchDetail;
import com.google.gerrit.client.patches.PatchDetailService;
@@ -28,6 +29,7 @@ import com.google.gerrit.client.reviewdb.Patch;
import com.google.gerrit.client.reviewdb.PatchLineComment;
import com.google.gerrit.client.reviewdb.PatchSet;
import com.google.gerrit.client.reviewdb.PatchSetInfo;
+import com.google.gerrit.client.reviewdb.Project;
import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gerrit.client.reviewdb.Account.Id;
import com.google.gerrit.client.rpc.BaseServiceImplementation;
@@ -355,4 +357,95 @@ public class PatchDetailServiceImpl extends BaseServiceImplementation implements
}
return VoidResult.INSTANCE;
}
+
+ public void abandonChange(final PatchSet.Id patchSetId, final String message,
+ final AsyncCallback<VoidResult> callback) {
+ run(callback, new Action<VoidResult>() {
+ public VoidResult run(final ReviewDb db) throws OrmException, Failure {
+ final Account.Id me = Common.getAccountId();
+ final Change change = db.changes().get(patchSetId.getParentKey());
+ if (change == null) {
+ throw new Failure(new NoSuchEntityException());
+ }
+ final PatchSet patch = db.patchSets().get(patchSetId);
+ final ProjectCache.Entry projEnt =
+ Common.getProjectCache().get(change.getDest().getParentKey());
+ if (me == null || patch == null || projEnt == null) {
+ throw new Failure(new NoSuchEntityException());
+ }
+ final Project proj = projEnt.getProject();
+
+ if (!me.equals(change.getOwner()) && !me.equals(patch.getUploader())
+ && !Common.getGroupCache().isAdministrator(me)
+ && !Common.getGroupCache().isInGroup(me, proj.getOwnerGroupId())) {
+ // The user doesn't have permission to abandon the change
+ throw new Failure(new NoSuchEntityException());
+ }
+
+ final ChangeMessage cmsg =
+ new ChangeMessage(new ChangeMessage.Key(change.getId(), ChangeUtil
+ .messageUUID(db)), me);
+ final StringBuilder msgBuf =
+ new StringBuilder("Patch Set " + change.currentPatchSetId().get()
+ + ": Abandoned");
+ if (message != null && message.length() > 0) {
+ msgBuf.append("\n\n");
+ msgBuf.append(message);
+ }
+ cmsg.setMessage(msgBuf.toString());
+
+ Boolean dbSuccess = db.run(new OrmRunnable<Boolean, ReviewDb>() {
+ public Boolean run(ReviewDb db, Transaction txn, boolean retry)
+ throws OrmException {
+ return doAbandonChange(message, change, patchSetId, cmsg, db, txn);
+ }
+ });
+
+ if (dbSuccess) {
+ // Email the reviewers
+ try {
+ final ChangeMail cm = new ChangeMail(server, change);
+ cm.setFrom(me);
+ cm.setReviewDb(db);
+ cm.setChangeMessage(cmsg);
+ cm.setHttpServletRequest(GerritJsonServlet.getCurrentCall()
+ .getHttpServletRequest());
+ cm.sendAbandoned();
+ } catch (MessagingException e) {
+ log.error("Cannot send abandon change email for change "
+ + change.getChangeId(), e);
+ throw new Failure(e);
+ }
+ }
+
+ return VoidResult.INSTANCE;
+ }
+ });
+ }
+
+ private Boolean doAbandonChange(final String message, final Change change,
+ final PatchSet.Id psid, final ChangeMessage cm, final ReviewDb db,
+ final Transaction txn) throws OrmException {
+
+ // Check to make sure the change status and current patchset ID haven't
+ // changed while the user was typing an abandon message
+ if (change.getStatus() == Change.Status.NEW
+ && change.currentPatchSetId().equals(psid)) {
+ change.setStatus(Change.Status.ABANDONED);
+ ChangeUtil.updated(change);
+
+ final List<ChangeApproval> approvals =
+ db.changeApprovals().byChange(change.getId()).toList();
+ for (ChangeApproval a : approvals) {
+ a.cache(change);
+ }
+ db.changeApprovals().update(approvals, txn);
+
+ db.changeMessages().insert(Collections.singleton(cm), txn);
+ db.changes().update(Collections.singleton(change), txn);
+ return Boolean.TRUE;
+ }
+
+ return Boolean.FALSE;
+ }
}