Fix NPE when deleting current patch set and previous patch set doesn't exist

When the current patch set was deleted we used patch set ID - 1 as ID
for the previous patch set but this patch set may not exist (e.g. it was
a draft that was deleted).

Change-Id: Ief0776d131d805ed403fb45bb6112d953dc5564d
Signed-off-by: Edwin Kempin <ekempin@google.com>
This commit is contained in:
Edwin Kempin
2017-05-02 16:20:16 +02:00
parent 6fab6a6b58
commit b283e15c1f
2 changed files with 46 additions and 8 deletions

View File

@@ -187,6 +187,26 @@ public class DeleteDraftPatchSetIT extends AbstractDaemonTest {
} }
} }
@Test
public void deleteCurrentDraftPatchSetWhenPreviousPatchSetDoesNotExist() throws Exception {
PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
String changeId = push.to("refs/for/master").getChangeId();
pushFactory
.create(db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT, "b.txt", "foo", changeId)
.to("refs/drafts/master");
pushFactory
.create(db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT, "b.txt", "bar", changeId)
.to("refs/drafts/master");
deletePatchSet(changeId, 2);
deletePatchSet(changeId, 3);
ChangeData cd = getChange(changeId);
assertThat(cd.patchSets()).hasSize(1);
assertThat(Iterables.getOnlyElement(cd.patchSets()).getId().get()).isEqualTo(1);
assertThat(cd.currentPatchSet().getId().get()).isEqualTo(1);
}
@Test @Test
public void deleteDraftPatchSetAndPushNewDraftPatchSet() throws Exception { public void deleteDraftPatchSetAndPushNewDraftPatchSet() throws Exception {
String ref = "refs/drafts/master"; String ref = "refs/drafts/master";
@@ -263,6 +283,10 @@ public class DeleteDraftPatchSetIT extends AbstractDaemonTest {
} }
private void deletePatchSet(String changeId, PatchSet ps) throws Exception { private void deletePatchSet(String changeId, PatchSet ps) throws Exception {
gApi.changes().id(changeId).revision(ps.getId().get()).delete(); deletePatchSet(changeId, ps.getId().get());
}
private void deletePatchSet(String changeId, int ps) throws Exception {
gApi.changes().id(changeId).revision(ps).delete();
} }
} }

View File

@@ -14,6 +14,8 @@
package com.google.gerrit.server.change; package com.google.gerrit.server.change;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.gerrit.common.TimeUtil; import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.registration.DynamicItem; import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.AuthException;
@@ -45,6 +47,7 @@ import com.google.inject.Provider;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.Map;
import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.transport.ReceiveCommand; import org.eclipse.jgit.transport.ReceiveCommand;
@@ -106,7 +109,8 @@ public class DeleteDraftPatchSet
@Override @Override
public boolean updateChange(ChangeContext ctx) public boolean updateChange(ChangeContext ctx)
throws RestApiException, OrmException, IOException, NoSuchChangeException { throws RestApiException, OrmException, IOException, NoSuchChangeException {
patchSet = psUtil.get(ctx.getDb(), ctx.getNotes(), psId); Map<PatchSet.Id, PatchSet> patchSets = psUtil.byChangeAsMap(db.get(), ctx.getNotes());
patchSet = patchSets.get(psId);
if (patchSet == null) { if (patchSet == null) {
return false; // Nothing to do. return false; // Nothing to do.
} }
@@ -120,9 +124,9 @@ public class DeleteDraftPatchSet
throw new AuthException("Not permitted to delete this draft patch set"); throw new AuthException("Not permitted to delete this draft patch set");
} }
patchSetsBeforeDeletion = psUtil.byChange(ctx.getDb(), ctx.getNotes()); patchSetsBeforeDeletion = patchSets.values();
deleteDraftPatchSet(patchSet, ctx); deleteDraftPatchSet(patchSet, ctx);
deleteOrUpdateDraftChange(ctx); deleteOrUpdateDraftChange(ctx, patchSets);
return true; return true;
} }
@@ -152,7 +156,7 @@ public class DeleteDraftPatchSet
db.patchSetApprovals().delete(db.patchSetApprovals().byPatchSet(psId)); db.patchSetApprovals().delete(db.patchSetApprovals().byPatchSet(psId));
} }
private void deleteOrUpdateDraftChange(ChangeContext ctx) private void deleteOrUpdateDraftChange(ChangeContext ctx, Map<PatchSet.Id, PatchSet> patchSets)
throws OrmException, RestApiException, IOException, NoSuchChangeException { throws OrmException, RestApiException, IOException, NoSuchChangeException {
Change c = ctx.getChange(); Change c = ctx.getChange();
if (deletedOnlyPatchSet()) { if (deletedOnlyPatchSet()) {
@@ -161,7 +165,7 @@ public class DeleteDraftPatchSet
return; return;
} }
if (c.currentPatchSetId().equals(psId)) { if (c.currentPatchSetId().equals(psId)) {
c.setCurrentPatchSet(previousPatchSetInfo(ctx)); c.setCurrentPatchSet(previousPatchSetInfo(ctx, patchSets));
} }
} }
@@ -170,12 +174,22 @@ public class DeleteDraftPatchSet
&& patchSetsBeforeDeletion.iterator().next().getId().equals(psId); && patchSetsBeforeDeletion.iterator().next().getId().equals(psId);
} }
private PatchSetInfo previousPatchSetInfo(ChangeContext ctx) throws OrmException { private PatchSetInfo previousPatchSetInfo(
ChangeContext ctx, Map<PatchSet.Id, PatchSet> patchSets) throws OrmException {
PatchSet.Id prevPsId = null;
for (PatchSet.Id id : patchSets.keySet()) {
if (id.get() < psId.get() && (prevPsId == null || id.get() > prevPsId.get())) {
prevPsId = id;
}
}
try { try {
// TODO(dborowitz): Get this in a way that doesn't involve re-opening // TODO(dborowitz): Get this in a way that doesn't involve re-opening
// the repo after the updateRepo phase. // the repo after the updateRepo phase.
return patchSetInfoFactory.get( return patchSetInfoFactory.get(
ctx.getDb(), ctx.getNotes(), new PatchSet.Id(psId.getParentKey(), psId.get() - 1)); ctx.getDb(),
ctx.getNotes(),
new PatchSet.Id(psId.getParentKey(), checkNotNull(prevPsId).get()));
} catch (PatchSetInfoNotAvailableException e) { } catch (PatchSetInfoNotAvailableException e) {
throw new OrmException(e); throw new OrmException(e);
} }