Allow deletion of open and abandoned changes
If a user includes some sensitive information in a change, admins need to be able to delete it. Previously, only draft changes could be deleted. Deleting merged changes isn't supported because we would need to rewrite history. Feature: Issue 4509 Change-Id: I6a5bf055257b3762271c61fdd82a349e24148be3
This commit is contained in:
@@ -1767,13 +1767,19 @@ Publishes a draft change.
|
|||||||
HTTP/1.1 204 No Content
|
HTTP/1.1 204 No Content
|
||||||
----
|
----
|
||||||
|
|
||||||
[[delete-draft-change]]
|
[[delete-change]]
|
||||||
=== Delete Draft Change
|
=== Delete Change
|
||||||
--
|
--
|
||||||
'DELETE /changes/link:#change-id[\{change-id\}]'
|
'DELETE /changes/link:#change-id[\{change-id\}]'
|
||||||
--
|
--
|
||||||
|
|
||||||
Deletes a draft change.
|
Deletes a change.
|
||||||
|
|
||||||
|
New or abandoned changes can only be deleted by administrators. The deletion of
|
||||||
|
merged changes isn't supported at the moment. Draft changes can only be deleted
|
||||||
|
by their owner or other users who have the permissions to view and delete
|
||||||
|
drafts. If the draft workflow is disabled, only administrators with those
|
||||||
|
permissions may delete draft changes.
|
||||||
|
|
||||||
.Request
|
.Request
|
||||||
----
|
----
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ import com.google.gerrit.extensions.common.MergeInput;
|
|||||||
import com.google.gerrit.extensions.common.MergePatchSetInput;
|
import com.google.gerrit.extensions.common.MergePatchSetInput;
|
||||||
import com.google.gerrit.extensions.common.RevisionInfo;
|
import com.google.gerrit.extensions.common.RevisionInfo;
|
||||||
import com.google.gerrit.extensions.restapi.AuthException;
|
import com.google.gerrit.extensions.restapi.AuthException;
|
||||||
|
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
|
||||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||||
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
||||||
@@ -398,7 +399,7 @@ public class ChangeIT extends AbstractDaemonTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void delete() throws Exception {
|
public void deleteDraftChange() throws Exception {
|
||||||
PushOneCommit.Result r = createChange("refs/drafts/master");
|
PushOneCommit.Result r = createChange("refs/drafts/master");
|
||||||
assertThat(query(r.getChangeId())).hasSize(1);
|
assertThat(query(r.getChangeId())).hasSize(1);
|
||||||
assertThat(info(r.getChangeId()).status).isEqualTo(ChangeStatus.DRAFT);
|
assertThat(info(r.getChangeId()).status).isEqualTo(ChangeStatus.DRAFT);
|
||||||
@@ -408,6 +409,110 @@ public class ChangeIT extends AbstractDaemonTest {
|
|||||||
assertThat(query(r.getChangeId())).isEmpty();
|
assertThat(query(r.getChangeId())).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deleteNewChangeAsAdmin() throws Exception {
|
||||||
|
PushOneCommit.Result changeResult = createChange();
|
||||||
|
String changeId = changeResult.getChangeId();
|
||||||
|
|
||||||
|
gApi.changes()
|
||||||
|
.id(changeId)
|
||||||
|
.delete();
|
||||||
|
|
||||||
|
assertThat(query(changeId)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestProjectInput(cloneAs = "user")
|
||||||
|
public void deleteNewChangeAsNormalUser() throws Exception {
|
||||||
|
PushOneCommit.Result changeResult =
|
||||||
|
pushFactory.create(db, user.getIdent(), testRepo)
|
||||||
|
.to("refs/for/master");
|
||||||
|
String changeId = changeResult.getChangeId();
|
||||||
|
Change.Id id = changeResult.getChange().getId();
|
||||||
|
|
||||||
|
setApiUser(user);
|
||||||
|
exception.expect(AuthException.class);
|
||||||
|
exception.expectMessage(String.format(
|
||||||
|
"Deleting change %s is not permitted", id));
|
||||||
|
gApi.changes()
|
||||||
|
.id(changeId)
|
||||||
|
.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestProjectInput(cloneAs = "user")
|
||||||
|
public void deleteNewChangeOfAnotherUserAsAdmin() throws Exception {
|
||||||
|
PushOneCommit.Result changeResult =
|
||||||
|
pushFactory.create(db, user.getIdent(), testRepo)
|
||||||
|
.to("refs/for/master");
|
||||||
|
changeResult.assertOkStatus();
|
||||||
|
String changeId = changeResult.getChangeId();
|
||||||
|
|
||||||
|
setApiUser(admin);
|
||||||
|
gApi.changes()
|
||||||
|
.id(changeId)
|
||||||
|
.delete();
|
||||||
|
|
||||||
|
assertThat(query(changeId)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestProjectInput(cloneAs = "user")
|
||||||
|
public void deleteAbandonedChangeAsNormalUser() throws Exception {
|
||||||
|
PushOneCommit.Result changeResult =
|
||||||
|
pushFactory.create(db, user.getIdent(), testRepo)
|
||||||
|
.to("refs/for/master");
|
||||||
|
String changeId = changeResult.getChangeId();
|
||||||
|
Change.Id id = changeResult.getChange().getId();
|
||||||
|
|
||||||
|
setApiUser(user);
|
||||||
|
gApi.changes()
|
||||||
|
.id(changeId)
|
||||||
|
.abandon();
|
||||||
|
|
||||||
|
exception.expect(AuthException.class);
|
||||||
|
exception.expectMessage(String.format(
|
||||||
|
"Deleting change %s is not permitted", id));
|
||||||
|
gApi.changes()
|
||||||
|
.id(changeId)
|
||||||
|
.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestProjectInput(cloneAs = "user")
|
||||||
|
public void deleteAbandonedChangeOfAnotherUserAsAdmin() throws Exception {
|
||||||
|
PushOneCommit.Result changeResult =
|
||||||
|
pushFactory.create(db, user.getIdent(), testRepo)
|
||||||
|
.to("refs/for/master");
|
||||||
|
String changeId = changeResult.getChangeId();
|
||||||
|
|
||||||
|
gApi.changes()
|
||||||
|
.id(changeId)
|
||||||
|
.abandon();
|
||||||
|
|
||||||
|
gApi.changes()
|
||||||
|
.id(changeId)
|
||||||
|
.delete();
|
||||||
|
|
||||||
|
assertThat(query(changeId)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deleteMergedChange() throws Exception {
|
||||||
|
PushOneCommit.Result changeResult = createChange();
|
||||||
|
String changeId = changeResult.getChangeId();
|
||||||
|
Change.Id id = changeResult.getChange().getId();
|
||||||
|
|
||||||
|
merge(changeResult);
|
||||||
|
|
||||||
|
exception.expect(MethodNotAllowedException.class);
|
||||||
|
exception.expectMessage(String.format(
|
||||||
|
"Deleting merged change %s is not allowed", id));
|
||||||
|
gApi.changes()
|
||||||
|
.id(changeId)
|
||||||
|
.delete();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void rebaseUpToDateChange() throws Exception {
|
public void rebaseUpToDateChange() throws Exception {
|
||||||
PushOneCommit.Result r = createChange();
|
PushOneCommit.Result r = createChange();
|
||||||
|
|||||||
@@ -22,20 +22,38 @@ import com.google.gerrit.acceptance.AbstractDaemonTest;
|
|||||||
import com.google.gerrit.acceptance.PushOneCommit;
|
import com.google.gerrit.acceptance.PushOneCommit;
|
||||||
import com.google.gerrit.acceptance.RestResponse;
|
import com.google.gerrit.acceptance.RestResponse;
|
||||||
import com.google.gerrit.acceptance.RestSession;
|
import com.google.gerrit.acceptance.RestSession;
|
||||||
|
import com.google.gerrit.acceptance.TestProjectInput;
|
||||||
|
import com.google.gerrit.common.TimeUtil;
|
||||||
|
import com.google.gerrit.common.data.Permission;
|
||||||
import com.google.gerrit.extensions.api.changes.ReviewInput;
|
import com.google.gerrit.extensions.api.changes.ReviewInput;
|
||||||
import com.google.gerrit.extensions.client.ChangeStatus;
|
import com.google.gerrit.extensions.client.ChangeStatus;
|
||||||
|
import com.google.gerrit.extensions.client.ListChangesOption;
|
||||||
import com.google.gerrit.extensions.client.ReviewerState;
|
import com.google.gerrit.extensions.client.ReviewerState;
|
||||||
import com.google.gerrit.extensions.common.AccountInfo;
|
import com.google.gerrit.extensions.common.AccountInfo;
|
||||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||||
import com.google.gerrit.extensions.common.LabelInfo;
|
import com.google.gerrit.extensions.common.LabelInfo;
|
||||||
|
import com.google.gerrit.extensions.common.RevisionInfo;
|
||||||
|
import com.google.gerrit.extensions.restapi.AuthException;
|
||||||
|
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
|
||||||
|
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||||
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
|
import com.google.gerrit.server.git.BatchUpdate;
|
||||||
|
import com.google.gerrit.server.git.UpdateException;
|
||||||
|
import com.google.gerrit.server.notedb.PatchSetState;
|
||||||
import com.google.gerrit.testutil.ConfigSuite;
|
import com.google.gerrit.testutil.ConfigSuite;
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
import org.eclipse.jgit.lib.Config;
|
import org.eclipse.jgit.lib.Config;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class DraftChangeIT extends AbstractDaemonTest {
|
public class DraftChangeIT extends AbstractDaemonTest {
|
||||||
@ConfigSuite.Config
|
@ConfigSuite.Config
|
||||||
@@ -43,20 +61,8 @@ public class DraftChangeIT extends AbstractDaemonTest {
|
|||||||
return allowDraftsDisabledConfig();
|
return allowDraftsDisabledConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Inject
|
||||||
public void deleteChange() throws Exception {
|
private BatchUpdate.Factory updateFactory;
|
||||||
PushOneCommit.Result result = createChange();
|
|
||||||
result.assertOkStatus();
|
|
||||||
String changeId = result.getChangeId();
|
|
||||||
String triplet = project.get() + "~master~" + changeId;
|
|
||||||
ChangeInfo c = get(triplet);
|
|
||||||
assertThat(c.id).isEqualTo(triplet);
|
|
||||||
assertThat(c.status).isEqualTo(ChangeStatus.NEW);
|
|
||||||
RestResponse response = deleteChange(changeId, adminRestSession);
|
|
||||||
assertThat(response.getEntityContent())
|
|
||||||
.isEqualTo("Change is not a draft: " + c._number);
|
|
||||||
response.assertConflict();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void deleteDraftChange() throws Exception {
|
public void deleteDraftChange() throws Exception {
|
||||||
@@ -74,6 +80,104 @@ public class DraftChangeIT extends AbstractDaemonTest {
|
|||||||
get(triplet);
|
get(triplet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deleteDraftChangeOfAnotherUser() throws Exception {
|
||||||
|
assume().that(isAllowDrafts()).isTrue();
|
||||||
|
PushOneCommit.Result changeResult = createDraftChange();
|
||||||
|
changeResult.assertOkStatus();
|
||||||
|
String changeId = changeResult.getChangeId();
|
||||||
|
Change.Id id = changeResult.getChange().getId();
|
||||||
|
|
||||||
|
// The user needs to be able to see the draft change (which reviewers can).
|
||||||
|
gApi.changes()
|
||||||
|
.id(changeId)
|
||||||
|
.addReviewer(user.fullName);
|
||||||
|
|
||||||
|
setApiUser(user);
|
||||||
|
exception.expect(AuthException.class);
|
||||||
|
exception.expectMessage(String.format(
|
||||||
|
"Deleting change %s is not permitted", id));
|
||||||
|
gApi.changes()
|
||||||
|
.id(changeId)
|
||||||
|
.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestProjectInput(cloneAs = "user")
|
||||||
|
public void deleteDraftChangeWhenDraftsNotAllowedAsNormalUser()
|
||||||
|
throws Exception {
|
||||||
|
assume().that(isAllowDrafts()).isFalse();
|
||||||
|
|
||||||
|
setApiUser(user);
|
||||||
|
// We can't create a draft change while the draft workflow is disabled.
|
||||||
|
// For this reason, we create a normal change and modify the database.
|
||||||
|
PushOneCommit.Result changeResult =
|
||||||
|
pushFactory.create(db, user.getIdent(), testRepo)
|
||||||
|
.to("refs/for/master");
|
||||||
|
Change.Id id = changeResult.getChange().getId();
|
||||||
|
markChangeAsDraft(id);
|
||||||
|
setDraftStatusOfPatchSetsOfChange(id, true);
|
||||||
|
|
||||||
|
String changeId = changeResult.getChangeId();
|
||||||
|
exception.expect(MethodNotAllowedException.class);
|
||||||
|
exception.expectMessage("Draft workflow is disabled");
|
||||||
|
gApi.changes()
|
||||||
|
.id(changeId)
|
||||||
|
.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestProjectInput(cloneAs = "user")
|
||||||
|
public void deleteDraftChangeWhenDraftsNotAllowedAsAdmin() throws Exception {
|
||||||
|
assume().that(isAllowDrafts()).isFalse();
|
||||||
|
|
||||||
|
setApiUser(user);
|
||||||
|
// We can't create a draft change while the draft workflow is disabled.
|
||||||
|
// For this reason, we create a normal change and modify the database.
|
||||||
|
PushOneCommit.Result changeResult =
|
||||||
|
pushFactory.create(db, user.getIdent(), testRepo)
|
||||||
|
.to("refs/for/master");
|
||||||
|
Change.Id id = changeResult.getChange().getId();
|
||||||
|
markChangeAsDraft(id);
|
||||||
|
setDraftStatusOfPatchSetsOfChange(id, true);
|
||||||
|
|
||||||
|
String changeId = changeResult.getChangeId();
|
||||||
|
|
||||||
|
// Grant those permissions to admins.
|
||||||
|
grant(Permission.VIEW_DRAFTS, project, "refs/*");
|
||||||
|
grant(Permission.DELETE_DRAFTS, project, "refs/*");
|
||||||
|
|
||||||
|
try {
|
||||||
|
setApiUser(admin);
|
||||||
|
gApi.changes()
|
||||||
|
.id(changeId)
|
||||||
|
.delete();
|
||||||
|
} finally {
|
||||||
|
removePermission(Permission.DELETE_DRAFTS, project, "refs/*");
|
||||||
|
removePermission(Permission.VIEW_DRAFTS, project, "refs/*");
|
||||||
|
}
|
||||||
|
|
||||||
|
setApiUser(user);
|
||||||
|
assertThat(query(changeId)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deleteDraftChangeWithNonDraftPatchSet() throws Exception {
|
||||||
|
assume().that(isAllowDrafts()).isTrue();
|
||||||
|
|
||||||
|
PushOneCommit.Result changeResult = createDraftChange();
|
||||||
|
Change.Id id = changeResult.getChange().getId();
|
||||||
|
setDraftStatusOfPatchSetsOfChange(id, false);
|
||||||
|
|
||||||
|
String changeId = changeResult.getChangeId();
|
||||||
|
exception.expect(ResourceConflictException.class);
|
||||||
|
exception.expectMessage(String.format(
|
||||||
|
"Cannot delete draft change %s: patch set 1 is not a draft", id));
|
||||||
|
gApi.changes()
|
||||||
|
.id(changeId)
|
||||||
|
.delete();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void publishDraftChange() throws Exception {
|
public void publishDraftChange() throws Exception {
|
||||||
assume().that(isAllowDrafts()).isTrue();
|
assume().that(isAllowDrafts()).isTrue();
|
||||||
@@ -160,4 +264,92 @@ public class DraftChangeIT extends AbstractDaemonTest {
|
|||||||
+ patchSet.getRevision().get()
|
+ patchSet.getRevision().get()
|
||||||
+ "/publish");
|
+ "/publish");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void markChangeAsDraft(Change.Id id) throws OrmException,
|
||||||
|
RestApiException, UpdateException {
|
||||||
|
try (BatchUpdate batchUpdate = updateFactory
|
||||||
|
.create(db, project, atrScope.get().getUser(), TimeUtil.nowTs())) {
|
||||||
|
batchUpdate.addOp(id, new MarkChangeAsDraftUpdateOp());
|
||||||
|
batchUpdate.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
ChangeStatus changeStatus = gApi.changes()
|
||||||
|
.id(id.get())
|
||||||
|
.get()
|
||||||
|
.status;
|
||||||
|
assertThat(changeStatus).isEqualTo(ChangeStatus.DRAFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setDraftStatusOfPatchSetsOfChange(Change.Id id,
|
||||||
|
boolean draftStatus) throws OrmException, RestApiException,
|
||||||
|
UpdateException {
|
||||||
|
try (BatchUpdate batchUpdate = updateFactory
|
||||||
|
.create(db, project, atrScope.get().getUser(), TimeUtil.nowTs())) {
|
||||||
|
batchUpdate.addOp(id, new DraftStatusOfPatchSetsUpdateOp(draftStatus));
|
||||||
|
batchUpdate.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean expectedDraftStatus = draftStatus ? Boolean.TRUE : null;
|
||||||
|
List<Boolean> patchSetDraftStatuses = getPatchSetDraftStatuses(id);
|
||||||
|
patchSetDraftStatuses.forEach(status ->
|
||||||
|
assertThat(status).isEqualTo(expectedDraftStatus));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Boolean> getPatchSetDraftStatuses(Change.Id id)
|
||||||
|
throws RestApiException {
|
||||||
|
Collection<RevisionInfo> revisionInfos = gApi.changes()
|
||||||
|
.id(id.get())
|
||||||
|
.get(EnumSet.of(ListChangesOption.ALL_REVISIONS))
|
||||||
|
.revisions
|
||||||
|
.values();
|
||||||
|
return revisionInfos.stream()
|
||||||
|
.map(revisionInfo -> revisionInfo.draft)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MarkChangeAsDraftUpdateOp extends BatchUpdate.Op {
|
||||||
|
@Override
|
||||||
|
public boolean updateChange(BatchUpdate.ChangeContext ctx)
|
||||||
|
throws Exception {
|
||||||
|
Change change = ctx.getChange();
|
||||||
|
|
||||||
|
// Change status in database.
|
||||||
|
change.setStatus(Change.Status.DRAFT);
|
||||||
|
|
||||||
|
// Change status in NoteDb.
|
||||||
|
PatchSet.Id currentPatchSetId = change.currentPatchSetId();
|
||||||
|
ctx.getUpdate(currentPatchSetId).setStatus(Change.Status.DRAFT);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DraftStatusOfPatchSetsUpdateOp extends BatchUpdate.Op {
|
||||||
|
private final boolean draftStatus;
|
||||||
|
|
||||||
|
DraftStatusOfPatchSetsUpdateOp(boolean draftStatus) {
|
||||||
|
this.draftStatus = draftStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateChange(BatchUpdate.ChangeContext ctx)
|
||||||
|
throws Exception {
|
||||||
|
Collection<PatchSet> patchSets = psUtil.byChange(db, ctx.getNotes());
|
||||||
|
|
||||||
|
// Change status in database.
|
||||||
|
patchSets.forEach(patchSet -> patchSet.setDraft(draftStatus));
|
||||||
|
db.patchSets().update(patchSets);
|
||||||
|
|
||||||
|
// Change status in NoteDb.
|
||||||
|
PatchSetState patchSetState = draftStatus ? PatchSetState.DRAFT
|
||||||
|
: PatchSetState.PUBLISHED;
|
||||||
|
patchSets.stream()
|
||||||
|
.map(PatchSet::getId)
|
||||||
|
.map(ctx::getUpdate)
|
||||||
|
.forEach(changeUpdate ->
|
||||||
|
changeUpdate.setPatchSetState(patchSetState));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ public interface ChangeApi {
|
|||||||
void publish() throws RestApiException;
|
void publish() throws RestApiException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a draft change.
|
* Deletes a change.
|
||||||
*/
|
*/
|
||||||
void delete() throws RestApiException;
|
void delete() throws RestApiException;
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,6 @@ public interface ChangeConstants extends Constants {
|
|||||||
String abandoned();
|
String abandoned();
|
||||||
|
|
||||||
String deleteChangeEdit();
|
String deleteChangeEdit();
|
||||||
String deleteDraftChange();
|
String deleteChange();
|
||||||
String deleteDraftRevision();
|
String deleteDraftRevision();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,5 +34,5 @@ abandoned = Abandoned
|
|||||||
deleteChangeEdit = Delete Change Edit?\n\
|
deleteChangeEdit = Delete Change Edit?\n\
|
||||||
\n\
|
\n\
|
||||||
All changes made in the edit revision will be lost.
|
All changes made in the edit revision will be lost.
|
||||||
deleteDraftChange = Delete Draft Change?
|
deleteChange = Delete Change?
|
||||||
deleteDraftRevision = Delete Draft Revision?
|
deleteDraftRevision = Delete Draft Revision?
|
||||||
|
|||||||
@@ -495,15 +495,13 @@ public class ChangeScreen extends Screen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initChangeAction(ChangeInfo info) {
|
private void initChangeAction(ChangeInfo info) {
|
||||||
if (info.status() == Status.DRAFT) {
|
NativeMap<ActionInfo> actions = info.hasActions()
|
||||||
NativeMap<ActionInfo> actions = info.hasActions()
|
? info.actions()
|
||||||
? info.actions()
|
: NativeMap.create();
|
||||||
: NativeMap.<ActionInfo> create();
|
actions.copyKeysIntoChildren("id");
|
||||||
actions.copyKeysIntoChildren("id");
|
if (actions.containsKey("/")) {
|
||||||
if (actions.containsKey("/")) {
|
deleteChange.setVisible(true);
|
||||||
deleteChange.setVisible(true);
|
deleteChange.setTitle(actions.get("/").title());
|
||||||
deleteChange.setTitle(actions.get("/").title());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -679,7 +677,7 @@ public class ChangeScreen extends Screen {
|
|||||||
|
|
||||||
@UiHandler("deleteChange")
|
@UiHandler("deleteChange")
|
||||||
void onDeleteChange(@SuppressWarnings("unused") ClickEvent e) {
|
void onDeleteChange(@SuppressWarnings("unused") ClickEvent e) {
|
||||||
if (Window.confirm(Resources.C.deleteDraftChange())) {
|
if (Window.confirm(Resources.C.deleteChange())) {
|
||||||
DraftActions.delete(changeId, publish, deleteRevision, deleteChange);
|
DraftActions.delete(changeId, publish, deleteRevision, deleteChange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ import com.google.gerrit.server.change.ChangeResource;
|
|||||||
import com.google.gerrit.server.change.Check;
|
import com.google.gerrit.server.change.Check;
|
||||||
import com.google.gerrit.server.change.CreateMergePatchSet;
|
import com.google.gerrit.server.change.CreateMergePatchSet;
|
||||||
import com.google.gerrit.server.change.DeleteAssignee;
|
import com.google.gerrit.server.change.DeleteAssignee;
|
||||||
import com.google.gerrit.server.change.DeleteDraftChange;
|
import com.google.gerrit.server.change.DeleteChange;
|
||||||
import com.google.gerrit.server.change.GetAssignee;
|
import com.google.gerrit.server.change.GetAssignee;
|
||||||
import com.google.gerrit.server.change.GetHashtags;
|
import com.google.gerrit.server.change.GetHashtags;
|
||||||
import com.google.gerrit.server.change.GetPastAssignees;
|
import com.google.gerrit.server.change.GetPastAssignees;
|
||||||
@@ -98,7 +98,7 @@ class ChangeApiImpl implements ChangeApi {
|
|||||||
private final Provider<SubmittedTogether> submittedTogether;
|
private final Provider<SubmittedTogether> submittedTogether;
|
||||||
private final PublishDraftPatchSet.CurrentRevision
|
private final PublishDraftPatchSet.CurrentRevision
|
||||||
publishDraftChange;
|
publishDraftChange;
|
||||||
private final DeleteDraftChange deleteDraftChange;
|
private final DeleteChange deleteChange;
|
||||||
private final GetTopic getTopic;
|
private final GetTopic getTopic;
|
||||||
private final PutTopic putTopic;
|
private final PutTopic putTopic;
|
||||||
private final PostReviewers postReviewers;
|
private final PostReviewers postReviewers;
|
||||||
@@ -129,7 +129,7 @@ class ChangeApiImpl implements ChangeApi {
|
|||||||
CreateMergePatchSet updateByMerge,
|
CreateMergePatchSet updateByMerge,
|
||||||
Provider<SubmittedTogether> submittedTogether,
|
Provider<SubmittedTogether> submittedTogether,
|
||||||
PublishDraftPatchSet.CurrentRevision publishDraftChange,
|
PublishDraftPatchSet.CurrentRevision publishDraftChange,
|
||||||
DeleteDraftChange deleteDraftChange,
|
DeleteChange deleteChange,
|
||||||
GetTopic getTopic,
|
GetTopic getTopic,
|
||||||
PutTopic putTopic,
|
PutTopic putTopic,
|
||||||
PostReviewers postReviewers,
|
PostReviewers postReviewers,
|
||||||
@@ -159,7 +159,7 @@ class ChangeApiImpl implements ChangeApi {
|
|||||||
this.updateByMerge = updateByMerge;
|
this.updateByMerge = updateByMerge;
|
||||||
this.submittedTogether = submittedTogether;
|
this.submittedTogether = submittedTogether;
|
||||||
this.publishDraftChange = publishDraftChange;
|
this.publishDraftChange = publishDraftChange;
|
||||||
this.deleteDraftChange = deleteDraftChange;
|
this.deleteChange = deleteChange;
|
||||||
this.getTopic = getTopic;
|
this.getTopic = getTopic;
|
||||||
this.putTopic = putTopic;
|
this.putTopic = putTopic;
|
||||||
this.postReviewers = postReviewers;
|
this.postReviewers = postReviewers;
|
||||||
@@ -324,7 +324,7 @@ class ChangeApiImpl implements ChangeApi {
|
|||||||
@Override
|
@Override
|
||||||
public void delete() throws RestApiException {
|
public void delete() throws RestApiException {
|
||||||
try {
|
try {
|
||||||
deleteDraftChange.apply(change, null);
|
deleteChange.apply(change, null);
|
||||||
} catch (UpdateException e) {
|
} catch (UpdateException e) {
|
||||||
throw new RestApiException("Cannot delete change", e);
|
throw new RestApiException("Cannot delete change", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -658,7 +658,7 @@ public class ConsistencyChecker {
|
|||||||
public boolean updateChange(ChangeContext ctx)
|
public boolean updateChange(ChangeContext ctx)
|
||||||
throws OrmException, PatchSetInfoNotAvailableException {
|
throws OrmException, PatchSetInfoNotAvailableException {
|
||||||
// Delete dangling key references.
|
// Delete dangling key references.
|
||||||
ReviewDb db = DeleteDraftChangeOp.unwrap(ctx.getDb());
|
ReviewDb db = DeleteChangeOp.unwrap(ctx.getDb());
|
||||||
accountPatchReviewStore.get().clearReviewed(psId);
|
accountPatchReviewStore.get().clearReviewed(psId);
|
||||||
db.changeMessages().delete(
|
db.changeMessages().delete(
|
||||||
db.changeMessages().byChange(psId.getParentKey()));
|
db.changeMessages().byChange(psId.getParentKey()));
|
||||||
|
|||||||
@@ -22,10 +22,11 @@ import com.google.gerrit.extensions.webui.UiAction;
|
|||||||
import com.google.gerrit.reviewdb.client.Change;
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
import com.google.gerrit.reviewdb.client.Change.Status;
|
import com.google.gerrit.reviewdb.client.Change.Status;
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
import com.google.gerrit.server.change.DeleteDraftChange.Input;
|
import com.google.gerrit.server.change.DeleteChange.Input;
|
||||||
import com.google.gerrit.server.config.GerritServerConfig;
|
import com.google.gerrit.server.config.GerritServerConfig;
|
||||||
import com.google.gerrit.server.git.BatchUpdate;
|
import com.google.gerrit.server.git.BatchUpdate;
|
||||||
import com.google.gerrit.server.git.UpdateException;
|
import com.google.gerrit.server.git.UpdateException;
|
||||||
|
import com.google.gerrit.server.project.ChangeControl;
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
@@ -34,25 +35,25 @@ import com.google.inject.Singleton;
|
|||||||
import org.eclipse.jgit.lib.Config;
|
import org.eclipse.jgit.lib.Config;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class DeleteDraftChange implements
|
public class DeleteChange implements
|
||||||
RestModifyView<ChangeResource, Input>, UiAction<ChangeResource> {
|
RestModifyView<ChangeResource, Input>, UiAction<ChangeResource> {
|
||||||
public static class Input {
|
public static class Input {
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Provider<ReviewDb> db;
|
private final Provider<ReviewDb> db;
|
||||||
private final BatchUpdate.Factory updateFactory;
|
private final BatchUpdate.Factory updateFactory;
|
||||||
private final Provider<DeleteDraftChangeOp> opProvider;
|
private final Provider<DeleteChangeOp> opProvider;
|
||||||
private final boolean allowDrafts;
|
private final boolean allowDrafts;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public DeleteDraftChange(Provider<ReviewDb> db,
|
public DeleteChange(Provider<ReviewDb> db,
|
||||||
BatchUpdate.Factory updateFactory,
|
BatchUpdate.Factory updateFactory,
|
||||||
Provider<DeleteDraftChangeOp> opProvider,
|
Provider<DeleteChangeOp> opProvider,
|
||||||
@GerritServerConfig Config cfg) {
|
@GerritServerConfig Config cfg) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.updateFactory = updateFactory;
|
this.updateFactory = updateFactory;
|
||||||
this.opProvider = opProvider;
|
this.opProvider = opProvider;
|
||||||
this.allowDrafts = DeleteDraftChangeOp.allowDrafts(cfg);
|
this.allowDrafts = DeleteChangeOp.allowDrafts(cfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -71,14 +72,21 @@ public class DeleteDraftChange implements
|
|||||||
@Override
|
@Override
|
||||||
public UiAction.Description getDescription(ChangeResource rsrc) {
|
public UiAction.Description getDescription(ChangeResource rsrc) {
|
||||||
try {
|
try {
|
||||||
|
Change.Status status = rsrc.getChange().getStatus();
|
||||||
|
ChangeControl changeControl = rsrc.getControl();
|
||||||
|
boolean visible = isActionAllowed(changeControl, status)
|
||||||
|
&& changeControl.canDelete(db.get(), status);
|
||||||
return new UiAction.Description()
|
return new UiAction.Description()
|
||||||
.setLabel("Delete")
|
.setLabel("Delete")
|
||||||
.setTitle("Delete draft change " + rsrc.getId())
|
.setTitle("Delete change " + rsrc.getId())
|
||||||
.setVisible(allowDrafts
|
.setVisible(visible);
|
||||||
&& rsrc.getChange().getStatus() == Status.DRAFT
|
|
||||||
&& rsrc.getControl().canDeleteDraft(db.get()));
|
|
||||||
} catch (OrmException e) {
|
} catch (OrmException e) {
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isActionAllowed(ChangeControl changeControl,
|
||||||
|
Status status) {
|
||||||
|
return status != Status.DRAFT || allowDrafts || changeControl.isAdmin();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -44,7 +44,7 @@ import org.eclipse.jgit.transport.ReceiveCommand;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
class DeleteDraftChangeOp extends BatchUpdate.Op {
|
class DeleteChangeOp extends BatchUpdate.Op {
|
||||||
static boolean allowDrafts(Config cfg) {
|
static boolean allowDrafts(Config cfg) {
|
||||||
return cfg.getBoolean("change", "allowDrafts", true);
|
return cfg.getBoolean("change", "allowDrafts", true);
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,7 @@ class DeleteDraftChangeOp extends BatchUpdate.Op {
|
|||||||
private Change.Id id;
|
private Change.Id id;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
DeleteDraftChangeOp(PatchSetUtil psUtil,
|
DeleteChangeOp(PatchSetUtil psUtil,
|
||||||
StarredChangesUtil starredChangesUtil,
|
StarredChangesUtil starredChangesUtil,
|
||||||
DynamicItem<AccountPatchReviewStore> accountPatchReviewStore,
|
DynamicItem<AccountPatchReviewStore> accountPatchReviewStore,
|
||||||
@GerritServerConfig Config cfg) {
|
@GerritServerConfig Config cfg) {
|
||||||
@@ -82,16 +82,18 @@ class DeleteDraftChangeOp extends BatchUpdate.Op {
|
|||||||
public boolean updateChange(ChangeContext ctx) throws RestApiException,
|
public boolean updateChange(ChangeContext ctx) throws RestApiException,
|
||||||
OrmException, IOException, NoSuchChangeException {
|
OrmException, IOException, NoSuchChangeException {
|
||||||
checkState(ctx.getOrder() == BatchUpdate.Order.DB_BEFORE_REPO,
|
checkState(ctx.getOrder() == BatchUpdate.Order.DB_BEFORE_REPO,
|
||||||
"must use DeleteDraftChangeOp with DB_BEFORE_REPO");
|
"must use DeleteChangeOp with DB_BEFORE_REPO");
|
||||||
checkState(id == null, "cannot reuse DeleteDraftChangeOp");
|
checkState(id == null, "cannot reuse DeleteChangeOp");
|
||||||
|
|
||||||
id = ctx.getChange().getId();
|
id = ctx.getChange().getId();
|
||||||
Collection<PatchSet> patchSets = psUtil.byChange(ctx.getDb(),
|
Collection<PatchSet> patchSets = psUtil.byChange(ctx.getDb(),
|
||||||
ctx.getNotes());
|
ctx.getNotes());
|
||||||
|
|
||||||
ensureDeletable(ctx, id, patchSets);
|
ensureDeletable(ctx, id, patchSets);
|
||||||
deleteChangeElementsFromDb(ctx, id);
|
// Cleaning up is only possible as long as the change and its elements are
|
||||||
|
// still part of the database.
|
||||||
cleanUpReferences(ctx, id, patchSets);
|
cleanUpReferences(ctx, id, patchSets);
|
||||||
|
deleteChangeElementsFromDb(ctx, id);
|
||||||
|
|
||||||
ctx.deleteChange();
|
ctx.deleteChange();
|
||||||
return true;
|
return true;
|
||||||
@@ -100,19 +102,25 @@ class DeleteDraftChangeOp extends BatchUpdate.Op {
|
|||||||
private void ensureDeletable(ChangeContext ctx, Change.Id id,
|
private void ensureDeletable(ChangeContext ctx, Change.Id id,
|
||||||
Collection<PatchSet> patchSets) throws ResourceConflictException,
|
Collection<PatchSet> patchSets) throws ResourceConflictException,
|
||||||
MethodNotAllowedException, OrmException, AuthException {
|
MethodNotAllowedException, OrmException, AuthException {
|
||||||
if (ctx.getChange().getStatus() != Change.Status.DRAFT) {
|
Change.Status status = ctx.getChange().getStatus();
|
||||||
throw new ResourceConflictException("Change is not a draft: " + id);
|
if (status == Change.Status.MERGED) {
|
||||||
|
throw new MethodNotAllowedException("Deleting merged change " + id
|
||||||
|
+ " is not allowed");
|
||||||
}
|
}
|
||||||
if (!allowDrafts) {
|
|
||||||
throw new MethodNotAllowedException("Draft workflow is disabled");
|
if (!ctx.getControl().canDelete(ctx.getDb(), status)) {
|
||||||
|
throw new AuthException("Deleting change " + id + " is not permitted");
|
||||||
}
|
}
|
||||||
if (!ctx.getControl().canDeleteDraft(ctx.getDb())) {
|
|
||||||
throw new AuthException("Not permitted to delete this draft change");
|
if (status == Change.Status.DRAFT) {
|
||||||
}
|
if (!allowDrafts && !ctx.getControl().isAdmin()) {
|
||||||
for (PatchSet ps : patchSets) {
|
throw new MethodNotAllowedException("Draft workflow is disabled");
|
||||||
if (!ps.isDraft()) {
|
}
|
||||||
throw new ResourceConflictException("Cannot delete draft change " + id
|
for (PatchSet ps : patchSets) {
|
||||||
+ ": patch set " + ps.getPatchSetId() + " is not a draft");
|
if (!ps.isDraft()) {
|
||||||
|
throw new ResourceConflictException("Cannot delete draft change " + id
|
||||||
|
+ ": patch set " + ps.getPatchSetId() + " is not a draft");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@ public class DeleteDraftPatchSet implements RestModifyView<RevisionResource, Inp
|
|||||||
private final BatchUpdate.Factory updateFactory;
|
private final BatchUpdate.Factory updateFactory;
|
||||||
private final PatchSetInfoFactory patchSetInfoFactory;
|
private final PatchSetInfoFactory patchSetInfoFactory;
|
||||||
private final PatchSetUtil psUtil;
|
private final PatchSetUtil psUtil;
|
||||||
private final Provider<DeleteDraftChangeOp> deleteChangeOpProvider;
|
private final Provider<DeleteChangeOp> deleteChangeOpProvider;
|
||||||
private final DynamicItem<AccountPatchReviewStore> accountPatchReviewStore;
|
private final DynamicItem<AccountPatchReviewStore> accountPatchReviewStore;
|
||||||
private final boolean allowDrafts;
|
private final boolean allowDrafts;
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ public class DeleteDraftPatchSet implements RestModifyView<RevisionResource, Inp
|
|||||||
BatchUpdate.Factory updateFactory,
|
BatchUpdate.Factory updateFactory,
|
||||||
PatchSetInfoFactory patchSetInfoFactory,
|
PatchSetInfoFactory patchSetInfoFactory,
|
||||||
PatchSetUtil psUtil,
|
PatchSetUtil psUtil,
|
||||||
Provider<DeleteDraftChangeOp> deleteChangeOpProvider,
|
Provider<DeleteChangeOp> deleteChangeOpProvider,
|
||||||
DynamicItem<AccountPatchReviewStore> accountPatchReviewStore,
|
DynamicItem<AccountPatchReviewStore> accountPatchReviewStore,
|
||||||
@GerritServerConfig Config cfg) {
|
@GerritServerConfig Config cfg) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
@@ -97,7 +97,7 @@ public class DeleteDraftPatchSet implements RestModifyView<RevisionResource, Inp
|
|||||||
|
|
||||||
private Collection<PatchSet> patchSetsBeforeDeletion;
|
private Collection<PatchSet> patchSetsBeforeDeletion;
|
||||||
private PatchSet patchSet;
|
private PatchSet patchSet;
|
||||||
private DeleteDraftChangeOp deleteChangeOp;
|
private DeleteChangeOp deleteChangeOp;
|
||||||
|
|
||||||
private Op(PatchSet.Id psId) {
|
private Op(PatchSet.Id psId) {
|
||||||
this.psId = psId;
|
this.psId = psId;
|
||||||
@@ -116,7 +116,7 @@ public class DeleteDraftPatchSet implements RestModifyView<RevisionResource, Inp
|
|||||||
if (!allowDrafts) {
|
if (!allowDrafts) {
|
||||||
throw new MethodNotAllowedException("Draft workflow is disabled");
|
throw new MethodNotAllowedException("Draft workflow is disabled");
|
||||||
}
|
}
|
||||||
if (!ctx.getControl().canDeleteDraft(ctx.getDb())) {
|
if (!ctx.getControl().canDelete(ctx.getDb(), Change.Status.DRAFT)) {
|
||||||
throw new AuthException("Not permitted to delete this draft patch set");
|
throw new AuthException("Not permitted to delete this draft patch set");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,8 +146,8 @@ public class DeleteDraftPatchSet implements RestModifyView<RevisionResource, Inp
|
|||||||
psUtil.delete(ctx.getDb(), ctx.getUpdate(patchSet.getId()), patchSet);
|
psUtil.delete(ctx.getDb(), ctx.getUpdate(patchSet.getId()), patchSet);
|
||||||
|
|
||||||
accountPatchReviewStore.get().clearReviewed(psId);
|
accountPatchReviewStore.get().clearReviewed(psId);
|
||||||
// Use the unwrap from DeleteDraftChangeOp to handle BatchUpdateReviewDb.
|
// Use the unwrap from DeleteChangeOp to handle BatchUpdateReviewDb.
|
||||||
ReviewDb db = DeleteDraftChangeOp.unwrap(ctx.getDb());
|
ReviewDb db = DeleteChangeOp.unwrap(ctx.getDb());
|
||||||
db.changeMessages().delete(db.changeMessages().byPatchSet(psId));
|
db.changeMessages().delete(db.changeMessages().byPatchSet(psId));
|
||||||
db.patchComments().delete(db.patchComments().byPatchSet(psId));
|
db.patchComments().delete(db.patchComments().byPatchSet(psId));
|
||||||
db.patchSetApprovals().delete(db.patchSetApprovals().byPatchSet(psId));
|
db.patchSetApprovals().delete(db.patchSetApprovals().byPatchSet(psId));
|
||||||
@@ -195,7 +195,7 @@ public class DeleteDraftPatchSet implements RestModifyView<RevisionResource, Inp
|
|||||||
rsrc.getPatchSet().getPatchSetId()))
|
rsrc.getPatchSet().getPatchSetId()))
|
||||||
.setVisible(allowDrafts
|
.setVisible(allowDrafts
|
||||||
&& rsrc.getPatchSet().isDraft()
|
&& rsrc.getPatchSet().isDraft()
|
||||||
&& rsrc.getControl().canDeleteDraft(db.get())
|
&& rsrc.getControl().canDelete(db.get(), Change.Status.DRAFT)
|
||||||
&& psCount > 1);
|
&& psCount > 1);
|
||||||
} catch (OrmException e) {
|
} catch (OrmException e) {
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ public class Module extends RestApiModule {
|
|||||||
post(CHANGE_KIND, "check").to(Check.class);
|
post(CHANGE_KIND, "check").to(Check.class);
|
||||||
put(CHANGE_KIND, "topic").to(PutTopic.class);
|
put(CHANGE_KIND, "topic").to(PutTopic.class);
|
||||||
delete(CHANGE_KIND, "topic").to(PutTopic.class);
|
delete(CHANGE_KIND, "topic").to(PutTopic.class);
|
||||||
delete(CHANGE_KIND).to(DeleteDraftChange.class);
|
delete(CHANGE_KIND).to(DeleteChange.class);
|
||||||
post(CHANGE_KIND, "abandon").to(Abandon.class);
|
post(CHANGE_KIND, "abandon").to(Abandon.class);
|
||||||
post(CHANGE_KIND, "hashtags").to(PostHashtags.class);
|
post(CHANGE_KIND, "hashtags").to(PostHashtags.class);
|
||||||
post(CHANGE_KIND, "publish").to(PublishDraftPatchSet.CurrentRevision.class);
|
post(CHANGE_KIND, "publish").to(PublishDraftPatchSet.CurrentRevision.class);
|
||||||
|
|||||||
@@ -261,10 +261,23 @@ public class ChangeControl {
|
|||||||
&& isVisible(db);
|
&& isVisible(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Can this user delete this draft change or any draft patch set of this change? */
|
/** Can this user delete this change or any patch set of this change? */
|
||||||
public boolean canDeleteDraft(final ReviewDb db) throws OrmException {
|
public boolean canDelete(ReviewDb db, Change.Status status)
|
||||||
return (isOwner() || getRefControl().canDeleteDrafts())
|
throws OrmException {
|
||||||
&& isVisible(db);
|
if (!isVisible(db)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case DRAFT:
|
||||||
|
return (isOwner() || getRefControl().canDeleteDrafts());
|
||||||
|
case NEW:
|
||||||
|
case ABANDONED:
|
||||||
|
return isAdmin();
|
||||||
|
case MERGED:
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Can this user rebase this change? */
|
/** Can this user rebase this change? */
|
||||||
@@ -377,6 +390,10 @@ public class ChangeControl {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAdmin() {
|
||||||
|
return getUser().getCapabilities().canAdministrateServer();
|
||||||
|
}
|
||||||
|
|
||||||
/** @return true if the user is allowed to remove this reviewer. */
|
/** @return true if the user is allowed to remove this reviewer. */
|
||||||
public boolean canRemoveReviewer(PatchSetApproval approval) {
|
public boolean canRemoveReviewer(PatchSetApproval approval) {
|
||||||
return canRemoveReviewer(approval.getAccountId(), approval.getValue());
|
return canRemoveReviewer(approval.getAccountId(), approval.getValue());
|
||||||
|
|||||||
Reference in New Issue
Block a user