| // Copyright (C) 2015 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.server.git; | |
| import static com.google.common.truth.Truth.assertThat; | |
| import com.google.common.collect.ImmutableListMultimap; | |
| import com.google.common.collect.ImmutableSet; | |
| import com.google.common.collect.SortedSetMultimap; | |
| import com.google.gerrit.entities.Change; | |
| import com.google.gerrit.entities.PatchSet; | |
| import com.google.gerrit.server.git.receive.ReceivePackRefCache; | |
| import java.io.IOException; | |
| import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; | |
| 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.revwalk.RevCommit; | |
| import org.eclipse.jgit.revwalk.RevSort; | |
| import org.eclipse.jgit.revwalk.RevWalk; | |
| import org.junit.Before; | |
| import org.junit.Test; | |
| public class GroupCollectorTest { | |
| private TestRepository<?> tr; | |
| @Before | |
| public void setUp() throws Exception { | |
| tr = new TestRepository<>(new InMemoryRepository(new DfsRepositoryDescription("repo"))); | |
| } | |
| @Test | |
| public void commitWhoseParentIsUninterestingGetsNewGroup() throws Exception { | |
| RevCommit branchTip = tr.commit().create(); | |
| RevCommit a = tr.commit().parent(branchTip).create(); | |
| SortedSetMultimap<ObjectId, String> groups = collectGroups(newWalk(a, branchTip), groups()); | |
| assertThat(groups).containsEntry(a, a.name()); | |
| } | |
| @Test | |
| public void commitWhoseParentIsNewPatchSetGetsParentsGroup() throws Exception { | |
| RevCommit branchTip = tr.commit().create(); | |
| RevCommit a = tr.commit().parent(branchTip).create(); | |
| RevCommit b = tr.commit().parent(a).create(); | |
| SortedSetMultimap<ObjectId, String> groups = collectGroups(newWalk(b, branchTip), groups()); | |
| assertThat(groups).containsEntry(a, a.name()); | |
| assertThat(groups).containsEntry(b, a.name()); | |
| } | |
| @Test | |
| public void commitWhoseParentIsExistingPatchSetGetsParentsGroup() throws Exception { | |
| RevCommit branchTip = tr.commit().create(); | |
| RevCommit a = tr.commit().parent(branchTip).create(); | |
| createRef(psId(1, 1), a, tr); | |
| RevCommit b = tr.commit().parent(a).create(); | |
| String group = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; | |
| SortedSetMultimap<ObjectId, String> groups = | |
| collectGroups(newWalk(b, branchTip), groups().put(psId(1, 1), group)); | |
| assertThat(groups).containsEntry(a, group); | |
| assertThat(groups).containsEntry(b, group); | |
| } | |
| @Test | |
| public void commitWhoseParentIsExistingPatchSetWithNoGroup() throws Exception { | |
| RevCommit branchTip = tr.commit().create(); | |
| RevCommit a = tr.commit().parent(branchTip).create(); | |
| RevCommit b = tr.commit().parent(a).create(); | |
| SortedSetMultimap<ObjectId, String> groups = collectGroups(newWalk(b, branchTip), groups()); | |
| assertThat(groups).containsEntry(a, a.name()); | |
| assertThat(groups).containsEntry(b, a.name()); | |
| } | |
| @Test | |
| public void mergeCommitAndNewParentsAllGetSameGroup() throws Exception { | |
| RevCommit branchTip = tr.commit().create(); | |
| RevCommit a = tr.commit().parent(branchTip).create(); | |
| RevCommit b = tr.commit().parent(branchTip).create(); | |
| RevCommit m = tr.commit().parent(a).parent(b).create(); | |
| SortedSetMultimap<ObjectId, String> groups = collectGroups(newWalk(m, branchTip), groups()); | |
| assertThat(groups).containsEntry(a, a.name()); | |
| assertThat(groups).containsEntry(b, a.name()); | |
| assertThat(groups).containsEntry(m, a.name()); | |
| } | |
| @Test | |
| public void mergeCommitWhereOneParentHasExistingGroup() throws Exception { | |
| RevCommit branchTip = tr.commit().create(); | |
| RevCommit a = tr.commit().parent(branchTip).create(); | |
| createRef(psId(1, 1), a, tr); | |
| RevCommit b = tr.commit().parent(branchTip).create(); | |
| RevCommit m = tr.commit().parent(a).parent(b).create(); | |
| String group = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; | |
| SortedSetMultimap<ObjectId, String> groups = | |
| collectGroups(newWalk(m, branchTip), groups().put(psId(1, 1), group)); | |
| // Merge commit and other parent get the existing group. | |
| assertThat(groups).containsEntry(a, group); | |
| assertThat(groups).containsEntry(b, group); | |
| assertThat(groups).containsEntry(m, group); | |
| } | |
| @Test | |
| public void mergeCommitWhereBothParentsHaveDifferentGroups() throws Exception { | |
| RevCommit branchTip = tr.commit().create(); | |
| RevCommit a = tr.commit().parent(branchTip).create(); | |
| createRef(psId(1, 1), a, tr); | |
| RevCommit b = tr.commit().parent(branchTip).create(); | |
| createRef(psId(2, 1), b, tr); | |
| RevCommit m = tr.commit().parent(a).parent(b).create(); | |
| String group1 = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; | |
| String group2 = "1234567812345678123456781234567812345678"; | |
| SortedSetMultimap<ObjectId, String> groups = | |
| collectGroups( | |
| newWalk(m, branchTip), groups().put(psId(1, 1), group1).put(psId(2, 1), group2)); | |
| assertThat(groups).containsEntry(a, group1); | |
| assertThat(groups).containsEntry(b, group2); | |
| // Merge commit gets joined group of parents. | |
| assertThat(groups.asMap()).containsEntry(m, ImmutableSet.of(group1, group2)); | |
| } | |
| @Test | |
| public void mergeCommitMergesGroupsFromParent() throws Exception { | |
| RevCommit branchTip = tr.commit().create(); | |
| RevCommit a = tr.commit().parent(branchTip).create(); | |
| createRef(psId(1, 1), a, tr); | |
| RevCommit b = tr.commit().parent(branchTip).create(); | |
| createRef(psId(2, 1), b, tr); | |
| RevCommit m = tr.commit().parent(a).parent(b).create(); | |
| String group1 = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; | |
| String group2a = "1234567812345678123456781234567812345678"; | |
| String group2b = "ef123456ef123456ef123456ef123456ef123456"; | |
| SortedSetMultimap<ObjectId, String> groups = | |
| collectGroups( | |
| newWalk(m, branchTip), | |
| groups().put(psId(1, 1), group1).put(psId(2, 1), group2a).put(psId(2, 1), group2b)); | |
| assertThat(groups).containsEntry(a, group1); | |
| assertThat(groups.asMap()).containsEntry(b, ImmutableSet.of(group2a, group2b)); | |
| // Joined parent groups are split and resorted. | |
| assertThat(groups.asMap()).containsEntry(m, ImmutableSet.of(group1, group2a, group2b)); | |
| } | |
| @Test | |
| public void mergeCommitWithOneUninterestingParentAndOtherParentIsExisting() throws Exception { | |
| RevCommit branchTip = tr.commit().create(); | |
| RevCommit a = tr.commit().parent(branchTip).create(); | |
| createRef(psId(1, 1), a, tr); | |
| RevCommit m = tr.commit().parent(branchTip).parent(a).create(); | |
| String group = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; | |
| SortedSetMultimap<ObjectId, String> groups = | |
| collectGroups(newWalk(m, branchTip), groups().put(psId(1, 1), group)); | |
| assertThat(groups).containsEntry(a, group); | |
| assertThat(groups).containsEntry(m, group); | |
| } | |
| @Test | |
| public void mergeCommitWithOneUninterestingParentAndOtherParentIsNew() throws Exception { | |
| RevCommit branchTip = tr.commit().create(); | |
| RevCommit a = tr.commit().parent(branchTip).create(); | |
| RevCommit m = tr.commit().parent(branchTip).parent(a).create(); | |
| SortedSetMultimap<ObjectId, String> groups = collectGroups(newWalk(m, branchTip), groups()); | |
| assertThat(groups).containsEntry(a, a.name()); | |
| assertThat(groups).containsEntry(m, a.name()); | |
| } | |
| @Test | |
| public void multipleMergeCommitsInHistoryAllResolveToSameGroup() throws Exception { | |
| RevCommit branchTip = tr.commit().create(); | |
| RevCommit a = tr.commit().parent(branchTip).create(); | |
| RevCommit b = tr.commit().parent(branchTip).create(); | |
| RevCommit c = tr.commit().parent(branchTip).create(); | |
| RevCommit m1 = tr.commit().parent(b).parent(c).create(); | |
| RevCommit m2 = tr.commit().parent(a).parent(m1).create(); | |
| SortedSetMultimap<ObjectId, String> groups = collectGroups(newWalk(m2, branchTip), groups()); | |
| assertThat(groups).containsEntry(a, a.name()); | |
| assertThat(groups).containsEntry(b, a.name()); | |
| assertThat(groups).containsEntry(c, a.name()); | |
| assertThat(groups).containsEntry(m1, a.name()); | |
| assertThat(groups).containsEntry(m2, a.name()); | |
| } | |
| @Test | |
| public void mergeCommitWithDuplicatedParentGetsParentsGroup() throws Exception { | |
| RevCommit branchTip = tr.commit().create(); | |
| RevCommit a = tr.commit().parent(branchTip).create(); | |
| RevCommit m = tr.commit().parent(a).parent(a).create(); | |
| tr.getRevWalk().parseBody(m); | |
| assertThat(m.getParentCount()).isEqualTo(2); | |
| assertThat(m.getParent(0)).isEqualTo(m.getParent(1)); | |
| SortedSetMultimap<ObjectId, String> groups = collectGroups(newWalk(m, branchTip), groups()); | |
| assertThat(groups).containsEntry(a, a.name()); | |
| assertThat(groups).containsEntry(m, a.name()); | |
| } | |
| @Test | |
| public void mergeCommitWithOneNewParentAndTwoExistingPatchSets() throws Exception { | |
| RevCommit branchTip = tr.commit().create(); | |
| RevCommit a = tr.commit().parent(branchTip).create(); | |
| createRef(psId(1, 1), a, tr); | |
| RevCommit b = tr.commit().parent(branchTip).create(); | |
| createRef(psId(2, 1), b, tr); | |
| RevCommit c = tr.commit().parent(b).create(); | |
| RevCommit m = tr.commit().parent(a).parent(c).create(); | |
| String group1 = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; | |
| String group2 = "1234567812345678123456781234567812345678"; | |
| SortedSetMultimap<ObjectId, String> groups = | |
| collectGroups( | |
| newWalk(m, branchTip), groups().put(psId(1, 1), group1).put(psId(2, 1), group2)); | |
| assertThat(groups).containsEntry(a, group1); | |
| assertThat(groups).containsEntry(b, group2); | |
| assertThat(groups).containsEntry(c, group2); | |
| assertThat(groups.asMap()).containsEntry(m, ImmutableSet.of(group1, group2)); | |
| } | |
| @Test | |
| public void collectGroupsForMultipleTipsInParallel() throws Exception { | |
| RevCommit branchTip = tr.commit().create(); | |
| RevCommit a = tr.commit().parent(branchTip).create(); | |
| RevCommit b = tr.commit().parent(a).create(); | |
| RevCommit c = tr.commit().parent(branchTip).create(); | |
| RevCommit d = tr.commit().parent(c).create(); | |
| RevWalk rw = newWalk(b, branchTip); | |
| rw.markStart(rw.parseCommit(d)); | |
| // Schema upgrade case: all commits are existing patch sets, but none have | |
| // groups assigned yet. | |
| SortedSetMultimap<ObjectId, String> groups = collectGroups(rw, groups()); | |
| assertThat(groups).containsEntry(a, a.name()); | |
| assertThat(groups).containsEntry(b, a.name()); | |
| assertThat(groups).containsEntry(c, c.name()); | |
| assertThat(groups).containsEntry(d, c.name()); | |
| } | |
| // TODO(dborowitz): Tests for octopus merges. | |
| private static PatchSet.Id psId(int c, int p) { | |
| return PatchSet.id(Change.id(c), p); | |
| } | |
| private static void createRef(PatchSet.Id psId, ObjectId id, TestRepository<?> tr) | |
| throws IOException { | |
| RefUpdate ru = tr.getRepository().updateRef(psId.toRefName()); | |
| ru.setNewObjectId(id); | |
| assertThat(ru.update()).isEqualTo(RefUpdate.Result.NEW); | |
| } | |
| private RevWalk newWalk(ObjectId start, ObjectId branchTip) throws Exception { | |
| // Match RevWalk conditions from ReceiveCommits. | |
| RevWalk rw = new RevWalk(tr.getRepository()); | |
| rw.sort(RevSort.TOPO); | |
| rw.sort(RevSort.REVERSE, true); | |
| rw.markStart(rw.parseCommit(start)); | |
| rw.markUninteresting(rw.parseCommit(branchTip)); | |
| return rw; | |
| } | |
| private SortedSetMultimap<ObjectId, String> collectGroups( | |
| RevWalk rw, ImmutableListMultimap.Builder<PatchSet.Id, String> groupLookup) throws Exception { | |
| ImmutableListMultimap<PatchSet.Id, String> groups = groupLookup.build(); | |
| GroupCollector gc = | |
| new GroupCollector( | |
| ReceivePackRefCache.noCache(tr.getRepository().getRefDatabase()), (s) -> groups.get(s)); | |
| RevCommit c; | |
| while ((c = rw.next()) != null) { | |
| gc.visit(c); | |
| } | |
| return gc.getGroups(); | |
| } | |
| private static ImmutableListMultimap.Builder<PatchSet.Id, String> groups() { | |
| return ImmutableListMultimap.builder(); | |
| } | |
| } |