// Copyright (C) 2011 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 org.easymock.EasyMock.capture; import static org.easymock.EasyMock.createStrictMock; import static org.easymock.EasyMock.eq; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; import static org.junit.Assert.assertEquals; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Branch; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.SubmoduleSubscription; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.reviewdb.server.SubmoduleSubscriptionAccess; import com.google.gerrit.server.extensions.events.GitReferenceUpdated; import com.google.gerrit.server.util.TimeUtil; import com.google.gwtorm.client.KeyUtil; import com.google.gwtorm.server.ListResultSet; import com.google.gwtorm.server.ResultSet; import com.google.gwtorm.server.SchemaFactory; import com.google.gwtorm.server.StandardKeyEncoder; import com.google.inject.Provider; import org.easymock.Capture; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; public class SubmoduleOpTest extends LocalDiskRepositoryTestCase { static { KeyUtil.setEncoderImpl(new StandardKeyEncoder()); } private static final String newLine = System.getProperty("line.separator"); private SchemaFactory<ReviewDb> schemaFactory; private SubmoduleSubscriptionAccess subscriptions; private ReviewDb schema; private Provider<String> urlProvider; private GitRepositoryManager repoManager; private GitReferenceUpdated gitRefUpdated; @SuppressWarnings("unchecked") @Override @Before public void setUp() throws Exception { super.setUp(); schemaFactory = createStrictMock(SchemaFactory.class); schema = createStrictMock(ReviewDb.class); subscriptions = createStrictMock(SubmoduleSubscriptionAccess.class); urlProvider = createStrictMock(Provider.class); repoManager = createStrictMock(GitRepositoryManager.class); gitRefUpdated = createStrictMock(GitReferenceUpdated.class); } private void doReplay() { replay(schemaFactory, schema, subscriptions, urlProvider, repoManager, gitRefUpdated); } private void doVerify() { verify(schemaFactory, schema, subscriptions, urlProvider, repoManager, gitRefUpdated); } /** * It tests Submodule.update in the scenario a merged commit is an empty one * (it does not have a .gitmodules file) and the project the commit was merged * is not a submodule of other project. * * @throws Exception If an exception occurs. */ @Test public void testEmptyCommit() throws Exception { expect(schemaFactory.open()).andReturn(schema); final Repository realDb = createWorkRepository(); final Git git = new Git(realDb); final RevCommit mergeTip = git.commit().setMessage("test").call(); final Branch.NameKey branchNameKey = new Branch.NameKey(new Project.NameKey("test-project"), "test-branch"); expect(urlProvider.get()).andReturn("http://localhost:8080"); expect(schema.submoduleSubscriptions()).andReturn(subscriptions); final ResultSet<SubmoduleSubscription> emptySubscriptions = new ListResultSet<>(new ArrayList<SubmoduleSubscription>()); expect(subscriptions.bySubmodule(branchNameKey)).andReturn( emptySubscriptions); schema.close(); doReplay(); final SubmoduleOp submoduleOp = new SubmoduleOp(branchNameKey, mergeTip, new RevWalk(realDb), urlProvider, schemaFactory, realDb, null, new ArrayList<Change>(), null, null, null, null); submoduleOp.update(); doVerify(); } /** * It tests SubmoduleOp.update in a scenario considering: * <ul> * <li>no subscriptions existing to destination project</li> * <li>a commit is merged to "dest-project"</li> * <li>commit contains .gitmodules file with content</li> * </ul> * * <pre> * [submodule "source"] * path = source * url = http://localhost:8080/source * branch = . * </pre> * <p> * It expects to insert a new row in subscriptions table. The row inserted * specifies: * <ul> * <li>target "dest-project" on branch "refs/heads/master"</li> * <li>source "a" on branch "refs/heads/master"</li> * <li>path "a"</li> * </ul> * </p> * * @throws Exception If an exception occurs. */ @Test public void testNewSubscriptionToDotBranchValue() throws Exception { doOneSubscriptionInsert(buildSubmoduleSection("source", "source", "http://localhost:8080/source", ".").toString(), "refs/heads/master"); doVerify(); } /** * It tests SubmoduleOp.update in a scenario considering: * <ul> * <li>no subscriptions existing to destination project</li> * <li>a commit is merged to "dest-project"</li> * <li>commit contains .gitmodules file with content</li> * </ul> * * <pre> * [submodule "source"] * path = source * url = http://localhost:8080/source * branch = refs/heads/master * </pre> * * <p> * It expects to insert a new row in subscriptions table. The row inserted * specifies: * <ul> * <li>target "dest-project" on branch "refs/heads/master"</li> * <li>source "source" on branch "refs/heads/master"</li> * <li>path "source"</li> * </ul> * </p> * * @throws Exception If an exception occurs. */ @Test public void testNewSubscriptionToSameBranch() throws Exception { doOneSubscriptionInsert(buildSubmoduleSection("source", "source", "http://localhost:8080/source", "refs/heads/master").toString(), "refs/heads/master"); doVerify(); } /** * It tests SubmoduleOp.update in a scenario considering: * <ul> * <li>no subscriptions existing to destination project</li> * <li>a commit is merged to "dest-project"</li> * <li>commit contains .gitmodules file with content</li> * </ul> * * <pre> * [submodule "source"] * path = source * url = http://localhost:8080/source * branch = refs/heads/test * </pre> * <p> * It expects to insert a new row in subscriptions table. The row inserted * specifies: * <ul> * <li>target "dest-project" on branch "refs/heads/master"</li> * <li>source "source" on branch "refs/heads/test"</li> * <li>path "source"</li> * </ul> * </p> * * @throws Exception If an exception occurs. */ @Test public void testNewSubscriptionToDifferentBranch() throws Exception { doOneSubscriptionInsert(buildSubmoduleSection("source", "source", "http://localhost:8080/source", "refs/heads/test").toString(), "refs/heads/test"); doVerify(); } /** * It tests SubmoduleOp.update in a scenario considering: * <ul> * <li>no subscriptions existing to destination project</li> * <li>a commit is merged to "dest-project" in "refs/heads/master" branch</li> * <li>commit contains .gitmodules file with content</li> * </ul> * * <pre> * [submodule "source-a"] * path = source-a * url = http://localhost:8080/source-a * branch = . * * [submodule "source-b"] * path = source-b * url = http://localhost:8080/source-b * branch = . * </pre> * <p> * It expects to insert new rows in subscriptions table. The rows inserted * specifies: * <ul> * <li>target "dest-project" on branch "refs/heads/master"</li> * <li>source "source-a" on branch "refs/heads/master" with "source-a" path</li> * <li>source "source-b" on branch "refs/heads/master" with "source-b" path</li> * </ul> * </p> * * @throws Exception If an exception occurs. */ @Test public void testNewSubscriptionsWithDotBranchValue() throws Exception { final StringBuilder sb = buildSubmoduleSection("source-a", "source-a", "http://localhost:8080/source-a", "."); sb.append(buildSubmoduleSection("source-b", "source-b", "http://localhost:8080/source-b", ".")); final Branch.NameKey mergedBranch = new Branch.NameKey(new Project.NameKey("dest-project"), "refs/heads/master"); List<SubmoduleSubscription> subscriptionsToInsert = new ArrayList<>(); subscriptionsToInsert .add(new SubmoduleSubscription(mergedBranch, new Branch.NameKey( new Project.NameKey("source-a"), "refs/heads/master"), "source-a")); subscriptionsToInsert .add(new SubmoduleSubscription(mergedBranch, new Branch.NameKey( new Project.NameKey("source-b"), "refs/heads/master"), "source-b")); doOnlySubscriptionInserts(sb.toString(), mergedBranch, subscriptionsToInsert); doVerify(); } /** * It tests SubmoduleOp.update in a scenario considering: * <ul> * <li>no subscriptions existing to destination project</li> * <li>a commit is merged to "dest-project" in "refs/heads/master" branch</li> * <li>commit contains .gitmodules file with content</li> * </ul> * * <pre> * [submodule "source-a"] * path = source-a * url = http://localhost:8080/source-a * branch = . * * [submodule "source-b"] * path = source-b * url = http://localhost:8080/source-b * branch = refs/heads/master * </pre> * <p> * It expects to insert new rows in subscriptions table. The rows inserted * specifies: * <ul> * <li>target "dest-project" on branch "refs/heads/master"</li> * <li>source "source-a" on branch "refs/heads/master" with "source-a" path</li> * <li>source "source-b" on branch "refs/heads/master" with "source-b" path</li> * </ul> * </p> * * @throws Exception If an exception occurs. */ @Test public void testNewSubscriptionsDotAndSameBranchValues() throws Exception { final StringBuilder sb = buildSubmoduleSection("source-a", "source-a", "http://localhost:8080/source-a", "."); sb.append(buildSubmoduleSection("source-b", "source-b", "http://localhost:8080/source-b", "refs/heads/master")); final Branch.NameKey mergedBranch = new Branch.NameKey(new Project.NameKey("dest-project"), "refs/heads/master"); List<SubmoduleSubscription> subscriptionsToInsert = new ArrayList<>(); subscriptionsToInsert .add(new SubmoduleSubscription(mergedBranch, new Branch.NameKey( new Project.NameKey("source-a"), "refs/heads/master"), "source-a")); subscriptionsToInsert .add(new SubmoduleSubscription(mergedBranch, new Branch.NameKey( new Project.NameKey("source-b"), "refs/heads/master"), "source-b")); doOnlySubscriptionInserts(sb.toString(), mergedBranch, subscriptionsToInsert); doVerify(); } /** * It tests SubmoduleOp.update in a scenario considering: * <ul> * <li>no subscriptions existing to destination project</li> * <li>a commit is merged to "dest-project" in "refs/heads/master" branch</li> * <li>commit contains .gitmodules file with content</li> * * <pre> * [submodule "source-a"] * path = source-a * url = http://localhost:8080/source-a * branch = refs/heads/test-a * * [submodule "source-b"] * path = source-b * url = http://localhost:8080/source-b * branch = refs/heads/test-b * </pre> * * <p> * It expects to insert new rows in subscriptions table. The rows inserted * specifies: * <ul> * <li>target "dest-project" on branch "refs/heads/master"</li> * <li>source "source-a" on branch "refs/heads/test-a" with "source-a" path</li> * <li>source "source-b" on branch "refs/heads/test-b" with "source-b" path</li> * </ul> * </p> * * @throws Exception If an exception occurs. */ @Test public void testNewSubscriptionsSpecificBranchValues() throws Exception { final StringBuilder sb = buildSubmoduleSection("source-a", "source-a", "http://localhost:8080/source-a", "refs/heads/test-a"); sb.append(buildSubmoduleSection("source-b", "source-b", "http://localhost:8080/source-b", "refs/heads/test-b")); final Branch.NameKey mergedBranch = new Branch.NameKey(new Project.NameKey("dest-project"), "refs/heads/master"); List<SubmoduleSubscription> subscriptionsToInsert = new ArrayList<>(); subscriptionsToInsert .add(new SubmoduleSubscription(mergedBranch, new Branch.NameKey( new Project.NameKey("source-a"), "refs/heads/test-a"), "source-a")); subscriptionsToInsert .add(new SubmoduleSubscription(mergedBranch, new Branch.NameKey( new Project.NameKey("source-b"), "refs/heads/test-b"), "source-b")); doOnlySubscriptionInserts(sb.toString(), mergedBranch, subscriptionsToInsert); doVerify(); } /** * It tests SubmoduleOp.update in a scenario considering: * <ul> * <li>one subscription existing to destination project/branch</li> * <li>a commit is merged to "dest-project" in "refs/heads/master" branch</li> * <li>commit contains .gitmodules file with content</li> * </ul> * * <pre> * [submodule "source"] * path = source * url = http://localhost:8080/source * branch = refs/heads/master * </pre> * <p> * It expects to insert a new row in subscriptions table. The rows inserted * specifies: * <ul> * <li>target "dest-project" on branch "refs/heads/master"</li> * <li>source "source" on branch "refs/heads/master" with "source" path</li> * </ul> * </p> * <p> * It also expects to remove the row in subscriptions table specifying another * project/branch subscribed to merged branch. This one to be removed is: * <ul> * <li>target "dest-project" on branch "refs/heads/master"</li> * <li>source "old-source" on branch "refs/heads/master" with "old-source" * path</li> * </ul> * </p> * * @throws Exception If an exception occurs. */ @Test public void testSubscriptionsInsertOneRemoveOne() throws Exception { final Branch.NameKey mergedBranch = new Branch.NameKey(new Project.NameKey("dest-project"), "refs/heads/master"); List<SubmoduleSubscription> subscriptionsToInsert = new ArrayList<>(); subscriptionsToInsert.add(new SubmoduleSubscription(mergedBranch, new Branch.NameKey(new Project.NameKey("source"), "refs/heads/master"), "source")); List<SubmoduleSubscription> oldOnesToMergedBranch = new ArrayList<>(); oldOnesToMergedBranch.add(new SubmoduleSubscription(mergedBranch, new Branch.NameKey(new Project.NameKey("old-source"), "refs/heads/master"), "old-source")); doOnlySubscriptionTableOperations(buildSubmoduleSection("source", "source", "http://localhost:8080/source", "refs/heads/master").toString(), mergedBranch, subscriptionsToInsert, oldOnesToMergedBranch); doVerify(); } /** * It tests SubmoduleOp.update in a scenario considering: * <ul> * <li>one subscription existing to destination project/branch with a source * called old on refs/heads/master branch</li> * <li>a commit is merged to "dest-project" in "refs/heads/master" branch</li> * <li> * commit contains .gitmodules file with content</li> * </ul> * * <pre> * [submodule "new"] * path = new * url = http://localhost:8080/new * branch = refs/heads/master * * [submodule "old"] * path = old * url = http://localhost:8080/old * branch = refs/heads/master * </pre> * <p> * It expects to insert a new row in subscriptions table. It should not remove * any row. The rows inserted specifies: * <ul> * <li>target "dest-project" on branch "refs/heads/master"</li> * <li>source "new" on branch "refs/heads/master" with "new" path</li> * </ul> * </p> * * @throws Exception If an exception occurs. */ @Test public void testSubscriptionAddedAndMantainPreviousOne() throws Exception { final StringBuilder sb = buildSubmoduleSection("new", "new", "http://localhost:8080/new", "refs/heads/master"); sb.append(buildSubmoduleSection("old", "old", "http://localhost:8080/old", "refs/heads/master")); final Branch.NameKey mergedBranch = new Branch.NameKey(new Project.NameKey("dest-project"), "refs/heads/master"); final SubmoduleSubscription old = new SubmoduleSubscription(mergedBranch, new Branch.NameKey(new Project.NameKey( "old"), "refs/heads/master"), "old"); List<SubmoduleSubscription> extractedsubscriptions = new ArrayList<>(); extractedsubscriptions.add(new SubmoduleSubscription(mergedBranch, new Branch.NameKey(new Project.NameKey("new"), "refs/heads/master"), "new")); extractedsubscriptions.add(old); List<SubmoduleSubscription> oldOnesToMergedBranch = new ArrayList<>(); oldOnesToMergedBranch.add(old); doOnlySubscriptionTableOperations(sb.toString(), mergedBranch, extractedsubscriptions, oldOnesToMergedBranch); doVerify(); } /** * It tests SubmoduleOp.update in a scenario considering an empty .gitmodules * file is part of a commit to a destination project/branch having two sources * subscribed. * <p> * It expects to remove the subscriptions to destination project/branch. * </p> * * @throws Exception If an exception occurs. */ @Test public void testRemoveSubscriptions() throws Exception { final Branch.NameKey mergedBranch = new Branch.NameKey(new Project.NameKey("dest-project"), "refs/heads/master"); List<SubmoduleSubscription> extractedsubscriptions = new ArrayList<>(); List<SubmoduleSubscription> oldOnesToMergedBranch = new ArrayList<>(); oldOnesToMergedBranch .add(new SubmoduleSubscription(mergedBranch, new Branch.NameKey( new Project.NameKey("source-a"), "refs/heads/master"), "source-a")); oldOnesToMergedBranch .add(new SubmoduleSubscription(mergedBranch, new Branch.NameKey( new Project.NameKey("source-b"), "refs/heads/master"), "source-b")); doOnlySubscriptionTableOperations("", mergedBranch, extractedsubscriptions, oldOnesToMergedBranch); } /** * It tests SubmoduleOp.update in a scenario considering no .gitmodules file * in a merged commit to a destination project/branch that is a source one to * one called "target-project". * <p> * It expects to update the git link called "source-project" to be in target * repository. * </p> * * @throws Exception If an exception occurs. */ @Test public void testOneSubscriberToUpdate() throws Exception { expect(schemaFactory.open()).andReturn(schema); final Repository sourceRepository = createWorkRepository(); final Git sourceGit = new Git(sourceRepository); addRegularFileToIndex("file.txt", "test content", sourceRepository); final RevCommit sourceMergeTip = sourceGit.commit().setMessage("test").call(); final Branch.NameKey sourceBranchNameKey = new Branch.NameKey(new Project.NameKey("source-project"), "refs/heads/master"); final CodeReviewCommit codeReviewCommit = new CodeReviewCommit(sourceMergeTip.toObjectId()); final Change submittedChange = new Change( new Change.Key(sourceMergeTip.toObjectId().getName()), new Change.Id(1), new Account.Id(1), sourceBranchNameKey, TimeUtil.nowTs()); final Map<Change.Id, CodeReviewCommit> mergedCommits = new HashMap<>(); mergedCommits.put(submittedChange.getId(), codeReviewCommit); final List<Change> submitted = new ArrayList<>(); submitted.add(submittedChange); final Repository targetRepository = createWorkRepository(); final Git targetGit = new Git(targetRepository); addGitLinkToIndex("a", sourceMergeTip.copy(), targetRepository); targetGit.commit().setMessage("test").call(); final Branch.NameKey targetBranchNameKey = new Branch.NameKey(new Project.NameKey("target-project"), sourceBranchNameKey.get()); expect(urlProvider.get()).andReturn("http://localhost:8080"); expect(schema.submoduleSubscriptions()).andReturn(subscriptions); final ResultSet<SubmoduleSubscription> subscribers = new ListResultSet<>(Collections .singletonList(new SubmoduleSubscription(targetBranchNameKey, sourceBranchNameKey, "source-project"))); expect(subscriptions.bySubmodule(sourceBranchNameKey)).andReturn( subscribers); expect(repoManager.openRepository(targetBranchNameKey.getParentKey())) .andReturn(targetRepository).anyTimes(); Capture<RefUpdate> ruCapture = new Capture<>(); gitRefUpdated.fire(eq(targetBranchNameKey.getParentKey()), capture(ruCapture)); expect(schema.submoduleSubscriptions()).andReturn(subscriptions); final ResultSet<SubmoduleSubscription> emptySubscriptions = new ListResultSet<>(new ArrayList<SubmoduleSubscription>()); expect(subscriptions.bySubmodule(targetBranchNameKey)).andReturn( emptySubscriptions); schema.close(); final PersonIdent myIdent = new PersonIdent("test-user", "test-user@email.com"); doReplay(); final SubmoduleOp submoduleOp = new SubmoduleOp(sourceBranchNameKey, sourceMergeTip, new RevWalk( sourceRepository), urlProvider, schemaFactory, sourceRepository, new Project(sourceBranchNameKey.getParentKey()), submitted, mergedCommits, myIdent, repoManager, gitRefUpdated); submoduleOp.update(); doVerify(); RefUpdate ru = ruCapture.getValue(); assertEquals(ru.getName(), targetBranchNameKey.get()); } /** * It tests SubmoduleOp.update in a scenario considering established circular * reference in submodule_subscriptions table. * <p> * In the tested scenario there is no .gitmodules file in a merged commit to a * destination project/branch that is a source one to one called * "target-project". * <p> * submodule_subscriptions table will be incorrect due source appearing as a * subscriber or target-project: according to database target-project has as * source the source-project, and source-project has as source the * target-project. * <p> * It expects to update the git link called "source-project" to be in target * repository and ignoring the incorrect row in database establishing the * circular reference. * </p> * * @throws Exception If an exception occurs. */ @Test public void testAvoidingCircularReference() throws Exception { expect(schemaFactory.open()).andReturn(schema); final Repository sourceRepository = createWorkRepository(); final Git sourceGit = new Git(sourceRepository); addRegularFileToIndex("file.txt", "test content", sourceRepository); final RevCommit sourceMergeTip = sourceGit.commit().setMessage("test").call(); final Branch.NameKey sourceBranchNameKey = new Branch.NameKey(new Project.NameKey("source-project"), "refs/heads/master"); final CodeReviewCommit codeReviewCommit = new CodeReviewCommit(sourceMergeTip.toObjectId()); final Change submittedChange = new Change( new Change.Key(sourceMergeTip.toObjectId().getName()), new Change.Id(1), new Account.Id(1), sourceBranchNameKey, TimeUtil.nowTs()); final Map<Change.Id, CodeReviewCommit> mergedCommits = new HashMap<>(); mergedCommits.put(submittedChange.getId(), codeReviewCommit); final List<Change> submitted = new ArrayList<>(); submitted.add(submittedChange); final Repository targetRepository = createWorkRepository(); final Git targetGit = new Git(targetRepository); addGitLinkToIndex("a", sourceMergeTip.copy(), targetRepository); targetGit.commit().setMessage("test").call(); final Branch.NameKey targetBranchNameKey = new Branch.NameKey(new Project.NameKey("target-project"), sourceBranchNameKey.get()); expect(urlProvider.get()).andReturn("http://localhost:8080"); expect(schema.submoduleSubscriptions()).andReturn(subscriptions); final ResultSet<SubmoduleSubscription> subscribers = new ListResultSet<>(Collections .singletonList(new SubmoduleSubscription(targetBranchNameKey, sourceBranchNameKey, "source-project"))); expect(subscriptions.bySubmodule(sourceBranchNameKey)).andReturn( subscribers); expect(repoManager.openRepository(targetBranchNameKey.getParentKey())) .andReturn(targetRepository).anyTimes(); Capture<RefUpdate> ruCapture = new Capture<>(); gitRefUpdated.fire(eq(targetBranchNameKey.getParentKey()), capture(ruCapture)); expect(schema.submoduleSubscriptions()).andReturn(subscriptions); final ResultSet<SubmoduleSubscription> incorrectSubscriptions = new ListResultSet<SubmoduleSubscription>(Collections .singletonList(new SubmoduleSubscription(sourceBranchNameKey, targetBranchNameKey, "target-project"))); expect(subscriptions.bySubmodule(targetBranchNameKey)).andReturn( incorrectSubscriptions); schema.close(); final PersonIdent myIdent = new PersonIdent("test-user", "test-user@email.com"); doReplay(); final SubmoduleOp submoduleOp = new SubmoduleOp(sourceBranchNameKey, sourceMergeTip, new RevWalk( sourceRepository), urlProvider, schemaFactory, sourceRepository, new Project(sourceBranchNameKey.getParentKey()), submitted, mergedCommits, myIdent, repoManager, gitRefUpdated); submoduleOp.update(); doVerify(); RefUpdate ru = ruCapture.getValue(); assertEquals(ru.getName(), targetBranchNameKey.get()); } /** * It calls SubmoduleOp.update considering only one insert on Subscriptions * table. * <p> * It considers a commit containing a .gitmodules file was merged in * refs/heads/master of a dest-project. * </p> * <p> * The .gitmodules file content should indicate a source project called * "source". * </p> * * @param gitModulesFileContent The .gitmodules file content. During the test * this file is created, so the commit containing it. * @param sourceBranchName The branch name of source project "pointed by" * .gitmodules file. * @throws Exception If an exception occurs. */ private void doOneSubscriptionInsert(final String gitModulesFileContent, final String sourceBranchName) throws Exception { final Branch.NameKey mergedBranch = new Branch.NameKey(new Project.NameKey("dest-project"), "refs/heads/master"); List<SubmoduleSubscription> subscriptionsToInsert = new ArrayList<>(); subscriptionsToInsert.add(new SubmoduleSubscription(mergedBranch, new Branch.NameKey(new Project.NameKey("source"), sourceBranchName), "source")); doOnlySubscriptionInserts(gitModulesFileContent, mergedBranch, subscriptionsToInsert); } /** * It calls SubmoduleOp.update method considering scenario only inserting new * subscriptions. * <p> * In this test a commit is created and considered merged to * {@code mergedBranch} branch. * </p> * <p> * The destination project the commit was merged is not considered to be a * source of another project (no subscribers found to this project). * </p> * * @param gitModulesFileContent The .gitmodules file content. * @param mergedBranch The {@code Branch.NameKey} instance representing the * project/branch the commit was merged. * @param extractedSubscriptions The subscription rows extracted from * gitmodules file. * @throws Exception If an exception occurs. */ private void doOnlySubscriptionInserts(final String gitModulesFileContent, final Branch.NameKey mergedBranch, final List<SubmoduleSubscription> extractedSubscriptions) throws Exception { doOnlySubscriptionTableOperations(gitModulesFileContent, mergedBranch, extractedSubscriptions, new ArrayList<SubmoduleSubscription>()); } /** * It calls SubmoduleOp.update method considering scenario only updating * Subscriptions table. * <p> * In this test a commit is created and considered merged to * {@code mergedBranch} branch. * </p> * <p> * The destination project the commit was merged is not considered to be a * source of another project (no subscribers found to this project). * </p> * * @param gitModulesFileContent The .gitmodules file content. * @param mergedBranch The {@code Branch.NameKey} instance representing the * project/branch the commit was merged. * @param extractedSubscriptions The subscription rows extracted from * gitmodules file. * @param previousSubscriptions The subscription rows to be considering as * existing and pointing as target to the {@code mergedBranch} * before updating the table. * @throws Exception If an exception occurs. */ private void doOnlySubscriptionTableOperations( final String gitModulesFileContent, final Branch.NameKey mergedBranch, final List<SubmoduleSubscription> extractedSubscriptions, final List<SubmoduleSubscription> previousSubscriptions) throws Exception { expect(schemaFactory.open()).andReturn(schema); final Repository realDb = createWorkRepository(); final Git git = new Git(realDb); addRegularFileToIndex(".gitmodules", gitModulesFileContent, realDb); final RevCommit mergeTip = git.commit().setMessage("test").call(); expect(urlProvider.get()).andReturn("http://localhost:8080").times(2); expect(schema.submoduleSubscriptions()).andReturn(subscriptions); expect(subscriptions.bySuperProject(mergedBranch)).andReturn( new ListResultSet<>(previousSubscriptions)); SortedSet<Project.NameKey> existingProjects = new TreeSet<>(); for (SubmoduleSubscription extracted : extractedSubscriptions) { existingProjects.add(extracted.getSubmodule().getParentKey()); } for (int index = 0; index < extractedSubscriptions.size(); index++) { expect(repoManager.list()).andReturn(existingProjects); } final Set<SubmoduleSubscription> alreadySubscribeds = new HashSet<>(); for (SubmoduleSubscription s : extractedSubscriptions) { if (previousSubscriptions.contains(s)) { alreadySubscribeds.add(s); } } final Set<SubmoduleSubscription> subscriptionsToRemove = new HashSet<>(previousSubscriptions); final List<SubmoduleSubscription> subscriptionsToInsert = new ArrayList<>(extractedSubscriptions); subscriptionsToRemove.removeAll(subscriptionsToInsert); subscriptionsToInsert.removeAll(alreadySubscribeds); if (!subscriptionsToRemove.isEmpty()) { expect(schema.submoduleSubscriptions()).andReturn(subscriptions); subscriptions.delete(subscriptionsToRemove); } expect(schema.submoduleSubscriptions()).andReturn(subscriptions); subscriptions.insert(subscriptionsToInsert); expect(schema.submoduleSubscriptions()).andReturn(subscriptions); expect(subscriptions.bySubmodule(mergedBranch)).andReturn( new ListResultSet<>(new ArrayList<SubmoduleSubscription>())); schema.close(); doReplay(); final SubmoduleOp submoduleOp = new SubmoduleOp(mergedBranch, mergeTip, new RevWalk(realDb), urlProvider, schemaFactory, realDb, new Project(mergedBranch .getParentKey()), new ArrayList<Change>(), null, null, repoManager, null); submoduleOp.update(); } /** * It creates and adds a regular file to git index of a repository. * * @param fileName The file name. * @param content File content. * @param repository The Repository instance. * @throws IOException If an I/O exception occurs. */ private void addRegularFileToIndex(final String fileName, final String content, final Repository repository) throws IOException { final ObjectInserter oi = repository.newObjectInserter(); AnyObjectId objectId = oi.insert(Constants.OBJ_BLOB, Constants.encode(content)); oi.flush(); addEntryToIndex(fileName, FileMode.REGULAR_FILE, objectId, repository); } /** * It creates and adds a git link to git index of a repository. * * @param fileName The file name. * @param objectId The sha-1 value of git link. * @param repository The Repository instance. * @throws IOException If an I/O exception occurs. */ private void addGitLinkToIndex(final String fileName, final AnyObjectId objectId, final Repository repository) throws IOException { addEntryToIndex(fileName, FileMode.GITLINK, objectId, repository); } /** * It adds an entry to index. * * @param path The entry path. * @param fileMode The entry file mode. * @param objectId The ObjectId value of the entry. * @param repository The repository instance. * @throws IOException If an I/O exception occurs. */ private void addEntryToIndex(final String path, final FileMode fileMode, final AnyObjectId objectId, final Repository repository) throws IOException { final DirCacheEntry e = new DirCacheEntry(path); e.setFileMode(fileMode); e.setObjectId(objectId); final DirCacheBuilder dirCacheBuilder = repository.lockDirCache().builder(); dirCacheBuilder.add(e); dirCacheBuilder.commit(); } private static StringBuilder buildSubmoduleSection(final String name, final String path, final String url, final String branch) { final StringBuilder sb = new StringBuilder(); sb.append("[submodule \""); sb.append(name); sb.append("\"]"); sb.append(newLine); sb.append("\tpath = "); sb.append(path); sb.append(newLine); sb.append("\turl = "); sb.append(url); sb.append(newLine); sb.append("\tbranch = "); sb.append(branch); sb.append(newLine); return sb; } }