diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java index b4abb53d8..63fe4212b 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java @@ -67,6 +67,7 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevBlob; @@ -96,10 +97,15 @@ public void setUp() throws Exception { writeTrashFile("Test.txt", "Hello world"); git.add().addFilepattern("Test.txt").call(); git.commit().setMessage("Initial commit").call(); - git.tag().setName("tag-initial").setMessage("Tag initial").call(); + Ref head = git.tag().setName("tag-initial").setMessage("Tag initial") + .call(); // create a test branch and switch to it git.checkout().setCreateBranch(true).setName("test").call(); + // create a non-standard ref + RefUpdate ru = db.updateRef("refs/meta/foo/bar"); + ru.setNewObjectId(head.getObjectId()); + ru.update(); // commit something on the test branch writeTrashFile("Test.txt", "Some change"); @@ -424,6 +430,32 @@ public void testBareCloneRepositoryOnlyOneBranch() throws Exception { specs.get(0)); } + @Test + public void testBareCloneRepositoryMirror() throws Exception { + File directory = createTempDirectory( + "testCloneRepositoryWithBranch_mirror"); + CloneCommand command = Git.cloneRepository(); + command.setBranch("refs/heads/master"); + command.setMirror(true); // implies bare repository + command.setDirectory(directory); + command.setURI(fileUri()); + Git git2 = command.call(); + addRepoToClose(git2.getRepository()); + assertNotNull(git2); + assertNotNull(git2.getRepository().resolve("tag-for-blob")); + assertNotNull(git2.getRepository().resolve("tag-initial")); + assertEquals(git2.getRepository().getFullBranch(), "refs/heads/master"); + assertEquals("refs/heads/master, refs/heads/test", allRefNames( + git2.branchList().setListMode(ListMode.ALL).call())); + assertNotNull(git2.getRepository().exactRef("refs/meta/foo/bar")); + RemoteConfig cfg = new RemoteConfig(git2.getRepository().getConfig(), + Constants.DEFAULT_REMOTE_NAME); + List specs = cfg.getFetchRefSpecs(); + assertEquals(1, specs.size()); + assertEquals(new RefSpec("+refs/*:refs/*"), + specs.get(0)); + } + @Test public void testCloneRepositoryOnlyOneTag() throws Exception { File directory = createTempDirectory("testCloneRepositoryWithBranch"); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java index 9f63d0f00..809f2d111 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java @@ -77,8 +77,8 @@ import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.TagOpt; import org.eclipse.jgit.transport.URIish; -import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.FileUtils; /** * Clone a repository into a new working directory @@ -104,7 +104,7 @@ public class CloneCommand extends TransportCommand { private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; - private boolean cloneAllBranches; + private FETCH_TYPE fetchType = FETCH_TYPE.ALL_BRANCHES; private boolean cloneSubmodules; @@ -118,6 +118,10 @@ public class CloneCommand extends TransportCommand { private boolean gitDirExistsInitially; + private enum FETCH_TYPE { + MULTIPLE_BRANCHES, ALL_BRANCHES, MIRROR + } + /** * Callback for status of clone operation. * @@ -282,12 +286,11 @@ private FetchResult fetch(Repository clonedRepo, URIish u) RemoteConfig config = new RemoteConfig(clonedRepo.getConfig(), remote); config.addURI(u); - final String dst = (bare ? Constants.R_HEADS : Constants.R_REMOTES - + config.getName() + '/') + '*'; - boolean fetchAll = cloneAllBranches || branchesToClone == null - || branchesToClone.isEmpty(); + boolean fetchAll = fetchType == FETCH_TYPE.ALL_BRANCHES + || fetchType == FETCH_TYPE.MIRROR; - config.setFetchRefSpecs(calculateRefSpecs(fetchAll, dst)); + config.setFetchRefSpecs(calculateRefSpecs(fetchType, config.getName())); + config.setMirror(fetchType == FETCH_TYPE.MIRROR); config.update(clonedRepo.getConfig()); clonedRepo.getConfig().save(); @@ -302,26 +305,33 @@ private FetchResult fetch(Repository clonedRepo, URIish u) return command.call(); } - private List calculateRefSpecs(boolean fetchAll, String dst) { - RefSpec heads = new RefSpec(); - heads = heads.setForceUpdate(true); - heads = heads.setSourceDestination(Constants.R_HEADS + '*', dst); + private List calculateRefSpecs(FETCH_TYPE type, + String remoteName) { List specs = new ArrayList<>(); - if (!fetchAll) { - RefSpec tags = new RefSpec(); - tags = tags.setForceUpdate(true); - tags = tags.setSourceDestination(Constants.R_TAGS + '*', - Constants.R_TAGS + '*'); - for (String selectedRef : branchesToClone) { - if (heads.matchSource(selectedRef)) { - specs.add(heads.expandFromSource(selectedRef)); - } else if (tags.matchSource(selectedRef)) { - specs.add(tags.expandFromSource(selectedRef)); - } - } + if (type == FETCH_TYPE.MIRROR) { + specs.add(new RefSpec().setForceUpdate(true).setSourceDestination( + Constants.R_REFS + '*', Constants.R_REFS + '*')); } else { - // We'll fetch the tags anyway. - specs.add(heads); + RefSpec heads = new RefSpec(); + heads = heads.setForceUpdate(true); + final String dst = (bare ? Constants.R_HEADS + : Constants.R_REMOTES + remoteName + '/') + '*'; + heads = heads.setSourceDestination(Constants.R_HEADS + '*', dst); + if (type == FETCH_TYPE.MULTIPLE_BRANCHES) { + RefSpec tags = new RefSpec().setForceUpdate(true) + .setSourceDestination(Constants.R_TAGS + '*', + Constants.R_TAGS + '*'); + for (String selectedRef : branchesToClone) { + if (heads.matchSource(selectedRef)) { + specs.add(heads.expandFromSource(selectedRef)); + } else if (tags.matchSource(selectedRef)) { + specs.add(tags.expandFromSource(selectedRef)); + } + } + } else { + // We'll fetch the tags anyway. + specs.add(heads); + } } return specs; } @@ -609,7 +619,31 @@ public CloneCommand setProgressMonitor(ProgressMonitor monitor) { * @return {@code this} */ public CloneCommand setCloneAllBranches(boolean cloneAllBranches) { - this.cloneAllBranches = cloneAllBranches; + this.fetchType = cloneAllBranches ? FETCH_TYPE.ALL_BRANCHES + : this.fetchType; + return this; + } + + /** + * Set up a mirror of the source repository. This implies that a bare + * repository will be created. Compared to {@link #setBare}, + * {@code #setMirror} not only maps local branches of the source to local + * branches of the target, it maps all refs (including remote-tracking + * branches, notes etc.) and sets up a refspec configuration such that all + * these refs are overwritten by a git remote update in the target + * repository. + * + * @param mirror + * whether to mirror all refs from the source repository + * + * @return {@code this} + * @since 5.6 + */ + public CloneCommand setMirror(boolean mirror) { + if (mirror) { + this.fetchType = FETCH_TYPE.MIRROR; + setBare(true); + } return this; } @@ -641,7 +675,13 @@ public CloneCommand setCloneSubmodules(boolean cloneSubmodules) { * @return {@code this} */ public CloneCommand setBranchesToClone(Collection branchesToClone) { - this.branchesToClone = branchesToClone; + if (branchesToClone == null || branchesToClone.isEmpty()) { + // fallback to default + fetchType = FETCH_TYPE.ALL_BRANCHES; + } else { + this.fetchType = FETCH_TYPE.MULTIPLE_BRANCHES; + this.branchesToClone = branchesToClone; + } return this; }