blob: 73a705b252f7a20c98c75689d8f53dbbab4c7a44 [file] [log] [blame]
/*
* Copyright (C) 2017 David Pursehouse <david.pursehouse@gmail.com>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.api;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import org.eclipse.jgit.api.ResetCommand.ResetType;
import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.submodule.SubmoduleStatus;
import org.eclipse.jgit.submodule.SubmoduleStatusType;
import org.eclipse.jgit.submodule.SubmoduleWalk;
import org.eclipse.jgit.transport.FetchResult;
import org.eclipse.jgit.transport.RefSpec;
import org.junit.Before;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;
@RunWith(Theories.class)
public class FetchAndPullCommandsRecurseSubmodulesTest extends RepositoryTestCase {
@DataPoints
public static boolean[] useFetch = { true, false };
private Git git;
private Git git2;
private Git sub1Git;
private Git sub2Git;
private RevCommit commit1;
private RevCommit commit2;
private ObjectId submodule1Head;
private ObjectId submodule2Head;
private final RefSpec REFSPEC = new RefSpec("refs/heads/master");
private final String REMOTE = "origin";
private final String PATH = "sub";
@Before
public void setUpSubmodules() throws Exception {
git = new Git(db);
// Create submodule 1
File submodule1 = createTempDirectory(
"testCloneRepositoryWithNestedSubmodules1");
sub1Git = Git.init().setDirectory(submodule1).call();
assertNotNull(sub1Git);
Repository sub1 = sub1Git.getRepository();
assertNotNull(sub1);
addRepoToClose(sub1);
String file = "file.txt";
write(new File(sub1.getWorkTree(), file), "content");
sub1Git.add().addFilepattern(file).call();
RevCommit commit = sub1Git.commit().setMessage("create file").call();
assertNotNull(commit);
// Create submodule 2
File submodule2 = createTempDirectory(
"testCloneRepositoryWithNestedSubmodules2");
sub2Git = Git.init().setDirectory(submodule2).call();
assertNotNull(sub2Git);
Repository sub2 = sub2Git.getRepository();
assertNotNull(sub2);
addRepoToClose(sub2);
write(new File(sub2.getWorkTree(), file), "content");
sub2Git.add().addFilepattern(file).call();
RevCommit sub2Head = sub2Git.commit().setMessage("create file").call();
assertNotNull(sub2Head);
// Add submodule 2 to submodule 1
Repository r2 = sub1Git.submoduleAdd().setPath(PATH)
.setURI(sub2.getDirectory().toURI().toString()).call();
assertNotNull(r2);
addRepoToClose(r2);
RevCommit sub1Head = sub1Git.commit().setAll(true)
.setMessage("Adding submodule").call();
assertNotNull(sub1Head);
// Add submodule 1 to default repository
Repository r1 = git.submoduleAdd().setPath(PATH)
.setURI(sub1.getDirectory().toURI().toString()).call();
assertNotNull(r1);
addRepoToClose(r1);
assertNotNull(git.commit().setAll(true).setMessage("Adding submodule")
.call());
// Clone default repository and include submodules
File directory = createTempDirectory(
"testCloneRepositoryWithNestedSubmodules");
CloneCommand clone = Git.cloneRepository();
clone.setDirectory(directory);
clone.setCloneSubmodules(true);
clone.setURI(git.getRepository().getDirectory().toURI().toString());
git2 = clone.call();
addRepoToClose(git2.getRepository());
assertNotNull(git2);
// Record current FETCH_HEAD of submodules
try (SubmoduleWalk walk = SubmoduleWalk
.forIndex(git2.getRepository())) {
assertTrue(walk.next());
Repository r = walk.getRepository();
submodule1Head = r.resolve(Constants.FETCH_HEAD);
try (SubmoduleWalk walk2 = SubmoduleWalk.forIndex(r)) {
assertTrue(walk2.next());
submodule2Head = walk2.getRepository()
.resolve(Constants.FETCH_HEAD);
}
}
// Commit in submodule 1
JGitTestUtil.writeTrashFile(r1, "f1.txt", "test");
sub1Git.add().addFilepattern("f1.txt").call();
commit1 = sub1Git.commit().setMessage("new commit").call();
// Commit in submodule 2
JGitTestUtil.writeTrashFile(r2, "f2.txt", "test");
sub2Git.add().addFilepattern("f2.txt").call();
commit2 = sub2Git.commit().setMessage("new commit").call();
}
@Theory
public void shouldNotFetchSubmodulesWhenNo(boolean fetch) throws Exception {
FetchResult result = execute(FetchRecurseSubmodulesMode.NO, fetch);
assertTrue(result.submoduleResults().isEmpty());
assertSubmoduleFetchHeads(submodule1Head, submodule2Head);
}
@Theory
public void shouldFetchSubmodulesWhenYes(boolean fetch) throws Exception {
FetchResult result = execute(FetchRecurseSubmodulesMode.YES, fetch);
assertTrue(result.submoduleResults().containsKey("sub"));
FetchResult subResult = result.submoduleResults().get("sub");
assertTrue(subResult.submoduleResults().containsKey("sub"));
assertSubmoduleFetchHeads(commit1, commit2);
}
@Theory
public void shouldFetchSubmodulesWhenOnDemandAndRevisionChanged(
boolean fetch) throws Exception {
RevCommit update = updateSubmoduleRevision();
FetchResult result = execute(FetchRecurseSubmodulesMode.ON_DEMAND,
fetch);
// The first submodule should have been updated
assertTrue(result.submoduleResults().containsKey("sub"));
FetchResult subResult = result.submoduleResults().get("sub");
// The second submodule should not get updated
assertTrue(subResult.submoduleResults().isEmpty());
assertSubmoduleFetchHeads(commit1, submodule2Head);
// After fetch the parent repo's fetch head should be the commit
// that updated the submodule.
assertEquals(update,
git2.getRepository().resolve(Constants.FETCH_HEAD));
}
@Theory
public void shouldNotFetchSubmodulesWhenOnDemandAndRevisionNotChanged(
boolean fetch) throws Exception {
FetchResult result = execute(FetchRecurseSubmodulesMode.ON_DEMAND,
fetch);
assertTrue(result.submoduleResults().isEmpty());
assertSubmoduleFetchHeads(submodule1Head, submodule2Head);
}
@Theory
public void shouldNotFetchSubmodulesWhenSubmoduleConfigurationSetToNo(
boolean fetch) throws Exception {
StoredConfig config = git2.getRepository().getConfig();
config.setEnum(ConfigConstants.CONFIG_SUBMODULE_SECTION, PATH,
ConfigConstants.CONFIG_KEY_FETCH_RECURSE_SUBMODULES,
FetchRecurseSubmodulesMode.NO);
config.save();
updateSubmoduleRevision();
FetchResult result = execute(null, fetch);
assertTrue(result.submoduleResults().isEmpty());
assertSubmoduleFetchHeads(submodule1Head, submodule2Head);
}
@Theory
public void shouldFetchSubmodulesWhenSubmoduleConfigurationSetToYes(
boolean fetch) throws Exception {
StoredConfig config = git2.getRepository().getConfig();
config.setEnum(ConfigConstants.CONFIG_SUBMODULE_SECTION, PATH,
ConfigConstants.CONFIG_KEY_FETCH_RECURSE_SUBMODULES,
FetchRecurseSubmodulesMode.YES);
config.save();
FetchResult result = execute(null, fetch);
assertTrue(result.submoduleResults().containsKey("sub"));
FetchResult subResult = result.submoduleResults().get("sub");
assertTrue(subResult.submoduleResults().containsKey("sub"));
assertSubmoduleFetchHeads(commit1, commit2);
}
@Theory
public void shouldNotFetchSubmodulesWhenFetchConfigurationSetToNo(
boolean fetch) throws Exception {
StoredConfig config = git2.getRepository().getConfig();
config.setEnum(ConfigConstants.CONFIG_FETCH_SECTION, null,
ConfigConstants.CONFIG_KEY_RECURSE_SUBMODULES,
FetchRecurseSubmodulesMode.NO);
config.save();
updateSubmoduleRevision();
FetchResult result = execute(null, fetch);
assertTrue(result.submoduleResults().isEmpty());
assertSubmoduleFetchHeads(submodule1Head, submodule2Head);
}
@Theory
public void shouldFetchSubmodulesWhenFetchConfigurationSetToYes(
boolean fetch) throws Exception {
StoredConfig config = git2.getRepository().getConfig();
config.setEnum(ConfigConstants.CONFIG_FETCH_SECTION, null,
ConfigConstants.CONFIG_KEY_RECURSE_SUBMODULES,
FetchRecurseSubmodulesMode.YES);
config.save();
FetchResult result = execute(null, fetch);
assertTrue(result.submoduleResults().containsKey("sub"));
FetchResult subResult = result.submoduleResults().get("sub");
assertTrue(subResult.submoduleResults().containsKey("sub"));
assertSubmoduleFetchHeads(commit1, commit2);
}
private RevCommit updateSubmoduleRevision() throws Exception {
// Fetch the submodule in the original git and reset it to
// the commit that was created
try (SubmoduleWalk w = SubmoduleWalk.forIndex(git.getRepository())) {
assertTrue(w.next());
try (Git g = new Git(w.getRepository())) {
g.fetch().setRemote(REMOTE).setRefSpecs(REFSPEC).call();
g.reset().setMode(ResetType.HARD).setRef(commit1.name()).call();
}
}
// Submodule index Id should be same as before, but head Id should be
// updated to the new commit, and status should be "checked out".
SubmoduleStatus subStatus = git.submoduleStatus().call().get("sub");
assertEquals(submodule1Head, subStatus.getIndexId());
assertEquals(commit1, subStatus.getHeadId());
assertEquals(SubmoduleStatusType.REV_CHECKED_OUT, subStatus.getType());
// Add and commit the submodule status
git.add().addFilepattern("sub").call();
RevCommit update = git.commit().setMessage("update sub").call();
// Both submodule index and head should now be at the new commit, and
// the status should be "initialized".
subStatus = git.submoduleStatus().call().get("sub");
assertEquals(commit1, subStatus.getIndexId());
assertEquals(commit1, subStatus.getHeadId());
assertEquals(SubmoduleStatusType.INITIALIZED, subStatus.getType());
return update;
}
private FetchResult execute(FetchRecurseSubmodulesMode mode, boolean fetch)
throws Exception {
FetchResult result;
if (fetch) {
result = git2.fetch().setRemote(REMOTE).setRefSpecs(REFSPEC)
.setRecurseSubmodules(mode).call();
} else {
// For the purposes of this test we don't need to care about the
// pull result, or the result of pull with merge. We are only
// interested in checking whether or not the submodules were updated
// as expected. Setting to rebase makes it easier to assert about
// the state of the parent repository head, i.e. we know it should
// be at the submodule update commit, and don't need to consider a
// merge commit created by the pull.
result = git2.pull().setRemote(REMOTE).setRebase(true)
.setRecurseSubmodules(mode).call().getFetchResult();
}
assertNotNull(result);
return result;
}
private void assertSubmoduleFetchHeads(ObjectId expectedHead1,
ObjectId expectedHead2) throws Exception {
try (SubmoduleWalk walk = SubmoduleWalk
.forIndex(git2.getRepository())) {
assertTrue(walk.next());
Repository r = walk.getRepository();
ObjectId newHead1 = r.resolve(Constants.FETCH_HEAD);
ObjectId newHead2;
try (SubmoduleWalk walk2 = SubmoduleWalk.forIndex(r)) {
assertTrue(walk2.next());
newHead2 = walk2.getRepository().resolve(Constants.FETCH_HEAD);
}
assertEquals(expectedHead1, newHead1);
assertEquals(expectedHead2, newHead2);
}
}
}