Merge branch 'master' into stable-4.2

Change-Id: Ia92c91e1226da7d6455ab14f1e255a1546f8f357
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java
index dec9b59..362a09d 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java
@@ -209,7 +209,7 @@ public void testInitialClone_Loose() throws Exception {
 		}
 
 		assertTrue(dst.hasObject(A_txt));
-		assertEquals(B, dst.getRef(master).getObjectId());
+		assertEquals(B, dst.exactRef(master).getObjectId());
 		fsck(dst, B);
 
 		List<AccessEvent> loose = getRequests(loose(remoteURI, A_txt));
@@ -234,7 +234,7 @@ public void testInitialClone_Packed() throws Exception {
 		}
 
 		assertTrue(dst.hasObject(A_txt));
-		assertEquals(B, dst.getRef(master).getObjectId());
+		assertEquals(B, dst.exactRef(master).getObjectId());
 		fsck(dst, B);
 
 		List<AccessEvent> req;
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java
index e385b95..da3a098 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java
@@ -225,7 +225,7 @@ public void testInitialClone_Small() throws Exception {
 		}
 
 		assertTrue(dst.hasObject(A_txt));
-		assertEquals(B, dst.getRef(master).getObjectId());
+		assertEquals(B, dst.exactRef(master).getObjectId());
 		fsck(dst, B);
 
 		List<AccessEvent> loose = getRequests(loose(remoteURI, A_txt));
@@ -253,7 +253,7 @@ public void testInitialClone_Packed() throws Exception {
 		}
 
 		assertTrue(dst.hasObject(A_txt));
-		assertEquals(B, dst.getRef(master).getObjectId());
+		assertEquals(B, dst.exactRef(master).getObjectId());
 		fsck(dst, B);
 
 		List<AccessEvent> req;
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java
index f1056f2..d67c817 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java
@@ -164,8 +164,8 @@ public void testPush_CreateBranch() throws Exception {
 		}
 
 		assertTrue(remoteRepository.hasObject(Q_txt));
-		assertNotNull("has " + dstName, remoteRepository.getRef(dstName));
-		assertEquals(Q, remoteRepository.getRef(dstName).getObjectId());
+		assertNotNull("has " + dstName, remoteRepository.exactRef(dstName));
+		assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId());
 		fsck(remoteRepository, Q);
 
 		List<AccessEvent> requests = getRequests();
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
index 1b6c552..9ca0789 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
@@ -296,7 +296,7 @@ public void testInitialClone_Small() throws Exception {
 		}
 
 		assertTrue(dst.hasObject(A_txt));
-		assertEquals(B, dst.getRef(master).getObjectId());
+		assertEquals(B, dst.exactRef(master).getObjectId());
 		fsck(dst, B);
 
 		List<AccessEvent> requests = getRequests();
@@ -337,7 +337,7 @@ public void testFetch_FewLocalCommits() throws Exception {
 		} finally {
 			t.close();
 		}
-		assertEquals(B, dst.getRepository().getRef(master).getObjectId());
+		assertEquals(B, dst.getRepository().exactRef(master).getObjectId());
 		List<AccessEvent> cloneRequests = getRequests();
 
 		// Only create a few new commits.
@@ -358,7 +358,7 @@ public void testFetch_FewLocalCommits() throws Exception {
 		} finally {
 			t.close();
 		}
-		assertEquals(Z, dst.getRepository().getRef(master).getObjectId());
+		assertEquals(Z, dst.getRepository().exactRef(master).getObjectId());
 
 		List<AccessEvent> requests = getRequests();
 		requests.removeAll(cloneRequests);
@@ -400,7 +400,7 @@ public void testFetch_TooManyLocalCommits() throws Exception {
 		} finally {
 			t.close();
 		}
-		assertEquals(B, dst.getRepository().getRef(master).getObjectId());
+		assertEquals(B, dst.getRepository().exactRef(master).getObjectId());
 		List<AccessEvent> cloneRequests = getRequests();
 
 		// Force enough into the local client that enumeration will
@@ -424,7 +424,7 @@ public void testFetch_TooManyLocalCommits() throws Exception {
 		} finally {
 			t.close();
 		}
-		assertEquals(Z, dst.getRepository().getRef(master).getObjectId());
+		assertEquals(Z, dst.getRepository().exactRef(master).getObjectId());
 
 		List<AccessEvent> requests = getRequests();
 		requests.removeAll(cloneRequests);
@@ -579,8 +579,8 @@ public void testPush_CreateBranch() throws Exception {
 		}
 
 		assertTrue(remoteRepository.hasObject(Q_txt));
-		assertNotNull("has " + dstName, remoteRepository.getRef(dstName));
-		assertEquals(Q, remoteRepository.getRef(dstName).getObjectId());
+		assertNotNull("has " + dstName, remoteRepository.exactRef(dstName));
+		assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId());
 		fsck(remoteRepository, Q);
 
 		final ReflogReader log = remoteRepository.getReflogReader(dstName);
@@ -657,8 +657,8 @@ public void testPush_ChunkedEncoding() throws Exception {
 		}
 
 		assertTrue(remoteRepository.hasObject(Q_bin));
-		assertNotNull("has " + dstName, remoteRepository.getRef(dstName));
-		assertEquals(Q, remoteRepository.getRef(dstName).getObjectId());
+		assertNotNull("has " + dstName, remoteRepository.exactRef(dstName));
+		assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId());
 		fsck(remoteRepository, Q);
 
 		List<AccessEvent> requests = getRequests();
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java
index 521593e..2962e71 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java
@@ -241,6 +241,17 @@ public static void deleteTrashFile(final Repository db,
 		FileUtils.delete(path);
 	}
 
+	/**
+	 * @param db
+	 *            the repository
+	 * @param link
+	 *            the path of the symbolic link to create
+	 * @param target
+	 *            the target of the symbolic link
+	 * @return the path to the symbolic link
+	 * @throws Exception
+	 * @since 4.2
+	 */
 	public static Path writeLink(Repository db, String link,
 			String target) throws Exception {
 		return FileUtils.createSymLink(new File(db.getWorkTree(), link),
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
index bb5f9ef..4d713b5 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
@@ -91,7 +91,10 @@ public abstract class LocalDiskRepositoryTestCase {
 	/** A fake (but stable) identity for committer fields in the test. */
 	protected PersonIdent committer;
 
-	/** A {@link SystemReader} used to coordinate time, envars, etc. */
+	/**
+	 * A {@link SystemReader} used to coordinate time, envars, etc.
+	 * @since 4.2
+	 */
 	protected MockSystemReader mockSystemReader;
 
 	private final List<Repository> toClose = new ArrayList<Repository>();
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
index 03a2b1a..28a9556 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
@@ -151,6 +151,7 @@ public long getCurrentTime() {
 	 *
 	 * @param secDelta
 	 *            number of seconds to add to the current time.
+	 * @since 4.2
 	 */
 	public void tick(final int secDelta) {
 		now += secDelta * 1000L;
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
index 28c6177..c649eb9 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
@@ -108,6 +108,17 @@ protected File writeTrashFile(final String name, final String data)
 		return JGitTestUtil.writeTrashFile(db, name, data);
 	}
 
+	/**
+	 * Create a symbolic link
+	 *
+	 * @param link
+	 *            the path of the symbolic link to create
+	 * @param target
+	 *            the target of the symbolic link
+	 * @return the path to the symbolic link
+	 * @throws Exception
+	 * @since 4.2
+	 */
 	protected Path writeLink(final String link, final String target)
 			throws Exception {
 		return JGitTestUtil.writeLink(db, link, target);
@@ -272,6 +283,19 @@ public static String lookup(Object l, String nameTemplate,
 	}
 
 	/**
+	 * Replaces '\' by '/'
+	 *
+	 * @param str
+	 *            the string in which backslashes should be replaced
+	 * @return the resulting string with slashes
+         * @since 4.2
+	 */
+	public static String slashify(String str) {
+		str = str.replace('\\', '/');
+		return str;
+	}
+
+	/**
 	 * Waits until it is guaranteed that a subsequent file modification has a
 	 * younger modification timestamp than the modification timestamp of the
 	 * given file. This is done by touching a temporary file, reading the
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
index 251e65f..ac9685d 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
@@ -175,6 +175,7 @@ public TestRepository(R db, RevWalk rw) throws IOException {
 	 *            the MockSystemReader to use for clock and other system
 	 *            operations.
 	 * @throws IOException
+	 * @since 4.2
 	 */
 	public TestRepository(R db, RevWalk rw, MockSystemReader reader)
 			throws IOException {
@@ -203,7 +204,10 @@ public Git git() {
 		return git;
 	}
 
-	/** @return current date. */
+	/**
+	 * @return current date.
+	 * @since 4.2
+	 */
 	public Date getDate() {
 		return new Date(mockSystemReader.getCurrentTime());
 	}
diff --git "a/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests \050Java7\051.launch" "b/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests \050Java7\051.launch"
index 600ce7b..3df0dcb 100644
--- "a/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests \050Java7\051.launch"
+++ "b/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests \050Java7\051.launch"
@@ -15,12 +15,6 @@
 <booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
 <stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
 <stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/>
-<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#10;"/>
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;org.eclipse.jgit.pgm.test&quot;/&gt;&#10;&lt;/runtimeClasspathEntry&gt;&#10;"/>
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry path=&quot;3&quot; projectName=&quot;org.eclipse.jgit.java7&quot; type=&quot;1&quot;/&gt;&#10;"/>
-</listAttribute>
-<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
 <stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
 <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value=""/>
 <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="org.eclipse.jgit.pgm.test"/>
diff --git "a/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests \050Java8\051.launch" "b/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests \050Java8\051.launch"
index 1b61d3d..ce473ed 100644
--- "a/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests \050Java8\051.launch"
+++ "b/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests \050Java8\051.launch"
@@ -19,7 +19,6 @@
 <listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
 <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#10;"/>
 <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;org.eclipse.jgit.pgm.test&quot;/&gt;&#10;&lt;/runtimeClasspathEntry&gt;&#10;"/>
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry path=&quot;3&quot; projectName=&quot;org.eclipse.jgit.java7&quot; type=&quot;1&quot;/&gt;&#10;"/>
 </listAttribute>
 <booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
 <stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
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 7bf4c26..939a9f6 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
@@ -198,7 +198,8 @@ public void testCheckoutOrphan() throws Exception {
 
 		assertStringArrayEquals("Switched to a new branch 'new_branch'",
 				execute("git checkout --orphan new_branch"));
-		assertEquals("refs/heads/new_branch", db.getRef("HEAD").getTarget().getName());
+		assertEquals("refs/heads/new_branch",
+				db.exactRef("HEAD").getTarget().getName());
 		RevCommit commit = git.commit().setMessage("orphan commit").call();
 		assertEquals(0, commit.getParentCount());
 	}
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RemoteTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RemoteTest.java
new file mode 100644
index 0000000..58e0e19
--- /dev/null
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RemoteTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.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 static org.junit.Assert.assertTrue;
+
+import java.util.List;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.lib.CLIRepositoryTestCase;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.URIish;
+import org.junit.Before;
+import org.junit.Test;
+
+public class RemoteTest extends CLIRepositoryTestCase {
+
+	private StoredConfig config;
+
+	private RemoteConfig remote;
+
+	@Before
+	@Override
+	public void setUp() throws Exception {
+		super.setUp();
+
+		// create another repository
+		Repository remoteRepository = createWorkRepository();
+
+		// set it up as a remote to this repository
+		config = db.getConfig();
+		remote = new RemoteConfig(config, "test");
+		remote.addFetchRefSpec(
+				new RefSpec("+refs/heads/*:refs/remotes/test/*"));
+		URIish uri = new URIish(
+				remoteRepository.getDirectory().toURI().toURL());
+		remote.addURI(uri);
+		remote.update(config);
+		config.save();
+
+		Git.wrap(remoteRepository).commit().setMessage("initial commit").call();
+	}
+
+	@Test
+	public void testList() throws Exception {
+		assertArrayEquals(new String[] { remote.getName(), "" },
+				execute("git remote"));
+	}
+
+	@Test
+	public void testVerboseList() throws Exception {
+		assertArrayEquals(
+				new String[] {
+						String.format("%s\t%s (fetch)", remote.getName(),
+								remote.getURIs().get(0)),
+						String.format("%s\t%s (push)", remote.getName(),
+								remote.getURIs().get(0)),
+						"" },
+				execute("git remote -v"));
+	}
+
+	@Test
+	public void testAdd() throws Exception {
+		assertArrayEquals(new String[] { "" },
+				execute("git remote add second git://test.com/second"));
+
+		List<RemoteConfig> remotes = RemoteConfig.getAllRemoteConfigs(config);
+		assertEquals(2, remotes.size());
+		assertEquals("second", remotes.get(0).getName());
+		assertEquals("test", remotes.get(1).getName());
+	}
+
+	@Test
+	public void testRemove() throws Exception {
+		assertArrayEquals(new String[] { "" },
+				execute("git remote remove test"));
+
+		assertTrue(RemoteConfig.getAllRemoteConfigs(config).isEmpty());
+	}
+
+	@Test
+	public void testSetUrl() throws Exception {
+		assertArrayEquals(new String[] { "" },
+				execute("git remote set-url test git://test.com/test"));
+
+		RemoteConfig result = new RemoteConfig(config, "test");
+		assertEquals("test", result.getName());
+		assertArrayEquals(new URIish[] { new URIish("git://test.com/test") },
+				result.getURIs().toArray());
+		assertTrue(result.getPushURIs().isEmpty());
+	}
+
+	@Test
+	public void testSetUrlPush() throws Exception {
+		assertArrayEquals(new String[] { "" },
+				execute("git remote set-url --push test git://test.com/test"));
+
+		RemoteConfig result = new RemoteConfig(config, "test");
+		assertEquals("test", result.getName());
+		assertEquals(remote.getURIs(), result.getURIs());
+		assertArrayEquals(new URIish[] { new URIish("git://test.com/test") },
+				result.getPushURIs().toArray());
+	}
+
+	@Test
+	public void testUpdate() throws Exception {
+		assertArrayEquals(new String[] {
+				"From " + remote.getURIs().get(0).toString(),
+				" * [new branch]      master     -> test/master", "", "" },
+				execute("git remote update test"));
+	}
+
+}
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java
index 0785c5c..dae4779 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java
@@ -67,7 +67,7 @@ public void testResetSelf() throws Exception {
 		assertStringArrayEquals("",
 				execute("git reset --hard " + commit.getId().name()));
 		assertEquals(commit.getId(),
-				git.getRepository().getRef("HEAD").getObjectId());
+				git.getRepository().exactRef("HEAD").getObjectId());
 	}
 
 	@Test
@@ -77,7 +77,7 @@ public void testResetPrevious() throws Exception {
 		assertStringArrayEquals("",
 				execute("git reset --hard " + commit.getId().name()));
 		assertEquals(commit.getId(),
-				git.getRepository().getRef("HEAD").getObjectId());
+				git.getRepository().exactRef("HEAD").getObjectId());
 	}
 
 	@Test
@@ -86,7 +86,7 @@ public void testResetEmptyPath() throws Exception {
 		assertStringArrayEquals("",
 				execute("git reset --hard " + commit.getId().name() + " --"));
 		assertEquals(commit.getId(),
-				git.getRepository().getRef("HEAD").getObjectId());
+				git.getRepository().exactRef("HEAD").getObjectId());
 	}
 
 	@Test
@@ -119,7 +119,7 @@ private void resetPath(boolean useDoubleDash) throws Exception {
 				(useDoubleDash) ? " --" : "");
 		assertStringArrayEquals("", execute(cmd));
 		assertEquals(commit.getId(),
-				git.getRepository().getRef("HEAD").getObjectId());
+				git.getRepository().exactRef("HEAD").getObjectId());
 
 		org.eclipse.jgit.api.Status status = git.status().call();
 		// assert that file a is unstaged
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java
index 793fc7d..854c52d 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java
@@ -42,13 +42,14 @@
  */
 package org.eclipse.jgit.pgm;
 
+import static org.eclipse.jgit.lib.Constants.MASTER;
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
 
 import java.io.IOException;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.lib.CLIRepositoryTestCase;
-import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.junit.Test;
 
@@ -254,7 +255,7 @@ private void mergeTestBranchInMaster(Git git, RevCommit aCommit)
 	}
 
 	private void detachHead(Git git) throws IOException, GitAPIException {
-		String commitId = db.getRef(Constants.MASTER).getObjectId().name();
+		String commitId = db.exactRef(R_HEADS + MASTER).getObjectId().name();
 		git.checkout().setName(commitId).call();
 	}
 
diff --git a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
index e1b0549..c13f63e 100644
--- a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
+++ b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
@@ -24,6 +24,7 @@
 org.eclipse.jgit.pgm.Push
 org.eclipse.jgit.pgm.ReceivePack
 org.eclipse.jgit.pgm.Reflog
+org.eclipse.jgit.pgm.Remote
 org.eclipse.jgit.pgm.Repo
 org.eclipse.jgit.pgm.Reset
 org.eclipse.jgit.pgm.RevList
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 64afdad..335336d 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
@@ -128,6 +128,7 @@
 metaVar_version=VERSION
 mostCommonlyUsedCommandsAre=The most commonly used commands are:
 needApprovalToDestroyCurrentRepository=Need approval to destroy current repository
+needSingleRevision=Needed a single revision
 noGitRepositoryConfigured=No Git repository configured.
 noNamesFound=No names found, cannot describe anything.
 noSuchFile=no such file: {0}
@@ -142,6 +143,7 @@
 notARevision=Not a revision: {0}
 notATree={0} is not a tree
 notAValidRefName={0} is not a valid ref name
+notAValidCommitName={0} is not a valid commit name
 notAnIndexFile={0} is not an index file
 notAnObject={0} is not an object
 notFound=!! NOT FOUND !!
@@ -186,6 +188,7 @@
 tooManyRefsGiven=Too many refs given
 unknownIoErrorStdout=An unknown I/O error occurred on standard output
 unknownMergeStrategy=unknown merge strategy {0} specified
+unknownSubcommand=Unknown subcommand: {0}
 unmergedPaths=Unmerged paths:
 unsupportedOperation=Unsupported operation: {0}
 untrackedFiles=Untracked files:
@@ -220,6 +223,7 @@
 usage_MergesTwoDevelopmentHistories=Merges two development histories
 usage_ReadDirCache= Read the DirCache 100 times
 usage_RebuildCommitGraph=Recreate a repository from another one's commit graph
+usage_Remote=Manage set of tracked repositories
 usage_RepositoryToReadFrom=Repository to read from
 usage_RepositoryToReceiveInto=Repository to receive into
 usage_RevList=List commit objects in reverse chronological order
@@ -327,6 +331,7 @@
 usage_portNumberToListenOn=port number to listen on
 usage_printOnlyBranchesThatContainTheCommit=print only branches that contain the commit
 usage_pruneStaleTrackingRefs=prune stale tracking refs
+usage_pushUrls=push URLs are manipulated
 usage_quiet=don't show progress messages
 usage_recordChangesToRepository=Record changes to the repository
 usage_recurseIntoSubtrees=recurse into subtrees
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java
index 83a1ca7..65aa24f 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java
@@ -154,10 +154,14 @@ protected void run() throws Exception {
 					startBranch = Constants.HEAD;
 				Ref startRef = db.getRef(startBranch);
 				ObjectId startAt = db.resolve(startBranch + "^0"); //$NON-NLS-1$
-				if (startRef != null)
+				if (startRef != null) {
 					startBranch = startRef.getName();
-				else
+				} else if (startAt != null) {
 					startBranch = startAt.name();
+				} else {
+					throw die(MessageFormat.format(
+							CLIText.get().notAValidCommitName, startBranch));
+				}
 				startBranch = Repository.shortenRefName(startBranch);
 				String newRefName = newHead;
 				if (!newRefName.startsWith(Constants.R_HEADS))
@@ -249,7 +253,7 @@ private void delete(boolean force) throws IOException {
 		String current = db.getBranch();
 		ObjectId head = db.resolve(Constants.HEAD);
 		for (String branch : branches) {
-			if (current.equals(branch)) {
+			if (branch.equals(current)) {
 				throw die(MessageFormat.format(CLIText.get().cannotDeleteTheBranchWhichYouAreCurrentlyOn, branch));
 			}
 			RefUpdate update = db.updateRef((remote ? Constants.R_REMOTES
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java
index f18242d..38d8d70 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java
@@ -96,6 +96,9 @@ protected void run() throws NoHeadException, NoMessageException,
 			commitCmd.setAmend(amend);
 			commitCmd.setAll(all);
 			Ref head = db.getRef(Constants.HEAD);
+			if (head == null) {
+				throw die(CLIText.get().onBranchToBeBorn);
+			}
 			RevCommit commit;
 			try {
 				commit = commitCmd.call();
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java
index e0ff058..cd65af9 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java
@@ -120,7 +120,7 @@ protected void run() throws Exception {
 			throw die(MessageFormat.format(
 					CLIText.get().refDoesNotExistOrNoCommit, ref));
 
-		Ref oldHead = db.getRef(Constants.HEAD);
+		Ref oldHead = getOldHead();
 		MergeResult result;
 		try (Git git = new Git(db)) {
 			MergeCommand mergeCmd = git.merge().setStrategy(mergeStrategy)
@@ -205,6 +205,14 @@ protected void run() throws Exception {
 		}
 	}
 
+	private Ref getOldHead() throws IOException {
+		Ref oldHead = db.getRef(Constants.HEAD);
+		if (oldHead == null) {
+			throw die(CLIText.get().onBranchToBeBorn);
+		}
+		return oldHead;
+	}
+
 	private boolean isMergedInto(Ref oldHead, AnyObjectId src)
 			throws IOException {
 		try (RevWalk revWalk = new RevWalk(db)) {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java
index 1879ef5..9098c12 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java
@@ -82,6 +82,9 @@ class Push extends TextBuiltin {
 	@Option(name = "--all")
 	private boolean all;
 
+	@Option(name = "--atomic")
+	private boolean atomic;
+
 	@Option(name = "--tags")
 	private boolean tags;
 
@@ -122,6 +125,7 @@ protected void run() throws Exception {
 				push.setPushTags();
 			push.setRemote(remote);
 			push.setThin(thin);
+			push.setAtomic(atomic);
 			push.setTimeout(timeout);
 			Iterable<PushResult> results = push.call();
 			for (PushResult result : results) {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Remote.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Remote.java
new file mode 100644
index 0000000..70868e9
--- /dev/null
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Remote.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.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 java.io.IOException;
+import java.io.StringWriter;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.RemoteAddCommand;
+import org.eclipse.jgit.api.RemoteListCommand;
+import org.eclipse.jgit.api.RemoteRemoveCommand;
+import org.eclipse.jgit.api.RemoteSetUrlCommand;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.pgm.internal.CLIText;
+import org.eclipse.jgit.pgm.opt.CmdLineParser;
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.util.io.ThrowingPrintWriter;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+
+@Command(common = false, usage = "usage_Remote")
+class Remote extends TextBuiltin {
+
+	@Option(name = "--verbose", aliases = { "-v" }, usage = "usage_beVerbose")
+	private boolean verbose = false;
+
+	@Option(name = "--prune", aliases = {
+			"-p" }, usage = "usage_pruneStaleTrackingRefs")
+	private boolean prune;
+
+	@Option(name = "--push", usage = "usage_pushUrls")
+	private boolean push;
+
+	@Argument(index = 0, metaVar = "metaVar_command")
+	private String command;
+
+	@Argument(index = 1, metaVar = "metaVar_remoteName")
+	private String name;
+
+	@Argument(index = 2, metaVar = "metaVar_uriish")
+	private String uri;
+
+	@Override
+	protected void run() throws Exception {
+		try (Git git = new Git(db)) {
+			if (command == null) {
+				RemoteListCommand cmd = git.remoteList();
+				List<RemoteConfig> remotes = cmd.call();
+				print(remotes);
+			} else if ("add".equals(command)) { //$NON-NLS-1$
+				RemoteAddCommand cmd = git.remoteAdd();
+				cmd.setName(name);
+				cmd.setUri(new URIish(uri));
+				cmd.call();
+			} else if ("remove".equals(command) || "rm".equals(command)) { //$NON-NLS-1$ //$NON-NLS-2$
+				RemoteRemoveCommand cmd = git.remoteRemove();
+				cmd.setName(name);
+				cmd.call();
+			} else if ("set-url".equals(command)) { //$NON-NLS-1$
+				RemoteSetUrlCommand cmd = git.remoteSetUrl();
+				cmd.setName(name);
+				cmd.setUri(new URIish(uri));
+				cmd.setPush(push);
+				cmd.call();
+			} else if ("update".equals(command)) { //$NON-NLS-1$
+				// reuse fetch command for basic implementation of remote update
+				Fetch fetch = new Fetch();
+				fetch.init(db, gitdir);
+
+				// redirect the output stream
+				StringWriter osw = new StringWriter();
+				fetch.outw = new ThrowingPrintWriter(osw);
+				// redirect the error stream
+				StringWriter esw = new StringWriter();
+				fetch.errw = new ThrowingPrintWriter(esw);
+
+				List<String> fetchArgs = new ArrayList<>();
+				if (verbose) {
+					fetchArgs.add("--verbose"); //$NON-NLS-1$
+				}
+				if (prune) {
+					fetchArgs.add("--prune"); //$NON-NLS-1$
+				}
+				if (name != null) {
+					fetchArgs.add(name);
+				}
+
+				fetch.execute(fetchArgs.toArray(new String[fetchArgs.size()]));
+
+				// flush the streams
+				fetch.outw.flush();
+				fetch.errw.flush();
+				outw.println(osw.toString());
+				errw.println(esw.toString());
+			} else {
+				throw new JGitInternalException(MessageFormat
+						.format(CLIText.get().unknownSubcommand, command));
+			}
+		}
+	}
+
+	@Override
+	public void printUsageAndExit(final String message, final CmdLineParser clp)
+			throws IOException {
+		errw.println(message);
+		errw.println("jgit remote [--verbose (-v)] [--help (-h)]"); //$NON-NLS-1$
+		errw.println("jgit remote add name uri-ish [--help (-h)]"); //$NON-NLS-1$
+		errw.println("jgit remote remove name [--help (-h)]"); //$NON-NLS-1$
+		errw.println("jgit remote rm name [--help (-h)]"); //$NON-NLS-1$
+		errw.println(
+				"jgit remote [--verbose (-v)] update [name] [--prune (-p)] [--help (-h)]"); //$NON-NLS-1$
+		errw.println("jgit remote set-url name uri-ish [--push] [--help (-h)]"); //$NON-NLS-1$
+
+		errw.println();
+		clp.printUsage(errw, getResourceBundle());
+		errw.println();
+
+		errw.flush();
+		throw die(true);
+	}
+
+	private void print(List<RemoteConfig> remotes) throws IOException {
+		for (RemoteConfig remote : remotes) {
+			String remoteName = remote.getName();
+			if (verbose) {
+				List<URIish> fetchURIs = remote.getURIs();
+				List<URIish> pushURIs = remote.getPushURIs();
+
+				String fetchURI = ""; //$NON-NLS-1$
+				if (!fetchURIs.isEmpty()) {
+					fetchURI = fetchURIs.get(0).toString();
+				} else if (!pushURIs.isEmpty()) {
+					fetchURI = pushURIs.get(0).toString();
+				}
+
+				String pushURI = ""; //$NON-NLS-1$
+				if (!pushURIs.isEmpty()) {
+					pushURI = pushURIs.get(0).toString();
+				} else if (!fetchURIs.isEmpty()) {
+					pushURI = fetchURIs.get(0).toString();
+				}
+
+				outw.println(
+						String.format("%s\t%s (fetch)", remoteName, fetchURI)); //$NON-NLS-1$
+				outw.println(
+						String.format("%s\t%s (push)", remoteName, pushURI)); //$NON-NLS-1$
+			} else {
+				outw.println(remoteName);
+			}
+		}
+	}
+
+}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java
index 5530ac5..4456fd5 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java
@@ -1,6 +1,7 @@
 /*
  * Copyright (C) 2009, Daniel Cheng (aka SDiZ) <git@sdiz.net>
  * Copyright (C) 2009, Daniel Cheng (aka SDiZ) <j16sdiz+freenet@gmail.com>
+ * Copyright (C) 2015 Thomas Meyer <thomas@m3y3r.de>
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -51,14 +52,19 @@
 import java.util.Map;
 
 import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.CmdLineException;
 import org.kohsuke.args4j.Option;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.pgm.internal.CLIText;;
 
 @Command(usage = "usage_RevParse")
 class RevParse extends TextBuiltin {
 	@Option(name = "--all", usage = "usage_RevParseAll")
-	boolean all = false;
+	boolean all;
+
+	@Option(name = "--verify", usage = "usage_RevParseVerify")
+	boolean verify;
 
 	@Argument(index = 0, metaVar = "metaVar_commitish")
 	private final List<ObjectId> commits = new ArrayList<ObjectId>();
@@ -67,11 +73,17 @@ class RevParse extends TextBuiltin {
 	protected void run() throws Exception {
 		if (all) {
 			Map<String, Ref> allRefs = db.getRefDatabase().getRefs(ALL);
-			for (final Ref r : allRefs.values())
+			for (final Ref r : allRefs.values()) {
 				outw.println(r.getObjectId().name());
+			}
 		} else {
-			for (final ObjectId o : commits)
+			if (verify && commits.size() > 1) {
+				throw new CmdLineException(CLIText.get().needSingleRevision);
+			}
+
+			for (final ObjectId o : commits) {
 				outw.println(o.name());
+			}
 		}
 	}
 }
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java
index df7ebb7..d856989 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java
@@ -242,9 +242,10 @@ public int compare(Test a, Test b) {
 			}
 		});
 
-		if (db.getDirectory() != null) {
-			String name = db.getDirectory().getName();
-			File parent = db.getDirectory().getParentFile();
+		File directory = db.getDirectory();
+		if (directory != null) {
+			String name = directory.getName();
+			File parent = directory.getParentFile();
 			if (name.equals(Constants.DOT_GIT) && parent != null)
 				name = parent.getName();
 			outw.println(name + ": start at " + startId.name());
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
index 494055a..6260cd9 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
@@ -117,9 +117,12 @@ class RebuildCommitGraph extends TextBuiltin {
 	@Override
 	protected void run() throws Exception {
 		if (!really && !db.getRefDatabase().getRefs(ALL).isEmpty()) {
+			File directory = db.getDirectory();
+			String absolutePath = directory == null ? "null" //$NON-NLS-1$
+					: directory.getAbsolutePath();
 			errw.println(
 				MessageFormat.format(CLIText.get().fatalThisProgramWillDestroyTheRepository
-					, db.getDirectory().getAbsolutePath(), REALLY));
+					, absolutePath, REALLY));
 			throw die(CLIText.get().needApprovalToDestroyCurrentRepository);
 		}
 		if (!refList.isFile())
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
index dcbc37b..887ad08 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
@@ -341,9 +341,10 @@ private void run(Repository db) throws Exception {
 			}
 		}
 
-		if (db.getDirectory() != null) {
-			String name = db.getDirectory().getName();
-			File parent = db.getDirectory().getParentFile();
+		File directory = db.getDirectory();
+		if (directory != null) {
+			String name = directory.getName();
+			File parent = directory.getParentFile();
 			if (name.equals(Constants.DOT_GIT) && parent != null)
 				name = parent.getName();
 			outw.println(name + ":"); //$NON-NLS-1$
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 433ddf2..f5d581a 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
@@ -187,6 +187,7 @@ public static String formatLine(String line) {
 	/***/ public String metaVar_version;
 	/***/ public String mostCommonlyUsedCommandsAre;
 	/***/ public String needApprovalToDestroyCurrentRepository;
+	/***/ public String needSingleRevision;
 	/***/ public String noGitRepositoryConfigured;
 	/***/ public String noNamesFound;
 	/***/ public String noSuchFile;
@@ -201,6 +202,7 @@ public static String formatLine(String line) {
 	/***/ public String notARevision;
 	/***/ public String notATree;
 	/***/ public String notAValidRefName;
+	/***/ public String notAValidCommitName;
 	/***/ public String notAnIndexFile;
 	/***/ public String notAnObject;
 	/***/ public String notFound;
@@ -245,6 +247,7 @@ public static String formatLine(String line) {
 	/***/ public String treeIsRequired;
 	/***/ public char[] unknownIoErrorStdout;
 	/***/ public String unknownMergeStrategy;
+	/***/ public String unknownSubcommand;
 	/***/ public String unmergedPaths;
 	/***/ public String unsupportedOperation;
 	/***/ public String untrackedFiles;
diff --git "a/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests \050Java 7\051.launch" "b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests \050Java 7\051.launch"
index af009c0..a83fabb 100644
--- "a/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests \050Java 7\051.launch"
+++ "b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests \050Java 7\051.launch"
@@ -18,7 +18,6 @@
 <listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
 <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#10;"/>
 <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;org.eclipse.jgit.test&quot;/&gt;&#10;&lt;/runtimeClasspathEntry&gt;&#10;"/>
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry path=&quot;3&quot; projectName=&quot;org.eclipse.jgit.java7&quot; type=&quot;1&quot;/&gt;&#10;"/>
 </listAttribute>
 <booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
 <stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
diff --git "a/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests \050Java 8\051.launch" "b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests \050Java 8\051.launch"
index 04aa3ea..b221a11 100644
--- "a/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests \050Java 8\051.launch"
+++ "b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests \050Java 8\051.launch"
@@ -19,7 +19,6 @@
 <listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
 <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#10;"/>
 <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;org.eclipse.jgit.test&quot;/&gt;&#10;&lt;/runtimeClasspathEntry&gt;&#10;"/>
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry path=&quot;3&quot; projectName=&quot;org.eclipse.jgit.java7&quot; type=&quot;1&quot;/&gt;&#10;"/>
 </listAttribute>
 <booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
 <stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AbstractRemoteCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AbstractRemoteCommandTest.java
new file mode 100644
index 0000000..d6a6342
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AbstractRemoteCommandTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.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 java.io.IOException;
+import java.net.URISyntaxException;
+
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.URIish;
+
+public class AbstractRemoteCommandTest extends RepositoryTestCase {
+
+	protected static final String REMOTE_NAME = "test";
+
+	protected RemoteConfig setupRemote()
+			throws IOException, URISyntaxException {
+		// create another repository
+		Repository remoteRepository = createWorkRepository();
+
+		// set it up as a remote to this repository
+		final StoredConfig config = db.getConfig();
+		RemoteConfig remoteConfig = new RemoteConfig(config, REMOTE_NAME);
+
+		RefSpec refSpec = new RefSpec();
+		refSpec = refSpec.setForceUpdate(true);
+		refSpec = refSpec.setSourceDestination(Constants.R_HEADS + "*",
+				Constants.R_REMOTES + REMOTE_NAME + "/*");
+		remoteConfig.addFetchRefSpec(refSpec);
+
+		URIish uri = new URIish(
+				remoteRepository.getDirectory().toURI().toURL());
+		remoteConfig.addURI(uri);
+
+		remoteConfig.update(config);
+		config.save();
+
+		return remoteConfig;
+	}
+
+	protected void assertRemoteConfigEquals(RemoteConfig expected,
+			RemoteConfig actual) {
+		assertEquals(expected.getName(), actual.getName());
+		assertEquals(expected.getURIs(), actual.getURIs());
+		assertEquals(expected.getPushURIs(), actual.getPushURIs());
+		assertEquals(expected.getFetchRefSpecs(), actual.getFetchRefSpecs());
+		assertEquals(expected.getPushRefSpecs(), actual.getPushRefSpecs());
+	}
+
+}
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 2abed3a..a5ad18d 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
@@ -45,6 +45,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import java.io.File;
@@ -52,11 +53,13 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 
+import org.eclipse.jgit.api.errors.FilterFailedException;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.api.errors.NoFilepatternException;
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheBuilder;
 import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
@@ -112,6 +115,191 @@ public void testAddExistingSingleFile() throws IOException, GitAPIException {
 	}
 
 	@Test
+	public void testCleanFilter() throws IOException,
+			GitAPIException {
+		writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
+		writeTrashFile("src/a.tmp", "foo");
+		// Caution: we need a trailing '\n' since sed on mac always appends
+		// linefeeds if missing
+		writeTrashFile("src/a.txt", "foo\n");
+		File script = writeTempFile("sed s/o/e/g");
+
+		Git git = new Git(db);
+		StoredConfig config = git.getRepository().getConfig();
+		config.setString("filter", "tstFilter", "clean",
+				"sh " + slashify(script.getPath()));
+		config.save();
+
+		git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp")
+				.call();
+
+		assertEquals(
+				"[src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:fee\n]",
+				indexState(CONTENT));
+	}
+
+	@Test
+	public void testCleanFilterEnvironment()
+			throws IOException, GitAPIException {
+		writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
+		writeTrashFile("src/a.txt", "foo");
+		File script = writeTempFile("echo $GIT_DIR; echo 1 >xyz");
+
+		Git git = new Git(db);
+		StoredConfig config = git.getRepository().getConfig();
+		config.setString("filter", "tstFilter", "clean",
+				"sh " + slashify(script.getPath()));
+		config.save();
+		git.add().addFilepattern("src/a.txt").call();
+
+		String gitDir = db.getDirectory().getAbsolutePath();
+		assertEquals("[src/a.txt, mode:100644, content:" + gitDir
+				+ "\n]", indexState(CONTENT));
+		assertTrue(new File(db.getWorkTree(), "xyz").exists());
+	}
+
+	@Test
+	public void testMultipleCleanFilter() throws IOException, GitAPIException {
+		writeTrashFile(".gitattributes",
+				"*.txt filter=tstFilter\n*.tmp filter=tstFilter2");
+		// Caution: we need a trailing '\n' since sed on mac always appends
+		// linefeeds if missing
+		writeTrashFile("src/a.tmp", "foo\n");
+		writeTrashFile("src/a.txt", "foo\n");
+		File script = writeTempFile("sed s/o/e/g");
+		File script2 = writeTempFile("sed s/f/x/g");
+
+		Git git = new Git(db);
+		StoredConfig config = git.getRepository().getConfig();
+		config.setString("filter", "tstFilter", "clean",
+				"sh " + slashify(script.getPath()));
+		config.setString("filter", "tstFilter2", "clean",
+				"sh " + slashify(script2.getPath()));
+		config.save();
+
+		git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp")
+				.call();
+
+		assertEquals(
+				"[src/a.tmp, mode:100644, content:xoo\n][src/a.txt, mode:100644, content:fee\n]",
+				indexState(CONTENT));
+
+		// TODO: multiple clean filters for one file???
+	}
+
+	/**
+	 * The path of an added file name contains ';' and afterwards malicious
+	 * commands. Make sure when calling filter commands to properly escape the
+	 * filenames
+	 *
+	 * @throws IOException
+	 * @throws GitAPIException
+	 */
+	@Test
+	public void testCommandInjection() throws IOException, GitAPIException {
+		// Caution: we need a trailing '\n' since sed on mac always appends
+		// linefeeds if missing
+		writeTrashFile("; echo virus", "foo\n");
+		File script = writeTempFile("sed s/o/e/g");
+
+		Git git = new Git(db);
+		StoredConfig config = git.getRepository().getConfig();
+		config.setString("filter", "tstFilter", "clean",
+				"sh " + slashify(script.getPath()) + " %f");
+		writeTrashFile(".gitattributes", "* filter=tstFilter");
+
+		git.add().addFilepattern("; echo virus").call();
+		// Without proper escaping the content would be "feovirus". The sed
+		// command and the "echo virus" would contribute to the content
+		assertEquals("[; echo virus, mode:100644, content:fee\n]",
+				indexState(CONTENT));
+	}
+
+	@Test
+	public void testBadCleanFilter() throws IOException, GitAPIException {
+		writeTrashFile("a.txt", "foo");
+		File script = writeTempFile("sedfoo s/o/e/g");
+
+		Git git = new Git(db);
+		StoredConfig config = git.getRepository().getConfig();
+		config.setString("filter", "tstFilter", "clean",
+				"sh " + script.getPath());
+		config.save();
+		writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
+
+		try {
+			git.add().addFilepattern("a.txt").call();
+			fail("Didn't received the expected exception");
+		} catch (FilterFailedException e) {
+			assertEquals(127, e.getReturnCode());
+		}
+	}
+
+	@Test
+	public void testBadCleanFilter2() throws IOException, GitAPIException {
+		writeTrashFile("a.txt", "foo");
+		File script = writeTempFile("sed s/o/e/g");
+
+		Git git = new Git(db);
+		StoredConfig config = git.getRepository().getConfig();
+		config.setString("filter", "tstFilter", "clean",
+				"shfoo " + script.getPath());
+		config.save();
+		writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
+
+		try {
+			git.add().addFilepattern("a.txt").call();
+			fail("Didn't received the expected exception");
+		} catch (FilterFailedException e) {
+			assertEquals(127, e.getReturnCode());
+		}
+	}
+
+	@Test
+	public void testCleanFilterReturning12() throws IOException,
+			GitAPIException {
+		writeTrashFile("a.txt", "foo");
+		File script = writeTempFile("exit 12");
+
+		Git git = new Git(db);
+		StoredConfig config = git.getRepository().getConfig();
+		config.setString("filter", "tstFilter", "clean",
+				"sh " + slashify(script.getPath()));
+		config.save();
+		writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
+
+		try {
+			git.add().addFilepattern("a.txt").call();
+			fail("Didn't received the expected exception");
+		} catch (FilterFailedException e) {
+			assertEquals(12, e.getReturnCode());
+		}
+	}
+
+	@Test
+	public void testNotApplicableFilter() throws IOException, GitAPIException {
+		writeTrashFile("a.txt", "foo");
+		File script = writeTempFile("sed s/o/e/g");
+
+		Git git = new Git(db);
+		StoredConfig config = git.getRepository().getConfig();
+		config.setString("filter", "tstFilter", "something",
+				"sh " + script.getPath());
+		config.save();
+		writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
+
+		git.add().addFilepattern("a.txt").call();
+
+		assertEquals("[a.txt, mode:100644, content:foo]", indexState(CONTENT));
+	}
+
+	private File writeTempFile(String body) throws IOException {
+		File f = File.createTempFile("AddCommandTest_", "");
+		JGitTestUtil.write(f, body);
+		return f;
+	}
+
+	@Test
 	public void testAddExistingSingleSmallFileWithNewLine() throws IOException,
 			GitAPIException {
 		File file = new File(db.getWorkTree(), "a.txt");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
index f7a50df..4bfb128 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
@@ -43,6 +43,8 @@
  */
 package org.eclipse.jgit.api;
 
+import static org.eclipse.jgit.lib.Constants.MASTER;
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -70,12 +72,14 @@
 import org.eclipse.jgit.api.errors.TransportException;
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheEntry;
+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.Ref;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.Sets;
 import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.storage.file.FileBasedConfig;
@@ -84,6 +88,7 @@
 import org.eclipse.jgit.transport.URIish;
 import org.eclipse.jgit.util.FileUtils;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
 public class CheckoutCommandTest extends RepositoryTestCase {
@@ -134,7 +139,7 @@ public void testCheckout() throws Exception {
 	@Test
 	public void testCreateBranchOnCheckout() throws Exception {
 		git.checkout().setCreateBranch(true).setName("test2").call();
-		assertNotNull(db.getRef("test2"));
+		assertNotNull(db.exactRef("refs/heads/test2"));
 	}
 
 	@Test
@@ -237,8 +242,8 @@ public void testCheckoutRemoteTrackingWithUpstream() throws Exception {
 				.setStartPoint("origin/test")
 				.setUpstreamMode(SetupUpstreamMode.TRACK).call();
 
-		assertEquals("refs/heads/test", db2.getRef(Constants.HEAD).getTarget()
-				.getName());
+		assertEquals("refs/heads/test",
+				db2.exactRef(Constants.HEAD).getTarget().getName());
 		StoredConfig config = db2.getConfig();
 		assertEquals("origin", config.getString(
 				ConfigConstants.CONFIG_BRANCH_SECTION, "test",
@@ -345,7 +350,7 @@ public void testDetachedHeadOnCheckout() throws JGitInternalException,
 		CheckoutCommand co = git.checkout();
 		co.setName("master").call();
 
-		String commitId = db.getRef(Constants.MASTER).getObjectId().name();
+		String commitId = db.exactRef(R_HEADS + MASTER).getObjectId().name();
 		co = git.checkout();
 		co.setName(commitId).call();
 
@@ -443,7 +448,7 @@ private void assertNoHead() throws IOException {
 	}
 
 	private void assertHeadDetached() throws IOException {
-		Ref head = db.getRef(Constants.HEAD);
+		Ref head = db.exactRef(Constants.HEAD);
 		assertFalse(head.isSymbolic());
 		assertSame(head, head.getTarget());
 	}
@@ -554,4 +559,125 @@ public void testCheckoutAutoCrlfTrue() throws Exception {
 		}
 		org.junit.Assume.assumeTrue(foundUnsmudged);
 	}
+
+	@Test
+	public void testSmudgeFilter_modifyExisting() throws IOException, GitAPIException {
+		File script = writeTempFile("sed s/o/e/g");
+		StoredConfig config = git.getRepository().getConfig();
+		config.setString("filter", "tstFilter", "smudge",
+				"sh " + slashify(script.getPath()));
+		config.save();
+
+		writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
+		git.add().addFilepattern(".gitattributes").call();
+		git.commit().setMessage("add filter").call();
+
+		writeTrashFile("src/a.tmp", "x");
+		// Caution: we need a trailing '\n' since sed on mac always appends
+		// linefeeds if missing
+		writeTrashFile("src/a.txt", "x\n");
+		git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt")
+				.call();
+		RevCommit content1 = git.commit().setMessage("add content").call();
+
+		writeTrashFile("src/a.tmp", "foo");
+		writeTrashFile("src/a.txt", "foo\n");
+		git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt")
+				.call();
+		RevCommit content2 = git.commit().setMessage("changed content").call();
+
+		git.checkout().setName(content1.getName()).call();
+		git.checkout().setName(content2.getName()).call();
+
+		assertEquals(
+				"[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some change][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:foo\n]",
+				indexState(CONTENT));
+		assertEquals(Sets.of("src/a.txt"), git.status().call().getModified());
+		assertEquals("foo", read("src/a.tmp"));
+		assertEquals("fee\n", read("src/a.txt"));
+	}
+
+	@Test
+	public void testSmudgeFilter_createNew()
+			throws IOException, GitAPIException {
+		File script = writeTempFile("sed s/o/e/g");
+		StoredConfig config = git.getRepository().getConfig();
+		config.setString("filter", "tstFilter", "smudge",
+				"sh " + slashify(script.getPath()));
+		config.save();
+
+		writeTrashFile("foo", "foo");
+		git.add().addFilepattern("foo").call();
+		RevCommit initial = git.commit().setMessage("initial").call();
+
+		writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
+		git.add().addFilepattern(".gitattributes").call();
+		git.commit().setMessage("add filter").call();
+
+		writeTrashFile("src/a.tmp", "foo");
+		// Caution: we need a trailing '\n' since sed on mac always appends
+		// linefeeds if missing
+		writeTrashFile("src/a.txt", "foo\n");
+		git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt")
+				.call();
+		RevCommit content = git.commit().setMessage("added content").call();
+
+		git.checkout().setName(initial.getName()).call();
+		git.checkout().setName(content.getName()).call();
+
+		assertEquals(
+				"[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some change][foo, mode:100644, content:foo][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:foo\n]",
+				indexState(CONTENT));
+		assertEquals("foo", read("src/a.tmp"));
+		assertEquals("fee\n", read("src/a.txt"));
+	}
+
+	@Test
+	@Ignore
+	public void testSmudgeAndClean() throws IOException, GitAPIException {
+		// @TODO: fix this test
+		File clean_filter = writeTempFile("sed s/V1/@version/g -");
+		File smudge_filter = writeTempFile("sed s/@version/V1/g -");
+
+		Git git = new Git(db);
+		StoredConfig config = git.getRepository().getConfig();
+		config.setString("filter", "tstFilter", "smudge",
+				"sh " + slashify(smudge_filter.getPath()));
+		config.setString("filter", "tstFilter", "clean",
+				"sh " + slashify(clean_filter.getPath()));
+		config.save();
+		writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
+		git.add().addFilepattern(".gitattributes").call();
+		git.commit().setMessage("add attributes").call();
+
+		writeTrashFile("filterTest.txt", "hello world, V1");
+		git.add().addFilepattern("filterTest.txt").call();
+		git.commit().setMessage("add filterText.txt").call();
+		assertEquals(
+				"[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some other change][filterTest.txt, mode:100644, content:hello world, @version]",
+				indexState(CONTENT));
+
+		git.checkout().setCreateBranch(true).setName("test2").call();
+		writeTrashFile("filterTest.txt", "bon giorno world, V1");
+		git.add().addFilepattern("filterTest.txt").call();
+		git.commit().setMessage("modified filterText.txt").call();
+
+		assertTrue(git.status().call().isClean());
+		assertEquals(
+				"[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some other change][filterTest.txt, mode:100644, content:bon giorno world, @version]",
+				indexState(CONTENT));
+
+		git.checkout().setName("refs/heads/test").call();
+		assertTrue(git.status().call().isClean());
+		assertEquals(
+				"[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some other change][filterTest.txt, mode:100644, content:hello world, @version]",
+				indexState(CONTENT));
+		assertEquals("hello world, V1", read("filterTest.txt"));
+	}
+
+	private File writeTempFile(String body) throws IOException {
+		File f = File.createTempFile("AddCommandTest_", "");
+		JGitTestUtil.write(f, body);
+		return f;
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
index 060a5b65c..0d03047 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
@@ -408,8 +408,10 @@ public void commitAfterSquashMerge() throws Exception {
 
 		checkoutBranch("refs/heads/master");
 
-		MergeResult result = git.merge().include(db.getRef("branch1"))
-				.setSquash(true).call();
+		MergeResult result = git.merge()
+				.include(db.exactRef("refs/heads/branch1"))
+				.setSquash(true)
+				.call();
 
 		assertTrue(new File(db.getWorkTree(), "file1").exists());
 		assertTrue(new File(db.getWorkTree(), "file2").exists());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
index 0c0c6e5..cea8393 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
@@ -43,6 +43,8 @@
  */
 package org.eclipse.jgit.api;
 
+import static org.eclipse.jgit.lib.Constants.MASTER;
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -97,7 +99,7 @@ public void testMergeInItself() throws Exception {
 		Git git = new Git(db);
 		git.commit().setMessage("initial commit").call();
 
-		MergeResult result = git.merge().include(db.getRef(Constants.HEAD)).call();
+		MergeResult result = git.merge().include(db.exactRef(Constants.HEAD)).call();
 		assertEquals(MergeResult.MergeStatus.ALREADY_UP_TO_DATE, result.getMergeStatus());
 		// no reflog entry written by merge
 		assertEquals("commit (initial): initial commit",
@@ -115,7 +117,7 @@ public void testAlreadyUpToDate() throws Exception {
 		createBranch(first, "refs/heads/branch1");
 
 		RevCommit second = git.commit().setMessage("second commit").call();
-		MergeResult result = git.merge().include(db.getRef("refs/heads/branch1")).call();
+		MergeResult result = git.merge().include(db.exactRef("refs/heads/branch1")).call();
 		assertEquals(MergeResult.MergeStatus.ALREADY_UP_TO_DATE, result.getMergeStatus());
 		assertEquals(second, result.getNewHead());
 		// no reflog entry written by merge
@@ -135,7 +137,7 @@ public void testFastForward() throws Exception {
 
 		checkoutBranch("refs/heads/branch1");
 
-		MergeResult result = git.merge().include(db.getRef(Constants.MASTER)).call();
+		MergeResult result = git.merge().include(db.exactRef(R_HEADS + MASTER)).call();
 
 		assertEquals(MergeResult.MergeStatus.FAST_FORWARD, result.getMergeStatus());
 		assertEquals(second, result.getNewHead());
@@ -155,7 +157,7 @@ public void testFastForwardNoCommit() throws Exception {
 
 		checkoutBranch("refs/heads/branch1");
 
-		MergeResult result = git.merge().include(db.getRef(Constants.MASTER))
+		MergeResult result = git.merge().include(db.exactRef(R_HEADS + MASTER))
 				.setCommit(false).call();
 
 		assertEquals(MergeResult.MergeStatus.FAST_FORWARD,
@@ -186,7 +188,7 @@ public void testFastForwardWithFiles() throws Exception {
 		checkoutBranch("refs/heads/branch1");
 		assertFalse(new File(db.getWorkTree(), "file2").exists());
 
-		MergeResult result = git.merge().include(db.getRef(Constants.MASTER)).call();
+		MergeResult result = git.merge().include(db.exactRef(R_HEADS + MASTER)).call();
 
 		assertTrue(new File(db.getWorkTree(), "file1").exists());
 		assertTrue(new File(db.getWorkTree(), "file2").exists());
@@ -221,7 +223,7 @@ public void testMultipleHeads() throws Exception {
 
 		MergeCommand merge = git.merge();
 		merge.include(second.getId());
-		merge.include(db.getRef(Constants.MASTER));
+		merge.include(db.exactRef(R_HEADS + MASTER));
 		try {
 			merge.call();
 			fail("Expected exception not thrown when merging multiple heads");
@@ -248,7 +250,7 @@ public void testMergeSuccessAllStrategies(MergeStrategy mergeStrategy)
 		git.commit().setMessage("third").call();
 
 		MergeResult result = git.merge().setStrategy(mergeStrategy)
-				.include(db.getRef(Constants.MASTER)).call();
+				.include(db.exactRef(R_HEADS + MASTER)).call();
 		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
 		assertEquals(
 				"merge refs/heads/master: Merge made by "
@@ -279,9 +281,9 @@ public void testMergeSuccessAllStrategiesNoCommit(
 
 		MergeResult result = git.merge().setStrategy(mergeStrategy)
 				.setCommit(false)
-				.include(db.getRef(Constants.MASTER)).call();
+				.include(db.exactRef(R_HEADS + MASTER)).call();
 		assertEquals(MergeStatus.MERGED_NOT_COMMITTED, result.getMergeStatus());
-		assertEquals(db.getRef(Constants.HEAD).getTarget().getObjectId(),
+		assertEquals(db.exactRef(Constants.HEAD).getTarget().getObjectId(),
 				thirdCommit.getId());
 	}
 
@@ -378,7 +380,7 @@ public void testMergeMessage() throws Exception {
 		git.add().addFilepattern("a").call();
 		git.commit().setMessage("main").call();
 
-		Ref sideBranch = db.getRef("side");
+		Ref sideBranch = db.exactRef("refs/heads/side");
 
 		git.merge().include(sideBranch)
 				.setStrategy(MergeStrategy.RESOLVE).call();
@@ -590,7 +592,7 @@ public void testSuccessfulContentMergeNoCommit() throws Exception {
 				.setCommit(false)
 				.setStrategy(MergeStrategy.RESOLVE).call();
 		assertEquals(MergeStatus.MERGED_NOT_COMMITTED, result.getMergeStatus());
-		assertEquals(db.getRef(Constants.HEAD).getTarget().getObjectId(),
+		assertEquals(db.exactRef(Constants.HEAD).getTarget().getObjectId(),
 				thirdCommit.getId());
 
 		assertEquals("1(side)\na\n3(main)\n", read(new File(db.getWorkTree(),
@@ -1310,8 +1312,10 @@ public void testSquashFastForward() throws Exception {
 		assertFalse(new File(db.getWorkTree(), "file2").exists());
 		assertFalse(new File(db.getWorkTree(), "file3").exists());
 
-		MergeResult result = git.merge().include(db.getRef("branch1"))
-				.setSquash(true).call();
+		MergeResult result = git.merge()
+				.include(db.exactRef("refs/heads/branch1"))
+				.setSquash(true)
+				.call();
 
 		assertTrue(new File(db.getWorkTree(), "file1").exists());
 		assertTrue(new File(db.getWorkTree(), "file2").exists());
@@ -1375,8 +1379,10 @@ public void testSquashMerge() throws Exception {
 		assertTrue(new File(db.getWorkTree(), "file2").exists());
 		assertFalse(new File(db.getWorkTree(), "file3").exists());
 
-		MergeResult result = git.merge().include(db.getRef("branch1"))
-				.setSquash(true).call();
+		MergeResult result = git.merge()
+				.include(db.exactRef("refs/heads/branch1"))
+				.setSquash(true)
+				.call();
 
 		assertTrue(new File(db.getWorkTree(), "file1").exists());
 		assertTrue(new File(db.getWorkTree(), "file2").exists());
@@ -1430,8 +1436,10 @@ public void testSquashMergeConflict() throws Exception {
 		assertTrue(new File(db.getWorkTree(), "file1").exists());
 		assertTrue(new File(db.getWorkTree(), "file2").exists());
 
-		MergeResult result = git.merge().include(db.getRef("branch1"))
-				.setSquash(true).call();
+		MergeResult result = git.merge()
+				.include(db.exactRef("refs/heads/branch1"))
+				.setSquash(true)
+				.call();
 
 		assertTrue(new File(db.getWorkTree(), "file1").exists());
 		assertTrue(new File(db.getWorkTree(), "file2").exists());
@@ -1468,7 +1476,7 @@ public void testFastForwardOnly() throws Exception {
 
 		MergeCommand merge = git.merge();
 		merge.setFastForward(FastForwardMode.FF_ONLY);
-		merge.include(db.getRef(Constants.MASTER));
+		merge.include(db.exactRef(R_HEADS + MASTER));
 		MergeResult result = merge.call();
 
 		assertEquals(MergeStatus.FAST_FORWARD, result.getMergeStatus());
@@ -1485,7 +1493,7 @@ public void testNoFastForward() throws Exception {
 
 		MergeCommand merge = git.merge();
 		merge.setFastForward(FastForwardMode.NO_FF);
-		merge.include(db.getRef(Constants.MASTER));
+		merge.include(db.exactRef(R_HEADS + MASTER));
 		MergeResult result = merge.call();
 
 		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
@@ -1505,7 +1513,7 @@ public void testNoFastForwardNoCommit() throws Exception {
 		// when
 		MergeCommand merge = git.merge();
 		merge.setFastForward(FastForwardMode.NO_FF);
-		merge.include(db.getRef(Constants.MASTER));
+		merge.include(db.exactRef(R_HEADS + MASTER));
 		merge.setCommit(false);
 		MergeResult result = merge.call();
 
@@ -1531,7 +1539,7 @@ public void testFastForwardOnlyNotPossible() throws Exception {
 		git.commit().setMessage("second commit on branch1").call();
 		MergeCommand merge = git.merge();
 		merge.setFastForward(FastForwardMode.FF_ONLY);
-		merge.include(db.getRef(Constants.MASTER));
+		merge.include(db.exactRef(R_HEADS + MASTER));
 		MergeResult result = merge.call();
 
 		assertEquals(MergeStatus.ABORTED, result.getMergeStatus());
@@ -1588,7 +1596,7 @@ public void testMergeWithMessageOption() throws Exception {
 		git.add().addFilepattern("c").call();
 		git.commit().setMessage("main").call();
 
-		Ref sideBranch = db.getRef("side");
+		Ref sideBranch = db.exactRef("refs/heads/side");
 
 		git.merge().include(sideBranch).setStrategy(MergeStrategy.RESOLVE)
 				.setMessage("user message").call();
@@ -1621,7 +1629,7 @@ public void testMergeConflictWithMessageOption() throws Exception {
 		git.add().addFilepattern("a").call();
 		git.commit().setMessage("main").call();
 
-		Ref sideBranch = db.getRef("side");
+		Ref sideBranch = db.exactRef("refs/heads/side");
 
 		git.merge().include(sideBranch).setStrategy(MergeStrategy.RESOLVE)
 				.setMessage("user message").call();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NameRevCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NameRevCommandTest.java
index 4915954..bd62200 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NameRevCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NameRevCommandTest.java
@@ -95,9 +95,9 @@ public void ref() throws Exception {
 		tr.update("refs/heads/master", c);
 		tr.update("refs/tags/tag", c);
 		assertOneResult("master",
-				git.nameRev().addRef(db.getRef("refs/heads/master")), c);
+				git.nameRev().addRef(db.exactRef("refs/heads/master")), c);
 		assertOneResult("tag",
-				git.nameRev().addRef(db.getRef("refs/tags/tag")), c);
+				git.nameRev().addRef(db.exactRef("refs/tags/tag")), c);
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
index ccf1a51..1fcfef9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
@@ -138,11 +138,9 @@ public void testPrePushHook() throws JGitInternalException, IOException,
 
 		RefSpec spec = new RefSpec("refs/heads/master:refs/heads/x");
 		git1.push().setRemote("test").setRefSpecs(spec).call();
-		assertEquals(
-				"1:test, 2:file://" + db2.getDirectory().toPath() //
-						+ "/, 3:\n" + "refs/heads/master " + commit.getName()
-						+ " refs/heads/x " + ObjectId.zeroId().name(),
-				read(hookOutput));
+		assertEquals("1:test, 2:" + uri + ", 3:\n" + "refs/heads/master "
+				+ commit.getName() + " refs/heads/x "
+				+ ObjectId.zeroId().name(), read(hookOutput));
 	}
 
 	private File writeHookFile(final String name, final String data)
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteAddCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteAddCommandTest.java
new file mode 100644
index 0000000..ed09446
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteAddCommandTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.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.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.URIish;
+import org.junit.Test;
+
+public class RemoteAddCommandTest extends AbstractRemoteCommandTest {
+
+	@Test
+	public void testAdd() throws Exception {
+		// create another repository
+		Repository remoteRepository = createWorkRepository();
+		URIish uri = new URIish(
+				remoteRepository.getDirectory().toURI().toURL());
+
+		// execute the command to add a new remote
+		RemoteAddCommand cmd = Git.wrap(db).remoteAdd();
+		cmd.setName(REMOTE_NAME);
+		cmd.setUri(uri);
+		RemoteConfig remote = cmd.call();
+
+		// assert that the added remote represents the remote repository
+		assertEquals(REMOTE_NAME, remote.getName());
+		assertArrayEquals(new URIish[] { uri }, remote.getURIs().toArray());
+		assertEquals(1, remote.getFetchRefSpecs().size());
+		assertEquals(
+				String.format("+refs/heads/*:refs/remotes/%s/*", REMOTE_NAME),
+				remote.getFetchRefSpecs().get(0).toString());
+
+		// assert that the added remote is available in the git configuration
+		assertRemoteConfigEquals(remote,
+				new RemoteConfig(db.getConfig(), REMOTE_NAME));
+	}
+
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteDeleteCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteDeleteCommandTest.java
new file mode 100644
index 0000000..7055daf
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteDeleteCommandTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.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.assertTrue;
+
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.junit.Test;
+
+public class RemoteDeleteCommandTest extends AbstractRemoteCommandTest {
+
+	@Test
+	public void testDelete() throws Exception {
+		// setup an initial remote
+		RemoteConfig remoteConfig = setupRemote();
+
+		// execute the command to remove the remote
+		RemoteRemoveCommand cmd = Git.wrap(db).remoteRemove();
+		cmd.setName(REMOTE_NAME);
+		RemoteConfig remote = cmd.call();
+
+		// assert that the removed remote is the initial remote
+		assertRemoteConfigEquals(remoteConfig, remote);
+		// assert that there are no remotes left
+		assertTrue(RemoteConfig.getAllRemoteConfigs(db.getConfig()).isEmpty());
+	}
+
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteListCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteListCommandTest.java
new file mode 100644
index 0000000..cf522ff
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteListCommandTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.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 java.util.List;
+
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.junit.Test;
+
+public class RemoteListCommandTest extends AbstractRemoteCommandTest {
+
+	@Test
+	public void testList() throws Exception {
+		// setup an initial remote
+		RemoteConfig remoteConfig = setupRemote();
+
+		// execute the command to list the remotes
+		List<RemoteConfig> remotes = Git.wrap(db).remoteList().call();
+
+		// assert that there is only one remote
+		assertEquals(1, remotes.size());
+		// assert that the available remote is the initial remote
+		assertRemoteConfigEquals(remoteConfig, remotes.get(0));
+	}
+
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteSetUrlCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteSetUrlCommandTest.java
new file mode 100644
index 0000000..6969c3d
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteSetUrlCommandTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.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.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.URIish;
+import org.junit.Test;
+
+public class RemoteSetUrlCommandTest extends AbstractRemoteCommandTest {
+
+	@Test
+	public void testSetUrl() throws Exception {
+		// setup an initial remote
+		setupRemote();
+
+		// execute the command to change the fetch url
+		RemoteSetUrlCommand cmd = Git.wrap(db).remoteSetUrl();
+		cmd.setName(REMOTE_NAME);
+		URIish newUri = new URIish("git://test.com/test");
+		cmd.setUri(newUri);
+		RemoteConfig remote = cmd.call();
+
+		// assert that the changed remote has the new fetch url
+		assertEquals(REMOTE_NAME, remote.getName());
+		assertArrayEquals(new URIish[] { newUri }, remote.getURIs().toArray());
+
+		// assert that the changed remote is available in the git configuration
+		assertRemoteConfigEquals(remote,
+				new RemoteConfig(db.getConfig(), REMOTE_NAME));
+	}
+
+	@Test
+	public void testSetPushUrl() throws Exception {
+		// setup an initial remote
+		RemoteConfig remoteConfig = setupRemote();
+
+		// execute the command to change the push url
+		RemoteSetUrlCommand cmd = Git.wrap(db).remoteSetUrl();
+		cmd.setName(REMOTE_NAME);
+		URIish newUri = new URIish("git://test.com/test");
+		cmd.setUri(newUri);
+		cmd.setPush(true);
+		RemoteConfig remote = cmd.call();
+
+		// assert that the changed remote has the old fetch url and the new push
+		// url
+		assertEquals(REMOTE_NAME, remote.getName());
+		assertEquals(remoteConfig.getURIs(), remote.getURIs());
+		assertArrayEquals(new URIish[] { newUri },
+				remote.getPushURIs().toArray());
+
+		// assert that the changed remote is available in the git configuration
+		assertRemoteConfigEquals(remote,
+				new RemoteConfig(db.getConfig(), REMOTE_NAME));
+	}
+
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
index 9997c8c..a67f2b9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
@@ -477,8 +477,10 @@ public void testHardResetAfterSquashMerge() throws Exception {
 
 		checkoutBranch("refs/heads/master");
 
-		MergeResult result = g.merge().include(db.getRef("branch1"))
-				.setSquash(true).call();
+		MergeResult result = g.merge()
+				.include(db.exactRef("refs/heads/branch1"))
+				.setSquash(true)
+				.call();
 
 		assertEquals(MergeResult.MergeStatus.FAST_FORWARD_SQUASHED,
 				result.getMergeStatus());
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 c317e3b..ae8551e 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
@@ -110,7 +110,7 @@ private void validateStashedCommit(final RevCommit commit,
 			int parentCount)
 			throws IOException {
 		assertNotNull(commit);
-		Ref stashRef = db.getRef(Constants.R_STASH);
+		Ref stashRef = db.exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
 		assertEquals(commit, stashRef.getObjectId());
 		assertNotNull(commit.getAuthorIdent());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java
index cfad817..859277e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java
@@ -96,13 +96,13 @@ public void dropWithNoStashedCommits() throws Exception {
 	@Test
 	public void dropWithInvalidLogIndex() throws Exception {
 		write(committedFile, "content2");
-		Ref stashRef = git.getRepository().getRef(Constants.R_STASH);
+		Ref stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNull(stashRef);
 		RevCommit stashed = git.stashCreate().call();
 		assertNotNull(stashed);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
-		assertEquals(stashed, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
+		assertEquals(stashed,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 		try {
 			assertNull(git.stashDrop().setStashRef(100).call());
 			fail("Exception not thrown");
@@ -115,15 +115,15 @@ public void dropWithInvalidLogIndex() throws Exception {
 	@Test
 	public void dropSingleStashedCommit() throws Exception {
 		write(committedFile, "content2");
-		Ref stashRef = git.getRepository().getRef(Constants.R_STASH);
+		Ref stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNull(stashRef);
 		RevCommit stashed = git.stashCreate().call();
 		assertNotNull(stashed);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
-		assertEquals(stashed, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
+		assertEquals(stashed,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 		assertNull(git.stashDrop().call());
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNull(stashRef);
 
 		ReflogReader reader = git.getRepository().getReflogReader(
@@ -134,25 +134,25 @@ public void dropSingleStashedCommit() throws Exception {
 	@Test
 	public void dropAll() throws Exception {
 		write(committedFile, "content2");
-		Ref stashRef = git.getRepository().getRef(Constants.R_STASH);
+		Ref stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNull(stashRef);
 		RevCommit firstStash = git.stashCreate().call();
 		assertNotNull(firstStash);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
-		assertEquals(firstStash, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		assertEquals(firstStash,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 
 		write(committedFile, "content3");
 		RevCommit secondStash = git.stashCreate().call();
 		assertNotNull(secondStash);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
-		assertEquals(secondStash, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		assertEquals(secondStash,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 
 		assertNull(git.stashDrop().setAll(true).call());
-		assertNull(git.getRepository().getRef(Constants.R_STASH));
+		assertNull(git.getRepository().exactRef(Constants.R_STASH));
 
 		ReflogReader reader = git.getRepository().getReflogReader(
 				Constants.R_STASH);
@@ -162,25 +162,25 @@ public void dropAll() throws Exception {
 	@Test
 	public void dropFirstStashedCommit() throws Exception {
 		write(committedFile, "content2");
-		Ref stashRef = git.getRepository().getRef(Constants.R_STASH);
+		Ref stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNull(stashRef);
 		RevCommit firstStash = git.stashCreate().call();
 		assertNotNull(firstStash);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
-		assertEquals(firstStash, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		assertEquals(firstStash,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 
 		write(committedFile, "content3");
 		RevCommit secondStash = git.stashCreate().call();
 		assertNotNull(secondStash);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
-		assertEquals(secondStash, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		assertEquals(secondStash,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 
 		assertEquals(firstStash, git.stashDrop().call());
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
 		assertEquals(firstStash, stashRef.getObjectId());
 
@@ -196,33 +196,33 @@ public void dropFirstStashedCommit() throws Exception {
 	@Test
 	public void dropMiddleStashCommit() throws Exception {
 		write(committedFile, "content2");
-		Ref stashRef = git.getRepository().getRef(Constants.R_STASH);
+		Ref stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNull(stashRef);
 		RevCommit firstStash = git.stashCreate().call();
 		assertNotNull(firstStash);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
-		assertEquals(firstStash, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		assertEquals(firstStash,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 
 		write(committedFile, "content3");
 		RevCommit secondStash = git.stashCreate().call();
 		assertNotNull(secondStash);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
-		assertEquals(secondStash, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		assertEquals(secondStash,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 
 		write(committedFile, "content4");
 		RevCommit thirdStash = git.stashCreate().call();
 		assertNotNull(thirdStash);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
-		assertEquals(thirdStash, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		assertEquals(thirdStash,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 
 		assertEquals(thirdStash, git.stashDrop().setStashRef(1).call());
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
 		assertEquals(thirdStash, stashRef.getObjectId());
 
@@ -241,46 +241,46 @@ public void dropMiddleStashCommit() throws Exception {
 	@Test
 	public void dropBoundaryStashedCommits() throws Exception {
 		write(committedFile, "content2");
-		Ref stashRef = git.getRepository().getRef(Constants.R_STASH);
+		Ref stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNull(stashRef);
 		RevCommit firstStash = git.stashCreate().call();
 		assertNotNull(firstStash);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
-		assertEquals(firstStash, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		assertEquals(firstStash,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 
 		write(committedFile, "content3");
 		RevCommit secondStash = git.stashCreate().call();
 		assertNotNull(secondStash);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
-		assertEquals(secondStash, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		assertEquals(secondStash,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 
 		write(committedFile, "content4");
 		RevCommit thirdStash = git.stashCreate().call();
 		assertNotNull(thirdStash);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
-		assertEquals(thirdStash, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		assertEquals(thirdStash,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 
 		write(committedFile, "content5");
 		RevCommit fourthStash = git.stashCreate().call();
 		assertNotNull(fourthStash);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
-		assertEquals(fourthStash, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		assertEquals(fourthStash,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 
 		assertEquals(thirdStash, git.stashDrop().call());
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
 		assertEquals(thirdStash, stashRef.getObjectId());
 
 		assertEquals(thirdStash, git.stashDrop().setStashRef(2).call());
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
 		assertEquals(thirdStash, stashRef.getObjectId());
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java
index 49279e6..0e595e6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java
@@ -52,9 +52,7 @@
 
 import java.io.IOException;
 import java.util.Collections;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.attributes.Attribute.State;
@@ -243,27 +241,29 @@ private void assertIteration(FileMode type, String pathName,
 		DirCacheIterator itr = walk.getTree(0, DirCacheIterator.class);
 		assertNotNull("has tree", itr);
 
-		AttributesNode attributeNode = itr.getEntryAttributesNode(db
+		AttributesNode attributesNode = itr.getEntryAttributesNode(db
 				.newObjectReader());
-		assertAttributeNode(pathName, attributeNode, nodeAttrs);
+		assertAttributesNode(pathName, attributesNode, nodeAttrs);
 
 		if (D.equals(type))
 			walk.enterSubtree();
 
 	}
 
-	private void assertAttributeNode(String pathName,
-			AttributesNode attributeNode, List<Attribute> nodeAttrs) {
-		if (attributeNode == null)
+	private void assertAttributesNode(String pathName,
+			AttributesNode attributesNode, List<Attribute> nodeAttrs) {
+		if (attributesNode == null)
 			assertTrue(nodeAttrs == null || nodeAttrs.isEmpty());
 		else {
 
-			Map<String, Attribute> entryAttributes = new LinkedHashMap<String, Attribute>();
-			attributeNode.getAttributes(pathName, false, entryAttributes);
+			Attributes entryAttributes = new Attributes();
+			attributesNode.getAttributes(pathName,
+					false, entryAttributes);
 
 			if (nodeAttrs != null && !nodeAttrs.isEmpty()) {
 				for (Attribute attribute : nodeAttrs) {
-					assertThat(entryAttributes.values(), hasItem(attribute));
+					assertThat(entryAttributes.getAll(),
+							hasItem(attribute));
 				}
 			} else {
 				assertTrue(
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeNodeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
similarity index 86%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeNodeTest.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
index ea25036..d478a7c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeNodeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
@@ -49,10 +49,6 @@
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Set;
 
 import org.junit.After;
 import org.junit.Test;
@@ -60,7 +56,7 @@
 /**
  * Test {@link AttributesNode}
  */
-public class AttributeNodeTest {
+public class AttributesNodeTest {
 
 	private static final Attribute A_SET_ATTR = new Attribute("A", SET);
 
@@ -104,8 +100,8 @@ public void testNegativePattern() throws IOException {
 		is = new ByteArrayInputStream(attributeFileContent.getBytes());
 		AttributesNode node = new AttributesNode();
 		node.parse(is);
-		assertAttribute("file.type1", node, Collections.<Attribute> emptySet());
-		assertAttribute("file.type2", node, Collections.<Attribute> emptySet());
+		assertAttribute("file.type1", node, new Attributes());
+		assertAttribute("file.type2", node, new Attributes());
 	}
 
 	@Test
@@ -115,7 +111,7 @@ public void testEmptyNegativeAttributeKey() throws IOException {
 		is = new ByteArrayInputStream(attributeFileContent.getBytes());
 		AttributesNode node = new AttributesNode();
 		node.parse(is);
-		assertAttribute("file.type1", node, Collections.<Attribute> emptySet());
+		assertAttribute("file.type1", node, new Attributes());
 		assertAttribute("file.type2", node, asSet(A_UNSET_ATTR));
 	}
 
@@ -127,8 +123,8 @@ public void testEmptyValueKey() throws IOException {
 		is = new ByteArrayInputStream(attributeFileContent.getBytes());
 		AttributesNode node = new AttributesNode();
 		node.parse(is);
-		assertAttribute("file.type1", node, Collections.<Attribute> emptySet());
-		assertAttribute("file.type2", node, Collections.<Attribute> emptySet());
+		assertAttribute("file.type1", node, new Attributes());
+		assertAttribute("file.type2", node, new Attributes());
 		assertAttribute("file.type3", node, asSet(new Attribute("attr", "")));
 	}
 
@@ -166,17 +162,14 @@ public void testTabSeparator() throws IOException {
 	}
 
 	private void assertAttribute(String path, AttributesNode node,
-			Set<Attribute> attrs) {
-		HashMap<String, Attribute> attributes = new HashMap<String, Attribute>();
+			Attributes attrs) {
+		Attributes attributes = new Attributes();
 		node.getAttributes(path, false, attributes);
-		assertEquals(attrs, new HashSet<Attribute>(attributes.values()));
+		assertEquals(attrs, attributes);
 	}
 
-	static Set<Attribute> asSet(Attribute... attrs) {
-		Set<Attribute> result = new HashSet<Attribute>();
-		for (Attribute attr : attrs)
-			result.add(attr);
-		return result;
+	static Attributes asSet(Attribute... attrs) {
+		return new Attributes(attrs);
 	}
 
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java
index 64b0535..6ad19a2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java
@@ -53,9 +53,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.Collections;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
 
 import org.eclipse.jgit.attributes.Attribute.State;
 import org.eclipse.jgit.errors.CorruptObjectException;
@@ -76,14 +74,10 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase {
 
 	private static final FileMode F = FileMode.REGULAR_FILE;
 
-	private static Attribute EOL_CRLF = new Attribute("eol", "crlf");
-
 	private static Attribute EOL_LF = new Attribute("eol", "lf");
 
 	private static Attribute DELTA_UNSET = new Attribute("delta", State.UNSET);
 
-	private static Attribute CUSTOM_VALUE = new Attribute("custom", "value");
-
 	private TreeWalk walk;
 
 	@Test
@@ -112,25 +106,19 @@ public void testRules() throws Exception {
 		walk = beginWalk();
 
 		assertIteration(F, ".gitattributes");
-		assertIteration(F, "global.txt", asList(EOL_LF), null,
-				asList(CUSTOM_VALUE));
-		assertIteration(F, "readme.txt", asList(EOL_LF), null,
-				asList(CUSTOM_VALUE));
+		assertIteration(F, "global.txt", asList(EOL_LF));
+		assertIteration(F, "readme.txt", asList(EOL_LF));
 
 		assertIteration(D, "src");
 
 		assertIteration(D, "src/config");
 		assertIteration(F, "src/config/.gitattributes");
-		assertIteration(F, "src/config/readme.txt", asList(DELTA_UNSET), null,
-				asList(CUSTOM_VALUE));
-		assertIteration(F, "src/config/windows.file", null, asList(EOL_CRLF),
-				null);
-		assertIteration(F, "src/config/windows.txt", asList(DELTA_UNSET),
-				asList(EOL_CRLF), asList(CUSTOM_VALUE));
+		assertIteration(F, "src/config/readme.txt", asList(DELTA_UNSET));
+		assertIteration(F, "src/config/windows.file", null);
+		assertIteration(F, "src/config/windows.txt", asList(DELTA_UNSET));
 
-		assertIteration(F, "windows.file", null, asList(EOL_CRLF), null);
-		assertIteration(F, "windows.txt", asList(EOL_LF), asList(EOL_CRLF),
-				asList(CUSTOM_VALUE));
+		assertIteration(F, "windows.file", null);
+		assertIteration(F, "windows.txt", asList(EOL_LF));
 
 		endWalk();
 	}
@@ -212,14 +200,11 @@ public void testNoMatchingAttributes() throws Exception {
 
 	private void assertIteration(FileMode type, String pathName)
 			throws IOException {
-		assertIteration(type, pathName, Collections.<Attribute> emptyList(),
-				Collections.<Attribute> emptyList(),
-				Collections.<Attribute> emptyList());
+		assertIteration(type, pathName, Collections.<Attribute> emptyList());
 	}
 
 	private void assertIteration(FileMode type, String pathName,
-			List<Attribute> nodeAttrs, List<Attribute> infoAttrs,
-			List<Attribute> globalAttrs)
+			List<Attribute> nodeAttrs)
 			throws IOException {
 		assertTrue("walk has entry", walk.next());
 		assertEquals(pathName, walk.getPathString());
@@ -227,29 +212,27 @@ private void assertIteration(FileMode type, String pathName,
 		WorkingTreeIterator itr = walk.getTree(0, WorkingTreeIterator.class);
 		assertNotNull("has tree", itr);
 
-		AttributesNode attributeNode = itr.getEntryAttributesNode();
-		assertAttributeNode(pathName, attributeNode, nodeAttrs);
-		AttributesNode infoAttributeNode = itr.getInfoAttributesNode();
-		assertAttributeNode(pathName, infoAttributeNode, infoAttrs);
-		AttributesNode globalAttributeNode = itr.getGlobalAttributesNode();
-		assertAttributeNode(pathName, globalAttributeNode, globalAttrs);
+		AttributesNode attributesNode = itr.getEntryAttributesNode();
+		assertAttributesNode(pathName, attributesNode, nodeAttrs);
 		if (D.equals(type))
 			walk.enterSubtree();
 
 	}
 
-	private void assertAttributeNode(String pathName,
-			AttributesNode attributeNode, List<Attribute> nodeAttrs) {
-		if (attributeNode == null)
+	private void assertAttributesNode(String pathName,
+			AttributesNode attributesNode, List<Attribute> nodeAttrs) {
+		if (attributesNode == null)
 			assertTrue(nodeAttrs == null || nodeAttrs.isEmpty());
 		else {
 
-			Map<String, Attribute> entryAttributes = new LinkedHashMap<String, Attribute>();
-			attributeNode.getAttributes(pathName, false, entryAttributes);
+			Attributes entryAttributes = new Attributes();
+			attributesNode.getAttributes(pathName,
+					false, entryAttributes);
 
 			if (nodeAttrs != null && !nodeAttrs.isEmpty()) {
 				for (Attribute attribute : nodeAttrs) {
-					assertThat(entryAttributes.values(), hasItem(attribute));
+					assertThat(entryAttributes.getAll(),
+							hasItem(attribute));
 				}
 			} else {
 				assertTrue(
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java
new file mode 100644
index 0000000..b044c01
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java
@@ -0,0 +1,866 @@
+/*
+ * Copyright (C) 2014, Obeo.
+ * 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.attributes;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.NoFilepatternException;
+import org.eclipse.jgit.attributes.Attribute.State;
+import org.eclipse.jgit.dircache.DirCacheIterator;
+import org.eclipse.jgit.errors.NoWorkTreeException;
+import org.eclipse.jgit.junit.JGitTestUtil;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests the attributes are correctly computed in a {@link TreeWalk}.
+ *
+ * @see TreeWalk#getAttributes()
+ */
+public class TreeWalkAttributeTest extends RepositoryTestCase {
+
+	private static final FileMode M = FileMode.MISSING;
+
+	private static final FileMode D = FileMode.TREE;
+
+	private static final FileMode F = FileMode.REGULAR_FILE;
+
+	private static Attribute EOL_CRLF = new Attribute("eol", "crlf");
+
+	private static Attribute EOL_LF = new Attribute("eol", "lf");
+
+	private static Attribute TEXT_SET = new Attribute("text", State.SET);
+
+	private static Attribute TEXT_UNSET = new Attribute("text", State.UNSET);
+
+	private static Attribute DELTA_UNSET = new Attribute("delta", State.UNSET);
+
+	private static Attribute DELTA_SET = new Attribute("delta", State.SET);
+
+	private static Attribute CUSTOM_GLOBAL = new Attribute("custom", "global");
+
+	private static Attribute CUSTOM_INFO = new Attribute("custom", "info");
+
+	private static Attribute CUSTOM_ROOT = new Attribute("custom", "root");
+
+	private static Attribute CUSTOM_PARENT = new Attribute("custom", "parent");
+
+	private static Attribute CUSTOM_CURRENT = new Attribute("custom", "current");
+
+	private static Attribute CUSTOM2_UNSET = new Attribute("custom2",
+			State.UNSET);
+
+	private static Attribute CUSTOM2_SET = new Attribute("custom2", State.SET);
+
+	private TreeWalk walk;
+
+	private TreeWalk ci_walk;
+
+	private Git git;
+
+	private File customAttributeFile;
+
+	@Override
+	@Before
+	public void setUp() throws Exception {
+		super.setUp();
+		git = new Git(db);
+	}
+
+	@Override
+	@After
+	public void tearDown() throws Exception {
+		super.tearDown();
+		if (customAttributeFile != null)
+			customAttributeFile.delete();
+	}
+
+	/**
+	 * Checks that the attributes are computed correctly depending on the
+	 * operation type.
+	 * <p>
+	 * In this test we changed the content of the attribute files in the working
+	 * tree compared to the one in the index.
+	 * </p>
+	 *
+	 * @throws IOException
+	 * @throws NoFilepatternException
+	 * @throws GitAPIException
+	 */
+	@Test
+	public void testCheckinCheckoutDifferences() throws IOException,
+			NoFilepatternException, GitAPIException {
+
+		writeGlobalAttributeFile("globalAttributesFile", "*.txt -custom2");
+		writeAttributesFile(".git/info/attributes", "*.txt eol=crlf");
+		writeAttributesFile(".gitattributes", "*.txt custom=root");
+		writeAttributesFile("level1/.gitattributes", "*.txt text");
+		writeAttributesFile("level1/level2/.gitattributes", "*.txt -delta");
+
+		writeTrashFile("l0.txt", "");
+
+		writeTrashFile("level1/l1.txt", "");
+
+		writeTrashFile("level1/level2/l2.txt", "");
+
+		git.add().addFilepattern(".").call();
+
+		beginWalk();
+
+		// Modify all attributes
+		writeGlobalAttributeFile("globalAttributesFile", "*.txt custom2");
+		writeAttributesFile(".git/info/attributes", "*.txt eol=lf");
+		writeAttributesFile(".gitattributes", "*.txt custom=info");
+		writeAttributesFile("level1/.gitattributes", "*.txt -text");
+		writeAttributesFile("level1/level2/.gitattributes", "*.txt delta");
+
+		assertEntry(F, ".gitattributes");
+		assertEntry(F, "l0.txt", asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET),
+				asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET));
+
+		assertEntry(D, "level1");
+		assertEntry(F, "level1/.gitattributes");
+		assertEntry(F, "level1/l1.txt",
+				asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET, TEXT_UNSET),
+				asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET, TEXT_SET));
+
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/.gitattributes");
+		assertEntry(F, "level1/level2/l2.txt",
+				asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET, TEXT_UNSET, DELTA_SET),
+				asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET, TEXT_SET, DELTA_UNSET));
+
+		endWalk();
+	}
+
+	/**
+	 * Checks that the index is used as fallback when the git attributes file
+	 * are missing in the working tree.
+	 *
+	 * @throws IOException
+	 * @throws NoFilepatternException
+	 * @throws GitAPIException
+	 */
+	@Test
+	public void testIndexOnly() throws IOException, NoFilepatternException,
+			GitAPIException {
+		List<File> attrFiles = new ArrayList<File>();
+		attrFiles.add(writeGlobalAttributeFile("globalAttributesFile",
+				"*.txt -custom2"));
+		attrFiles.add(writeAttributesFile(".git/info/attributes",
+				"*.txt eol=crlf"));
+		attrFiles
+				.add(writeAttributesFile(".gitattributes", "*.txt custom=root"));
+		attrFiles
+				.add(writeAttributesFile("level1/.gitattributes", "*.txt text"));
+		attrFiles.add(writeAttributesFile("level1/level2/.gitattributes",
+				"*.txt -delta"));
+
+		writeTrashFile("l0.txt", "");
+
+		writeTrashFile("level1/l1.txt", "");
+
+		writeTrashFile("level1/level2/l2.txt", "");
+
+		git.add().addFilepattern(".").call();
+
+		// Modify all attributes
+		for (File attrFile : attrFiles)
+			attrFile.delete();
+
+		beginWalk();
+
+		assertEntry(M, ".gitattributes");
+		assertEntry(F, "l0.txt", asSet(CUSTOM_ROOT));
+
+		assertEntry(D, "level1");
+		assertEntry(M, "level1/.gitattributes");
+		assertEntry(F, "level1/l1.txt",
+
+		asSet(CUSTOM_ROOT, TEXT_SET));
+
+		assertEntry(D, "level1/level2");
+		assertEntry(M, "level1/level2/.gitattributes");
+		assertEntry(F, "level1/level2/l2.txt",
+
+		asSet(CUSTOM_ROOT, TEXT_SET, DELTA_UNSET));
+
+		endWalk();
+	}
+
+	/**
+	 * Check that we search in the working tree for attributes although the file
+	 * we are currently inspecting does not exist anymore in the working tree.
+	 *
+	 * @throws IOException
+	 * @throws NoFilepatternException
+	 * @throws GitAPIException
+	 */
+	@Test
+	public void testIndexOnly2()
+			throws IOException, NoFilepatternException, GitAPIException {
+		File l2 = writeTrashFile("level1/level2/l2.txt", "");
+		writeTrashFile("level1/level2/l1.txt", "");
+
+		git.add().addFilepattern(".").call();
+
+		writeAttributesFile(".gitattributes", "*.txt custom=root");
+		assertTrue(l2.delete());
+
+		beginWalk();
+
+		assertEntry(F, ".gitattributes");
+		assertEntry(D, "level1");
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/l1.txt", asSet(CUSTOM_ROOT));
+		assertEntry(M, "level1/level2/l2.txt", asSet(CUSTOM_ROOT));
+
+		endWalk();
+	}
+
+	/**
+	 * Basic test for git attributes.
+	 * <p>
+	 * In this use case files are present in both the working tree and the index
+	 * </p>
+	 *
+	 * @throws IOException
+	 * @throws NoFilepatternException
+	 * @throws GitAPIException
+	 */
+	@Test
+	public void testRules() throws IOException, NoFilepatternException,
+			GitAPIException {
+		writeAttributesFile(".git/info/attributes", "windows* eol=crlf");
+
+		writeAttributesFile(".gitattributes", "*.txt eol=lf");
+		writeTrashFile("windows.file", "");
+		writeTrashFile("windows.txt", "");
+		writeTrashFile("readme.txt", "");
+
+		writeAttributesFile("src/config/.gitattributes", "*.txt -delta");
+		writeTrashFile("src/config/readme.txt", "");
+		writeTrashFile("src/config/windows.file", "");
+		writeTrashFile("src/config/windows.txt", "");
+
+		beginWalk();
+
+		git.add().addFilepattern(".").call();
+
+		assertEntry(F, ".gitattributes");
+		assertEntry(F, "readme.txt", asSet(EOL_LF));
+
+		assertEntry(D, "src");
+		assertEntry(D, "src/config");
+		assertEntry(F, "src/config/.gitattributes");
+		assertEntry(F, "src/config/readme.txt", asSet(DELTA_UNSET, EOL_LF));
+		assertEntry(F, "src/config/windows.file", asSet(EOL_CRLF));
+		assertEntry(F, "src/config/windows.txt", asSet(DELTA_UNSET, EOL_CRLF));
+
+		assertEntry(F, "windows.file", asSet(EOL_CRLF));
+		assertEntry(F, "windows.txt", asSet(EOL_CRLF));
+
+		endWalk();
+	}
+
+	/**
+	 * Checks that if there is no .gitattributes file in the repository
+	 * everything still work fine.
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testNoAttributes() throws IOException {
+		writeTrashFile("l0.txt", "");
+		writeTrashFile("level1/l1.txt", "");
+		writeTrashFile("level1/level2/l2.txt", "");
+
+		beginWalk();
+
+		assertEntry(F, "l0.txt");
+
+		assertEntry(D, "level1");
+		assertEntry(F, "level1/l1.txt");
+
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/l2.txt");
+
+		endWalk();
+	}
+
+	/**
+	 * Checks that an empty .gitattribute file does not return incorrect value.
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testEmptyGitAttributeFile() throws IOException {
+		writeAttributesFile(".git/info/attributes", "");
+		writeTrashFile("l0.txt", "");
+		writeAttributesFile(".gitattributes", "");
+		writeTrashFile("level1/l1.txt", "");
+		writeTrashFile("level1/level2/l2.txt", "");
+
+		beginWalk();
+
+		assertEntry(F, ".gitattributes");
+		assertEntry(F, "l0.txt");
+
+		assertEntry(D, "level1");
+		assertEntry(F, "level1/l1.txt");
+
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/l2.txt");
+
+		endWalk();
+	}
+
+	@Test
+	public void testNoMatchingAttributes() throws IOException {
+		writeAttributesFile(".git/info/attributes", "*.java delta");
+		writeAttributesFile(".gitattributes", "*.java -delta");
+		writeAttributesFile("levelA/.gitattributes", "*.java eol=lf");
+		writeAttributesFile("levelB/.gitattributes", "*.txt eol=lf");
+
+		writeTrashFile("levelA/lA.txt", "");
+
+		beginWalk();
+
+		assertEntry(F, ".gitattributes");
+
+		assertEntry(D, "levelA");
+		assertEntry(F, "levelA/.gitattributes");
+		assertEntry(F, "levelA/lA.txt");
+
+		assertEntry(D, "levelB");
+		assertEntry(F, "levelB/.gitattributes");
+
+		endWalk();
+	}
+
+	/**
+	 * Checks that $GIT_DIR/info/attributes file has the highest precedence.
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testPrecedenceInfo() throws IOException {
+		writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
+		writeAttributesFile(".git/info/attributes", "*.txt custom=info");
+		writeAttributesFile(".gitattributes", "*.txt custom=root");
+		writeAttributesFile("level1/.gitattributes", "*.txt custom=parent");
+		writeAttributesFile("level1/level2/.gitattributes",
+				"*.txt custom=current");
+
+		writeTrashFile("level1/level2/file.txt", "");
+
+		beginWalk();
+
+		assertEntry(F, ".gitattributes");
+
+		assertEntry(D, "level1");
+		assertEntry(F, "level1/.gitattributes");
+
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/.gitattributes");
+		assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_INFO));
+
+		endWalk();
+	}
+
+	/**
+	 * Checks that a subfolder ".gitattributes" file has precedence over its
+	 * parent.
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testPrecedenceCurrent() throws IOException {
+		writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
+		writeAttributesFile(".gitattributes", "*.txt custom=root");
+		writeAttributesFile("level1/.gitattributes", "*.txt custom=parent");
+		writeAttributesFile("level1/level2/.gitattributes",
+				"*.txt custom=current");
+
+		writeTrashFile("level1/level2/file.txt", "");
+
+		beginWalk();
+
+		assertEntry(F, ".gitattributes");
+
+		assertEntry(D, "level1");
+		assertEntry(F, "level1/.gitattributes");
+
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/.gitattributes");
+		assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_CURRENT));
+
+		endWalk();
+	}
+
+	/**
+	 * Checks that the parent ".gitattributes" file is used as fallback.
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testPrecedenceParent() throws IOException {
+		writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
+		writeAttributesFile(".gitattributes", "*.txt custom=root");
+		writeAttributesFile("level1/.gitattributes", "*.txt custom=parent");
+
+		writeTrashFile("level1/level2/file.txt", "");
+
+		beginWalk();
+
+		assertEntry(F, ".gitattributes");
+
+		assertEntry(D, "level1");
+		assertEntry(F, "level1/.gitattributes");
+
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_PARENT));
+
+		endWalk();
+	}
+
+	/**
+	 * Checks that the grand parent ".gitattributes" file is used as fallback.
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testPrecedenceRoot() throws IOException {
+		writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
+		writeAttributesFile(".gitattributes", "*.txt custom=root");
+
+		writeTrashFile("level1/level2/file.txt", "");
+
+		beginWalk();
+
+		assertEntry(F, ".gitattributes");
+
+		assertEntry(D, "level1");
+
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_ROOT));
+
+		endWalk();
+	}
+
+	/**
+	 * Checks that the global attribute file is used as fallback.
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testPrecedenceGlobal() throws IOException {
+		writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
+
+		writeTrashFile("level1/level2/file.txt", "");
+
+		beginWalk();
+
+		assertEntry(D, "level1");
+
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_GLOBAL));
+
+		endWalk();
+	}
+
+	/**
+	 * Checks the precedence on a hierarchy with multiple attributes.
+	 * <p>
+	 * In this test all file are present in both the working tree and the index.
+	 * </p>
+	 *
+	 * @throws IOException
+	 * @throws GitAPIException
+	 * @throws NoFilepatternException
+	 */
+	@Test
+	public void testHierarchyBothIterator() throws IOException,
+			NoFilepatternException, GitAPIException {
+		writeAttributesFile(".git/info/attributes", "*.global eol=crlf");
+		writeAttributesFile(".gitattributes", "*.local eol=lf");
+		writeAttributesFile("level1/.gitattributes", "*.local text");
+		writeAttributesFile("level1/level2/.gitattributes", "*.local -text");
+
+		writeTrashFile("l0.global", "");
+		writeTrashFile("l0.local", "");
+
+		writeTrashFile("level1/l1.global", "");
+		writeTrashFile("level1/l1.local", "");
+
+		writeTrashFile("level1/level2/l2.global", "");
+		writeTrashFile("level1/level2/l2.local", "");
+
+		beginWalk();
+
+		git.add().addFilepattern(".").call();
+
+		assertEntry(F, ".gitattributes");
+		assertEntry(F, "l0.global", asSet(EOL_CRLF));
+		assertEntry(F, "l0.local", asSet(EOL_LF));
+
+		assertEntry(D, "level1");
+		assertEntry(F, "level1/.gitattributes");
+		assertEntry(F, "level1/l1.global", asSet(EOL_CRLF));
+		assertEntry(F, "level1/l1.local", asSet(EOL_LF, TEXT_SET));
+
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/.gitattributes");
+		assertEntry(F, "level1/level2/l2.global", asSet(EOL_CRLF));
+		assertEntry(F, "level1/level2/l2.local", asSet(EOL_LF, TEXT_UNSET));
+
+		endWalk();
+
+	}
+
+	/**
+	 * Checks the precedence on a hierarchy with multiple attributes.
+	 * <p>
+	 * In this test all file are present only in the working tree.
+	 * </p>
+	 *
+	 * @throws IOException
+	 * @throws GitAPIException
+	 * @throws NoFilepatternException
+	 */
+	@Test
+	public void testHierarchyWorktreeOnly()
+			throws IOException, NoFilepatternException, GitAPIException {
+		writeAttributesFile(".git/info/attributes", "*.global eol=crlf");
+		writeAttributesFile(".gitattributes", "*.local eol=lf");
+		writeAttributesFile("level1/.gitattributes", "*.local text");
+		writeAttributesFile("level1/level2/.gitattributes", "*.local -text");
+
+		writeTrashFile("l0.global", "");
+		writeTrashFile("l0.local", "");
+
+		writeTrashFile("level1/l1.global", "");
+		writeTrashFile("level1/l1.local", "");
+
+		writeTrashFile("level1/level2/l2.global", "");
+		writeTrashFile("level1/level2/l2.local", "");
+
+		beginWalk();
+
+		assertEntry(F, ".gitattributes");
+		assertEntry(F, "l0.global", asSet(EOL_CRLF));
+		assertEntry(F, "l0.local", asSet(EOL_LF));
+
+		assertEntry(D, "level1");
+		assertEntry(F, "level1/.gitattributes");
+		assertEntry(F, "level1/l1.global", asSet(EOL_CRLF));
+		assertEntry(F, "level1/l1.local", asSet(EOL_LF, TEXT_SET));
+
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/.gitattributes");
+		assertEntry(F, "level1/level2/l2.global", asSet(EOL_CRLF));
+		assertEntry(F, "level1/level2/l2.local", asSet(EOL_LF, TEXT_UNSET));
+
+		endWalk();
+
+	}
+
+	/**
+	 * Checks that the list of attributes is an aggregation of all the
+	 * attributes from the attributes files hierarchy.
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testAggregation() throws IOException {
+		writeGlobalAttributeFile("globalAttributesFile", "*.txt -custom2");
+		writeAttributesFile(".git/info/attributes", "*.txt eol=crlf");
+		writeAttributesFile(".gitattributes", "*.txt custom=root");
+		writeAttributesFile("level1/.gitattributes", "*.txt text");
+		writeAttributesFile("level1/level2/.gitattributes", "*.txt -delta");
+
+		writeTrashFile("l0.txt", "");
+
+		writeTrashFile("level1/l1.txt", "");
+
+		writeTrashFile("level1/level2/l2.txt", "");
+
+		beginWalk();
+
+		assertEntry(F, ".gitattributes");
+		assertEntry(F, "l0.txt", asSet(EOL_CRLF, CUSTOM_ROOT, CUSTOM2_UNSET));
+
+		assertEntry(D, "level1");
+		assertEntry(F, "level1/.gitattributes");
+		assertEntry(F, "level1/l1.txt",
+				asSet(EOL_CRLF, CUSTOM_ROOT, TEXT_SET, CUSTOM2_UNSET));
+
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/.gitattributes");
+		assertEntry(
+				F,
+				"level1/level2/l2.txt",
+				asSet(EOL_CRLF, CUSTOM_ROOT, TEXT_SET, DELTA_UNSET,
+						CUSTOM2_UNSET));
+
+		endWalk();
+
+	}
+
+	/**
+	 * Checks that the last entry in .gitattributes is used if 2 lines match the
+	 * same attribute
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testOverriding() throws IOException {
+		writeAttributesFile(".git/info/attributes",//
+				//
+				"*.txt custom=current",//
+				"*.txt custom=parent",//
+				"*.txt custom=root",//
+				"*.txt custom=info",
+				//
+				"*.txt delta",//
+				"*.txt -delta",
+				//
+				"*.txt eol=lf",//
+				"*.txt eol=crlf",
+				//
+				"*.txt text",//
+				"*.txt -text");
+
+		writeTrashFile("l0.txt", "");
+		beginWalk();
+
+		assertEntry(F, "l0.txt",
+				asSet(TEXT_UNSET, EOL_CRLF, DELTA_UNSET, CUSTOM_INFO));
+
+		endWalk();
+	}
+
+	/**
+	 * Checks that the last value of an attribute is used if in the same line an
+	 * attribute is defined several time.
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testOverriding2() throws IOException {
+		writeAttributesFile(".git/info/attributes",
+				"*.txt custom=current custom=parent custom=root custom=info",//
+				"*.txt delta -delta",//
+				"*.txt eol=lf eol=crlf",//
+				"*.txt text -text");
+		writeTrashFile("l0.txt", "");
+		beginWalk();
+
+		assertEntry(F, "l0.txt",
+				asSet(TEXT_UNSET, EOL_CRLF, DELTA_UNSET, CUSTOM_INFO));
+
+		endWalk();
+	}
+
+	@Test
+	public void testRulesInherited() throws Exception {
+		writeAttributesFile(".gitattributes", "**/*.txt eol=lf");
+		writeTrashFile("src/config/readme.txt", "");
+		writeTrashFile("src/config/windows.file", "");
+
+		beginWalk();
+
+		assertEntry(F, ".gitattributes");
+		assertEntry(D, "src");
+		assertEntry(D, "src/config");
+
+		assertEntry(F, "src/config/readme.txt", asSet(EOL_LF));
+		assertEntry(F, "src/config/windows.file",
+				Collections.<Attribute> emptySet());
+
+		endWalk();
+	}
+
+	private void beginWalk() throws NoWorkTreeException, IOException {
+		walk = new TreeWalk(db);
+		walk.addTree(new FileTreeIterator(db));
+		walk.addTree(new DirCacheIterator(db.readDirCache()));
+
+		ci_walk = new TreeWalk(db);
+		ci_walk.setOperationType(OperationType.CHECKIN_OP);
+		ci_walk.addTree(new FileTreeIterator(db));
+		ci_walk.addTree(new DirCacheIterator(db.readDirCache()));
+	}
+
+	/**
+	 * Assert an entry in which checkin and checkout attributes are expected to
+	 * be the same.
+	 *
+	 * @param type
+	 * @param pathName
+	 * @param forBothOperaiton
+	 * @throws IOException
+	 */
+	private void assertEntry(FileMode type, String pathName,
+			Set<Attribute> forBothOperaiton) throws IOException {
+		assertEntry(type, pathName, forBothOperaiton, forBothOperaiton);
+	}
+
+	/**
+	 * Assert an entry with no attribute expected.
+	 *
+	 * @param type
+	 * @param pathName
+	 * @throws IOException
+	 */
+	private void assertEntry(FileMode type, String pathName) throws IOException {
+		assertEntry(type, pathName, Collections.<Attribute> emptySet(),
+				Collections.<Attribute> emptySet());
+	}
+
+	/**
+	 * Assert that an entry;
+	 * <ul>
+	 * <li>Has the correct type</li>
+	 * <li>Exist in the tree walk</li>
+	 * <li>Has the expected attributes on a checkin operation</li>
+	 * <li>Has the expected attributes on a checkout operation</li>
+	 * </ul>
+	 *
+	 * @param type
+	 * @param pathName
+	 * @param checkinAttributes
+	 * @param checkoutAttributes
+	 * @throws IOException
+	 */
+	private void assertEntry(FileMode type, String pathName,
+			Set<Attribute> checkinAttributes, Set<Attribute> checkoutAttributes)
+			throws IOException {
+		assertTrue("walk has entry", walk.next());
+		assertTrue("walk has entry", ci_walk.next());
+		assertEquals(pathName, walk.getPathString());
+		assertEquals(type, walk.getFileMode(0));
+
+		assertEquals(checkinAttributes,
+				asSet(ci_walk.getAttributes().getAll()));
+		assertEquals(checkoutAttributes,
+				asSet(walk.getAttributes().getAll()));
+
+		if (D.equals(type)) {
+			walk.enterSubtree();
+			ci_walk.enterSubtree();
+		}
+	}
+
+	private static Set<Attribute> asSet(Collection<Attribute> attributes) {
+		Set<Attribute> ret = new HashSet<Attribute>();
+		for (Attribute a : attributes) {
+			ret.add(a);
+		}
+		return (ret);
+	}
+
+	private File writeAttributesFile(String name, String... rules)
+			throws IOException {
+		StringBuilder data = new StringBuilder();
+		for (String line : rules)
+			data.append(line + "\n");
+		return writeTrashFile(name, data.toString());
+	}
+
+	/**
+	 * Creates an attributes file and set its location in the git configuration.
+	 *
+	 * @param fileName
+	 * @param attributes
+	 * @return The attribute file
+	 * @throws IOException
+	 * @see Repository#getConfig()
+	 */
+	private File writeGlobalAttributeFile(String fileName, String... attributes)
+			throws IOException {
+		customAttributeFile = File.createTempFile("tmp_", fileName, null);
+		customAttributeFile.deleteOnExit();
+		StringBuilder attributesFileContent = new StringBuilder();
+		for (String attr : attributes) {
+			attributesFileContent.append(attr).append("\n");
+		}
+		JGitTestUtil.write(customAttributeFile,
+				attributesFileContent.toString());
+		db.getConfig().setString("core", null, "attributesfile",
+				customAttributeFile.getAbsolutePath());
+		return customAttributeFile;
+	}
+
+	static Set<Attribute> asSet(Attribute... attrs) {
+		HashSet<Attribute> result = new HashSet<Attribute>();
+		for (Attribute attr : attrs)
+			result.add(attr);
+		return result;
+	}
+
+	private void endWalk() throws IOException {
+		assertFalse("Not all files tested", walk.next());
+		assertFalse("Not all files tested", ci_walk.next());
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java
index e83ef77..4315be9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java
@@ -48,6 +48,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.util.Arrays;
 import java.util.List;
 
 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
@@ -227,6 +228,19 @@ public void testExactRename_OneDeleteManyAdds() throws Exception {
 	}
 
 	@Test
+	public void testExactRename_UnstagedFile() throws Exception {
+		ObjectId aId = blob("foo");
+		DiffEntry a = DiffEntry.delete(PATH_A, aId);
+		DiffEntry b = DiffEntry.add(PATH_B, aId);
+
+		rd.addAll(Arrays.asList(a, b));
+		List<DiffEntry> entries = rd.compute();
+
+		assertEquals(1, entries.size());
+		assertRename(a, b, 100, entries.get(0));
+	}
+
+	@Test
 	public void testInexactRename_OnePair() throws Exception {
 		ObjectId aId = blob("foo\nbar\nbaz\nblarg\n");
 		ObjectId bId = blob("foo\nbar\nbaz\nblah\n");
@@ -430,6 +444,23 @@ public void testNoRenames_SymlinkAndFileSamePath() throws Exception {
 	}
 
 	@Test
+	public void testNoRenames_UntrackedFile() throws Exception {
+		ObjectId aId = blob("foo");
+		ObjectId bId = ObjectId
+				.fromString("3049eb6eee7e1318f4e78e799bf33f1e54af9cbf");
+
+		DiffEntry a = DiffEntry.delete(PATH_A, aId);
+		DiffEntry b = DiffEntry.add(PATH_B, bId);
+
+		rd.addAll(Arrays.asList(a, b));
+		List<DiffEntry> entries = rd.compute();
+
+		assertEquals(2, entries.size());
+		assertSame(a, entries.get(0));
+		assertSame(b, entries.get(1));
+	}
+
+	@Test
 	public void testBreakModify_BreakAll() throws Exception {
 		ObjectId aId = blob("foo");
 		ObjectId bId = blob("bar");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
index e159ed9..dc7181a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
@@ -69,9 +69,9 @@ public void testIsValidPath() {
 		assertFalse(isValidPath("a\u0000b"));
 	}
 
-	private static boolean isValidPath(final String path) {
+	private static boolean isValidPath(String path) {
 		try {
-			DirCacheCheckout.checkValidPath(path);
+			new DirCacheEntry(path);
 			return true;
 		} catch (InvalidPathException e) {
 			return false;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
index c7336da..fa40458 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
@@ -77,7 +77,7 @@ public void looseRefPacked() throws Exception {
 		tr.lightweightTag("t", a);
 
 		gc.packRefs();
-		assertSame(repo.getRef("t").getStorage(), Storage.PACKED);
+		assertSame(repo.exactRef("refs/tags/t").getStorage(), Storage.PACKED);
 	}
 
 	@Test
@@ -126,8 +126,8 @@ public void whileRefLockedRefNotPackedNoError()
 			refLock.unlock();
 		}
 
-		assertSame(repo.getRef("refs/tags/t1").getStorage(), Storage.LOOSE);
-		assertSame(repo.getRef("refs/tags/t2").getStorage(), Storage.PACKED);
+		assertSame(repo.exactRef("refs/tags/t1").getStorage(), Storage.LOOSE);
+		assertSame(repo.exactRef("refs/tags/t2").getStorage(), Storage.PACKED);
 	}
 
 	@Test
@@ -146,7 +146,7 @@ public void whileRefUpdatedRefUpdateSucceeds()
 				public Result call() throws Exception {
 					RefUpdate update = new RefDirectoryUpdate(
 							(RefDirectory) repo.getRefDatabase(),
-							repo.getRef("refs/tags/t")) {
+							repo.exactRef("refs/tags/t")) {
 						@Override
 						public boolean isForceUpdate() {
 							try {
@@ -182,7 +182,7 @@ public Void call() throws Exception {
 			pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
 		}
 
-		assertEquals(repo.getRef("refs/tags/t").getObjectId(), b);
+		assertEquals(repo.exactRef("refs/tags/t").getObjectId(), b);
 	}
 
 	@Test
@@ -194,23 +194,23 @@ public void dontPackHEAD_nonBare() throws Exception {
 
 		// check for the unborn branch master. HEAD should point to master and
 		// master doesn't exist.
-		assertEquals(repo.getRef("HEAD").getTarget().getName(),
+		assertEquals(repo.exactRef("HEAD").getTarget().getName(),
 				"refs/heads/master");
-		assertNull(repo.getRef("HEAD").getTarget().getObjectId());
+		assertNull(repo.exactRef("HEAD").getTarget().getObjectId());
 		gc.packRefs();
-		assertSame(repo.getRef("HEAD").getStorage(), Storage.LOOSE);
-		assertEquals(repo.getRef("HEAD").getTarget().getName(),
+		assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
+		assertEquals(repo.exactRef("HEAD").getTarget().getName(),
 				"refs/heads/master");
-		assertNull(repo.getRef("HEAD").getTarget().getObjectId());
+		assertNull(repo.exactRef("HEAD").getTarget().getObjectId());
 
 		git.checkout().setName("refs/heads/side").call();
 		gc.packRefs();
-		assertSame(repo.getRef("HEAD").getStorage(), Storage.LOOSE);
+		assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
 
 		// check for detached HEAD
 		git.checkout().setName(first.getName()).call();
 		gc.packRefs();
-		assertSame(repo.getRef("HEAD").getStorage(), Storage.LOOSE);
+		assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
 	}
 
 	@Test
@@ -229,20 +229,20 @@ public void dontPackHEAD_bare() throws Exception {
 
 		// check for the unborn branch master. HEAD should point to master and
 		// master doesn't exist.
-		assertEquals(repo.getRef("HEAD").getTarget().getName(),
+		assertEquals(repo.exactRef("HEAD").getTarget().getName(),
 				"refs/heads/master");
-		assertNull(repo.getRef("HEAD").getTarget().getObjectId());
+		assertNull(repo.exactRef("HEAD").getTarget().getObjectId());
 		gc.packRefs();
-		assertSame(repo.getRef("HEAD").getStorage(), Storage.LOOSE);
-		assertEquals(repo.getRef("HEAD").getTarget().getName(),
+		assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
+		assertEquals(repo.exactRef("HEAD").getTarget().getName(),
 				"refs/heads/master");
-		assertNull(repo.getRef("HEAD").getTarget().getObjectId());
+		assertNull(repo.exactRef("HEAD").getTarget().getObjectId());
 
 		// check for non-detached HEAD
 		repo.updateRef(Constants.HEAD).link("refs/heads/side");
 		gc.packRefs();
-		assertSame(repo.getRef("HEAD").getStorage(), Storage.LOOSE);
-		assertEquals(repo.getRef("HEAD").getTarget().getObjectId(),
+		assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
+		assertEquals(repo.exactRef("HEAD").getTarget().getObjectId(),
 				second.getId());
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
index 0991598..52e181b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
@@ -858,6 +858,36 @@ public void testGetRefs_CycleInSymbolicRef() throws IOException {
 	}
 
 	@Test
+	public void testGetRef_CycleInSymbolicRef() throws IOException {
+		Ref r;
+
+		writeLooseRef("refs/1", "ref: refs/2\n");
+		writeLooseRef("refs/2", "ref: refs/3\n");
+		writeLooseRef("refs/3", "ref: refs/4\n");
+		writeLooseRef("refs/4", "ref: refs/5\n");
+		writeLooseRef("refs/5", "ref: refs/end\n");
+		writeLooseRef("refs/end", A);
+
+		r = refdir.getRef("1");
+		assertEquals("refs/1", r.getName());
+		assertEquals(A, r.getObjectId());
+		assertTrue(r.isSymbolic());
+
+		writeLooseRef("refs/5", "ref: refs/6\n");
+		writeLooseRef("refs/6", "ref: refs/end\n");
+
+		r = refdir.getRef("1");
+		assertNull("missing 1 due to cycle", r);
+
+		writeLooseRef("refs/heads/1", B);
+
+		r = refdir.getRef("1");
+		assertEquals("refs/heads/1", r.getName());
+		assertEquals(B, r.getObjectId());
+		assertFalse(r.isSymbolic());
+	}
+
+	@Test
 	public void testGetRefs_PackedNotPeeled_Sorted() throws IOException {
 		Map<String, Ref> all;
 
@@ -1025,6 +1055,25 @@ public void testGetRef_EmptyDatabase() throws IOException {
 	}
 
 	@Test
+	public void testExactRef_EmptyDatabase() throws IOException {
+		Ref r;
+
+		r = refdir.exactRef(HEAD);
+		assertTrue(r.isSymbolic());
+		assertSame(LOOSE, r.getStorage());
+		assertEquals("refs/heads/master", r.getTarget().getName());
+		assertSame(NEW, r.getTarget().getStorage());
+		assertNull(r.getTarget().getObjectId());
+
+		assertNull(refdir.exactRef("refs/heads/master"));
+		assertNull(refdir.exactRef("refs/tags/v1.0"));
+		assertNull(refdir.exactRef("FETCH_HEAD"));
+		assertNull(refdir.exactRef("NOT.A.REF.NAME"));
+		assertNull(refdir.exactRef("master"));
+		assertNull(refdir.exactRef("v1.0"));
+	}
+
+	@Test
 	public void testGetRef_FetchHead() throws IOException {
 		// This is an odd special case where we need to make sure we read
 		// exactly the first 40 bytes of the file and nothing further on
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
index 098b31f..8744ed1 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
@@ -347,7 +347,7 @@ public void testUpdateRefDetached() throws Exception {
 		Result update = updateRef.update();
 		assertEquals(Result.FORCED, update);
 		assertEquals(ppid, db.resolve("HEAD"));
-		Ref ref = db.getRef("HEAD");
+		Ref ref = db.exactRef("HEAD");
 		assertEquals("HEAD", ref.getName());
 		assertTrue("is detached", !ref.isSymbolic());
 
@@ -377,7 +377,7 @@ public void testUpdateRefDetachedUnbornHead() throws Exception {
 		Result update = updateRef.update();
 		assertEquals(Result.NEW, update);
 		assertEquals(ppid, db.resolve("HEAD"));
-		Ref ref = db.getRef("HEAD");
+		Ref ref = db.exactRef("HEAD");
 		assertEquals("HEAD", ref.getName());
 		assertTrue("is detached", !ref.isSymbolic());
 
@@ -681,13 +681,13 @@ public void testRenameCurrentBranch() throws IOException {
 	public void testRenameBranchAlsoInPack() throws IOException {
 		ObjectId rb = db.resolve("refs/heads/b");
 		ObjectId rb2 = db.resolve("refs/heads/b~1");
-		assertEquals(Ref.Storage.PACKED, db.getRef("refs/heads/b").getStorage());
+		assertEquals(Ref.Storage.PACKED, db.exactRef("refs/heads/b").getStorage());
 		RefUpdate updateRef = db.updateRef("refs/heads/b");
 		updateRef.setNewObjectId(rb2);
 		updateRef.setForceUpdate(true);
 		Result update = updateRef.update();
 		assertEquals("internal check new ref is loose", Result.FORCED, update);
-		assertEquals(Ref.Storage.LOOSE, db.getRef("refs/heads/b").getStorage());
+		assertEquals(Ref.Storage.LOOSE, db.exactRef("refs/heads/b").getStorage());
 		writeReflog(db, rb, "Just a message", "refs/heads/b");
 		assertTrue("log on old branch", new File(db.getDirectory(),
 				"logs/refs/heads/b").exists());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java
index b69b8e0..8b54dab 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java
@@ -127,25 +127,25 @@ public void insertExplicitChangeId() throws Exception {
 	@Test
 	public void resetFromSymref() throws Exception {
 		repo.updateRef("HEAD").link("refs/heads/master");
-		Ref head = repo.getRef("HEAD");
+		Ref head = repo.exactRef("HEAD");
 		RevCommit master = tr.branch("master").commit().create();
 		RevCommit branch = tr.branch("branch").commit().create();
 		RevCommit detached = tr.commit().create();
 
 		assertTrue(head.isSymbolic());
 		assertEquals("refs/heads/master", head.getTarget().getName());
-		assertEquals(master, repo.getRef("refs/heads/master").getObjectId());
-		assertEquals(branch, repo.getRef("refs/heads/branch").getObjectId());
+		assertEquals(master, repo.exactRef("refs/heads/master").getObjectId());
+		assertEquals(branch, repo.exactRef("refs/heads/branch").getObjectId());
 
 		// Reset to branches preserves symref.
 		tr.reset("master");
-		head = repo.getRef("HEAD");
+		head = repo.exactRef("HEAD");
 		assertEquals(master, head.getObjectId());
 		assertTrue(head.isSymbolic());
 		assertEquals("refs/heads/master", head.getTarget().getName());
 
 		tr.reset("branch");
-		head = repo.getRef("HEAD");
+		head = repo.exactRef("HEAD");
 		assertEquals(branch, head.getObjectId());
 		assertTrue(head.isSymbolic());
 		assertEquals("refs/heads/master", head.getTarget().getName());
@@ -153,50 +153,50 @@ public void resetFromSymref() throws Exception {
 
 		// Reset to a SHA-1 detaches.
 		tr.reset(detached);
-		head = repo.getRef("HEAD");
+		head = repo.exactRef("HEAD");
 		assertEquals(detached, head.getObjectId());
 		assertFalse(head.isSymbolic());
 
 		tr.reset(detached.name());
-		head = repo.getRef("HEAD");
+		head = repo.exactRef("HEAD");
 		assertEquals(detached, head.getObjectId());
 		assertFalse(head.isSymbolic());
 
 		// Reset back to a branch remains detached.
 		tr.reset("master");
-		head = repo.getRef("HEAD");
+		head = repo.exactRef("HEAD");
 		assertEquals(lastHeadBeforeDetach, head.getObjectId());
 		assertFalse(head.isSymbolic());
 	}
 
 	@Test
 	public void resetFromDetachedHead() throws Exception {
-		Ref head = repo.getRef("HEAD");
+		Ref head = repo.exactRef("HEAD");
 		RevCommit master = tr.branch("master").commit().create();
 		RevCommit branch = tr.branch("branch").commit().create();
 		RevCommit detached = tr.commit().create();
 
 		assertNull(head);
-		assertEquals(master, repo.getRef("refs/heads/master").getObjectId());
-		assertEquals(branch, repo.getRef("refs/heads/branch").getObjectId());
+		assertEquals(master, repo.exactRef("refs/heads/master").getObjectId());
+		assertEquals(branch, repo.exactRef("refs/heads/branch").getObjectId());
 
 		tr.reset("master");
-		head = repo.getRef("HEAD");
+		head = repo.exactRef("HEAD");
 		assertEquals(master, head.getObjectId());
 		assertFalse(head.isSymbolic());
 
 		tr.reset("branch");
-		head = repo.getRef("HEAD");
+		head = repo.exactRef("HEAD");
 		assertEquals(branch, head.getObjectId());
 		assertFalse(head.isSymbolic());
 
 		tr.reset(detached);
-		head = repo.getRef("HEAD");
+		head = repo.exactRef("HEAD");
 		assertEquals(detached, head.getObjectId());
 		assertFalse(head.isSymbolic());
 
 		tr.reset(detached.name());
-		head = repo.getRef("HEAD");
+		head = repo.exactRef("HEAD");
 		assertEquals(detached, head.getObjectId());
 		assertFalse(head.isSymbolic());
 	}
@@ -222,7 +222,7 @@ public void amendRef() throws Exception {
 				.tick(3)
 				.add("bar", "fixed bar contents")
 				.create();
-		assertEquals(amended, repo.getRef("refs/heads/master").getObjectId());
+		assertEquals(amended, repo.exactRef("refs/heads/master").getObjectId());
 		rw.parseBody(amended);
 
 		assertEquals(1, amended.getParentCount());
@@ -257,7 +257,7 @@ public void amendHead() throws Exception {
 				.add("foo", "fixed foo contents")
 				.create();
 
-		Ref head = repo.getRef(Constants.HEAD);
+		Ref head = repo.exactRef(Constants.HEAD);
 		assertEquals(amended, head.getObjectId());
 		assertTrue(head.isSymbolic());
 		assertEquals("refs/heads/master", head.getTarget().getName());
@@ -291,7 +291,7 @@ public void amendCommit() throws Exception {
 	public void commitToUnbornHead() throws Exception {
 		repo.updateRef("HEAD").link("refs/heads/master");
 		RevCommit root = tr.branch("HEAD").commit().create();
-		Ref ref = repo.getRef(Constants.HEAD);
+		Ref ref = repo.exactRef(Constants.HEAD);
 		assertEquals(root, ref.getObjectId());
 		assertTrue(ref.isSymbolic());
 		assertEquals("refs/heads/master", ref.getTarget().getName());
@@ -316,7 +316,7 @@ public void cherryPick() throws Exception {
 		RevCommit result = tr.cherryPick(toPick);
 		rw.parseBody(result);
 
-		Ref headRef = tr.getRepository().getRef("HEAD");
+		Ref headRef = tr.getRepository().exactRef("HEAD");
 		assertEquals(result, headRef.getObjectId());
 		assertTrue(headRef.isSymbolic());
 		assertEquals("refs/heads/master", headRef.getLeaf().getName());
@@ -371,7 +371,7 @@ public void cherryPickWithIdenticalContents() throws Exception {
 				.create();
 		assertNotEquals(head, toPick);
 		assertNull(tr.cherryPick(toPick));
-		assertEquals(head, repo.getRef("HEAD").getObjectId());
+		assertEquals(head, repo.exactRef("HEAD").getObjectId());
 	}
 
 	private String blobAsString(AnyObjectId treeish, String path)
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
index 6d62528..d768e0f 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
@@ -79,6 +79,7 @@
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FileUtils;
+import org.junit.Assume;
 import org.junit.Test;
 
 public class DirCacheCheckoutTest extends RepositoryTestCase {
@@ -112,7 +113,7 @@ private List<String> getRemoved() {
 		return dco.getRemoved();
 	}
 
-	private Map<String, ObjectId> getUpdated() {
+	private Map<String, String> getUpdated() {
 		return dco.getUpdated();
 	}
 
@@ -267,8 +268,6 @@ private void assertIndex(HashMap<String, String> i)
 	@Test
 	public void testRules1thru3_NoIndexEntry() throws IOException {
 		ObjectId head = buildTree(mk("foo"));
-		TreeWalk tw = TreeWalk.forPath(db, "foo", head);
-		ObjectId objectId = tw.getObjectId(0);
 		ObjectId merge = db.newObjectInserter().insert(Constants.OBJ_TREE,
 				new byte[0]);
 
@@ -278,10 +277,9 @@ public void testRules1thru3_NoIndexEntry() throws IOException {
 
 		prescanTwoTrees(merge, head);
 
-		assertEquals(objectId, getUpdated().get("foo"));
+		assertTrue(getUpdated().containsKey("foo"));
 
 		merge = buildTree(mkmap("foo", "a"));
-		tw = TreeWalk.forPath(db, "foo", merge);
 
 		prescanTwoTrees(head, merge);
 
@@ -925,6 +923,7 @@ public void testCheckoutOutChanges() throws IOException {
 
 	@Test
 	public void testCheckoutChangeLinkToEmptyDir() throws Exception {
+		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
 		String fname = "was_file";
 		Git git = Git.wrap(db);
 
@@ -961,6 +960,7 @@ public void testCheckoutChangeLinkToEmptyDir() throws Exception {
 
 	@Test
 	public void testCheckoutChangeLinkToEmptyDirs() throws Exception {
+		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
 		String fname = "was_file";
 		Git git = Git.wrap(db);
 
@@ -999,6 +999,7 @@ public void testCheckoutChangeLinkToEmptyDirs() throws Exception {
 
 	@Test
 	public void testCheckoutChangeLinkToNonEmptyDirs() throws Exception {
+		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
 		String fname = "file";
 		Git git = Git.wrap(db);
 
@@ -1043,6 +1044,7 @@ public void testCheckoutChangeLinkToNonEmptyDirs() throws Exception {
 	@Test
 	public void testCheckoutChangeLinkToNonEmptyDirsAndNewIndexEntry()
 			throws Exception {
+		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
 		String fname = "file";
 		Git git = Git.wrap(db);
 
@@ -1364,6 +1366,7 @@ public void testOverwriteUntrackedFileModeChange()
 	@Test
 	public void testOverwriteUntrackedLinkModeChange()
 			throws Exception {
+		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
 		String fname = "file.txt";
 		Git git = Git.wrap(db);
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java
index 863d79d..3259f62 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java
@@ -87,7 +87,7 @@ public void setUp() throws Exception {
 				.call();
 
 		submodule_db = (FileRepository) Git.wrap(db).submoduleAdd()
-				.setPath("submodule")
+				.setPath("modules/submodule")
 				.setURI(submoduleStandalone.getDirectory().toURI().toString())
 				.call();
 		submoduleStandalone.close();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
index 109f401..707757b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
@@ -163,7 +163,7 @@ public void testReadAllIncludingSymrefs() throws Exception {
 	@Test
 	public void testReadSymRefToPacked() throws IOException {
 		writeSymref("HEAD", "refs/heads/b");
-		Ref ref = db.getRef("HEAD");
+		Ref ref = db.exactRef("HEAD");
 		assertEquals(Ref.Storage.LOOSE, ref.getStorage());
 		assertTrue("is symref", ref.isSymbolic());
 		ref = ref.getTarget();
@@ -181,7 +181,7 @@ public void testReadSymRefToLoosePacked() throws IOException {
 		assertEquals(Result.FORCED, update); // internal
 
 		writeSymref("HEAD", "refs/heads/master");
-		Ref ref = db.getRef("HEAD");
+		Ref ref = db.exactRef("HEAD");
 		assertEquals(Ref.Storage.LOOSE, ref.getStorage());
 		ref = ref.getTarget();
 		assertEquals("refs/heads/master", ref.getName());
@@ -194,13 +194,13 @@ public void testReadLooseRef() throws IOException {
 		updateRef.setNewObjectId(db.resolve("refs/heads/master"));
 		Result update = updateRef.update();
 		assertEquals(Result.NEW, update);
-		Ref ref = db.getRef("ref/heads/new");
+		Ref ref = db.exactRef("ref/heads/new");
 		assertEquals(Storage.LOOSE, ref.getStorage());
 	}
 
 	@Test
 	public void testGetShortRef() throws IOException {
-		Ref ref = db.getRef("master");
+		Ref ref = db.exactRef("refs/heads/master");
 		assertEquals("refs/heads/master", ref.getName());
 		assertEquals(db.resolve("refs/heads/master"), ref.getObjectId());
 	}
@@ -222,7 +222,7 @@ public void testRefsUnderRefs() throws IOException {
 
 		assertNull(db.getRefDatabase().exactRef("refs/foo/bar"));
 
-		Ref ref = db.getRef("refs/foo/bar");
+		Ref ref = db.findRef("refs/foo/bar");
 		assertEquals("refs/heads/refs/foo/bar", ref.getName());
 		assertEquals(db.resolve("refs/heads/master"), ref.getObjectId());
 	}
@@ -237,7 +237,7 @@ public void testAmbiguousRefsUnderRefs() throws IOException {
 		assertEquals("refs/foo/bar", exactRef.getName());
 		assertEquals(masterId, exactRef.getObjectId());
 
-		Ref ref = db.getRef("refs/foo/bar");
+		Ref ref = db.findRef("refs/foo/bar");
 		assertEquals("refs/foo/bar", ref.getName());
 		assertEquals(masterId, ref.getObjectId());
 	}
@@ -251,7 +251,7 @@ public void testAmbiguousRefsUnderRefs() throws IOException {
 	@Test
 	public void testReadLoosePackedRef() throws IOException,
 			InterruptedException {
-		Ref ref = db.getRef("refs/heads/master");
+		Ref ref = db.exactRef("refs/heads/master");
 		assertEquals(Storage.PACKED, ref.getStorage());
 		FileOutputStream os = new FileOutputStream(new File(db.getDirectory(),
 				"refs/heads/master"));
@@ -259,7 +259,7 @@ public void testReadLoosePackedRef() throws IOException,
 		os.write('\n');
 		os.close();
 
-		ref = db.getRef("refs/heads/master");
+		ref = db.exactRef("refs/heads/master");
 		assertEquals(Storage.LOOSE, ref.getStorage());
 	}
 
@@ -271,7 +271,7 @@ public void testReadLoosePackedRef() throws IOException,
 	 */
 	@Test
 	public void testReadSimplePackedRefSameRepo() throws IOException {
-		Ref ref = db.getRef("refs/heads/master");
+		Ref ref = db.exactRef("refs/heads/master");
 		ObjectId pid = db.resolve("refs/heads/master^");
 		assertEquals(Storage.PACKED, ref.getStorage());
 		RefUpdate updateRef = db.updateRef("refs/heads/master");
@@ -280,19 +280,19 @@ public void testReadSimplePackedRefSameRepo() throws IOException {
 		Result update = updateRef.update();
 		assertEquals(Result.FORCED, update);
 
-		ref = db.getRef("refs/heads/master");
+		ref = db.exactRef("refs/heads/master");
 		assertEquals(Storage.LOOSE, ref.getStorage());
 	}
 
 	@Test
 	public void testResolvedNamesBranch() throws IOException {
-		Ref ref = db.getRef("a");
+		Ref ref = db.findRef("a");
 		assertEquals("refs/heads/a", ref.getName());
 	}
 
 	@Test
 	public void testResolvedSymRef() throws IOException {
-		Ref ref = db.getRef(Constants.HEAD);
+		Ref ref = db.exactRef(Constants.HEAD);
 		assertEquals(Constants.HEAD, ref.getName());
 		assertTrue("is symbolic ref", ref.isSymbolic());
 		assertSame(Ref.Storage.LOOSE, ref.getStorage());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java
index 21ef747..6c90f7d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java
@@ -84,44 +84,44 @@ public void setUp() throws Exception {
 
 	@Test
 	public void testOneBranch() throws IOException {
-		Ref a = db.getRef("refs/heads/a");
-		Ref master = db.getRef("refs/heads/master");
+		Ref a = db.exactRef("refs/heads/a");
+		Ref master = db.exactRef("refs/heads/master");
 		String message = formatter.format(Arrays.asList(a), master);
 		assertEquals("Merge branch 'a'", message);
 	}
 
 	@Test
 	public void testTwoBranches() throws IOException {
-		Ref a = db.getRef("refs/heads/a");
-		Ref b = db.getRef("refs/heads/b");
-		Ref master = db.getRef("refs/heads/master");
+		Ref a = db.exactRef("refs/heads/a");
+		Ref b = db.exactRef("refs/heads/b");
+		Ref master = db.exactRef("refs/heads/master");
 		String message = formatter.format(Arrays.asList(a, b), master);
 		assertEquals("Merge branches 'a' and 'b'", message);
 	}
 
 	@Test
 	public void testThreeBranches() throws IOException {
-		Ref c = db.getRef("refs/heads/c");
-		Ref b = db.getRef("refs/heads/b");
-		Ref a = db.getRef("refs/heads/a");
-		Ref master = db.getRef("refs/heads/master");
+		Ref c = db.exactRef("refs/heads/c");
+		Ref b = db.exactRef("refs/heads/b");
+		Ref a = db.exactRef("refs/heads/a");
+		Ref master = db.exactRef("refs/heads/master");
 		String message = formatter.format(Arrays.asList(c, b, a), master);
 		assertEquals("Merge branches 'c', 'b' and 'a'", message);
 	}
 
 	@Test
 	public void testRemoteBranch() throws Exception {
-		Ref remoteA = db.getRef("refs/remotes/origin/remote-a");
-		Ref master = db.getRef("refs/heads/master");
+		Ref remoteA = db.exactRef("refs/remotes/origin/remote-a");
+		Ref master = db.exactRef("refs/heads/master");
 		String message = formatter.format(Arrays.asList(remoteA), master);
 		assertEquals("Merge remote-tracking branch 'origin/remote-a'", message);
 	}
 
 	@Test
 	public void testMixed() throws IOException {
-		Ref c = db.getRef("refs/heads/c");
-		Ref remoteA = db.getRef("refs/remotes/origin/remote-a");
-		Ref master = db.getRef("refs/heads/master");
+		Ref c = db.exactRef("refs/heads/c");
+		Ref remoteA = db.exactRef("refs/remotes/origin/remote-a");
+		Ref master = db.exactRef("refs/heads/master");
 		String message = formatter.format(Arrays.asList(c, remoteA), master);
 		assertEquals("Merge branch 'c', remote-tracking branch 'origin/remote-a'",
 				message);
@@ -129,8 +129,8 @@ public void testMixed() throws IOException {
 
 	@Test
 	public void testTag() throws IOException {
-		Ref tagA = db.getRef("refs/tags/A");
-		Ref master = db.getRef("refs/heads/master");
+		Ref tagA = db.exactRef("refs/tags/A");
+		Ref master = db.exactRef("refs/heads/master");
 		String message = formatter.format(Arrays.asList(tagA), master);
 		assertEquals("Merge tag 'A'", message);
 	}
@@ -141,7 +141,7 @@ public void testCommit() throws IOException {
 				.fromString("6db9c2ebf75590eef973081736730a9ea169a0c4");
 		Ref commit = new ObjectIdRef.Unpeeled(Storage.LOOSE,
 				objectId.getName(), objectId);
-		Ref master = db.getRef("refs/heads/master");
+		Ref master = db.exactRef("refs/heads/master");
 		String message = formatter.format(Arrays.asList(commit), master);
 		assertEquals("Merge commit '6db9c2ebf75590eef973081736730a9ea169a0c4'",
 				message);
@@ -154,7 +154,7 @@ public void testPullWithUri() throws IOException {
 				.fromString("6db9c2ebf75590eef973081736730a9ea169a0c4");
 		Ref remoteBranch = new ObjectIdRef.Unpeeled(Storage.LOOSE, name,
 				objectId);
-		Ref master = db.getRef("refs/heads/master");
+		Ref master = db.exactRef("refs/heads/master");
 		String message = formatter.format(Arrays.asList(remoteBranch), master);
 		assertEquals("Merge branch 'test' of http://egit.eclipse.org/jgit.git",
 				message);
@@ -162,16 +162,16 @@ public void testPullWithUri() throws IOException {
 
 	@Test
 	public void testIntoOtherThanMaster() throws IOException {
-		Ref a = db.getRef("refs/heads/a");
-		Ref b = db.getRef("refs/heads/b");
+		Ref a = db.exactRef("refs/heads/a");
+		Ref b = db.exactRef("refs/heads/b");
 		String message = formatter.format(Arrays.asList(a), b);
 		assertEquals("Merge branch 'a' into b", message);
 	}
 
 	@Test
 	public void testIntoHeadOtherThanMaster() throws IOException {
-		Ref a = db.getRef("refs/heads/a");
-		Ref b = db.getRef("refs/heads/b");
+		Ref a = db.exactRef("refs/heads/a");
+		Ref b = db.exactRef("refs/heads/b");
 		SymbolicRef head = new SymbolicRef("HEAD", b);
 		String message = formatter.format(Arrays.asList(a), head);
 		assertEquals("Merge branch 'a' into b", message);
@@ -179,8 +179,8 @@ public void testIntoHeadOtherThanMaster() throws IOException {
 
 	@Test
 	public void testIntoSymbolicRefHeadPointingToMaster() throws IOException {
-		Ref a = db.getRef("refs/heads/a");
-		Ref master = db.getRef("refs/heads/master");
+		Ref a = db.exactRef("refs/heads/a");
+		Ref master = db.exactRef("refs/heads/master");
 		SymbolicRef head = new SymbolicRef("HEAD", master);
 		String message = formatter.format(Arrays.asList(a), head);
 		assertEquals("Merge branch 'a'", message);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java
index cd6a4be..674619f 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java
@@ -630,7 +630,7 @@ public void checkMergeCrissCross(MergeStrategy strategy) throws Exception {
 		// ResolveMerge
 		try {
 			MergeResult mergeResult = git.merge().setStrategy(strategy)
-					.include(git.getRepository().getRef("refs/heads/side"))
+					.include(git.getRepository().exactRef("refs/heads/side"))
 					.call();
 			assertEquals(MergeStrategy.RECURSIVE, strategy);
 			assertEquals(MergeResult.MergeStatus.MERGED,
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java
index b7b2291..cddbbd5 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java
@@ -76,7 +76,7 @@ public void testCommit() throws Exception {
 		Git git = new Git(db);
 		revCommit = git.commit().setMessage("squash_me").call();
 
-		Ref master = db.getRef("refs/heads/master");
+		Ref master = db.exactRef("refs/heads/master");
 		String message = msgFormatter.format(Arrays.asList(revCommit), master);
 		assertEquals(
 				"Squashed commit of the following:\n\ncommit "
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/AtomicPushTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/AtomicPushTest.java
new file mode 100644
index 0000000..782e414
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/AtomicPushTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2015, Google Inc.
+ * 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.transport;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class AtomicPushTest {
+	private URIish uri;
+	private TestProtocol<Object> testProtocol;
+	private Object ctx = new Object();
+	private InMemoryRepository server;
+	private InMemoryRepository client;
+	private ObjectId obj1;
+	private ObjectId obj2;
+
+	@Before
+	public void setUp() throws Exception {
+		server = newRepo("server");
+		client = newRepo("client");
+		testProtocol = new TestProtocol<>(
+				null,
+				new ReceivePackFactory<Object>() {
+					@Override
+					public ReceivePack create(Object req, Repository db)
+							throws ServiceNotEnabledException,
+							ServiceNotAuthorizedException {
+						return new ReceivePack(db);
+					}
+				});
+		uri = testProtocol.register(ctx, server);
+
+		try (ObjectInserter ins = client.newObjectInserter()) {
+			obj1 = ins.insert(Constants.OBJ_BLOB, Constants.encode("test"));
+			obj2 = ins.insert(Constants.OBJ_BLOB, Constants.encode("file"));
+			ins.flush();
+		}
+	}
+
+	@After
+	public void tearDown() {
+		Transport.unregister(testProtocol);
+	}
+
+	private static InMemoryRepository newRepo(String name) {
+		return new InMemoryRepository(new DfsRepositoryDescription(name));
+	}
+
+	@Test
+	public void pushNonAtomic() throws Exception {
+		PushResult r;
+		server.setPerformsAtomicTransactions(false);
+		Transport tn = testProtocol.open(uri, client, "server");
+		try {
+			tn.setPushAtomic(false);
+			r = tn.push(NullProgressMonitor.INSTANCE, commands());
+		} finally {
+			tn.close();
+		}
+
+		RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
+		RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two");
+		assertSame(RemoteRefUpdate.Status.OK, one.getStatus());
+		assertSame(
+				RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
+				two.getStatus());
+	}
+
+	@Test
+	public void pushAtomicClientGivesUpEarly() throws Exception {
+		PushResult r;
+		Transport tn = testProtocol.open(uri, client, "server");
+		try {
+			tn.setPushAtomic(true);
+			r = tn.push(NullProgressMonitor.INSTANCE, commands());
+		} finally {
+			tn.close();
+		}
+
+		RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
+		RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two");
+		assertSame(
+				RemoteRefUpdate.Status.REJECTED_OTHER_REASON,
+				one.getStatus());
+		assertSame(
+				RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
+				two.getStatus());
+		assertEquals(JGitText.get().transactionAborted, one.getMessage());
+	}
+
+	@Test
+	public void pushAtomicDisabled() throws Exception {
+		List<RemoteRefUpdate> cmds = new ArrayList<>();
+		cmds.add(new RemoteRefUpdate(
+				null, null,
+				obj1, "refs/heads/one",
+				true /* force update */,
+				null /* no local tracking ref */,
+				ObjectId.zeroId()));
+		cmds.add(new RemoteRefUpdate(
+				null, null,
+				obj2, "refs/heads/two",
+				true /* force update */,
+				null /* no local tracking ref */,
+				ObjectId.zeroId()));
+
+		server.setPerformsAtomicTransactions(false);
+		Transport tn = testProtocol.open(uri, client, "server");
+		try {
+			tn.setPushAtomic(true);
+			tn.push(NullProgressMonitor.INSTANCE, cmds);
+			fail("did not throw TransportException");
+		} catch (TransportException e) {
+			assertEquals(
+					uri + ": " + JGitText.get().atomicPushNotSupported,
+					e.getMessage());
+		} finally {
+			tn.close();
+		}
+	}
+
+	private List<RemoteRefUpdate> commands() throws IOException {
+		List<RemoteRefUpdate> cmds = new ArrayList<>();
+		cmds.add(new RemoteRefUpdate(
+				null, null,
+				obj1, "refs/heads/one",
+				true /* force update */,
+				null /* no local tracking ref */,
+				ObjectId.zeroId()));
+		cmds.add(new RemoteRefUpdate(
+				null, null,
+				obj2, "refs/heads/two",
+				true /* force update */,
+				null /* no local tracking ref */,
+				obj1));
+		return cmds;
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java
new file mode 100644
index 0000000..a3b4134
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2015, Google Inc.
+ * 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.transport;
+
+import static org.eclipse.jgit.transport.RemoteRefUpdate.Status.REJECTED_OTHER_REASON;
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PushConnectionTest {
+	private URIish uri;
+	private TestProtocol<Object> testProtocol;
+	private Object ctx = new Object();
+	private InMemoryRepository server;
+	private InMemoryRepository client;
+	private ObjectId obj1;
+	private ObjectId obj2;
+	private ObjectId obj3;
+	private String refName = "refs/tags/blob";
+
+	@Before
+	public void setUp() throws Exception {
+		server = newRepo("server");
+		client = newRepo("client");
+		testProtocol = new TestProtocol<>(
+				null,
+				new ReceivePackFactory<Object>() {
+					@Override
+					public ReceivePack create(Object req, Repository db)
+							throws ServiceNotEnabledException,
+							ServiceNotAuthorizedException {
+						return new ReceivePack(db);
+					}
+				});
+		uri = testProtocol.register(ctx, server);
+
+		try (ObjectInserter ins = server.newObjectInserter()) {
+			obj1 = ins.insert(Constants.OBJ_BLOB, Constants.encode("test"));
+			obj3 = ins.insert(Constants.OBJ_BLOB, Constants.encode("not"));
+			ins.flush();
+
+			RefUpdate u = server.updateRef(refName);
+			u.setNewObjectId(obj1);
+			assertEquals(RefUpdate.Result.NEW, u.update());
+		}
+
+		try (ObjectInserter ins = client.newObjectInserter()) {
+			obj2 = ins.insert(Constants.OBJ_BLOB, Constants.encode("file"));
+			ins.flush();
+		}
+	}
+
+	@After
+	public void tearDown() {
+		Transport.unregister(testProtocol);
+	}
+
+	private static InMemoryRepository newRepo(String name) {
+		return new InMemoryRepository(new DfsRepositoryDescription(name));
+	}
+
+	@Test
+	public void testWrongOldIdDoesNotReplace() throws IOException {
+		RemoteRefUpdate rru = new RemoteRefUpdate(null, null, obj2, refName,
+				false, null, obj3);
+
+		Map<String, RemoteRefUpdate> updates = new HashMap<>();
+		updates.put(rru.getRemoteName(), rru);
+
+		Transport tn = testProtocol.open(uri, client, "server");
+		try {
+			PushConnection connection = tn.openPush();
+			try {
+				connection.push(NullProgressMonitor.INSTANCE, updates);
+			} finally {
+				connection.close();
+			}
+		} finally {
+			tn.close();
+		}
+
+		assertEquals(REJECTED_OTHER_REASON, rru.getStatus());
+		assertEquals("invalid old id sent", rru.getMessage());
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java
index 7f86526..31b6418 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java
@@ -125,7 +125,7 @@ public void testFetch() throws Exception {
 					.setRefSpecs(HEADS)
 					.call();
 			assertEquals(master,
-					local.getRepository().getRef("master").getObjectId());
+					local.getRepository().exactRef("refs/heads/master").getObjectId());
 		}
 	}
 
@@ -142,7 +142,7 @@ public void testPush() throws Exception {
 					.setRefSpecs(HEADS)
 					.call();
 			assertEquals(master,
-					remote.getRepository().getRef("master").getObjectId());
+					remote.getRepository().exactRef("refs/heads/master").getObjectId());
 		}
 	}
 
@@ -177,7 +177,7 @@ public UploadPack create(User req, Repository db)
 				// Expected.
 			}
 			assertEquals(1, rejected.get());
-			assertNull(local.getRepository().getRef("master"));
+			assertNull(local.getRepository().exactRef("refs/heads/master"));
 
 			git.fetch()
 					.setRemote(user2Uri.toString())
@@ -185,7 +185,7 @@ public UploadPack create(User req, Repository db)
 					.call();
 			assertEquals(1, rejected.get());
 			assertEquals(master,
-					local.getRepository().getRef("master").getObjectId());
+					local.getRepository().exactRef("refs/heads/master").getObjectId());
 		}
 	}
 
@@ -222,7 +222,7 @@ public ReceivePack create(User req, Repository db)
 						JGitText.get().pushNotPermitted));
 			}
 			assertEquals(1, rejected.get());
-			assertNull(remote.getRepository().getRef("master"));
+			assertNull(remote.getRepository().exactRef("refs/heads/master"));
 
 			git.push()
 					.setRemote(user2Uri.toString())
@@ -230,7 +230,7 @@ public ReceivePack create(User req, Repository db)
 					.call();
 			assertEquals(1, rejected.get());
 			assertEquals(master,
-					remote.getRepository().getRef("master").getObjectId());
+					remote.getRepository().exactRef("refs/heads/master").getObjectId());
 		}
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java
index 745c322..76eb18a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java
@@ -928,4 +928,19 @@ public void testALot() throws URISyntaxException {
 			}
 		}
 	}
+
+	@Test
+	public void testStringConstructor() throws Exception {
+		String str = "http://example.com/";
+		URIish u = new URIish(str);
+		assertEquals("example.com", u.getHost());
+		assertEquals("/", u.getPath());
+		assertEquals(str, u.toString());
+
+		str = "http://example.com";
+		u = new URIish(str);
+		assertEquals("example.com", u.getHost());
+		assertEquals("", u.getPath());
+		assertEquals(str, u.toString());
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java
index 90d78e4..ac2bfd1 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java
@@ -43,6 +43,20 @@
 
 package org.eclipse.jgit.transport;
 
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.UTF_8;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.cryptoCipherListPBE;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.cryptoCipherListTrans;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.folderDelete;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.permitLongTests;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.policySetup;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.product;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.proxySetup;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.publicAddress;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.reportPolicy;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.securityProviderName;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.textWrite;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.transferStream;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.verifyFileContent;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -59,7 +73,10 @@
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.PrintWriter;
+import java.net.SocketTimeoutException;
 import java.net.URL;
+import java.net.URLConnection;
+import java.net.UnknownHostException;
 import java.nio.charset.Charset;
 import java.nio.file.Files;
 import java.security.GeneralSecurityException;
@@ -94,8 +111,6 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.*;
-
 /**
  * Amazon S3 encryption pipeline test.
  *
@@ -401,14 +416,22 @@ static void folderDelete(String folder) throws Exception {
 		 * @throws Exception
 		 */
 		static String publicAddress() throws Exception {
-			String service = "http://checkip.amazonaws.com";
-			URL url = new URL(service);
-			BufferedReader reader = new BufferedReader(
-					new InputStreamReader(url.openStream()));
 			try {
-				return reader.readLine();
-			} finally {
-				reader.close();
+				String service = "http://checkip.amazonaws.com";
+				URL url = new URL(service);
+				URLConnection c = url.openConnection();
+				c.setConnectTimeout(500);
+				c.setReadTimeout(500);
+				BufferedReader reader = new BufferedReader(
+						new InputStreamReader(c.getInputStream()));
+				try {
+					return reader.readLine();
+				} finally {
+					reader.close();
+				}
+			} catch (UnknownHostException | SocketTimeoutException e) {
+				return "Can't reach http://checkip.amazonaws.com to"
+						+ " determine public address";
 			}
 		}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java
index 52da69e..f5e97c2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java
@@ -43,6 +43,8 @@
 
 package org.eclipse.jgit.treewalk;
 
+import static org.eclipse.jgit.lib.FileMode.REGULAR_FILE;
+import static org.eclipse.jgit.lib.FileMode.SYMLINK;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertSame;
@@ -50,9 +52,11 @@
 
 import java.io.ByteArrayOutputStream;
 
+import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.TreeFormatter;
 import org.eclipse.jgit.util.RawParseUtils;
 import org.junit.Before;
 import org.junit.Test;
@@ -369,4 +373,41 @@ public void testFreakingHugePathName() throws Exception {
 		assertEquals(name, RawParseUtils.decode(Constants.CHARSET, ctp.path,
 				ctp.pathOffset, ctp.pathLen));
 	}
+
+	@Test
+	public void testFindAttributesWhenFirst() throws CorruptObjectException {
+		TreeFormatter tree = new TreeFormatter();
+		tree.append(".gitattributes", REGULAR_FILE, hash_a);
+		ctp.reset(tree.toByteArray());
+
+		assertTrue(ctp.findFile(".gitattributes"));
+		assertEquals(REGULAR_FILE.getBits(), ctp.getEntryRawMode());
+		assertEquals(".gitattributes", ctp.getEntryPathString());
+		assertEquals(hash_a, ctp.getEntryObjectId());
+	}
+
+	@Test
+	public void testFindAttributesWhenSecond() throws CorruptObjectException {
+		TreeFormatter tree = new TreeFormatter();
+		tree.append(".config", SYMLINK, hash_a);
+		tree.append(".gitattributes", REGULAR_FILE, hash_foo);
+		ctp.reset(tree.toByteArray());
+
+		assertTrue(ctp.findFile(".gitattributes"));
+		assertEquals(REGULAR_FILE.getBits(), ctp.getEntryRawMode());
+		assertEquals(".gitattributes", ctp.getEntryPathString());
+		assertEquals(hash_foo, ctp.getEntryObjectId());
+	}
+
+	@Test
+	public void testFindAttributesWhenMissing() throws CorruptObjectException {
+		TreeFormatter tree = new TreeFormatter();
+		tree.append("src", REGULAR_FILE, hash_a);
+		tree.append("zoo", REGULAR_FILE, hash_foo);
+		ctp.reset(tree.toByteArray());
+
+		assertFalse(ctp.findFile(".gitattributes"));
+		assertEquals(11, ctp.idOffset()); // Did not walk the entire tree.
+		assertEquals("src", ctp.getEntryPathString());
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorJava7Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorJava7Test.java
index 8875410..429f35e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorJava7Test.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorJava7Test.java
@@ -67,6 +67,7 @@
 public class FileTreeIteratorJava7Test extends RepositoryTestCase {
 	@Test
 	public void testFileModeSymLinkIsNotATree() throws IOException {
+		org.junit.Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
 		FS fs = db.getFS();
 		// mål = target in swedish, just to get som unicode in here
 		writeTrashFile("mål/data", "targetdata");
@@ -163,6 +164,7 @@ public void apply(DirCacheEntry ent) {
 	 */
 	@Test
 	public void testSymlinkActuallyModified() throws Exception {
+		org.junit.Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
 		final String NORMALIZED = "target";
 		final byte[] NORMALIZED_BYTES = Constants.encode(NORMALIZED);
 		try (ObjectInserter oi = db.newObjectInserter()) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkJava7Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkJava7Test.java
index bb1f2a6..1328b38 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkJava7Test.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkJava7Test.java
@@ -55,6 +55,7 @@
 public class TreeWalkJava7Test extends RepositoryTestCase {
 	@Test
 	public void testSymlinkToDirNotRecursingViaSymlink() throws Exception {
+		org.junit.Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
 		FS fs = db.getFS();
 		assertTrue(fs.supportsSymlinks());
 		writeTrashFile("target/data", "targetdata");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSJava7Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSJava7Test.java
index e6a244e..53b6fec 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSJava7Test.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSJava7Test.java
@@ -57,6 +57,7 @@
 
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.junit.After;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -87,6 +88,7 @@ public void tearDown() throws Exception {
 	 */
 	@Test
 	public void testSymlinkAttributes() throws IOException, InterruptedException {
+		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
 		FS fs = FS.DETECTED;
 		File link = new File(trash, "ä");
 		File target = new File(trash, "å");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java
index 4625f30..cc1fdc2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java
@@ -73,6 +73,7 @@ public void tearDown() throws Exception {
 	@Test
 	public void testDeleteSymlinkToDirectoryDoesNotDeleteTarget()
 			throws IOException {
+		org.junit.Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
 		FS fs = FS.DETECTED;
 		File dir = new File(trash, "dir");
 		File file = new File(dir, "file");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java
index 8aa14c5..e07076e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java
@@ -92,11 +92,9 @@ public void testFailedCommitMsgHookBlocksCommit() throws Exception {
 			fail("expected commit-msg hook to abort commit");
 		} catch (AbortedByHookException e) {
 			assertEquals("unexpected error message from commit-msg hook",
-					"Rejected by \"commit-msg\" hook.\nstderr"
-							+ System.lineSeparator(),
+					"Rejected by \"commit-msg\" hook.\nstderr\n",
 					e.getMessage());
-			assertEquals("unexpected output from commit-msg hook",
-					"test" + System.lineSeparator(),
+			assertEquals("unexpected output from commit-msg hook", "test\n",
 					out.toString());
 		}
 	}
@@ -114,7 +112,7 @@ public void testCommitMsgHookReceivesCorrectParameter() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		git.commit().setMessage("commit")
 				.setHookOutputStream(new PrintStream(out)).call();
-		assertEquals(".git/COMMIT_EDITMSG" + System.lineSeparator(),
+		assertEquals(".git/COMMIT_EDITMSG\n",
 				out.toString("UTF-8"));
 	}
 
@@ -147,11 +145,10 @@ public void testRunHook() throws Exception {
 				new String[] {
 				"arg1", "arg2" },
 				new PrintStream(out), new PrintStream(err), "stdin");
-		assertEquals("unexpected hook output", "test arg1 arg2"
-				+ System.lineSeparator() + "stdin" + System.lineSeparator(),
+
+		assertEquals("unexpected hook output", "test arg1 arg2\nstdin\n",
 				out.toString("UTF-8"));
-		assertEquals("unexpected output on stderr stream",
-				"stderr" + System.lineSeparator(),
+		assertEquals("unexpected output on stderr stream", "stderr\n",
 				err.toString("UTF-8"));
 		assertEquals("unexpected exit code", 0, res.getExitCode());
 		assertEquals("unexpected process status", ProcessResult.Status.OK,
@@ -175,11 +172,9 @@ public void testFailedPreCommitHookBlockCommit() throws Exception {
 			fail("expected pre-commit hook to abort commit");
 		} catch (AbortedByHookException e) {
 			assertEquals("unexpected error message from pre-commit hook",
-					"Rejected by \"pre-commit\" hook.\nstderr"
-							+ System.lineSeparator(),
+					"Rejected by \"pre-commit\" hook.\nstderr\n",
 					e.getMessage());
-			assertEquals("unexpected output from pre-commit hook",
-					"test" + System.lineSeparator(),
+			assertEquals("unexpected output from pre-commit hook", "test\n",
 					out.toString());
 		}
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java
index 82beab2..7c0985e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java
@@ -52,16 +52,17 @@
 import java.io.InputStream;
 
 import org.eclipse.jgit.junit.JGitTestUtil;
+import org.eclipse.jgit.util.FS.ExecutionResult;
 import org.junit.Before;
 import org.junit.Test;
 
 public class RunExternalScriptTest {
+	private static final String LF = "\n";
+
 	private ByteArrayOutputStream out;
 
 	private ByteArrayOutputStream err;
 
-	private String sep = System.getProperty("line.separator");
-
 	@Before
 	public void setUp() throws Exception {
 		out = new ByteArrayOutputStream();
@@ -73,7 +74,7 @@ public void testCopyStdIn() throws IOException, InterruptedException {
 		String inputStr = "a\nb\rc\r\nd";
 		File script = writeTempFile("cat -");
 		int rc = FS.DETECTED.runProcess(
-				new ProcessBuilder("/bin/sh", script.getPath()), out, err,
+				new ProcessBuilder("sh", script.getPath()), out, err,
 				new ByteArrayInputStream(inputStr.getBytes()));
 		assertEquals(0, rc);
 		assertEquals(inputStr, new String(out.toByteArray()));
@@ -84,7 +85,7 @@ public void testCopyStdIn() throws IOException, InterruptedException {
 	public void testCopyNullStdIn() throws IOException, InterruptedException {
 		File script = writeTempFile("cat -");
 		int rc = FS.DETECTED.runProcess(
-				new ProcessBuilder("/bin/sh", script.getPath()), out, err,
+				new ProcessBuilder("sh", script.getPath()), out, err,
 				(InputStream) null);
 		assertEquals(0, rc);
 		assertEquals("", new String(out.toByteArray()));
@@ -94,7 +95,8 @@ public void testCopyNullStdIn() throws IOException, InterruptedException {
 	@Test
 	public void testArguments() throws IOException, InterruptedException {
 		File script = writeTempFile("echo $#,$1,$2,$3,$4,$5,$6");
-		int rc = FS.DETECTED.runProcess(new ProcessBuilder("/bin/bash",
+		int rc = FS.DETECTED.runProcess(
+				new ProcessBuilder("sh",
 				script.getPath(), "a", "b", "c"), out, err, (InputStream) null);
 		assertEquals(0, rc);
 		assertEquals("3,a,b,c,,,\n", new String(out.toByteArray()));
@@ -105,7 +107,7 @@ public void testArguments() throws IOException, InterruptedException {
 	public void testRc() throws IOException, InterruptedException {
 		File script = writeTempFile("exit 3");
 		int rc = FS.DETECTED.runProcess(
-				new ProcessBuilder("/bin/sh", script.getPath(), "a", "b", "c"),
+				new ProcessBuilder("sh", script.getPath(), "a", "b", "c"),
 				out, err, (InputStream) null);
 		assertEquals(3, rc);
 		assertEquals("", new String(out.toByteArray()));
@@ -116,7 +118,7 @@ public void testRc() throws IOException, InterruptedException {
 	public void testNullStdout() throws IOException, InterruptedException {
 		File script = writeTempFile("echo hi");
 		int rc = FS.DETECTED.runProcess(
-				new ProcessBuilder("/bin/sh", script.getPath()), null, err,
+				new ProcessBuilder("sh", script.getPath()), null, err,
 				(InputStream) null);
 		assertEquals(0, rc);
 		assertEquals("", new String(out.toByteArray()));
@@ -127,11 +129,11 @@ public void testNullStdout() throws IOException, InterruptedException {
 	public void testStdErr() throws IOException, InterruptedException {
 		File script = writeTempFile("echo hi >&2");
 		int rc = FS.DETECTED.runProcess(
-				new ProcessBuilder("/bin/sh", script.getPath()), null, err,
+				new ProcessBuilder("sh", script.getPath()), null, err,
 				(InputStream) null);
 		assertEquals(0, rc);
 		assertEquals("", new String(out.toByteArray()));
-		assertEquals("hi" + sep, new String(err.toByteArray()));
+		assertEquals("hi" + LF, new String(err.toByteArray()));
 	}
 
 	@Test
@@ -139,11 +141,11 @@ public void testAllTogetherBin() throws IOException, InterruptedException {
 		String inputStr = "a\nb\rc\r\nd";
 		File script = writeTempFile("echo $#,$1,$2,$3,$4,$5,$6 >&2 ; cat -; exit 5");
 		int rc = FS.DETECTED.runProcess(
-				new ProcessBuilder("/bin/sh", script.getPath(), "a", "b", "c"),
+				new ProcessBuilder("sh", script.getPath(), "a", "b", "c"),
 				out, err, new ByteArrayInputStream(inputStr.getBytes()));
 		assertEquals(5, rc);
 		assertEquals(inputStr, new String(out.toByteArray()));
-		assertEquals("3,a,b,c,,," + sep, new String(err.toByteArray()));
+		assertEquals("3,a,b,c,,," + LF, new String(err.toByteArray()));
 	}
 
 	@Test(expected = IOException.class)
@@ -158,11 +160,50 @@ public void testWrongSh() throws IOException, InterruptedException {
 	public void testWrongScript() throws IOException, InterruptedException {
 		File script = writeTempFile("cat-foo -");
 		int rc = FS.DETECTED.runProcess(
-				new ProcessBuilder("/bin/sh", script.getPath(), "a", "b", "c"),
+				new ProcessBuilder("sh", script.getPath(), "a", "b", "c"),
 				out, err, (InputStream) null);
 		assertEquals(127, rc);
 	}
 
+	@Test
+	public void testCopyStdInExecute()
+			throws IOException, InterruptedException {
+		String inputStr = "a\nb\rc\r\nd";
+		File script = writeTempFile("cat -");
+		ProcessBuilder pb = new ProcessBuilder("sh", script.getPath());
+		ExecutionResult res = FS.DETECTED.execute(pb,
+				new ByteArrayInputStream(inputStr.getBytes()));
+		assertEquals(0, res.getRc());
+		assertEquals(inputStr, new String(res.getStdout().toByteArray()));
+		assertEquals("", new String(res.getStderr().toByteArray()));
+	}
+
+	@Test
+	public void testStdErrExecute() throws IOException, InterruptedException {
+		File script = writeTempFile("echo hi >&2");
+		ProcessBuilder pb = new ProcessBuilder("sh", script.getPath());
+		ExecutionResult res = FS.DETECTED.execute(pb, null);
+		assertEquals(0, res.getRc());
+		assertEquals("", new String(res.getStdout().toByteArray()));
+		assertEquals("hi" + LF, new String(res.getStderr().toByteArray()));
+	}
+
+	@Test
+	public void testAllTogetherBinExecute()
+			throws IOException, InterruptedException {
+		String inputStr = "a\nb\rc\r\nd";
+		File script = writeTempFile(
+				"echo $#,$1,$2,$3,$4,$5,$6 >&2 ; cat -; exit 5");
+		ProcessBuilder pb = new ProcessBuilder("sh", script.getPath(), "a",
+				"b", "c");
+		ExecutionResult res = FS.DETECTED.execute(pb,
+				new ByteArrayInputStream(inputStr.getBytes()));
+		assertEquals(5, res.getRc());
+		assertEquals(inputStr, new String(res.getStdout().toByteArray()));
+		assertEquals("3,a,b,c,,," + LF,
+				new String(res.getStderr().toByteArray()));
+	}
+
 	private File writeTempFile(String body) throws IOException {
 		File f = File.createTempFile("RunProcessTestScript_", "");
 		JGitTestUtil.write(f, body);
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index ed330b1..b2a8f67 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -1,5 +1,13 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <component id="org.eclipse.jgit" version="2">
+    <resource path="src/org/eclipse/jgit/attributes/AttributesNode.java" type="org.eclipse.jgit.attributes.AttributesNode">
+        <filter comment="attributes weren't really usable in earlier versions" id="338792546">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.attributes.AttributesNode"/>
+                <message_argument value="getAttributes(String, boolean, Map&lt;String,Attribute&gt;)"/>
+            </message_arguments>
+        </filter>
+    </resource>
     <resource path="src/org/eclipse/jgit/lib/BitmapIndex.java" type="org.eclipse.jgit.lib.BitmapIndex$BitmapBuilder">
         <filter comment="interface is implemented by extenders but not clients of the API" id="403804204">
             <message_arguments>
@@ -14,6 +22,14 @@
             </message_arguments>
         </filter>
     </resource>
+    <resource path="src/org/eclipse/jgit/lib/Repository.java" type="org.eclipse.jgit.lib.Repository">
+        <filter comment="Only implementors of Repository are affected. That should be allowed" id="336695337">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.lib.Repository"/>
+                <message_argument value="createAttributesNodeProvider()"/>
+            </message_arguments>
+        </filter>
+    </resource>
     <resource path="src/org/eclipse/jgit/transport/PushCertificate.java" type="org.eclipse.jgit.transport.PushCertificate">
         <filter comment="PushCertificate wasn't really usable in 4.0" id="338722907">
             <message_arguments>
@@ -35,6 +51,20 @@
             </message_arguments>
         </filter>
     </resource>
+    <resource path="src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java" type="org.eclipse.jgit.treewalk.WorkingTreeIterator">
+        <filter comment="attributes weren't really usable in earlier versions" id="338792546">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.treewalk.WorkingTreeIterator"/>
+                <message_argument value="getGlobalAttributesNode()"/>
+            </message_arguments>
+        </filter>
+        <filter comment="attributes weren't really usable in earlier versions" id="338792546">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.treewalk.WorkingTreeIterator"/>
+                <message_argument value="getInfoAttributesNode()"/>
+            </message_arguments>
+        </filter>
+    </resource>
     <resource path="src/org/eclipse/jgit/util/FileUtils.java" type="org.eclipse.jgit.util.FileUtils">
         <filter id="338792546">
             <message_arguments>
diff --git a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
index 195987d..45d6d2c 100644
--- a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
@@ -2,7 +2,7 @@
 org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled
 org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
 org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jgit.annotations.NonNull
-org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jgit.annotations.NonByDefault
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jgit.annotations.NonNullByDefault
 org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jgit.annotations.Nullable
 org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index b6899b2..2a953b5 100644
--- a/org.eclipse.jgit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/MANIFEST.MF
@@ -6,7 +6,8 @@
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
-Export-Package: org.eclipse.jgit.api;version="4.2.0";
+Export-Package: org.eclipse.jgit.annotations;version="4.2.0",
+ org.eclipse.jgit.api;version="4.2.0";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.diff,
@@ -20,9 +21,7 @@
    org.eclipse.jgit.submodule,
    org.eclipse.jgit.transport,
    org.eclipse.jgit.merge",
- org.eclipse.jgit.api.errors;version="4.2.0";
-  uses:="org.eclipse.jgit.lib,
-   org.eclipse.jgit.errors",
+ org.eclipse.jgit.api.errors;version="4.2.0";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors",
  org.eclipse.jgit.attributes;version="4.2.0",
  org.eclipse.jgit.blame;version="4.2.0";
   uses:="org.eclipse.jgit.lib,
@@ -47,8 +46,7 @@
    org.eclipse.jgit.internal.storage.pack,
    org.eclipse.jgit.transport,
    org.eclipse.jgit.dircache",
- org.eclipse.jgit.events;version="4.2.0";
-  uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.events;version="4.2.0";uses:="org.eclipse.jgit.lib",
  org.eclipse.jgit.fnmatch;version="4.2.0",
  org.eclipse.jgit.gitrepo;version="4.2.0";
   uses:="org.eclipse.jgit.api,
@@ -57,14 +55,11 @@
    org.xml.sax.helpers,
    org.xml.sax",
  org.eclipse.jgit.gitrepo.internal;version="4.2.0";x-internal:=true,
- org.eclipse.jgit.hooks;version="4.2.0";
-  uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.hooks;version="4.2.0";uses:="org.eclipse.jgit.lib",
  org.eclipse.jgit.ignore;version="4.2.0",
  org.eclipse.jgit.ignore.internal;version="4.2.0";x-friends:="org.eclipse.jgit.test",
  org.eclipse.jgit.internal;version="4.2.0";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
- org.eclipse.jgit.internal.storage.dfs;version="4.2.0";
-  x-friends:="org.eclipse.jgit.test,
-   org.eclipse.jgit.http.server",
+ org.eclipse.jgit.internal.storage.dfs;version="4.2.0";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.server",
  org.eclipse.jgit.internal.storage.file;version="4.2.0";
   x-friends:="org.eclipse.jgit.test,
    org.eclipse.jgit.junit,
@@ -96,31 +91,18 @@
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.merge",
- org.eclipse.jgit.patch;version="4.2.0";
-  uses:="org.eclipse.jgit.lib,
-   org.eclipse.jgit.diff",
- org.eclipse.jgit.revplot;version="4.2.0";
-  uses:="org.eclipse.jgit.lib,
-   org.eclipse.jgit.revwalk",
+ org.eclipse.jgit.patch;version="4.2.0";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff",
+ org.eclipse.jgit.revplot;version="4.2.0";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk",
  org.eclipse.jgit.revwalk;version="4.2.0";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.diff,
    org.eclipse.jgit.revwalk.filter",
- org.eclipse.jgit.revwalk.filter;version="4.2.0";
-  uses:="org.eclipse.jgit.revwalk,
-   org.eclipse.jgit.lib,
-   org.eclipse.jgit.util",
- org.eclipse.jgit.storage.file;version="4.2.0";
-  uses:="org.eclipse.jgit.lib,
-   org.eclipse.jgit.util",
- org.eclipse.jgit.storage.pack;version="4.2.0";
-  uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.submodule;version="4.2.0";
-  uses:="org.eclipse.jgit.lib,
-   org.eclipse.jgit.treewalk.filter,
-   org.eclipse.jgit.treewalk",
+ org.eclipse.jgit.revwalk.filter;version="4.2.0";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util",
+ org.eclipse.jgit.storage.file;version="4.2.0";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util",
+ org.eclipse.jgit.storage.pack;version="4.2.0";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.submodule;version="4.2.0";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk",
  org.eclipse.jgit.transport;version="4.2.0";
   uses:="org.eclipse.jgit.transport.resolver,
    org.eclipse.jgit.revwalk,
@@ -133,11 +115,8 @@
    org.eclipse.jgit.transport.http,
    org.eclipse.jgit.errors,
    org.eclipse.jgit.storage.pack",
- org.eclipse.jgit.transport.http;version="4.2.0";
-  uses:="javax.net.ssl",
- org.eclipse.jgit.transport.resolver;version="4.2.0";
-  uses:="org.eclipse.jgit.lib,
-   org.eclipse.jgit.transport",
+ org.eclipse.jgit.transport.http;version="4.2.0";uses:="javax.net.ssl",
+ org.eclipse.jgit.transport.resolver;version="4.2.0";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport",
  org.eclipse.jgit.treewalk;version="4.2.0";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
@@ -145,8 +124,7 @@
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.util,
    org.eclipse.jgit.dircache",
- org.eclipse.jgit.treewalk.filter;version="4.2.0";
-  uses:="org.eclipse.jgit.treewalk",
+ org.eclipse.jgit.treewalk.filter;version="4.2.0";uses:="org.eclipse.jgit.treewalk",
  org.eclipse.jgit.util;version="4.2.0";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.transport.http,
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 51e44fd..0e9b0b5 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -20,6 +20,7 @@
 atLeastOnePathIsRequired=At least one path is required.
 atLeastOnePatternIsRequired=At least one pattern is required.
 atLeastTwoFiltersNeeded=At least two filters needed.
+atomicPushNotSupported=Atomic push not supported.
 authenticationNotSupported=authentication not supported
 badBase64InputCharacterAt=Bad Base64 input character at {0} : {1} (decimal)
 badEntryDelimiter=Bad entry delimiter
@@ -45,6 +46,7 @@
 cannotBeRecursiveWhenTreesAreIncluded=TreeWalk shouldn't be recursive when tree objects are included.
 cannotChangeActionOnComment=Cannot change action on comment line in git-rebase-todo file, old action: {0}, new action: {1}.
 cannotChangeToComment=Cannot change a non-comment line to a comment line.
+cannotCheckoutFromUnbornBranch=Cannot checkout from unborn branch
 cannotCheckoutOursSwitchBranch=Checking out ours/theirs is only possible when checking out index, not when switching branches.
 cannotCombineSquashWithNoff=Cannot combine --squash with --no-ff.
 cannotCombineTreeFilterWithRevFilter=Cannot combine TreeFilter {0} with RevFilter {1}.
@@ -87,6 +89,7 @@
 cannotReadCommit=Cannot read commit {0}
 cannotReadFile=Cannot read file {0}
 cannotReadHEAD=cannot read HEAD: {0} {1}
+cannotReadIndex=The index file {0} exists but cannot be read
 cannotReadObject=Cannot read object
 cannotReadObjectsPath=Cannot read {0}/{1}: {2}
 cannotReadTree=Cannot read tree {0}
@@ -279,6 +282,8 @@
 fileIsTooBigForThisConvenienceMethod=File is too big for this convenience method ({0} bytes).
 fileIsTooLarge=File is too large: {0}
 fileModeNotSetForPath=FileMode not set for path {0}
+filterExecutionFailed=Execution of filter command ''{0}'' on file ''{1}'' failed
+filterExecutionFailedRc=Execution of filter command ''{0}'' on file ''{1}'' failed with return code ''{2}'', message on stderr: ''{3}''
 findingGarbage=Finding garbage
 flagIsDisposed={0} is disposed.
 flagNotFromThis={0} not from this.
@@ -344,6 +349,7 @@
 invalidReflogRevision=Invalid reflog revision: {0}
 invalidRefName=Invalid ref name: {0}
 invalidRemote=Invalid remote: {0}
+invalidRepositoryStateNoHead=Invalid repository --- cannot read HEAD
 invalidShallowObject=invalid shallow object {0}, expected commit
 invalidStageForPath=Invalid stage {0} for path {1}
 invalidTagOption=Invalid tag option: {0}
@@ -450,6 +456,7 @@
 packfileIsTruncatedNoParam=Packfile is truncated.
 packHandleIsStale=Pack file {0} handle is stale, removing it from pack list
 packHasUnresolvedDeltas=pack has unresolved deltas
+packInaccessible=Pack file {0} now inaccessible; removing it from pack list
 packingCancelledDuringObjectsWriting=Packing cancelled during objects writing
 packObjectCountMismatch=Pack object count mismatch: pack {0} index {1}: {2}
 packRefs=Pack refs
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/annotations/NonNull.java b/org.eclipse.jgit/src/org/eclipse/jgit/annotations/NonNull.java
new file mode 100644
index 0000000..08f81cb
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/annotations/NonNull.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2015, Andrey Loskutov <loskutov@gmx.de>
+ * 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.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * JGit's replacement for the {@code javax.annotation.Nonnull}.
+ * <p>
+ * Denotes that a local variable, parameter, field, method return value expected
+ * to be non {@code null}.
+ *
+ * @since 4.2
+ */
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({ FIELD, METHOD, PARAMETER, LOCAL_VARIABLE })
+public @interface NonNull {
+	// marker annotation with no members
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java b/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java
index 254920e..7b91567 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java
@@ -54,13 +54,46 @@
 import java.lang.annotation.Target;
 
 /**
- * JGit's replacement for the {@code javax.annotations.Nullable}.
+ * Marks types that can hold the value {@code null} at run time.
  * <p>
- * Denotes that a local variable, parameter, field, method return value can be
- * {@code null}.
+ * Unlike {@code org.eclipse.jdt.annotation.Nullable}, this has run-time
+ * retention, allowing the annotation to be recognized by
+ * <a href="https://github.com/google/guice/wiki/UseNullable">Guice</a>. Unlike
+ * {@code javax.annotation.Nullable}, this does not involve importing new classes
+ * to a standard (Java EE) package, so it can be deployed in an OSGi container
+ * without running into
+ * <a href="http://wiki.osgi.org/wiki/Split_Packages">split-package</a>
+ * <a href="https://gerrit-review.googlesource.com/50112">problems</a>.
+ * <p>
+ * You can use this annotation to qualify a type in a method signature or local
+ * variable declaration. The entity whose type has this annotation is allowed to
+ * hold the value {@code null} at run time. This allows annotation based null
+ * analysis to infer that
+ * <ul>
+ * <li>Binding a {@code null} value to the entity is legal.
+ * <li>Dereferencing the entity is unsafe and can trigger a
+ * {@code NullPointerException}.
+ * </ul>
+ * <p>
+ * To avoid a dependency on Java 8, this annotation does not use
+ * {@link Target @Target} {@code TYPE_USE}. That may change when JGit starts
+ * requiring Java 8.
+ * <p>
+ * <b>Warning:</b> Please do not use this annotation on arrays. Different
+ * annotation processors treat {@code @Nullable Object[]} differently: some
+ * treat it as an array of nullable objects, for consistency with versions of
+ * {@code Nullable} defined with {@code @Target} {@code TYPE_USE}, while others
+ * treat it as a nullable array of objects. JGit therefore avoids using this
+ * annotation on arrays altogether.
+ *
+ * @see <a href=
+ *      "http://types.cs.washington.edu/checker-framework/current/checker-framework-manual.html#faq-array-syntax-meaning">
+ *      The checker-framework manual</a>
+ *
+ * @since 4.2
  */
 @Documented
-@Retention(RetentionPolicy.CLASS)
+@Retention(RetentionPolicy.RUNTIME)
 @Target({ FIELD, METHOD, PARAMETER, LOCAL_VARIABLE })
 public @interface Nullable {
 	// marker annotation with no members
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
index de6c32a..67fb342 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
@@ -48,6 +48,7 @@
 import java.util.Collection;
 import java.util.LinkedList;
 
+import org.eclipse.jgit.api.errors.FilterFailedException;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.api.errors.JGitInternalException;
 import org.eclipse.jgit.api.errors.NoFilepatternException;
@@ -63,6 +64,7 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.treewalk.FileTreeIterator;
 import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
 import org.eclipse.jgit.treewalk.WorkingTreeIterator;
 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
 
@@ -139,6 +141,7 @@ public DirCache call() throws GitAPIException, NoFilepatternException {
 
 		try (ObjectInserter inserter = repo.newObjectInserter();
 				final TreeWalk tw = new TreeWalk(repo)) {
+			tw.setOperationType(OperationType.CHECKIN_OP);
 			dc = repo.lockDirCache();
 			DirCacheIterator c;
 
@@ -146,6 +149,7 @@ public DirCache call() throws GitAPIException, NoFilepatternException {
 			tw.addTree(new DirCacheBuildIterator(builder));
 			if (workingTreeIterator == null)
 				workingTreeIterator = new FileTreeIterator(repo);
+			workingTreeIterator.setDirCacheIterator(tw, 0);
 			tw.addTree(workingTreeIterator);
 			tw.setRecursive(true);
 			if (!addAll)
@@ -208,6 +212,9 @@ else if (!(path.equals(lastAddedFile))) {
 			builder.commit();
 			setCallable(false);
 		} catch (IOException e) {
+			Throwable cause = e.getCause();
+			if (cause != null && cause instanceof FilterFailedException)
+				throw (FilterFailedException) cause;
 			throw new JGitInternalException(
 					JGitText.get().exceptionCaughtDuringExecutionOfAddCommand, e);
 		} finally {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
index 8d85bfc..8743ea9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
@@ -222,6 +222,12 @@ public Ref call() throws GitAPIException, RefAlreadyExistsException,
 			}
 
 			Ref headRef = repo.getRef(Constants.HEAD);
+			if (headRef == null) {
+				// TODO Git CLI supports checkout from unborn branch, we should
+				// also allow this
+				throw new UnsupportedOperationException(
+						JGitText.get().cannotCheckoutFromUnbornBranch);
+			}
 			String shortHeadRef = getShortBranchName(headRef);
 			String refLogMessage = "checkout: moving from " + shortHeadRef; //$NON-NLS-1$
 			ObjectId branch;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
index 6174d48..9466dab 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
@@ -86,6 +86,7 @@
 import org.eclipse.jgit.treewalk.CanonicalTreeParser;
 import org.eclipse.jgit.treewalk.FileTreeIterator;
 import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
 import org.eclipse.jgit.util.ChangeIdUtil;
 
 /**
@@ -328,9 +329,12 @@ private DirCache createTemporaryIndex(ObjectId headId, DirCache index,
 		boolean emptyCommit = true;
 
 		try (TreeWalk treeWalk = new TreeWalk(repo)) {
+			treeWalk.setOperationType(OperationType.CHECKIN_OP);
 			int dcIdx = treeWalk
 					.addTree(new DirCacheBuildIterator(existingBuilder));
-			int fIdx = treeWalk.addTree(new FileTreeIterator(repo));
+			FileTreeIterator fti = new FileTreeIterator(repo);
+			fti.setDirCacheIterator(treeWalk, 0);
+			int fIdx = treeWalk.addTree(fti);
 			int hIdx = -1;
 			if (headId != null)
 				hIdx = treeWalk.addTree(rw.parseTree(headId));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
index addca4c..2cd5f59 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
@@ -713,8 +713,48 @@ public DescribeCommand describe() {
 	}
 
 	/**
-	 * @return the git repository this class is interacting with; see {@link
-	 *         #close()} for notes on closing this repository.
+	 * Return a command used to list the available remotes.
+	 *
+	 * @return a {@link RemoteListCommand}
+	 * @since 4.2
+	 */
+	public RemoteListCommand remoteList() {
+		return new RemoteListCommand(repo);
+	}
+
+	/**
+	 * Return a command used to add a new remote.
+	 *
+	 * @return a {@link RemoteAddCommand}
+	 * @since 4.2
+	 */
+	public RemoteAddCommand remoteAdd() {
+		return new RemoteAddCommand(repo);
+	}
+
+	/**
+	 * Return a command used to remove an existing remote.
+	 *
+	 * @return a {@link RemoteRemoveCommand}
+	 * @since 4.2
+	 */
+	public RemoteRemoveCommand remoteRemove() {
+		return new RemoteRemoveCommand(repo);
+	}
+
+	/**
+	 * Return a command used to change the URL of an existing remote.
+	 *
+	 * @return a {@link RemoteSetUrlCommand}
+	 * @since 4.2
+	 */
+	public RemoteSetUrlCommand remoteSetUrl() {
+		return new RemoteSetUrlCommand(repo);
+	}
+
+	/**
+	 * @return the git repository this class is interacting with; see
+	 *         {@link #close()} for notes on closing this repository.
 	 */
 	public Repository getRepository() {
 		return repo;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
index 227e322..f5b82bd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
@@ -89,9 +89,8 @@ public class PushCommand extends
 	private String receivePack = RemoteConfig.DEFAULT_RECEIVE_PACK;
 
 	private boolean dryRun;
-
+	private boolean atomic;
 	private boolean force;
-
 	private boolean thin = Transport.DEFAULT_PUSH_THIN;
 
 	private OutputStream out;
@@ -145,6 +144,7 @@ public Iterable<PushResult> call() throws GitAPIException,
 			transports = Transport.openAll(repo, remote, Transport.Operation.PUSH);
 			for (final Transport transport : transports) {
 				transport.setPushThin(thin);
+				transport.setPushAtomic(atomic);
 				if (receivePack != null)
 					transport.setOptionReceivePack(receivePack);
 				transport.setDryRun(dryRun);
@@ -397,6 +397,29 @@ public PushCommand setThin(boolean thin) {
 	}
 
 	/**
+	 * @return true if all-or-nothing behavior is requested.
+	 * @since 4.2
+	 */
+	public boolean isAtomic() {
+		return atomic;
+	}
+
+	/**
+	 * Requests atomic push (all references updated, or no updates).
+	 *
+	 * Default setting is false.
+	 *
+	 * @param atomic
+	 * @return {@code this}
+	 * @since 4.2
+	 */
+	public PushCommand setAtomic(boolean atomic) {
+		checkCallable();
+		this.atomic = atomic;
+		return this;
+	}
+
+	/**
 	 * @return the force preference for push operation
 	 */
 	public boolean isForce() {
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 ff29008..8582bbb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
@@ -668,12 +668,13 @@ private void writeCurrentCommit(RevCommit commit) throws IOException {
 	}
 
 	private void writeRewrittenHashes() throws RevisionSyntaxException,
-			IOException {
+			IOException, RefNotFoundException {
 		File currentCommitFile = rebaseState.getFile(CURRENT_COMMIT);
 		if (!currentCommitFile.exists())
 			return;
 
-		String head = repo.resolve(Constants.HEAD).getName();
+		ObjectId headId = getHead().getObjectId();
+		String head = headId.getName();
 		String currentCommits = rebaseState.readFile(CURRENT_COMMIT);
 		for (String current : currentCommits.split("\n")) //$NON-NLS-1$
 			RebaseState
@@ -743,8 +744,8 @@ private RevCommit doSquashFixup(boolean isSquash, RevCommit commitToPick,
 
 	private void resetSoftToParent() throws IOException,
 			GitAPIException, CheckoutConflictException {
-		Ref orig_head = repo.getRef(Constants.ORIG_HEAD);
-		ObjectId orig_headId = orig_head.getObjectId();
+		Ref ref = repo.getRef(Constants.ORIG_HEAD);
+		ObjectId orig_head = ref == null ? null : ref.getObjectId();
 		try {
 			// we have already commited the cherry-picked commit.
 			// what we need is to have changes introduced by this
@@ -755,7 +756,7 @@ private void resetSoftToParent() throws IOException,
 		} finally {
 			// set ORIG_HEAD back to where we started because soft
 			// reset moved it
-			repo.writeOrigHead(orig_headId);
+			repo.writeOrigHead(orig_head);
 		}
 	}
 
@@ -980,6 +981,9 @@ private PersonIdent parseAuthor() throws IOException {
 		try {
 			raw = IO.readFully(authorScriptFile);
 		} catch (FileNotFoundException notFound) {
+			if (authorScriptFile.exists()) {
+				throw notFound;
+			}
 			return null;
 		}
 		return parseAuthor(raw);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteAddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteAddCommand.java
new file mode 100644
index 0000000..6795669
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteAddCommand.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.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 java.io.IOException;
+import java.net.URISyntaxException;
+
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.URIish;
+
+/**
+ * Used to add a new remote.
+ *
+ * This class has setters for all supported options and arguments of this
+ * command and a {@link #call()} method to finally execute the command.
+ *
+ * @see <a href=
+ *      "http://www.kernel.org/pub/software/scm/git/docs/git-remote.html" > Git
+ *      documentation about Remote</a>
+ *
+ * @since 4.2
+ */
+public class RemoteAddCommand extends GitCommand<RemoteConfig> {
+
+	private String name;
+
+	private URIish uri;
+
+	/**
+	 * @param repo
+	 */
+	protected RemoteAddCommand(Repository repo) {
+		super(repo);
+	}
+
+	/**
+	 * The name of the remote to add.
+	 *
+	 * @param name
+	 *            a remote name
+	 */
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	/**
+	 * The URL of the repository for the new remote.
+	 *
+	 * @param uri
+	 *            an URL for the remote
+	 */
+	public void setUri(URIish uri) {
+		this.uri = uri;
+	}
+
+	/**
+	 * Executes the {@code remote add} command with all the options and
+	 * parameters collected by the setter methods of this class.
+	 *
+	 * @return the {@link RemoteConfig} object of the added remote
+	 */
+	@Override
+	public RemoteConfig call() throws GitAPIException {
+		checkCallable();
+
+		try {
+			StoredConfig config = repo.getConfig();
+			RemoteConfig remote = new RemoteConfig(config, name);
+
+			RefSpec refSpec = new RefSpec();
+			refSpec = refSpec.setForceUpdate(true);
+			refSpec = refSpec.setSourceDestination(Constants.R_HEADS + "*", //$NON-NLS-1$
+					Constants.R_REMOTES + name + "/*"); //$NON-NLS-1$
+			remote.addFetchRefSpec(refSpec);
+
+			remote.addURI(uri);
+
+			remote.update(config);
+			config.save();
+			return remote;
+		} catch (IOException | URISyntaxException e) {
+			throw new JGitInternalException(e.getMessage(), e);
+		}
+
+	}
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteListCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteListCommand.java
new file mode 100644
index 0000000..f778eaa
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteListCommand.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.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 java.net.URISyntaxException;
+import java.util.List;
+
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.RemoteConfig;
+
+/**
+ * Used to obtain the list of remotes.
+ *
+ * This class has setters for all supported options and arguments of this
+ * command and a {@link #call()} method to finally execute the command.
+ *
+ * @see <a href=
+ *      "http://www.kernel.org/pub/software/scm/git/docs/git-remote.html" > Git
+ *      documentation about Remote</a>
+ *
+ * @since 4.2
+ */
+public class RemoteListCommand extends GitCommand<List<RemoteConfig>> {
+
+	/**
+	 * @param repo
+	 */
+	protected RemoteListCommand(Repository repo) {
+		super(repo);
+	}
+
+	/**
+	 * Executes the {@code remote} command with all the options and parameters
+	 * collected by the setter methods of this class.
+	 *
+	 * @return a list of {@link RemoteConfig} objects.
+	 */
+	@Override
+	public List<RemoteConfig> call() throws GitAPIException {
+		checkCallable();
+
+		try {
+			return RemoteConfig.getAllRemoteConfigs(repo.getConfig());
+		} catch (URISyntaxException e) {
+			throw new JGitInternalException(e.getMessage(), e);
+		}
+	}
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java
new file mode 100644
index 0000000..5782bf6
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.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 java.io.IOException;
+import java.net.URISyntaxException;
+
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.transport.RemoteConfig;
+
+/**
+ * Used to remove an existing remote.
+ *
+ * This class has setters for all supported options and arguments of this
+ * command and a {@link #call()} method to finally execute the command.
+ *
+ * @see <a href=
+ *      "http://www.kernel.org/pub/software/scm/git/docs/git-remote.html" > Git
+ *      documentation about Remote</a>
+ *
+ * @since 4.2
+ */
+public class RemoteRemoveCommand extends GitCommand<RemoteConfig> {
+
+	private String name;
+
+	/**
+	 * @param repo
+	 */
+	protected RemoteRemoveCommand(Repository repo) {
+		super(repo);
+	}
+
+	/**
+	 * The name of the remote to remove.
+	 *
+	 * @param name
+	 *            a remote name
+	 */
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	/**
+	 * Executes the {@code remote} command with all the options and parameters
+	 * collected by the setter methods of this class.
+	 *
+	 * @return the {@link RemoteConfig} object of the removed remote
+	 */
+	@Override
+	public RemoteConfig call() throws GitAPIException {
+		checkCallable();
+
+		try {
+			StoredConfig config = repo.getConfig();
+			RemoteConfig remote = new RemoteConfig(config, name);
+			config.unsetSection(ConfigConstants.CONFIG_KEY_REMOTE, name);
+			config.save();
+			return remote;
+		} catch (IOException | URISyntaxException e) {
+			throw new JGitInternalException(e.getMessage(), e);
+		}
+
+	}
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java
new file mode 100644
index 0000000..6bd2ac7
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.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 java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.List;
+
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.URIish;
+
+/**
+ * Used to to change the URL of a remote.
+ *
+ * This class has setters for all supported options and arguments of this
+ * command and a {@link #call()} method to finally execute the command.
+ *
+ * @see <a href=
+ *      "http://www.kernel.org/pub/software/scm/git/docs/git-remote.html" > Git
+ *      documentation about Remote</a>
+ *
+ * @since 4.2
+ */
+public class RemoteSetUrlCommand extends GitCommand<RemoteConfig> {
+
+	private String name;
+
+	private URIish uri;
+
+	private boolean push;
+
+	/**
+	 * @param repo
+	 */
+	protected RemoteSetUrlCommand(Repository repo) {
+		super(repo);
+	}
+
+	/**
+	 * The name of the remote to change the URL for.
+	 *
+	 * @param name
+	 *            a remote name
+	 */
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	/**
+	 * The new URL for the remote.
+	 *
+	 * @param uri
+	 *            an URL for the remote
+	 */
+	public void setUri(URIish uri) {
+		this.uri = uri;
+	}
+
+	/**
+	 * Whether to change the push URL of the remote instead of the fetch URL.
+	 *
+	 * @param push
+	 *            <code>true</code> to set the push url, <code>false</code> to
+	 *            set the fetch url
+	 */
+	public void setPush(boolean push) {
+		this.push = push;
+	}
+
+	/**
+	 * Executes the {@code remote} command with all the options and parameters
+	 * collected by the setter methods of this class.
+	 *
+	 * @return the {@link RemoteConfig} object of the modified remote
+	 */
+	@Override
+	public RemoteConfig call() throws GitAPIException {
+		checkCallable();
+
+		try {
+			StoredConfig config = repo.getConfig();
+			RemoteConfig remote = new RemoteConfig(config, name);
+			if (push) {
+				List<URIish> uris = remote.getPushURIs();
+				if (uris.size() > 1) {
+					throw new JGitInternalException(
+							"remote.newtest.pushurl has multiple values"); //$NON-NLS-1$
+				} else if (uris.size() == 1) {
+					remote.removePushURI(uris.get(0));
+				}
+				remote.addPushURI(uri);
+			} else {
+				List<URIish> uris = remote.getURIs();
+				if (uris.size() > 1) {
+					throw new JGitInternalException(
+							"remote.newtest.url has multiple values"); //$NON-NLS-1$
+				} else if (uris.size() == 1) {
+					remote.removeURI(uris.get(0));
+				}
+				remote.addURI(uri);
+			}
+
+			remote.update(config);
+			config.save();
+			return remote;
+		} catch (IOException | URISyntaxException e) {
+			throw new JGitInternalException(e.getMessage(), e);
+		}
+	}
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java
index 607253b..0731dd4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java
@@ -51,6 +51,7 @@
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.api.errors.InvalidRefNameException;
 import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.api.errors.NoHeadException;
 import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
 import org.eclipse.jgit.api.errors.RefNotFoundException;
 import org.eclipse.jgit.internal.JGitText;
@@ -121,6 +122,10 @@ public Ref call() throws GitAPIException, RefNotFoundException, InvalidRefNameEx
 				fullOldName = ref.getName();
 			} else {
 				fullOldName = repo.getFullBranch();
+				if (fullOldName == null) {
+					throw new NoHeadException(
+							JGitText.get().invalidRepositoryStateNoHead);
+				}
 				if (ObjectId.isId(fullOldName))
 					throw new DetachedHeadException();
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/FilterFailedException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/FilterFailedException.java
new file mode 100644
index 0000000..fbc30ef
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/FilterFailedException.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2015, Christian Halstrick <christian.halstrick@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.api.errors;
+
+import java.text.MessageFormat;
+
+import org.eclipse.jgit.internal.JGitText;
+
+/**
+ * Exception thrown when the execution of a filter command failed
+ *
+ * @since 4.2
+ */
+public class FilterFailedException extends GitAPIException {
+	private static final long serialVersionUID = 1L;
+
+	private String filterCommand;
+
+	private String path;
+
+	private byte[] stdout;
+
+	private String stderr;
+
+	private int rc;
+
+	/**
+	 * Thrown if during execution of filter command an exception occurred
+	 *
+	 * @param cause
+	 *            the exception
+	 * @param filterCommand
+	 *            the command which failed
+	 * @param path
+	 *            the path processed by the filter
+	 */
+	public FilterFailedException(Exception cause, String filterCommand,
+			String path) {
+		super(MessageFormat.format(JGitText.get().filterExecutionFailed,
+				filterCommand, path), cause);
+		this.filterCommand = filterCommand;
+		this.path = path;
+	}
+
+	/**
+	 * Thrown if a filter command returns a non-zero return code
+	 *
+	 * @param rc
+	 *            the return code
+	 * @param filterCommand
+	 *            the command which failed
+	 * @param path
+	 *            the path processed by the filter
+	 * @param stdout
+	 *            the output the filter generated so far. This should be limited
+	 *            to reasonable size.
+	 * @param stderr
+	 *            the stderr output of the filter
+	 */
+	@SuppressWarnings("boxing")
+	public FilterFailedException(int rc, String filterCommand, String path,
+			byte[] stdout, String stderr) {
+		super(MessageFormat.format(JGitText.get().filterExecutionFailedRc,
+				filterCommand, path, rc, stderr));
+		this.rc = rc;
+		this.filterCommand = filterCommand;
+		this.path = path;
+		this.stdout = stdout;
+		this.stderr = stderr;
+	}
+
+	/**
+	 * @return the filterCommand
+	 */
+	public String getFilterCommand() {
+		return filterCommand;
+	}
+
+	/**
+	 * @return the path of the file processed by the filter command
+	 */
+	public String getPath() {
+		return path;
+	}
+
+	/**
+	 * @return the output generated by the filter command. Might be truncated to
+	 *         limit memory consumption.
+	 */
+	public byte[] getOutput() {
+		return stdout;
+	}
+
+	/**
+	 * @return the error output returned by the filter command
+	 */
+	public String getError() {
+		return stderr;
+	}
+
+	/**
+	 * @return the return code returned by the filter command
+	 */
+	public int getReturnCode() {
+		return rc;
+	}
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java
index d3ce685..905ad76 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java
@@ -50,8 +50,10 @@
  * <li>Set - represented by {@link State#SET}</li>
  * <li>Unset - represented by {@link State#UNSET}</li>
  * <li>Set to a value - represented by {@link State#CUSTOM}</li>
- * <li>Unspecified - <code>null</code> is used instead of an instance of this
- * class</li>
+ * <li>Unspecified - used to revert an attribute . This is crucial in order to
+ * mark an attribute as unspecified in the attributes map and thus preventing
+ * following (with lower priority) nodes from setting the attribute to a value
+ * at all</li>
  * </ul>
  * </p>
  *
@@ -61,6 +63,7 @@ public final class Attribute {
 
 	/**
 	 * The attribute value state
+	 * see also https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html
 	 */
 	public static enum State {
 		/** the attribute is set */
@@ -69,6 +72,13 @@ public static enum State {
 		/** the attribute is unset */
 		UNSET,
 
+		/**
+		 * the attribute appears as if it would not be defined at all
+		 *
+		 * @since 4.2
+		 */
+		UNSPECIFIED,
+
 		/** the attribute is set to a custom value */
 		CUSTOM
 	}
@@ -176,6 +186,8 @@ public String toString() {
 			return key;
 		case UNSET:
 			return "-" + key; //$NON-NLS-1$
+		case UNSPECIFIED:
+			return "!" + key; //$NON-NLS-1$
 		case CUSTOM:
 		default:
 			return key + "=" + value; //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java
new file mode 100644
index 0000000..0810e31
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com>
+ *
+ * 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.attributes;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.eclipse.jgit.attributes.Attribute.State;
+
+/**
+ * Represents a set of attributes for a path
+ * <p>
+ *
+ * @since 4.2
+ */
+public final class Attributes {
+	private final Map<String, Attribute> map = new LinkedHashMap<>();
+
+	/**
+	 * Creates a new instance
+	 *
+	 * @param attributes
+	 */
+	public Attributes(Attribute... attributes) {
+		if (attributes != null) {
+			for (Attribute a : attributes) {
+				put(a);
+			}
+		}
+	}
+
+	/**
+	 * @return true if the set does not contain any attributes
+	 */
+	public boolean isEmpty() {
+		return map.isEmpty();
+	}
+
+	/**
+	 * @param key
+	 * @return the attribute or null
+	 */
+	public Attribute get(String key) {
+		return map.get(key);
+	}
+
+	/**
+	 * @return all attributes
+	 */
+	public Collection<Attribute> getAll() {
+		return new ArrayList<>(map.values());
+	}
+
+	/**
+	 * @param a
+	 */
+	public void put(Attribute a) {
+		map.put(a.getKey(), a);
+	}
+
+	/**
+	 * @param key
+	 */
+	public void remove(String key) {
+		map.remove(key);
+	}
+
+	/**
+	 * @param key
+	 * @return true if the {@link Attributes} contains this key
+	 */
+	public boolean containsKey(String key) {
+		return map.containsKey(key);
+	}
+
+	/**
+	 * Returns the state.
+	 *
+	 * @param key
+	 *
+	 * @return the state (never returns <code>null</code>)
+	 */
+	public Attribute.State getState(String key) {
+		Attribute a = map.get(key);
+		return a != null ? a.getState() : Attribute.State.UNSPECIFIED;
+	}
+
+	/**
+	 * @param key
+	 * @return true if the key is {@link State#SET}, false in all other cases
+	 */
+	public boolean isSet(String key) {
+		return (getState(key) == State.SET);
+	}
+
+	/**
+	 * @param key
+	 * @return true if the key is {@link State#UNSET}, false in all other cases
+	 */
+	public boolean isUnset(String key) {
+		return (getState(key) == State.UNSET);
+	}
+
+	/**
+	 * @param key
+	 * @return true if the key is {@link State#UNSPECIFIED}, false in all other
+	 *         cases
+	 */
+	public boolean isUnspecified(String key) {
+		return (getState(key) == State.UNSPECIFIED);
+	}
+
+	/**
+	 * @param key
+	 * @return true if the key is {@link State#CUSTOM}, false in all other cases
+	 *         see {@link #getValue(String)} for the value of the key
+	 */
+	public boolean isCustom(String key) {
+		return (getState(key) == State.CUSTOM);
+	}
+
+	/**
+	 * @param key
+	 * @return the attribute value (may be <code>null</code>)
+	 */
+	public String getValue(String key) {
+		Attribute a = map.get(key);
+		return a != null ? a.getValue() : null;
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder buf = new StringBuilder();
+		buf.append(getClass().getSimpleName());
+		buf.append("["); //$NON-NLS-1$
+		buf.append(" "); //$NON-NLS-1$
+		for (Attribute a : map.values()) {
+			buf.append(a.toString());
+			buf.append(" "); //$NON-NLS-1$
+		}
+		buf.append("]"); //$NON-NLS-1$
+		return buf.toString();
+	}
+
+	@Override
+	public int hashCode() {
+		return map.hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (!(obj instanceof Attributes))
+			return false;
+		Attributes other = (Attributes) obj;
+		return this.map.equals(other.map);
+	}
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java
index 70f56ff..5c0aba2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java
@@ -50,7 +50,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.ListIterator;
-import java.util.Map;
 
 import org.eclipse.jgit.lib.Constants;
 
@@ -134,11 +133,12 @@ public List<AttributesRule> getRules() {
 	 *            true if the target item is a directory.
 	 * @param attributes
 	 *            Map that will hold the attributes matching this entry path. If
-	 *            it is not empty, this method will NOT override any
-	 *            existing entry.
+	 *            it is not empty, this method will NOT override any existing
+	 *            entry.
+	 * @since 4.2
 	 */
-	public void getAttributes(String entryPath, boolean isDirectory,
-			Map<String, Attribute> attributes) {
+	public void getAttributes(String entryPath,
+			boolean isDirectory, Attributes attributes) {
 		// Parse rules in the reverse order that they were read since the last
 		// entry should be used
 		ListIterator<AttributesRule> ruleIterator = rules.listIterator(rules
@@ -153,7 +153,7 @@ public void getAttributes(String entryPath, boolean isDirectory,
 				while (attributeIte.hasPrevious()) {
 					Attribute attr = attributeIte.previous();
 					if (!attributes.containsKey(attr.getKey()))
-						attributes.put(attr.getKey(), attr);
+						attributes.put(attr);
 				}
 			}
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java
new file mode 100644
index 0000000..6f2ebad
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2014, Arthur Daussy <arthur.daussy@obeo.fr>
+ * 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.attributes;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.CoreConfig;
+
+/**
+ * An interface used to retrieve the global and info {@link AttributesNode}s.
+ *
+ * @since 4.2
+ *
+ */
+public interface AttributesNodeProvider {
+
+	/**
+	 * Retrieve the {@link AttributesNode} that holds the information located
+	 * in $GIT_DIR/info/attributes file.
+	 *
+	 * @return the {@link AttributesNode} that holds the information located in
+	 *         $GIT_DIR/info/attributes file.
+	 * @throws IOException
+	 *             if an error is raised while parsing the attributes file
+	 */
+	public AttributesNode getInfoAttributesNode() throws IOException;
+
+	/**
+	 * Retrieve the {@link AttributesNode} that holds the information located
+	 * in the global gitattributes file.
+	 *
+	 * @return the {@link AttributesNode} that holds the information located in
+	 *         the global gitattributes file.
+	 * @throws IOException
+	 *             IOException if an error is raised while parsing the
+	 *             attributes file
+	 * @see CoreConfig#getAttributesFile()
+	 */
+	public AttributesNode getGlobalAttributesNode() throws IOException;
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java
new file mode 100644
index 0000000..1037f69
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015, Christian Halstrick <christian.halstrick@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.attributes;
+
+/**
+ * Interface for classes which provide git attributes
+ *
+ * @since 4.2
+ */
+public interface AttributesProvider {
+	/**
+	 * @return the currently active attributes
+	 */
+	public Attributes getAttributes();
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
index bcac14b..35d18c4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
@@ -84,6 +84,13 @@ private static List<Attribute> parseAttributes(String attributesLine) {
 				continue;
 			}
 
+			if (attribute.startsWith("!")) {//$NON-NLS-1$
+				if (attribute.length() > 1)
+					result.add(new Attribute(attribute.substring(1),
+							State.UNSPECIFIED));
+				continue;
+			}
+
 			final int equalsIndex = attribute.indexOf("="); //$NON-NLS-1$
 			if (equalsIndex == -1)
 				result.add(new Attribute(attribute, State.SET));
@@ -200,4 +207,16 @@ public boolean isMatch(String relativeTarget, boolean isDirectory) {
 		boolean match = matcher.matches(relativeTarget, isDirectory);
 		return match;
 	}
+
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append(pattern);
+		for (Attribute a : attributes) {
+			sb.append(" "); //$NON-NLS-1$
+			sb.append(a);
+		}
+		return sb.toString();
+
+	}
 }
\ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java
index 0dc4b05..444ab1c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java
@@ -132,7 +132,11 @@ private static class ObjectReaderSource extends ContentSource {
 
 		@Override
 		public long size(String path, ObjectId id) throws IOException {
-			return reader.getObjectSize(id, Constants.OBJ_BLOB);
+			try {
+				return reader.getObjectSize(id, Constants.OBJ_BLOB);
+			} catch (MissingObjectException ignore) {
+				return 0;
+			}
 		}
 
 		@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
index 6d9a32d..fa03395 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
@@ -63,6 +63,7 @@
 import java.util.List;
 
 import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.IndexReadException;
 import org.eclipse.jgit.errors.LockFailedException;
 import org.eclipse.jgit.errors.UnmergedPathException;
 import org.eclipse.jgit.events.IndexChangedEvent;
@@ -70,12 +71,15 @@
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.file.FileSnapshot;
 import org.eclipse.jgit.internal.storage.file.LockFile;
+import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.treewalk.FileTreeIterator;
 import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.IO;
@@ -145,6 +149,28 @@ public static DirCache newInCore() {
 	}
 
 	/**
+	 * Create a new in memory index read from the contents of a tree.
+	 *
+	 * @param reader
+	 *            reader to access the tree objects from a repository.
+	 * @param treeId
+	 *            tree to read. Must identify a tree, not a tree-ish.
+	 * @return a new cache which has no backing store file, but contains the
+	 *         contents of {@code treeId}.
+	 * @throws IOException
+	 *             one or more trees not available from the ObjectReader.
+	 * @since 4.2
+	 */
+	public static DirCache read(ObjectReader reader, AnyObjectId treeId)
+			throws IOException {
+		DirCache d = newInCore();
+		DirCacheBuilder b = d.builder();
+		b.addTree(null, DirCacheEntry.STAGE_0, reader, treeId);
+		b.finish();
+		return d;
+	}
+
+	/**
 	 * Create a new in-core index representation and read an index from disk.
 	 * <p>
 	 * The new index will be read before it is returned to the caller. Read
@@ -417,6 +443,12 @@ else if (snapshot == null || snapshot.isModified(liveFile)) {
 					}
 				}
 			} catch (FileNotFoundException fnfe) {
+				if (liveFile.exists()) {
+					// Panic: the index file exists but we can't read it
+					throw new IndexReadException(
+							MessageFormat.format(JGitText.get().cannotReadIndex,
+									liveFile.getAbsolutePath(), fnfe));
+				}
 				// Someone must have deleted it between our exists test
 				// and actually opening the path. That's fine, its empty.
 				//
@@ -869,8 +901,8 @@ public DirCacheEntry getEntry(final String path) {
 	 */
 	public DirCacheEntry[] getEntriesWithin(String path) {
 		if (path.length() == 0) {
-			final DirCacheEntry[] r = new DirCacheEntry[sortedEntries.length];
-			System.arraycopy(sortedEntries, 0, r, 0, sortedEntries.length);
+			DirCacheEntry[] r = new DirCacheEntry[entryCnt];
+			System.arraycopy(sortedEntries, 0, r, 0, entryCnt);
 			return r;
 		}
 		if (!path.endsWith("/")) //$NON-NLS-1$
@@ -963,6 +995,7 @@ private void registerIndexChangedListener(IndexChangedListener listener) {
 	private void updateSmudgedEntries() throws IOException {
 		List<String> paths = new ArrayList<String>(128);
 		try (TreeWalk walk = new TreeWalk(repository)) {
+			walk.setOperationType(OperationType.CHECKIN_OP);
 			for (int i = 0; i < entryCnt; i++)
 				if (sortedEntries[i].isSmudged())
 					paths.add(sortedEntries[i].getPathString());
@@ -974,6 +1007,7 @@ private void updateSmudgedEntries() throws IOException {
 			FileTreeIterator fIter = new FileTreeIterator(repository);
 			walk.addTree(iIter);
 			walk.addTree(fIter);
+			fIter.setDirCacheIterator(walk, 0);
 			walk.setRecursive(true);
 			while (walk.next()) {
 				iIter = walk.getTree(0, DirCacheIterator.class);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java
index 6c7a70c..cfebe2d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java
@@ -44,6 +44,9 @@
 
 package org.eclipse.jgit.dircache;
 
+import static org.eclipse.jgit.lib.FileMode.TYPE_MASK;
+import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
+
 import java.io.IOException;
 import java.text.MessageFormat;
 import java.util.Arrays;
@@ -51,9 +54,7 @@
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.ObjectReader;
-import org.eclipse.jgit.treewalk.AbstractTreeIterator;
 import org.eclipse.jgit.treewalk.CanonicalTreeParser;
-import org.eclipse.jgit.treewalk.TreeWalk;
 
 /**
  * Updates a {@link DirCache} by adding individual {@link DirCacheEntry}s.
@@ -102,8 +103,9 @@ protected DirCacheBuilder(final DirCache dc, final int ecnt) {
 	 */
 	public void add(final DirCacheEntry newEntry) {
 		if (newEntry.getRawMode() == 0)
-			throw new IllegalArgumentException(MessageFormat.format(JGitText.get().fileModeNotSetForPath
-					, newEntry.getPathString()));
+			throw new IllegalArgumentException(MessageFormat.format(
+					JGitText.get().fileModeNotSetForPath,
+					newEntry.getPathString()));
 		beforeAdd(newEntry);
 		fastAdd(newEntry);
 	}
@@ -162,27 +164,56 @@ public void keep(final int pos, int cnt) {
 	 * @throws IOException
 	 *             a tree cannot be read to iterate through its entries.
 	 */
-	public void addTree(final byte[] pathPrefix, final int stage,
-			final ObjectReader reader, final AnyObjectId tree) throws IOException {
-		final TreeWalk tw = new TreeWalk(reader);
-		tw.addTree(new CanonicalTreeParser(pathPrefix, reader, tree
-				.toObjectId()));
-		tw.setRecursive(true);
-		if (tw.next()) {
-			final DirCacheEntry newEntry = toEntry(stage, tw);
-			beforeAdd(newEntry);
-			fastAdd(newEntry);
-			while (tw.next())
-				fastAdd(toEntry(stage, tw));
+	public void addTree(byte[] pathPrefix, int stage, ObjectReader reader,
+			AnyObjectId tree) throws IOException {
+		CanonicalTreeParser p = createTreeParser(pathPrefix, reader, tree);
+		while (!p.eof()) {
+			if (isTree(p)) {
+				p = enterTree(p, reader);
+				continue;
+			}
+
+			DirCacheEntry first = toEntry(stage, p);
+			beforeAdd(first);
+			fastAdd(first);
+			p = p.next();
+			break;
+		}
+
+		// Rest of tree entries are correctly sorted; use fastAdd().
+		while (!p.eof()) {
+			if (isTree(p)) {
+				p = enterTree(p, reader);
+			} else {
+				fastAdd(toEntry(stage, p));
+				p = p.next();
+			}
 		}
 	}
 
-	private DirCacheEntry toEntry(final int stage, final TreeWalk tw) {
-		final DirCacheEntry e = new DirCacheEntry(tw.getRawPath(), stage);
-		final AbstractTreeIterator i;
+	private static CanonicalTreeParser createTreeParser(byte[] pathPrefix,
+			ObjectReader reader, AnyObjectId tree) throws IOException {
+		return new CanonicalTreeParser(pathPrefix, reader, tree);
+	}
 
-		i = tw.getTree(0, AbstractTreeIterator.class);
-		e.setFileMode(tw.getFileMode(0));
+	private static boolean isTree(CanonicalTreeParser p) {
+		return (p.getEntryRawMode() & TYPE_MASK) == TYPE_TREE;
+	}
+
+	private static CanonicalTreeParser enterTree(CanonicalTreeParser p,
+			ObjectReader reader) throws IOException {
+		p = p.createSubtreeIterator(reader);
+		return p.eof() ? p.next() : p;
+	}
+
+	private static DirCacheEntry toEntry(int stage, CanonicalTreeParser i) {
+		byte[] buf = i.getEntryPathBuffer();
+		int len = i.getEntryPathLength();
+		byte[] path = new byte[len];
+		System.arraycopy(buf, 0, path, 0, len);
+
+		DirCacheEntry e = new DirCacheEntry(path, stage);
+		e.setFileMode(i.getEntryRawMode());
 		e.setObjectIdFromRaw(i.idBuffer(), i.idOffset());
 		return e;
 	}
@@ -242,9 +273,9 @@ private void resort() {
 		sorted = true;
 	}
 
-	private static IllegalStateException bad(final DirCacheEntry a,
-			final String msg) {
-		return new IllegalStateException(msg + ": " + a.getStage() + " " //$NON-NLS-1$ //$NON-NLS-2$
-				+ a.getPathString());
+	private static IllegalStateException bad(DirCacheEntry a, String msg) {
+		return new IllegalStateException(String.format(
+				"%s: %d %s", //$NON-NLS-1$
+				msg, Integer.valueOf(a.getStage()), a.getPathString()));
 	}
 }
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 0036ab5..4eb6881 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -52,15 +52,18 @@
 import java.util.List;
 import java.util.Map;
 
+import org.eclipse.jgit.api.errors.FilterFailedException;
 import org.eclipse.jgit.errors.CheckoutConflictException;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.IndexWriteException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
 import org.eclipse.jgit.lib.CoreConfig.SymLinks;
 import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectChecker;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectLoader;
@@ -76,6 +79,7 @@
 import org.eclipse.jgit.treewalk.WorkingTreeOptions;
 import org.eclipse.jgit.treewalk.filter.PathFilter;
 import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FS.ExecutionResult;
 import org.eclipse.jgit.util.FileUtils;
 import org.eclipse.jgit.util.RawParseUtils;
 import org.eclipse.jgit.util.SystemReader;
@@ -85,9 +89,10 @@
  * This class handles checking out one or two trees merging with the index.
  */
 public class DirCacheCheckout {
+	private static final int MAX_EXCEPTION_TEXT_SIZE = 10 * 1024;
 	private Repository repo;
 
-	private HashMap<String, ObjectId> updated = new HashMap<String, ObjectId>();
+	private HashMap<String, String> updated = new HashMap<String, String>();
 
 	private ArrayList<String> conflicts = new ArrayList<String>();
 
@@ -112,9 +117,9 @@ public class DirCacheCheckout {
 	private boolean emptyDirCache;
 
 	/**
-	 * @return a list of updated paths and objectIds
+	 * @return a list of updated paths and smudgeFilterCommands
 	 */
-	public Map<String, ObjectId> getUpdated() {
+	public Map<String, String> getUpdated() {
 		return updated;
 	}
 
@@ -447,7 +452,8 @@ private boolean doCheckout() throws CorruptObjectException, IOException,
 			for (String path : updated.keySet()) {
 				DirCacheEntry entry = dc.getEntry(path);
 				if (!FileMode.GITLINK.equals(entry.getRawMode()))
-					checkoutEntry(repo, entry, objectReader, false);
+					checkoutEntry(repo, entry, objectReader, false,
+							updated.get(path));
 			}
 
 			// commit the index builder - a new index is persisted
@@ -996,9 +1002,12 @@ private void remove(String path) {
 		removed.add(path);
 	}
 
-	private void update(String path, ObjectId mId, FileMode mode) {
+	private void update(String path, ObjectId mId, FileMode mode)
+			throws IOException {
 		if (!FileMode.TREE.equals(mode)) {
-			updated.put(path, mId);
+			updated.put(path,
+					walk.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE));
+
 			DirCacheEntry entry = new DirCacheEntry(path, DirCacheEntry.STAGE_0);
 			entry.setObjectId(mId);
 			entry.setFileMode(mode);
@@ -1150,7 +1159,7 @@ private boolean isModifiedSubtree_IndexTree(String path, ObjectId tree)
 	 */
 	public static void checkoutEntry(Repository repo, DirCacheEntry entry,
 			ObjectReader or) throws IOException {
-		checkoutEntry(repo, entry, or, false);
+		checkoutEntry(repo, entry, or, false, null);
 	}
 
 	/**
@@ -1186,6 +1195,46 @@ public static void checkoutEntry(Repository repo, DirCacheEntry entry,
 	 */
 	public static void checkoutEntry(Repository repo, DirCacheEntry entry,
 			ObjectReader or, boolean deleteRecursive) throws IOException {
+		checkoutEntry(repo, entry, or, deleteRecursive, null);
+	}
+
+	/**
+	 * Updates the file in the working tree with content and mode from an entry
+	 * in the index. The new content is first written to a new temporary file in
+	 * the same directory as the real file. Then that new file is renamed to the
+	 * final filename.
+	 *
+	 * <p>
+	 * <b>Note:</b> if the entry path on local file system exists as a file, it
+	 * will be deleted and if it exists as a directory, it will be deleted
+	 * recursively, independently if has any content.
+	 * </p>
+	 *
+	 * <p>
+	 * TODO: this method works directly on File IO, we may need another
+	 * abstraction (like WorkingTreeIterator). This way we could tell e.g.
+	 * Eclipse that Files in the workspace got changed
+	 * </p>
+	 *
+	 * @param repo
+	 *            repository managing the destination work tree.
+	 * @param entry
+	 *            the entry containing new mode and content
+	 * @param or
+	 *            object reader to use for checkout
+	 * @param deleteRecursive
+	 *            true to recursively delete final path if it exists on the file
+	 *            system
+	 * @param smudgeFilterCommand
+	 *            the filter command to be run for smudging the entry to be
+	 *            checked out
+	 *
+	 * @throws IOException
+	 * @since 4.2
+	 */
+	public static void checkoutEntry(Repository repo, DirCacheEntry entry,
+			ObjectReader or, boolean deleteRecursive,
+			String smudgeFilterCommand) throws IOException {
 		ObjectLoader ol = or.open(entry.getObjectId());
 		File f = new File(repo.getWorkTree(), entry.getPathString());
 		File parentDir = f.getParentFile();
@@ -1210,14 +1259,52 @@ public static void checkoutEntry(Repository repo, DirCacheEntry entry,
 		OutputStream channel = new FileOutputStream(tmpFile);
 		if (opt.getAutoCRLF() == AutoCRLF.TRUE)
 			channel = new AutoCRLFOutputStream(channel);
-		try {
-			ol.copyTo(channel);
-		} finally {
-			channel.close();
+		if (smudgeFilterCommand != null) {
+			ProcessBuilder filterProcessBuilder = fs
+					.runInShell(smudgeFilterCommand, new String[0]);
+			filterProcessBuilder.directory(repo.getWorkTree());
+			filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY,
+					repo.getDirectory().getAbsolutePath());
+			ExecutionResult result;
+			int rc;
+			try {
+				// TODO: wire correctly with AUTOCRLF
+				result = fs.execute(filterProcessBuilder, ol.openStream());
+				rc = result.getRc();
+				if (rc == 0) {
+					result.getStdout().writeTo(channel,
+							NullProgressMonitor.INSTANCE);
+				}
+			} catch (IOException | InterruptedException e) {
+				throw new IOException(new FilterFailedException(e,
+						smudgeFilterCommand, entry.getPathString()));
+
+			} finally {
+				channel.close();
+			}
+			if (rc != 0) {
+				throw new IOException(new FilterFailedException(rc,
+						smudgeFilterCommand, entry.getPathString(),
+						result.getStdout().toByteArray(MAX_EXCEPTION_TEXT_SIZE),
+						RawParseUtils.decode(result.getStderr()
+								.toByteArray(MAX_EXCEPTION_TEXT_SIZE))));
+			}
+		} else {
+			try {
+				ol.copyTo(channel);
+			} finally {
+				channel.close();
+			}
 		}
-		entry.setLength(opt.getAutoCRLF() == AutoCRLF.TRUE ? //
-				tmpFile.length() // AutoCRLF wants on-disk-size
-				: (int) ol.getSize());
+		// The entry needs to correspond to the on-disk filesize. If the content
+		// was filtered (either by autocrlf handling or smudge filters) ask the
+		// filesystem again for the length. Otherwise the objectloader knows the
+		// size
+		if (opt.getAutoCRLF() == AutoCRLF.TRUE || smudgeFilterCommand != null) {
+			entry.setLength(tmpFile.length());
+		} else {
+			entry.setLength(ol.getSize());
+		}
 
 		if (opt.isFileMode() && fs.supportsExecute()) {
 			if (FileMode.EXECUTABLE_FILE.equals(entry.getRawMode())) {
@@ -1255,24 +1342,6 @@ private static void checkValidPath(CanonicalTreeParser t)
 			checkValidPathSegment(chk, i);
 	}
 
-	/**
-	 * Check if path is a valid path for a checked out file name or ref name.
-	 *
-	 * @param path
-	 * @throws InvalidPathException
-	 *             if the path is invalid
-	 * @since 3.3
-	 */
-	static void checkValidPath(String path) throws InvalidPathException {
-		try {
-			SystemReader.getInstance().checkPath(path);
-		} catch (CorruptObjectException e) {
-			InvalidPathException p = new InvalidPathException(path);
-			p.initCause(e);
-			throw p;
-		}
-	}
-
 	private static void checkValidPathSegment(ObjectChecker chk,
 			CanonicalTreeParser t) throws InvalidPathException {
 		try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
index eef2e6d..c8bc096 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
@@ -65,6 +65,7 @@
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.MutableInteger;
 import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.SystemReader;
 
 /**
  * A single file (or stage of a file) in a {@link DirCache}.
@@ -191,7 +192,7 @@ public class DirCacheEntry {
 		}
 
 		try {
-			DirCacheCheckout.checkValidPath(toString(path));
+			checkPath(path);
 		} catch (InvalidPathException e) {
 			CorruptObjectException p =
 				new CorruptObjectException(e.getMessage());
@@ -263,7 +264,7 @@ public DirCacheEntry(final byte[] newPath) {
 	/**
 	 * Create an empty entry at the specified stage.
 	 *
-	 * @param newPath
+	 * @param path
 	 *            name of the cache entry, in the standard encoding.
 	 * @param stage
 	 *            the stage index of the new entry.
@@ -274,16 +275,16 @@ public DirCacheEntry(final byte[] newPath) {
 	 *             range 0..3, inclusive.
 	 */
 	@SuppressWarnings("boxing")
-	public DirCacheEntry(final byte[] newPath, final int stage) {
-		DirCacheCheckout.checkValidPath(toString(newPath));
+	public DirCacheEntry(byte[] path, final int stage) {
+		checkPath(path);
 		if (stage < 0 || 3 < stage)
 			throw new IllegalArgumentException(MessageFormat.format(
 					JGitText.get().invalidStageForPath,
-					stage, toString(newPath)));
+					stage, toString(path)));
 
 		info = new byte[INFO_LEN];
 		infoOffset = 0;
-		path = newPath;
+		this.path = path;
 
 		int flags = ((stage & 0x3) << 12);
 		if (path.length < NAME_MASK)
@@ -498,12 +499,16 @@ public void setFileMode(final FileMode mode) {
 		switch (mode.getBits() & FileMode.TYPE_MASK) {
 		case FileMode.TYPE_MISSING:
 		case FileMode.TYPE_TREE:
-			throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidModeForPath
-					, mode, getPathString()));
+			throw new IllegalArgumentException(MessageFormat.format(
+					JGitText.get().invalidModeForPath, mode, getPathString()));
 		}
 		NB.encodeInt32(info, infoOffset + P_MODE, mode.getBits());
 	}
 
+	void setFileMode(int mode) {
+		NB.encodeInt32(info, infoOffset + P_MODE, mode);
+	}
+
 	/**
 	 * Get the cached creation time of this file, in milliseconds.
 	 *
@@ -730,6 +735,16 @@ private int getExtendedFlags() {
 			return 0;
 	}
 
+	private static void checkPath(byte[] path) {
+		try {
+			SystemReader.getInstance().checkPath(path);
+		} catch (CorruptObjectException e) {
+			InvalidPathException p = new InvalidPathException(toString(path));
+			p.initCause(e);
+			throw p;
+		}
+	}
+
 	private static String toString(final byte[] path) {
 		return Constants.CHARSET.decode(ByteBuffer.wrap(path)).toString();
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java
index 354a074..ad93f72 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java
@@ -103,9 +103,6 @@ public class DirCacheIterator extends AbstractTreeIterator {
 	/** The subtree containing {@link #currentEntry} if this is first entry. */
 	protected DirCacheTree currentSubtree;
 
-	/** Holds an {@link AttributesNode} for the current entry */
-	private AttributesNode attributesNode;
-
 	/**
 	 * Create a new iterator for an already loaded DirCache instance.
 	 * <p>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/IndexReadException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/IndexReadException.java
new file mode 100644
index 0000000..70f650d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/IndexReadException.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015, Christian Halstrick <christian.halstrick@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.errors;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.internal.JGitText;
+
+/**
+ * Cannot read the index. This is a serious error that users need to be made
+ * aware of.
+ *
+ * @since 4.2
+ */
+public class IndexReadException extends IOException {
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * Constructs an IndexReadException with the default message.
+	 */
+	public IndexReadException() {
+		super(JGitText.get().indexWriteException);
+	}
+
+	/**
+	 * Constructs an IndexReadException with the specified detail message.
+	 *
+	 * @param s
+	 *            message
+	 */
+	public IndexReadException(final String s) {
+		super(s);
+	}
+
+	/**
+	 * Constructs an IndexReadException with the specified detail message.
+	 *
+	 * @param s
+	 *            message
+	 * @param cause
+	 *            root cause exception
+	 */
+	public IndexReadException(final String s, final Throwable cause) {
+		super(s);
+		initCause(cause);
+	}
+}
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 1d2d3bf..ff9f233 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,9 @@
  */
 package org.eclipse.jgit.gitrepo;
 
+import static org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME;
+import static org.eclipse.jgit.lib.Constants.R_REMOTES;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -574,7 +577,7 @@ private void addSubmodule(String url, String name, String revision,
 	private static String findRef(String ref, Repository repo)
 			throws IOException {
 		if (!ObjectId.isId(ref)) {
-			Ref r = repo.getRef(Constants.DEFAULT_REMOTE_NAME + "/" + ref); //$NON-NLS-1$
+			Ref r = repo.exactRef(R_REMOTES + DEFAULT_REMOTE_NAME + "/" + ref); //$NON-NLS-1$
 			if (r != null)
 				return r.getName();
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java
index 2e65828..a501fee 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java
@@ -113,6 +113,9 @@ public String getHookName() {
 	 */
 	@Override
 	protected String[] getParameters() {
+		if (remoteName == null) {
+			remoteName = remoteLocation;
+		}
 		return new String[] { remoteName, remoteLocation };
 	}
 
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 e39469b..796eaae 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -79,6 +79,7 @@ public static JGitText get() {
 	/***/ public String atLeastOnePathIsRequired;
 	/***/ public String atLeastOnePatternIsRequired;
 	/***/ public String atLeastTwoFiltersNeeded;
+	/***/ public String atomicPushNotSupported;
 	/***/ public String authenticationNotSupported;
 	/***/ public String badBase64InputCharacterAt;
 	/***/ public String badEntryDelimiter;
@@ -104,6 +105,7 @@ public static JGitText get() {
 	/***/ public String cannotBeRecursiveWhenTreesAreIncluded;
 	/***/ public String cannotChangeActionOnComment;
 	/***/ public String cannotChangeToComment;
+	/***/ public String cannotCheckoutFromUnbornBranch;
 	/***/ public String cannotCheckoutOursSwitchBranch;
 	/***/ public String cannotCombineSquashWithNoff;
 	/***/ public String cannotCombineTreeFilterWithRevFilter;
@@ -146,6 +148,7 @@ public static JGitText get() {
 	/***/ public String cannotReadCommit;
 	/***/ public String cannotReadFile;
 	/***/ public String cannotReadHEAD;
+	/***/ public String cannotReadIndex;
 	/***/ public String cannotReadObject;
 	/***/ public String cannotReadObjectsPath;
 	/***/ public String cannotReadTree;
@@ -338,6 +341,8 @@ public static JGitText get() {
 	/***/ public String fileIsTooBigForThisConvenienceMethod;
 	/***/ public String fileIsTooLarge;
 	/***/ public String fileModeNotSetForPath;
+	/***/ public String filterExecutionFailed;
+	/***/ public String filterExecutionFailedRc;
 	/***/ public String findingGarbage;
 	/***/ public String flagIsDisposed;
 	/***/ public String flagNotFromThis;
@@ -410,6 +415,7 @@ public static JGitText get() {
 	/***/ public String invalidURL;
 	/***/ public String invalidWildcards;
 	/***/ public String invalidRefSpec;
+	/***/ public String invalidRepositoryStateNoHead;
 	/***/ public String invalidWindowSize;
 	/***/ public String isAStaticFlagAndHasNorevWalkInstance;
 	/***/ public String JRELacksMD5Implementation;
@@ -509,6 +515,7 @@ public static JGitText get() {
 	/***/ public String packfileIsTruncatedNoParam;
 	/***/ public String packHandleIsStale;
 	/***/ public String packHasUnresolvedDeltas;
+	/***/ public String packInaccessible;
 	/***/ public String packingCancelledDuringObjectsWriting;
 	/***/ public String packObjectCountMismatch;
 	/***/ public String packRefs;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java
index 122f6d3..0d5fd0f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java
@@ -44,8 +44,13 @@
 package org.eclipse.jgit.internal.storage.dfs;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.text.MessageFormat;
+import java.util.Collections;
 
+import org.eclipse.jgit.attributes.AttributesNode;
+import org.eclipse.jgit.attributes.AttributesNodeProvider;
+import org.eclipse.jgit.attributes.AttributesRule;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.RefUpdate;
@@ -126,4 +131,36 @@ public void notifyIndexChanged() {
 	public ReflogReader getReflogReader(String refName) throws IOException {
 		throw new UnsupportedOperationException();
 	}
+
+	@Override
+	public AttributesNodeProvider createAttributesNodeProvider() {
+		// TODO Check if the implementation used in FileRepository can be used
+		// for this kind of repository
+		return new EmptyAttributesNodeProvider();
+	}
+
+	private static class EmptyAttributesNodeProvider implements
+			AttributesNodeProvider {
+		private EmptyAttributesNode emptyAttributesNode = new EmptyAttributesNode();
+
+		public AttributesNode getInfoAttributesNode() throws IOException {
+			return emptyAttributesNode;
+		}
+
+		public AttributesNode getGlobalAttributesNode() throws IOException {
+			return emptyAttributesNode;
+		}
+
+		private static class EmptyAttributesNode extends AttributesNode {
+
+			public EmptyAttributesNode() {
+				super(Collections.<AttributesRule> emptyList());
+			}
+
+			@Override
+			public void parse(InputStream in) throws IOException {
+				// Do nothing
+			}
+		}
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
index 832e4fb..1c664b4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
@@ -13,14 +13,22 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 
+import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Ref.Storage;
 import org.eclipse.jgit.lib.SymbolicRef;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTag;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
 import org.eclipse.jgit.util.RefList;
 
 /**
@@ -46,8 +54,8 @@ public InMemoryRepository build() throws IOException {
 	static final AtomicInteger packId = new AtomicInteger();
 
 	private final DfsObjDatabase objdb;
-
 	private final DfsRefDatabase refdb;
+	private boolean performsAtomicTransactions = true;
 
 	/**
 	 * Initialize a new in-memory repository.
@@ -76,6 +84,17 @@ public DfsRefDatabase getRefDatabase() {
 		return refdb;
 	}
 
+	/**
+	 * Enable (or disable) the atomic reference transaction support.
+	 * <p>
+	 * Useful for testing atomic support enabled or disabled.
+	 *
+	 * @param atomic
+	 */
+	public void setPerformsAtomicTransactions(boolean atomic) {
+		performsAtomicTransactions = atomic;
+	}
+
 	private class MemObjDatabase extends DfsObjDatabase {
 		private List<DfsPackDescription> packs = new ArrayList<DfsPackDescription>();
 
@@ -235,41 +254,143 @@ public void setReadAheadBytes(int b) {
 
 	private class MemRefDatabase extends DfsRefDatabase {
 		private final ConcurrentMap<String, Ref> refs = new ConcurrentHashMap<String, Ref>();
+		private final ReadWriteLock lock = new ReentrantReadWriteLock(true /* fair */);
 
 		MemRefDatabase() {
 			super(InMemoryRepository.this);
 		}
 
 		@Override
+		public boolean performsAtomicTransactions() {
+			return performsAtomicTransactions;
+		}
+
+		@Override
+		public BatchRefUpdate newBatchUpdate() {
+			return new BatchRefUpdate(this) {
+				@Override
+				public void execute(RevWalk walk, ProgressMonitor monitor)
+						throws IOException {
+					if (performsAtomicTransactions()) {
+						try {
+							lock.writeLock().lock();
+							batch(walk, getCommands());
+						} finally {
+							lock.writeLock().unlock();
+						}
+					} else {
+						super.execute(walk, monitor);
+					}
+				}
+			};
+		}
+
+		@Override
 		protected RefCache scanAllRefs() throws IOException {
 			RefList.Builder<Ref> ids = new RefList.Builder<Ref>();
 			RefList.Builder<Ref> sym = new RefList.Builder<Ref>();
-			for (Ref ref : refs.values()) {
-				if (ref.isSymbolic())
-					sym.add(ref);
-				ids.add(ref);
+			try {
+				lock.readLock().lock();
+				for (Ref ref : refs.values()) {
+					if (ref.isSymbolic())
+						sym.add(ref);
+					ids.add(ref);
+				}
+			} finally {
+				lock.readLock().unlock();
 			}
 			ids.sort();
 			sym.sort();
 			return new RefCache(ids.toRefList(), sym.toRefList());
 		}
 
+		private void batch(RevWalk walk, List<ReceiveCommand> cmds) {
+			// Validate that the target exists in a new RevWalk, as the RevWalk
+			// from the RefUpdate might be reading back unflushed objects.
+			Map<ObjectId, ObjectId> peeled = new HashMap<>();
+			try (RevWalk rw = new RevWalk(getRepository())) {
+				for (ReceiveCommand c : cmds) {
+					if (!ObjectId.zeroId().equals(c.getNewId())) {
+						try {
+							RevObject o = rw.parseAny(c.getNewId());
+							if (o instanceof RevTag) {
+								peeled.put(o, rw.peel(o).copy());
+							}
+						} catch (IOException e) {
+							c.setResult(ReceiveCommand.Result.REJECTED_MISSING_OBJECT);
+							reject(cmds);
+							return;
+						}
+					}
+				}
+			}
+
+			// Check all references conform to expected old value.
+			for (ReceiveCommand c : cmds) {
+				Ref r = refs.get(c.getRefName());
+				if (r == null) {
+					if (c.getType() != ReceiveCommand.Type.CREATE) {
+						c.setResult(ReceiveCommand.Result.LOCK_FAILURE);
+						reject(cmds);
+						return;
+					}
+				} else if (r.isSymbolic() || r.getObjectId() == null
+						|| !r.getObjectId().equals(c.getOldId())) {
+					c.setResult(ReceiveCommand.Result.LOCK_FAILURE);
+					reject(cmds);
+					return;
+				}
+			}
+
+			// Write references.
+			for (ReceiveCommand c : cmds) {
+				if (c.getType() == ReceiveCommand.Type.DELETE) {
+					refs.remove(c.getRefName());
+					c.setResult(ReceiveCommand.Result.OK);
+					continue;
+				}
+
+				ObjectId p = peeled.get(c.getNewId());
+				Ref r;
+				if (p != null) {
+					r = new ObjectIdRef.PeeledTag(Storage.PACKED,
+							c.getRefName(), c.getNewId(), p);
+				} else {
+					r = new ObjectIdRef.PeeledNonTag(Storage.PACKED,
+							c.getRefName(), c.getNewId());
+				}
+				refs.put(r.getName(), r);
+				c.setResult(ReceiveCommand.Result.OK);
+			}
+			clearCache();
+		}
+
+		private void reject(List<ReceiveCommand> cmds) {
+			for (ReceiveCommand c : cmds) {
+				if (c.getResult() == ReceiveCommand.Result.NOT_ATTEMPTED) {
+					c.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON,
+							JGitText.get().transactionAborted);
+				}
+			}
+		}
+
 		@Override
 		protected boolean compareAndPut(Ref oldRef, Ref newRef)
 				throws IOException {
-			ObjectId id = newRef.getObjectId();
-			if (id != null) {
-				try (RevWalk rw = new RevWalk(getRepository())) {
-					// Validate that the target exists in a new RevWalk, as the RevWalk
-					// from the RefUpdate might be reading back unflushed objects.
-					rw.parseAny(id);
+			try {
+				lock.writeLock().lock();
+				ObjectId id = newRef.getObjectId();
+				if (id != null) {
+					try (RevWalk rw = new RevWalk(getRepository())) {
+						// Validate that the target exists in a new RevWalk, as the RevWalk
+						// from the RefUpdate might be reading back unflushed objects.
+						rw.parseAny(id);
+					}
 				}
-			}
-			String name = newRef.getName();
-			if (oldRef == null)
-				return refs.putIfAbsent(name, newRef) == null;
+				String name = newRef.getName();
+				if (oldRef == null)
+					return refs.putIfAbsent(name, newRef) == null;
 
-			synchronized (refs) {
 				Ref cur = refs.get(name);
 				Ref toCompare = cur;
 				if (toCompare != null) {
@@ -294,22 +415,29 @@ protected boolean compareAndPut(Ref oldRef, Ref newRef)
 					if (eq(toCompare, oldRef))
 						return refs.replace(name, cur, newRef);
 				}
+
+				if (oldRef.getStorage() == Storage.NEW)
+					return refs.putIfAbsent(name, newRef) == null;
+
+				return false;
+			} finally {
+				lock.writeLock().unlock();
 			}
-
-			if (oldRef.getStorage() == Storage.NEW)
-				return refs.putIfAbsent(name, newRef) == null;
-
-			return false;
 		}
 
 		@Override
 		protected boolean compareAndRemove(Ref oldRef) throws IOException {
-			String name = oldRef.getName();
-			Ref cur = refs.get(name);
-			if (cur != null && eq(cur, oldRef))
-				return refs.remove(name, cur);
-			else
-				return false;
+			try {
+				lock.writeLock().lock();
+				String name = oldRef.getName();
+				Ref cur = refs.get(name);
+				if (cur != null && eq(cur, oldRef))
+					return refs.remove(name, cur);
+				else
+					return false;
+			} finally {
+				lock.writeLock().unlock();
+			}
 		}
 
 		private boolean eq(Ref a, Ref b) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
index 995621e..490cbca 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
@@ -49,11 +49,15 @@
 import static org.eclipse.jgit.lib.RefDatabase.ALL;
 
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.text.MessageFormat;
 import java.util.HashSet;
 import java.util.Set;
 
+import org.eclipse.jgit.attributes.AttributesNode;
+import org.eclipse.jgit.attributes.AttributesNodeProvider;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.events.ConfigChangedEvent;
 import org.eclipse.jgit.events.ConfigChangedListener;
@@ -479,4 +483,63 @@ public ReflogReader getReflogReader(String refName) throws IOException {
 			return new ReflogReaderImpl(this, ref.getName());
 		return null;
 	}
+
+	@Override
+	public AttributesNodeProvider createAttributesNodeProvider() {
+		return new AttributesNodeProviderImpl(this);
+	}
+
+	/**
+	 * Implementation a {@link AttributesNodeProvider} for a
+	 * {@link FileRepository}.
+	 *
+	 * @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a>
+	 *
+	 */
+	static class AttributesNodeProviderImpl implements
+			AttributesNodeProvider {
+
+		private AttributesNode infoAttributesNode;
+
+		private AttributesNode globalAttributesNode;
+
+		/**
+		 * Constructor.
+		 *
+		 * @param repo
+		 *            {@link Repository} that will provide the attribute nodes.
+		 */
+		protected AttributesNodeProviderImpl(Repository repo) {
+			infoAttributesNode = new InfoAttributesNode(repo);
+			globalAttributesNode = new GlobalAttributesNode(repo);
+		}
+
+		public AttributesNode getInfoAttributesNode() throws IOException {
+			if (infoAttributesNode instanceof InfoAttributesNode)
+				infoAttributesNode = ((InfoAttributesNode) infoAttributesNode)
+						.load();
+			return infoAttributesNode;
+		}
+
+		public AttributesNode getGlobalAttributesNode() throws IOException {
+			if (globalAttributesNode instanceof GlobalAttributesNode)
+				globalAttributesNode = ((GlobalAttributesNode) globalAttributesNode)
+						.load();
+			return globalAttributesNode;
+		}
+
+		static void loadRulesFromFile(AttributesNode r, File attrs)
+				throws FileNotFoundException, IOException {
+			if (attrs.exists()) {
+				FileInputStream in = new FileInputStream(attrs);
+				try {
+					r.parse(in);
+				} finally {
+					in.close();
+				}
+			}
+		}
+
+	}
+
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
index e7005c2..4c40538 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
@@ -90,6 +90,7 @@
 import org.eclipse.jgit.lib.Ref.Storage;
 import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.ReflogEntry;
+import org.eclipse.jgit.lib.ReflogReader;
 import org.eclipse.jgit.revwalk.ObjectWalk;
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevWalk;
@@ -592,7 +593,11 @@ public Collection<PackFile> repack() throws IOException {
 	 * @throws IOException
 	 */
 	private Set<ObjectId> listRefLogObjects(Ref ref, long minTime) throws IOException {
-		List<ReflogEntry> rlEntries = repo.getReflogReader(ref.getName())
+		ReflogReader reflogReader = repo.getReflogReader(ref.getName());
+		if (reflogReader == null) {
+			return Collections.emptySet();
+		}
+		List<ReflogEntry> rlEntries = reflogReader
 				.getReverseEntries();
 		if (rlEntries == null || rlEntries.isEmpty())
 			return Collections.<ObjectId> emptySet();
@@ -635,10 +640,7 @@ private Map<String, Ref> getAllRefs() throws IOException {
 	 */
 	private Set<ObjectId> listNonHEADIndexObjects()
 			throws CorruptObjectException, IOException {
-		try {
-			if (repo.getIndexFile() == null)
-				return Collections.emptySet();
-		} catch (NoWorkTreeException e) {
+		if (repo.isBare()) {
 			return Collections.emptySet();
 		}
 		try (TreeWalk treeWalk = new TreeWalk(repo)) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java
new file mode 100644
index 0000000..454d3bf
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2014, Arthur Daussy <arthur.daussy@obeo.fr>
+ * Copyright (C) 2015, Christian Halstrick <christian.halstrick@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.internal.storage.file;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.jgit.attributes.AttributesNode;
+import org.eclipse.jgit.lib.CoreConfig;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.util.FS;
+
+/** Attribute node loaded from global system-wide file. */
+public class GlobalAttributesNode extends AttributesNode {
+	final Repository repository;
+
+	/**
+	 * @param repository
+	 */
+	public GlobalAttributesNode(Repository repository) {
+		this.repository = repository;
+	}
+
+	/**
+	 * @return the attributes node
+	 * @throws IOException
+	 */
+	public AttributesNode load() throws IOException {
+		AttributesNode r = new AttributesNode();
+
+		FS fs = repository.getFS();
+		String path = repository.getConfig().get(CoreConfig.KEY)
+				.getAttributesFile();
+		if (path != null) {
+			File attributesFile;
+			if (path.startsWith("~/")) { //$NON-NLS-1$
+				attributesFile = fs.resolve(fs.userHome(),
+						path.substring(2));
+			} else {
+				attributesFile = fs.resolve(null, path);
+			}
+			FileRepository.AttributesNodeProviderImpl.loadRulesFromFile(r, attributesFile);
+		}
+		return r.getRules().isEmpty() ? null : r;
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java
new file mode 100644
index 0000000..bda5cbe
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2014, Arthur Daussy <arthur.daussy@obeo.fr>
+ * Copyright (C) 2015, Christian Halstrick <christian.halstrick@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.internal.storage.file;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.jgit.attributes.AttributesNode;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.util.FS;
+
+/** Attribute node loaded from the $GIT_DIR/info/attributes file. */
+public class InfoAttributesNode extends AttributesNode {
+	final Repository repository;
+
+	/**
+	 * @param repository
+	 */
+	public InfoAttributesNode(Repository repository) {
+		this.repository = repository;
+	}
+
+	/**
+	 * @return the attributes node
+	 * @throws IOException
+	 */
+	public AttributesNode load() throws IOException {
+		AttributesNode r = new AttributesNode();
+
+		FS fs = repository.getFS();
+
+		File attributes = fs.resolve(repository.getDirectory(),
+				Constants.INFO_ATTRIBUTES);
+		FileRepository.AttributesNodeProviderImpl.loadRulesFromFile(r, attributes);
+
+		return r.getRules().isEmpty() ? null : r;
+	}
+
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
index 50297a9..e23ca74 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
@@ -227,6 +227,10 @@ public void copyCurrentContent() throws IOException {
 				fis.close();
 			}
 		} catch (FileNotFoundException fnfe) {
+			if (ref.exists()) {
+				unlock();
+				throw fnfe;
+			}
 			// Don't worry about a file that doesn't exist yet, it
 			// conceptually has no current content to copy.
 			//
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
index e7ef127..bd1d488 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
@@ -433,16 +433,14 @@ ObjectLoader openPackedObject(WindowCursor curs, AnyObjectId objectId) {
 
 	ObjectLoader openLooseObject(WindowCursor curs, AnyObjectId id)
 			throws IOException {
-		try {
-			File path = fileFor(id);
-			FileInputStream in = new FileInputStream(path);
-			try {
-				unpackedObjectCache.add(id);
-				return UnpackedObject.open(in, path, id, curs);
-			} finally {
-				in.close();
-			}
+		File path = fileFor(id);
+		try (FileInputStream in = new FileInputStream(path)) {
+			unpackedObjectCache.add(id);
+			return UnpackedObject.open(in, path, id, curs);
 		} catch (FileNotFoundException noFile) {
+			if (path.exists()) {
+				throw noFile;
+			}
 			unpackedObjectCache.remove(id);
 			return null;
 		}
@@ -513,15 +511,14 @@ private long getPackedObjectSize(WindowCursor curs, AnyObjectId id) {
 
 	private long getLooseObjectSize(WindowCursor curs, AnyObjectId id)
 			throws IOException {
-		try {
-			FileInputStream in = new FileInputStream(fileFor(id));
-			try {
-				unpackedObjectCache.add(id);
-				return UnpackedObject.getSize(in, id, curs);
-			} finally {
-				in.close();
-			}
+		File f = fileFor(id);
+		try (FileInputStream in = new FileInputStream(f)) {
+			unpackedObjectCache.add(id);
+			return UnpackedObject.getSize(in, id, curs);
 		} catch (FileNotFoundException noFile) {
+			if (f.exists()) {
+				throw noFile;
+			}
 			unpackedObjectCache.remove(id);
 			return -1;
 		}
@@ -561,7 +558,11 @@ private void handlePackError(IOException e, PackFile p) {
 			// Assume the pack is corrupted, and remove it from the list.
 			removePack(p);
 		} else if (e instanceof FileNotFoundException) {
-			warnTmpl = JGitText.get().packWasDeleted;
+			if (p.getPackFile().exists()) {
+				warnTmpl = JGitText.get().packInaccessible;
+			} else {
+				warnTmpl = JGitText.get().packWasDeleted;
+			}
 			removePack(p);
 		} else if (FileUtils.isStaleFileHandle(e)) {
 			warnTmpl = JGitText.get().packHandleIsStale;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
index 731bbd3..69f7e97 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
@@ -262,6 +262,30 @@ private RefList<LooseRef> getLooseRefs() {
 	}
 
 	@Override
+	public Ref exactRef(String name) throws IOException {
+		RefList<Ref> packed = getPackedRefs();
+		Ref ref;
+		try {
+			ref = readRef(name, packed);
+			if (ref != null) {
+				ref = resolve(ref, 0, null, null, packed);
+			}
+		} catch (IOException e) {
+			if (name.contains("/") //$NON-NLS-1$
+					|| !(e.getCause() instanceof InvalidObjectIdException)) {
+				throw e;
+			}
+
+			// While looking for a ref outside of refs/ (e.g., 'config'), we
+			// found a non-ref file (e.g., a config file) instead.  Treat this
+			// as a ref-not-found condition.
+			ref = null;
+		}
+		fireRefsChanged();
+		return ref;
+	}
+
+	@Override
 	public Ref getRef(final String needle) throws IOException {
 		final RefList<Ref> packed = getPackedRefs();
 		Ref ref = null;
@@ -270,6 +294,8 @@ public Ref getRef(final String needle) throws IOException {
 				ref = readRef(prefix + needle, packed);
 				if (ref != null) {
 					ref = resolve(ref, 0, null, null, packed);
+				}
+				if (ref != null) {
 					break;
 				}
 			} catch (IOException e) {
@@ -762,6 +788,9 @@ private PackedRefList readPackedRefs() throws IOException {
 						new DigestInputStream(new FileInputStream(packedRefsFile),
 								digest), CHARSET));
 			} catch (FileNotFoundException noPackedRefs) {
+				if (packedRefsFile.exists()) {
+					throw noPackedRefs;
+				}
 				// Ignore it and leave the new list empty.
 				return PackedRefList.NO_PACKED_REFS;
 			}
@@ -918,7 +947,10 @@ LooseRef scanRef(LooseRef ref, String name) throws IOException {
 		try {
 			buf = IO.readSome(path, limit);
 		} catch (FileNotFoundException noFile) {
-			return null; // doesn't exist; not a reference.
+			if (path.exists() && path.isFile()) {
+				throw noFile;
+			}
+			return null; // doesn't exist or no file; not a reference.
 		}
 
 		int n = buf.length;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java
index dadc631..2f583b2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java
@@ -96,6 +96,9 @@ public ReflogEntry getReverseEntry(int number) throws IOException {
 		try {
 			log = IO.readFully(logName);
 		} catch (FileNotFoundException e) {
+			if (logName.exists()) {
+				throw e;
+			}
 			return null;
 		}
 
@@ -118,6 +121,9 @@ public List<ReflogEntry> getReverseEntries(int max) throws IOException {
 		try {
 			log = IO.readFully(logName);
 		} catch (FileNotFoundException e) {
+			if (logName.exists()) {
+				throw e;
+			}
 			return Collections.emptyList();
 		}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java
index 2cc2563..a027437 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java
@@ -399,6 +399,9 @@ public ObjectStream openStream() throws MissingObjectException,
 			try {
 				in = buffer(new FileInputStream(path));
 			} catch (FileNotFoundException gone) {
+				if (path.exists()) {
+					throw gone;
+				}
 				// If the loose file no longer exists, it may have been
 				// moved into a pack file in the mean time. Try again
 				// to locate the object.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
index 535a6ee..d30edaf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
@@ -273,6 +273,13 @@ public final class Constants {
 	public static final String INFO_EXCLUDE = "info/exclude";
 
 	/**
+	 * Attributes-override-file
+	 *
+	 * @since 4.2
+	 */
+	public static final String INFO_ATTRIBUTES = "info/attributes";
+
+	/**
 	 * The system property that contains the system user name
 	 *
 	 * @since 3.6
@@ -363,6 +370,27 @@ public final class Constants {
 	 */
 	public static final String DOT_GIT_ATTRIBUTES = ".gitattributes";
 
+	/**
+	 * Key for filters in .gitattributes
+	 *
+	 * @since 4.2
+	 */
+	public static final String ATTR_FILTER = "filter";
+
+	/**
+	 * clean command name, used to call filter driver
+	 *
+	 * @since 4.2
+	 */
+	public static final String ATTR_FILTER_TYPE_CLEAN = "clean";
+
+	/**
+	 * smudge command name, used to call filter driver
+	 *
+	 * @since 4.2
+	 */
+	public static final String ATTR_FILTER_TYPE_SMUDGE = "smudge";
+
 	/** Name of the ignore file */
 	public static final String DOT_GIT_IGNORE = ".gitignore";
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
index 3fde2f9..9e474f8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
@@ -74,6 +74,7 @@
 import org.eclipse.jgit.treewalk.FileTreeIterator;
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.treewalk.WorkingTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
 import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
 import org.eclipse.jgit.treewalk.filter.IndexDiffFilter;
 import org.eclipse.jgit.treewalk.filter.SkipWorkTreeFilter;
@@ -403,6 +404,7 @@ public boolean diff(final ProgressMonitor monitor, int estWorkTreeSize,
 		dirCache = repository.readDirCache();
 
 		try (TreeWalk treeWalk = new TreeWalk(repository)) {
+			treeWalk.setOperationType(OperationType.CHECKIN_OP);
 			treeWalk.setRecursive(true);
 			// add the trees (tree, dirchache, workdir)
 			if (tree != null)
@@ -411,6 +413,7 @@ public boolean diff(final ProgressMonitor monitor, int estWorkTreeSize,
 				treeWalk.addTree(new EmptyTreeIterator());
 			treeWalk.addTree(new DirCacheIterator(dirCache));
 			treeWalk.addTree(initialWorkingTreeIterator);
+			initialWorkingTreeIterator.setDirCacheIterator(treeWalk, 1);
 			Collection<TreeFilter> filters = new ArrayList<TreeFilter>(4);
 
 			if (monitor != null) {
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 ef22fb9..986666f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
@@ -51,6 +51,9 @@
 import java.util.List;
 import java.util.Map;
 
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+
 /**
  * Abstraction of name to {@link ObjectId} mapping.
  * <p>
@@ -132,6 +135,7 @@ public abstract class RefDatabase {
 	 * @since 2.3
 	 * @see #isNameConflicting(String)
 	 */
+	@NonNull
 	public Collection<String> getConflictingNames(String name)
 			throws IOException {
 		Map<String, Ref> allRefs = getRefs(ALL);
@@ -169,6 +173,7 @@ public Collection<String> getConflictingNames(String name)
 	 * @throws IOException
 	 *             the reference space cannot be accessed.
 	 */
+	@NonNull
 	public abstract RefUpdate newUpdate(String name, boolean detach)
 			throws IOException;
 
@@ -183,6 +188,7 @@ public abstract RefUpdate newUpdate(String name, boolean detach)
 	 * @throws IOException
 	 *             the reference space cannot be accessed.
 	 */
+	@NonNull
 	public abstract RefRename newRename(String fromName, String toName)
 			throws IOException;
 
@@ -193,6 +199,7 @@ public abstract RefRename newRename(String fromName, String toName)
 	 *
 	 * @return a new batch update object.
 	 */
+	@NonNull
 	public BatchRefUpdate newBatchUpdate() {
 		return new BatchRefUpdate(this);
 	}
@@ -223,6 +230,7 @@ public boolean performsAtomicTransactions() {
 	 * @throws IOException
 	 *             the reference space cannot be accessed.
 	 */
+	@Nullable
 	public abstract Ref getRef(String name) throws IOException;
 
 	/**
@@ -238,21 +246,13 @@ public boolean performsAtomicTransactions() {
 	 *             the reference space cannot be accessed.
 	 * @since 4.1
 	 */
+	@Nullable
 	public Ref exactRef(String name) throws IOException {
-		int slash = name.lastIndexOf('/');
-		String prefix = name.substring(0, slash + 1);
-		String rest = name.substring(slash + 1);
-		Ref result = getRefs(prefix).get(rest);
-		if (result != null || slash != -1) {
-			return result;
+		Ref ref = getRef(name);
+		if (ref == null || !name.equals(ref.getName())) {
+			return null;
 		}
-
-		for (Ref ref : getAdditionalRefs()) {
-			if (name.equals(ref.getName())) {
-				return ref;
-			}
-		}
-		return null;
+		return ref;
 	}
 
 	/**
@@ -270,6 +270,7 @@ public Ref exactRef(String name) throws IOException {
 	 *             the reference space cannot be accessed.
 	 * @since 4.1
 	 */
+	@NonNull
 	public Map<String, Ref> exactRef(String... refs) throws IOException {
 		Map<String, Ref> result = new HashMap<>(refs.length);
 		for (String name : refs) {
@@ -294,6 +295,7 @@ public Map<String, Ref> exactRef(String... refs) throws IOException {
 	 *             the reference space cannot be accessed.
 	 * @since 4.1
 	 */
+	@Nullable
 	public Ref firstExactRef(String... refs) throws IOException {
 		for (String name : refs) {
 			Ref ref = exactRef(name);
@@ -317,6 +319,7 @@ public Ref firstExactRef(String... refs) throws IOException {
 	 * @throws IOException
 	 *             the reference space cannot be accessed.
 	 */
+	@NonNull
 	public abstract Map<String, Ref> getRefs(String prefix) throws IOException;
 
 	/**
@@ -331,6 +334,7 @@ public Ref firstExactRef(String... refs) throws IOException {
 	 * @throws IOException
 	 *             the reference space cannot be accessed.
 	 */
+	@NonNull
 	public abstract List<Ref> getAdditionalRefs() throws IOException;
 
 	/**
@@ -347,10 +351,11 @@ public Ref firstExactRef(String... refs) throws IOException {
 	 * @return {@code ref} if {@code ref.isPeeled()} is true; otherwise a new
 	 *         Ref object representing the same data as Ref, but isPeeled() will
 	 *         be true and getPeeledObjectId() will contain the peeled object
-	 *         (or null).
+	 *         (or {@code null}).
 	 * @throws IOException
 	 *             the reference space or object space cannot be accessed.
 	 */
+	@NonNull
 	public abstract Ref peel(Ref ref) throws IOException;
 
 	/**
@@ -374,9 +379,10 @@ public void refresh() {
 	 * @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.
+	 * @return The first ref matching the name, or {@code null} if not found.
 	 * @since 3.4
 	 */
+	@Nullable
 	public static Ref findRef(Map<String, Ref> map, String name) {
 		for (String prefix : SEARCH_PATH) {
 			String fullname = prefix + name;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java
index 05eb311..59f852b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java
@@ -170,7 +170,7 @@ public Result rename() throws IOException {
 	 */
 	protected boolean needToUpdateHEAD() throws IOException {
 		Ref head = source.getRefDatabase().getRef(Constants.HEAD);
-		if (head.isSymbolic()) {
+		if (head != null && head.isSymbolic()) {
 			head = head.getTarget();
 			return head.getName().equals(source.getName());
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
index d4c72cb..49a970d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -64,6 +64,9 @@
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.attributes.AttributesNodeProvider;
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.errors.AmbiguousObjectException;
 import org.eclipse.jgit.errors.CorruptObjectException;
@@ -137,6 +140,7 @@ protected Repository(final BaseRepositoryBuilder options) {
 	}
 
 	/** @return listeners observing only events on this repository. */
+	@NonNull
 	public ListenerList getListenerList() {
 		return myListeners;
 	}
@@ -181,7 +185,16 @@ public void create() throws IOException {
 	 */
 	public abstract void create(boolean bare) throws IOException;
 
-	/** @return local metadata directory; null if repository isn't local. */
+	/**
+	 * @return local metadata directory; {@code null} if repository isn't local.
+	 */
+	/*
+	 * TODO This method should be annotated as Nullable, because in some
+	 * specific configurations metadata is not located in the local file system
+	 * (for example in memory databases). In "usual" repositories this
+	 * annotation would only cause compiler errors at places where the actual
+	 * directory can never be null.
+	 */
 	public File getDirectory() {
 		return gitDir;
 	}
@@ -189,28 +202,52 @@ public File getDirectory() {
 	/**
 	 * @return the object database which stores this repository's data.
 	 */
+	@NonNull
 	public abstract ObjectDatabase getObjectDatabase();
 
 	/** @return a new inserter to create objects in {@link #getObjectDatabase()} */
+	@NonNull
 	public ObjectInserter newObjectInserter() {
 		return getObjectDatabase().newInserter();
 	}
 
 	/** @return a new reader to read objects from {@link #getObjectDatabase()} */
+	@NonNull
 	public ObjectReader newObjectReader() {
 		return getObjectDatabase().newReader();
 	}
 
 	/** @return the reference database which stores the reference namespace. */
+	@NonNull
 	public abstract RefDatabase getRefDatabase();
 
 	/**
 	 * @return the configuration of this repository
 	 */
+	@NonNull
 	public abstract StoredConfig getConfig();
 
 	/**
-	 * @return the used file system abstraction
+	 * @return a new {@link AttributesNodeProvider}. This
+	 *         {@link AttributesNodeProvider} is lazy loaded only once. It means
+	 *         that it will not be updated after loading. Prefer creating new
+	 *         instance for each use.
+	 * @since 4.2
+	 */
+	@NonNull
+	public abstract AttributesNodeProvider createAttributesNodeProvider();
+
+
+	/**
+	 * @return the used file system abstraction, or or {@code null} if
+	 *         repository isn't local.
+	 */
+	/*
+	 * TODO This method should be annotated as Nullable, because in some
+	 * specific configurations metadata is not located in the local file system
+	 * (for example in memory databases). In "usual" repositories this
+	 * annotation would only cause compiler errors at places where the actual
+	 * directory can never be null.
 	 */
 	public FS getFS() {
 		return fs;
@@ -244,6 +281,7 @@ public boolean hasObject(AnyObjectId objectId) {
 	 * @throws IOException
 	 *             the object store cannot be accessed.
 	 */
+	@NonNull
 	public ObjectLoader open(final AnyObjectId objectId)
 			throws MissingObjectException, IOException {
 		return getObjectDatabase().open(objectId);
@@ -271,6 +309,7 @@ public ObjectLoader open(final AnyObjectId objectId)
 	 * @throws IOException
 	 *             the object store cannot be accessed.
 	 */
+	@NonNull
 	public ObjectLoader open(AnyObjectId objectId, int typeHint)
 			throws MissingObjectException, IncorrectObjectTypeException,
 			IOException {
@@ -289,6 +328,7 @@ public ObjectLoader open(AnyObjectId objectId, int typeHint)
 	 *             a symbolic ref was passed in and could not be resolved back
 	 *             to the base ref, as the symbolic ref could not be read.
 	 */
+	@NonNull
 	public RefUpdate updateRef(final String ref) throws IOException {
 		return updateRef(ref, false);
 	}
@@ -307,6 +347,7 @@ public RefUpdate updateRef(final String ref) throws IOException {
 	 *             a symbolic ref was passed in and could not be resolved back
 	 *             to the base ref, as the symbolic ref could not be read.
 	 */
+	@NonNull
 	public RefUpdate updateRef(final String ref, final boolean detach) throws IOException {
 		return getRefDatabase().newUpdate(ref, detach);
 	}
@@ -323,6 +364,7 @@ public RefUpdate updateRef(final String ref, final boolean detach) throws IOExce
 	 *             the rename could not be performed.
 	 *
 	 */
+	@NonNull
 	public RefRename renameRef(final String fromRef, final String toRef) throws IOException {
 		return getRefDatabase().newRename(fromRef, toRef);
 	}
@@ -362,7 +404,8 @@ public RefRename renameRef(final String fromRef, final String toRef) throws IOEx
 	 *
 	 * @param revstr
 	 *            A git object references expression
-	 * @return an ObjectId or null if revstr can't be resolved to any ObjectId
+	 * @return an ObjectId or {@code null} if revstr can't be resolved to any
+	 *         ObjectId
 	 * @throws AmbiguousObjectException
 	 *             {@code revstr} contains an abbreviated ObjectId and this
 	 *             repository contains more than one object which match to the
@@ -376,6 +419,7 @@ public RefRename renameRef(final String fromRef, final String toRef) throws IOEx
 	 * @throws IOException
 	 *             on serious errors
 	 */
+	@Nullable
 	public ObjectId resolve(final String revstr)
 			throws AmbiguousObjectException, IncorrectObjectTypeException,
 			RevisionSyntaxException, IOException {
@@ -397,10 +441,12 @@ public ObjectId resolve(final String revstr)
 	 * expects a branch or revision id.
 	 *
 	 * @param revstr
-	 * @return object id or ref name from resolved expression
+	 * @return object id or ref name from resolved expression or {@code null} if
+	 *         given expression cannot be resolved
 	 * @throws AmbiguousObjectException
 	 * @throws IOException
 	 */
+	@Nullable
 	public String simplify(final String revstr)
 			throws AmbiguousObjectException, IOException {
 		try (RevWalk rw = new RevWalk(this)) {
@@ -414,6 +460,7 @@ public String simplify(final String revstr)
 		}
 	}
 
+	@Nullable
 	private Object resolve(final RevWalk rw, final String revstr)
 			throws IOException {
 		char[] revChars = revstr.toCharArray();
@@ -717,11 +764,13 @@ private static boolean isAllHex(String str, int ptr) {
 		return true;
 	}
 
+	@Nullable
 	private RevObject parseSimple(RevWalk rw, String revstr) throws IOException {
 		ObjectId id = resolveSimple(revstr);
 		return id != null ? rw.parseAny(id) : null;
 	}
 
+	@Nullable
 	private ObjectId resolveSimple(final String revstr) throws IOException {
 		if (ObjectId.isId(revstr))
 			return ObjectId.fromString(revstr);
@@ -749,6 +798,7 @@ && isAllHex(revstr, dashg + 4)) {
 		return null;
 	}
 
+	@Nullable
 	private String resolveReflogCheckout(int checkoutNo)
 			throws IOException {
 		ReflogReader reader = getReflogReader(Constants.HEAD);
@@ -790,6 +840,7 @@ private RevCommit resolveReflog(RevWalk rw, Ref ref, String time)
 		return rw.parseCommit(entry.getNewId());
 	}
 
+	@Nullable
 	private ObjectId resolveAbbreviation(final String revstr) throws IOException,
 			AmbiguousObjectException {
 		AbbreviatedObjectId id = AbbreviatedObjectId.fromString(revstr);
@@ -826,11 +877,13 @@ protected void doClose() {
 		getRefDatabase().close();
 	}
 
+	@NonNull
 	@SuppressWarnings("nls")
 	public String toString() {
 		String desc;
-		if (getDirectory() != null)
-			desc = getDirectory().getPath();
+		File directory = getDirectory();
+		if (directory != null)
+			desc = directory.getPath();
 		else
 			desc = getClass().getSimpleName() + "-" //$NON-NLS-1$
 					+ System.identityHashCode(this);
@@ -850,10 +903,12 @@ public String toString() {
 	 * current ObjectId in hexadecimal string format.
 	 *
 	 * @return name of current branch (for example {@code refs/heads/master}),
-	 *         an ObjectId in hex format if the current branch is detached,
-	 *         or null if the repository is corrupt and has no HEAD reference.
+	 *         an ObjectId in hex format if the current branch is detached, or
+	 *         {@code null} if the repository is corrupt and has no HEAD
+	 *         reference.
 	 * @throws IOException
 	 */
+	@Nullable
 	public String getFullBranch() throws IOException {
 		Ref head = getRef(Constants.HEAD);
 		if (head == null)
@@ -872,16 +927,17 @@ public String getFullBranch() throws IOException {
 	 * leading prefix {@code refs/heads/} is removed from the reference before
 	 * it is returned to the caller.
 	 *
-	 * @return name of current branch (for example {@code master}), an
-	 *         ObjectId in hex format if the current branch is detached,
-	 *         or null if the repository is corrupt and has no HEAD reference.
+	 * @return name of current branch (for example {@code master}), an ObjectId
+	 *         in hex format if the current branch is detached, or {@code null}
+	 *         if the repository is corrupt and has no HEAD reference.
 	 * @throws IOException
 	 */
+	@Nullable
 	public String getBranch() throws IOException {
 		String name = getFullBranch();
 		if (name != null)
 			return shortenRefName(name);
-		return name;
+		return null;
 	}
 
 	/**
@@ -894,6 +950,7 @@ public String getBranch() throws IOException {
 	 *
 	 * @return unmodifiable collection of other known objects.
 	 */
+	@NonNull
 	public Set<ObjectId> getAdditionalHaves() {
 		return Collections.emptySet();
 	}
@@ -905,16 +962,53 @@ public Set<ObjectId> getAdditionalHaves() {
 	 *            the name of the ref to lookup. May be a short-hand form, e.g.
 	 *            "master" which is is automatically expanded to
 	 *            "refs/heads/master" if "refs/heads/master" already exists.
-	 * @return the Ref with the given name, or null if it does not exist
+	 * @return the Ref with the given name, or {@code null} if it does not exist
 	 * @throws IOException
+	 * @deprecated Use {@link #exactRef(String)} or {@link #findRef(String)}
+	 * instead.
 	 */
+	@Deprecated
+	@Nullable
 	public Ref getRef(final String name) throws IOException {
+		return findRef(name);
+	}
+
+	/**
+	 * Get a ref by name.
+	 *
+	 * @param name
+	 *            the name of the ref to lookup. Must not be a short-hand
+	 *            form; e.g., "master" is not automatically expanded to
+	 *            "refs/heads/master".
+	 * @return the Ref with the given name, or {@code null} if it does not exist
+	 * @throws IOException
+	 * @since 4.2
+	 */
+	@Nullable
+	public Ref exactRef(String name) throws IOException {
+		return getRefDatabase().exactRef(name);
+	}
+
+	/**
+	 * Search for a ref by (possibly abbreviated) name.
+	 *
+	 * @param name
+	 *            the name of the ref to lookup. May be a short-hand form, e.g.
+	 *            "master" which is is automatically expanded to
+	 *            "refs/heads/master" if "refs/heads/master" already exists.
+	 * @return the Ref with the given name, or {@code null} if it does not exist
+	 * @throws IOException
+	 * @since 4.2
+	 */
+	@Nullable
+	public Ref findRef(String name) throws IOException {
 		return getRefDatabase().getRef(name);
 	}
 
 	/**
 	 * @return mutable map of all known refs (heads, tags, remotes).
 	 */
+	@NonNull
 	public Map<String, Ref> getAllRefs() {
 		try {
 			return getRefDatabase().getRefs(RefDatabase.ALL);
@@ -928,6 +1022,7 @@ public Map<String, Ref> getAllRefs() {
 	 *         of the entry contains the ref with the full tag name
 	 *         ("refs/tags/v1.0").
 	 */
+	@NonNull
 	public Map<String, Ref> getTags() {
 		try {
 			return getRefDatabase().getRefs(Constants.R_TAGS);
@@ -949,6 +1044,7 @@ public Map<String, Ref> getTags() {
 	 *         will be true and getPeeledObjectId will contain the peeled object
 	 *         (or null).
 	 */
+	@NonNull
 	public Ref peel(final Ref ref) {
 		try {
 			return getRefDatabase().peel(ref);
@@ -963,6 +1059,7 @@ public Ref peel(final Ref ref) {
 	/**
 	 * @return a map with all objects referenced by a peeled ref.
 	 */
+	@NonNull
 	public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId() {
 		Map<String, Ref> allRefs = getAllRefs();
 		Map<AnyObjectId, Set<Ref>> ret = new HashMap<AnyObjectId, Set<Ref>>(allRefs.size());
@@ -987,11 +1084,13 @@ public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId() {
 	}
 
 	/**
-	 * @return the index file location
+	 * @return the index file location or {@code null} if repository isn't
+	 *         local.
 	 * @throws NoWorkTreeException
 	 *             if this is bare, which implies it has no working directory.
 	 *             See {@link #isBare()}.
 	 */
+	@NonNull
 	public File getIndexFile() throws NoWorkTreeException {
 		if (isBare())
 			throw new NoWorkTreeException();
@@ -1016,6 +1115,7 @@ public File getIndexFile() throws NoWorkTreeException {
 	 *             the index file is using a format or extension that this
 	 *             library does not support.
 	 */
+	@NonNull
 	public DirCache readDirCache() throws NoWorkTreeException,
 			CorruptObjectException, IOException {
 		return DirCache.read(this);
@@ -1040,6 +1140,7 @@ public DirCache readDirCache() throws NoWorkTreeException,
 	 *             the index file is using a format or extension that this
 	 *             library does not support.
 	 */
+	@NonNull
 	public DirCache lockDirCache() throws NoWorkTreeException,
 			CorruptObjectException, IOException {
 		// we want DirCache to inform us so that we can inform registered
@@ -1065,6 +1166,7 @@ public void onIndexChanged(IndexChangedEvent event) {
 	/**
 	 * @return an important state
 	 */
+	@NonNull
 	public RepositoryState getRepositoryState() {
 		if (isBare() || getDirectory() == null)
 			return RepositoryState.BARE;
@@ -1207,6 +1309,7 @@ public static boolean isValidRefName(final String refName) {
 	 * @return normalized repository relative path or the empty
 	 *         string if the file is not relative to the work directory.
 	 */
+	@NonNull
 	public static String stripWorkDir(File workDir, File file) {
 		final String filePath = file.getPath();
 		final String workDirPath = workDir.getPath();
@@ -1241,6 +1344,7 @@ public boolean isBare() {
 	 *             if this is bare, which implies it has no working directory.
 	 *             See {@link #isBare()}.
 	 */
+	@NonNull
 	public File getWorkTree() throws NoWorkTreeException {
 		if (isBare())
 			throw new NoWorkTreeException();
@@ -1264,6 +1368,7 @@ public File getWorkTree() throws NoWorkTreeException {
 	 *
 	 * @return a more user friendly ref name
 	 */
+	@NonNull
 	public static String shortenRefName(String refName) {
 		if (refName.startsWith(Constants.R_HEADS))
 			return refName.substring(Constants.R_HEADS.length());
@@ -1279,9 +1384,10 @@ public static String shortenRefName(String refName) {
 	 * @return the remote branch name part of <code>refName</code>, i.e. without
 	 *         the <code>refs/remotes/&lt;remote&gt;</code> prefix, if
 	 *         <code>refName</code> represents a remote tracking branch;
-	 *         otherwise null.
+	 *         otherwise {@code null}.
 	 * @since 3.4
 	 */
+	@Nullable
 	public String shortenRemoteBranchName(String refName) {
 		for (String remote : getRemoteNames()) {
 			String remotePrefix = Constants.R_REMOTES + remote + "/"; //$NON-NLS-1$
@@ -1296,9 +1402,10 @@ public String shortenRemoteBranchName(String refName) {
 	 * @return the remote name part of <code>refName</code>, i.e. without the
 	 *         <code>refs/remotes/&lt;remote&gt;</code> prefix, if
 	 *         <code>refName</code> represents a remote tracking branch;
-	 *         otherwise null.
+	 *         otherwise {@code null}.
 	 * @since 3.4
 	 */
+	@Nullable
 	public String getRemoteName(String refName) {
 		for (String remote : getRemoteNames()) {
 			String remotePrefix = Constants.R_REMOTES + remote + "/"; //$NON-NLS-1$
@@ -1310,12 +1417,13 @@ public String getRemoteName(String refName) {
 
 	/**
 	 * @param refName
-	 * @return a {@link ReflogReader} for the supplied refname, or null if the
-	 *         named ref does not exist.
+	 * @return a {@link ReflogReader} for the supplied refname, or {@code null}
+	 *         if the named ref does not exist.
 	 * @throws IOException
 	 *             the ref could not be accessed.
 	 * @since 3.0
 	 */
+	@Nullable
 	public abstract ReflogReader getReflogReader(String refName)
 			throws IOException;
 
@@ -1331,6 +1439,7 @@ public abstract ReflogReader getReflogReader(String refName)
 	 *             if this is bare, which implies it has no working directory.
 	 *             See {@link #isBare()}.
 	 */
+	@Nullable
 	public String readMergeCommitMsg() throws IOException, NoWorkTreeException {
 		return readCommitMsgFile(Constants.MERGE_MSG);
 	}
@@ -1365,6 +1474,7 @@ public void writeMergeCommitMsg(String msg) throws IOException {
 	 *             See {@link #isBare()}.
 	 * @since 4.0
 	 */
+	@Nullable
 	public String readCommitEditMsg() throws IOException, NoWorkTreeException {
 		return readCommitMsgFile(Constants.COMMIT_EDITMSG);
 	}
@@ -1399,6 +1509,7 @@ public void writeCommitEditMsg(String msg) throws IOException {
 	 *             if this is bare, which implies it has no working directory.
 	 *             See {@link #isBare()}.
 	 */
+	@Nullable
 	public List<ObjectId> readMergeHeads() throws IOException, NoWorkTreeException {
 		if (isBare() || getDirectory() == null)
 			throw new NoWorkTreeException();
@@ -1442,6 +1553,7 @@ public void writeMergeHeads(List<? extends ObjectId> heads) throws IOException {
 	 *             if this is bare, which implies it has no working directory.
 	 *             See {@link #isBare()}.
 	 */
+	@Nullable
 	public ObjectId readCherryPickHead() throws IOException,
 			NoWorkTreeException {
 		if (isBare() || getDirectory() == null)
@@ -1465,6 +1577,7 @@ public ObjectId readCherryPickHead() throws IOException,
 	 *             if this is bare, which implies it has no working directory.
 	 *             See {@link #isBare()}.
 	 */
+	@Nullable
 	public ObjectId readRevertHead() throws IOException, NoWorkTreeException {
 		if (isBare() || getDirectory() == null)
 			throw new NoWorkTreeException();
@@ -1530,6 +1643,7 @@ public void writeOrigHead(ObjectId head) throws IOException {
 	 *             if this is bare, which implies it has no working directory.
 	 *             See {@link #isBare()}.
 	 */
+	@Nullable
 	public ObjectId readOrigHead() throws IOException, NoWorkTreeException {
 		if (isBare() || getDirectory() == null)
 			throw new NoWorkTreeException();
@@ -1550,6 +1664,7 @@ public ObjectId readOrigHead() throws IOException, NoWorkTreeException {
 	 *             if this is bare, which implies it has no working directory.
 	 *             See {@link #isBare()}.
 	 */
+	@Nullable
 	public String readSquashCommitMsg() throws IOException {
 		return readCommitMsgFile(Constants.SQUASH_MSG);
 	}
@@ -1571,6 +1686,7 @@ public void writeSquashCommitMsg(String msg) throws IOException {
 		writeCommitMsg(squashMsgFile, msg);
 	}
 
+	@Nullable
 	private String readCommitMsgFile(String msgFilename) throws IOException {
 		if (isBare() || getDirectory() == null)
 			throw new NoWorkTreeException();
@@ -1579,6 +1695,9 @@ private String readCommitMsgFile(String msgFilename) throws IOException {
 		try {
 			return RawParseUtils.decode(IO.readFully(mergeMsgFile));
 		} catch (FileNotFoundException e) {
+			if (mergeMsgFile.exists()) {
+				throw e;
+			}
 			// the file has disappeared in the meantime ignore it
 			return null;
 		}
@@ -1601,15 +1720,20 @@ private void writeCommitMsg(File msgFile, String msg) throws IOException {
 	 * Read a file from the git directory.
 	 *
 	 * @param filename
-	 * @return the raw contents or null if the file doesn't exist or is empty
+	 * @return the raw contents or {@code null} if the file doesn't exist or is
+	 *         empty
 	 * @throws IOException
 	 */
+	@Nullable
 	private byte[] readGitDirectoryFile(String filename) throws IOException {
 		File file = new File(getDirectory(), filename);
 		try {
 			byte[] raw = IO.readFully(file);
 			return raw.length > 0 ? raw : null;
 		} catch (FileNotFoundException notFound) {
+			if (file.exists()) {
+				throw notFound;
+			}
 			return null;
 		}
 	}
@@ -1657,6 +1781,7 @@ private void writeHeadsFile(List<? extends ObjectId> heads, String filename)
 	 * @throws IOException
 	 * @since 3.2
 	 */
+	@NonNull
 	public List<RebaseTodoLine> readRebaseTodo(String path,
 			boolean includeComments)
 			throws IOException {
@@ -1686,6 +1811,7 @@ public void writeRebaseTodoFile(String path, List<RebaseTodoLine> steps,
 	 * @return the names of all known remotes
 	 * @since 3.4
 	 */
+	@NonNull
 	public Set<String> getRemoteNames() {
 		return getConfig()
 				.getSubsections(ConfigConstants.CONFIG_REMOTE_SECTION);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
index aef47c5..e055644 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
@@ -57,8 +57,6 @@
 import java.util.TimeZone;
 
 import org.eclipse.jgit.dircache.DirCache;
-import org.eclipse.jgit.dircache.DirCacheBuilder;
-import org.eclipse.jgit.dircache.DirCacheEntry;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.NoMergeBaseException;
 import org.eclipse.jgit.internal.JGitText;
@@ -70,7 +68,6 @@
 import org.eclipse.jgit.revwalk.filter.RevFilter;
 import org.eclipse.jgit.treewalk.AbstractTreeIterator;
 import org.eclipse.jgit.treewalk.EmptyTreeIterator;
-import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.treewalk.WorkingTreeIterator;
 
 /**
@@ -181,7 +178,7 @@ protected RevCommit getBaseCommit(RevCommit a, RevCommit b, int callDepth)
 		WorkingTreeIterator oldWTreeIt = workingTreeIterator;
 		workingTreeIterator = null;
 		try {
-			dircache = dircacheFromTree(currentBase.getTree());
+			dircache = DirCache.read(reader, currentBase.getTree());
 			inCore = true;
 
 			List<RevCommit> parents = new ArrayList<RevCommit>();
@@ -256,30 +253,4 @@ private static PersonIdent mockAuthor(List<RevCommit> parents) {
 				new Date((time + 1) * 1000L),
 				TimeZone.getTimeZone("GMT+0000")); //$NON-NLS-1$
 	}
-
-	/**
-	 * Create a new in memory dircache which has the same content as a given
-	 * tree.
-	 *
-	 * @param treeId
-	 *            the tree which should be used to fill the dircache
-	 * @return a new in memory dircache
-	 * @throws IOException
-	 */
-	private DirCache dircacheFromTree(ObjectId treeId) throws IOException {
-		DirCache ret = DirCache.newInCore();
-		DirCacheBuilder aBuilder = ret.builder();
-		try (TreeWalk atw = new TreeWalk(reader)) {
-			atw.addTree(treeId);
-			atw.setRecursive(true);
-			while (atw.next()) {
-				DirCacheEntry e = new DirCacheEntry(atw.getRawPath());
-				e.setFileMode(atw.getFileMode(0));
-				e.setObjectId(atw.getObjectId(0));
-				aBuilder.add(e);
-			}
-		}
-		aBuilder.finish();
-		return ret;
-	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
index 8a6343c..de08e4b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -786,11 +786,6 @@ private void updateIndex(CanonicalTreeParser base,
 	private File writeMergedFile(MergeResult<RawText> result)
 			throws FileNotFoundException, IOException {
 		File workTree = db.getWorkTree();
-		if (workTree == null)
-			// TODO: This should be handled by WorkingTreeIterators which
-			// support write operations
-			throw new UnsupportedOperationException();
-
 		FS fs = db.getFS();
 		File of = new File(workTree, tw.getPathString());
 		File parentFolder = of.getParentFile();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
index 5509fc6..29c7f98 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
@@ -163,6 +163,9 @@ public void load() throws IOException, ConfigInvalidException {
 				hash = newHash;
 			}
 		} catch (FileNotFoundException noFile) {
+			if (configFile.exists()) {
+				throw noFile;
+			}
 			clear();
 			snapshot = newSnapshot;
 		} catch (IOException e) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
index 24fb3be..0834c35 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
@@ -44,6 +44,8 @@
 
 package org.eclipse.jgit.transport;
 
+import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC;
+
 import java.io.IOException;
 import java.io.OutputStream;
 import java.text.MessageFormat;
@@ -110,17 +112,15 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen
 	public static final String CAPABILITY_SIDE_BAND_64K = GitProtocolConstants.CAPABILITY_SIDE_BAND_64K;
 
 	private final boolean thinPack;
+	private final boolean atomic;
 
+	private boolean capableAtomic;
 	private boolean capableDeleteRefs;
-
 	private boolean capableReport;
-
 	private boolean capableSideBand;
-
 	private boolean capableOfsDelta;
 
 	private boolean sentCommand;
-
 	private boolean writePack;
 
 	/** Time in milliseconds spent transferring the pack data. */
@@ -135,6 +135,7 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen
 	public BasePackPushConnection(final PackTransport packTransport) {
 		super(packTransport);
 		thinPack = transport.isPushThin();
+		atomic = transport.isPushAtomic();
 	}
 
 	public void push(final ProgressMonitor monitor,
@@ -224,6 +225,11 @@ protected void doPush(final ProgressMonitor monitor,
 	private void writeCommands(final Collection<RemoteRefUpdate> refUpdates,
 			final ProgressMonitor monitor, OutputStream outputStream) throws IOException {
 		final String capabilities = enableCapabilities(monitor, outputStream);
+		if (atomic && !capableAtomic) {
+			throw new TransportException(uri,
+					JGitText.get().atomicPushNotSupported);
+		}
+
 		for (final RemoteRefUpdate rru : refUpdates) {
 			if (!capableDeleteRefs && rru.isDelete()) {
 				rru.setStatus(Status.REJECTED_NODELETE);
@@ -231,9 +237,11 @@ private void writeCommands(final Collection<RemoteRefUpdate> refUpdates,
 			}
 
 			final StringBuilder sb = new StringBuilder();
-			final Ref advertisedRef = getRef(rru.getRemoteName());
-			final ObjectId oldId = (advertisedRef == null ? ObjectId.zeroId()
-					: advertisedRef.getObjectId());
+			ObjectId oldId = rru.getExpectedOldObjectId();
+			if (oldId == null) {
+				Ref adv = getRef(rru.getRemoteName());
+				oldId = adv != null ? adv.getObjectId() : ObjectId.zeroId();
+			}
 			sb.append(oldId.name());
 			sb.append(' ');
 			sb.append(rru.getNewObjectId().name());
@@ -259,6 +267,8 @@ private void writeCommands(final Collection<RemoteRefUpdate> refUpdates,
 	private String enableCapabilities(final ProgressMonitor monitor,
 			OutputStream outputStream) {
 		final StringBuilder line = new StringBuilder();
+		if (atomic)
+			capableAtomic = wantCapability(line, CAPABILITY_ATOMIC);
 		capableReport = wantCapability(line, CAPABILITY_REPORT_STATUS);
 		capableDeleteRefs = wantCapability(line, CAPABILITY_DELETE_REFS);
 		capableOfsDelta = wantCapability(line, CAPABILITY_OFS_DELTA);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java
index 7e9434a..622680a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java
@@ -42,6 +42,7 @@
  */
 package org.eclipse.jgit.transport;
 
+import java.io.File;
 import java.io.UnsupportedEncodingException;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
@@ -85,12 +86,16 @@ public HMACSHA1NonceGenerator(String seed) throws IllegalStateException {
 	public synchronized String createNonce(Repository repo, long timestamp)
 			throws IllegalStateException {
 		String path;
-		if (repo instanceof DfsRepository)
+		if (repo instanceof DfsRepository) {
 			path = ((DfsRepository) repo).getDescription().getRepositoryName();
-		else if (repo.getDirectory() != null)
-			path = repo.getDirectory().getPath();
-		else
-			throw new IllegalStateException();
+		} else {
+			File directory = repo.getDirectory();
+			if (directory != null) {
+				path = directory.getPath();
+			} else {
+				throw new IllegalStateException();
+			}
+		}
 
 		String input = path + ":" + String.valueOf(timestamp); //$NON-NLS-1$
 		byte[] rawHmac;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java
index 3594ea9..998f280 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java
@@ -219,7 +219,8 @@ boolean authorize(URIish uri, CredentialsProvider credentialsProvider) {
 			if (credentialsProvider.supports(u, p)
 					&& credentialsProvider.get(uri, u, p)) {
 				username = u.getValue();
-				password = new String(p.getValue());
+				char[] v = p.getValue();
+				password = (v == null) ? null : new String(p.getValue());
 				p.clear();
 			} else
 				return false;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
index 8947f27..d436e08 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
@@ -65,7 +65,6 @@
 import java.util.NoSuchElementException;
 
 import org.eclipse.jgit.dircache.DirCache;
-import org.eclipse.jgit.dircache.DirCacheBuilder;
 import org.eclipse.jgit.dircache.DirCacheEditor;
 import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
 import org.eclipse.jgit.dircache.DirCacheEntry;
@@ -448,13 +447,10 @@ public int compare(PendingCert a, PendingCert b) {
 	}
 
 	private DirCache newDirCache() throws IOException {
-		DirCache dc = DirCache.newInCore();
 		if (commit != null) {
-			DirCacheBuilder b = dc.builder();
-			b.addTree(new byte[0], DirCacheEntry.STAGE_0, reader, commit.getTree());
-			b.finish();
+			return DirCache.read(reader, commit.getTree());
 		}
-		return dc;
+		return DirCache.newInCore();
 	}
 
 	private ObjectId saveCert(ObjectInserter inserter, DirCache dc,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
index 9721ee9..4fd192d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
@@ -47,6 +47,7 @@
 import java.io.OutputStream;
 import java.text.MessageFormat;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -183,6 +184,7 @@ else if (!preprocessed.isEmpty())
 
 	private Map<String, RemoteRefUpdate> prepareRemoteUpdates()
 			throws TransportException {
+		boolean atomic = transport.isPushAtomic();
 		final Map<String, RemoteRefUpdate> result = new HashMap<String, RemoteRefUpdate>();
 		for (final RemoteRefUpdate rru : toPush.values()) {
 			final Ref advertisedRef = connection.getRef(rru.getRemoteName());
@@ -205,8 +207,14 @@ private Map<String, RemoteRefUpdate> prepareRemoteUpdates()
 			if (rru.isExpectingOldObjectId()
 					&& !rru.getExpectedOldObjectId().equals(advertisedOld)) {
 				rru.setStatus(Status.REJECTED_REMOTE_CHANGED);
+				if (atomic) {
+					return rejectAll();
+				}
 				continue;
 			}
+			if (!rru.isExpectingOldObjectId()) {
+				rru.setExpectedOldObjectId(advertisedOld);
+			}
 
 			// create ref (hasn't existed on remote side) and delete ref
 			// are always fast-forward commands, feasible at this level
@@ -236,14 +244,28 @@ private Map<String, RemoteRefUpdate> prepareRemoteUpdates()
 						JGitText.get().readingObjectsFromLocalRepositoryFailed, x.getMessage()), x);
 			}
 			rru.setFastForward(fastForward);
-			if (!fastForward && !rru.isForceUpdate())
+			if (!fastForward && !rru.isForceUpdate()) {
 				rru.setStatus(Status.REJECTED_NONFASTFORWARD);
-			else
+				if (atomic) {
+					return rejectAll();
+				}
+			} else {
 				result.put(rru.getRemoteName(), rru);
+			}
 		}
 		return result;
 	}
 
+	private Map<String, RemoteRefUpdate> rejectAll() {
+		for (RemoteRefUpdate rru : toPush.values()) {
+			if (rru.getStatus() == Status.NOT_ATTEMPTED) {
+				rru.setStatus(RemoteRefUpdate.Status.REJECTED_OTHER_REASON);
+				rru.setMessage(JGitText.get().transactionAborted);
+			}
+		}
+		return Collections.emptyMap();
+	}
+
 	private void modifyUpdatesForDryRun() {
 		for (final RemoteRefUpdate rru : toPush.values())
 			if (rru.getStatus() == Status.NOT_ATTEMPTED)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
index 7c44dba..5702b6d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
@@ -46,6 +46,7 @@
 import java.io.IOException;
 import java.text.MessageFormat;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 
 import org.eclipse.jgit.internal.JGitText;
@@ -127,6 +128,31 @@ public static enum Result {
 	}
 
 	/**
+	 * Filter a collection of commands according to result.
+	 *
+	 * @param in
+	 *            commands to filter.
+	 * @param want
+	 *            desired status to filter by.
+	 * @return a copy of the command list containing only those commands with
+	 *         the desired status.
+	 * @since 4.2
+	 */
+	public static List<ReceiveCommand> filter(Iterable<ReceiveCommand> in,
+			Result want) {
+		List<ReceiveCommand> r;
+		if (in instanceof Collection)
+			r = new ArrayList<>(((Collection<?>) in).size());
+		else
+			r = new ArrayList<>();
+		for (ReceiveCommand cmd : in) {
+			if (cmd.getResult() == want)
+				r.add(cmd);
+		}
+		return r;
+	}
+
+	/**
 	 * Filter a list of commands according to result.
 	 *
 	 * @param commands
@@ -138,13 +164,8 @@ public static enum Result {
 	 * @since 2.0
 	 */
 	public static List<ReceiveCommand> filter(List<ReceiveCommand> commands,
-			final Result want) {
-		List<ReceiveCommand> r = new ArrayList<ReceiveCommand>(commands.size());
-		for (final ReceiveCommand cmd : commands) {
-			if (cmd.getResult() == want)
-				r.add(cmd);
-		}
-		return r;
+			Result want) {
+		return filter((Iterable<ReceiveCommand>) commands, want);
 	}
 
 	private final ObjectId oldId;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
index 1b82a36..5c58346 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
@@ -125,7 +125,7 @@ public static enum Status {
 		OK;
 	}
 
-	private final ObjectId expectedOldObjectId;
+	private ObjectId expectedOldObjectId;
 
 	private final ObjectId newObjectId;
 
@@ -440,6 +440,10 @@ public String getMessage() {
 		return message;
 	}
 
+	void setExpectedOldObjectId(ObjectId id) {
+		expectedOldObjectId = id;
+	}
+
 	void setStatus(final Status status) {
 		this.status = status;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
index cc7db47..6af153c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
@@ -752,6 +752,9 @@ private static String findTrackingRefName(final String remoteName,
 	/** Should push produce thin-pack when sending objects to remote repository. */
 	private boolean pushThin = DEFAULT_PUSH_THIN;
 
+	/** Should push be all-or-nothing atomic behavior? */
+	private boolean pushAtomic;
+
 	/** Should push just check for operation result, not really push. */
 	private boolean dryRun;
 
@@ -970,6 +973,31 @@ public void setPushThin(final boolean pushThin) {
 	}
 
 	/**
+	 * Default setting is false.
+	 *
+	 * @return true if push requires all-or-nothing atomic behavior.
+	 * @since 4.2
+	 */
+	public boolean isPushAtomic() {
+		return pushAtomic;
+	}
+
+	/**
+	 * Request atomic push (all references succeed, or none do).
+	 * <p>
+	 * Server must also support atomic push. If the server does not support the
+	 * feature the push will abort without making changes.
+	 *
+	 * @param atomic
+	 *            true when push should be an all-or-nothing operation.
+	 * @see PackTransport
+	 * @since 4.2
+	 */
+	public void setPushAtomic(final boolean atomic) {
+		this.pushAtomic = atomic;
+	}
+
+	/**
 	 * @return true if destination refs should be removed if they no longer
 	 *         exist at the source repository.
 	 */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
index 7729c11..23c506b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
@@ -148,8 +148,9 @@ public Transport open(URIish uri, Repository local, String remoteName)
 		super(local, uri);
 
 		Properties props = loadProperties();
-		if (!props.containsKey("tmpdir") && local.getDirectory() != null) //$NON-NLS-1$
-			props.put("tmpdir", local.getDirectory().getPath()); //$NON-NLS-1$
+		File directory = local.getDirectory();
+		if (!props.containsKey("tmpdir") && directory != null) //$NON-NLS-1$
+			props.put("tmpdir", directory.getPath()); //$NON-NLS-1$
 
 		s3 = new AmazonS3(props);
 		bucket = uri.getHost();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java
index b27fa0d..52f0f04 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java
@@ -46,6 +46,7 @@
 
 package org.eclipse.jgit.transport;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.text.MessageFormat;
@@ -235,9 +236,10 @@ public Process exec(String command, int timeout)
 			ProcessBuilder pb = new ProcessBuilder();
 			pb.command(args);
 
-			if (local.getDirectory() != null)
+			File directory = local.getDirectory();
+			if (directory != null)
 				pb.environment().put(Constants.GIT_DIR_KEY,
-						local.getDirectory().getPath());
+						directory.getPath());
 
 			try {
 				return pb.start();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
index 3700b49..9aeb840 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
@@ -137,7 +137,11 @@ public class URIish implements Serializable {
 			+ OPT_PORT_P //
 			+ "(" // open a group capturing the user-home-dir-part //$NON-NLS-1$
 			+ (USER_HOME_P + "?") //$NON-NLS-1$
-			+ "[\\\\/])" //$NON-NLS-1$
+			+ "(?:" // start non capturing group for host //$NON-NLS-1$
+					// separator or end of line
+			+ "[\\\\/])|$" //$NON-NLS-1$
+			+ ")" // close non capturing group for the host//$NON-NLS-1$
+					// separator or end of line
 			+ ")?" // close the optional group containing hostname //$NON-NLS-1$
 			+ "(.+)?" //$NON-NLS-1$
 			+ "$"); //$NON-NLS-1$
@@ -593,6 +597,8 @@ public boolean equals(final Object obj) {
 	private static boolean eq(final String a, final String b) {
 		if (a == b)
 			return true;
+		if (StringUtils.isEmptyOrNull(a) && StringUtils.isEmptyOrNull(b))
+			return true;
 		if (a == null || b == null)
 			return false;
 		return a.equals(b);
@@ -638,7 +644,7 @@ private String format(final boolean includePassword, boolean escapeNonAscii) {
 
 		if (getPath() != null) {
 			if (getScheme() != null) {
-				if (!getPath().startsWith("/")) //$NON-NLS-1$
+				if (!getPath().startsWith("/") && !getPath().isEmpty()) //$NON-NLS-1$
 					r.append('/');
 			} else if (getHost() != null)
 				r.append(':');
@@ -709,9 +715,9 @@ public String toPrivateASCIIString() {
 	 */
 	public String getHumanishName() throws IllegalArgumentException {
 		String s = getPath();
-		if ("/".equals(s)) //$NON-NLS-1$
+		if ("/".equals(s) || "".equals(s)) //$NON-NLS-1$
 			s = getHost();
-		if ("".equals(s) || s == null) //$NON-NLS-1$
+		if (s == null) // $NON-NLS-1$
 			throw new IllegalArgumentException();
 
 		String[] elements;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
index 41e593e..5e71889 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
@@ -49,6 +49,7 @@
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 
+import org.eclipse.jgit.attributes.AttributesNode;
 import org.eclipse.jgit.dircache.DirCacheCheckout;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -93,6 +94,13 @@ public abstract class AbstractTreeIterator {
 	AbstractTreeIterator matches;
 
 	/**
+	 * Parsed rules of .gitattributes file if it exists.
+	 *
+	 * @since 4.2
+	 */
+	protected AttributesNode attributesNode;
+
+	/**
 	 * Number of entries we moved forward to force a D/F conflict match.
 	 *
 	 * @see NameConflictTreeWalk
@@ -320,6 +328,42 @@ int pathCompare(final AbstractTreeIterator p, final int pMode) {
 	}
 
 	/**
+	 * Seek the iterator on a file, if present.
+	 *
+	 * @param name
+	 *            file name to find (will not find a directory).
+	 * @return true if the file exists in this tree; false otherwise.
+	 * @throws CorruptObjectException
+	 *             tree is invalid.
+	 * @since 4.2
+	 */
+	public boolean findFile(String name) throws CorruptObjectException {
+		return findFile(Constants.encode(name));
+	}
+
+	/**
+	 * Seek the iterator on a file, if present.
+	 *
+	 * @param name
+	 *            file name to find (will not find a directory).
+	 * @return true if the file exists in this tree; false otherwise.
+	 * @throws CorruptObjectException
+	 *             tree is invalid.
+	 * @since 4.2
+	 */
+	public boolean findFile(byte[] name) throws CorruptObjectException {
+		for (; !eof(); next(1)) {
+			int cmp = pathCompare(name, 0, name.length, 0, pathOffset);
+			if (cmp == 0) {
+				return true;
+			} else if (cmp > 0) {
+				return false;
+			}
+		}
+		return false;
+	}
+
+	/**
 	 * Compare the path of this current entry to a raw buffer.
 	 *
 	 * @param buf
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java
index 0805e50..c038f07 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java
@@ -44,13 +44,23 @@
 
 package org.eclipse.jgit.treewalk;
 
-import java.io.IOException;
-import java.util.Arrays;
+import static org.eclipse.jgit.lib.Constants.DOT_GIT_ATTRIBUTES;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
+import static org.eclipse.jgit.lib.Constants.TYPE_TREE;
+import static org.eclipse.jgit.lib.Constants.encode;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.eclipse.jgit.attributes.AttributesNode;
+import org.eclipse.jgit.attributes.AttributesRule;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.AnyObjectId;
-import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.MutableObjectId;
 import org.eclipse.jgit.lib.ObjectId;
@@ -59,6 +69,7 @@
 /** Parses raw Git trees from the canonical semi-text/semi-binary format. */
 public class CanonicalTreeParser extends AbstractTreeIterator {
 	private static final byte[] EMPTY = {};
+	private static final byte[] ATTRS = encode(DOT_GIT_ATTRIBUTES);
 
 	private byte[] raw;
 
@@ -124,6 +135,7 @@ public CanonicalTreeParser getParent() {
 	 *            the raw tree content.
 	 */
 	public void reset(final byte[] treeData) {
+		attributesNode = null;
 		raw = treeData;
 		prevPtr = -1;
 		currPtr = 0;
@@ -199,7 +211,7 @@ public CanonicalTreeParser next() {
 	 */
 	public void reset(final ObjectReader reader, final AnyObjectId id)
 			throws IncorrectObjectTypeException, IOException {
-		reset(reader.open(id, Constants.OBJ_TREE).getCachedBytes());
+		reset(reader.open(id, OBJ_TREE).getCachedBytes());
 	}
 
 	@Override
@@ -209,7 +221,7 @@ public CanonicalTreeParser createSubtreeIterator(final ObjectReader reader,
 		idBuffer.fromRaw(idBuffer(), idOffset());
 		if (!FileMode.TREE.equals(mode)) {
 			final ObjectId me = idBuffer.toObjectId();
-			throw new IncorrectObjectTypeException(me, Constants.TYPE_TREE);
+			throw new IncorrectObjectTypeException(me, TYPE_TREE);
 		}
 		return createSubtreeIterator0(reader, idBuffer);
 	}
@@ -254,7 +266,7 @@ public boolean hasId() {
 
 	@Override
 	public int idOffset() {
-		return nextPtr - Constants.OBJECT_ID_LENGTH;
+		return nextPtr - OBJECT_ID_LENGTH;
 	}
 
 	@Override
@@ -292,7 +304,7 @@ public void next(int delta) {
 			prevPtr = ptr;
 			while (raw[ptr] != 0)
 				ptr++;
-			ptr += Constants.OBJECT_ID_LENGTH + 1;
+			ptr += OBJECT_ID_LENGTH + 1;
 		}
 		if (delta != 0)
 			throw new ArrayIndexOutOfBoundsException(delta);
@@ -328,7 +340,7 @@ public void back(int delta) {
 			trace[delta] = ptr;
 			while (raw[ptr] != 0)
 				ptr++;
-			ptr += Constants.OBJECT_ID_LENGTH + 1;
+			ptr += OBJECT_ID_LENGTH + 1;
 		}
 		if (trace[1] == -1)
 			throw new ArrayIndexOutOfBoundsException(delta);
@@ -363,6 +375,46 @@ private void parseEntry() {
 			}
 		}
 		pathLen = tmp;
-		nextPtr = ptr + Constants.OBJECT_ID_LENGTH;
+		nextPtr = ptr + OBJECT_ID_LENGTH;
+	}
+
+	/**
+	 * Retrieve the {@link AttributesNode} for the current entry.
+	 *
+	 * @param reader
+	 *            {@link ObjectReader} used to parse the .gitattributes entry.
+	 * @return {@link AttributesNode} for the current entry.
+	 * @throws IOException
+	 * @since 4.2
+	 */
+	public AttributesNode getEntryAttributesNode(ObjectReader reader)
+			throws IOException {
+		if (attributesNode == null) {
+			attributesNode = findAttributes(reader);
+		}
+		return attributesNode.getRules().isEmpty() ? null : attributesNode;
+	}
+
+	private AttributesNode findAttributes(ObjectReader reader)
+			throws IOException {
+		CanonicalTreeParser itr = new CanonicalTreeParser();
+		itr.reset(raw);
+		if (itr.findFile(ATTRS)) {
+			return loadAttributes(reader, itr.getEntryObjectId());
+		}
+		return noAttributes();
+	}
+
+	private static AttributesNode loadAttributes(ObjectReader reader,
+			AnyObjectId id) throws IOException {
+		AttributesNode r = new AttributesNode();
+		try (InputStream in = reader.open(id, OBJ_BLOB).openStream()) {
+			r.parse(in);
+		}
+		return r.getRules().isEmpty() ? noAttributes() : r;
+	}
+
+	private static AttributesNode noAttributes() {
+		return new AttributesNode(Collections.<AttributesRule> emptyList());
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
index 8d2cb1d..accf495 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
@@ -67,8 +67,8 @@
  */
 public class FileTreeIterator extends WorkingTreeIterator {
 	/**
-	 * the starting directory. This directory should correspond to the root of
-	 * the repository.
+	 * the starting directory of this Iterator. All entries are located directly
+	 * in this directory.
 	 */
 	protected final File directory;
 
@@ -238,8 +238,6 @@ public File getEntryFile() {
 
 	@Override
 	protected byte[] idSubmodule(final Entry e) {
-		if (repository == null)
-			return idSubmodule(getDirectory(), e);
-		return super.idSubmodule(e);
+		return idSubmodule(getDirectory(), e);
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
index 2d6acbd..350f563 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
@@ -96,7 +96,7 @@ public class NameConflictTreeWalk extends TreeWalk {
 	 *            the repository the walker will obtain data from.
 	 */
 	public NameConflictTreeWalk(final Repository repo) {
-		this(repo.newObjectReader());
+		super(repo);
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
index 06e8284..06dc0bf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
@@ -45,12 +45,25 @@
 package org.eclipse.jgit.treewalk;
 
 import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
 
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.attributes.Attribute;
+import org.eclipse.jgit.attributes.Attribute.State;
+import org.eclipse.jgit.attributes.Attributes;
+import org.eclipse.jgit.attributes.AttributesNode;
+import org.eclipse.jgit.attributes.AttributesNodeProvider;
+import org.eclipse.jgit.attributes.AttributesProvider;
+import org.eclipse.jgit.dircache.DirCacheIterator;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.StopWalkException;
 import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.MutableObjectId;
@@ -60,6 +73,7 @@
 import org.eclipse.jgit.revwalk.RevTree;
 import org.eclipse.jgit.treewalk.filter.PathFilter;
 import org.eclipse.jgit.treewalk.filter.TreeFilter;
+import org.eclipse.jgit.util.QuotedString;
 import org.eclipse.jgit.util.RawParseUtils;
 
 /**
@@ -82,10 +96,45 @@
  * Multiple simultaneous TreeWalk instances per {@link Repository} are
  * permitted, even from concurrent threads.
  */
-public class TreeWalk implements AutoCloseable {
+public class TreeWalk implements AutoCloseable, AttributesProvider {
 	private static final AbstractTreeIterator[] NO_TREES = {};
 
 	/**
+	 * @since 4.2
+	 */
+	public static enum OperationType {
+		/**
+		 * Represents a checkout operation (for example a checkout or reset
+		 * operation).
+		 */
+		CHECKOUT_OP,
+
+		/**
+		 * Represents a checkin operation (for example an add operation)
+		 */
+		CHECKIN_OP
+	}
+
+	/**
+	 *            Type of operation you want to retrieve the git attributes for.
+	 */
+	private OperationType operationType = OperationType.CHECKOUT_OP;
+
+	/**
+	 * The filter command as defined in gitattributes. The keys are
+	 * filterName+"."+filterCommandType. E.g. "lfs.clean"
+	 */
+	private Map<String, String> filterCommandsByNameDotType = new HashMap<String, String>();
+
+	/**
+	 * @param operationType
+	 * @since 4.2
+	 */
+	public void setOperationType(OperationType operationType) {
+		this.operationType = operationType;
+	}
+
+	/**
 	 * Open a tree walk and filter to exactly one path.
 	 * <p>
 	 * The returned tree walk is already positioned on the requested path, so
@@ -213,8 +262,15 @@ public static TreeWalk forPath(final Repository db, final String path,
 
 	private boolean postChildren;
 
+	private AttributesNodeProvider attributesNodeProvider;
+
 	AbstractTreeIterator currentHead;
 
+	/** Cached attribute for the current entry */
+	private Attributes attrs = null;
+
+	private Config config;
+
 	/**
 	 * Create a new tree walker for a given repository.
 	 *
@@ -225,6 +281,8 @@ public static TreeWalk forPath(final Repository db, final String path,
 	 */
 	public TreeWalk(final Repository repo) {
 		this(repo.newObjectReader(), true);
+		config = repo.getConfig();
+		attributesNodeProvider = repo.createAttributesNodeProvider();
 	}
 
 	/**
@@ -356,8 +414,29 @@ public void setPostOrderTraversal(final boolean b) {
 		postOrderTraversal = b;
 	}
 
+	/**
+	 * Sets the {@link AttributesNodeProvider} for this {@link TreeWalk}.
+	 * <p>
+	 * This is a requirement for a correct computation of the git attributes.
+	 * If this {@link TreeWalk} has been built using
+	 * {@link #TreeWalk(Repository)} constructor, the
+	 * {@link AttributesNodeProvider} has already been set. Indeed,the
+	 * {@link Repository} can provide an {@link AttributesNodeProvider} using
+	 * {@link Repository#createAttributesNodeProvider()} method. Otherwise you
+	 * should provide one.
+	 * </p>
+	 *
+	 * @see Repository#createAttributesNodeProvider()
+	 * @param provider
+	 * @since 4.2
+	 */
+	public void setAttributesNodeProvider(AttributesNodeProvider provider) {
+		attributesNodeProvider = provider;
+	}
+
 	/** Reset this walker so new tree iterators can be added to it. */
 	public void reset() {
+		attrs = null;
 		trees = NO_TREES;
 		advance = false;
 		depth = 0;
@@ -401,6 +480,7 @@ public void reset(final AnyObjectId id) throws MissingObjectException,
 
 		advance = false;
 		depth = 0;
+		attrs = null;
 	}
 
 	/**
@@ -450,6 +530,7 @@ public void reset(final AnyObjectId... ids) throws MissingObjectException,
 		trees = r;
 		advance = false;
 		depth = 0;
+		attrs = null;
 	}
 
 	/**
@@ -546,6 +627,7 @@ public int getTreeCount() {
 	public boolean next() throws MissingObjectException,
 			IncorrectObjectTypeException, CorruptObjectException, IOException {
 		try {
+			attrs = null;
 			if (advance) {
 				advance = false;
 				postChildren = false;
@@ -915,6 +997,7 @@ public boolean isPostChildren() {
 	 */
 	public void enterSubtree() throws MissingObjectException,
 			IncorrectObjectTypeException, CorruptObjectException, IOException {
+		attrs = null;
 		final AbstractTreeIterator ch = currentHead;
 		final AbstractTreeIterator[] tmp = new AbstractTreeIterator[trees.length];
 		for (int i = 0; i < trees.length; i++) {
@@ -1008,4 +1091,296 @@ static String pathOf(final AbstractTreeIterator t) {
 	static String pathOf(final byte[] buf, int pos, int end) {
 		return RawParseUtils.decode(Constants.CHARSET, buf, pos, end);
 	}
+
+	/**
+	 * Retrieve the git attributes for the current entry.
+	 *
+	 * <h4>Git attribute computation</h4>
+	 *
+	 * <ul>
+	 * <li>Get the attributes matching the current path entry from the info file
+	 * (see {@link AttributesNodeProvider#getInfoAttributesNode()}).</li>
+	 * <li>Completes the list of attributes using the .gitattributes files
+	 * located on the current path (the further the directory that contains
+	 * .gitattributes is from the path in question, the lower its precedence).
+	 * For a checkin operation, it will look first on the working tree (if any).
+	 * If there is no attributes file, it will fallback on the index. For a
+	 * checkout operation, it will first use the index entry and then fallback
+	 * on the working tree if none.</li>
+	 * <li>In the end, completes the list of matching attributes using the
+	 * global attribute file define in the configuration (see
+	 * {@link AttributesNodeProvider#getGlobalAttributesNode()})</li>
+	 *
+	 * </ul>
+	 *
+	 *
+	 * <h4>Iterator constraints</h4>
+	 *
+	 * <p>
+	 * In order to have a correct list of attributes for the current entry, this
+	 * {@link TreeWalk} requires to have at least one
+	 * {@link AttributesNodeProvider} and a {@link DirCacheIterator} set up. An
+	 * {@link AttributesNodeProvider} is used to retrieve the attributes from
+	 * the info attributes file and the global attributes file. The
+	 * {@link DirCacheIterator} is used to retrieve the .gitattributes files
+	 * stored in the index. A {@link WorkingTreeIterator} can also be provided
+	 * to access the local version of the .gitattributes files. If none is
+	 * provided it will fallback on the {@link DirCacheIterator}.
+	 * </p>
+	 *
+	 * @return a {@link Set} of {@link Attribute}s that match the current entry.
+	 * @since 4.2
+	 */
+	public Attributes getAttributes() {
+		if (attrs != null)
+			return attrs;
+
+		if (attributesNodeProvider == null) {
+			// The work tree should have a AttributesNodeProvider to be able to
+			// retrieve the info and global attributes node
+			throw new IllegalStateException(
+					"The tree walk should have one AttributesNodeProvider set in order to compute the git attributes."); //$NON-NLS-1$
+		}
+
+		WorkingTreeIterator workingTreeIterator = getTree(WorkingTreeIterator.class);
+		DirCacheIterator dirCacheIterator = getTree(DirCacheIterator.class);
+		CanonicalTreeParser other = getTree(CanonicalTreeParser.class);
+
+		if (workingTreeIterator == null && dirCacheIterator == null
+				&& other == null) {
+			// Can not retrieve the attributes without at least one of the above
+			// iterators.
+			return new Attributes();
+		}
+
+		String path = currentHead.getEntryPathString();
+		final boolean isDir = FileMode.TREE.equals(currentHead.mode);
+		Attributes attributes = new Attributes();
+		try {
+			// Gets the global attributes node
+			AttributesNode globalNodeAttr = attributesNodeProvider
+					.getGlobalAttributesNode();
+			// Gets the info attributes node
+			AttributesNode infoNodeAttr = attributesNodeProvider
+					.getInfoAttributesNode();
+
+			// Gets the info attributes
+			if (infoNodeAttr != null) {
+				infoNodeAttr.getAttributes(path, isDir, attributes);
+			}
+
+			// Gets the attributes located on the current entry path
+			getPerDirectoryEntryAttributes(path, isDir, operationType,
+					workingTreeIterator, dirCacheIterator, other, attributes);
+
+			// Gets the attributes located in the global attribute file
+			if (globalNodeAttr != null) {
+				globalNodeAttr.getAttributes(path, isDir, attributes);
+			}
+		} catch (IOException e) {
+			throw new JGitInternalException("Error while parsing attributes", e); //$NON-NLS-1$
+		}
+		// now after all attributes are collected - in the correct hierarchy
+		// order - remove all unspecified entries (the ! marker)
+		for (Attribute a : attributes.getAll()) {
+			if (a.getState() == State.UNSPECIFIED)
+				attributes.remove(a.getKey());
+		}
+		return attributes;
+	}
+
+	/**
+	 * Get the attributes located on the current entry path.
+	 *
+	 * @param path
+	 *            current entry path
+	 * @param isDir
+	 *            holds true if the current entry is a directory
+	 * @param opType
+	 *            type of operation
+	 * @param workingTreeIterator
+	 *            a {@link WorkingTreeIterator} matching the current entry
+	 * @param dirCacheIterator
+	 *            a {@link DirCacheIterator} matching the current entry
+	 * @param other
+	 *            a {@link CanonicalTreeParser} matching the current entry
+	 * @param attributes
+	 *            Non null map holding the existing attributes. This map will be
+	 *            augmented with new entry. None entry will be overrided.
+	 * @throws IOException
+	 *             It raises an {@link IOException} if a problem appears while
+	 *             parsing one on the attributes file.
+	 */
+	private void getPerDirectoryEntryAttributes(String path, boolean isDir,
+			OperationType opType, WorkingTreeIterator workingTreeIterator,
+			DirCacheIterator dirCacheIterator, CanonicalTreeParser other,
+			Attributes attributes)
+			throws IOException {
+		// Prevents infinite recurrence
+		if (workingTreeIterator != null || dirCacheIterator != null
+				|| other != null) {
+			AttributesNode currentAttributesNode = getCurrentAttributesNode(
+					opType, workingTreeIterator, dirCacheIterator, other);
+			if (currentAttributesNode != null) {
+				currentAttributesNode.getAttributes(path, isDir, attributes);
+			}
+			getPerDirectoryEntryAttributes(path, isDir, opType,
+					getParent(workingTreeIterator, WorkingTreeIterator.class),
+					getParent(dirCacheIterator, DirCacheIterator.class),
+					getParent(other, CanonicalTreeParser.class), attributes);
+		}
+	}
+
+	private static <T extends AbstractTreeIterator> T getParent(T current,
+			Class<T> type) {
+		if (current != null) {
+			AbstractTreeIterator parent = current.parent;
+			if (type.isInstance(parent)) {
+				return type.cast(parent);
+			}
+		}
+		return null;
+	}
+
+	private <T extends AbstractTreeIterator> T getTree(Class<T> type) {
+		for (int i = 0; i < trees.length; i++) {
+			AbstractTreeIterator tree = trees[i];
+			if (type.isInstance(tree)) {
+				return type.cast(tree);
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Get the {@link AttributesNode} for the current entry.
+	 * <p>
+	 * This method implements the fallback mechanism between the index and the
+	 * working tree depending on the operation type
+	 * </p>
+	 *
+	 * @param opType
+	 * @param workingTreeIterator
+	 * @param dirCacheIterator
+	 * @param other
+	 * @return a {@link AttributesNode} of the current entry,
+	 *         {@link NullPointerException} otherwise.
+	 * @throws IOException
+	 *             It raises an {@link IOException} if a problem appears while
+	 *             parsing one on the attributes file.
+	 */
+	private AttributesNode getCurrentAttributesNode(OperationType opType,
+			@Nullable WorkingTreeIterator workingTreeIterator,
+			@Nullable DirCacheIterator dirCacheIterator,
+			@Nullable CanonicalTreeParser other)
+					throws IOException {
+		AttributesNode attributesNode = null;
+		switch (opType) {
+		case CHECKIN_OP:
+			if (workingTreeIterator != null) {
+				attributesNode = workingTreeIterator.getEntryAttributesNode();
+			}
+			if (attributesNode == null && dirCacheIterator != null) {
+				attributesNode = getAttributesNode(dirCacheIterator
+						.getEntryAttributesNode(getObjectReader()),
+						attributesNode);
+			}
+			if (attributesNode == null && other != null) {
+				attributesNode = getAttributesNode(
+						other.getEntryAttributesNode(getObjectReader()),
+						attributesNode);
+			}
+			break;
+		case CHECKOUT_OP:
+			if (other != null) {
+				attributesNode = other
+						.getEntryAttributesNode(getObjectReader());
+			}
+			if (dirCacheIterator != null) {
+				attributesNode = getAttributesNode(dirCacheIterator
+						.getEntryAttributesNode(getObjectReader()),
+						attributesNode);
+			}
+			if (attributesNode == null && workingTreeIterator != null) {
+				attributesNode = getAttributesNode(
+						workingTreeIterator.getEntryAttributesNode(),
+						attributesNode);
+			}
+			break;
+		default:
+			throw new IllegalStateException(
+					"The only supported operation types are:" //$NON-NLS-1$
+							+ OperationType.CHECKIN_OP + "," //$NON-NLS-1$
+							+ OperationType.CHECKOUT_OP);
+		}
+
+		return attributesNode;
+	}
+
+	private static AttributesNode getAttributesNode(AttributesNode value,
+			AttributesNode defaultValue) {
+		return (value == null) ? defaultValue : value;
+	}
+
+	/**
+	 * Inspect config and attributes to return a filtercommand applicable for
+	 * the current path
+	 *
+	 * @param filterCommandType
+	 *            which type of filterCommand should be executed. E.g. "clean",
+	 *            "smudge"
+	 * @return a filter command
+	 * @throws IOException
+	 * @since 4.2
+	 */
+	public String getFilterCommand(String filterCommandType)
+			throws IOException {
+		Attributes attributes = getAttributes();
+
+		Attribute f = attributes.get(Constants.ATTR_FILTER);
+		if (f == null) {
+			return null;
+		}
+		String filterValue = f.getValue();
+		if (filterValue == null) {
+			return null;
+		}
+
+		String filterCommand = getFilterCommandDefinition(filterValue,
+				filterCommandType);
+		if (filterCommand == null) {
+			return null;
+		}
+		return filterCommand.replaceAll("%f", //$NON-NLS-1$
+				QuotedString.BOURNE.quote((getPathString())));
+	}
+
+	/**
+	 * Get the filter command how it is defined in gitconfig. The returned
+	 * string may contain "%f" which needs to be replaced by the current path
+	 * before executing the filter command. These filter definitions are cached
+	 * for better performance.
+	 *
+	 * @param filterDriverName
+	 *            The name of the filter driver as it is referenced in the
+	 *            gitattributes file. E.g. "lfs". For each filter driver there
+	 *            may be many commands defined in the .gitconfig
+	 * @param filterCommandType
+	 *            The type of the filter command for a specific filter driver.
+	 *            May be "clean" or "smudge".
+	 * @return the definition of the command to be executed for this filter
+	 *         driver and filter command
+	 */
+	private String getFilterCommandDefinition(String filterDriverName,
+			String filterCommandType) {
+		String key = filterDriverName + "." + filterCommandType; //$NON-NLS-1$
+		String filterCommand = filterCommandsByNameDotType.get(key);
+		if (filterCommand != null)
+			return filterCommand;
+		filterCommand = config.getString(Constants.ATTR_FILTER,
+				filterDriverName, filterCommandType);
+		if (filterCommand != null)
+			filterCommandsByNameDotType.put(key, filterCommand);
+		return filterCommand;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
index 73ab04f..94beeeb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -62,6 +62,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 
+import org.eclipse.jgit.api.errors.FilterFailedException;
 import org.eclipse.jgit.attributes.AttributesNode;
 import org.eclipse.jgit.attributes.AttributesRule;
 import org.eclipse.jgit.diff.RawText;
@@ -76,6 +77,7 @@
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.CoreConfig;
+import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
 import org.eclipse.jgit.lib.CoreConfig.CheckStat;
 import org.eclipse.jgit.lib.CoreConfig.SymLinks;
 import org.eclipse.jgit.lib.FileMode;
@@ -85,6 +87,7 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.submodule.SubmoduleWalk;
 import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FS.ExecutionResult;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.RawParseUtils;
 import org.eclipse.jgit.util.io.EolCanonicalizingInputStream;
@@ -101,6 +104,8 @@
  * @see FileTreeIterator
  */
 public abstract class WorkingTreeIterator extends AbstractTreeIterator {
+	private static final int MAX_EXCEPTION_TEXT_SIZE = 10 * 1024;
+
 	/** An empty entry array, suitable for {@link #init(Entry[])}. */
 	protected static final Entry[] EOF = {};
 
@@ -134,8 +139,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
 	/** If there is a .gitignore file present, the parsed rules from it. */
 	private IgnoreNode ignoreNode;
 
-	/** If there is a .gitattributes file present, the parsed rules from it. */
-	private AttributesNode attributesNode;
+	private String cleanFilterCommand;
 
 	/** Repository that is the root level being iterated over */
 	protected Repository repository;
@@ -147,19 +151,6 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
 	private int contentIdOffset;
 
 	/**
-	 * Holds the {@link AttributesNode} that is stored in
-	 * $GIT_DIR/info/attributes file.
-	 */
-	private AttributesNode infoAttributeNode;
-
-	/**
-	 * Holds the {@link AttributesNode} that is stored in global attribute file.
-	 *
-	 * @see CoreConfig#getAttributesFile()
-	 */
-	private AttributesNode globalAttributeNode;
-
-	/**
 	 * Create a new iterator with no parent.
 	 *
 	 * @param options
@@ -202,8 +193,7 @@ protected WorkingTreeIterator(final String prefix,
 	protected WorkingTreeIterator(final WorkingTreeIterator p) {
 		super(p);
 		state = p.state;
-		infoAttributeNode = p.infoAttributeNode;
-		globalAttributeNode = p.globalAttributeNode;
+		repository = p.repository;
 	}
 
 	/**
@@ -223,10 +213,6 @@ protected void initRootIterator(Repository repo) {
 		else
 			entry = null;
 		ignoreNode = new RootIgnoreNode(entry, repo);
-
-		infoAttributeNode = new InfoAttributesNode(repo);
-
-		globalAttributeNode = new GlobalAttributesNode(repo);
 	}
 
 	/**
@@ -370,7 +356,8 @@ public boolean hasId() {
 
 	private InputStream possiblyFilteredInputStream(final Entry e,
 			final InputStream is, final long len) throws IOException {
-		if (!mightNeedCleaning()) {
+		boolean mightNeedCleaning = mightNeedCleaning();
+		if (!mightNeedCleaning) {
 			canonLen = len;
 			return is;
 		}
@@ -388,7 +375,8 @@ private InputStream possiblyFilteredInputStream(final Entry e,
 			return new ByteArrayInputStream(raw, 0, n);
 		}
 
-		if (isBinary(e)) {
+		// TODO: fix autocrlf causing mightneedcleaning
+		if (!mightNeedCleaning && isBinary(e)) {
 			canonLen = len;
 			return is;
 		}
@@ -412,10 +400,12 @@ private static void safeClose(final InputStream in) {
 		}
 	}
 
-	private boolean mightNeedCleaning() {
+	private boolean mightNeedCleaning() throws IOException {
 		switch (getOptions().getAutoCRLF()) {
 		case FALSE:
 		default:
+			if (getCleanFilterCommand() != null)
+				return true;
 			return false;
 
 		case TRUE:
@@ -437,8 +427,7 @@ private static boolean isBinary(Entry entry) throws IOException {
 		}
 	}
 
-	private static ByteBuffer filterClean(byte[] src, int n)
-			throws IOException {
+	private ByteBuffer filterClean(byte[] src, int n) throws IOException {
 		InputStream in = new ByteArrayInputStream(src);
 		try {
 			return IO.readWholeStream(filterClean(in), n);
@@ -447,8 +436,42 @@ private static ByteBuffer filterClean(byte[] src, int n)
 		}
 	}
 
-	private static InputStream filterClean(InputStream in) {
-		return new EolCanonicalizingInputStream(in, true);
+	private InputStream filterClean(InputStream in) throws IOException {
+		in = handleAutoCRLF(in);
+		String filterCommand = getCleanFilterCommand();
+		if (filterCommand != null) {
+			FS fs = repository.getFS();
+			ProcessBuilder filterProcessBuilder = fs.runInShell(filterCommand,
+					new String[0]);
+			filterProcessBuilder.directory(repository.getWorkTree());
+			filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY,
+					repository.getDirectory().getAbsolutePath());
+			ExecutionResult result;
+			try {
+				result = fs.execute(filterProcessBuilder, in);
+			} catch (IOException | InterruptedException e) {
+				throw new IOException(new FilterFailedException(e,
+						filterCommand, getEntryPathString()));
+			}
+			int rc = result.getRc();
+			if (rc != 0) {
+				throw new IOException(new FilterFailedException(rc,
+						filterCommand, getEntryPathString(),
+						result.getStdout().toByteArray(MAX_EXCEPTION_TEXT_SIZE),
+						RawParseUtils.decode(result.getStderr()
+								.toByteArray(MAX_EXCEPTION_TEXT_SIZE))));
+			}
+			return result.getStdout().openInputStream();
+		}
+		return in;
+	}
+
+	private InputStream handleAutoCRLF(InputStream in) {
+		AutoCRLF autoCRLF = getOptions().getAutoCRLF();
+		if (autoCRLF == AutoCRLF.TRUE || autoCRLF == AutoCRLF.INPUT) {
+			in = new EolCanonicalizingInputStream(in, true);
+		}
+		return in;
 	}
 
 	/**
@@ -507,6 +530,7 @@ private void parseEntry() {
 		System.arraycopy(e.encodedName, 0, path, pathOffset, nameLen);
 		pathLen = pathOffset + nameLen;
 		canonLen = -1;
+		cleanFilterCommand = null;
 	}
 
 	/**
@@ -667,41 +691,6 @@ public AttributesNode getEntryAttributesNode() throws IOException {
 		return attributesNode;
 	}
 
-	/**
-	 * Retrieves the {@link AttributesNode} that holds the information located
-	 * in $GIT_DIR/info/attributes file.
-	 *
-	 * @return the {@link AttributesNode} that holds the information located in
-	 *         $GIT_DIR/info/attributes file.
-	 * @throws IOException
-	 *             if an error is raised while parsing the attributes file
-	 * @since 3.7
-	 */
-	public AttributesNode getInfoAttributesNode() throws IOException {
-		if (infoAttributeNode instanceof InfoAttributesNode)
-			infoAttributeNode = ((InfoAttributesNode) infoAttributeNode).load();
-		return infoAttributeNode;
-	}
-
-	/**
-	 * Retrieves the {@link AttributesNode} that holds the information located
-	 * in system-wide file.
-	 *
-	 * @return the {@link AttributesNode} that holds the information located in
-	 *         system-wide file.
-	 * @throws IOException
-	 *             IOException if an error is raised while parsing the
-	 *             attributes file
-	 * @see CoreConfig#getAttributesFile()
-	 * @since 3.7
-	 */
-	public AttributesNode getGlobalAttributesNode() throws IOException {
-		if (globalAttributeNode instanceof GlobalAttributesNode)
-			globalAttributeNode = ((GlobalAttributesNode) globalAttributeNode)
-					.load();
-		return globalAttributeNode;
-	}
-
 	private static final Comparator<Entry> ENTRY_CMP = new Comparator<Entry>() {
 		public int compare(final Entry o1, final Entry o2) {
 			final byte[] a = o1.encodedName;
@@ -1296,68 +1285,6 @@ AttributesNode load() throws IOException {
 		}
 	}
 
-	/**
-	 * Attributes node loaded from global system-wide file.
-	 */
-	private static class GlobalAttributesNode extends AttributesNode {
-		final Repository repository;
-
-		GlobalAttributesNode(Repository repository) {
-			this.repository = repository;
-		}
-
-		AttributesNode load() throws IOException {
-			AttributesNode r = new AttributesNode();
-
-			FS fs = repository.getFS();
-			String path = repository.getConfig().get(CoreConfig.KEY)
-					.getAttributesFile();
-			if (path != null) {
-				File attributesFile;
-				if (path.startsWith("~/")) //$NON-NLS-1$
-					attributesFile = fs.resolve(fs.userHome(),
-							path.substring(2));
-				else
-					attributesFile = fs.resolve(null, path);
-				loadRulesFromFile(r, attributesFile);
-			}
-			return r.getRules().isEmpty() ? null : r;
-		}
-	}
-
-	/** Magic type indicating there may be rules for the top level. */
-	private static class InfoAttributesNode extends AttributesNode {
-		final Repository repository;
-
-		InfoAttributesNode(Repository repository) {
-			this.repository = repository;
-		}
-
-		AttributesNode load() throws IOException {
-			AttributesNode r = new AttributesNode();
-
-			FS fs = repository.getFS();
-
-			File attributes = fs.resolve(repository.getDirectory(),
-					"info/attributes"); //$NON-NLS-1$
-			loadRulesFromFile(r, attributes);
-
-			return r.getRules().isEmpty() ? null : r;
-		}
-
-	}
-
-	private static void loadRulesFromFile(AttributesNode r, File attrs)
-			throws FileNotFoundException, IOException {
-		if (attrs.exists()) {
-			FileInputStream in = new FileInputStream(attrs);
-			try {
-				r.parse(in);
-			} finally {
-				in.close();
-			}
-		}
-	}
 
 	private static final class IteratorState {
 		/** Options used to process the working tree. */
@@ -1390,4 +1317,18 @@ void initializeDigestAndReadBuffer() {
 			}
 		}
 	}
+
+	/**
+	 * @return the clean filter command for the current entry or
+	 *         <code>null</code> if no such command is defined
+	 * @throws IOException
+	 * @since 4.2
+	 */
+	public String getCleanFilterCommand() throws IOException {
+		if (cleanFilterCommand == null && state.walk != null) {
+			cleanFilterCommand = state.walk
+					.getFilterCommand(Constants.ATTR_FILTER_TYPE_CLEAN);
+		}
+		return cleanFilterCommand;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
index e407dd8..253acbb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -110,7 +110,54 @@ public FS detect(Boolean cygwinUsed) {
 		}
 	}
 
-	final static Logger LOG = LoggerFactory.getLogger(FS.class);
+	/**
+	 * Result of an executed process. The caller is responsible to close the
+	 * contained {@link TemporaryBuffer}s
+	 *
+	 * @since 4.2
+	 */
+	public static class ExecutionResult {
+		private TemporaryBuffer stdout;
+
+		private TemporaryBuffer stderr;
+
+		private int rc;
+
+		/**
+		 * @param stdout
+		 * @param stderr
+		 * @param rc
+		 */
+		public ExecutionResult(TemporaryBuffer stdout, TemporaryBuffer stderr,
+				int rc) {
+			this.stdout = stdout;
+			this.stderr = stderr;
+			this.rc = rc;
+		}
+
+		/**
+		 * @return buffered standard output stream
+		 */
+		public TemporaryBuffer getStdout() {
+			return stdout;
+		}
+
+		/**
+		 * @return buffered standard error stream
+		 */
+		public TemporaryBuffer getStderr() {
+			return stderr;
+		}
+
+		/**
+		 * @return the return code of the process
+		 */
+		public int getRc() {
+			return rc;
+		}
+	}
+
+	private final static Logger LOG = LoggerFactory.getLogger(FS.class);
 
 	/** The auto-detected implementation selected for this operating system and JRE. */
 	public static final FS DETECTED = detect();
@@ -902,9 +949,7 @@ public int runProcess(ProcessBuilder processBuilder,
 	 * @param outRedirect
 	 *            An OutputStream on which to redirect the processes stdout. Can
 	 *            be <code>null</code>, in which case the processes standard
-	 *            output will be lost. If binary is set to <code>false</code>
-	 *            then it is expected that the process emits text data which
-	 *            should be processed line by line.
+	 *            output will be lost.
 	 * @param errRedirect
 	 *            An OutputStream on which to redirect the processes stderr. Can
 	 *            be <code>null</code>, in which case the processes standard
@@ -912,9 +957,9 @@ public int runProcess(ProcessBuilder processBuilder,
 	 * @param inRedirect
 	 *            An InputStream from which to redirect the processes stdin. Can
 	 *            be <code>null</code>, in which case the process doesn't get
-	 *            any data over stdin. If binary is set to
-	 *            <code>false</code> then it is expected that the process
-	 *            expects text data which should be processed line by line.
+	 *            any data over stdin. It is assumed that the whole InputStream
+	 *            will be consumed by the process. The method will close the
+	 *            inputstream after all bytes are read.
 	 * @return the return code of this process.
 	 * @throws IOException
 	 *             if an I/O error occurs while executing this process.
@@ -964,6 +1009,9 @@ public int runProcess(ProcessBuilder processBuilder,
 				// A process doesn't clean its own resources even when destroyed
 				// Explicitly try and close all three streams, preserving the
 				// outer I/O exception if any.
+				if (inRedirect != null) {
+					inRedirect.close();
+				}
 				try {
 					process.getErrorStream().close();
 				} catch (IOException e) {
@@ -1004,10 +1052,10 @@ private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
 		pool.shutdown(); // Disable new tasks from being submitted
 		try {
 			// Wait a while for existing tasks to terminate
-			if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {
+			if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
 				pool.shutdownNow(); // Cancel currently executing tasks
 				// Wait a while for tasks to respond to being canceled
-				if (!pool.awaitTermination(5, TimeUnit.SECONDS))
+				if (!pool.awaitTermination(60, TimeUnit.SECONDS))
 					hasShutdown = false;
 			}
 		} catch (InterruptedException ie) {
@@ -1034,6 +1082,31 @@ private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
 	 */
 	public abstract ProcessBuilder runInShell(String cmd, String[] args);
 
+	/**
+	 * Execute a command defined by a {@link ProcessBuilder}.
+	 *
+	 * @param pb
+	 *            The command to be executed
+	 * @param in
+	 *            The standard input stream passed to the process
+	 * @return The result of the executed command
+	 * @throws InterruptedException
+	 * @throws IOException
+	 * @since 4.2
+	 */
+	public ExecutionResult execute(ProcessBuilder pb, InputStream in)
+			throws IOException, InterruptedException {
+		TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null);
+		TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024, 1024 * 1024);
+		try {
+			int rc = runProcess(pb, stdout, stderr, in);
+			return new ExecutionResult(stdout, stderr, rc);
+		} finally {
+			stdout.close();
+			stderr.close();
+		}
+	}
+
 	private static class Holder<V> {
 		final V value;
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
index f0a2e72..defe14f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
@@ -171,7 +171,8 @@ private void detectSymlinkSupport() {
 			createSymLink(linkName, tempFile.getPath());
 			supportSymlinks = Boolean.TRUE;
 			linkName.delete();
-		} catch (IOException | UnsupportedOperationException e) {
+		} catch (IOException | UnsupportedOperationException
+				| InternalError e) {
 			supportSymlinks = Boolean.FALSE;
 		} finally {
 			if (tempFile != null)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
index 2450be4..ec581b3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
@@ -162,17 +162,15 @@ public ProcessResult runHookIfPresent(Repository repository, String hookName,
 				errRedirect, stdinArgs);
 	}
 
-	@Override
-	public boolean supportsSymlinks() {
-		return true;
-	}
-
 	/**
 	 * @since 3.7
 	 */
 	@Override
 	public File findHook(Repository repository, String hookName) {
 		final File gitdir = repository.getDirectory();
+		if (gitdir == null) {
+			return null;
+		}
 		final Path hookPath = gitdir.toPath().resolve(Constants.HOOKS)
 				.resolve(hookName);
 		if (Files.isExecutable(hookPath))
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 6d0318c..727ea79 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
@@ -398,8 +398,10 @@ public static void createNewFile(File f) throws IOException {
 	 * Create a symbolic link
 	 *
 	 * @param path
+	 *            the path of the symbolic link to create
 	 * @param target
-	 * @return path to the created link
+	 *            the target of the symbolic link
+	 * @return the path to the symbolic link
 	 * @throws IOException
 	 * @since 4.2
 	 */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
index 4795c89..9860ef0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
@@ -339,4 +339,19 @@ public String run() {
 	public void checkPath(String path) throws CorruptObjectException {
 		platformChecker.checkPath(path);
 	}
+
+	/**
+	 * Check tree path entry for validity.
+	 * <p>
+	 * Scans a multi-directory path string such as {@code "src/main.c"}.
+	 *
+	 * @param path
+	 *            path string to scan.
+	 * @throws CorruptObjectException
+	 *             path is invalid.
+	 * @since 4.2
+	 */
+	public void checkPath(byte[] path) throws CorruptObjectException {
+		platformChecker.checkPath(path, 0, path.length);
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java
index ca47f50..3cd5929 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java
@@ -247,6 +247,37 @@ private long inCoreLength() {
 	}
 
 	/**
+	 * Convert this buffer's contents into a contiguous byte array. If this size
+	 * of the buffer exceeds the limit only return the first {@code limit} bytes
+	 * <p>
+	 * The buffer is only complete after {@link #close()} has been invoked.
+	 *
+	 * @param limit
+	 *            the maximum number of bytes to be returned
+	 *
+	 * @return the byte array limited to {@code limit} bytes.
+	 * @throws IOException
+	 *             an error occurred reading from a local temporary file
+	 * @throws OutOfMemoryError
+	 *             the buffer cannot fit in memory
+	 *
+	 * @since 4.2
+	 */
+	public byte[] toByteArray(int limit) throws IOException {
+		final long len = Math.min(length(), limit);
+		if (Integer.MAX_VALUE < len)
+			throw new OutOfMemoryError(
+					JGitText.get().lengthExceedsMaximumArraySize);
+		final byte[] out = new byte[(int) len];
+		int outPtr = 0;
+		for (final Block b : blocks) {
+			System.arraycopy(b.buffer, 0, out, outPtr, b.count);
+			outPtr += b.count;
+		}
+		return out;
+	}
+
+	/**
 	 * Send this buffer to an output stream.
 	 * <p>
 	 * This method may only be invoked after {@link #close()} has completed