// Copyright (C) 2013 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.acceptance.rest.change; 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.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.Comment; import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.RefNames; import com.google.gerrit.server.config.AllUsersName; import com.google.gerrit.server.query.change.ChangeData; import com.google.inject.Inject; import java.util.HashMap; import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.junit.Test; @NoHttpd public class DeleteDraftPatchSetIT extends AbstractDaemonTest { @Inject private AllUsersName allUsers; @Test public void deletePatchSetNotDraft() throws Exception { String changeId = createChange().getChangeId(); PatchSet ps = getCurrentPatchSet(changeId); String triplet = project.get() + "~master~" + changeId; ChangeInfo c = get(triplet); assertThat(c.id).isEqualTo(triplet); assertThat(c.status).isEqualTo(ChangeStatus.NEW); exception.expect(ResourceConflictException.class); exception.expectMessage("Patch set is not a draft"); setApiUser(admin); deletePatchSet(changeId, ps); } @Test public void deleteDraftPatchSetNoACL() throws Exception { String changeId = createDraftChangeWith2PS(); PatchSet ps = getCurrentPatchSet(changeId); String triplet = project.get() + "~master~" + changeId; ChangeInfo c = get(triplet); assertThat(c.id).isEqualTo(triplet); assertThat(c.status).isEqualTo(ChangeStatus.DRAFT); exception.expect(ResourceNotFoundException.class); exception.expectMessage("Not found: " + changeId); setApiUser(user); deletePatchSet(changeId, ps); } @Test public void deleteDraftPatchSetAndChange() throws Exception { String changeId = createDraftChangeWith2PS(); PatchSet ps = getCurrentPatchSet(changeId); Change.Id id = ps.getId().getParentKey(); DraftInput din = new DraftInput(); din.path = "a.txt"; din.message = "comment on a.txt"; gApi.changes().id(changeId).current().createDraft(din); if (notesMigration.commitChangeWrites()) { assertThat(getDraftRef(admin, id)).isNotNull(); } ChangeData cd = getChange(changeId); assertThat(cd.patchSets()).hasSize(2); assertThat(cd.change().currentPatchSetId().get()).isEqualTo(2); assertThat(cd.change().getStatus()).isEqualTo(Change.Status.DRAFT); deletePatchSet(changeId, ps); cd = getChange(changeId); assertThat(cd.patchSets()).hasSize(1); assertThat(cd.change().currentPatchSetId().get()).isEqualTo(1); ps = getCurrentPatchSet(changeId); deletePatchSet(changeId, ps); assertThat(queryProvider.get().byKeyPrefix(changeId)).isEmpty(); if (notesMigration.commitChangeWrites()) { assertThat(getDraftRef(admin, id)).isNull(); assertThat(getMetaRef(id)).isNull(); } exception.expect(ResourceNotFoundException.class); gApi.changes().id(id.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 (Comment c : cd.publishedComments()) { assertThat(c.key.patchSetId).named(c.toString()).isNotEqualTo(delPsId.get()); } } @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 (Comment c : cd.publishedComments()) { assertThat(c.key.patchSetId).named(c.toString()).isNotEqualTo(delPsId.get()); } } @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 public void deleteDraftPatchSetAndPushNewDraftPatchSet() throws Exception { String ref = "refs/drafts/master"; // Clone repository TestRepository<InMemoryRepository> testRepo = cloneProject(project, admin); // Create change PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo); PushOneCommit.Result r1 = push.to(ref); r1.assertOkStatus(); String revPs1 = r1.getChange().currentPatchSet().getRevision().get(); // Push draft patch set PushOneCommit.Result r2 = amendChange(r1.getChangeId(), ref, admin, testRepo); r2.assertOkStatus(); String revPs2 = r2.getChange().currentPatchSet().getRevision().get(); assertThat(gApi.changes().id(r1.getChange().getId().get()).get().currentRevision) .isEqualTo(revPs2); // Remove draft patch set gApi.changes().id(r1.getChange().getId().get()).revision(revPs2).delete(); assertThat(gApi.changes().id(r1.getChange().getId().get()).get().currentRevision) .isEqualTo(revPs1); // Push new draft patch set PushOneCommit.Result r3 = amendChange(r1.getChangeId(), ref, admin, testRepo); r3.assertOkStatus(); String revPs3 = r2.getChange().currentPatchSet().getRevision().get(); assertThat(gApi.changes().id(r1.getChange().getId().get()).get().currentRevision) .isEqualTo(revPs3); // Check that all patch sets have different SHA1s assertThat(revPs1).doesNotMatch(revPs2); assertThat(revPs2).doesNotMatch(revPs3); } private Ref getDraftRef(TestAccount account, Change.Id changeId) throws Exception { try (Repository repo = repoManager.openRepository(allUsers)) { return repo.exactRef(RefNames.refsDraftComments(changeId, account.id)); } } private Ref getMetaRef(Change.Id changeId) throws Exception { try (Repository repo = repoManager.openRepository(project)) { return repo.exactRef(RefNames.changeMetaRef(changeId)); } } private String createDraftChangeWith2PS() throws Exception { PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo); Result result = push.to("refs/drafts/master"); push = pushFactory.create( db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT, "b.txt", "4711", result.getChangeId()); return push.to("refs/drafts/master").getChangeId(); } private PatchSet getCurrentPatchSet(String changeId) throws Exception { return getChange(changeId).currentPatchSet(); } private ChangeData getChange(String changeId) throws Exception { return Iterables.getOnlyElement(queryProvider.get().byKeyPrefix(changeId)); } private void deletePatchSet(String changeId, PatchSet ps) throws Exception { deletePatchSet(changeId, ps.getId().get()); } private void deletePatchSet(String changeId, int ps) throws Exception { gApi.changes().id(changeId).revision(ps).delete(); } }