Merge "Archive: Add the ability to select one or more paths."
diff --git a/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF
index c16455c..66c467d 100644
--- a/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF
@@ -1,7 +1,7 @@
 Manifest-Version: 1.0
 Bundle-ManifestVersion: 2
 Bundle-Name: org.eclipse.jgit.archive - Sources
-Bundle-SymbolicName: org.eclipse.jgit.archive.source;singleton:=true
+Bundle-SymbolicName: org.eclipse.jgit.archive.source
 Bundle-Vendor: Eclipse.org - JGit
 Bundle-Version: 3.4.0.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.archive;version="3.4.0";roots="."
+Eclipse-SourceBundle: org.eclipse.jgit.archive;version="3.4.0.qualifier";roots="."
diff --git a/org.eclipse.jgit.java7/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.java7/META-INF/SOURCE-MANIFEST.MF
index 51d3f50..02d41a3 100644
--- a/org.eclipse.jgit.java7/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.java7/META-INF/SOURCE-MANIFEST.MF
@@ -1,8 +1,8 @@
 Manifest-Version: 1.0
 Bundle-ManifestVersion: 2
 Bundle-Name: org.eclipse.jgit.java7 - Sources
-Bundle-SymbolicName: org.eclipse.jgit.java7.source;singleton:=true
+Bundle-SymbolicName: org.eclipse.jgit.java7.source
 Bundle-Vendor: Eclipse.org - JGit
 Bundle-Version: 3.4.0.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.java7;version="3.4.0";roots="."
+Eclipse-SourceBundle: org.eclipse.jgit.java7;version="3.4.0.qualifier";roots="."
 
diff --git a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java
index a328bae..50ddfe0 100644
--- a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java
+++ b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java
@@ -81,6 +81,10 @@ protected File writeTrashFile(final String name, final String data)
 		return JGitTestUtil.writeTrashFile(db, name, data);
 	}
 
+	protected String read(final File file) throws IOException {
+		return JGitTestUtil.read(file);
+	}
+
 	protected void deleteTrashFile(final String name) throws IOException {
 		JGitTestUtil.deleteTrashFile(db, name);
 	}
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java
index 8012893..2c1f59f 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java
@@ -42,6 +42,8 @@
  */
 package org.eclipse.jgit.pgm;
 
+import static org.junit.Assert.assertArrayEquals;
+
 import java.io.File;
 
 import org.eclipse.jgit.api.Git;
@@ -191,4 +193,26 @@ static private void assertEquals(String expected, String[] actual) {
 						: actual.length);
 		Assert.assertEquals(expected, actual[0]);
 	}
+
+	@Test
+	public void testCheckoutPath() throws Exception {
+		Git git = new Git(db);
+		writeTrashFile("a", "Hello world a");
+		git.add().addFilepattern(".").call();
+		git.commit().setMessage("commit file a").call();
+		git.branchCreate().setName("branch_1").call();
+		git.checkout().setName("branch_1").call();
+		File b = writeTrashFile("b", "Hello world b");
+		git.add().addFilepattern("b").call();
+		git.commit().setMessage("commit file b").call();
+		File a = writeTrashFile("a", "New Hello world a");
+		git.add().addFilepattern(".").call();
+		git.commit().setMessage("modified a").call();
+		assertArrayEquals(new String[] { "" },
+				execute("git checkout HEAD~2 -- a"));
+		assertEquals("Hello world a", read(a));
+		assertArrayEquals(new String[] { "* branch_1", "  master", "" },
+				execute("git branch"));
+		assertEquals("Hello world b", read(b));
+	}
 }
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ConfigTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ConfigTest.java
index 3c62e85..aefdff1 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ConfigTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ConfigTest.java
@@ -42,15 +42,15 @@
  */
 package org.eclipse.jgit.pgm;
 
-import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.lib.CLIRepositoryTestCase;
+import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.SystemReader;
 import org.junit.Before;
 import org.junit.Test;
@@ -77,28 +77,12 @@ public void testListConfig() throws Exception {
 		if (isMac)
 			expect.add("core.precomposeunicode=true");
 		expect.add("core.repositoryformatversion=0");
-		if (SystemReader.getInstance().isWindows() && osVersion() < 6
-				|| javaVersion() < 1.7) {
+		if (!FS.DETECTED.supportsSymlinks())
 			expect.add("core.symlinks=false");
-		}
 		expect.add(""); // ends with LF (last line empty)
-		assertArrayEquals("expected default configuration", expect.toArray(),
-				output);
+		assertEquals("expected default configuration",
+				Arrays.asList(expect.toArray()).toString(),
+				Arrays.asList(output).toString());
 	}
 
-	private static float javaVersion() {
-		String versionString = System.getProperty("java.version");
-		Matcher matcher = Pattern.compile("(\\d+\\.\\d+).*").matcher(
-				versionString);
-		matcher.matches();
-		return Float.parseFloat(matcher.group(1));
-	}
-
-	private static float osVersion() {
-		String versionString = System.getProperty("os.version");
-		Matcher matcher = Pattern.compile("(\\d+\\.\\d+).*").matcher(
-				versionString);
-		matcher.matches();
-		return Float.parseFloat(matcher.group(1));
-	}
 }
