blob: 25d25c7fd1da64b691619e072715f7428965e8da [file] [log] [blame]
// Copyright (C) 2021 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.git;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
import static com.google.gerrit.testing.TestActionRefUpdateContext.testRefAction;
import static org.eclipse.jgit.lib.Constants.HEAD;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.RawInputUtil;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.common.MergeInput;
import com.google.inject.Inject;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
import org.junit.Before;
import org.junit.Test;
/** Ensures that auto merge commits are created when a new patch set or change is uploaded. */
public class AutoMergeIT extends AbstractDaemonTest {
@Inject private ProjectOperations projectOperations;
private RevCommit parent1;
private RevCommit parent2;
@Before
public void setup() throws Exception {
ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId();
PushOneCommit.Result p1 =
pushFactory
.create(
admin.newIdent(),
testRepo,
"parent 1",
ImmutableMap.of("foo", "foo-1.2", "bar", "bar-1.2"))
.to("refs/for/master");
parent1 = p1.getCommit();
// reset HEAD in order to create a sibling of the first change
testRepo.reset(initial);
PushOneCommit.Result p2 =
pushFactory
.create(
admin.newIdent(),
testRepo,
"parent 2",
ImmutableMap.of("foo", "foo-2.2", "bar", "bar-2.2"))
.to("refs/for/master");
parent2 = p2.getCommit();
}
@Test
public void autoMergeCreatedWhenPushingNewChange() throws Exception {
PushOneCommit m =
pushFactory.create(
admin.newIdent(), testRepo, "merge", ImmutableMap.of("foo", "foo-1", "bar", "bar-2"));
m.setParents(ImmutableList.of(parent1, parent2));
PushOneCommit.Result result = m.to("refs/for/master");
result.assertOkStatus();
assertAutoMergeCreated(result.getCommit());
}
@Test
public void autoMergeCreatedWhenPushingNewPatchSet() throws Exception {
PushOneCommit m =
pushFactory.create(
admin.newIdent(), testRepo, "merge", ImmutableMap.of("foo", "foo-1", "bar", "bar-2"));
m.setParents(ImmutableList.of(parent1, parent2));
PushOneCommit.Result ps1 = m.to("refs/for/master");
RevCommit ps2 =
testRepo
.amend(ps1.getCommit())
.message("PS2")
.insertChangeId(ps1.getChangeId().substring(1))
.create();
testRepo.reset(ps2);
GitUtil.pushHead(testRepo, "refs/for/master");
// Make sure we have two patch sets
assertThat(ps2.getParents().length).isEqualTo(2);
assertThat(gApi.changes().id(ps1.getChangeId()).get().revisions.size()).isEqualTo(2);
assertAutoMergeCreated(ps2);
}
@Test
public void autoMergeCreatedWhenPushingMergeBetweenTwoInitialCommits() throws Exception {
Project.NameKey projectWithoutInitialCommit =
projectOperations.newProject().createEmptyCommit(false).create();
TestRepository<InMemoryRepository> testRepo =
cloneProject(projectWithoutInitialCommit, getCloneAsAccount(configRule.description()));
RevCommit initialCommit1 =
testRepo.parseBody(
testRepo
.commit()
.message("Initial Change 1")
.insertChangeId()
.add("file1", "contents1")
.create());
RevCommit initialCommit2 =
testRepo.parseBody(
testRepo
.commit()
.message("Initial Change 2")
.insertChangeId()
.add("file1", "contents2")
.create());
RevCommit mergeCommit =
testRepo
.branch("master")
.commit()
.message("Merge Change")
.parent(initialCommit1)
.parent(initialCommit2)
.insertChangeId()
.create();
testRepo.reset(mergeCommit);
PushResult r = pushHead(testRepo, "refs/for/master");
assertThat(r.getRemoteUpdate("refs/for/master").getStatus()).isEqualTo(Status.OK);
assertAutoMergeCreated(projectWithoutInitialCommit, mergeCommit);
}
@Test
public void autoMergeCreatedWhenPushingCrissCrossMerge() throws Exception {
RevCommit initialCommit = repo().parseCommit(repo().exactRef(HEAD).getLeaf().getObjectId());
RevCommit baseCommit1 =
testRepo.parseBody(
testRepo
.commit()
.parent(initialCommit)
.message("Change 1")
.insertChangeId()
.add("file1", "contents1")
.create());
RevCommit baseCommit2 =
testRepo.parseBody(
testRepo
.commit()
.parent(initialCommit)
.message("Change 2")
.insertChangeId()
.add("file2", "contents2")
.create());
RevCommit mergeCommit1 =
testRepo
.commit()
.message("Merge Change In Source")
.parent(baseCommit1)
.parent(baseCommit2)
.insertChangeId()
.create();
RevCommit mergeCommit2 =
testRepo
.commit()
.message("Merge Change In Target")
.parent(baseCommit2)
.parent(baseCommit1)
.insertChangeId()
.create();
RevCommit conflictingCommit1 =
testRepo.parseBody(
testRepo
.commit()
.message("Change 1")
.parent(mergeCommit1)
.insertChangeId()
.add("conflicting-file", "contents1")
.create());
RevCommit conflictingCommit2 =
testRepo.parseBody(
testRepo
.commit()
.message("Change 2")
.parent(mergeCommit2)
.insertChangeId()
.add("conflicting-file", "contents2")
.create());
RevCommit crissCrossMerge =
testRepo
.commit()
.message("Criss-Cross Merge")
.parent(conflictingCommit1)
.parent(conflictingCommit2)
.insertChangeId()
.create();
testRepo.reset(crissCrossMerge);
PushResult r = pushHead(testRepo, "refs/for/master");
assertThat(r.getRemoteUpdate("refs/for/master").getStatus()).isEqualTo(Status.OK);
assertAutoMergeCreated(crissCrossMerge);
}
@Test
public void autoMergeCreatedWhenPushingCrissCrossMergeWithConflictingBases() throws Exception {
RevCommit initialCommit = repo().parseCommit(repo().exactRef(HEAD).getLeaf().getObjectId());
String baseFile = "baseFile,txt";
RevCommit baseCommit1 =
testRepo.parseBody(
testRepo
.commit()
.parent(initialCommit)
.message("Change 1")
.insertChangeId()
.add(baseFile, "contents1")
.create());
RevCommit baseCommit2 =
testRepo.parseBody(
testRepo
.commit()
.parent(initialCommit)
.message("Change 2")
.insertChangeId()
.add(baseFile, "contents2")
.create());
RevCommit mergeCommit1 =
testRepo
.commit()
.message("Merge Change In Source")
.add(baseFile, "contents1")
.parent(baseCommit1)
.parent(baseCommit2)
.insertChangeId()
.create();
RevCommit mergeCommit2 =
testRepo
.commit()
.message("Merge Change In Target")
.add(baseFile, "contents2")
.parent(baseCommit2)
.parent(baseCommit1)
.insertChangeId()
.create();
RevCommit conflictingCommit1 =
testRepo.parseBody(
testRepo
.commit()
.message("Change 1")
.parent(mergeCommit1)
.insertChangeId()
.add("conflicting-file", "contents1")
.create());
RevCommit conflictingCommit2 =
testRepo.parseBody(
testRepo
.commit()
.message("Change 2")
.parent(mergeCommit2)
.insertChangeId()
.add("conflicting-file", "contents2")
.create());
RevCommit crissCrossMerge =
testRepo
.commit()
.message("Criss-Cross Merge")
.parent(conflictingCommit1)
.parent(conflictingCommit2)
.insertChangeId()
.create();
testRepo.reset(crissCrossMerge);
PushResult r = pushHead(testRepo, "refs/for/master");
assertThat(r.getRemoteUpdate("refs/for/master").getStatus()).isEqualTo(Status.OK);
assertAutoMergeCreated(crissCrossMerge);
}
@Test
public void autoMergeCreatedWhenChangeCreatedOnApi() throws Exception {
ChangeInput ci = new ChangeInput(project.get(), "master", "Merge commit");
ci.merge = new MergeInput();
ci.merge.source = parent1.name();
String newChangePatchSetSha1 = gApi.changes().create(ci).get().currentRevision;
assertAutoMergeCreated(ObjectId.fromString(newChangePatchSetSha1));
}
@Test
public void autoMergeCreatedWhenNewPatchSetCreatedOnApi() throws Exception {
ChangeInput ci = new ChangeInput(project.get(), "master", "Merge commit");
ci.merge = new MergeInput();
ci.merge.source = parent1.name();
String changeId = gApi.changes().create(ci).get().changeId;
gApi.changes().id(changeId).setMessage("New Commit Message\n\nChange-Id: " + changeId);
assertThat(gApi.changes().id(changeId).get().revisions.size()).isEqualTo(2);
String newChangePatchSetSha1 = gApi.changes().id(changeId).get().currentRevision;
assertAutoMergeCreated(ObjectId.fromString(newChangePatchSetSha1));
}
@Test
public void autoMergeCreatedWhenChangeEditIsPublished() throws Exception {
PushOneCommit m =
pushFactory.create(
admin.newIdent(), testRepo, "merge", ImmutableMap.of("foo", "foo-1", "bar", "bar-2"));
m.setParents(ImmutableList.of(parent1, parent2));
PushOneCommit.Result result = m.to("refs/for/master");
result.assertOkStatus();
assertAutoMergeCreated(result.getCommit());
gApi.changes()
.id(result.getChangeId())
.edit()
.modifyFile("new-file", RawInputUtil.create("content"));
gApi.changes().id(result.getChangeId()).edit().publish();
assertThat(gApi.changes().id(result.getChangeId()).get().revisions.size()).isEqualTo(2);
String newChangePatchSetSha1 = gApi.changes().id(result.getChangeId()).get().currentRevision;
assertAutoMergeCreated(ObjectId.fromString(newChangePatchSetSha1));
}
@Test
public void noAutoMergeCreatedWhenPushingNonMergeCommit() throws Exception {
PushOneCommit.Result change = createChange();
change.assertOkStatus();
assertNoAutoMergeCreated(change.getCommit());
}
@Test
public void autoMergeComputedInMemoryWhenMissing() throws Exception {
PushOneCommit m =
pushFactory.create(
admin.newIdent(), testRepo, "merge", ImmutableMap.of("foo", "foo-1", "bar", "bar-2"));
m.setParents(ImmutableList.of(parent1, parent2));
PushOneCommit.Result result = m.to("refs/for/master");
result.assertOkStatus();
assertAutoMergeCreated(result.getCommit());
// Delete auto merge branch
deleteAutoMergeBranch(result.getCommit());
// Trigger AutoMerge computation
assertThat(gApi.changes().id(result.getChangeId()).revision(1).file("foo").blameRequest().get())
.isNotEmpty();
assertNoAutoMergeCreated(result.getCommit());
}
@Test
public void pushWorksIfAutoMergeExists() throws Exception {
PushOneCommit m =
pushFactory.create(
admin.newIdent(), testRepo, "merge", ImmutableMap.of("foo", "foo-1", "bar", "bar-2"));
m.setParents(ImmutableList.of(parent1, parent2));
PushOneCommit.Result result = m.to("refs/for/master");
result.assertOkStatus();
assertAutoMergeCreated(result.getCommit());
// Delete change and push commit again.
gApi.changes().id(result.getChangeId()).delete();
// Push again successfully and check that AutoMerge commit is still there
result = m.to("refs/for/master");
result.assertOkStatus();
assertAutoMergeCreated(result.getCommit());
}
private void assertAutoMergeCreated(ObjectId mergeCommit) throws Exception {
assertAutoMergeCreated(project, mergeCommit);
}
private void assertAutoMergeCreated(Project.NameKey project, ObjectId mergeCommit)
throws Exception {
try (Repository repo = repoManager.openRepository(project)) {
assertThat(repo.exactRef(RefNames.refsCacheAutomerge(mergeCommit.name()))).isNotNull();
}
}
private void assertNoAutoMergeCreated(ObjectId mergeCommit) throws Exception {
try (Repository repo = repoManager.openRepository(project)) {
assertThat(repo.exactRef(RefNames.refsCacheAutomerge(mergeCommit.name()))).isNull();
}
}
private void deleteAutoMergeBranch(ObjectId mergeCommit) throws Exception {
try (Repository repo = repoManager.openRepository(project)) {
RefUpdate ru = repo.updateRef(RefNames.refsCacheAutomerge(mergeCommit.name()));
ru.setForceUpdate(true);
testRefAction(() -> assertThat(ru.delete()).isEqualTo(RefUpdate.Result.FORCED));
}
assertNoAutoMergeCreated(mergeCommit);
}
}