diff options
Diffstat (limited to 'javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java')
-rw-r--r-- | javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java | 732 |
1 files changed, 732 insertions, 0 deletions
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java new file mode 100644 index 0000000000..c1dda00aaf --- /dev/null +++ b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java @@ -0,0 +1,732 @@ +// 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 static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS; +import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; + +import com.google.gerrit.acceptance.GitUtil; +import com.google.gerrit.acceptance.PushOneCommit; +import com.google.gerrit.common.data.Permission; +import com.google.gerrit.extensions.api.changes.ChangeApi; +import com.google.gerrit.extensions.api.changes.CherryPickInput; +import com.google.gerrit.extensions.api.changes.ReviewInput; +import com.google.gerrit.extensions.api.changes.SubmitInput; +import com.google.gerrit.extensions.api.projects.BranchInput; +import com.google.gerrit.extensions.client.ChangeStatus; +import com.google.gerrit.extensions.client.SubmitType; +import com.google.gerrit.extensions.restapi.BinaryResult; +import com.google.gerrit.extensions.restapi.ResourceNotFoundException; +import com.google.gerrit.extensions.restapi.RestApiException; +import com.google.gerrit.reviewdb.client.Branch; +import com.google.gerrit.reviewdb.client.PatchSet; +import com.google.gerrit.reviewdb.client.Project; +import java.io.File; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.zip.GZIPInputStream; +import org.apache.commons.compress.archivers.ArchiveStreamFactory; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.transport.RefSpec; +import org.junit.Test; + +public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge { + + @Override + protected SubmitType getSubmitType() { + return SubmitType.MERGE_IF_NECESSARY; + } + + @Test + public void submitWithFastForward() throws Exception { + RevCommit initialHead = getRemoteHead(); + PushOneCommit.Result change = createChange(); + submit(change.getChangeId()); + RevCommit updatedHead = getRemoteHead(); + assertThat(updatedHead.getId()).isEqualTo(change.getCommit()); + assertThat(updatedHead.getParent(0)).isEqualTo(initialHead); + assertSubmitter(change.getChangeId(), 1); + assertPersonEquals(admin.getIdent(), updatedHead.getAuthorIdent()); + assertPersonEquals(admin.getIdent(), updatedHead.getCommitterIdent()); + + assertRefUpdatedEvents(initialHead, updatedHead); + assertChangeMergedEvents(change.getChangeId(), updatedHead.name()); + } + + @Test + public void submitMultipleChanges() throws Exception { + RevCommit initialHead = getRemoteHead(); + + testRepo.reset(initialHead); + PushOneCommit.Result change = createChange("Change 1", "b", "b"); + + testRepo.reset(initialHead); + PushOneCommit.Result change2 = createChange("Change 2", "c", "c"); + + testRepo.reset(initialHead); + PushOneCommit.Result change3 = createChange("Change 3", "d", "d"); + PushOneCommit.Result change4 = createChange("Change 4", "e", "e"); + PushOneCommit.Result change5 = createChange("Change 5", "f", "f"); + + // Change 2 is a fast-forward, no need to merge. + submit(change2.getChangeId()); + + RevCommit headAfterFirstSubmit = getRemoteLog().get(0); + assertThat(headAfterFirstSubmit.getShortMessage()) + .isEqualTo(change2.getCommit().getShortMessage()); + assertThat(headAfterFirstSubmit.getParent(0).getId()).isEqualTo(initialHead.getId()); + assertPersonEquals(admin.getIdent(), headAfterFirstSubmit.getAuthorIdent()); + assertPersonEquals(admin.getIdent(), headAfterFirstSubmit.getCommitterIdent()); + + // We need to merge changes 3, 4 and 5. + approve(change3.getChangeId()); + approve(change4.getChangeId()); + submit(change5.getChangeId()); + + RevCommit headAfterSecondSubmit = getRemoteLog().get(0); + assertThat(headAfterSecondSubmit.getParent(1).getShortMessage()) + .isEqualTo(change5.getCommit().getShortMessage()); + assertThat(headAfterSecondSubmit.getParent(0).getShortMessage()) + .isEqualTo(change2.getCommit().getShortMessage()); + + assertPersonEquals(admin.getIdent(), headAfterSecondSubmit.getAuthorIdent()); + assertPersonEquals(serverIdent.get(), headAfterSecondSubmit.getCommitterIdent()); + + // First change stays untouched. + assertNew(change.getChangeId()); + + // The two submit operations should have resulted in two ref-update events + // and three change-merged events. + assertRefUpdatedEvents( + initialHead, headAfterFirstSubmit, headAfterFirstSubmit, headAfterSecondSubmit); + assertChangeMergedEvents( + change2.getChangeId(), + headAfterFirstSubmit.name(), + change3.getChangeId(), + headAfterSecondSubmit.name(), + change4.getChangeId(), + headAfterSecondSubmit.name(), + change5.getChangeId(), + headAfterSecondSubmit.name()); + } + + @Test + public void submitChangesAcrossRepos() throws Exception { + Project.NameKey p1 = createProject("project-where-we-submit"); + Project.NameKey p2 = createProject("project-impacted-via-topic"); + Project.NameKey p3 = createProject("project-impacted-indirectly-via-topic"); + + RevCommit initialHead2 = getRemoteHead(p2, "master"); + RevCommit initialHead3 = getRemoteHead(p3, "master"); + + TestRepository<?> repo1 = cloneProject(p1); + TestRepository<?> repo2 = cloneProject(p2); + TestRepository<?> repo3 = cloneProject(p3); + + PushOneCommit.Result change1a = + createChange( + repo1, + "master", + "An ancestor of the change we want to submit", + "a.txt", + "1", + "dependent-topic"); + PushOneCommit.Result change1b = + createChange( + repo1, + "master", + "We're interested in submitting this change", + "a.txt", + "2", + "topic-to-submit"); + + PushOneCommit.Result change2a = + createChange(repo2, "master", "indirection level 1", "a.txt", "1", "topic-indirect"); + PushOneCommit.Result change2b = + createChange( + repo2, "master", "should go in with first change", "a.txt", "2", "dependent-topic"); + + PushOneCommit.Result change3 = + createChange(repo3, "master", "indirection level 2", "a.txt", "1", "topic-indirect"); + + approve(change1a.getChangeId()); + approve(change2a.getChangeId()); + approve(change2b.getChangeId()); + approve(change3.getChangeId()); + + // get a preview before submitting: + Map<Branch.NameKey, ObjectId> preview = fetchFromSubmitPreview(change1b.getChangeId()); + submit(change1b.getChangeId()); + + RevCommit tip1 = getRemoteLog(p1, "master").get(0); + RevCommit tip2 = getRemoteLog(p2, "master").get(0); + RevCommit tip3 = getRemoteLog(p3, "master").get(0); + + assertThat(tip1.getShortMessage()).isEqualTo(change1b.getCommit().getShortMessage()); + + if (isSubmitWholeTopicEnabled()) { + assertThat(tip2.getShortMessage()).isEqualTo(change2b.getCommit().getShortMessage()); + assertThat(tip3.getShortMessage()).isEqualTo(change3.getCommit().getShortMessage()); + + // check that the preview matched what happened: + assertThat(preview).hasSize(3); + + assertThat(preview).containsKey(new Branch.NameKey(p1, "refs/heads/master")); + assertTrees(p1, preview); + + assertThat(preview).containsKey(new Branch.NameKey(p2, "refs/heads/master")); + assertTrees(p2, preview); + + assertThat(preview).containsKey(new Branch.NameKey(p3, "refs/heads/master")); + assertTrees(p3, preview); + } else { + assertThat(tip2.getShortMessage()).isEqualTo(initialHead2.getShortMessage()); + assertThat(tip3.getShortMessage()).isEqualTo(initialHead3.getShortMessage()); + assertThat(preview).hasSize(1); + assertThat(preview.get(new Branch.NameKey(p1, "refs/heads/master"))).isNotNull(); + } + } + + @Test + public void submitChangesAcrossReposBlocked() throws Exception { + Project.NameKey p1 = createProject("project-where-we-submit"); + Project.NameKey p2 = createProject("project-impacted-via-topic"); + Project.NameKey p3 = createProject("project-impacted-indirectly-via-topic"); + + TestRepository<?> repo1 = cloneProject(p1); + TestRepository<?> repo2 = cloneProject(p2); + TestRepository<?> repo3 = cloneProject(p3); + + RevCommit initialHead1 = getRemoteHead(p1, "master"); + RevCommit initialHead2 = getRemoteHead(p2, "master"); + RevCommit initialHead3 = getRemoteHead(p3, "master"); + + PushOneCommit.Result change1a = + createChange( + repo1, + "master", + "An ancestor of the change we want to submit", + "a.txt", + "1", + "dependent-topic"); + PushOneCommit.Result change1b = + createChange( + repo1, + "master", + "we're interested to submit this change", + "a.txt", + "2", + "topic-to-submit"); + + PushOneCommit.Result change2a = + createChange(repo2, "master", "indirection level 2a", "a.txt", "1", "topic-indirect"); + PushOneCommit.Result change2b = + createChange( + repo2, "master", "should go in with first change", "a.txt", "2", "dependent-topic"); + + PushOneCommit.Result change3 = + createChange(repo3, "master", "indirection level 2b", "a.txt", "1", "topic-indirect"); + + // Create a merge conflict for change3 which is only indirectly related + // via topics. + repo3.reset(initialHead3); + PushOneCommit.Result change3Conflict = + createChange(repo3, "master", "conflicting change", "a.txt", "2\n2", "conflicting-topic"); + submit(change3Conflict.getChangeId()); + RevCommit tipConflict = getRemoteLog(p3, "master").get(0); + assertThat(tipConflict.getShortMessage()) + .isEqualTo(change3Conflict.getCommit().getShortMessage()); + + approve(change1a.getChangeId()); + approve(change2a.getChangeId()); + approve(change2b.getChangeId()); + approve(change3.getChangeId()); + + if (isSubmitWholeTopicEnabled()) { + String msg = + "Failed to submit 5 changes due to the following problems:\n" + + "Change " + + change3.getChange().getId() + + ": Change could not be " + + "merged due to a path conflict. Please rebase the change locally " + + "and upload the rebased commit for review."; + + // Get a preview before submitting: + try (BinaryResult r = gApi.changes().id(change1b.getChangeId()).current().submitPreview()) { + // We cannot just use the ExpectedException infrastructure as provided + // by AbstractDaemonTest, as then we'd stop early and not test the + // actual submit. + + fail("expected failure"); + } catch (RestApiException e) { + assertThat(e.getMessage()).isEqualTo(msg); + } + submitWithConflict(change1b.getChangeId(), msg); + } else { + submit(change1b.getChangeId()); + } + + RevCommit tip1 = getRemoteLog(p1, "master").get(0); + RevCommit tip2 = getRemoteLog(p2, "master").get(0); + RevCommit tip3 = getRemoteLog(p3, "master").get(0); + if (isSubmitWholeTopicEnabled()) { + assertThat(tip1.getShortMessage()).isEqualTo(initialHead1.getShortMessage()); + assertThat(tip2.getShortMessage()).isEqualTo(initialHead2.getShortMessage()); + assertThat(tip3.getShortMessage()).isEqualTo(change3Conflict.getCommit().getShortMessage()); + assertNoSubmitter(change1a.getChangeId(), 1); + assertNoSubmitter(change2a.getChangeId(), 1); + assertNoSubmitter(change2b.getChangeId(), 1); + assertNoSubmitter(change3.getChangeId(), 1); + } else { + assertThat(tip1.getShortMessage()).isEqualTo(change1b.getCommit().getShortMessage()); + assertThat(tip2.getShortMessage()).isEqualTo(initialHead2.getShortMessage()); + assertThat(tip3.getShortMessage()).isEqualTo(change3Conflict.getCommit().getShortMessage()); + assertNoSubmitter(change2a.getChangeId(), 1); + assertNoSubmitter(change2b.getChangeId(), 1); + assertNoSubmitter(change3.getChangeId(), 1); + } + } + + @Test + public void submitWithMergedAncestorsOnOtherBranch() throws Exception { + RevCommit initialHead = getRemoteHead(); + + PushOneCommit.Result change1 = + createChange(testRepo, "master", "base commit", "a.txt", "1", ""); + submit(change1.getChangeId()); + RevCommit headAfterFirstSubmit = getRemoteHead(); + + gApi.projects().name(project.get()).branch("branch").create(new BranchInput()); + + PushOneCommit.Result change2 = + createChange( + testRepo, "master", "We want to commit this to master first", "a.txt", "2", ""); + + submit(change2.getChangeId()); + + RevCommit headAfterSecondSubmit = getRemoteLog(project, "master").get(0); + assertThat(headAfterSecondSubmit.getShortMessage()) + .isEqualTo(change2.getCommit().getShortMessage()); + + RevCommit tip2 = getRemoteLog(project, "branch").get(0); + assertThat(tip2.getShortMessage()).isEqualTo(change1.getCommit().getShortMessage()); + + PushOneCommit.Result change3 = + createChange( + testRepo, + "branch", + "This commit is based on master, which includes change2, " + + "but is targeted at branch, which doesn't include it.", + "a.txt", + "3", + ""); + + submit(change3.getChangeId()); + + List<RevCommit> log3 = getRemoteLog(project, "branch"); + assertThat(log3.get(0).getShortMessage()).isEqualTo(change3.getCommit().getShortMessage()); + assertThat(log3.get(1).getShortMessage()).isEqualTo(change2.getCommit().getShortMessage()); + + assertRefUpdatedEvents( + initialHead, headAfterFirstSubmit, headAfterFirstSubmit, headAfterSecondSubmit); + assertChangeMergedEvents( + change1.getChangeId(), + headAfterFirstSubmit.name(), + change2.getChangeId(), + headAfterSecondSubmit.name()); + } + + @Test + public void submitWithOpenAncestorsOnOtherBranch() throws Exception { + RevCommit initialHead = getRemoteHead(); + PushOneCommit.Result change1 = + createChange(testRepo, "master", "base commit", "a.txt", "1", ""); + submit(change1.getChangeId()); + RevCommit headAfterFirstSubmit = getRemoteHead(); + + gApi.projects().name(project.get()).branch("branch").create(new BranchInput()); + + PushOneCommit.Result change2 = + createChange( + testRepo, "master", "We want to commit this to master first", "a.txt", "2", ""); + + approve(change2.getChangeId()); + + RevCommit tip1 = getRemoteLog(project, "master").get(0); + assertThat(tip1.getShortMessage()).isEqualTo(change1.getCommit().getShortMessage()); + + RevCommit tip2 = getRemoteLog(project, "branch").get(0); + assertThat(tip2.getShortMessage()).isEqualTo(change1.getCommit().getShortMessage()); + + PushOneCommit.Result change3a = + createChange( + testRepo, + "branch", + "This commit is based on change2 pending for master, " + + "but is targeted itself at branch, which doesn't include it.", + "a.txt", + "3", + "a-topic-here"); + + Project.NameKey p3 = createProject("project-related-to-change3"); + TestRepository<?> repo3 = cloneProject(p3); + RevCommit repo3Head = getRemoteHead(p3, "master"); + PushOneCommit.Result change3b = + createChange( + repo3, + "master", + "some accompanying changes for change3a in another repo tied together via topic", + "a.txt", + "1", + "a-topic-here"); + approve(change3b.getChangeId()); + + String cnt = isSubmitWholeTopicEnabled() ? "2 changes" : "1 change"; + submitWithConflict( + change3a.getChangeId(), + "Failed to submit " + + cnt + + " due to the following problems:\n" + + "Change " + + change3a.getChange().getId() + + ": Depends on change that" + + " was not submitted." + + " Commit " + + change3a.getCommit().name() + + " depends on commit " + + change2.getCommit().name() + + " of change " + + change2.getChange().getId() + + " which cannot be merged."); + + RevCommit tipbranch = getRemoteLog(project, "branch").get(0); + assertThat(tipbranch.getShortMessage()).isEqualTo(change1.getCommit().getShortMessage()); + + RevCommit tipmaster = getRemoteLog(p3, "master").get(0); + assertThat(tipmaster.getShortMessage()).isEqualTo(repo3Head.getShortMessage()); + + assertRefUpdatedEvents(initialHead, headAfterFirstSubmit); + assertChangeMergedEvents(change1.getChangeId(), headAfterFirstSubmit.name()); + } + + @Test + public void gerritWorkflow() throws Exception { + RevCommit initialHead = getRemoteHead(); + + // We'll setup a master and a stable branch. + // Then we create a change to be applied to master, which is + // then cherry picked back to stable. The stable branch will + // be merged up into master again. + gApi.projects().name(project.get()).branch("stable").create(new BranchInput()); + + // Push a change to master + PushOneCommit push = + pushFactory.create(db, user.getIdent(), testRepo, "small fix", "a.txt", "2"); + PushOneCommit.Result change = push.to("refs/for/master"); + submit(change.getChangeId()); + RevCommit headAfterFirstSubmit = getRemoteLog(project, "master").get(0); + assertThat(headAfterFirstSubmit.getShortMessage()) + .isEqualTo(change.getCommit().getShortMessage()); + + // Now cherry pick to stable + CherryPickInput in = new CherryPickInput(); + in.destination = "stable"; + in.message = "This goes to stable as well\n" + headAfterFirstSubmit.getFullMessage(); + ChangeApi orig = gApi.changes().id(change.getChangeId()); + String cherryId = orig.current().cherryPick(in).id(); + gApi.changes().id(cherryId).current().review(ReviewInput.approve()); + gApi.changes().id(cherryId).current().submit(); + + // Create the merge locally + RevCommit stable = getRemoteHead(project, "stable"); + RevCommit master = getRemoteHead(project, "master"); + testRepo.git().fetch().call(); + testRepo.git().branchCreate().setName("stable").setStartPoint(stable).call(); + testRepo.git().branchCreate().setName("master").setStartPoint(master).call(); + + RevCommit merge = + testRepo + .commit() + .parent(master) + .parent(stable) + .message("Merge stable into master") + .insertChangeId() + .create(); + + testRepo.branch("refs/heads/master").update(merge); + testRepo.git().push().setRefSpecs(new RefSpec("refs/heads/master:refs/for/master")).call(); + + String changeId = GitUtil.getChangeId(testRepo, merge).get(); + approve(changeId); + submit(changeId); + RevCommit headAfterSecondSubmit = getRemoteLog(project, "master").get(0); + assertThat(headAfterSecondSubmit.getShortMessage()).isEqualTo(merge.getShortMessage()); + + assertRefUpdatedEvents( + initialHead, headAfterFirstSubmit, headAfterFirstSubmit, headAfterSecondSubmit); + assertChangeMergedEvents( + change.getChangeId(), headAfterFirstSubmit.name(), changeId, headAfterSecondSubmit.name()); + } + + @Test + public void openChangeForTargetBranchPreventsMerge() throws Exception { + gApi.projects().name(project.get()).branch("stable").create(new BranchInput()); + + // Propose a change for master, but leave it open for master! + PushOneCommit change = + pushFactory.create(db, user.getIdent(), testRepo, "small fix", "a.txt", "2"); + PushOneCommit.Result change2result = change.to("refs/for/master"); + + // Now cherry pick to stable + CherryPickInput in = new CherryPickInput(); + in.destination = "stable"; + in.message = "it goes to stable branch"; + ChangeApi orig = gApi.changes().id(change2result.getChangeId()); + ChangeApi cherry = orig.current().cherryPick(in); + cherry.current().review(ReviewInput.approve()); + cherry.current().submit(); + + // Create a commit locally + testRepo.git().fetch().setRefSpecs(new RefSpec("refs/heads/stable")).call(); + + PushOneCommit.Result change3 = createChange(testRepo, "stable", "test", "a.txt", "3", ""); + submitWithConflict( + change3.getChangeId(), + "Failed to submit 1 change due to the following problems:\n" + + "Change " + + change3.getPatchSetId().getParentKey().get() + + ": Depends on change that was not submitted." + + " Commit " + + change3.getCommit().name() + + " depends on commit " + + change2result.getCommit().name() + + " of change " + + change2result.getChange().getId() + + " which cannot be merged."); + + assertRefUpdatedEvents(); + assertChangeMergedEvents(); + } + + @Test + public void dependencyOnOutdatedPatchSetPreventsMerge() throws Exception { + // Create a change + PushOneCommit change = pushFactory.create(db, user.getIdent(), testRepo, "fix", "a.txt", "foo"); + PushOneCommit.Result changeResult = change.to("refs/for/master"); + PatchSet.Id patchSetId = changeResult.getPatchSetId(); + + // Create a successor change. + PushOneCommit change2 = + pushFactory.create(db, user.getIdent(), testRepo, "feature", "b.txt", "bar"); + PushOneCommit.Result change2Result = change2.to("refs/for/master"); + + // Create new patch set for first change. + testRepo.reset(changeResult.getCommit().name()); + amendChange(changeResult.getChangeId()); + + // Approve both changes + approve(changeResult.getChangeId()); + approve(change2Result.getChangeId()); + + submitWithConflict( + change2Result.getChangeId(), + "Failed to submit 2 changes due to the following problems:\n" + + "Change " + + change2Result.getChange().getId() + + ": Depends on change that was not submitted." + + " Commit " + + change2Result.getCommit().name() + + " depends on commit " + + changeResult.getCommit().name() + + ", which is outdated patch set " + + patchSetId.get() + + " of change " + + changeResult.getChange().getId() + + ". The latest patch set is " + + changeResult.getPatchSetId().get() + + "."); + + assertRefUpdatedEvents(); + assertChangeMergedEvents(); + } + + @Test + public void dependencyOnDeletedChangePreventsMerge() throws Exception { + // Create a change + PushOneCommit change = pushFactory.create(db, user.getIdent(), testRepo, "fix", "a.txt", "foo"); + PushOneCommit.Result changeResult = change.to("refs/for/master"); + + // Create a successor change. + PushOneCommit change2 = + pushFactory.create(db, user.getIdent(), testRepo, "feature", "b.txt", "bar"); + PushOneCommit.Result change2Result = change2.to("refs/for/master"); + + // Delete first change. + gApi.changes().id(changeResult.getChangeId()).delete(); + + // Submit is expected to fail. + submitWithConflict( + change2Result.getChangeId(), + "Failed to submit 1 change due to the following problems:\n" + + "Change " + + change2Result.getChange().getId() + + ": Depends on change that was not submitted." + + " Commit " + + change2Result.getCommit().name() + + " depends on commit " + + changeResult.getCommit().name() + + " which cannot be merged." + + " Is the change of this commit not visible or was it deleted?"); + + assertRefUpdatedEvents(); + assertChangeMergedEvents(); + } + + @Test + public void dependencyOnChangeForNonVisibleBranchPreventsMerge() throws Exception { + grantLabel("Code-Review", -2, 2, project, "refs/heads/*", false, REGISTERED_USERS, false); + grant(project, "refs/*", Permission.SUBMIT, false, REGISTERED_USERS); + + // Create a change + PushOneCommit change = + pushFactory.create(db, admin.getIdent(), testRepo, "fix", "a.txt", "foo"); + PushOneCommit.Result changeResult = change.to("refs/for/master"); + approve(changeResult.getChangeId()); + + // Create a successor change. + PushOneCommit change2 = + pushFactory.create(db, admin.getIdent(), testRepo, "feature", "b.txt", "bar"); + PushOneCommit.Result change2Result = change2.to("refs/for/master"); + + // Move the first change to a destination branch that is non-visible to user so that user cannot + // this change anymore. + Branch.NameKey secretBranch = new Branch.NameKey(project, "secretBranch"); + gApi.projects() + .name(secretBranch.getParentKey().get()) + .branch(secretBranch.get()) + .create(new BranchInput()); + gApi.changes().id(changeResult.getChangeId()).move(secretBranch.get()); + block(secretBranch.get(), "read", ANONYMOUS_USERS); + + setApiUser(user); + + // Verify that user cannot see the first change. + try { + gApi.changes().id(changeResult.getChangeId()).get(); + fail("expected failure"); + } catch (ResourceNotFoundException e) { + assertThat(e.getMessage()).isEqualTo("Not found: " + changeResult.getChangeId()); + } + + // Submit is expected to fail. + submitWithConflict( + change2Result.getChangeId(), + "Failed to submit 1 change due to the following problems:\n" + + "Change " + + change2Result.getChange().getId() + + ": Depends on change that was not submitted." + + " Commit " + + change2Result.getCommit().name() + + " depends on commit " + + changeResult.getCommit().name() + + " which cannot be merged." + + " Is the change of this commit not visible or was it deleted?"); + + assertRefUpdatedEvents(); + assertChangeMergedEvents(); + } + + @Test + public void dependencyOnHiddenChangeShouldPreventMergeButDoesnt() throws Exception { + grantLabel("Code-Review", -2, 2, project, "refs/heads/*", false, REGISTERED_USERS, false); + grant(project, "refs/*", Permission.SUBMIT, false, REGISTERED_USERS); + + // Create a change + PushOneCommit change = + pushFactory.create(db, admin.getIdent(), testRepo, "fix", "a.txt", "foo"); + PushOneCommit.Result changeResult = change.to("refs/for/master"); + approve(changeResult.getChangeId()); + + // Create a successor change. + PushOneCommit change2 = + pushFactory.create(db, admin.getIdent(), testRepo, "feature", "b.txt", "bar"); + PushOneCommit.Result change2Result = change2.to("refs/for/master"); + approve(change2Result.getChangeId()); + + // Mark the first change private so that it's not visible to user. + gApi.changes().id(changeResult.getChangeId()).setPrivate(true, "nobody should see this"); + + setApiUser(user); + + // Verify that user cannot see the first change. + try { + gApi.changes().id(changeResult.getChangeId()).get(); + fail("expected failure"); + } catch (ResourceNotFoundException e) { + assertThat(e.getMessage()).isEqualTo("Not found: " + changeResult.getChangeId()); + } + + // Submit the second change which has a dependency on the first change which is not visible to + // the user. We would expect the submit to fail, but instead the submit succeeds and the hidden + // change gets submitted too. + // TODO(ekempin): Make this submit fail. + gApi.changes().id(change2Result.getChangeId()).current().submit(new SubmitInput()); + + // Verify that both changes have been submitted. + setApiUser(admin); + assertThat(gApi.changes().id(changeResult.getChangeId()).get().status) + .isEqualTo(ChangeStatus.MERGED); + assertThat(gApi.changes().id(change2Result.getChangeId()).get().status) + .isEqualTo(ChangeStatus.MERGED); + } + + @Test + public void testPreviewSubmitTgz() throws Exception { + Project.NameKey p1 = createProject("project-name"); + + TestRepository<?> repo1 = cloneProject(p1); + PushOneCommit.Result change1 = createChange(repo1, "master", "test", "a.txt", "1", "topic"); + approve(change1.getChangeId()); + + // get a preview before submitting: + File tempfile; + try (BinaryResult request = + gApi.changes().id(change1.getChangeId()).current().submitPreview("tgz")) { + assertThat(request.getContentType()).isEqualTo("application/x-gzip"); + tempfile = File.createTempFile("test", null); + request.writeTo(Files.newOutputStream(tempfile.toPath())); + } + + InputStream is = new GZIPInputStream(Files.newInputStream(tempfile.toPath())); + + List<String> untarredFiles = new ArrayList<>(); + try (TarArchiveInputStream tarInputStream = + (TarArchiveInputStream) new ArchiveStreamFactory().createArchiveInputStream("tar", is)) { + TarArchiveEntry entry; + while ((entry = (TarArchiveEntry) tarInputStream.getNextEntry()) != null) { + untarredFiles.add(entry.getName()); + } + } + assertThat(untarredFiles).containsExactly(name("project-name") + ".git"); + } +} |