[pgm] Implement clone using CloneCommand

Change-Id: I56699b7bf9a71f673cb308d3015f51de5b06c1d9
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CloneTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CloneTest.java
new file mode 100644
index 0000000..c65e162
--- /dev/null
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CloneTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2014 Matthias Sohn <matthias.sohn@sap.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.pgm;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.util.List;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.junit.JGitTestUtil;
+import org.eclipse.jgit.junit.MockSystemReader;
+import org.eclipse.jgit.lib.CLIRepositoryTestCase;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.util.SystemReader;
+import org.junit.Before;
+import org.junit.Test;
+
+public class CloneTest extends CLIRepositoryTestCase {
+
+	private Git git;
+
+	@Override
+	@Before
+	public void setUp() throws Exception {
+		super.setUp();
+		git = new Git(db);
+	}
+
+	@Test
+	public void testClone() throws Exception {
+		createInitialCommit();
+
+		File gitDir = db.getDirectory();
+		String sourceURI = gitDir.toURI().toString();
+		File target = createTempDirectory("target");
+		StringBuilder cmd = new StringBuilder("git clone ").append(sourceURI
+				+ " " + target.getPath());
+		String[] result = execute(cmd.toString());
+		assertArrayEquals(new String[] {
+				"Cloning into '" + target.getPath() + "'...",
+						"", "" }, result);
+
+		Git git2 = Git.open(target);
+		List<Ref> branches = git2.branchList().call();
+		assertEquals("expected 1 branch", 1, branches.size());
+	}
+
+	private void createInitialCommit() throws Exception {
+		JGitTestUtil.writeTrashFile(db, "hello.txt", "world");
+		git.add().addFilepattern("hello.txt").call();
+		git.commit().setMessage("Initial commit").call();
+	}
+
+	@Test
+	public void testCloneEmpty() throws Exception {
+		File gitDir = db.getDirectory();
+		String sourceURI = gitDir.toURI().toString();
+		File target = createTempDirectory("target");
+		StringBuilder cmd = new StringBuilder("git clone ").append(sourceURI
+				+ " " + target.getPath());
+		String[] result = execute(cmd.toString());
+		assertArrayEquals(new String[] {
+				"Cloning into '" + target.getPath() + "'...",
+				"warning: You appear to have cloned an empty repository.", "",
+				"" }, result);
+
+		Git git2 = Git.open(target);
+		List<Ref> branches = git2.branchList().call();
+		assertEquals("expected 0 branch", 0, branches.size());
+	}
+
+	@Test
+	public void testCloneIntoCurrentDir() throws Exception {
+		createInitialCommit();
+		File target = createTempDirectory("target");
+
+		MockSystemReader sr = (MockSystemReader) SystemReader.getInstance();
+		sr.setProperty(Constants.OS_USER_DIR, target.getAbsolutePath());
+
+		File gitDir = db.getDirectory();
+		String sourceURI = gitDir.toURI().toString();
+		String name = new URIish(sourceURI).getHumanishName();
+		StringBuilder cmd = new StringBuilder("git clone ").append(sourceURI);
+		String[] result = execute(cmd.toString());
+		assertArrayEquals(new String[] {
+				"Cloning into '" + new File(target, name).getName() + "'...",
+				"", "" }, result);
+		Git git2 = Git.open(new File(target, name));
+		List<Ref> branches = git2.branchList().call();
+		assertEquals("expected 1 branch", 1, branches.size());
+	}
+}
diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
index 7ed77ee..c0ecc83 100644
--- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
+++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
@@ -43,6 +43,8 @@
 changesToBeCommitted=Changes to be committed:
 checkoutConflict=error: Your local changes to the following files would be overwritten by checkout:
 checkoutConflictPathLine=\t{0}
+clonedEmptyRepository=warning: You appear to have cloned an empty repository.
+cloningInto=Cloning into ''{0}''...
 commitLabel=commit
 configFileNotFound=configuration file {0} not found
 conflictingUsageOf_git_dir_andArguments=conflicting usage of --git-dir and arguments
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java
index d9a877cd..88ea120 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java
@@ -44,30 +44,15 @@
 package org.eclipse.jgit.pgm;
 
 import java.io.File;
-import java.io.IOException;
-import java.net.URISyntaxException;
 import java.text.MessageFormat;
 
-import org.eclipse.jgit.dircache.DirCache;
-import org.eclipse.jgit.dircache.DirCacheCheckout;
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.api.CloneCommand;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.InvalidRemoteException;
 import org.eclipse.jgit.lib.Constants;
-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.lib.TextProgressMonitor;
 import org.eclipse.jgit.pgm.internal.CLIText;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
-import org.eclipse.jgit.transport.FetchResult;
-import org.eclipse.jgit.transport.RefSpec;
-import org.eclipse.jgit.transport.RemoteConfig;
-import org.eclipse.jgit.transport.TagOpt;
-import org.eclipse.jgit.transport.Transport;
 import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.util.SystemReader;
 import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.Option;
 