diff --git a/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF
index 7bf0c26..6d29757 100644
--- a/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF
@@ -1,7 +1,7 @@
 Manifest-Version: 1.0
 Bundle-ManifestVersion: 2
 Bundle-Name: org.eclipse.jgit.pgm - Sources
-Bundle-SymbolicName: org.eclipse.jgit.pgm.source;singleton:=true
+Bundle-SymbolicName: org.eclipse.jgit.pgm.source
 Bundle-Vendor: Eclipse.org - JGit
 Bundle-Version: 3.4.0.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="3.4.0";roots="."
+Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="3.4.0.qualifier";roots="."
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java
index 2f35ecb..8f911fd 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java
@@ -45,6 +45,8 @@
 package org.eclipse.jgit.pgm;
 
 import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
 
 import org.eclipse.jgit.api.CheckoutCommand;
 import org.eclipse.jgit.api.Git;
@@ -58,6 +60,7 @@
 import org.eclipse.jgit.pgm.internal.CLIText;
 import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.Option;
+import org.kohsuke.args4j.spi.StopOptionHandler;
 
 @Command(common = true, usage = "usage_checkout")
 class Checkout extends TextBuiltin {
@@ -68,9 +71,13 @@ class Checkout extends TextBuiltin {
 	@Option(name = "--force", aliases = { "-f" }, usage = "usage_forceCheckout")
 	private boolean force = false;
 
-	@Argument(required = true, metaVar = "metaVar_name", usage = "usage_checkout")
+	@Argument(required = true, index = 0, metaVar = "metaVar_name", usage = "usage_checkout")
 	private String name;
 
+	@Argument(index = 1)
+	@Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = StopOptionHandler.class)
+	private List<String> paths = new ArrayList<String>();
+
 	@Override
 	protected void run() throws Exception {
 		if (createBranch) {
@@ -80,9 +87,15 @@ protected void run() throws Exception {
 		}
 
 		CheckoutCommand command = new Git(db).checkout();
-		command.setCreateBranch(createBranch);
-		command.setName(name);
-		command.setForce(force);
+		if (paths.size() > 0) {
+			command.setStartPoint(name);
+			for (String path : paths)
+				command.addPath(path);
+		} else {
+			command.setCreateBranch(createBranch);
+			command.setName(name);
+			command.setForce(force);
+		}
 		try {
 			String oldBranch = db.getBranch();
 			Ref ref = command.call();
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java
index e444d21..4b16ed8 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java
@@ -53,6 +53,7 @@
 import org.eclipse.jgit.treewalk.AbstractTreeIterator;
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
+import org.eclipse.jgit.util.QuotedString;
 import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.Option;
 import org.kohsuke.args4j.spi.StopOptionHandler;
@@ -90,7 +91,7 @@ protected void run() throws Exception {
 			outw.print(walk.getObjectId(0).name());
 
 			outw.print('\t');
-			outw.print(walk.getPathString());
+			outw.print(QuotedString.GIT_PATH.quote(walk.getPathString()));
 			outw.println();
 		}
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
index 27d3220..f6dea74 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
@@ -50,14 +50,20 @@
 import java.io.File;
 import java.io.FileReader;
 
+import org.eclipse.jgit.api.CloneCommand;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
 import org.junit.Test;
 
 public class RepoCommandTest extends RepositoryTestCase {
 
+	private static final String BRANCH = "branch";
+	private static final String TAG = "release";
+
 	private Repository defaultDb;
 	private Repository notDefaultDb;
 	private Repository groupADb;
@@ -69,14 +75,22 @@ public class RepoCommandTest extends RepositoryTestCase {
 	private String groupAUri;
 	private String groupBUri;
 
+	private ObjectId oldCommitId;
+
 	public void setUp() throws Exception {
 		super.setUp();
 
 		defaultDb = createWorkRepository();
 		Git git = new Git(defaultDb);
-		JGitTestUtil.writeTrashFile(defaultDb, "hello.txt", "world");
+		JGitTestUtil.writeTrashFile(defaultDb, "hello.txt", "branch world");
 		git.add().addFilepattern("hello.txt").call();
-		git.commit().setMessage("Initial commit").call();
+		oldCommitId = git.commit().setMessage("Initial commit").call().getId();
+		git.checkout().setName(BRANCH).setCreateBranch(true).call();
+		git.checkout().setName("master").call();
+		git.tag().setName(TAG).call();
+		JGitTestUtil.writeTrashFile(defaultDb, "hello.txt", "master world");
+		git.add().addFilepattern("hello.txt").call();
+		git.commit().setMessage("Second commit").call();
 
 		notDefaultDb = createWorkRepository();
 		git = new Git(notDefaultDb);
@@ -120,7 +134,7 @@ public void testAddRepoManifest() throws Exception {
 		BufferedReader reader = new BufferedReader(new FileReader(hello));
 		String content = reader.readLine();
 		reader.close();
-		assertEquals("submodule content is as expected.", "world", content);
+		assertEquals("submodule content is as expected.", "master world", content);
 	}
 
 	@Test
@@ -179,7 +193,7 @@ public void testRepoManifestGroups() throws Exception {
 	}
 
 	@Test
-	public void testRepoManifestCopyfile() throws Exception {
+	public void testRepoManifestCopyFile() throws Exception {
 		Repository localDb = createWorkRepository();
 		StringBuilder xmlContent = new StringBuilder();
 		xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
@@ -203,14 +217,198 @@ public void testRepoManifestCopyfile() throws Exception {
 		BufferedReader reader = new BufferedReader(new FileReader(hello));
 		String content = reader.readLine();
 		reader.close();
-		assertEquals("The original file has expected content", "world", content);
+		assertEquals("The original file has expected content", "master world", content);
 		// The dest file should also exist
 		hello = new File(localDb.getWorkTree(), "Hello");
 		assertTrue("The destination file exists", hello.exists());
 		reader = new BufferedReader(new FileReader(hello));
 		content = reader.readLine();
 		reader.close();
-		assertEquals("The destination file has expected content", "world", content);
+		assertEquals("The destination file has expected content", "master world", content);
+	}
+
+	@Test
+	public void testBareRepo() throws Exception {
+		Repository remoteDb = createBareRepository();
+		Repository tempDb = createWorkRepository();
+		StringBuilder xmlContent = new StringBuilder();
+		xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+			.append("<manifest>")
+			.append("<remote name=\"remote1\" fetch=\".\" />")
+			.append("<default revision=\"master\" remote=\"remote1\" />")
+			.append("<project path=\"foo\" name=\"")
+			.append(defaultUri)
+			.append("\" />")
+			.append("</manifest>");
+		JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", xmlContent.toString());
+		RepoCommand command = new RepoCommand(remoteDb);
+		command.setPath(tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml")
+			.setURI(rootUri)
+			.call();
+		// Clone it
+		File directory = createTempDirectory("testBareRepo");
+		CloneCommand clone = Git.cloneRepository();
+		clone.setDirectory(directory);
+		clone.setURI(remoteDb.getDirectory().toURI().toString());
+		Repository localDb = clone.call().getRepository();
+		// The .gitmodules file should exist
+		File gitmodules = new File(localDb.getWorkTree(), ".gitmodules");
+		assertTrue("The .gitmodules file exists", gitmodules.exists());
+		// The first line of .gitmodules file should be expected
+		BufferedReader reader = new BufferedReader(new FileReader(gitmodules));
+		String content = reader.readLine();
+		reader.close();
+		assertEquals("The first line of .gitmodules file is as expected.",
+				"[submodule \"foo\"]", content);
+		// The gitlink should be the same as remote head sha1
+		String gitlink = localDb.resolve(Constants.HEAD + ":foo").name();
+		String remote = defaultDb.resolve(Constants.HEAD).name();
+		assertEquals("The gitlink is same as remote head", remote, gitlink);
+	}
+
+	@Test
+	public void testRevision() throws Exception {
+		StringBuilder xmlContent = new StringBuilder();
+		xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+			.append("<manifest>")
+			.append("<remote name=\"remote1\" fetch=\".\" />")
+			.append("<default revision=\"master\" remote=\"remote1\" />")
+			.append("<project path=\"foo\" name=\"")
+			.append(defaultUri)
+			.append("\" revision=\"")
+			.append(oldCommitId.name())
+			.append("\" />")
+			.append("</manifest>");
+		writeTrashFile("manifest.xml", xmlContent.toString());
+		RepoCommand command = new RepoCommand(db);
+		command.setPath(db.getWorkTree().getAbsolutePath() + "/manifest.xml")
+			.setURI(rootUri)
+			.call();
+		File hello = new File(db.getWorkTree(), "foo/hello.txt");
+		BufferedReader reader = new BufferedReader(new FileReader(hello));
+		String content = reader.readLine();
+		reader.close();
+		assertEquals("submodule content is as expected.", "branch world", content);
+	}
+
+	@Test
+	public void testRevisionBranch() throws Exception {
+		StringBuilder xmlContent = new StringBuilder();
+		xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+			.append("<manifest>")
+			.append("<remote name=\"remote1\" fetch=\".\" />")
+			.append("<default revision=\"")
+			.append(BRANCH)
+			.append("\" remote=\"remote1\" />")
+			.append("<project path=\"foo\" name=\"")
+			.append(defaultUri)
+			.append("\" />")
+			.append("</manifest>");
+		writeTrashFile("manifest.xml", xmlContent.toString());
+		RepoCommand command = new RepoCommand(db);
+		command.setPath(db.getWorkTree().getAbsolutePath() + "/manifest.xml")
+			.setURI(rootUri)
+			.call();
+		File hello = new File(db.getWorkTree(), "foo/hello.txt");
+		BufferedReader reader = new BufferedReader(new FileReader(hello));
+		String content = reader.readLine();
+		reader.close();
+		assertEquals("submodule content is as expected.", "branch world", content);
+	}
+
+	@Test
+	public void testRevisionTag() throws Exception {
+		StringBuilder xmlContent = new StringBuilder();
+		xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+			.append("<manifest>")
+			.append("<remote name=\"remote1\" fetch=\".\" />")
+			.append("<default revision=\"master\" remote=\"remote1\" />")
+			.append("<project path=\"foo\" name=\"")
+			.append(defaultUri)
+			.append("\" revision=\"")
+			.append(TAG)
+			.append("\" />")
+			.append("</manifest>");
+		writeTrashFile("manifest.xml", xmlContent.toString());
+		RepoCommand command = new RepoCommand(db);
+		command.setPath(db.getWorkTree().getAbsolutePath() + "/manifest.xml")
+			.setURI(rootUri)
+			.call();
+		File hello = new File(db.getWorkTree(), "foo/hello.txt");
+		BufferedReader reader = new BufferedReader(new FileReader(hello));
+		String content = reader.readLine();
+		reader.close();
+		assertEquals("submodule content is as expected.", "branch world", content);
+	}
+
+	@Test
+	public void testRevisionBare() throws Exception {
+		Repository remoteDb = createBareRepository();
+		Repository tempDb = createWorkRepository();
+		StringBuilder xmlContent = new StringBuilder();
+		xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+			.append("<manifest>")
+			.append("<remote name=\"remote1\" fetch=\".\" />")
+			.append("<default revision=\"")
+			.append(BRANCH)
+			.append("\" remote=\"remote1\" />")
+			.append("<project path=\"foo\" name=\"")
+			.append(defaultUri)
+			.append("\" />")
+			.append("</manifest>");
+		JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", xmlContent.toString());
+		RepoCommand command = new RepoCommand(remoteDb);
+		command.setPath(tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml")
+			.setURI(rootUri)
+			.call();
+		// Clone it
+		File directory = createTempDirectory("testRevisionBare");
+		CloneCommand clone = Git.cloneRepository();
+		clone.setDirectory(directory);
+		clone.setURI(remoteDb.getDirectory().toURI().toString());
+		Repository localDb = clone.call().getRepository();
+		// The gitlink should be the same as oldCommitId
+		String gitlink = localDb.resolve(Constants.HEAD + ":foo").name();
+		assertEquals("The gitlink is same as remote head",
+				oldCommitId.name(), gitlink);
+	}
+
+	@Test
+	public void testCopyFileBare() throws Exception {
+		Repository remoteDb = createBareRepository();
+		Repository tempDb = createWorkRepository();
+		StringBuilder xmlContent = new StringBuilder();
+		xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+			.append("<manifest>")
+			.append("<remote name=\"remote1\" fetch=\".\" />")
+			.append("<default revision=\"master\" remote=\"remote1\" />")
+			.append("<project path=\"foo\" name=\"")
+			.append(defaultUri)
+			.append("\" revision=\"")
+			.append(BRANCH)
+			.append("\" >")
+			.append("<copyfile src=\"hello.txt\" dest=\"Hello\" />")
+			.append("</project>")
+			.append("</manifest>");
+		JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", xmlContent.toString());
+		RepoCommand command = new RepoCommand(remoteDb);
+		command.setPath(tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml")
+			.setURI(rootUri)
+			.call();
+		// Clone it
+		File directory = createTempDirectory("testCopyFileBare");
+		CloneCommand clone = Git.cloneRepository();
+		clone.setDirectory(directory);
+		clone.setURI(remoteDb.getDirectory().toURI().toString());
+		Repository localDb = clone.call().getRepository();
+		// The Hello file should exist
+		File hello = new File(localDb.getWorkTree(), "Hello");
+		assertTrue("The Hello file exists", hello.exists());
+		// The content of Hello file should be expected
+		BufferedReader reader = new BufferedReader(new FileReader(hello));
+		String content = reader.readLine();
+		reader.close();
+		assertEquals("The Hello file has expected content", "branch world", content);
 	}
 
 	private void resolveRelativeUris() {
diff --git a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
index 3a2e556..dbbbbb0 100644
--- a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
@@ -1,7 +1,7 @@
 Manifest-Version: 1.0
 Bundle-ManifestVersion: 2
 Bundle-Name: org.eclipse.jgit - Sources
-Bundle-SymbolicName: org.eclipse.jgit.source;singleton:=true
+Bundle-SymbolicName: org.eclipse.jgit.source
 Bundle-Vendor: Eclipse.org - JGit
 Bundle-Version: 3.4.0.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit;version="3.4.0";roots="."
+Eclipse-SourceBundle: org.eclipse.jgit;version="3.4.0.qualifier";roots="."
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties
index 29aa51c..33fdc2e 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties
@@ -1,5 +1,6 @@
 copyFileFailed=Error occurred during execution of copyfile rule.
 errorNoDefault=Error: no default remote in file {0}.
 errorParsingManifestFile=Error occurred during parsing manifest file {0}.
+errorRemoteUnavailable=Error remote {0} is unavailable.
 invalidManifest=Invalid manifest.
 repoCommitMessage=Added repo manifest.
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index bb95fa8..b3049b0 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -51,6 +51,7 @@
 cannotCreateDirectory=Cannot create directory {0}
 cannotCreateHEAD=cannot create HEAD
 cannotCreateIndexfile=Cannot create an index file with name {0}
+cannotCreateTempDir=Cannot create a temp dir
 cannotDeleteCheckedOutBranch=Branch {0} is checked out and can not be deleted
 cannotDeleteFile=Cannot delete file: {0}
 cannotDeleteStaleTrackingRef=Cannot delete stale tracking ref {0}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
index 95551c3..8277ead 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
@@ -145,6 +145,8 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
 
 	private static final String INTERACTIVE = "interactive"; //$NON-NLS-1$
 
+	private static final String QUIET = "quiet"; //$NON-NLS-1$
+
 	private static final String MESSAGE = "message"; //$NON-NLS-1$
 
 	private static final String ONTO = "onto"; //$NON-NLS-1$
@@ -928,6 +930,7 @@ else if (!isInteractive() && walk.isMergedInto(headCommit, upstream)) {
 		rebaseState.createFile(ONTO, upstreamCommit.name());
 		rebaseState.createFile(ONTO_NAME, upstreamCommitName);
 		rebaseState.createFile(INTERACTIVE, ""); //$NON-NLS-1$
+		rebaseState.createFile(QUIET, ""); //$NON-NLS-1$
 
 		ArrayList<RebaseTodoLine> toDoSteps = new ArrayList<RebaseTodoLine>();
 		toDoSteps.add(new RebaseTodoLine("# Created by EGit: rebasing " + headId.name() //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java
index e4431c3..6cbcd06 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java
@@ -219,7 +219,7 @@ public ObjectId call() throws GitAPIException {
 				FileUtils.delete(stashFile);
 				if (!stashLockFile.renameTo(stashFile))
 					throw new JGitInternalException(MessageFormat.format(
-							JGitText.get().couldNotWriteFile,
+							JGitText.get().renameFileFailed,
 							stashLockFile.getPath(), stashFile.getPath()));
 			}
 		} catch (IOException e) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
index 5275b4c..fdf8c05 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -1154,7 +1154,7 @@ public static void checkoutEntry(final Repository repo, File f,
 				FileUtils.rename(tmpFile, f);
 			} catch (IOException e) {
 				throw new IOException(MessageFormat.format(
-						JGitText.get().couldNotWriteFile, tmpFile.getPath(),
+						JGitText.get().renameFileFailed, tmpFile.getPath(),
 						f.getPath()));
 			}
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
index 475fbac..e90abb5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
@@ -42,6 +42,7 @@
  */
 package org.eclipse.jgit.gitrepo;
 
+import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -51,22 +52,41 @@
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import org.eclipse.jgit.api.AddCommand;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.GitCommand;
 import org.eclipse.jgit.api.SubmoduleAddCommand;
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
 import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheEntry;
 import org.eclipse.jgit.gitrepo.internal.RepoText;
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.FileUtils;
 
 import org.xml.sax.Attributes;
 import org.xml.sax.InputSource;
@@ -89,25 +109,114 @@ public class RepoCommand extends GitCommand<RevCommit> {
 	private String path;
 	private String uri;
 	private String groups;
+	private String branch;
+	private PersonIdent author;
+	private RemoteReader callback;
 
+	private List<Project> bareProjects;
 	private Git git;
 	private ProgressMonitor monitor;
 
+	/**
+	 * A callback to get ref sha1 of a repository from its uri.
+	 *
+	 * We provided a default implementation {@link DefaultRemoteReader} to
+	 * use ls-remote command to read the sha1 from the repository and clone the
+	 * repository to read the file. Callers may have their own quicker
+	 * implementation.
+	 *
+	 * @since 3.4
+	 */
+	public interface RemoteReader {
+		/**
+		 * Read a remote ref sha1.
+		 *
+		 * @param uri
+		 *            The URI of the remote repository
+		 * @param ref
+		 *            The ref (branch/tag/etc.) to read
+		 * @return the sha1 of the remote repository
+		 * @throws GitAPIException
+		 */
+		public ObjectId sha1(String uri, String ref) throws GitAPIException;
+
+		/**
+		 * Read a file from a remote repository.
+		 *
+		 * @param uri
+		 *            The URI of the remote repository
+		 * @param ref
+		 *            The ref (branch/tag/etc.) to read
+		 * @param path
+		 *            The relative path (inside the repo) to the file to read
+		 * @return the file content.
+		 * @throws GitAPIException
+		 * @throws IOException
+		 */
+		public byte[] readFile(String uri, String ref, String path)
+				throws GitAPIException, IOException;
+	}
+
+	/** A default implementation of {@link RemoteReader} callback. */
+	public static class DefaultRemoteReader implements RemoteReader {
+		public ObjectId sha1(String uri, String ref) throws GitAPIException {
+			Collection<Ref> refs = Git
+					.lsRemoteRepository()
+					.setRemote(uri)
+					.call();
+			// Since LsRemoteCommand.call() only returned Map.values() to us, we
+			// have to rebuild the map here.
+			Map<String, Ref> map = new HashMap<String, Ref>(refs.size());
+			for (Ref r : refs)
+				map.put(r.getName(), r);
+
+			Ref r = RefDatabase.findRef(map, ref);
+			return r != null ? r.getObjectId() : null;
+		}
+
+		public byte[] readFile(String uri, String ref, String path)
+				throws GitAPIException, IOException {
+			File dir = FileUtils.createTempDir("jgit_", ".git", null); //$NON-NLS-1$ //$NON-NLS-2$
+			Repository repo = Git
+					.cloneRepository()
+					.setBare(true)
+					.setDirectory(dir)
+					.setURI(uri)
+					.call()
+					.getRepository();
+			ObjectReader reader = repo.newObjectReader();
+			byte[] result;
+			try {
+				ObjectId oid = repo.resolve(ref + ":" + path); //$NON-NLS-1$
+				result = reader.open(oid).getBytes(Integer.MAX_VALUE);
+			} finally {
+				reader.release();
+				FileUtils.delete(dir, FileUtils.RECURSIVE);
+			}
+			return result;
+		}
+	}
+
 	private static class CopyFile {
+		final Repository repo;
+		final String path;
 		final String src;
 		final String dest;
-		final String relativeDest;
 
 		CopyFile(Repository repo, String path, String src, String dest) {
-			this.src = repo.getWorkTree() + "/" + path + "/" + src; //$NON-NLS-1$ //$NON-NLS-2$
-			this.relativeDest = dest;
-			this.dest = repo.getWorkTree() + "/" + dest; //$NON-NLS-1$
+			this.repo = repo;
+			this.path = path;
+			this.src = src;
+			this.dest = dest;
 		}
 
 		void copy() throws IOException {
-			FileInputStream input = new FileInputStream(src);
+			File srcFile = new File(repo.getWorkTree(),
+					path + "/" + src); //$NON-NLS-1$
+			File destFile = new File(repo.getWorkTree(), dest);
+			FileInputStream input = new FileInputStream(srcFile);
 			try {
-				FileOutputStream output = new FileOutputStream(dest);
+				FileOutputStream output = new FileOutputStream(destFile);
 				try {
 					FileChannel channel = input.getChannel();
 					output.getChannel().transferFrom(channel, 0, channel.size());
@@ -123,12 +232,14 @@ void copy() throws IOException {
 	private static class Project {
 		final String name;
 		final String path;
+		final String revision;
 		final Set<String> groups;
 		final List<CopyFile> copyfiles;
 
-		Project(String name, String path, String groups) {
+		Project(String name, String path, String revision, String groups) {
 			this.name = name;
 			this.path = path;
+			this.revision = revision;
 			this.groups = new HashSet<String>();
 			if (groups != null && groups.length() > 0)
 				this.groups.addAll(Arrays.asList(groups.split(","))); //$NON-NLS-1$
@@ -149,6 +260,7 @@ private static class XmlManifest extends DefaultHandler {
 		private final Set<String> plusGroups;
 		private final Set<String> minusGroups;
 		private String defaultRemote;
+		private String defaultRevision;
 		private Project currentProject;
 
 		XmlManifest(RepoCommand command, String filename, String baseUrl, String groups) {
@@ -203,12 +315,16 @@ public void startElement(
 				currentProject = new Project( //$NON-NLS-1$
 						attributes.getValue("name"), //$NON-NLS-1$
 						attributes.getValue("path"), //$NON-NLS-1$
+						attributes.getValue("revision"), //$NON-NLS-1$
 						attributes.getValue("groups")); //$NON-NLS-1$
 			} else if ("remote".equals(qName)) { //$NON-NLS-1$
 				remotes.put(attributes.getValue("name"), //$NON-NLS-1$
 						attributes.getValue("fetch")); //$NON-NLS-1$
 			} else if ("default".equals(qName)) { //$NON-NLS-1$
 				defaultRemote = attributes.getValue("remote"); //$NON-NLS-1$
+				defaultRevision = attributes.getValue("revision"); //$NON-NLS-1$
+				if (defaultRevision == null)
+					defaultRevision = command.branch;
 			} else if ("copyfile".equals(qName)) { //$NON-NLS-1$
 				if (currentProject == null)
 					throw new SAXException(RepoText.get().invalidManifest);
@@ -246,24 +362,11 @@ public void endDocument() throws SAXException {
 			}
 			for (Project proj : projects) {
 				if (inGroups(proj)) {
-					String url = remoteUrl + proj.name;
-					command.addSubmodule(url, proj.path);
-					for (CopyFile copyfile : proj.copyfiles) {
-						try {
-							copyfile.copy();
-						} catch (IOException e) {
-							throw new SAXException(
-									RepoText.get().copyFileFailed, e);
-						}
-						AddCommand add = command.git
-							.add()
-							.addFilepattern(copyfile.relativeDest);
-						try {
-							add.call();
-						} catch (GitAPIException e) {
-							throw new SAXException(e);
-						}
-					}
+					command.addSubmodule(remoteUrl + proj.name,
+							proj.path,
+							proj.revision == null ?
+									defaultRevision : proj.revision,
+							proj.copyfiles);
 				}
 			}
 		}
@@ -293,6 +396,12 @@ private static class ManifestErrorException extends GitAPIException {
 		}
 	}
 
+	private static class RemoteUnavailableException extends GitAPIException {
+		RemoteUnavailableException(String uri) {
+			super(MessageFormat.format(RepoText.get().errorRemoteUnavailable, uri));
+		}
+	}
+
 	/**
 	 * @param repo
 	 */
@@ -335,6 +444,21 @@ public RepoCommand setGroups(final String groups) {
 	}
 
 	/**
+	 * Set default branch.
+	 *
+	 * This is generally the name of the branch the manifest file was in. If
+	 * there's no default revision (branch) specified in manifest and no
+	 * revision specified in project, this branch will be used.
+	 *
+	 * @param branch
+	 * @return this command
+	 */
+	public RepoCommand setBranch(final String branch) {
+		this.branch = branch;
+		return this;
+	}
+
+	/**
 	 * The progress monitor associated with the clone operation. By default,
 	 * this is set to <code>NullProgressMonitor</code>
 	 *
@@ -347,6 +471,32 @@ public RepoCommand setProgressMonitor(final ProgressMonitor monitor) {
 		return this;
 	}
 
+	/**
+	 * Set the author/committer for the bare repository commit.
+	 *
+	 * For non-bare repositories, the current user will be used and this will be ignored.
+	 *
+	 * @param author
+	 * @return this command
+	 */
+	public RepoCommand setAuthor(final PersonIdent author) {
+		this.author = author;
+		return this;
+	}
+
+	/**
+	 * Set the GetHeadFromUri callback.
+	 *
+	 * This is only used in bare repositories.
+	 *
+	 * @param callback
+	 * @return this command
+	 */
+	public RepoCommand setRemoteReader(final RemoteReader callback) {
+		this.callback = callback;
+		return this;
+	}
+
 	@Override
 	public RevCommit call() throws GitAPIException {
 		checkCallable();
@@ -355,7 +505,15 @@ public RevCommit call() throws GitAPIException {
 		if (uri == null || uri.length() == 0)
 			throw new IllegalArgumentException(JGitText.get().uriNotConfigured);
 
-		git = new Git(repo);
+		if (repo.isBare()) {
+			bareProjects = new ArrayList<Project>();
+			if (author == null)
+				author = new PersonIdent(repo);
+			if (callback == null)
+				callback = new DefaultRemoteReader();
+		} else
+			git = new Git(repo);
+
 		XmlManifest manifest = new XmlManifest(this, path, uri, groups);
 		try {
 			manifest.read();
@@ -363,23 +521,146 @@ public RevCommit call() throws GitAPIException {
 			throw new ManifestErrorException(e);
 		}
 
-		return git
-			.commit()
-			.setMessage(RepoText.get().repoCommitMessage)
-			.call();
+		if (repo.isBare()) {
+			DirCache index = DirCache.newInCore();
+			DirCacheBuilder builder = index.builder();
+			ObjectInserter inserter = repo.newObjectInserter();
+			RevWalk rw = new RevWalk(repo);
+
+			try {
+				Config cfg = new Config();
+				for (Project proj : bareProjects) {
+					String name = proj.path;
+					String uri = proj.name;
+					cfg.setString("submodule", name, "path", name); //$NON-NLS-1$ //$NON-NLS-2$
+					cfg.setString("submodule", name, "url", uri); //$NON-NLS-1$ //$NON-NLS-2$
+					// create gitlink
+					DirCacheEntry dcEntry = new DirCacheEntry(name);
+					ObjectId objectId;
+					if (ObjectId.isId(proj.revision))
+						objectId = ObjectId.fromString(proj.revision);
+					else {
+						objectId = callback.sha1(uri, proj.revision);
+					}
+					if (objectId == null)
+						throw new RemoteUnavailableException(uri);
+					dcEntry.setObjectId(objectId);
+					dcEntry.setFileMode(FileMode.GITLINK);
+					builder.add(dcEntry);
+
+					for (CopyFile copyfile : proj.copyfiles) {
+						byte[] src = callback.readFile(
+								uri, proj.revision, copyfile.src);
+						objectId = inserter.insert(Constants.OBJ_BLOB, src);
+						dcEntry = new DirCacheEntry(copyfile.dest);
+						dcEntry.setObjectId(objectId);
+						dcEntry.setFileMode(FileMode.REGULAR_FILE);
+						builder.add(dcEntry);
+					}
+				}
+				String content = cfg.toText();
+
+				// create a new DirCacheEntry for .gitmodules file.
+				final DirCacheEntry dcEntry = new DirCacheEntry(Constants.DOT_GIT_MODULES);
+				ObjectId objectId = inserter.insert(Constants.OBJ_BLOB,
+						content.getBytes(Constants.CHARACTER_ENCODING));
+				dcEntry.setObjectId(objectId);
+				dcEntry.setFileMode(FileMode.REGULAR_FILE);
+				builder.add(dcEntry);
+
+				builder.finish();
+				ObjectId treeId = index.writeTree(inserter);
+
+				// Create a Commit object, populate it and write it
+				ObjectId headId = repo.resolve(Constants.HEAD + "^{commit}"); //$NON-NLS-1$
+				CommitBuilder commit = new CommitBuilder();
+				commit.setTreeId(treeId);
+				if (headId != null)
+					commit.setParentIds(headId);
+				commit.setAuthor(author);
+				commit.setCommitter(author);
+				commit.setMessage(RepoText.get().repoCommitMessage);
+
+				ObjectId commitId = inserter.insert(commit);
+				inserter.flush();
+
+				RefUpdate ru = repo.updateRef(Constants.HEAD);
+				ru.setNewObjectId(commitId);
+				ru.setExpectedOldObjectId(headId != null ? headId : ObjectId.zeroId());
+				Result rc = ru.update(rw);
+
+				switch (rc) {
+					case NEW:
+					case FORCED:
+					case FAST_FORWARD:
+						// Successful. Do nothing.
+						break;
+					case REJECTED:
+					case LOCK_FAILURE:
+						throw new ConcurrentRefUpdateException(
+								JGitText.get().couldNotLockHEAD, ru.getRef(),
+								rc);
+					default:
+						throw new JGitInternalException(MessageFormat.format(
+								JGitText.get().updatingRefFailed,
+								Constants.HEAD, commitId.name(), rc));
+				}
+
+				return rw.parseCommit(commitId);
+			} catch (IOException e) {
+				throw new ManifestErrorException(e);
+			} finally {
+				rw.release();
+			}
+		} else {
+			return git
+				.commit()
+				.setMessage(RepoText.get().repoCommitMessage)
+				.call();
+		}
 	}
 
-	private void addSubmodule(String url, String name) throws SAXException {
-		SubmoduleAddCommand add = git
-			.submoduleAdd()
-			.setPath(name)
-			.setURI(url);
-		if (monitor != null)
-			add.setProgressMonitor(monitor);
-		try {
-			add.call();
-		} catch (GitAPIException e) {
-			throw new SAXException(e);
+	private void addSubmodule(String url, String name, String revision,
+			List<CopyFile> copyfiles) throws SAXException {
+		if (repo.isBare()) {
+			Project proj = new Project(url, name, revision, null);
+			proj.copyfiles.addAll(copyfiles);
+			bareProjects.add(proj);
+		} else {
+			SubmoduleAddCommand add = git
+				.submoduleAdd()
+				.setPath(name)
+				.setURI(url);
+			if (monitor != null)
+				add.setProgressMonitor(monitor);
+
+			try {
+				Repository subRepo = add.call();
+				if (revision != null) {
+					Git sub = new Git(subRepo);
+					sub.checkout().setName(findRef(revision, subRepo)).call();
+					git.add().addFilepattern(name).call();
+				}
+				for (CopyFile copyfile : copyfiles) {
+					copyfile.copy();
+					git.add().addFilepattern(copyfile.dest).call();
+				}
+			} catch (GitAPIException e) {
+				throw new SAXException(e);
+			} catch (IOException e) {
+				throw new SAXException(e);
+			}
 		}
 	}
+
+	private static String findRef(String ref, Repository repo)
+			throws IOException {
+		if (!ObjectId.isId(ref)) {
+			Ref r = repo.getRef(
+					Constants.DEFAULT_REMOTE_NAME + "/" + ref);
+			if (r != null)
+				return r.getName();
+		}
+		return ref;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java
index 1313fff..34db723 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java
@@ -62,6 +62,7 @@ public static RepoText get() {
 	/***/ public String copyFileFailed;
 	/***/ public String errorNoDefault;
 	/***/ public String errorParsingManifestFile;
+	/***/ public String errorRemoteUnavailable;
 	/***/ public String invalidManifest;
 	/***/ public String repoCommitMessage;
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index 40e74ed..576ba0f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -113,6 +113,7 @@ public static JGitText get() {
 	/***/ public String cannotCreateDirectory;
 	/***/ public String cannotCreateHEAD;
 	/***/ public String cannotCreateIndexfile;
+	/***/ public String cannotCreateTempDir;
 	/***/ public String cannotDeleteCheckedOutBranch;
 	/***/ public String cannotDeleteFile;
 	/***/ public String cannotDeleteStaleTrackingRef;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
index 2ee63f18e..682cac1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
@@ -271,4 +271,26 @@ public BatchRefUpdate newBatchUpdate() {
 	public void refresh() {
 		// nothing
 	}
+
+	/**
+	 * Try to find the specified name in the ref map using {@link #SEARCH_PATH}.
+	 *
+	 * @param map
+	 *            map of refs to search within. Names should be fully qualified,
+	 *            e.g. "refs/heads/master".
+	 * @param name
+	 *            short name of ref to find, e.g. "master" to find
+	 *            "refs/heads/master" in map.
+	 * @return The first ref matching the name, or null if not found.
+	 * @since 3.4
+	 */
+	public static Ref findRef(Map<String, Ref> map, String name) {
+		for (String prefix : SEARCH_PATH) {
+			String fullname = prefix + name;
+			Ref ref = map.get(fullname);
+			if (ref != null)
+				return ref;
+		}
+		return null;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
index b77807c..330cac1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
@@ -362,4 +362,29 @@ public static void createSymLink(File path, String target)
 	public static String readSymLink(File path) throws IOException {
 		return FS.DETECTED.readSymLink(path);
 	}
+
+	/**
+	 * Create a temporary directory.
+	 *
+	 * @param prefix
+	 * @param suffix
+	 * @param dir
+	 *            The parent dir, can be null to use system default temp dir.
+	 * @return the temp dir created.
+	 * @throws IOException
+	 * @since 3.4
+	 */
+	public static File createTempDir(String prefix, String suffix, File dir)
+			throws IOException {
+		final int RETRY = 1; // When something bad happens, retry once.
+		for (int i = 0; i < RETRY; i++) {
+			File tmp = File.createTempFile(prefix, suffix, dir);
+			if (!tmp.delete())
+				continue;
+			if (!tmp.mkdir())
+				continue;
+			return tmp;
+		}
+		throw new IOException(JGitText.get().cannotCreateTempDir);
+	}
 }