Merge "Check connection's error stream before reading from it"
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
index 632288e..2fb228e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
@@ -111,7 +111,7 @@ public void testAddExistingSingleFile() throws IOException, NoFilepatternExcepti
 	}
 
 	@Test
-	public void testAddExistingSingleFileWithNewLine() throws IOException,
+	public void testAddExistingSingleSmallFileWithNewLine() throws IOException,
 			NoFilepatternException {
 		File file = new File(db.getWorkTree(), "a.txt");
 		FileUtils.createNewFile(file);
@@ -135,6 +135,35 @@ public void testAddExistingSingleFileWithNewLine() throws IOException,
 	}
 
 	@Test
+	public void testAddExistingSingleMediumSizeFileWithNewLine()
+			throws IOException, NoFilepatternException {
+		File file = new File(db.getWorkTree(), "a.txt");
+		FileUtils.createNewFile(file);
+		StringBuilder data = new StringBuilder();
+		for (int i = 0; i < 1000; ++i) {
+			data.append("row1\r\nrow2");
+		}
+		String crData = data.toString();
+		PrintWriter writer = new PrintWriter(file);
+		writer.print(crData);
+		writer.close();
+		String lfData = data.toString().replaceAll("\r", "");
+		Git git = new Git(db);
+		db.getConfig().setString("core", null, "autocrlf", "false");
+		git.add().addFilepattern("a.txt").call();
+		assertEquals("[a.txt, mode:100644, content:" + data + "]",
+				indexState(CONTENT));
+		db.getConfig().setString("core", null, "autocrlf", "true");
+		git.add().addFilepattern("a.txt").call();
+		assertEquals("[a.txt, mode:100644, content:" + lfData + "]",
+				indexState(CONTENT));
+		db.getConfig().setString("core", null, "autocrlf", "input");
+		git.add().addFilepattern("a.txt").call();
+		assertEquals("[a.txt, mode:100644, content:" + lfData + "]",
+				indexState(CONTENT));
+	}
+
+	@Test
 	public void testAddExistingSingleBinaryFile() throws IOException,
 			NoFilepatternException {
 		File file = new File(db.getWorkTree(), "a.txt");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java
index 0b17972..97d0efe 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java
@@ -109,7 +109,7 @@ public void indexAdd() throws Exception {
 		File addedFile = writeTrashFile(addedPath, "content2");
 		git.add().addFilepattern(addedPath).call();
 
-		RevCommit stashed = Git.wrap(db).stashCreate().call();
+		RevCommit stashed = git.stashCreate().call();
 		assertNotNull(stashed);
 		assertFalse(addedFile.exists());
 
@@ -134,7 +134,7 @@ public void indexAdd() throws Exception {
 	public void indexDelete() throws Exception {
 		git.rm().addFilepattern("file.txt").call();
 
-		RevCommit stashed = Git.wrap(db).stashCreate().call();
+		RevCommit stashed = git.stashCreate().call();
 		assertNotNull(stashed);
 		assertEquals("content", read(committedFile));
 
@@ -158,7 +158,7 @@ public void indexDelete() throws Exception {
 	public void workingDirectoryModify() throws Exception {
 		writeTrashFile("file.txt", "content2");
 
-		RevCommit stashed = Git.wrap(db).stashCreate().call();
+		RevCommit stashed = git.stashCreate().call();
 		assertNotNull(stashed);
 		assertEquals("content", read(committedFile));
 
@@ -187,7 +187,7 @@ public void workingDirectoryModifyInSubfolder() throws Exception {
 
 		writeTrashFile(path, "content2");
 
-		RevCommit stashed = Git.wrap(db).stashCreate().call();
+		RevCommit stashed = git.stashCreate().call();
 		assertNotNull(stashed);
 		assertEquals("content", read(subfolderFile));
 
@@ -213,7 +213,7 @@ public void workingDirectoryModifyIndexChanged() throws Exception {
 		git.add().addFilepattern("file.txt").call();
 		writeTrashFile("file.txt", "content3");
 
-		RevCommit stashed = Git.wrap(db).stashCreate().call();
+		RevCommit stashed = git.stashCreate().call();
 		assertNotNull(stashed);
 		assertEquals("content", read(committedFile));
 
@@ -240,7 +240,7 @@ public void workingDirectoryCleanIndexModify() throws Exception {
 		git.add().addFilepattern("file.txt").call();
 		writeTrashFile("file.txt", "content");
 
-		RevCommit stashed = Git.wrap(db).stashCreate().call();
+		RevCommit stashed = git.stashCreate().call();
 		assertNotNull(stashed);
 		assertEquals("content", read(committedFile));
 
@@ -269,7 +269,7 @@ public void workingDirectoryDeleteIndexAdd() throws Exception {
 		FileUtils.delete(added);
 		assertFalse(added.exists());
 
-		RevCommit stashed = Git.wrap(db).stashCreate().call();
+		RevCommit stashed = git.stashCreate().call();
 		assertNotNull(stashed);
 		assertFalse(added.exists());
 
@@ -296,7 +296,7 @@ public void workingDirectoryDeleteIndexEdit() throws Exception {
 		FileUtils.delete(committedFile);
 		assertFalse(committedFile.exists());
 
-		RevCommit stashed = Git.wrap(db).stashCreate().call();
+		RevCommit stashed = git.stashCreate().call();
 		assertNotNull(stashed);
 		assertEquals("content", read(committedFile));
 
@@ -323,7 +323,7 @@ public void multipleEdits() throws Exception {
 		File addedFile = writeTrashFile(addedPath, "content2");
 		git.add().addFilepattern(addedPath).call();
 
-		RevCommit stashed = Git.wrap(db).stashCreate().call();
+		RevCommit stashed = git.stashCreate().call();
 		assertNotNull(stashed);
 		assertTrue(committedFile.exists());
 		assertFalse(addedFile.exists());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java
index b91a50a..16fadef 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java
@@ -54,10 +54,14 @@
 
 import org.eclipse.jgit.diff.DiffEntry;
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RepositoryTestCase;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.file.ReflogEntry;
+import org.eclipse.jgit.storage.file.ReflogReader;
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.treewalk.filter.TreeFilter;
 import org.eclipse.jgit.util.FileUtils;
@@ -394,4 +398,23 @@ public void multipleEdits() throws Exception {
 		assertEquals(DiffEntry.ChangeType.ADD, diffs.get(1).getChangeType());
 		assertEquals("file2.txt", diffs.get(1).getNewPath());
 	}
+
+	@Test
+	public void refLogIncludesCommitMessage() throws Exception {
+		PersonIdent who = new PersonIdent("user", "user@email.com");
+		deleteTrashFile("file.txt");
+		RevCommit stashed = git.stashCreate().setPerson(who).call();
+		assertNotNull(stashed);
+		assertEquals("content", read(committedFile));
+		validateStashedCommit(stashed);
+
+		ReflogReader reader = new ReflogReader(git.getRepository(),
+				Constants.R_STASH);
+		ReflogEntry entry = reader.getLastEntry();
+		assertNotNull(entry);
+		assertEquals(ObjectId.zeroId(), entry.getOldId());
+		assertEquals(stashed, entry.getNewId());
+		assertEquals(who, entry.getWho());
+		assertEquals(stashed.getFullMessage(), entry.getComment());
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RemoteConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RemoteConfigTest.java
index 03a6c01..0cada5c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RemoteConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RemoteConfigTest.java
@@ -50,6 +50,7 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
+import java.util.Arrays;
 import java.util.List;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -455,4 +456,66 @@ public void testSaveTimeout() throws Exception {
 				+ "\tfetch = +refs/heads/*:refs/remotes/origin/*\n"
 				+ "\ttimeout = 60\n");
 	}
+
+	@Test
+	public void noInsteadOf() throws Exception {
+		config.setString("remote", "origin", "url", "short:project.git");
+		config.setString("url", "https://server/repos/", "name", "short:");
+		RemoteConfig rc = new RemoteConfig(config, "origin");
+		assertFalse(rc.getURIs().isEmpty());
+		assertEquals("short:project.git", rc.getURIs().get(0).toASCIIString());
+	}
+
+	@Test
+	public void singleInsteadOf() throws Exception {
+		config.setString("remote", "origin", "url", "short:project.git");
+		config.setString("url", "https://server/repos/", "insteadOf", "short:");
+		RemoteConfig rc = new RemoteConfig(config, "origin");
+		assertFalse(rc.getURIs().isEmpty());
+		assertEquals("https://server/repos/project.git", rc.getURIs().get(0)
+				.toASCIIString());
+	}
+
+	@Test
+	public void multipleInsteadOf() throws Exception {
+		config.setString("remote", "origin", "url", "prefixproject.git");
+		config.setStringList("url", "https://server/repos/", "insteadOf",
+				Arrays.asList("pre", "prefix", "pref", "perf"));
+		RemoteConfig rc = new RemoteConfig(config, "origin");
+		assertFalse(rc.getURIs().isEmpty());
+		assertEquals("https://server/repos/project.git", rc.getURIs().get(0)
+				.toASCIIString());
+	}
+
+	@Test
+	public void noPushInsteadOf() throws Exception {
+		config.setString("remote", "origin", "pushurl", "short:project.git");
+		config.setString("url", "https://server/repos/", "name", "short:");
+		RemoteConfig rc = new RemoteConfig(config, "origin");
+		assertFalse(rc.getPushURIs().isEmpty());
+		assertEquals("short:project.git", rc.getPushURIs().get(0)
+				.toASCIIString());
+	}
+
+	@Test
+	public void singlePushInsteadOf() throws Exception {
+		config.setString("remote", "origin", "pushurl", "short:project.git");
+		config.setString("url", "https://server/repos/", "pushInsteadOf",
+				"short:");
+		RemoteConfig rc = new RemoteConfig(config, "origin");
+		assertFalse(rc.getPushURIs().isEmpty());
+		assertEquals("https://server/repos/project.git", rc.getPushURIs()
+				.get(0).toASCIIString());
+	}
+
+	@Test
+	public void multiplePushInsteadOf() throws Exception {
+		config.setString("remote", "origin", "pushurl", "prefixproject.git");
+		config.setStringList("url", "https://server/repos/", "pushInsteadOf",
+				Arrays.asList("pre", "prefix", "pref", "perf"));
+		RemoteConfig rc = new RemoteConfig(config, "origin");
+		assertFalse(rc.getPushURIs().isEmpty());
+		assertEquals("https://server/repos/project.git", rc.getPushURIs()
+				.get(0).toASCIIString());
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
index 8ead2b5..d5fffb9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
@@ -142,9 +142,11 @@ public StashCreateCommand setWorkingDirectoryMessage(String message) {
 	 * Set the person to use as the author and committer in the commits made
 	 *
 	 * @param person
+	 * @return {@code this}
 	 */
-	public void setPerson(PersonIdent person) {
+	public StashCreateCommand setPerson(PersonIdent person) {
 		this.person = person;
+		return this;
 	}
 
 	/**
@@ -153,9 +155,11 @@ public void setPerson(PersonIdent person) {
 	 * This value defaults to {@link Constants#R_STASH}
 	 *
 	 * @param ref
+	 * @return {@code this}
 	 */
-	public void setRef(String ref) {
+	public StashCreateCommand setRef(String ref) {
 		this.ref = ref;
+		return this;
 	}
 
 	private RevCommit parseCommit(final ObjectReader reader,
@@ -176,10 +180,13 @@ private CommitBuilder createBuilder(ObjectId headId) {
 		return builder;
 	}
 
-	private void updateStashRef(ObjectId commitId) throws IOException {
+	private void updateStashRef(ObjectId commitId, PersonIdent refLogIdent,
+			String refLogMessage) throws IOException {
 		Ref currentRef = repo.getRef(ref);
 		RefUpdate refUpdate = repo.updateRef(ref);
 		refUpdate.setNewObjectId(commitId);
+		refUpdate.setRefLogIdent(refLogIdent);
+		refUpdate.setRefLogMessage(refLogMessage, false);
 		if (currentRef != null)
 			refUpdate.setExpectedOldObjectId(currentRef.getObjectId());
 		else
@@ -296,7 +303,8 @@ else if (wtIter == null && headIter != null)
 				commitId = inserter.insert(builder);
 				inserter.flush();
 
-				updateStashRef(commitId);
+				updateStashRef(commitId, builder.getAuthor(),
+						builder.getMessage());
 			} finally {
 				inserter.release();
 				cache.unlock();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java
index 0355e56..e03fe35 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java
@@ -109,6 +109,29 @@ protected ObjectInserter() {
 		return tempBuffer;
 	}
 
+	static private final int tempBufSize;
+	static {
+		String s = System.getProperty("jgit.tempbufmaxsize");
+		if (s != null)
+			tempBufSize = Integer.parseInt(s);
+		else
+			tempBufSize = 1000000;
+	}
+
+	/**
+	 * @param hintSize
+	 * @return a temporary byte array for use by the caller
+	 */
+	protected byte[] buffer(long hintSize) {
+		if (hintSize >= tempBufSize)
+			tempBuffer = new byte[0];
+		else if (tempBuffer == null)
+			tempBuffer = new byte[(int) hintSize];
+		else if (tempBuffer.length < hintSize)
+			tempBuffer = new byte[(int) hintSize];
+		return tempBuffer;
+	}
+
 	/** @return digest to help compute an ObjectId */
 	protected MessageDigest digest() {
 		digest.reset();
@@ -172,7 +195,7 @@ public ObjectId idFor(int objectType, long length, InputStream in)
 		md.update((byte) ' ');
 		md.update(Constants.encodeASCII(length));
 		md.update((byte) 0);
-		byte[] buf = buffer();
+		byte[] buf = buffer(length);
 		while (length > 0) {
 			int n = in.read(buf, 0, (int) Math.min(length, buf.length));
 			if (n < 0)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/dfs/DfsInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/dfs/DfsInserter.java
index 335e284e..e80a324 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/dfs/DfsInserter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/dfs/DfsInserter.java
@@ -116,7 +116,7 @@ public ObjectId insert(int type, byte[] data, int off, int len)
 	@Override
 	public ObjectId insert(int type, long len, InputStream in)
 			throws IOException {
-		byte[] buf = buffer();
+		byte[] buf = buffer(len);
 		if (len <= buf.length) {
 			IO.readFully(in, buf, 0, (int) len);
 			return insert(type, buf, 0, (int) len);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java
index ffd5c41..75f6b0b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java
@@ -95,8 +95,8 @@ public ObjectId insert(int type, byte[] data, int off, int len)
 	@Override
 	public ObjectId insert(final int type, long len, final InputStream is)
 			throws IOException {
-		if (len <= buffer().length) {
-			byte[] buf = buffer();
+		byte[] buf = buffer(len);
+		if (len <= buf.length) {
 			int actLen = IO.readFully(is, buf, 0);
 			return insert(type, buf, 0, actLen);
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java
index 3df56c6..f75ac70 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java
@@ -49,7 +49,10 @@
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 
 import org.eclipse.jgit.lib.Config;
 
@@ -84,6 +87,10 @@ public class RemoteConfig implements Serializable {
 
 	private static final String KEY_TIMEOUT = "timeout";
 
+	private static final String KEY_INSTEADOF = "insteadof";
+
+	private static final String KEY_PUSHINSTEADOF = "pushinsteadof";
+
 	private static final boolean DEFAULT_MIRROR = false;
 
 	/** Default value for {@link #getUploadPack()} if not specified. */
@@ -161,14 +168,17 @@ public RemoteConfig(final Config rc, final String remoteName)
 		String val;
 
 		vlst = rc.getStringList(SECTION, name, KEY_URL);
+		Map<String, String> insteadOf = getReplacements(rc, KEY_INSTEADOF);
 		uris = new ArrayList<URIish>(vlst.length);
 		for (final String s : vlst)
-			uris.add(new URIish(s));
+			uris.add(new URIish(replaceUri(s, insteadOf)));
 
+		Map<String, String> pushInsteadOf = getReplacements(rc,
+				KEY_PUSHINSTEADOF);
 		vlst = rc.getStringList(SECTION, name, KEY_PUSHURL);
 		pushURIs = new ArrayList<URIish>(vlst.length);
 		for (final String s : vlst)
-			pushURIs.add(new URIish(s));
+			pushURIs.add(new URIish(replaceUri(s, pushInsteadOf)));
 
 		vlst = rc.getStringList(SECTION, name, KEY_FETCH);
 		fetch = new ArrayList<RefSpec>(vlst.length);
@@ -260,6 +270,35 @@ private void unset(final Config rc, final String key) {
 		rc.unset(SECTION, getName(), key);
 	}
 
+	private Map<String, String> getReplacements(final Config config,
+			final String keyName) {
+		final Map<String, String> replacements = new HashMap<String, String>();
+		for (String url : config.getSubsections(KEY_URL))
+			for (String insteadOf : config.getStringList(KEY_URL, url, keyName))
+				replacements.put(insteadOf, url);
+		return replacements;
+	}
+
+	private String replaceUri(final String uri,
+			final Map<String, String> replacements) {
+		if (replacements.isEmpty())
+			return uri;
+		Entry<String, String> match = null;
+		for (Entry<String, String> replacement : replacements.entrySet()) {
+			// Ignore current entry if not longer than previous match
+			if (match != null
+					&& match.getKey().length() > replacement.getKey().length())
+				continue;
+			if (!uri.startsWith(replacement.getKey()))
+				continue;
+			match = replacement;
+		}
+		if (match != null)
+			return match.getValue() + uri.substring(match.getKey().length());
+		else
+			return uri;
+	}
+
 	/**
 	 * Get the local name this remote configuration is recognized as.
 	 *