@@ -88,8 +73,6 @@ class Clone extends AbstractFetchCommand {
 	@Argument(index = 1, metaVar = "metaVar_directory")
 	private String localName;
 
-	private Repository dst;
-
 	@Override
 	protected final boolean requiresRepository() {
 		return false;
@@ -101,116 +84,42 @@ protected void run() throws Exception {
 			throw die(CLIText.get().conflictingUsageOf_git_dir_andArguments);
 
 		final URIish uri = new URIish(sourceUri);
+		File localNameF;
 		if (localName == null) {
 			try {
 				localName = uri.getHumanishName();
+				localNameF = new File(SystemReader.getInstance().getProperty(
+						Constants.OS_USER_DIR), localName);
 			} catch (IllegalArgumentException e) {
-				throw die(MessageFormat.format(CLIText.get().cannotGuessLocalNameFrom, sourceUri));
+				throw die(MessageFormat.format(
+						CLIText.get().cannotGuessLocalNameFrom, sourceUri));
 			}
+		} else
+			localNameF = new File(localName);
+
+		if (branch == null)
+			branch = Constants.HEAD;
+
+		CloneCommand command = Git.cloneRepository();
+		command.setURI(sourceUri).setRemote(remoteName)
+				.setNoCheckout(noCheckout).setBranch(branch);
+
+		command.setGitDir(gitdir == null ? null : new File(gitdir));
+		command.setDirectory(localNameF);
+		outw.println(MessageFormat.format(CLIText.get().cloningInto, localName));
+		try {
+			db = command.call().getRepository();
+			if (db.resolve(Constants.HEAD) == null)
+				outw.println(CLIText.get().clonedEmptyRepository);
+		} catch (InvalidRemoteException e) {
+			throw die(MessageFormat.format(CLIText.get().doesNotExist,
+					sourceUri));
+		} finally {
+			if (db != null)
+				db.close();
 		}
-		if (gitdir == null)
-			gitdir = new File(localName, Constants.DOT_GIT).getAbsolutePath();
 
-		dst = new FileRepositoryBuilder().setGitDir(new File(gitdir)).build();
-		dst.create();
-		final StoredConfig dstcfg = dst.getConfig();
-		dstcfg.setBoolean("core", null, "bare", false); //$NON-NLS-1$ //$NON-NLS-2$
-		dstcfg.save();
-		db = dst;
-
-		outw.print(MessageFormat.format(
-				CLIText.get().initializedEmptyGitRepositoryIn, gitdir));
 		outw.println();
 		outw.flush();
-
-		saveRemote(uri);
-		final FetchResult r = runFetch();
-
-		if (!noCheckout) {
-			final Ref checkoutRef;
-			if (branch == null)
-				checkoutRef = guessHEAD(r);
-			else {
-				checkoutRef = r.getAdvertisedRef(Constants.R_HEADS + branch);
-				if (checkoutRef == null)
-					throw die(MessageFormat.format(
-							CLIText.get().noSuchRemoteRef, branch));
-			}
-			doCheckout(checkoutRef);
-		}
-	}
-
-	private void saveRemote(final URIish uri) throws URISyntaxException,
-			IOException {
-		final StoredConfig dstcfg = dst.getConfig();
-		final RemoteConfig rc = new RemoteConfig(dstcfg, remoteName);
-		rc.addURI(uri);
-		rc.addFetchRefSpec(new RefSpec().setForceUpdate(true)
-				.setSourceDestination(Constants.R_HEADS + "*", //$NON-NLS-1$
-						Constants.R_REMOTES + remoteName + "/*")); //$NON-NLS-1$
-		rc.update(dstcfg);
-		dstcfg.save();
-	}
-
-	private FetchResult runFetch() throws URISyntaxException, IOException {
-		final Transport tn = Transport.open(db, remoteName);
-		final FetchResult r;
-		try {
-			tn.setTagOpt(TagOpt.FETCH_TAGS);
-			r = tn.fetch(new TextProgressMonitor(), null);
-		} finally {
-			tn.close();
-		}
-		showFetchResult(r);
-		return r;
-	}
-
-	private static Ref guessHEAD(final FetchResult result) {
-		final Ref idHEAD = result.getAdvertisedRef(Constants.HEAD);
-		Ref head = null;
-		for (final Ref r : result.getAdvertisedRefs()) {
-			final String n = r.getName();
-			if (!n.startsWith(Constants.R_HEADS))
-				continue;
-			if (idHEAD == null || head != null)
-				continue;
-			if (r.getObjectId().equals(idHEAD.getObjectId()))
-				head = r;
-		}
-		if (idHEAD != null && head == null)
-			head = idHEAD;
-		return head;
-	}
-
-	private void doCheckout(final Ref branch) throws IOException {
-		if (branch == null)
-			throw die(CLIText.get().cannotChekoutNoHeadsAdvertisedByRemote);
-		if (!Constants.HEAD.equals(branch.getName())) {
-			RefUpdate u = db.updateRef(Constants.HEAD);
-			u.disableRefLog();
-			u.link(branch.getName());
-		}
-
-		final RevCommit commit = parseCommit(branch);
-		final RefUpdate u = db.updateRef(Constants.HEAD);
-		u.setNewObjectId(commit);
-		u.forceUpdate();
-
-		DirCache dc = db.lockDirCache();
-		DirCacheCheckout co = new DirCacheCheckout(db, dc, commit.getTree());
-		co.checkout();
-	}
-
-	private RevCommit parseCommit(final Ref branch)
-			throws MissingObjectException, IncorrectObjectTypeException,
-			IOException {
-		final RevWalk rw = new RevWalk(db);
-		final RevCommit commit;
-		try {
-			commit = rw.parseCommit(branch.getObjectId());
-		} finally {
-			rw.release();
-		}
-		return commit;
 	}
 }
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
index c42e5fb..e7d995e 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
@@ -109,6 +109,8 @@ public static String formatLine(String line) {
 	/***/ public String changesToBeCommitted;
 	/***/ public String checkoutConflict;
 	/***/ public String checkoutConflictPathLine;
+	/***/ public String clonedEmptyRepository;
+	/***/ public String cloningInto;
 	/***/ public String commitLabel;
 	/***/ public String conflictingUsageOf_git_dir_andArguments;
 	/***/ public String couldNotCreateBranch;