| // 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.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<SubmoduleSubscription>(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"); |
| |
| final List<SubmoduleSubscription> subscriptionsToInsert = |
| new ArrayList<SubmoduleSubscription>(); |
| 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"); |
| |
| final List<SubmoduleSubscription> subscriptionsToInsert = |
| new ArrayList<SubmoduleSubscription>(); |
| 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"); |
| |
| final List<SubmoduleSubscription> subscriptionsToInsert = |
| new ArrayList<SubmoduleSubscription>(); |
| 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"); |
| |
| final List<SubmoduleSubscription> subscriptionsToInsert = |
| new ArrayList<SubmoduleSubscription>(); |
| subscriptionsToInsert.add(new SubmoduleSubscription(mergedBranch, |
| new Branch.NameKey(new Project.NameKey("source"), "refs/heads/master"), |
| "source")); |
| |
| final List<SubmoduleSubscription> oldOnesToMergedBranch = |
| new ArrayList<SubmoduleSubscription>(); |
| 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"); |
| |
| final List<SubmoduleSubscription> extractedsubscriptions = |
| new ArrayList<SubmoduleSubscription>(); |
| extractedsubscriptions.add(new SubmoduleSubscription(mergedBranch, |
| new Branch.NameKey(new Project.NameKey("new"), "refs/heads/master"), |
| "new")); |
| extractedsubscriptions.add(old); |
| |
| final List<SubmoduleSubscription> oldOnesToMergedBranch = |
| new ArrayList<SubmoduleSubscription>(); |
| 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"); |
| |
| final List<SubmoduleSubscription> extractedsubscriptions = |
| new ArrayList<SubmoduleSubscription>(); |
| |
| final List<SubmoduleSubscription> oldOnesToMergedBranch = |
| new ArrayList<SubmoduleSubscription>(); |
| 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); |
| codeReviewCommit.change = submittedChange; |
| |
| final Map<Change.Id, CodeReviewCommit> mergedCommits = |
| new HashMap<Change.Id, CodeReviewCommit>(); |
| mergedCommits.put(codeReviewCommit.change.getId(), codeReviewCommit); |
| |
| final List<Change> submitted = new ArrayList<Change>(); |
| 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<SubmoduleSubscription>(Collections |
| .singletonList(new SubmoduleSubscription(targetBranchNameKey, |
| sourceBranchNameKey, "source-project"))); |
| expect(subscriptions.bySubmodule(sourceBranchNameKey)).andReturn( |
| subscribers); |
| |
| expect(repoManager.openRepository(targetBranchNameKey.getParentKey())) |
| .andReturn(targetRepository); |
| |
| Capture<RefUpdate> ruCapture = new Capture<RefUpdate>(); |
| gitRefUpdated.fire(eq(targetBranchNameKey.getParentKey()), |
| capture(ruCapture)); |
| |
| expect(schema.submoduleSubscriptions()).andReturn(subscriptions); |
| final ResultSet<SubmoduleSubscription> emptySubscriptions = |
| new ListResultSet<SubmoduleSubscription>(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); |
| codeReviewCommit.change = submittedChange; |
| |
| final Map<Change.Id, CodeReviewCommit> mergedCommits = |
| new HashMap<Change.Id, CodeReviewCommit>(); |
| mergedCommits.put(codeReviewCommit.change.getId(), codeReviewCommit); |
| |
| final List<Change> submitted = new ArrayList<Change>(); |
| 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<SubmoduleSubscription>(Collections |
| .singletonList(new SubmoduleSubscription(targetBranchNameKey, |
| sourceBranchNameKey, "source-project"))); |
| expect(subscriptions.bySubmodule(sourceBranchNameKey)).andReturn( |
| subscribers); |
| |
| expect(repoManager.openRepository(targetBranchNameKey.getParentKey())) |
| .andReturn(targetRepository); |
| |
| Capture<RefUpdate> ruCapture = new Capture<RefUpdate>(); |
| 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"); |
| |
| final List<SubmoduleSubscription> subscriptionsToInsert = |
| new ArrayList<SubmoduleSubscription>(); |
| 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</code> 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 {@link 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</code> 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 {@link 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</code> |
| * 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<SubmoduleSubscription>(previousSubscriptions)); |
| |
| SortedSet<Project.NameKey> existingProjects = |
| new TreeSet<Project.NameKey>(); |
| |
| 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<SubmoduleSubscription>(); |
| for (SubmoduleSubscription s : extractedSubscriptions) { |
| if (previousSubscriptions.contains(s)) { |
| alreadySubscribeds.add(s); |
| } |
| } |
| |
| final Set<SubmoduleSubscription> subscriptionsToRemove = |
| new HashSet<SubmoduleSubscription>(previousSubscriptions); |
| final List<SubmoduleSubscription> subscriptionsToInsert = |
| new ArrayList<SubmoduleSubscription>(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<SubmoduleSubscription>(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; |
| } |
| } |