Merge changes I36da735c,I89dde294
* changes:
ChangeRebuilderImpl: Support deleting patch set 1 of a change
DeleteDraftPatchSetIT: Convert to extension API
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftPatchSetIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftPatchSetIT.java
index cda2c31..cc66394 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftPatchSetIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftPatchSetIT.java
@@ -16,18 +16,24 @@
import static com.google.common.truth.Truth.assertThat;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.PushOneCommit.Result;
-import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.RestSession;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.extensions.api.changes.DraftInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
import com.google.gerrit.extensions.client.ChangeStatus;
+import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.ChangeMessage;
+import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.AllUsersName;
@@ -38,6 +44,9 @@
import org.eclipse.jgit.lib.Repository;
import org.junit.Test;
+import java.util.HashMap;
+
+@NoHttpd
public class DeleteDraftPatchSetIT extends AbstractDaemonTest {
@Inject
@@ -51,9 +60,11 @@
ChangeInfo c = get(triplet);
assertThat(c.id).isEqualTo(triplet);
assertThat(c.status).isEqualTo(ChangeStatus.NEW);
- RestResponse r = deletePatchSet(changeId, ps, adminRestSession);
- assertThat(r.getEntityContent()).isEqualTo("Patch set is not a draft");
- r.assertConflict();
+
+ exception.expect(ResourceConflictException.class);
+ exception.expectMessage("Patch set is not a draft");
+ setApiUser(admin);
+ deletePatchSet(changeId, ps);
}
@Test
@@ -64,9 +75,11 @@
ChangeInfo c = get(triplet);
assertThat(c.id).isEqualTo(triplet);
assertThat(c.status).isEqualTo(ChangeStatus.DRAFT);
- RestResponse r = deletePatchSet(changeId, ps, userRestSession);
- assertThat(r.getEntityContent()).isEqualTo("Not found: " + changeId);
- r.assertNotFound();
+
+ exception.expect(ResourceNotFoundException.class);
+ exception.expectMessage("Not found: " + changeId);
+ setApiUser(user);
+ deletePatchSet(changeId, ps);
}
@Test
@@ -87,14 +100,14 @@
assertThat(cd.patchSets()).hasSize(2);
assertThat(cd.change().currentPatchSetId().get()).isEqualTo(2);
assertThat(cd.change().getStatus()).isEqualTo(Change.Status.DRAFT);
- deletePatchSet(changeId, ps, adminRestSession).assertNoContent();
+ deletePatchSet(changeId, ps);
cd = getChange(changeId);
assertThat(cd.patchSets()).hasSize(1);
assertThat(cd.change().currentPatchSetId().get()).isEqualTo(1);
ps = getCurrentPatchSet(changeId);
- deletePatchSet(changeId, ps, adminRestSession).assertNoContent();
+ deletePatchSet(changeId, ps);
assertThat(queryProvider.get().byKeyPrefix(changeId)).isEmpty();
if (notesMigration.writeChanges()) {
@@ -105,6 +118,76 @@
gApi.changes().id(ps.getId().getParentKey().get());
}
+ @Test
+ public void deleteDraftPS1() throws Exception {
+ String changeId = createDraftChangeWith2PS();
+
+ ReviewInput rin = new ReviewInput();
+ rin.message = "Change message";
+ CommentInput cin = new CommentInput();
+ cin.line = 1;
+ cin.patchSet = 1;
+ cin.path = PushOneCommit.FILE_NAME;
+ cin.side = Side.REVISION;
+ cin.message = "Inline comment";
+ rin.comments = new HashMap<>();
+ rin.comments.put(cin.path, ImmutableList.of(cin));
+ gApi.changes().id(changeId).revision(1).review(rin);
+
+ ChangeData cd = getChange(changeId);
+ PatchSet.Id delPsId = new PatchSet.Id(cd.getId(), 1);
+ PatchSet ps = cd.patchSet(delPsId);
+ deletePatchSet(changeId, ps);
+
+ cd = getChange(changeId);
+ assertThat(cd.patchSets()).hasSize(1);
+ assertThat(Iterables.getOnlyElement(cd.patchSets()).getId().get())
+ .isEqualTo(2);
+
+ // Other entities based on deleted patch sets are also deleted.
+ for (ChangeMessage m : cd.messages()) {
+ assertThat(m.getPatchSetId()).named(m.toString()).isNotEqualTo(delPsId);
+ }
+ for (PatchLineComment c : cd.publishedComments()) {
+ assertThat(c.getPatchSetId()).named(c.toString()).isNotEqualTo(delPsId);
+ }
+ }
+
+ @Test
+ public void deleteDraftPS2() throws Exception {
+ String changeId = createDraftChangeWith2PS();
+
+ ReviewInput rin = new ReviewInput();
+ rin.message = "Change message";
+ CommentInput cin = new CommentInput();
+ cin.line = 1;
+ cin.patchSet = 1;
+ cin.path = PushOneCommit.FILE_NAME;
+ cin.side = Side.REVISION;
+ cin.message = "Inline comment";
+ rin.comments = new HashMap<>();
+ rin.comments.put(cin.path, ImmutableList.of(cin));
+ gApi.changes().id(changeId).revision(1).review(rin);
+
+ ChangeData cd = getChange(changeId);
+ PatchSet.Id delPsId = new PatchSet.Id(cd.getId(), 2);
+ PatchSet ps = cd.patchSet(delPsId);
+ deletePatchSet(changeId, ps);
+
+ cd = getChange(changeId);
+ assertThat(cd.patchSets()).hasSize(1);
+ assertThat(Iterables.getOnlyElement(cd.patchSets()).getId().get())
+ .isEqualTo(1);
+
+ // Other entities based on deleted patch sets are also deleted.
+ for (ChangeMessage m : cd.messages()) {
+ assertThat(m.getPatchSetId()).named(m.toString()).isNotEqualTo(delPsId);
+ }
+ for (PatchLineComment c : cd.publishedComments()) {
+ assertThat(c.getPatchSetId()).named(c.toString()).isNotEqualTo(delPsId);
+ }
+ }
+
private Ref getDraftRef(TestAccount account, Change.Id changeId)
throws Exception {
try (Repository repo = repoManager.openRepository(allUsers)) {
@@ -128,11 +211,7 @@
return Iterables.getOnlyElement(queryProvider.get().byKeyPrefix(changeId));
}
- private static RestResponse deletePatchSet(String changeId,
- PatchSet ps, RestSession s) throws Exception {
- return s.delete("/changes/"
- + changeId
- + "/revisions/"
- + ps.getRevision().get());
+ private void deletePatchSet(String changeId, PatchSet ps) throws Exception {
+ gApi.changes().id(changeId).revision(ps.getId().get()).delete();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java
index a3a58a3..53269b2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.notedb;
+import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
@@ -239,8 +240,16 @@
// Delete ref only after hashtags have been read
deleteRef(change, changeMetaRepo, manager.getChangeRepo().cmds);
+ Integer minPsNum = null;
for (PatchSet ps : bundle.getPatchSets()) {
- events.add(new PatchSetEvent(change, ps, manager.getChangeRepo().rw));
+ int n = ps.getId().get();
+ if (minPsNum == null || n < minPsNum) {
+ minPsNum = n;
+ }
+ }
+ for (PatchSet ps : bundle.getPatchSets()) {
+ events.add(new PatchSetEvent(
+ change, ps, checkNotNull(minPsNum), manager.getChangeRepo().rw));
for (PatchLineComment c : getPatchLineComments(bundle, ps)) {
PatchLineCommentEvent e =
new PatchLineCommentEvent(c, change, ps, patchListCache);
@@ -262,7 +271,7 @@
new ChangeMessageEvent(msg, noteDbChange, change.getCreatedOn()));
}
- sortEvents(change.getId(), events);
+ sortEvents(change.getId(), events, minPsNum);
events.add(new FinalUpdatesEvent(change, noteDbChange));
@@ -301,13 +310,21 @@
}).toSortedList(PatchLineCommentsUtil.PLC_ORDER);
}
- private void sortEvents(Change.Id changeId, List<Event> events) {
+ private void sortEvents(Change.Id changeId, List<Event> events,
+ Integer minPsNum) {
Collections.sort(events, EVENT_ORDER);
// Fill in any missing patch set IDs using the latest patch set of the
- // change at the time of the event. This is as if a user added a
+ // change at the time of the event, because NoteDb can't represent actions
+ // with no associated patch set ID. This workaround is as if a user added a
// ChangeMessage on the change by replying from the latest patch set.
- int ps = 1;
+ //
+ // Start with the first patch set that actually exists. If there are no
+ // patch sets at all, minPsNum will be null, so just bail and use 1 as the
+ // patch set ID. The corresponding patch set won't exist, but this change is
+ // probably corrupt anyway, as deleting the last draft patch set should have
+ // deleted the whole change.
+ int ps = firstNonNull(minPsNum, 1);
for (Event e : events) {
if (e.psId == null) {
e.psId = new PatchSet.Id(changeId, ps);
@@ -613,14 +630,16 @@
private static class PatchSetEvent extends Event {
private final Change change;
private final PatchSet ps;
+ private final boolean createChange;
private final RevWalk rw;
- PatchSetEvent(Change change, PatchSet ps, RevWalk rw) {
+ PatchSetEvent(Change change, PatchSet ps, int minPsNum, RevWalk rw) {
super(ps.getId(), ps.getUploader(), ps.getCreatedOn(),
change.getCreatedOn(), null);
this.change = change;
this.ps = ps;
this.rw = rw;
+ this.createChange = ps.getPatchSetId() == minPsNum;
}
@Override
@@ -632,7 +651,7 @@
void apply(ChangeUpdate update) throws IOException, OrmException {
checkUpdate(update);
update.setSubject(change.getSubject());
- if (ps.getPatchSetId() == 1) {
+ if (createChange) {
update.setSubjectForCommit("Create change");
update.setChangeId(change.getKey().get());
update.setBranch(change.getDest().get());