diff --git a/Documentation/cmd-review.txt b/Documentation/cmd-review.txt index 370cb24e17..ac613e5586 100644 --- a/Documentation/cmd-review.txt +++ b/Documentation/cmd-review.txt @@ -15,6 +15,7 @@ SYNOPSIS [--submit] [--abandon | --restore] [--publish] + [--delete] [--verified ] [--code-review ] {COMMIT | CHANGEID,PATCHSET}... @@ -82,7 +83,11 @@ successfully, even if the label could not be changed. --publish:: Publish the specified draft patch set(s). - (option is mutually exclusive with --submit, --restore, and --abandon) + (option is mutually exclusive with --submit, --restore, --abandon, and --delete) + +--delete:: + Delete the specified draft patch set(s). + (option is mutually exclusive with --submit, --restore, --abandon, and --publish) --code-review:: --verified:: 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 2a7c628d0c..d5865e624f 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 boolean canAbandon; protected boolean canRestore; protected boolean canRevert; + protected boolean canDeleteDraft; protected Change change; protected boolean starred; protected List dependsOn; @@ -94,6 +95,14 @@ public class ChangeDetail { canSubmit = a; } + public boolean canDeleteDraft() { + return canDeleteDraft; + } + + public void setCanDeleteDraft(boolean a) { + canDeleteDraft = 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 3e8c5ebe49..b667877c8c 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 @@ -20,6 +20,7 @@ import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwtjsonrpc.client.RemoteJsonService; import com.google.gwtjsonrpc.client.RpcImpl; import com.google.gwtjsonrpc.client.RpcImpl.Version; +import com.google.gwtjsonrpc.client.VoidResult; @RpcImpl(version = Version.V2_0) public interface ChangeManageService extends RemoteJsonService { @@ -40,4 +41,7 @@ public interface ChangeManageService extends RemoteJsonService { @SignInRequired void publish(PatchSet.Id patchSetId, AsyncCallback callback); + + @SignInRequired + void deleteDraftChange(PatchSet.Id patchSetId, AsyncCallback callback); } diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchDetailService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchDetailService.java index 0899b6fc77..3744ef054b 100644 --- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchDetailService.java +++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchDetailService.java @@ -44,6 +44,9 @@ public interface PatchDetailService extends RemoteJsonService { @SignInRequired void deleteDraft(PatchLineComment.Key key, AsyncCallback callback); + @SignInRequired + void deleteDraftPatchSet(PatchSet.Id psid, AsyncCallback callback); + @SignInRequired void publishComments(PatchSet.Id psid, String message, Set approvals, 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 8f74fe0983..3cddd2b985 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 @@ -140,6 +140,9 @@ public interface ChangeConstants extends Constants { String buttonPublishPatchSet(); + String buttonDeleteDraftChange(); + String buttonDeleteDraftPatchSet(); + 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 ab2f6b8b17..35991f4584 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 @@ -117,6 +117,9 @@ headingPatchComments = Patch Comments: buttonPublishPatchSet = Publish +buttonDeleteDraftChange = Delete Draft Change +buttonDeleteDraftPatchSet = Delete Draft Patch Set + pagedChangeListPrev = ⇦Prev pagedChangeListNext = Next⇨ 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 117728ab0d..4d7795008f 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 @@ -17,6 +17,7 @@ package com.google.gerrit.client.changes; import com.google.gerrit.client.Dispatcher; import com.google.gerrit.client.FormatUtil; import com.google.gerrit.client.Gerrit; +import com.google.gerrit.client.patches.PatchUtil; import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.ui.AccountDashboardLink; import com.google.gerrit.client.ui.ComplexDisclosurePanel; @@ -49,10 +50,11 @@ import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.DisclosurePanel; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.HTMLTable.CellFormatter; import com.google.gwt.user.client.ui.InlineLabel; import com.google.gwt.user.client.ui.Panel; -import com.google.gwt.user.client.ui.HTMLTable.CellFormatter; import com.google.gwtexpui.clippy.client.CopyableLabel; +import com.google.gwtjsonrpc.client.VoidResult; import java.util.HashSet; import java.util.List; @@ -179,6 +181,9 @@ class PatchSetComplexDisclosurePanel extends ComplexDisclosurePanel implements O if (detail.getPatchSet().isDraft()) { populatePublishAction(); } + if (canDeletePatchSet(detail)) { + populateDeleteDraftPatchSetAction(); + } } populateDiffAllActions(detail); body.add(patchTable); @@ -490,6 +495,29 @@ class PatchSetComplexDisclosurePanel extends ComplexDisclosurePanel implements O actionsPanel.add(b); } + if (changeDetail.canDeleteDraft()) { + final Button b = new Button(Util.C.buttonDeleteDraftChange()); + b.addClickHandler(new ClickHandler() { + @Override + public void onClick(final ClickEvent event) { + b.setEnabled(false); + Util.MANAGE_SVC.deleteDraftChange(patchSet.getId(), + new GerritCallback() { + public void onSuccess(VoidResult result) { + Gerrit.display(PageLinks.MINE); + } + + @Override + public void onFailure(Throwable caught) { + b.setEnabled(true); + super.onFailure(caught); + } + }); + } + }); + actionsPanel.add(b); + } + if (changeDetail.canRestore()) { final Button b = new Button(Util.C.buttonRestoreChangeBegin()); b.addClickHandler(new ClickHandler() { @@ -572,6 +600,29 @@ class PatchSetComplexDisclosurePanel extends ComplexDisclosurePanel implements O actionsPanel.add(b); } + private void populateDeleteDraftPatchSetAction() { + final Button b = new Button(Util.C.buttonDeleteDraftPatchSet()); + b.addClickHandler(new ClickHandler() { + @Override + public void onClick(final ClickEvent event) { + b.setEnabled(false); + PatchUtil.DETAIL_SVC.deleteDraftPatchSet(patchSet.getId(), + new GerritCallback() { + public void onSuccess(VoidResult result) { + Gerrit.display(PageLinks.MINE); + } + + @Override + public void onFailure(Throwable caught) { + b.setEnabled(true); + super.onFailure(caught); + } + }); + } + }); + actionsPanel.add(b); + } + public void refresh() { AccountDiffPreference diffPrefs; if (patchTable == null) { @@ -654,6 +705,17 @@ class PatchSetComplexDisclosurePanel extends ComplexDisclosurePanel implements O changeScreen.update(result); } + private boolean canDeletePatchSet(PatchSetDetail detail) { + if (!detail.getPatchSet().isDraft()) { + return false; + } + // If the draft PS is the only one in a draft change, just delete the change. + if (changeDetail.getPatchSets().size() <= 1) { + return false; + } + return true; + } + public PatchSet getPatchSet() { return patchSet; } 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 b9c3fcd0fe..6529f0f4c6 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 @@ -121,8 +121,9 @@ public class ChangeDetailFactory extends Handler { detail.setChange(change); detail.setAllowsAnonymous(control.forUser(anonymousUser).isVisible(db)); - detail.setCanAbandon(change.getStatus().isOpen() && control.canAbandon()); + detail.setCanAbandon(change.getStatus() != Change.Status.DRAFT && change.getStatus().isOpen() && control.canAbandon()); detail.setCanRestore(change.getStatus() == Change.Status.ABANDONED && control.canRestore()); + detail.setCanDeleteDraft(change.getStatus() == Change.Status.DRAFT && control.isOwner()); detail.setStarred(control.getCurrentUser().getStarredChanges().contains( changeId)); 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 81bb092fa2..11434a7c06 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 @@ -18,6 +18,7 @@ import com.google.gerrit.common.data.ChangeDetail; import com.google.gerrit.common.data.ChangeManageService; import com.google.gerrit.reviewdb.PatchSet; import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwtjsonrpc.client.VoidResult; import com.google.inject.Inject; class ChangeManageServiceImpl implements ChangeManageService { @@ -26,18 +27,21 @@ class ChangeManageServiceImpl implements ChangeManageService { private final RestoreChange.Factory restoreChangeFactory; private final RevertChange.Factory revertChangeFactory; private final PublishAction.Factory publishAction; + private final DeleteDraftChange.Factory deleteDraftChangeFactory; @Inject ChangeManageServiceImpl(final SubmitAction.Factory patchSetAction, final AbandonChange.Factory abandonChangeFactory, final RestoreChange.Factory restoreChangeFactory, final RevertChange.Factory revertChangeFactory, - final PublishAction.Factory publishAction) { + final PublishAction.Factory publishAction, + final DeleteDraftChange.Factory deleteDraftChangeFactory) { this.submitAction = patchSetAction; this.abandonChangeFactory = abandonChangeFactory; this.restoreChangeFactory = restoreChangeFactory; this.revertChangeFactory = revertChangeFactory; this.publishAction = publishAction; + this.deleteDraftChangeFactory = deleteDraftChangeFactory; } public void submit(final PatchSet.Id patchSetId, @@ -64,4 +68,9 @@ class ChangeManageServiceImpl implements ChangeManageService { final AsyncCallback callback) { publishAction.create(patchSetId).to(callback); } -} + + public void deleteDraftChange(final PatchSet.Id patchSetId, + final AsyncCallback callback) { + deleteDraftChangeFactory.create(patchSetId).to(callback); + } +} \ No newline at end of file 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 0a76aa307a..34685ec19a 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 @@ -37,6 +37,7 @@ public class ChangeModule extends RpcServletModule { factory(PatchSetPublishDetailFactory.Factory.class); factory(SubmitAction.Factory.class); factory(PublishAction.Factory.class); + factory(DeleteDraftChange.Factory.class); } }); rpc(ChangeDetailServiceImpl.class); diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/DeleteDraftChange.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/DeleteDraftChange.java new file mode 100644 index 0000000000..e389d5f207 --- /dev/null +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/DeleteDraftChange.java @@ -0,0 +1,71 @@ +// 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.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.git.GitRepositoryManager; +import com.google.gerrit.server.git.ReplicationQueue; +import com.google.gerrit.server.project.ChangeControl; +import com.google.gerrit.server.project.NoSuchChangeException; +import com.google.gwtjsonrpc.client.VoidResult; +import com.google.gwtorm.client.OrmException; +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; + +import java.io.IOException; + +class DeleteDraftChange extends Handler { + interface Factory { + DeleteDraftChange create(PatchSet.Id patchSetId); + } + + private final ChangeControl.Factory changeControlFactory; + private final ReviewDb db; + private final GitRepositoryManager gitManager; + private final ReplicationQueue replication; + + private final PatchSet.Id patchSetId; + + @Inject + DeleteDraftChange(final ReviewDb db, + final ChangeControl.Factory changeControlFactory, + final GitRepositoryManager gitManager, + final ReplicationQueue replication, + @Assisted final PatchSet.Id patchSetId) { + this.changeControlFactory = changeControlFactory; + this.db = db; + this.gitManager = gitManager; + this.replication = replication; + + this.patchSetId = patchSetId; + } + + @Override + public VoidResult call() throws NoSuchChangeException, OrmException, IOException { + + final Change.Id changeId = patchSetId.getParentKey(); + final ChangeControl control = changeControlFactory.validateFor(changeId); + if (!control.isOwner()) { + throw new NoSuchChangeException(changeId); + } + + ChangeUtil.deleteDraftChange(patchSetId, gitManager, replication, db); + return VoidResult.INSTANCE; + } +} \ No newline at end of file diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java index cb4ea15c08..6e0e1af30d 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java @@ -35,8 +35,13 @@ import com.google.gerrit.reviewdb.PatchSet; import com.google.gerrit.reviewdb.PatchSetApproval; import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.reviewdb.Patch.Key; +import com.google.gerrit.server.ChangeUtil; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.account.AccountInfoCacheFactory; +import com.google.gerrit.server.git.GitRepositoryManager; +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.patch.PublishComments; import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.NoSuchChangeException; @@ -47,6 +52,7 @@ import com.google.gwtorm.client.OrmException; import com.google.inject.Inject; import com.google.inject.Provider; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -65,6 +71,9 @@ class PatchDetailServiceImpl extends BaseServiceImplementation implements private final PublishComments.Factory publishCommentsFactory; private final PatchScriptFactory.Factory patchScriptFactoryFactory; private final SaveDraft.Factory saveDraftFactory; + private final PatchSetInfoFactory patchSetInfoFactory; + private final GitRepositoryManager gitManager; + private final ReplicationQueue replication; @Inject PatchDetailServiceImpl(final Provider schema, @@ -77,7 +86,10 @@ class PatchDetailServiceImpl extends BaseServiceImplementation implements final FunctionState.Factory functionStateFactory, final PatchScriptFactory.Factory patchScriptFactoryFactory, final PublishComments.Factory publishCommentsFactory, - final SaveDraft.Factory saveDraftFactory) { + final SaveDraft.Factory saveDraftFactory, + final PatchSetInfoFactory patchSetInfoFactory, + final GitRepositoryManager gitManager, + final ReplicationQueue replication) { super(schema, currentUser); this.approvalTypes = approvalTypes; @@ -89,6 +101,9 @@ class PatchDetailServiceImpl extends BaseServiceImplementation implements this.patchScriptFactoryFactory = patchScriptFactoryFactory; this.publishCommentsFactory = publishCommentsFactory; this.saveDraftFactory = saveDraftFactory; + this.patchSetInfoFactory = patchSetInfoFactory; + this.gitManager = gitManager; + this.replication = replication; } public void patchScript(final Patch.Key patchKey, final PatchSet.Id psa, @@ -133,6 +148,28 @@ class PatchDetailServiceImpl extends BaseServiceImplementation implements }); } + public void deleteDraftPatchSet(final PatchSet.Id psid, + final AsyncCallback callback) { + run(callback, new Action() { + public VoidResult run(ReviewDb db) throws OrmException, Failure { + try { + final ChangeControl cc = changeControlFactory.validateFor(psid.getParentKey()); + if (!cc.isOwner()) { + throw new Failure(new NoSuchEntityException()); + } + ChangeUtil.deleteDraftPatchSet(psid, gitManager, replication, patchSetInfoFactory, db); + } catch (NoSuchChangeException e) { + throw new Failure(new NoSuchChangeException(psid.getParentKey())); + } catch (PatchSetInfoNotAvailableException e) { + throw new Failure(e); + } catch (IOException e) { + throw new Failure(e); + } + return VoidResult.INSTANCE; + } + }); + } + public void publishComments(final PatchSet.Id psid, final String msg, final Set tags, final AsyncCallback cb) { diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountPatchReviewAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountPatchReviewAccess.java index 91e8837406..e1fa4d4c76 100644 --- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountPatchReviewAccess.java +++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountPatchReviewAccess.java @@ -28,4 +28,6 @@ public interface AccountPatchReviewAccess @Query("WHERE key.accountId = ? AND key.patchKey.patchSetId = ?") ResultSet byReviewer(Account.Id who, PatchSet.Id ps) throws OrmException; + @Query("WHERE key.patchKey.patchSetId = ?") + ResultSet byPatchSet(PatchSet.Id ps) throws OrmException; } diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Change.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Change.java index e72b3db871..fc544fa21e 100644 --- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Change.java +++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Change.java @@ -478,6 +478,15 @@ public final class Change { ++nbrPatchSets; } + /** + * Reverts to an older PatchSet id within this change. + *

+ * Note: This makes the change dirty. Call update() after. + */ + public void removeLastPatchSetId() { + --nbrPatchSets; + } + public PatchSet.Id currPatchSetId() { return new PatchSet.Id(changeId, nbrPatchSets); } diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ChangeMessageAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ChangeMessageAccess.java index f68f6f3f4d..725c49f211 100644 --- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ChangeMessageAccess.java +++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ChangeMessageAccess.java @@ -28,6 +28,9 @@ public interface ChangeMessageAccess extends @Query("WHERE key.changeId = ? ORDER BY writtenOn") ResultSet byChange(Change.Id id) throws OrmException; + @Query("WHERE patchset = ?") + ResultSet byPatchSet(PatchSet.Id id) throws OrmException; + @Query ResultSet all() throws OrmException; } diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchLineCommentAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchLineCommentAccess.java index fbf38cf712..4a88a329f6 100644 --- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchLineCommentAccess.java +++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchLineCommentAccess.java @@ -28,6 +28,13 @@ public interface PatchLineCommentAccess extends @Query("WHERE key.patchKey.patchSetId.changeId = ?") ResultSet byChange(Change.Id id) throws OrmException; + @Query("WHERE key.patchKey.patchSetId = ?") + ResultSet byPatchSet(PatchSet.Id id) throws OrmException; + + @Query("WHERE key.patchKey = ? AND status = '" + + PatchLineComment.STATUS_PUBLISHED + "' ORDER BY lineNbr,writtenOn") + ResultSet published(Patch.Key patch) throws OrmException; + @Query("WHERE key.patchKey.patchSetId.changeId = ?" + " AND key.patchKey.fileName = ? AND status = '" + PatchLineComment.STATUS_PUBLISHED + "' ORDER BY lineNbr,writtenOn") diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchSetAncestorAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchSetAncestorAccess.java index eeea37218d..838af5d566 100644 --- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchSetAncestorAccess.java +++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchSetAncestorAccess.java @@ -28,6 +28,9 @@ public interface PatchSetAncestorAccess extends @Query("WHERE key.patchSetId = ? ORDER BY key.position") ResultSet ancestorsOf(PatchSet.Id id) throws OrmException; + @Query("WHERE key.patchSetId = ?") + ResultSet byPatchSet(PatchSet.Id id) throws OrmException; + @Query("WHERE ancestorRevision = ?") ResultSet descendantsOf(RevId revision) throws OrmException; 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 f2151e821d..7aca649578 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 @@ -52,6 +52,7 @@ 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.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.FooterLine; import org.eclipse.jgit.revwalk.RevCommit; @@ -440,6 +441,96 @@ public class ChangeUtil { } } + public static void deleteDraftChange(final PatchSet.Id patchSetId, + GitRepositoryManager gitManager, + final ReplicationQueue replication, final ReviewDb db) + throws NoSuchChangeException, OrmException, IOException { + final Change.Id changeId = patchSetId.getParentKey(); + final Change change = db.changes().get(changeId); + if (change == null || change.getStatus() != Change.Status.DRAFT) { + throw new NoSuchChangeException(changeId); + } + + for (PatchSet ps : db.patchSets().byChange(changeId)) { + // These should all be draft patch sets. + deleteOnlyDraftPatchSet(ps, change, gitManager, replication, db); + } + + db.changeMessages().delete(db.changeMessages().byChange(changeId)); + db.starredChanges().delete(db.starredChanges().byChange(changeId)); + db.trackingIds().delete(db.trackingIds().byChange(changeId)); + db.changes().delete(Collections.singleton(change)); + } + + public static void deleteDraftPatchSet(final PatchSet.Id patchSetId, + GitRepositoryManager gitManager, + final ReplicationQueue replication, + final PatchSetInfoFactory patchSetInfoFactory, + final ReviewDb db) throws NoSuchChangeException, OrmException, + PatchSetInfoNotAvailableException, IOException { + final Change.Id changeId = patchSetId.getParentKey(); + final Change change = db.changes().get(changeId); + final PatchSet patch = db.patchSets().get(patchSetId); + + deleteOnlyDraftPatchSet(patch, change, gitManager, replication, db); + + List restOfPatches = db.patchSets().byChange(changeId).toList(); + if (restOfPatches.size() == 0) { + deleteDraftChange(patchSetId, gitManager, replication, db); + } else { + PatchSet.Id highestId = null; + for (PatchSet ps : restOfPatches) { + if (highestId == null || ps.getPatchSetId() > highestId.get()) { + highestId = ps.getId(); + } + } + if (change.currentPatchSetId().equals(patchSetId)) { + change.removeLastPatchSetId(); + change.setCurrentPatchSet(patchSetInfoFactory.get(db, change.currPatchSetId())); + db.changes().update(Collections.singleton(change)); + } + } + } + + private static void deleteOnlyDraftPatchSet(final PatchSet patch, + final Change change, GitRepositoryManager gitManager, + final ReplicationQueue replication, final ReviewDb db) + throws NoSuchChangeException, OrmException, IOException { + final PatchSet.Id patchSetId = patch.getId(); + if (patch == null || !patch.isDraft()) { + throw new NoSuchChangeException(patchSetId.getParentKey()); + } + + Repository repo = gitManager.openRepository(change.getProject()); + try { + RefUpdate update = repo.updateRef(patch.getRefName()); + update.setForceUpdate(true); + update.disableRefLog(); + switch (update.delete()) { + case NEW: + case FAST_FORWARD: + case FORCED: + case NO_CHANGE: + // Successful deletion. + break; + default: + throw new IOException("Failed to delete ref " + patch.getRefName() + + " in " + repo.getDirectory() + ": " + update.getResult()); + } + replication.scheduleUpdate(change.getProject(), update.getName()); + } finally { + repo.close(); + } + + db.accountPatchReviews().delete(db.accountPatchReviews().byPatchSet(patchSetId)); + db.changeMessages().delete(db.changeMessages().byPatchSet(patchSetId)); + db.patchComments().delete(db.patchComments().byPatchSet(patchSetId)); + db.patchSetApprovals().delete(db.patchSetApprovals().byPatchSet(patchSetId)); + db.patchSetAncestors().delete(db.patchSetAncestors().byPatchSet(patchSetId)); + + db.patchSets().delete(Collections.singleton(patch)); + } + private static void updatedChange( final ReviewDb db, final IdentifiedUser user, final Change change, final ChangeMessage cmsg, ReplyToChangeSender.Factory senderFactory, diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java index 3db273d7cf..7f7f290570 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java @@ -28,11 +28,15 @@ import com.google.gerrit.reviewdb.RevId; import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.server.ChangeUtil; import com.google.gerrit.server.IdentifiedUser; +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.mail.AbandonedSender; import com.google.gerrit.server.mail.EmailException; import com.google.gerrit.server.mail.RestoredSender; +import com.google.gerrit.server.patch.PatchSetInfoFactory; +import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException; import com.google.gerrit.server.patch.PublishComments; import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.InvalidChangeOperationException; @@ -107,6 +111,9 @@ public class ReviewCommand extends BaseCommand { @Option(name = "--publish", usage = "publish a draft patch set") private boolean publishPatchSet; + @Option(name = "--delete", usage = "delete a draft patch set") + private boolean deleteDraftPatchSet; + @Inject private ReviewDb db; @@ -140,6 +147,15 @@ public class ReviewCommand extends BaseCommand { @Inject private ChangeHookRunner hooks; + @Inject + private GitRepositoryManager gitManager; + + @Inject + private ReplicationQueue replication; + + @Inject + private PatchSetInfoFactory patchSetInfoFactory; + private List optionList; private Set toSubmit = new HashSet(); @@ -161,6 +177,9 @@ public class ReviewCommand extends BaseCommand { if (publishPatchSet) { throw error("abandon and publish actions are mutually exclusive"); } + if (deleteDraftPatchSet) { + throw error("abandon and delete actions are mutually exclusive"); + } } if (publishPatchSet) { if (restoreChange) { @@ -169,6 +188,9 @@ public class ReviewCommand extends BaseCommand { if (submitChange) { throw error("publish and submit actions are mutually exclusive"); } + if (deleteDraftPatchSet) { + throw error("publish and delete actions are mutually exclusive"); + } } boolean ok = true; @@ -336,6 +358,19 @@ public class ReviewCommand extends BaseCommand { throw error("Not permitted to publish draft patchset"); } } + if (deleteDraftPatchSet) { + if (changeControl.isOwner() && changeControl.isVisible(db)) { + try { + ChangeUtil.deleteDraftPatchSet(patchSetId, gitManager, replication, patchSetInfoFactory, db); + } catch (PatchSetInfoNotAvailableException e) { + throw error("Error retrieving draft patchset: " + patchSetId); + } catch (IOException e) { + throw error("Error deleting draft patchset: " + patchSetId); + } + } else { + throw error("Not permitted to delete draft patchset"); + } + } } private Set parsePatchSetId(final String patchIdentity)