Merge "Make the FileLfsRepository thread safe"
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 0db71f7..86b87b4 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
@@ -144,10 +144,10 @@
 
 	protected static void checkFile(File f, final String checkData)
 			throws IOException {
-		Reader r = new InputStreamReader(new FileInputStream(f), "ISO-8859-1");
+		Reader r = new InputStreamReader(new FileInputStream(f), "UTF-8");
 		try {
-			char[] data = new char[(int) f.length()];
-			if (f.length() !=  r.read(data))
+			char[] data = new char[checkData.length()];
+			if (checkData.length() != r.read(data))
 				throw new IOException("Internal error reading file data from "+f);
 			assertEquals(checkData, new String(data));
 		} finally {
diff --git a/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
index 61456f1..dc8b884 100644
--- a/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
@@ -15,6 +15,5 @@
  org.junit;version="[4.0.0,5.0.0)",
  org.junit.runner;version="[4.0.0,5.0.0)",
  org.junit.runners;version="[4.0.0,5.0.0)"
-Export-Package: org.eclipse.jgit.lfs.lib;version="4.3.0";x-internal:=true,
- org.eclipse.jgit.lfs.test;version="4.3.0";x-friends:="org.eclipse.jgit.lfs.server.test"
+Export-Package: org.eclipse.jgit.lfs.test;version="4.3.0";x-friends:="org.eclipse.jgit.lfs.server.test"
 
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target
index ce71222..08116eb 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform -->
-<target name="jgit-4.5" sequenceNumber="1440022750">
+<target name="jgit-4.5" sequenceNumber="1456522731">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.jetty.client" version="9.2.13.v20150730"/>
@@ -31,8 +31,8 @@
       <unit id="org.apache.commons.logging.source" version="1.1.1.v201101211721"/>
       <unit id="org.apache.httpcomponents.httpcore" version="4.3.3.v201411290715"/>
       <unit id="org.apache.httpcomponents.httpcore.source" version="4.3.3.v201411290715"/>
-      <unit id="org.apache.httpcomponents.httpclient" version="4.3.6.v201411290715"/>
-      <unit id="org.apache.httpcomponents.httpclient.source" version="4.3.6.v201411290715"/>
+      <unit id="org.apache.httpcomponents.httpclient" version="4.3.6.v201411290715B"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.3.6.v201411290715B"/>
       <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
       <unit id="org.apache.log4j.source" version="1.2.15.v201012070815"/>
       <unit id="org.kohsuke.args4j" version="2.0.21.v201301150030"/>
@@ -58,7 +58,7 @@
       <unit id="org.slf4j.api.source" version="1.7.2.v20121108-1250"/>
       <unit id="org.slf4j.impl.log4j12" version="1.7.2.v20131105-2200"/>
       <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
-      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20150821153341/repository/"/>
+      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20160221192158/repository/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.osgi" version="0.0.0"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.tpd
index cdf24b5..5cd1037 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.tpd
@@ -1,7 +1,7 @@
 target "jgit-4.5" with source configurePhase
 
 include "projects/jetty-9.2.13.tpd"
-include "orbit/R20150821153341-Mars.tpd"
+include "orbit/R20160221192158-Mars.tpd"
 
 location "http://download.eclipse.org/releases/mars/" {
 	org.eclipse.osgi lazy
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target
new file mode 100644
index 0000000..e506aec
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?pde?>
+<!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform -->
+<target name="jgit-4.5" sequenceNumber="1455835295">
+  <locations>
+    <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
+      <unit id="org.eclipse.jetty.client" version="9.2.13.v20150730"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.2.13.v20150730"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.2.13.v20150730"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.2.13.v20150730"/>
+      <unit id="org.eclipse.jetty.http" version="9.2.13.v20150730"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.2.13.v20150730"/>
+      <unit id="org.eclipse.jetty.io" version="9.2.13.v20150730"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.2.13.v20150730"/>
+      <unit id="org.eclipse.jetty.security" version="9.2.13.v20150730"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.2.13.v20150730"/>
+      <unit id="org.eclipse.jetty.server" version="9.2.13.v20150730"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.2.13.v20150730"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.2.13.v20150730"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.2.13.v20150730"/>
+      <unit id="org.eclipse.jetty.util" version="9.2.13.v20150730"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.2.13.v20150730"/>
+      <repository id="jetty-9.2.13" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.2.13.v20150730/"/>
+    </location>
+    <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
+      <unit id="org.apache.ant" version="1.9.4.v201504302020"/>
+      <unit id="org.apache.ant.source" version="1.9.4.v201504302020"/>
+      <unit id="org.apache.commons.compress" version="1.6.0.v201310281400"/>
+      <unit id="org.apache.commons.compress.source" version="1.6.0.v201310281400"/>
+      <unit id="org.apache.commons.logging" version="1.1.1.v201101211721"/>
+      <unit id="org.apache.commons.logging.source" version="1.1.1.v201101211721"/>
+      <unit id="org.apache.httpcomponents.httpcore" version="4.3.3.v201411290715"/>
+      <unit id="org.apache.httpcomponents.httpcore.source" version="4.3.3.v201411290715"/>
+      <unit id="org.apache.httpcomponents.httpclient" version="4.3.6.v201511171540"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.3.6.v201511171540"/>
+      <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
+      <unit id="org.apache.log4j.source" version="1.2.15.v201012070815"/>
+      <unit id="org.kohsuke.args4j" version="2.0.21.v201301150030"/>
+      <unit id="org.kohsuke.args4j.source" version="2.0.21.v201301150030"/>
+      <unit id="org.hamcrest.core" version="1.3.0.v201303031735"/>
+      <unit id="org.hamcrest.core.source" version="1.3.0.v201303031735"/>
+      <unit id="javaewah" version="0.7.9.v201401101600"/>
+      <unit id="javaewah.source" version="0.7.9.v201401101600"/>
+      <unit id="org.objenesis" version="1.0.0.v201505121915"/>
+      <unit id="org.objenesis.source" version="1.0.0.v201505121915"/>
+      <unit id="org.mockito" version="1.8.4.v201303031500"/>
+      <unit id="org.mockito.source" version="1.8.4.v201303031500"/>
+      <unit id="com.google.gson" version="2.2.4.v201311231704"/>
+      <unit id="com.jcraft.jsch" version="0.1.53.v201508180515"/>
+      <unit id="com.jcraft.jsch.source" version="0.1.53.v201508180515"/>
+      <unit id="org.junit" version="4.11.0.v201303080030"/>
+      <unit id="org.junit.source" version="4.11.0.v201303080030"/>
+      <unit id="javax.servlet" version="3.1.0.v201410161800"/>
+      <unit id="javax.servlet.source" version="3.1.0.v201410161800"/>
+      <unit id="org.tukaani.xz" version="1.3.0.v201308270617"/>
+      <unit id="org.tukaani.xz.source" version="1.3.0.v201308270617"/>
+      <unit id="org.slf4j.api" version="1.7.2.v20121108-1250"/>
+      <unit id="org.slf4j.api.source" version="1.7.2.v20121108-1250"/>
+      <unit id="org.slf4j.impl.log4j12" version="1.7.2.v20131105-2200"/>
+      <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
+      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/S20151204220443/repository/"/>
+    </location>
+    <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
+      <unit id="org.eclipse.osgi" version="0.0.0"/>
+      <repository location="http://download.eclipse.org/releases/mars/"/>
+    </location>
+  </locations>
+</target>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd
new file mode 100644
index 0000000..3963361
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd
@@ -0,0 +1,8 @@
+target "jgit-4.5" with source configurePhase
+
+include "projects/jetty-9.2.13.tpd"
+include "orbit/S20151204220443-Neon.tpd"
+
+location "http://download.eclipse.org/releases/mars/" {
+	org.eclipse.osgi lazy
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20150821153341-Mars.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20160221192158-Mars.tpd
similarity index 91%
rename from org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20150821153341-Mars.tpd
rename to org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20160221192158-Mars.tpd
index 5cacd71..5e47f68 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20150821153341-Mars.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20160221192158-Mars.tpd
@@ -1,7 +1,7 @@
-target "R20150821153341-Mars" with source configurePhase
+target "R20160221192158-Mars" with source configurePhase
 // see http://download.eclipse.org/tools/orbit/downloads/
 
-location "http://download.eclipse.org/tools/orbit/downloads/drops/R20150821153341/repository/" {
+location "http://download.eclipse.org/tools/orbit/downloads/drops/R20160221192158/repository/" {
 	org.apache.ant [1.9.4.v201504302020,1.9.4.v201504302020]
 	org.apache.ant.source [1.9.4.v201504302020,1.9.4.v201504302020]
 	org.apache.commons.compress [1.6.0.v201310281400,1.6.0.v201310281400]
@@ -10,8 +10,8 @@
 	org.apache.commons.logging.source [1.1.1.v201101211721,1.1.1.v201101211721]
 	org.apache.httpcomponents.httpcore [4.3.3.v201411290715,4.3.3.v201411290715]
 	org.apache.httpcomponents.httpcore.source [4.3.3.v201411290715,4.3.3.v201411290715]
-	org.apache.httpcomponents.httpclient [4.3.6.v201411290715,4.3.6.v201411290715]
-	org.apache.httpcomponents.httpclient.source [4.3.6.v201411290715,4.3.6.v201411290715]
+	org.apache.httpcomponents.httpclient [4.3.6.v201411290715B,4.3.6.v201411290715B]
+	org.apache.httpcomponents.httpclient.source [4.3.6.v201411290715B,4.3.6.v201411290715B]
 	org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815]
 	org.apache.log4j.source [1.2.15.v201012070815,1.2.15.v201012070815]
 	org.kohsuke.args4j [2.0.21.v201301150030,2.0.21.v201301150030]
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20150821153341-Mars.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20151204220443-Neon.tpd
similarity index 89%
copy from org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20150821153341-Mars.tpd
copy to org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20151204220443-Neon.tpd
index 5cacd71..3afdbaa 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20150821153341-Mars.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20151204220443-Neon.tpd
@@ -1,7 +1,7 @@
-target "R20150821153341-Mars" with source configurePhase
+target "S20151204220443-Neon" with source configurePhase
 // see http://download.eclipse.org/tools/orbit/downloads/
 
-location "http://download.eclipse.org/tools/orbit/downloads/drops/R20150821153341/repository/" {
+location "http://download.eclipse.org/tools/orbit/downloads/drops/S20151204220443/repository/" {
 	org.apache.ant [1.9.4.v201504302020,1.9.4.v201504302020]
 	org.apache.ant.source [1.9.4.v201504302020,1.9.4.v201504302020]
 	org.apache.commons.compress [1.6.0.v201310281400,1.6.0.v201310281400]
@@ -10,8 +10,8 @@
 	org.apache.commons.logging.source [1.1.1.v201101211721,1.1.1.v201101211721]
 	org.apache.httpcomponents.httpcore [4.3.3.v201411290715,4.3.3.v201411290715]
 	org.apache.httpcomponents.httpcore.source [4.3.3.v201411290715,4.3.3.v201411290715]
-	org.apache.httpcomponents.httpclient [4.3.6.v201411290715,4.3.6.v201411290715]
-	org.apache.httpcomponents.httpclient.source [4.3.6.v201411290715,4.3.6.v201411290715]
+	org.apache.httpcomponents.httpclient [4.3.6.v201511171540,4.3.6.v201511171540]
+	org.apache.httpcomponents.httpclient.source [4.3.6.v201511171540,4.3.6.v201511171540]
 	org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815]
 	org.apache.log4j.source [1.2.15.v201012070815,1.2.15.v201012070815]
 	org.kohsuke.args4j [2.0.21.v201301150030,2.0.21.v201301150030]
diff --git a/org.eclipse.jgit.packaging/pom.xml b/org.eclipse.jgit.packaging/pom.xml
index 68efc46..9e8a8b8 100644
--- a/org.eclipse.jgit.packaging/pom.xml
+++ b/org.eclipse.jgit.packaging/pom.xml
@@ -61,6 +61,7 @@
   <properties>
     <tycho-version>0.23.0</tycho-version>
     <tycho-extras-version>${tycho-version}</tycho-extras-version>
+    <target-platform>jgit-4.5</target-platform>
   </properties>
 
   <pluginRepositories>
@@ -228,43 +229,6 @@
 
   <profiles>
     <profile>
-      <id>platform-kepler</id>
-      <activation>
-        <property>
-          <name>platform-version-name</name>
-          <value>kepler</value>
-        </property>
-      </activation>
-      <properties>
-        <target-platform>jgit-4.3</target-platform>
-      </properties>
-    </profile>
-    <profile>
-      <id>platform-luna</id>
-      <activation>
-        <property>
-          <name>platform-version-name</name>
-          <value>luna</value>
-        </property>
-      </activation>
-      <properties>
-        <target-platform>jgit-4.4</target-platform>
-      </properties>
-    </profile>
-    <profile>
-      <id>platform-mars</id>
-      <activation>
-        <activeByDefault>true</activeByDefault>
-        <property>
-          <name>platform-version-name</name>
-          <value>mars</value>
-        </property>
-      </activation>
-      <properties>
-        <target-platform>jgit-4.5</target-platform>
-      </properties>
-    </profile>
-    <profile>
       <id>eclipse-sign</id>
       <build>
         <plugins>
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 33fb3ba..fc412ba 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
@@ -244,6 +244,7 @@
 usage_ReadDirCache= Read the DirCache 100 times
 usage_RebuildCommitGraph=Recreate a repository from another one's commit graph
 usage_RebuildRefTree=Copy references into a RefTree
+usage_RebuildRefTreeEnable=set extensions.refsStorage = reftree
 usage_Remote=Manage set of tracked repositories
 usage_RepositoryToReadFrom=Repository to read from
 usage_RepositoryToReceiveInto=Repository to receive into
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java
index fbd4672..57345e2 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java
@@ -67,7 +67,7 @@
 
 @Command(usage = "usage_RebuildRefTree")
 class RebuildRefTree extends TextBuiltin {
-	@Option(name = "--enable", usage = "set extensions.refsStorage = reftree")
+	@Option(name = "--enable", usage = "usage_RebuildRefTreeEnable")
 	boolean enable;
 
 	private String txnNamespace;
@@ -154,7 +154,7 @@
 		}
 
 		for (Ref r : refdb.getRefs(RefDatabase.ALL).values()) {
-			if (r.getName().equals(txnCommitted)
+			if (r.getName().equals(txnCommitted) || r.getName().equals(HEAD)
 					|| r.getName().startsWith(txnNamespace)) {
 				continue;
 			}
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII.patch
new file mode 100644
index 0000000..d6408c9
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII.patch
@@ -0,0 +1,8 @@
+diff --git a/NonASCII b/NonASCII
+index 2e65efe..7898192 100644
+--- a/NonASCII
++++ b/NonASCII
+@@ -1 +1 @@
+-あ
+\ No newline at end of file
++あ
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII2.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII2.patch
new file mode 100644
index 0000000..cea6cfd
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII2.patch
@@ -0,0 +1,9 @@
+diff --git a/NonASCII2 b/NonASCII2
+index 2e65efe..7898192 100644
+--- a/NonASCII2
++++ b/NonASCII2
+@@ -1 +1 @@
+-你好, 世界!
+\ No newline at end of file
++再见
+\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII2_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII2_PostImage
new file mode 100644
index 0000000..5c8e34d
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII2_PostImage
@@ -0,0 +1 @@
+再见
\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII2_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII2_PreImage
new file mode 100644
index 0000000..4369661
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII2_PreImage
@@ -0,0 +1 @@
+你好, 世界!
\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd.patch
new file mode 100644
index 0000000..de31a9e
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd.patch
@@ -0,0 +1,7 @@
+diff --git a/NonASCIIAdd b/NonASCIIAdd
+index 2e65efe..7898192 100644
+--- a/NonASCIIAdd
++++ b/NonASCIIAdd
+@@ -0,0 +1 @@
++あ
+\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd2.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd2.patch
new file mode 100644
index 0000000..5b41e9c
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd2.patch
@@ -0,0 +1,8 @@
+diff --git a/NonASCIIAdd2 b/NonASCIIAdd2
+new file mode 100644
+index 2e65efe..7898192 100644
+--- /dev/null
++++ b/NonASCIIAdd2
+@@ -0,0 +1 @@
++あ
+\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd2_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd2_PostImage
new file mode 100644
index 0000000..0575c79
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd2_PostImage
@@ -0,0 +1 @@
+あ
\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd_PostImage
new file mode 100644
index 0000000..0575c79
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd_PostImage
@@ -0,0 +1 @@
+あ
\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd_PreImage
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd_PreImage
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIDel.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIDel.patch
new file mode 100644
index 0000000..80e8f82
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIDel.patch
@@ -0,0 +1,9 @@
+diff --git a/NonASCIIDel b/NonASCIIDel
+deleted file mode 100644
+new file mode 100644
+index 2e65efe..7898192 100644
+--- a/NonASCIIDel
++++ /dev/null
+@@ -1 +0,0 @@
+-あ
+\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIDel_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIDel_PreImage
new file mode 100644
index 0000000..0575c79
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIDel_PreImage
@@ -0,0 +1 @@
+あ
\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII_PostImage
new file mode 100644
index 0000000..f2435a2
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII_PostImage
@@ -0,0 +1 @@
+あ
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII_PreImage
new file mode 100644
index 0000000..0575c79
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII_PreImage
@@ -0,0 +1 @@
+あ
\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java
index d842046..239c844 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java
@@ -186,6 +186,55 @@
 				b.getString(0, b.size(), false));
 	}
 
+	@Test
+	public void testNonASCII() throws Exception {
+		ApplyResult result = init("NonASCII");
+		assertEquals(1, result.getUpdatedFiles().size());
+		assertEquals(new File(db.getWorkTree(), "NonASCII"),
+				result.getUpdatedFiles().get(0));
+		checkFile(new File(db.getWorkTree(), "NonASCII"),
+				b.getString(0, b.size(), false));
+	}
+
+	@Test
+	public void testNonASCII2() throws Exception {
+		ApplyResult result = init("NonASCII2");
+		assertEquals(1, result.getUpdatedFiles().size());
+		assertEquals(new File(db.getWorkTree(), "NonASCII2"),
+				result.getUpdatedFiles().get(0));
+		checkFile(new File(db.getWorkTree(), "NonASCII2"),
+				b.getString(0, b.size(), false));
+	}
+
+	@Test
+	public void testNonASCIIAdd() throws Exception {
+		ApplyResult result = init("NonASCIIAdd");
+		assertEquals(1, result.getUpdatedFiles().size());
+		assertEquals(new File(db.getWorkTree(), "NonASCIIAdd"),
+				result.getUpdatedFiles().get(0));
+		checkFile(new File(db.getWorkTree(), "NonASCIIAdd"),
+				b.getString(0, b.size(), false));
+	}
+
+	@Test
+	public void testNonASCIIAdd2() throws Exception {
+		ApplyResult result = init("NonASCIIAdd2", false, true);
+		assertEquals(1, result.getUpdatedFiles().size());
+		assertEquals(new File(db.getWorkTree(), "NonASCIIAdd2"),
+				result.getUpdatedFiles().get(0));
+		checkFile(new File(db.getWorkTree(), "NonASCIIAdd2"),
+				b.getString(0, b.size(), false));
+	}
+
+	@Test
+	public void testNonASCIIDel() throws Exception {
+		ApplyResult result = init("NonASCIIDel", true, false);
+		assertEquals(1, result.getUpdatedFiles().size());
+		assertEquals(new File(db.getWorkTree(), "NonASCIIDel"),
+				result.getUpdatedFiles().get(0));
+		assertFalse(new File(db.getWorkTree(), "NonASCIIDel").exists());
+	}
+
 	private static byte[] readFile(final String patchFile) throws IOException {
 		final InputStream in = getTestResource(patchFile);
 		if (in == null) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java
new file mode 100644
index 0000000..5dd8da5
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java
@@ -0,0 +1,692 @@
+/*
+ * 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.api;
+
+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 org.eclipse.jgit.api.ResetCommand.ResetType;
+import org.eclipse.jgit.api.errors.CheckoutConflictException;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.NoFilepatternException;
+import org.eclipse.jgit.attributes.Attribute;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.dircache.DirCacheIterator;
+import org.eclipse.jgit.errors.RevisionSyntaxException;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
+import org.eclipse.jgit.lib.CoreConfig.EOL;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.util.IO;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.experimental.theories.DataPoint;
+import org.junit.experimental.theories.Theories;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for end-of-line conversion and settings using core.autocrlf, *
+ * core.eol and the .gitattributes eol, text, binary (macro for -diff -merge
+ * -text)
+ */
+@RunWith(Theories.class)
+public class EolRepositoryTest extends RepositoryTestCase {
+	private static final FileMode D = FileMode.TREE;
+
+	private static final FileMode F = FileMode.REGULAR_FILE;
+
+	@DataPoint
+	public static String smallContents[] = {
+			generateTestData(3, 1, true, false),
+			generateTestData(3, 1, false, true),
+			generateTestData(3, 1, true, true) };
+
+	@DataPoint
+	public static String hugeContents[] = {
+			generateTestData(1000000, 17, true, false),
+			generateTestData(1000000, 17, false, true),
+			generateTestData(1000000, 17, true, true) };
+
+	static String generateTestData(int size, int lineSize, boolean withCRLF,
+			boolean withLF) {
+		StringBuilder sb = new StringBuilder();
+		for (int i = 0; i < size; i++) {
+			if (i > 0 && i % lineSize == 0) {
+				// newline
+				if (withCRLF && withLF) {
+					// mixed
+					if (i % 2 == 0)
+						sb.append("\r\n");
+					else
+						sb.append("\n");
+				} else if (withCRLF) {
+					sb.append("\r\n");
+				} else if (withLF) {
+					sb.append("\n");
+				}
+			}
+			sb.append("A");
+		}
+		return sb.toString();
+	}
+
+	public EolRepositoryTest(String[] testContent) {
+		CONTENT_CRLF = testContent[0];
+		CONTENT_LF = testContent[1];
+		CONTENT_MIXED = testContent[2];
+	}
+
+	protected String CONTENT_CRLF;
+
+	protected String CONTENT_LF;
+
+	protected String CONTENT_MIXED;
+
+	private TreeWalk walk;
+
+	/** work tree root .gitattributes */
+	private File dotGitattributes;
+
+	/** file containing CRLF */
+	private File fileCRLF;
+
+	/** file containing LF */
+	private File fileLF;
+
+	/** file containing mixed CRLF and LF */
+	private File fileMixed;
+
+	/** this values are set in {@link #collectRepositoryState()} */
+	private static class ActualEntry {
+		private String attrs;
+
+		private String file;
+
+		private String index;
+
+		private int indexContentLength;
+	}
+
+	private ActualEntry entryCRLF = new ActualEntry();
+
+	private ActualEntry entryLF = new ActualEntry();
+
+	private ActualEntry entryMixed = new ActualEntry();
+
+	private DirCache dc;
+
+	@Test
+	public void testDefaultSetup() throws Exception {
+		// for EOL to work, the text attribute must be set
+		setupGitAndDoHardReset(null, null, null, null, "* text=auto");
+		collectRepositoryState();
+		assertEquals("text=auto", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
+	}
+
+	public void checkEntryContent(ActualEntry entry, String fileContent,
+			String indexContent) {
+		assertEquals(fileContent, entry.file);
+		assertEquals(indexContent, entry.index);
+		assertEquals(fileContent.length(), entry.indexContentLength);
+	}
+
+	@Test
+	public void test_ConfigAutoCRLF_false() throws Exception {
+		// for EOL to work, the text attribute must be set
+		setupGitAndDoHardReset(AutoCRLF.FALSE, null, null, null, "* text=auto");
+		collectRepositoryState();
+		assertEquals("text=auto", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
+	}
+
+	@Test
+	public void test_ConfigAutoCRLF_true() throws Exception {
+		// for EOL to work, the text attribute must be set
+		setupGitAndDoHardReset(AutoCRLF.TRUE, null, null, null, "* text=auto");
+		collectRepositoryState();
+		assertEquals("text=auto", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
+	}
+
+	@Test
+	public void test_ConfigAutoCRLF_input() throws Exception {
+		// for EOL to work, the text attribute must be set
+		setupGitAndDoHardReset(AutoCRLF.INPUT, null, null, null, "* text=auto");
+		collectRepositoryState();
+		assertEquals("text=auto", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
+	}
+
+	@Test
+	public void test_ConfigEOL_lf() throws Exception {
+		// for EOL to work, the text attribute must be set
+		setupGitAndDoHardReset(null, EOL.LF, "*.txt text", null, null);
+		collectRepositoryState();
+		assertEquals("text", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
+	}
+
+	@Test
+	public void test_ConfigEOL_crlf() throws Exception {
+		// for EOL to work, the text attribute must be set
+		setupGitAndDoHardReset(null, EOL.CRLF, "*.txt text", null, null);
+		collectRepositoryState();
+		assertEquals("text", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
+	}
+
+	@Test
+	public void test_ConfigEOL_native_windows() throws Exception {
+		String origLineSeparator = System.getProperty("line.separator", "\n");
+		System.setProperty("line.separator", "\r\n");
+		try {
+			// for EOL to work, the text attribute must be set
+			setupGitAndDoHardReset(null, EOL.NATIVE, "*.txt text", null, null);
+			collectRepositoryState();
+			assertEquals("text", entryCRLF.attrs);
+			checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
+			checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
+		} finally {
+			System.setProperty("line.separator", origLineSeparator);
+		}
+	}
+
+	@Test
+	public void test_ConfigEOL_native_xnix() throws Exception {
+		String origLineSeparator = System.getProperty("line.separator", "\n");
+		System.setProperty("line.separator", "\n");
+		try {
+			// for EOL to work, the text attribute must be set
+			setupGitAndDoHardReset(null, EOL.NATIVE, "*.txt text", null, null);
+			collectRepositoryState();
+			assertEquals("text", entryCRLF.attrs);
+			checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
+			checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
+		} finally {
+			System.setProperty("line.separator", origLineSeparator);
+		}
+	}
+
+	@Test
+	public void test_ConfigAutoCRLF_false_ConfigEOL_lf() throws Exception {
+		// for EOL to work, the text attribute must be set
+		setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.LF, "*.txt text", null, null);
+		collectRepositoryState();
+		assertEquals("text", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
+	}
+
+	@Test
+	public void test_ConfigAutoCRLF_false_ConfigEOL_native() throws Exception {
+		// for EOL to work, the text attribute must be set
+		setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.NATIVE, "*.txt text", null, null);
+		collectRepositoryState();
+		assertEquals("text", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
+	}
+
+	@Test
+	public void test_ConfigAutoCRLF_true_ConfigEOL_lf() throws Exception {
+		// for EOL to work, the text attribute must be set
+		setupGitAndDoHardReset(AutoCRLF.TRUE, EOL.LF, "*.txt text", null, null);
+		collectRepositoryState();
+		assertEquals("text", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
+	}
+
+	@Test
+	public void test_switchToBranchWithTextAttributes()
+			throws Exception {
+		Git git = Git.wrap(db);
+
+		// for EOL to work, the text attribute must be set
+		setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.CRLF, null, null,
+				"file1.txt text\nfile2.txt text\nfile3.txt text");
+		collectRepositoryState();
+		assertEquals("text", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
+
+		// switch to binary for file1
+		dotGitattributes = createAndAddFile(git, Constants.DOT_GIT_ATTRIBUTES,
+				"file1.txt binary\nfile2.txt text\nfile3.txt text");
+		gitCommit(git, "switchedToBinaryFor1");
+		recreateWorktree(git);
+		collectRepositoryState();
+		assertEquals("binary -diff -merge -text", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
+		assertEquals("text", entryLF.attrs);
+		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
+		assertEquals("text", entryMixed.attrs);
+		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
+
+		// checkout the commit which has text for file1
+		gitCheckout(git, "HEAD^");
+		recreateWorktree(git);
+		collectRepositoryState();
+		assertEquals("text", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
+	}
+
+	@Test
+	public void test_switchToBranchWithBinaryAttributes() throws Exception {
+		Git git = Git.wrap(db);
+
+		// for EOL to work, the text attribute must be set
+		setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.LF, null, null,
+				"file1.txt binary\nfile2.txt binary\nfile3.txt binary");
+		collectRepositoryState();
+		assertEquals("binary -diff -merge -text", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_CRLF);
+		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_MIXED, CONTENT_MIXED);
+
+		// switch to text for file1
+		dotGitattributes = createAndAddFile(git, Constants.DOT_GIT_ATTRIBUTES,
+				"file1.txt text\nfile2.txt binary\nfile3.txt binary");
+		gitCommit(git, "switchedToTextFor1");
+		recreateWorktree(git);
+		collectRepositoryState();
+		assertEquals("text", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
+		assertEquals("binary -diff -merge -text", entryLF.attrs);
+		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
+		assertEquals("binary -diff -merge -text", entryMixed.attrs);
+		checkEntryContent(entryMixed, CONTENT_MIXED, CONTENT_MIXED);
+
+		// checkout the commit which has text for file1
+		gitCheckout(git, "HEAD^");
+		recreateWorktree(git);
+		collectRepositoryState();
+		assertEquals("binary -diff -merge -text", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_CRLF);
+		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_MIXED, CONTENT_MIXED);
+	}
+
+	@Test
+	public void test_ConfigAutoCRLF_input_ConfigEOL_lf() throws Exception {
+		// for EOL to work, the text attribute must be set
+		setupGitAndDoHardReset(AutoCRLF.INPUT, EOL.LF, "*.txt text", null, null);
+		collectRepositoryState();
+		assertEquals("text", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
+	}
+
+	@Test
+	public void test_ConfigAutoCRLF_true_GlobalEOL_lf() throws Exception {
+		setupGitAndDoHardReset(AutoCRLF.TRUE, EOL.LF, "*.txt eol=lf", null, null);
+		collectRepositoryState();
+		assertEquals("eol=lf", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
+	}
+
+	@Test
+	public void test_ConfigAutoCRLF_false_GlobalEOL_lf() throws Exception {
+		setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.LF, "*.txt eol=lf", null, null);
+		collectRepositoryState();
+		assertEquals("eol=lf", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
+	}
+
+	@Test
+	public void test_ConfigAutoCRLF_input_GlobalEOL_lf() throws Exception {
+		setupGitAndDoHardReset(AutoCRLF.INPUT, EOL.LF, "*.txt eol=lf", null, null);
+		collectRepositoryState();
+		assertEquals("eol=lf", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
+	}
+
+	@Test
+	public void test_ConfigAutoCRLF_true_GlobalEOL_crlf() throws Exception {
+		setupGitAndDoHardReset(AutoCRLF.TRUE, EOL.LF, "*.txt eol=crlf", null, null);
+		collectRepositoryState();
+		assertEquals("eol=crlf", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
+	}
+
+	@Test
+	public void test_ConfigAutoCRLF_false_GlobalEOL_crlf() throws Exception {
+		setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.LF, "*.txt eol=crlf", null, null);
+		collectRepositoryState();
+		assertEquals("eol=crlf", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
+	}
+
+	@Test
+	public void test_ConfigAutoCRLF_input_GlobalEOL_crlf() throws Exception {
+		setupGitAndDoHardReset(AutoCRLF.INPUT, EOL.LF, "*.txt eol=crlf", null, null);
+		collectRepositoryState();
+		assertEquals("eol=crlf", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
+	}
+
+	@Test
+	public void test_ConfigAutoCRLF_true_GlobalEOL_lf_InfoEOL_crlf()
+			throws Exception {
+		setupGitAndDoHardReset(AutoCRLF.TRUE, null, "*.txt eol=lf", "*.txt eol=crlf", null);
+		// info decides
+		collectRepositoryState();
+		assertEquals("eol=crlf", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
+	}
+
+	@Test
+	public void test_ConfigAutoCRLF_false_GlobalEOL_crlf_InfoEOL_lf()
+			throws Exception {
+		setupGitAndDoHardReset(AutoCRLF.FALSE, null, "*.txt eol=crlf", "*.txt eol=lf", null);
+		// info decides
+		collectRepositoryState();
+		assertEquals("eol=lf", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
+	}
+
+	@Test
+	public void test_GlobalEOL_lf_RootEOL_crlf() throws Exception {
+		setupGitAndDoHardReset(null, null, "*.txt eol=lf", null, "*.txt eol=crlf");
+		// root over global
+		collectRepositoryState();
+		assertEquals("eol=crlf", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
+	}
+
+	@Test
+	public void test_GlobalEOL_lf_InfoEOL_crlf_RootEOL_lf() throws Exception {
+		setupGitAndDoHardReset(null, null, "*.txt eol=lf", "*.txt eol=crlf", "*.txt eol=lf");
+		// info overrides all
+		collectRepositoryState();
+		assertEquals("eol=crlf", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
+	}
+
+	@Test
+	public void test_GlobalEOL_lf_InfoEOL_crlf_RootEOL_unspec()
+			throws Exception {
+		setupGitAndDoHardReset(null, null, "*.txt eol=lf", "*.txt eol=crlf",
+				"*.txt text !eol");
+		// info overrides all
+		collectRepositoryState();
+		assertEquals("eol=crlf text", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
+	}
+
+	@Test
+	public void test_GlobalEOL_lf_InfoEOL_unspec_RootEOL_crlf()
+			throws Exception {
+		setupGitAndDoHardReset(null, null, "*.txt eol=lf", "*.txt !eol",
+				"*.txt text eol=crlf");
+		// info overrides all
+		collectRepositoryState();
+		assertEquals("text", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
+	}
+
+	@Test
+	public void testBinary1() throws Exception {
+		setupGitAndDoHardReset(AutoCRLF.TRUE, EOL.CRLF, "*.txt text", "*.txt binary",
+				"*.txt eol=crlf");
+		// info overrides all
+		collectRepositoryState();
+		assertEquals("binary -diff -merge -text eol=crlf", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_CRLF);
+		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_MIXED, CONTENT_MIXED);
+	}
+
+	@Test
+	public void testBinary2() throws Exception {
+		setupGitAndDoHardReset(AutoCRLF.TRUE, EOL.CRLF, "*.txt text eol=crlf", null,
+				"*.txt binary");
+		// root over global
+		collectRepositoryState();
+		assertEquals("binary -diff -merge -text eol=crlf", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_CRLF);
+		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryMixed, CONTENT_MIXED, CONTENT_MIXED);
+	}
+
+	// create new repo with
+	// global .gitattributes
+	// info .git/config/info/.gitattributes
+	// workdir root .gitattributes
+	// text file lf.txt CONTENT_LF
+	// text file crlf.txt CONTENT_CRLF
+	//
+	// commit files (checkin)
+	// delete working dir files
+	// reset hard (checkout)
+	private void setupGitAndDoHardReset(AutoCRLF autoCRLF, EOL eol,
+			String globalAttributesContent, String infoAttributesContent,
+			String workDirRootAttributesContent) throws Exception {
+		Git git = new Git(db);
+		FileBasedConfig config = db.getConfig();
+		if (autoCRLF != null) {
+			config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
+					ConfigConstants.CONFIG_KEY_AUTOCRLF, autoCRLF);
+		}
+		if (eol != null) {
+			config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
+					ConfigConstants.CONFIG_KEY_EOL, eol);
+		}
+		if (globalAttributesContent != null) {
+			File f = new File(db.getDirectory(), "global/attrs");
+			write(f, globalAttributesContent);
+			config.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
+					ConfigConstants.CONFIG_KEY_ATTRIBUTESFILE,
+					f.getAbsolutePath());
+
+		}
+		if (infoAttributesContent != null) {
+			File f = new File(db.getDirectory(), Constants.INFO_ATTRIBUTES);
+			write(f, infoAttributesContent);
+		}
+		config.save();
+
+		if (workDirRootAttributesContent != null) {
+			dotGitattributes = createAndAddFile(git,
+					Constants.DOT_GIT_ATTRIBUTES, workDirRootAttributesContent);
+		} else {
+			dotGitattributes = null;
+		}
+
+		fileCRLF = createAndAddFile(git, "file1.txt", CONTENT_CRLF);
+
+		fileLF = createAndAddFile(git, "file2.txt", CONTENT_LF);
+
+		fileMixed = createAndAddFile(git, "file3.txt", CONTENT_MIXED);
+
+		gitCommit(git, "addFiles");
+
+		recreateWorktree(git);
+	}
+
+	private void recreateWorktree(Git git)
+			throws GitAPIException, CheckoutConflictException,
+			InterruptedException, IOException, NoFilepatternException {
+		// re-create file from the repo
+		for (File f : new File[] { dotGitattributes, fileCRLF, fileLF, fileMixed }) {
+			if (f == null)
+				continue;
+			f.delete();
+			Assert.assertFalse(f.exists());
+		}
+		gitResetHard(git);
+		fsTick(db.getIndexFile());
+		gitAdd(git, ".");
+	}
+
+	protected void gitCommit(Git git, String msg) throws GitAPIException {
+		git.commit().setMessage(msg).call();
+	}
+
+	protected void gitAdd(Git git, String path) throws GitAPIException {
+		git.add().addFilepattern(path).call();
+	}
+
+	protected void gitResetHard(Git git) throws GitAPIException {
+		git.reset().setMode(ResetType.HARD).call();
+	}
+
+	protected void gitCheckout(Git git, String revstr)
+			throws GitAPIException, RevisionSyntaxException, IOException {
+		git.checkout().setName(db.resolve(revstr).getName()).call();
+	}
+
+	// create a file and add it to the repo
+	private File createAndAddFile(Git git, String path, String content)
+			throws Exception {
+		File f;
+		int pos = path.lastIndexOf('/');
+		if (pos < 0) {
+			f = writeTrashFile(path, content);
+		} else {
+			f = writeTrashFile(path.substring(0, pos), path.substring(pos + 1),
+					content);
+		}
+		gitAdd(git, path);
+		Assert.assertTrue(f.exists());
+		return f;
+	}
+
+	private void collectRepositoryState() throws Exception {
+		dc = db.readDirCache();
+		walk = beginWalk();
+		if (dotGitattributes != null)
+			collectEntryContentAndAttributes(F, ".gitattributes", null);
+		collectEntryContentAndAttributes(F, fileCRLF.getName(), entryCRLF);
+		collectEntryContentAndAttributes(F, fileLF.getName(), entryLF);
+		collectEntryContentAndAttributes(F, fileMixed.getName(), entryMixed);
+		endWalk();
+	}
+
+	private TreeWalk beginWalk() throws Exception {
+		TreeWalk newWalk = new TreeWalk(db);
+		newWalk.addTree(new FileTreeIterator(db));
+		newWalk.addTree(new DirCacheIterator(db.readDirCache()));
+		return newWalk;
+	}
+
+	private void endWalk() throws IOException {
+		assertFalse("Not all files tested", walk.next());
+	}
+
+	private void collectEntryContentAndAttributes(FileMode type, String pathName,
+			ActualEntry e) throws IOException {
+		assertTrue("walk has entry", walk.next());
+
+		assertEquals(pathName, walk.getPathString());
+		assertEquals(type, walk.getFileMode(0));
+
+		if (e != null) {
+			e.attrs = "";
+			for (Attribute a : walk.getAttributes().getAll()) {
+				e.attrs += " " + a.toString();
+			}
+			e.attrs = e.attrs.trim();
+			e.file = new String(
+					IO.readFully(new File(db.getWorkTree(), pathName)));
+			DirCacheEntry dce = dc.getEntry(pathName);
+			ObjectLoader open = walk.getObjectReader().open(dce.getObjectId());
+			e.index = new String(open.getBytes());
+			e.indexContentLength = dce.getLength();
+		}
+
+		if (D.equals(type))
+			walk.enterSubtree();
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolStreamTypeUtilTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolStreamTypeUtilTest.java
new file mode 100644
index 0000000..8ca1b31
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolStreamTypeUtilTest.java
@@ -0,0 +1,335 @@
+/*
+ * 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.api;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.eclipse.jgit.lib.CoreConfig.EolStreamType.*;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
+import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.io.EolStreamTypeUtil;
+import org.junit.Test;
+
+/**
+ * Unit tests for end-of-line conversion streams
+ */
+public class EolStreamTypeUtilTest {
+
+	@Test
+	public void testCheckoutDirect() throws Exception {
+		testCheckout(DIRECT, DIRECT, "", "");
+		testCheckout(DIRECT, DIRECT, "\r", "\r");
+		testCheckout(DIRECT, DIRECT, "\n", "\n");
+
+		testCheckout(DIRECT, DIRECT, "\r\n", "\r\n");
+		testCheckout(DIRECT, DIRECT, "\n\r", "\n\r");
+
+		testCheckout(DIRECT, DIRECT, "\n\r\n", "\n\r\n");
+		testCheckout(DIRECT, DIRECT, "\r\n\r", "\r\n\r");
+
+		testCheckout(DIRECT, DIRECT, "a\nb\n", "a\nb\n");
+		testCheckout(DIRECT, DIRECT, "a\rb\r", "a\rb\r");
+		testCheckout(DIRECT, DIRECT, "a\n\rb\n\r", "a\n\rb\n\r");
+		testCheckout(DIRECT, DIRECT, "a\r\nb\r\n", "a\r\nb\r\n");
+	}
+
+	@Test
+	public void testCheckoutLF() throws Exception {
+		testCheckout(TEXT_LF, AUTO_LF, "", "");
+		testCheckout(TEXT_LF, AUTO_LF, "\r", "\r");
+		testCheckout(TEXT_LF, AUTO_LF, "\n", "\n");
+
+		testCheckout(TEXT_LF, AUTO_LF, "\r\n", "\n");
+		testCheckout(TEXT_LF, AUTO_LF, "\n\r", "\n\r");
+
+		testCheckout(TEXT_LF, AUTO_LF, "\n\r\n", "\n\n");
+		testCheckout(TEXT_LF, AUTO_LF, "\r\n\r", "\n\r");
+
+		testCheckout(TEXT_LF, AUTO_LF, "a\nb\n", "a\nb\n");
+		testCheckout(TEXT_LF, AUTO_LF, "a\rb\r", "a\rb\r");
+		testCheckout(TEXT_LF, AUTO_LF, "a\n\rb\n\r", "a\n\rb\n\r");
+		testCheckout(TEXT_LF, AUTO_LF, "a\r\nb\r\n", "a\nb\n");
+	}
+
+	@Test
+	public void testCheckoutCRLF() throws Exception {
+		testCheckout(TEXT_CRLF, AUTO_CRLF, "", "");
+		testCheckout(TEXT_CRLF, AUTO_CRLF, "\r", "\r");
+		testCheckout(TEXT_CRLF, AUTO_CRLF, "\n", "\r\n");
+
+		testCheckout(TEXT_CRLF, AUTO_CRLF, "\r\n", "\r\n");
+		testCheckout(TEXT_CRLF, AUTO_CRLF, "\n\r", "\r\n\r");
+
+		testCheckout(TEXT_CRLF, AUTO_CRLF, "\n\r\n", "\r\n\r\n");
+		testCheckout(TEXT_CRLF, AUTO_CRLF, "\r\n\r", "\r\n\r");
+
+		testCheckout(TEXT_CRLF, AUTO_CRLF, "a\nb\n", "a\r\nb\r\n");
+		testCheckout(TEXT_CRLF, AUTO_CRLF, "a\rb\r", "a\rb\r");
+		testCheckout(TEXT_CRLF, AUTO_CRLF, "a\n\rb\n\r", "a\r\n\rb\r\n\r");
+		testCheckout(TEXT_CRLF, AUTO_CRLF, "a\r\nb\r\n", "a\r\nb\r\n");
+	}
+
+	/**
+	 * Test stream type detection based on stream content.
+	 * <p>
+	 * Tests three things with the output text:
+	 * <p>
+	 * 1) conversion if output was declared as text
+	 * <p>
+	 * 2) conversion if output was declared as potentially text (AUTO_...) and
+	 * is in fact text
+	 * <p>
+	 * 3) conversion if modified output (now with binary characters) was
+	 * declared as potentially text but now contains binary characters
+	 * <p>
+	 *
+	 * @param streamTypeText
+	 *            is the enum meaning that the output is definitely text (no
+	 *            binary check at all)
+	 * @param streamTypeWithBinaryCheck
+	 *            is the enum meaning that the output may be text (binary check
+	 *            is done)
+	 * @param output
+	 *            is a text output without binary characters
+	 * @param expectedConversion
+	 *            is the expected converted output without binary characters
+	 * @throws Exception
+	 */
+	private void testCheckout(EolStreamType streamTypeText,
+			EolStreamType streamTypeWithBinaryCheck, String output,
+			String expectedConversion) throws Exception {
+		ByteArrayOutputStream b;
+		byte[] outputBytes = output.getBytes(StandardCharsets.UTF_8);
+		byte[] expectedConversionBytes = expectedConversion
+				.getBytes(StandardCharsets.UTF_8);
+
+		// test using output text and assuming it was declared TEXT
+		b = new ByteArrayOutputStream();
+		try (OutputStream out = EolStreamTypeUtil.wrapOutputStream(b,
+				streamTypeText)) {
+			out.write(outputBytes);
+		}
+		assertArrayEquals(expectedConversionBytes, b.toByteArray());
+
+		// test using ouput text and assuming it was declared AUTO, using binary
+		// detection
+		b = new ByteArrayOutputStream();
+		try (OutputStream out = EolStreamTypeUtil.wrapOutputStream(b,
+				streamTypeWithBinaryCheck)) {
+			out.write(outputBytes);
+		}
+		assertArrayEquals(expectedConversionBytes, b.toByteArray());
+
+		// now pollute output text with some binary bytes
+		outputBytes = extendWithBinaryData(outputBytes);
+		expectedConversionBytes = extendWithBinaryData(expectedConversionBytes);
+
+		// again, test using output text and assuming it was declared TEXT
+		b = new ByteArrayOutputStream();
+		try (OutputStream out = EolStreamTypeUtil.wrapOutputStream(b,
+				streamTypeText)) {
+			out.write(outputBytes);
+		}
+		assertArrayEquals(expectedConversionBytes, b.toByteArray());
+
+		// again, test using ouput text and assuming it was declared AUTO, using
+		// binary
+		// detection
+		b = new ByteArrayOutputStream();
+		try (OutputStream out = EolStreamTypeUtil.wrapOutputStream(b,
+				streamTypeWithBinaryCheck)) {
+			out.write(outputBytes);
+		}
+		// expect no conversion
+		assertArrayEquals(outputBytes, b.toByteArray());
+	}
+
+	@Test
+	public void testCheckinDirect() throws Exception {
+		testCheckin(DIRECT, DIRECT, "", "");
+		testCheckin(DIRECT, DIRECT, "\r", "\r");
+		testCheckin(DIRECT, DIRECT, "\n", "\n");
+
+		testCheckin(DIRECT, DIRECT, "\r\n", "\r\n");
+		testCheckin(DIRECT, DIRECT, "\n\r", "\n\r");
+
+		testCheckin(DIRECT, DIRECT, "\n\r\n", "\n\r\n");
+		testCheckin(DIRECT, DIRECT, "\r\n\r", "\r\n\r");
+
+		testCheckin(DIRECT, DIRECT, "a\nb\n", "a\nb\n");
+		testCheckin(DIRECT, DIRECT, "a\rb\r", "a\rb\r");
+		testCheckin(DIRECT, DIRECT, "a\n\rb\n\r", "a\n\rb\n\r");
+		testCheckin(DIRECT, DIRECT, "a\r\nb\r\n", "a\r\nb\r\n");
+	}
+
+	@Test
+	public void testCheckinLF() throws Exception {
+		testCheckin(TEXT_LF, AUTO_LF, "", "");
+		testCheckin(TEXT_LF, AUTO_LF, "\r", "\r");
+		testCheckin(TEXT_LF, AUTO_LF, "\n", "\n");
+
+		testCheckin(TEXT_LF, AUTO_LF, "\r\n", "\n");
+		testCheckin(TEXT_LF, AUTO_LF, "\n\r", "\n\r");
+
+		testCheckin(TEXT_LF, AUTO_LF, "\n\r\n", "\n\n");
+		testCheckin(TEXT_LF, AUTO_LF, "\r\n\r", "\n\r");
+
+		testCheckin(TEXT_LF, AUTO_LF, "a\nb\n", "a\nb\n");
+		testCheckin(TEXT_LF, AUTO_LF, "a\rb\r", "a\rb\r");
+		testCheckin(TEXT_LF, AUTO_LF, "a\n\rb\n\r", "a\n\rb\n\r");
+		testCheckin(TEXT_LF, AUTO_LF, "a\r\nb\r\n", "a\nb\n");
+	}
+
+	@Test
+	public void testCheckinCRLF() throws Exception {
+		testCheckin(TEXT_CRLF, AUTO_CRLF, "", "");
+		testCheckin(TEXT_CRLF, AUTO_CRLF, "\r", "\r");
+		testCheckin(TEXT_CRLF, AUTO_CRLF, "\n", "\r\n");
+
+		testCheckin(TEXT_CRLF, AUTO_CRLF, "\r\n", "\r\n");
+		testCheckin(TEXT_CRLF, AUTO_CRLF, "\n\r", "\r\n\r");
+
+		testCheckin(TEXT_CRLF, AUTO_CRLF, "\n\r\n", "\r\n\r\n");
+		testCheckin(TEXT_CRLF, AUTO_CRLF, "\r\n\r", "\r\n\r");
+
+		testCheckin(TEXT_CRLF, AUTO_CRLF, "a\nb\n", "a\r\nb\r\n");
+		testCheckin(TEXT_CRLF, AUTO_CRLF, "a\rb\r", "a\rb\r");
+		testCheckin(TEXT_CRLF, AUTO_CRLF, "a\n\rb\n\r", "a\r\n\rb\r\n\r");
+		testCheckin(TEXT_CRLF, AUTO_CRLF, "a\r\nb\r\n", "a\r\nb\r\n");
+	}
+
+	/**
+	 * Test stream type detection based on stream content.
+	 * <p>
+	 * Tests three things with the input text:
+	 * <p>
+	 * 1) conversion if input was declared as text
+	 * <p>
+	 * 2) conversion if input was declared as potentially text (AUTO_...) and is
+	 * in fact text
+	 * <p>
+	 * 3) conversion if modified input (now with binary characters) was declared
+	 * as potentially text but now contains binary characters
+	 * <p>
+	 *
+	 * @param streamTypeText
+	 *            is the enum meaning that the input is definitely text (no
+	 *            binary check at all)
+	 * @param streamTypeWithBinaryCheck
+	 *            is the enum meaning that the input may be text (binary check
+	 *            is done)
+	 * @param input
+	 *            is a text input without binary characters
+	 * @param expectedConversion
+	 *            is the expected converted input without binary characters
+	 * @throws Exception
+	 */
+	private void testCheckin(EolStreamType streamTypeText,
+			EolStreamType streamTypeWithBinaryCheck, String input,
+			String expectedConversion) throws Exception {
+		byte[] inputBytes = input.getBytes(StandardCharsets.UTF_8);
+		byte[] expectedConversionBytes = expectedConversion
+				.getBytes(StandardCharsets.UTF_8);
+
+		// test using input text and assuming it was declared TEXT
+		try (InputStream in = EolStreamTypeUtil.wrapInputStream(
+				new ByteArrayInputStream(inputBytes),
+				streamTypeText)) {
+			byte[] b = new byte[1024];
+			int len = IO.readFully(in, b, 0);
+			assertArrayEquals(expectedConversionBytes, Arrays.copyOf(b, len));
+		}
+
+		// test using input text and assuming it was declared AUTO, using binary
+		// detection
+		try (InputStream in = EolStreamTypeUtil.wrapInputStream(
+				new ByteArrayInputStream(inputBytes),
+				streamTypeWithBinaryCheck)) {
+			byte[] b = new byte[1024];
+			int len = IO.readFully(in, b, 0);
+			assertArrayEquals(expectedConversionBytes, Arrays.copyOf(b, len));
+		}
+
+		// now pollute input text with some binary bytes
+		inputBytes = extendWithBinaryData(inputBytes);
+		expectedConversionBytes = extendWithBinaryData(expectedConversionBytes);
+
+		// again, test using input text and assuming it was declared TEXT
+		try (InputStream in = EolStreamTypeUtil.wrapInputStream(
+				new ByteArrayInputStream(inputBytes), streamTypeText)) {
+			byte[] b = new byte[1024];
+			int len = IO.readFully(in, b, 0);
+			assertArrayEquals(expectedConversionBytes, Arrays.copyOf(b, len));
+		}
+
+		// again, test using input text and assuming it was declared AUTO, using
+		// binary
+		// detection
+		try (InputStream in = EolStreamTypeUtil.wrapInputStream(
+				new ByteArrayInputStream(inputBytes),
+				streamTypeWithBinaryCheck)) {
+			byte[] b = new byte[1024];
+			int len = IO.readFully(in, b, 0);
+			// expect no conversion
+			assertArrayEquals(inputBytes, Arrays.copyOf(b, len));
+		}
+	}
+
+	private byte[] extendWithBinaryData(byte[] data) throws Exception {
+		int n = 3;
+		byte[] dataEx = new byte[data.length + n];
+		System.arraycopy(data, 0, dataEx, 0, data.length);
+		for (int i = 0; i < n; i++) {
+			dataEx[data.length + i] = (byte) i;
+		}
+		return dataEx;
+	}
+
+}
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 c1b882a..5578c03 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
@@ -65,6 +65,7 @@
 import org.eclipse.jgit.api.errors.NoFilepatternException;
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheCheckout;
+import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
 import org.eclipse.jgit.dircache.DirCacheEditor;
 import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
 import org.eclipse.jgit.dircache.DirCacheEntry;
@@ -113,7 +114,7 @@
 		return dco.getRemoved();
 	}
 
-	private Map<String, String> getUpdated() {
+	private Map<String, CheckoutMetadata> getUpdated() {
 		return dco.getUpdated();
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
index df17a3e..f80e117 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
@@ -62,15 +62,14 @@
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
+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.FileMode;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.*;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
 import org.eclipse.jgit.treewalk.WorkingTreeIterator.MetadataDiff;
 import org.eclipse.jgit.treewalk.filter.PathFilter;
+import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FileUtils;
 import org.eclipse.jgit.util.RawParseUtils;
 import org.junit.Before;
@@ -537,6 +536,87 @@
 		}
 	}
 
+	private final FileTreeIterator.FileModeStrategy NO_GITLINKS_STRATEGY =
+			new FileTreeIterator.FileModeStrategy() {
+				@Override
+				public FileMode getMode(File f, FS.Attributes attributes) {
+					if (attributes.isSymbolicLink()) {
+						return FileMode.SYMLINK;
+					} else if (attributes.isDirectory()) {
+						// NOTE: in the production DefaultFileModeStrategy, there is
+						// a check here for a subdirectory called '.git', and if it
+						// exists, we create a GITLINK instead of recursing into the
+						// tree.  In this custom strategy, we ignore nested git dirs
+						// and treat all directories the same.
+						return FileMode.TREE;
+					} else if (attributes.isExecutable()) {
+						return FileMode.EXECUTABLE_FILE;
+					} else {
+						return FileMode.REGULAR_FILE;
+					}
+				}
+			};
+
+	private Repository createNestedRepo() throws IOException {
+		File gitdir = createUniqueTestGitDir(false);
+		FileRepositoryBuilder builder = new FileRepositoryBuilder();
+		builder.setGitDir(gitdir);
+		Repository nestedRepo = builder.build();
+		nestedRepo.create();
+
+		JGitTestUtil.writeTrashFile(nestedRepo, "sub", "a.txt", "content");
+
+		File nestedRepoPath = new File(nestedRepo.getWorkTree(), "sub/nested");
+		FileRepositoryBuilder nestedBuilder = new FileRepositoryBuilder();
+		nestedBuilder.setWorkTree(nestedRepoPath);
+		nestedBuilder.build().create();
+
+		JGitTestUtil.writeTrashFile(nestedRepo, "sub/nested", "b.txt",
+				"content b");
+
+		return nestedRepo;
+	}
+
+	@Test
+	public void testCustomFileModeStrategy() throws Exception {
+		Repository nestedRepo = createNestedRepo();
+
+		Git git = new Git(nestedRepo);
+		// validate that our custom strategy is honored
+		WorkingTreeIterator customIterator =
+				new FileTreeIterator(nestedRepo, NO_GITLINKS_STRATEGY);
+		git.add().setWorkingTreeIterator(customIterator)
+				.addFilepattern(".").call();
+		assertEquals(
+				"[sub/a.txt, mode:100644, content:content]" +
+				"[sub/nested/b.txt, mode:100644, content:content b]",
+				indexState(nestedRepo, CONTENT));
+
+	}
+
+	@Test
+	public void testCustomFileModeStrategyFromParentIterator() throws Exception {
+		Repository nestedRepo = createNestedRepo();
+
+		Git git = new Git(nestedRepo);
+
+		FileTreeIterator customIterator =
+				new FileTreeIterator(nestedRepo, NO_GITLINKS_STRATEGY);
+		File r = new File(nestedRepo.getWorkTree(), "sub");
+
+		// here we want to validate that if we create a new iterator using the
+		// constructor that accepts a parent iterator, that the child iterator
+		// correctly inherits the FileModeStrategy from the parent iterator.
+		FileTreeIterator childIterator =
+				new FileTreeIterator(customIterator, r, nestedRepo.getFS());
+		git.add().setWorkingTreeIterator(childIterator).addFilepattern(".").call();
+		assertEquals(
+				"[sub/a.txt, mode:100644, content:content]" +
+				"[sub/nested/b.txt, mode:100644, content:content b]",
+				indexState(nestedRepo, CONTENT));
+	}
+
+
 	private static void assertEntry(String sha1string, String path, TreeWalk tw)
 			throws MissingObjectException, IncorrectObjectTypeException,
 			CorruptObjectException, IOException {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/EolCanonicalizingInputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoLFInputStreamTest.java
similarity index 94%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/EolCanonicalizingInputStreamTest.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoLFInputStreamTest.java
index ed2a4f2..40cac93 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/EolCanonicalizingInputStreamTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoLFInputStreamTest.java
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2010, Marc Strapetz <marc.strapetz@syntevo.com>
+ * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com>
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -52,7 +53,7 @@
 
 import org.junit.Test;
 
-public class EolCanonicalizingInputStreamTest {
+public class AutoLFInputStreamTest {
 
 	@Test
 	public void testLF() throws IOException {
@@ -97,7 +98,7 @@
 	private static void test(byte[] input, byte[] expected,
 			boolean detectBinary) throws IOException {
 		final InputStream bis1 = new ByteArrayInputStream(input);
-		final InputStream cis1 = new EolCanonicalizingInputStream(bis1, detectBinary);
+		final InputStream cis1 = new AutoLFInputStream(bis1, detectBinary);
 		int index1 = 0;
 		for (int b = cis1.read(); b != -1; b = cis1.read()) {
 			assertEquals(expected[index1], (byte) b);
@@ -109,7 +110,7 @@
 		for (int bufferSize = 1; bufferSize < 10; bufferSize++) {
 			final byte[] buffer = new byte[bufferSize];
 			final InputStream bis2 = new ByteArrayInputStream(input);
-			final InputStream cis2 = new EolCanonicalizingInputStream(bis2, detectBinary);
+			final InputStream cis2 = new AutoLFInputStream(bis2, detectBinary);
 
 			int read = 0;
 			for (int readNow = cis2.read(buffer, 0, buffer.length); readNow != -1
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index a5000dd..c0dbc77 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -16,4 +16,25 @@
             </message_arguments>
         </filter>
     </resource>
+    <resource path="src/org/eclipse/jgit/dircache/DirCacheCheckout.java" type="org.eclipse.jgit.dircache.DirCacheCheckout">
+        <filter comment="add eol stream type conversion" id="338792546">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.dircache.DirCacheCheckout"/>
+                <message_argument value="checkoutEntry(Repository, DirCacheEntry, ObjectReader, boolean)"/>
+            </message_arguments>
+        </filter>
+        <filter comment="add eol stream type conversion" id="338792546">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.dircache.DirCacheCheckout"/>
+                <message_argument value="checkoutEntry(Repository, DirCacheEntry, ObjectReader, boolean, String)"/>
+            </message_arguments>
+        </filter>
+        <filter comment="add eol stream type conversion" id="1141899266">
+            <message_arguments>
+                <message_argument value="4.2"/>
+                <message_argument value="4.3"/>
+                <message_argument value="checkoutEntry(Repository, DirCacheEntry, ObjectReader, boolean, DirCacheCheckout.CheckoutMetadata)"/>
+            </message_arguments>
+        </filter>
+    </resource>
 </component>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
index 676ae03..bde450f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
@@ -201,10 +201,12 @@
 			oldLines.add(rt.getString(i));
 		List<String> newLines = new ArrayList<String>(oldLines);
 		for (HunkHeader hh : fh.getHunks()) {
-			StringBuilder hunk = new StringBuilder();
-			for (int j = hh.getStartOffset(); j < hh.getEndOffset(); j++)
-				hunk.append((char) hh.getBuffer()[j]);
-			RawText hrt = new RawText(hunk.toString().getBytes());
+
+			byte[] b = new byte[hh.getEndOffset() - hh.getStartOffset()];
+			System.arraycopy(hh.getBuffer(), hh.getStartOffset(), b, 0,
+					b.length);
+			RawText hrt = new RawText(b);
+
 			List<String> hunkLines = new ArrayList<String>(hrt.size());
 			for (int i = 0; i < hrt.size(); i++)
 				hunkLines.add(hrt.getString(i));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java
index a83814e..d803efd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java
@@ -66,7 +66,7 @@
 import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
 import org.eclipse.jgit.treewalk.WorkingTreeOptions;
 import org.eclipse.jgit.util.IO;
-import org.eclipse.jgit.util.io.EolCanonicalizingInputStream;
+import org.eclipse.jgit.util.io.AutoLFInputStream;
 
 /**
  * Blame command for building a {@link BlameResult} for a file path.
@@ -248,7 +248,7 @@
 			rawText = new RawText(inTree);
 			break;
 		case TRUE:
-			EolCanonicalizingInputStream in = new EolCanonicalizingInputStream(
+			AutoLFInputStream in = new AutoLFInputStream(
 					new FileInputStream(inTree), true);
 			// Canonicalization should lead to same or shorter length
 			// (CRLF to LF), so the file size on disk is an upper size bound
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 4f918fa..c37c317 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
@@ -59,6 +59,7 @@
 import org.eclipse.jgit.api.errors.RefNotFoundException;
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheCheckout;
+import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
 import org.eclipse.jgit.dircache.DirCacheEditor;
 import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
 import org.eclipse.jgit.dircache.DirCacheEntry;
@@ -68,6 +69,7 @@
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectReader;
@@ -395,7 +397,8 @@
 			RefNotFoundException {
 		DirCache dc = repo.lockDirCache();
 		try (RevWalk revWalk = new RevWalk(repo);
-				TreeWalk treeWalk = new TreeWalk(revWalk.getObjectReader())) {
+				TreeWalk treeWalk = new TreeWalk(repo,
+						revWalk.getObjectReader())) {
 			treeWalk.setRecursive(true);
 			if (!checkoutAllPaths)
 				treeWalk.setFilter(PathFilterGroup.createFromStrings(paths));
@@ -426,20 +429,23 @@
 			if (path.equals(previousPath))
 				continue;
 
+			final EolStreamType eolStreamType = treeWalk.getEolStreamType();
 			editor.add(new PathEdit(path) {
 				public void apply(DirCacheEntry ent) {
 					int stage = ent.getStage();
 					if (stage > DirCacheEntry.STAGE_0) {
 						if (checkoutStage != null) {
 							if (stage == checkoutStage.number)
-								checkoutPath(ent, r);
+								checkoutPath(ent, r, new CheckoutMetadata(
+										eolStreamType, null));
 						} else {
 							UnmergedPathException e = new UnmergedPathException(
 									ent);
 							throw new JGitInternalException(e.getMessage(), e);
 						}
 					} else {
-						checkoutPath(ent, r);
+						checkoutPath(ent, r,
+								new CheckoutMetadata(eolStreamType, null));
 					}
 				}
 			});
@@ -457,20 +463,24 @@
 		while (treeWalk.next()) {
 			final ObjectId blobId = treeWalk.getObjectId(0);
 			final FileMode mode = treeWalk.getFileMode(0);
+			final EolStreamType eolStreamType = treeWalk.getEolStreamType();
 			editor.add(new PathEdit(treeWalk.getPathString()) {
 				public void apply(DirCacheEntry ent) {
 					ent.setObjectId(blobId);
 					ent.setFileMode(mode);
-					checkoutPath(ent, r);
+					checkoutPath(ent, r,
+							new CheckoutMetadata(eolStreamType, null));
 				}
 			});
 		}
 		editor.commit();
 	}
 
-	private void checkoutPath(DirCacheEntry entry, ObjectReader reader) {
+	private void checkoutPath(DirCacheEntry entry, ObjectReader reader,
+			CheckoutMetadata checkoutMetadata) {
 		try {
-			DirCacheCheckout.checkoutEntry(repo, entry, reader, true);
+			DirCacheCheckout.checkoutEntry(repo, entry, reader, true,
+					checkoutMetadata);
 		} catch (IOException e) {
 			throw new JGitInternalException(MessageFormat.format(
 					JGitText.get().checkoutConflictWithFile,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
index 8ef5508..1699b9f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
@@ -54,11 +54,13 @@
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheBuilder;
 import org.eclipse.jgit.dircache.DirCacheCheckout;
+import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
 import org.eclipse.jgit.dircache.DirCacheEntry;
 import org.eclipse.jgit.dircache.DirCacheIterator;
 import org.eclipse.jgit.errors.CheckoutConflictException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.Repository;
@@ -336,6 +338,7 @@
 					// Not in commit, don't create untracked
 					continue;
 
+				final EolStreamType eolStreamType = walk.getEolStreamType();
 				final DirCacheEntry entry = new DirCacheEntry(walk.getRawPath());
 				entry.setFileMode(cIter.getEntryFileMode());
 				entry.setObjectIdFromRaw(cIter.idBuffer(), cIter.idOffset());
@@ -350,14 +353,17 @@
 					}
 				}
 
-				checkoutPath(entry, reader);
+				checkoutPath(entry, reader,
+						new CheckoutMetadata(eolStreamType, null));
 			}
 		}
 	}
 
-	private void checkoutPath(DirCacheEntry entry, ObjectReader reader) {
+	private void checkoutPath(DirCacheEntry entry, ObjectReader reader,
+			CheckoutMetadata checkoutMetadata) {
 		try {
-			DirCacheCheckout.checkoutEntry(repo, entry, reader, true);
+			DirCacheCheckout.checkoutEntry(repo, entry, reader, true,
+					checkoutMetadata);
 		} catch (IOException e) {
 			throw new JGitInternalException(MessageFormat.format(
 					JGitText.get().checkoutConflictWithFile,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
index 2cdaf24..ef32ac9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
@@ -245,12 +245,14 @@
 			DirCache cache = repo.lockDirCache();
 			ObjectId commitId;
 			try (ObjectInserter inserter = repo.newObjectInserter();
-					TreeWalk treeWalk = new TreeWalk(reader)) {
+					TreeWalk treeWalk = new TreeWalk(repo, reader)) {
 
 				treeWalk.setRecursive(true);
 				treeWalk.addTree(headCommit.getTree());
 				treeWalk.addTree(new DirCacheIterator(cache));
 				treeWalk.addTree(new FileTreeIterator(repo));
+				treeWalk.getTree(2, FileTreeIterator.class)
+						.setDirCacheIterator(treeWalk, 1);
 				treeWalk.setFilter(AndTreeFilter.create(new SkipWorkTreeFilter(
 						1), new IndexDiffFilter(1, 2)));
 
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 a1e1d15..3fcaa38 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -62,6 +62,7 @@
 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.EolStreamType;
 import org.eclipse.jgit.lib.CoreConfig.SymLinks;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.NullProgressMonitor;
@@ -84,16 +85,43 @@
 import org.eclipse.jgit.util.FileUtils;
 import org.eclipse.jgit.util.RawParseUtils;
 import org.eclipse.jgit.util.SystemReader;
-import org.eclipse.jgit.util.io.AutoCRLFOutputStream;
+import org.eclipse.jgit.util.io.EolStreamTypeUtil;
 
 /**
  * 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;
+
+	/**
+	 * Metadata used in checkout process
+	 *
+	 * @since 4.3
+	 */
+	public static class CheckoutMetadata {
+		/** git attributes */
+		public final EolStreamType eolStreamType;
+
+		/** filter command to apply */
+		public final String smudgeFilterCommand;
+
+		/**
+		 * @param eolStreamType
+		 * @param smudgeFilterCommand
+		 */
+		public CheckoutMetadata(EolStreamType eolStreamType,
+				String smudgeFilterCommand) {
+			this.eolStreamType = eolStreamType;
+			this.smudgeFilterCommand = smudgeFilterCommand;
+		}
+
+		static CheckoutMetadata EMPTY = new CheckoutMetadata(
+				EolStreamType.DIRECT, null);
+	}
+
 	private Repository repo;
 
-	private HashMap<String, String> updated = new HashMap<String, String>();
+	private HashMap<String, CheckoutMetadata> updated = new HashMap<String, CheckoutMetadata>();
 
 	private ArrayList<String> conflicts = new ArrayList<String>();
 
@@ -120,7 +148,7 @@
 	/**
 	 * @return a list of updated paths and smudgeFilterCommands
 	 */
-	public Map<String, String> getUpdated() {
+	public Map<String, CheckoutMetadata> getUpdated() {
 		return updated;
 	}
 
@@ -450,11 +478,12 @@
 			if (file != null)
 				removeEmptyParents(file);
 
-			for (String path : updated.keySet()) {
+			for (Map.Entry<String, CheckoutMetadata> e : updated.entrySet()) {
+				String path = e.getKey();
+				CheckoutMetadata meta = e.getValue();
 				DirCacheEntry entry = dc.getEntry(path);
 				if (!FileMode.GITLINK.equals(entry.getRawMode()))
-					checkoutEntry(repo, entry, objectReader, false,
-							updated.get(path));
+					checkoutEntry(repo, entry, objectReader, false, meta);
 			}
 
 			// commit the index builder - a new index is persisted
@@ -1006,8 +1035,8 @@
 	private void update(String path, ObjectId mId, FileMode mode)
 			throws IOException {
 		if (!FileMode.TREE.equals(mode)) {
-			updated.put(path,
-					walk.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE));
+			updated.put(path, new CheckoutMetadata(walk.getEolStreamType(),
+					walk.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE)));
 
 			DirCacheEntry entry = new DirCacheEntry(path, DirCacheEntry.STAGE_0);
 			entry.setObjectId(mId);
@@ -1190,52 +1219,22 @@
 	 * @param deleteRecursive
 	 *            true to recursively delete final path if it exists on the file
 	 *            system
-	 *
-	 * @throws IOException
-	 * @since 4.2
-	 */
-	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
+	 * @param checkoutMetadata
+	 *            containing
+	 *            <ul>
+	 *            <li>smudgeFilterCommand to be run for smudging the entry to be
+	 *            checked out</li>
+	 *            <li>eolStreamType used for stream conversion</li>
+	 *            </ul>
 	 *
 	 * @throws IOException
 	 * @since 4.2
 	 */
 	public static void checkoutEntry(Repository repo, DirCacheEntry entry,
 			ObjectReader or, boolean deleteRecursive,
-			String smudgeFilterCommand) throws IOException {
+			CheckoutMetadata checkoutMetadata) throws IOException {
+		if (checkoutMetadata == null)
+			checkoutMetadata = CheckoutMetadata.EMPTY;
 		ObjectLoader ol = or.open(entry.getObjectId());
 		File f = new File(repo.getWorkTree(), entry.getPathString());
 		File parentDir = f.getParentFile();
@@ -1257,12 +1256,19 @@
 
 		File tmpFile = File.createTempFile(
 				"._" + f.getName(), null, parentDir); //$NON-NLS-1$
-		OutputStream channel = new FileOutputStream(tmpFile);
-		if (opt.getAutoCRLF() == AutoCRLF.TRUE)
-			channel = new AutoCRLFOutputStream(channel);
-		if (smudgeFilterCommand != null) {
-			ProcessBuilder filterProcessBuilder = fs
-					.runInShell(smudgeFilterCommand, new String[0]);
+		EolStreamType nonNullEolStreamType;
+		if (checkoutMetadata.eolStreamType != null) {
+			nonNullEolStreamType = checkoutMetadata.eolStreamType;
+		} else if (opt.getAutoCRLF() == AutoCRLF.TRUE) {
+			nonNullEolStreamType = EolStreamType.AUTO_CRLF;
+		} else {
+			nonNullEolStreamType = EolStreamType.DIRECT;
+		}
+		OutputStream channel = EolStreamTypeUtil.wrapOutputStream(
+				new FileOutputStream(tmpFile), nonNullEolStreamType);
+		if (checkoutMetadata.smudgeFilterCommand != null) {
+			ProcessBuilder filterProcessBuilder = fs.runInShell(
+					checkoutMetadata.smudgeFilterCommand, new String[0]);
 			filterProcessBuilder.directory(repo.getWorkTree());
 			filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY,
 					repo.getDirectory().getAbsolutePath());
@@ -1278,14 +1284,16 @@
 				}
 			} catch (IOException | InterruptedException e) {
 				throw new IOException(new FilterFailedException(e,
-						smudgeFilterCommand, entry.getPathString()));
+						checkoutMetadata.smudgeFilterCommand,
+						entry.getPathString()));
 
 			} finally {
 				channel.close();
 			}
 			if (rc != 0) {
 				throw new IOException(new FilterFailedException(rc,
-						smudgeFilterCommand, entry.getPathString(),
+						checkoutMetadata.smudgeFilterCommand,
+						entry.getPathString(),
 						result.getStdout().toByteArray(MAX_EXCEPTION_TEXT_SIZE),
 						RawParseUtils.decode(result.getStderr()
 								.toByteArray(MAX_EXCEPTION_TEXT_SIZE))));
@@ -1301,10 +1309,11 @@
 		// 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 {
+		if (checkoutMetadata.eolStreamType == EolStreamType.DIRECT
+				&& checkoutMetadata.smudgeFilterCommand == null) {
 			entry.setLength(ol.getSize());
+		} else {
+			entry.setLength(tmpFile.length());
 		}
 
 		if (opt.isFileMode() && fs.supportsExecute()) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java
index 0cbb83d..8bcf4bf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java
@@ -249,7 +249,15 @@
 		return children[i];
 	}
 
-	ObjectId getObjectId() {
+	/**
+	 * Get the tree's ObjectId.
+	 * <p>
+	 * If {@link #isValid()} returns false this method will return null.
+	 *
+	 * @return ObjectId of this tree or null.
+	 * @since 4.3
+	 */
+	public ObjectId getObjectId() {
 		return id;
 	}
 
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 ff9f233..ee937f5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
@@ -54,6 +54,7 @@
 import java.util.List;
 import java.util.Map;
 
+import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.GitCommand;
 import org.eclipse.jgit.api.SubmoduleAddCommand;
@@ -103,7 +104,6 @@
  * @since 3.4
  */
 public class RepoCommand extends GitCommand<RevCommit> {
-
 	private String path;
 	private String uri;
 	private String groups;
@@ -114,6 +114,7 @@
 	private RemoteReader callback;
 	private InputStream inputStream;
 	private IncludedFileReader includedReader;
+	private boolean ignoreRemoteFailures = false;
 
 	private List<RepoProject> bareProjects;
 	private Git git;
@@ -137,9 +138,11 @@
 		 *            The URI of the remote repository
 		 * @param ref
 		 *            The ref (branch/tag/etc.) to read
-		 * @return the sha1 of the remote repository
+		 * @return the sha1 of the remote repository, or null if the ref does
+		 *         not exist.
 		 * @throws GitAPIException
 		 */
+		@Nullable
 		public ObjectId sha1(String uri, String ref) throws GitAPIException;
 
 		/**
@@ -318,7 +321,7 @@
 	}
 
 	/**
-	 * Set whether the branch name should be recorded in .gitmodules
+	 * Set whether the branch name should be recorded in .gitmodules.
 	 * <p>
 	 * Submodule entries in .gitmodules can include a "branch" field
 	 * to indicate what remote branch each submodule tracks.
@@ -355,6 +358,26 @@
 	}
 
 	/**
+	 * Set whether to skip projects whose commits don't exist remotely.
+	 * <p>
+	 * When set to true, we'll just skip the manifest entry and continue
+	 * on to the next one.
+	 * <p>
+	 * When set to false (default), we'll throw an error when remote
+	 * failures occur.
+	 * <p>
+	 * Not implemented for non-bare repositories.
+	 *
+	 * @param ignore Whether to ignore the remote failures.
+	 * @return this command
+	 * @since 4.3
+	 */
+	public RepoCommand setIgnoreRemoteFailures(boolean ignore) {
+		this.ignoreRemoteFailures = ignore;
+		return this;
+	}
+
+	/**
 	 * Set the author/committer for the bare repository commit.
 	 * <p>
 	 * For non-bare repositories, the current user will be used and this will be
@@ -452,22 +475,29 @@
 				for (RepoProject proj : bareProjects) {
 					String name = proj.getPath();
 					String nameUri = proj.getName();
-					cfg.setString("submodule", name, "path", name); //$NON-NLS-1$ //$NON-NLS-2$
-					cfg.setString("submodule", name, "url", nameUri); //$NON-NLS-1$ //$NON-NLS-2$
-					// create gitlink
-					DirCacheEntry dcEntry = new DirCacheEntry(name);
 					ObjectId objectId;
-					if (ObjectId.isId(proj.getRevision())) {
+					if (ObjectId.isId(proj.getRevision())
+							&& !ignoreRemoteFailures) {
 						objectId = ObjectId.fromString(proj.getRevision());
 					} else {
 						objectId = callback.sha1(nameUri, proj.getRevision());
-						if (recordRemoteBranch)
+						if (objectId == null) {
+							if (ignoreRemoteFailures) {
+								continue;
+							}
+							throw new RemoteUnavailableException(nameUri);
+						}
+						if (recordRemoteBranch) {
 							// can be branch or tag
 							cfg.setString("submodule", name, "branch", //$NON-NLS-1$ //$NON-NLS-2$
 									proj.getRevision());
+						}
 					}
-					if (objectId == null)
-						throw new RemoteUnavailableException(nameUri);
+					cfg.setString("submodule", name, "path", name); //$NON-NLS-1$ //$NON-NLS-2$
+					cfg.setString("submodule", name, "url", nameUri); //$NON-NLS-1$ //$NON-NLS-2$
+
+					// create gitlink
+					DirCacheEntry dcEntry = new DirCacheEntry(name);
 					dcEntry.setObjectId(objectId);
 					dcEntry.setFileMode(FileMode.GITLINK);
 					builder.add(dcEntry);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
index a89bcee..054d193 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -107,6 +107,13 @@
 	/** The "autocrlf" key */
 	public static final String CONFIG_KEY_AUTOCRLF = "autocrlf";
 
+	/**
+	 * The "eol" key
+	 *
+	 * @since 4.3
+	 */
+	public static final String CONFIG_KEY_EOL = "eol";
+
 	/** The "bare" key */
 	public static final String CONFIG_KEY_BARE = "bare";
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
index 5a7634a..83efd43 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
@@ -76,6 +76,46 @@
 	}
 
 	/**
+	 * Permissible values for {@code core.eol}.
+	 * <p>
+	 * https://git-scm.com/docs/gitattributes
+	 *
+	 * @since 4.3
+	 */
+	public static enum EOL {
+		/** checkin with LF, checkout with CRLF. */
+		CRLF,
+
+		/** checkin with LF, checkout without conversion. */
+		LF,
+
+		/** use the platform's native line ending. */
+		NATIVE;
+	}
+
+	/**
+	 * EOL stream conversion protocol
+	 *
+	 * @since 4.3
+	 */
+	public static enum EolStreamType {
+		/** convert to CRLF without binary detection */
+		TEXT_CRLF,
+
+		/** convert to LF without binary detection */
+		TEXT_LF,
+
+		/** convert to CRLF with binary detection */
+		AUTO_CRLF,
+
+		/** convert to LF with binary detection */
+		AUTO_LF,
+
+		/** do not convert */
+		DIRECT;
+	}
+
+	/**
 	 * Permissible values for {@code core.checkstat}
 	 *
 	 * @since 3.0
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 3ee2feb..3c5c8da 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
@@ -376,8 +376,10 @@
 	public URIish(final URL u) {
 		scheme = u.getProtocol();
 		path = u.getPath();
+		path = cleanLeadingSlashes(path, scheme);
 		try {
 			rawPath = u.toURI().getRawPath();
+			rawPath = cleanLeadingSlashes(rawPath, scheme);
 		} catch (URISyntaxException e) {
 			throw new RuntimeException(e); // Impossible
 		}
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 accf495..eb4f1a8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
@@ -79,14 +79,39 @@
 	protected final FS fs;
 
 	/**
+	 * the strategy used to compute the FileMode for a FileEntry. Can be used to
+	 * control things such as whether to recurse into a directory or create a
+	 * gitlink.
+	 *
+	 * @since 4.3
+	 */
+	protected final FileModeStrategy fileModeStrategy;
+
+	/**
 	 * Create a new iterator to traverse the work tree and its children.
 	 *
 	 * @param repo
 	 *            the repository whose working tree will be scanned.
 	 */
 	public FileTreeIterator(Repository repo) {
+		this(repo, DefaultFileModeStrategy.INSTANCE);
+	}
+
+	/**
+	 * Create a new iterator to traverse the work tree and its children.
+	 *
+	 * @param repo
+	 *            the repository whose working tree will be scanned.
+	 * @param fileModeStrategy
+	 *            the strategy to use to determine the FileMode for a FileEntry;
+	 *            controls gitlinks etc.
+	 *
+	 * @since 4.3
+	 */
+	public FileTreeIterator(Repository repo, FileModeStrategy fileModeStrategy) {
 		this(repo.getWorkTree(), repo.getFS(),
-				repo.getConfig().get(WorkingTreeOptions.KEY));
+				repo.getConfig().get(WorkingTreeOptions.KEY),
+				fileModeStrategy);
 		initRootIterator(repo);
 	}
 
@@ -103,9 +128,32 @@
 	 *            working tree options to be used
 	 */
 	public FileTreeIterator(final File root, FS fs, WorkingTreeOptions options) {
+		this(root, fs, options, DefaultFileModeStrategy.INSTANCE);
+	}
+
+	/**
+	 * Create a new iterator to traverse the given directory and its children.
+	 *
+	 * @param root
+	 *            the starting directory. This directory should correspond to
+	 *            the root of the repository.
+	 * @param fs
+	 *            the file system abstraction which will be necessary to perform
+	 *            certain file system operations.
+	 * @param options
+	 *            working tree options to be used
+	 * @param fileModeStrategy
+	 *            the strategy to use to determine the FileMode for a FileEntry;
+	 *            controls gitlinks etc.
+	 *
+	 * @since 4.3
+	 */
+	public FileTreeIterator(final File root, FS fs, WorkingTreeOptions options,
+							FileModeStrategy fileModeStrategy) {
 		super(options);
 		directory = root;
 		this.fs = fs;
+		this.fileModeStrategy = fileModeStrategy;
 		init(entries());
 	}
 
@@ -114,25 +162,72 @@
 	 *
 	 * @param p
 	 *            the parent iterator we were created from.
-	 * @param fs
-	 *            the file system abstraction which will be necessary to perform
-	 *            certain file system operations.
 	 * @param root
 	 *            the subdirectory. This should be a directory contained within
 	 *            the parent directory.
+	 * @param fs
+	 *            the file system abstraction which will be necessary to perform
+	 *            certain file system operations.
+	 * @since 4.3
+	 * @deprecated use {@link #FileTreeIterator(FileTreeIterator, File, FS)}
+	 *             instead.
 	 */
+	@Deprecated
 	protected FileTreeIterator(final WorkingTreeIterator p, final File root,
 			FS fs) {
+		this(p, root, fs, DefaultFileModeStrategy.INSTANCE);
+	}
+
+	/**
+	 * Create a new iterator to traverse a subdirectory.
+	 *
+	 * @param p
+	 *            the parent iterator we were created from.
+	 * @param root
+	 *            the subdirectory. This should be a directory contained within
+	 *            the parent directory.
+	 * @param fs
+	 *            the file system abstraction which will be necessary to perform
+	 *            certain file system operations.
+	 *
+	 * @since 4.3
+	 */
+	protected FileTreeIterator(final FileTreeIterator p, final File root,
+			FS fs) {
+		this(p, root, fs, p.fileModeStrategy);
+	}
+
+	/**
+	 * Create a new iterator to traverse a subdirectory, given the specified
+	 * FileModeStrategy.
+	 *
+	 * @param p
+	 *            the parent iterator we were created from.
+	 * @param root
+	 *            the subdirectory. This should be a directory contained within
+	 *            the parent directory
+	 * @param fs
+	 *            the file system abstraction which will be necessary to perform
+	 *            certain file system operations.
+	 * @param fileModeStrategy
+	 *            the strategy to use to determine the FileMode for a given
+	 *            FileEntry.
+	 *
+	 * @since 4.3
+	 */
+	protected FileTreeIterator(final WorkingTreeIterator p, final File root,
+			FS fs, FileModeStrategy fileModeStrategy) {
 		super(p);
 		directory = root;
 		this.fs = fs;
+		this.fileModeStrategy = fileModeStrategy;
 		init(entries());
 	}
 
 	@Override
 	public AbstractTreeIterator createSubtreeIterator(final ObjectReader reader)
 			throws IncorrectObjectTypeException, IOException {
-		return new FileTreeIterator(this, ((FileEntry) current()).getFile(), fs);
+		return new FileTreeIterator(this, ((FileEntry) current()).getFile(), fs, fileModeStrategy);
 	}
 
 	private Entry[] entries() {
@@ -141,11 +236,63 @@
 			return EOF;
 		final Entry[] r = new Entry[all.length];
 		for (int i = 0; i < r.length; i++)
-			r[i] = new FileEntry(all[i], fs);
+			r[i] = new FileEntry(all[i], fs, fileModeStrategy);
 		return r;
 	}
 
 	/**
+	 * An interface representing the methods used to determine the FileMode for
+	 * a FileEntry.
+	 *
+	 * @since 4.3
+	 */
+	public interface FileModeStrategy {
+		/**
+		 * Compute the FileMode for a given File, based on its attributes.
+		 *
+		 * @param f
+		 *            the file to return a FileMode for
+		 * @param attributes
+		 *            the attributes of a file
+		 * @return a FileMode indicating whether the file is a regular file, a
+		 *         directory, a gitlink, etc.
+		 */
+		FileMode getMode(File f, FS.Attributes attributes);
+	}
+
+	/**
+	 * A default implementation of a FileModeStrategy; defaults to treating
+	 * nested .git directories as gitlinks, etc.
+	 *
+	 * @since 4.3
+	 */
+	static public class DefaultFileModeStrategy implements FileModeStrategy {
+		/**
+		 * a singleton instance of the default FileModeStrategy
+		 */
+		public final static DefaultFileModeStrategy INSTANCE =
+				new DefaultFileModeStrategy();
+
+		@Override
+		public FileMode getMode(File f, FS.Attributes attributes) {
+			if (attributes.isSymbolicLink()) {
+				return FileMode.SYMLINK;
+			} else if (attributes.isDirectory()) {
+				if (new File(f, Constants.DOT_GIT).exists()) {
+					return FileMode.GITLINK;
+				} else {
+					return FileMode.TREE;
+				}
+			} else if (attributes.isExecutable()) {
+				return FileMode.EXECUTABLE_FILE;
+			} else {
+				return FileMode.REGULAR_FILE;
+			}
+		}
+	}
+
+
+	/**
 	 * Wrapper for a standard Java IO file
 	 */
 	static public class FileEntry extends Entry {
@@ -164,20 +311,27 @@
 		 *            file system
 		 */
 		public FileEntry(File f, FS fs) {
+			this(f, fs, DefaultFileModeStrategy.INSTANCE);
+		}
+
+		/**
+		 * Create a new file entry given the specified FileModeStrategy
+		 *
+		 * @param f
+		 *            file
+		 * @param fs
+		 *            file system
+		 * @param fileModeStrategy
+		 *            the strategy to use when determining the FileMode of a
+		 *            file; controls gitlinks etc.
+		 *
+		 * @since 4.3
+		 */
+		public FileEntry(File f, FS fs, FileModeStrategy fileModeStrategy) {
 			this.fs = fs;
 			f = fs.normalize(f);
 			attributes = fs.getAttributes(f);
-			if (attributes.isSymbolicLink())
-				mode = FileMode.SYMLINK;
-			else if (attributes.isDirectory()) {
-				if (new File(f, Constants.DOT_GIT).exists())
-					mode = FileMode.GITLINK;
-				else
-					mode = FileMode.TREE;
-			} else if (attributes.isExecutable())
-				mode = FileMode.EXECUTABLE_FILE;
-			else
-				mode = FileMode.REGULAR_FILE;
+			mode = fileModeStrategy.getMode(f, attributes);
 		}
 
 		@Override
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 d2195a8..b9293eb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
@@ -104,6 +104,19 @@
 	/**
 	 * Create a new tree walker for a given repository.
 	 *
+	 * @param repo
+	 *            the repository the walker will obtain data from.
+	 * @param or
+	 *            the reader the walker will obtain tree data from.
+	 * @since 4.3
+	 */
+	public NameConflictTreeWalk(Repository repo, final ObjectReader or) {
+		super(repo, or);
+	}
+
+	/**
+	 * Create a new tree walker for a given repository.
+	 *
 	 * @param or
 	 *            the reader the walker will obtain tree data from.
 	 */
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 4775e96..aecbac1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
@@ -49,6 +49,7 @@
 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.Attributes;
@@ -64,6 +65,7 @@
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.MutableObjectId;
 import org.eclipse.jgit.lib.ObjectId;
@@ -74,6 +76,7 @@
 import org.eclipse.jgit.treewalk.filter.TreeFilter;
 import org.eclipse.jgit.util.QuotedString;
 import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.io.EolStreamTypeUtil;
 
 /**
  * Walks one or more {@link AbstractTreeIterator}s in parallel.
@@ -161,7 +164,44 @@
 	public static TreeWalk forPath(final ObjectReader reader, final String path,
 			final AnyObjectId... trees) throws MissingObjectException,
 			IncorrectObjectTypeException, CorruptObjectException, IOException {
-		TreeWalk tw = new TreeWalk(reader);
+		return forPath(null, reader, path, trees);
+	}
+
+	/**
+	 * Open a tree walk and filter to exactly one path.
+	 * <p>
+	 * The returned tree walk is already positioned on the requested path, so
+	 * the caller should not need to invoke {@link #next()} unless they are
+	 * looking for a possible directory/file name conflict.
+	 *
+	 * @param repo
+	 *            repository to read config data and
+	 *            {@link AttributesNodeProvider} from.
+	 * @param reader
+	 *            the reader the walker will obtain tree data from.
+	 * @param path
+	 *            single path to advance the tree walk instance into.
+	 * @param trees
+	 *            one or more trees to walk through, all with the same root.
+	 * @return a new tree walk configured for exactly this one path; null if no
+	 *         path was found in any of the trees.
+	 * @throws IOException
+	 *             reading a pack file or loose object failed.
+	 * @throws CorruptObjectException
+	 *             an tree object could not be read as its data stream did not
+	 *             appear to be a tree, or could not be inflated.
+	 * @throws IncorrectObjectTypeException
+	 *             an object we expected to be a tree was not a tree.
+	 * @throws MissingObjectException
+	 *             a tree object was not found.
+	 * @since 4.3
+	 */
+	public static TreeWalk forPath(final @Nullable Repository repo,
+			final ObjectReader reader, final String path,
+			final AnyObjectId... trees)
+					throws MissingObjectException, IncorrectObjectTypeException,
+					CorruptObjectException, IOException {
+		TreeWalk tw = new TreeWalk(repo, reader);
 		PathFilter f = PathFilter.create(path);
 		tw.setFilter(f);
 		tw.reset(trees);
@@ -206,7 +246,7 @@
 			final AnyObjectId... trees) throws MissingObjectException,
 			IncorrectObjectTypeException, CorruptObjectException, IOException {
 		try (ObjectReader reader = db.newObjectReader()) {
-			return forPath(reader, path, trees);
+			return forPath(db, reader, path, trees);
 		}
 	}
 
@@ -282,9 +322,23 @@
 	 *            when the walker is closed.
 	 */
 	public TreeWalk(final Repository repo) {
-		this(repo.newObjectReader(), true);
-		config = repo.getConfig();
-		attributesNodeProvider = repo.createAttributesNodeProvider();
+		this(repo, repo.newObjectReader(), true);
+	}
+
+	/**
+	 * Create a new tree walker for a given repository.
+	 *
+	 * @param repo
+	 *            the repository the walker will obtain data from. An
+	 *            ObjectReader will be created by the walker, and will be closed
+	 *            when the walker is closed.
+	 * @param or
+	 *            the reader the walker will obtain tree data from. The reader
+	 *            is not closed when the walker is closed.
+	 * @since 4.3
+	 */
+	public TreeWalk(final @Nullable Repository repo, final ObjectReader or) {
+		this(repo, or, false);
 	}
 
 	/**
@@ -295,10 +349,18 @@
 	 *            is not closed when the walker is closed.
 	 */
 	public TreeWalk(final ObjectReader or) {
-		this(or, false);
+		this(null, or, false);
 	}
 
-	private TreeWalk(final ObjectReader or, final boolean closeReader) {
+	private TreeWalk(final @Nullable Repository repo, final ObjectReader or,
+			final boolean closeReader) {
+		if (repo != null) {
+			config = repo.getConfig();
+			attributesNodeProvider = repo.createAttributesNodeProvider();
+		} else {
+			config = null;
+			attributesNodeProvider = null;
+		}
 		reader = or;
 		filter = TreeFilter.ALL;
 		trees = NO_TREES;
@@ -517,6 +579,19 @@
 		}
 	}
 
+	/**
+	 * @return the EOL stream type of the current entry using the config and
+	 *         {@link #getAttributes()} Note that this method may return null if
+	 *         the {@link TreeWalk} is not based on a working tree
+	 * @since 4.3
+	 */
+	public @Nullable EolStreamType getEolStreamType() {
+			if (attributesNodeProvider == null || config == null)
+				return null;
+			return EolStreamTypeUtil.detectStreamType(operationType,
+					config.get(WorkingTreeOptions.KEY), getAttributes());
+	}
+
 	/** Reset this walker so new tree iterators can be added to it. */
 	public void reset() {
 		attrs = null;
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 0d617ee..ca8f9aa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -77,8 +77,8 @@
 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.EolStreamType;
 import org.eclipse.jgit.lib.CoreConfig.SymLinks;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
@@ -88,10 +88,12 @@
 import org.eclipse.jgit.submodule.SubmoduleWalk;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FS.ExecutionResult;
+import org.eclipse.jgit.util.Holder;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.Paths;
 import org.eclipse.jgit.util.RawParseUtils;
-import org.eclipse.jgit.util.io.EolCanonicalizingInputStream;
+import org.eclipse.jgit.util.io.AutoLFInputStream;
+import org.eclipse.jgit.util.io.EolStreamTypeUtil;
 
 /**
  * Walks a working directory tree as part of a {@link TreeWalk}.
@@ -140,7 +142,17 @@
 	/** If there is a .gitignore file present, the parsed rules from it. */
 	private IgnoreNode ignoreNode;
 
-	private String cleanFilterCommand;
+	/**
+	 * cached clean filter command. Use a Ref in order to distinguish between
+	 * the ref not cached yet and the value null
+	 */
+	private Holder<String> cleanFilterCommandHolder;
+
+	/**
+	 * cached eol stream type. Use a Ref in order to distinguish between the ref
+	 * not cached yet and the value null
+	 */
+	private Holder<EolStreamType> eolStreamTypeHolder;
 
 	/** Repository that is the root level being iterated over */
 	protected Repository repository;
@@ -357,8 +369,8 @@
 
 	private InputStream possiblyFilteredInputStream(final Entry e,
 			final InputStream is, final long len) throws IOException {
-		boolean mightNeedCleaning = mightNeedCleaning();
-		if (!mightNeedCleaning) {
+		if (getCleanFilterCommand() == null
+				&& getEolStreamType() == EolStreamType.DIRECT) {
 			canonLen = len;
 			return is;
 		}
@@ -376,11 +388,10 @@
 			return new ByteArrayInputStream(raw, 0, n);
 		}
 
-		// TODO: fix autocrlf causing mightneedcleaning
-		if (!mightNeedCleaning && isBinary(e)) {
-			canonLen = len;
-			return is;
-		}
+		if (getCleanFilterCommand() == null && isBinary(e)) {
+				canonLen = len;
+				return is;
+			}
 
 		final InputStream lenIs = filterClean(e.openInputStream());
 		try {
@@ -401,20 +412,6 @@
 		}
 	}
 
-	private boolean mightNeedCleaning() throws IOException {
-		switch (getOptions().getAutoCRLF()) {
-		case FALSE:
-		default:
-			if (getCleanFilterCommand() != null)
-				return true;
-			return false;
-
-		case TRUE:
-		case INPUT:
-			return true;
-		}
-	}
-
 	private static boolean isBinary(byte[] content, int sz) {
 		return RawText.isBinary(content, sz);
 	}
@@ -467,12 +464,8 @@
 		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;
+	private InputStream handleAutoCRLF(InputStream in) throws IOException {
+		return EolStreamTypeUtil.wrapInputStream(in, getEolStreamType());
 	}
 
 	/**
@@ -531,7 +524,8 @@
 		System.arraycopy(e.encodedName, 0, path, pathOffset, nameLen);
 		pathLen = pathOffset + nameLen;
 		canonLen = -1;
-		cleanFilterCommand = null;
+		cleanFilterCommandHolder = null;
+		eolStreamTypeHolder = null;
 	}
 
 	/**
@@ -594,10 +588,11 @@
 	 */
 	public InputStream openEntryStream() throws IOException {
 		InputStream rawis = current().openInputStream();
-		if (mightNeedCleaning())
-			return filterClean(rawis);
-		else
+		if (getCleanFilterCommand() == null
+				&& getEolStreamType() == EolStreamType.DIRECT)
 			return rawis;
+		else
+			return filterClean(rawis);
 	}
 
 	/**
@@ -971,10 +966,11 @@
 			// Content differs: that's a real change, perhaps
 			if (reader == null) // deprecated use, do no further checks
 				return true;
-			switch (getOptions().getAutoCRLF()) {
-			case INPUT:
-			case TRUE:
-				InputStream dcIn = null;
+
+			switch (getEolStreamType()) {
+			case DIRECT:
+				return true;
+			default:
 				try {
 					ObjectLoader loader = reader.open(entry.getObjectId());
 					if (loader == null)
@@ -982,37 +978,26 @@
 
 					// We need to compute the length, but only if it is not
 					// a binary stream.
-					dcIn = new EolCanonicalizingInputStream(
-							loader.openStream(), true, true /* abort if binary */);
 					long dcInLen;
-					try {
+					try (InputStream dcIn = new AutoLFInputStream(
+							loader.openStream(), true,
+							true /* abort if binary */)) {
 						dcInLen = computeLength(dcIn);
-					} catch (EolCanonicalizingInputStream.IsBinaryException e) {
+					} catch (AutoLFInputStream.IsBinaryException e) {
 						return true;
-					} finally {
-						dcIn.close();
 					}
 
-					dcIn = new EolCanonicalizingInputStream(
-							loader.openStream(), true);
-					byte[] autoCrLfHash = computeHash(dcIn, dcInLen);
-					boolean changed = getEntryObjectId().compareTo(
-							autoCrLfHash, 0) != 0;
-					return changed;
+					try (InputStream dcIn = new AutoLFInputStream(
+							loader.openStream(), true)) {
+						byte[] autoCrLfHash = computeHash(dcIn, dcInLen);
+						boolean changed = getEntryObjectId()
+								.compareTo(autoCrLfHash, 0) != 0;
+						return changed;
+					}
 				} catch (IOException e) {
 					return true;
-				} finally {
-					if (dcIn != null)
-						try {
-							dcIn.close();
-						} catch (IOException e) {
-							// empty
-						}
 				}
-			case FALSE:
-				break;
 			}
-			return true;
 		}
 	}
 
@@ -1308,10 +1293,43 @@
 	 * @since 4.2
 	 */
 	public String getCleanFilterCommand() throws IOException {
-		if (cleanFilterCommand == null && state.walk != null) {
-			cleanFilterCommand = state.walk
-					.getFilterCommand(Constants.ATTR_FILTER_TYPE_CLEAN);
+		if (cleanFilterCommandHolder == null) {
+			String cmd = null;
+			if (state.walk != null) {
+				cmd = state.walk
+						.getFilterCommand(Constants.ATTR_FILTER_TYPE_CLEAN);
+			}
+			cleanFilterCommandHolder = new Holder<String>(cmd);
 		}
-		return cleanFilterCommand;
+		return cleanFilterCommandHolder.get();
+	}
+
+	/**
+	 * @return the eol stream type for the current entry or <code>null</code> if
+	 *         it cannot be determined. When state or state.walk is null or the
+	 *         {@link TreeWalk} is not based on a {@link Repository} then null
+	 *         is returned.
+	 * @throws IOException
+	 * @since 4.3
+	 */
+	public EolStreamType getEolStreamType() throws IOException {
+		if (eolStreamTypeHolder == null) {
+			EolStreamType type=null;
+			if (state.walk != null) {
+				type=state.walk.getEolStreamType();
+			} else {
+				switch (getOptions().getAutoCRLF()) {
+				case FALSE:
+					type = EolStreamType.DIRECT;
+					break;
+				case TRUE:
+				case INPUT:
+					type = EolStreamType.AUTO_LF;
+					break;
+				}
+			}
+			eolStreamTypeHolder = new Holder<EolStreamType>(type);
+		}
+		return eolStreamTypeHolder.get();
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java
index a6dccce..a8990b1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java
@@ -48,6 +48,7 @@
 import org.eclipse.jgit.lib.Config.SectionParser;
 import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
 import org.eclipse.jgit.lib.CoreConfig.CheckStat;
+import org.eclipse.jgit.lib.CoreConfig.EOL;
 import org.eclipse.jgit.lib.CoreConfig.HideDotFiles;
 import org.eclipse.jgit.lib.CoreConfig.SymLinks;
 
@@ -64,6 +65,8 @@
 
 	private final AutoCRLF autoCRLF;
 
+	private final EOL eol;
+
 	private final CheckStat checkStat;
 
 	private final SymLinks symlinks;
@@ -75,6 +78,8 @@
 				ConfigConstants.CONFIG_KEY_FILEMODE, true);
 		autoCRLF = rc.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
 				ConfigConstants.CONFIG_KEY_AUTOCRLF, AutoCRLF.FALSE);
+		eol = rc.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_EOL, EOL.NATIVE);
 		checkStat = rc.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
 				ConfigConstants.CONFIG_KEY_CHECKSTAT, CheckStat.DEFAULT);
 		symlinks = rc.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
@@ -95,6 +100,15 @@
 	}
 
 	/**
+	 * @return how text line endings should be normalized.
+	 *
+	 * @since 4.3
+	 */
+	public EOL getEOL() {
+		return eol;
+	}
+
+	/**
 	 * @return how stat data is compared
 	 * @since 3.0
 	 */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Holder.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Holder.java
new file mode 100644
index 0000000..3563e1b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Holder.java
@@ -0,0 +1,77 @@
+/*
+ * 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.util;
+
+/**
+ * Holder of an object.
+ *
+ * @param <T>
+ *            the type of value held by this {@link Holder}
+ *
+ * @since 4.3
+ */
+public class Holder<T> {
+	private T value;
+
+	/**
+	 * @param value
+	 *            is the initial value that is {@link #set(Object)}
+	 */
+	public Holder(T value) {
+		set(value);
+	}
+
+	/**
+	 * @return the value held by this {@link Holder}
+	 */
+	public T get() {
+		return value;
+	}
+
+	/**
+	 * @param value
+	 *            to be set as new value held by this {@link Holder}
+	 */
+	public void set(T value) {
+		this.value = value;
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java
index 98c5477..30f9ce9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java
@@ -50,7 +50,7 @@
 import org.eclipse.jgit.diff.RawText;
 
 /**
- * An OutputStream that expands LF to CRLF.
+ * An InputStream that expands LF to CRLF.
  *
  * Existing CRLF are not expanded to CRCRLF, but retained as is.
  *
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java
index f05da1c..3a72f7e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java
@@ -50,8 +50,11 @@
 
 /**
  * An OutputStream that expands LF to CRLF.
- * <p>
+ *
  * Existing CRLF are not expanded to CRCRLF, but retained as is.
+ *
+ * A binary check on the first 8000 bytes is performed and in case of binary
+ * files, canonicalization is turned off (for the complete file).
  */
 public class AutoCRLFOutputStream extends OutputStream {
 
@@ -67,13 +70,26 @@
 
 	private int binbufcnt = 0;
 
+	private boolean detectBinary;
+
 	private boolean isBinary;
 
 	/**
 	 * @param out
 	 */
 	public AutoCRLFOutputStream(OutputStream out) {
+		this(out, true);
+	}
+
+	/**
+	 * @param out
+	 * @param detectBinary
+	 *            whether binaries should be detected
+	 * @since 4.3
+	 */
+	public AutoCRLFOutputStream(OutputStream out, boolean detectBinary) {
 		this.out = out;
+		this.detectBinary = detectBinary;
 	}
 
 	@Override
@@ -141,7 +157,10 @@
 	}
 
 	private void decideMode() throws IOException {
-		isBinary = RawText.isBinary(binbuf, binbufcnt);
+		if (detectBinary) {
+			isBinary = RawText.isBinary(binbuf, binbufcnt);
+			detectBinary = false;
+		}
 		int cachedLen = binbufcnt;
 		binbufcnt = binbuf.length + 1; // full!
 		write(binbuf, 0, cachedLen);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java
new file mode 100644
index 0000000..6e33f99
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2010, 2013 Marc Strapetz <marc.strapetz@syntevo.com>
+ * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.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.util.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.jgit.diff.RawText;
+
+/**
+ * An InputStream that normalizes CRLF to LF.
+ *
+ * Existing single CR are not changed to LF, but retained as is.
+ *
+ * Optionally, a binary check on the first 8000 bytes is performed and in case
+ * of binary files, canonicalization is turned off (for the complete file).
+ * <p>
+ * This is the former EolCanonicalizingInputStream with a new name in order to
+ * have same naming for all LF / CRLF streams
+ *
+ * @since 4.3
+ */
+public class AutoLFInputStream extends InputStream {
+	private final byte[] single = new byte[1];
+
+	private final byte[] buf = new byte[8096];
+
+	private final InputStream in;
+
+	private int cnt;
+
+	private int ptr;
+
+	private boolean isBinary;
+
+	private boolean detectBinary;
+
+	private boolean abortIfBinary;
+
+	/**
+	 * A special exception thrown when {@link AutoLFInputStream} is told to
+	 * throw an exception when attempting to read a binary file. The exception
+	 * may be thrown at any stage during reading.
+	 *
+	 * @since 3.3
+	 */
+	public static class IsBinaryException extends IOException {
+		private static final long serialVersionUID = 1L;
+
+		IsBinaryException() {
+			super();
+		}
+	}
+
+	/**
+	 * Creates a new InputStream, wrapping the specified stream
+	 *
+	 * @param in
+	 *            raw input stream
+	 * @param detectBinary
+	 *            whether binaries should be detected
+	 * @since 2.0
+	 */
+	public AutoLFInputStream(InputStream in, boolean detectBinary) {
+		this(in, detectBinary, false);
+	}
+
+	/**
+	 * Creates a new InputStream, wrapping the specified stream
+	 *
+	 * @param in
+	 *            raw input stream
+	 * @param detectBinary
+	 *            whether binaries should be detected
+	 * @param abortIfBinary
+	 *            throw an IOException if the file is binary
+	 * @since 3.3
+	 */
+	public AutoLFInputStream(InputStream in, boolean detectBinary,
+			boolean abortIfBinary) {
+		this.in = in;
+		this.detectBinary = detectBinary;
+		this.abortIfBinary = abortIfBinary;
+	}
+
+	@Override
+	public int read() throws IOException {
+		final int read = read(single, 0, 1);
+		return read == 1 ? single[0] & 0xff : -1;
+	}
+
+	@Override
+	public int read(byte[] bs, final int off, final int len)
+			throws IOException {
+		if (len == 0)
+			return 0;
+
+		if (cnt == -1)
+			return -1;
+
+		int i = off;
+		final int end = off + len;
+
+		while (i < end) {
+			if (ptr == cnt && !fillBuffer()) {
+				break;
+			}
+
+			byte b = buf[ptr++];
+			if (isBinary || b != '\r') {
+				// Logic for binary files ends here
+				bs[i++] = b;
+				continue;
+			}
+
+			if (ptr == cnt && !fillBuffer()) {
+				bs[i++] = '\r';
+				break;
+			}
+
+			if (buf[ptr] == '\n') {
+				bs[i++] = '\n';
+				ptr++;
+			} else
+				bs[i++] = '\r';
+		}
+
+		return i == off ? -1 : i - off;
+	}
+
+	/**
+	 * @return true if the stream has detected as a binary so far
+	 * @since 3.3
+	 */
+	public boolean isBinary() {
+		return isBinary;
+	}
+
+	@Override
+	public void close() throws IOException {
+		in.close();
+	}
+
+	private boolean fillBuffer() throws IOException {
+		cnt = in.read(buf, 0, buf.length);
+		if (cnt < 1)
+			return false;
+		if (detectBinary) {
+			isBinary = RawText.isBinary(buf, cnt);
+			detectBinary = false;
+			if (isBinary && abortIfBinary)
+				throw new IsBinaryException();
+		}
+		ptr = 0;
+		return true;
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java
new file mode 100644
index 0000000..c932b00
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java
@@ -0,0 +1,200 @@
+/*
+ * 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.util.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.eclipse.jgit.diff.RawText;
+
+/**
+ * An OutputStream that reduces CRLF to LF.
+ *
+ * Existing single CR are not changed to LF, but retained as is.
+ *
+ * A binary check on the first 8000 bytes is performed and in case of binary
+ * files, canonicalization is turned off (for the complete file).
+ *
+ * @since 4.3
+ */
+public class AutoLFOutputStream extends OutputStream {
+
+	static final int BUFFER_SIZE = 8000;
+
+	private final OutputStream out;
+
+	private int buf = -1;
+
+	private byte[] binbuf = new byte[BUFFER_SIZE];
+
+	private byte[] onebytebuf = new byte[1];
+
+	private int binbufcnt = 0;
+
+	private boolean detectBinary;
+
+	private boolean isBinary;
+
+	/**
+	 * @param out
+	 */
+	public AutoLFOutputStream(OutputStream out) {
+		this(out, true);
+	}
+
+	/**
+	 * @param out
+	 * @param detectBinary
+	 *            whether binaries should be detected
+	 */
+	public AutoLFOutputStream(OutputStream out, boolean detectBinary) {
+		this.out = out;
+		this.detectBinary = detectBinary;
+	}
+
+	@Override
+	public void write(int b) throws IOException {
+		onebytebuf[0] = (byte) b;
+		write(onebytebuf, 0, 1);
+	}
+
+	@Override
+	public void write(byte[] b) throws IOException {
+		int overflow = buffer(b, 0, b.length);
+		if (overflow > 0) {
+			write(b, b.length - overflow, overflow);
+		}
+	}
+
+	@Override
+	public void write(byte[] b, final int startOff, final int startLen)
+			throws IOException {
+		final int overflow = buffer(b, startOff, startLen);
+		if (overflow < 0) {
+			return;
+		}
+		final int off = startOff + startLen - overflow;
+		final int len = overflow;
+		if (len == 0) {
+			return;
+		}
+		int lastw = off;
+		if (isBinary) {
+			out.write(b, off, len);
+			return;
+		}
+		for (int i = off; i < off + len; ++i) {
+			final byte c = b[i];
+			if (c == '\r') {
+				// skip write r but backlog r
+				if (lastw < i) {
+					out.write(b, lastw, i - lastw);
+				}
+				lastw = i + 1;
+				buf = '\r';
+			} else if (c == '\n') {
+				if (buf == '\r') {
+					out.write('\n');
+					lastw = i + 1;
+					buf = -1;
+				} else {
+					if (lastw < i + 1) {
+						out.write(b, lastw, i + 1 - lastw);
+					}
+					lastw = i + 1;
+				}
+			} else {
+				if (buf == '\r') {
+					out.write('\r');
+					lastw = i;
+				}
+				buf = -1;
+			}
+		}
+		if (lastw < off + len) {
+			out.write(b, lastw, off + len - lastw);
+		}
+	}
+
+	private int buffer(byte[] b, int off, int len) throws IOException {
+		if (binbufcnt > binbuf.length) {
+			return len;
+		}
+		int copy = Math.min(binbuf.length - binbufcnt, len);
+		System.arraycopy(b, off, binbuf, binbufcnt, copy);
+		binbufcnt += copy;
+		int remaining = len - copy;
+		if (remaining > 0) {
+			decideMode();
+		}
+		return remaining;
+	}
+
+	private void decideMode() throws IOException {
+		if (detectBinary) {
+			isBinary = RawText.isBinary(binbuf, binbufcnt);
+			detectBinary = false;
+		}
+		int cachedLen = binbufcnt;
+		binbufcnt = binbuf.length + 1; // full!
+		write(binbuf, 0, cachedLen);
+	}
+
+	@Override
+	public void flush() throws IOException {
+		if (binbufcnt <= binbuf.length) {
+			decideMode();
+		}
+		out.flush();
+	}
+
+	@Override
+	public void close() throws IOException {
+		flush();
+		if (buf == '\r') {
+			out.write(buf);
+			buf = -1;
+		}
+		out.close();
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolCanonicalizingInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolCanonicalizingInputStream.java
index 98485e9..ee729e8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolCanonicalizingInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolCanonicalizingInputStream.java
@@ -46,46 +46,16 @@
 import java.io.IOException;
 import java.io.InputStream;
 
-import org.eclipse.jgit.diff.RawText;
-
 /**
  * An input stream which canonicalizes EOLs bytes on the fly to '\n'.
  *
- * Optionally, a binary check on the first 8000 bytes is performed
- * and in case of binary files, canonicalization is turned off
- * (for the complete file).
+ * Optionally, a binary check on the first 8000 bytes is performed and in case
+ * of binary files, canonicalization is turned off (for the complete file).
+ *
+ * @deprecated use {@link AutoLFInputStream} instead
  */
-public class EolCanonicalizingInputStream extends InputStream {
-	private final byte[] single = new byte[1];
-
-	private final byte[] buf = new byte[8096];
-
-	private final InputStream in;
-
-	private int cnt;
-
-	private int ptr;
-
-	private boolean isBinary;
-
-	private boolean detectBinary;
-
-	private boolean abortIfBinary;
-
-	/**
-	 * A special exception thrown when {@link EolCanonicalizingInputStream} is
-	 * told to throw an exception when attempting to read a binary file. The
-	 * exception may be thrown at any stage during reading.
-	 *
-	 * @since 3.3
-	 */
-	public static class IsBinaryException extends IOException {
-		private static final long serialVersionUID = 1L;
-
-		IsBinaryException() {
-			super();
-		}
-	}
+@Deprecated
+public class EolCanonicalizingInputStream extends AutoLFInputStream {
 
 	/**
 	 * Creates a new InputStream, wrapping the specified stream
@@ -94,10 +64,9 @@
 	 *            raw input stream
 	 * @param detectBinary
 	 *            whether binaries should be detected
-	 * @since 2.0
 	 */
 	public EolCanonicalizingInputStream(InputStream in, boolean detectBinary) {
-		this(in, detectBinary, false);
+		super(in, detectBinary);
 	}
 
 	/**
@@ -109,83 +78,25 @@
 	 *            whether binaries should be detected
 	 * @param abortIfBinary
 	 *            throw an IOException if the file is binary
-	 * @since 3.3
 	 */
 	public EolCanonicalizingInputStream(InputStream in, boolean detectBinary,
 			boolean abortIfBinary) {
-		this.in = in;
-		this.detectBinary = detectBinary;
-		this.abortIfBinary = abortIfBinary;
-	}
-
-	@Override
-	public int read() throws IOException {
-		final int read = read(single, 0, 1);
-		return read == 1 ? single[0] & 0xff : -1;
-	}
-
-	@Override
-	public int read(byte[] bs, final int off, final int len) throws IOException {
-		if (len == 0)
-			return 0;
-
-		if (cnt == -1)
-			return -1;
-
-		int i = off;
-		final int end = off + len;
-
-		while (i < end) {
-			if (ptr == cnt && !fillBuffer()) {
-				break;
-			}
-
-			byte b = buf[ptr++];
-			if (isBinary || b != '\r') {
-				// Logic for binary files ends here
-				bs[i++] = b;
-				continue;
-			}
-
-			if (ptr == cnt && !fillBuffer()) {
-				bs[i++] = '\r';
-				break;
-			}
-
-			if (buf[ptr] == '\n') {
-				bs[i++] = '\n';
-				ptr++;
-			} else
-				bs[i++] = '\r';
-		}
-
-		return i == off ? -1 : i - off;
+		super(in, detectBinary, abortIfBinary);
 	}
 
 	/**
-	 * @return true if the stream has detected as a binary so far
+	 * A special exception thrown when {@link AutoLFInputStream} is told to
+	 * throw an exception when attempting to read a binary file. The exception
+	 * may be thrown at any stage during reading.
+	 *
 	 * @since 3.3
 	 */
-	public boolean isBinary() {
-		return isBinary;
-	}
+	public static class IsBinaryException extends IOException {
+		private static final long serialVersionUID = 1L;
 
-	@Override
-	public void close() throws IOException {
-		in.close();
-	}
-
-	private boolean fillBuffer() throws IOException {
-		cnt = in.read(buf, 0, buf.length);
-		if (cnt < 1)
-			return false;
-		if (detectBinary) {
-			isBinary = RawText.isBinary(buf, cnt);
-			detectBinary = false;
-			if (isBinary && abortIfBinary)
-				throw new IsBinaryException();
+		IsBinaryException() {
+			super();
 		}
-		ptr = 0;
-		return true;
 	}
+
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java
new file mode 100644
index 0000000..c95992f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java
@@ -0,0 +1,255 @@
+/*
+ * 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.util.io;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.eclipse.jgit.attributes.Attributes;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
+import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
+import org.eclipse.jgit.treewalk.WorkingTreeOptions;
+
+/**
+ * Utility used to create input and output stream wrappers for
+ * {@link EolStreamType}
+ *
+ * @since 4.3
+ */
+public final class EolStreamTypeUtil {
+	private static final boolean FORCE_EOL_LF_ON_CHECKOUT = false;
+
+	private EolStreamTypeUtil() {
+	}
+
+	/**
+	 * Convenience method used to detect if CRLF conversion has been configured
+	 * using the
+	 * <ul>
+	 * <li>global repo options</li>
+	 * <li>global attributes</li>
+	 * <li>info attributes</li>
+	 * <li>working tree .gitattributes</li>
+	 *
+	 * @param op
+	 *            is the {@link OperationType} of the current traversal
+	 * @param options
+	 *            are the {@link Config} options with key
+	 *            {@link WorkingTreeOptions#KEY}
+	 * @param attrs
+	 *            are the {@link Attributes} of the file for which the
+	 *            {@link EolStreamType} is to be detected
+	 *
+	 * @return the stream conversion {@link EolStreamType} to be performed for
+	 *         the selected {@link OperationType}
+	 */
+	public static EolStreamType detectStreamType(OperationType op,
+			WorkingTreeOptions options, Attributes attrs) {
+		switch (op) {
+		case CHECKIN_OP:
+			return checkInStreamType(options, attrs);
+		case CHECKOUT_OP:
+			return checkOutStreamType(options, attrs);
+		default:
+			throw new IllegalArgumentException("unknown OperationType " + op); //$NON-NLS-1$
+		}
+	}
+
+	/**
+	 * @param in
+	 *            original stream
+	 * @param conversion
+	 *            to be performed
+	 * @return the converted stream depending on {@link EolStreamType}
+	 */
+	public static InputStream wrapInputStream(InputStream in,
+			EolStreamType conversion) {
+		switch (conversion) {
+		case TEXT_CRLF:
+			return new AutoCRLFInputStream(in, false);
+		case TEXT_LF:
+			return new AutoLFInputStream(in, false);
+		case AUTO_CRLF:
+			return new AutoCRLFInputStream(in, true);
+		case AUTO_LF:
+			return new AutoLFInputStream(in, true);
+		default:
+			return in;
+		}
+	}
+
+	/**
+	 * @param out
+	 *            original stream
+	 * @param conversion
+	 *            to be performed
+	 * @return the converted stream depending on {@link EolStreamType}
+	 */
+	public static OutputStream wrapOutputStream(OutputStream out,
+			EolStreamType conversion) {
+		switch (conversion) {
+		case TEXT_CRLF:
+			return new AutoCRLFOutputStream(out, false);
+		case AUTO_CRLF:
+			return new AutoCRLFOutputStream(out, true);
+		case TEXT_LF:
+			return new AutoLFOutputStream(out, false);
+		case AUTO_LF:
+			return new AutoLFOutputStream(out, true);
+		default:
+			return out;
+		}
+	}
+
+	private static EolStreamType checkInStreamType(WorkingTreeOptions options,
+			Attributes attrs) {
+		// old git system
+		if (attrs.isSet("crlf")) {//$NON-NLS-1$
+			return EolStreamType.TEXT_LF;
+		} else if (attrs.isUnset("crlf")) {//$NON-NLS-1$
+			return EolStreamType.DIRECT;
+		} else if ("input".equals(attrs.getValue("crlf"))) {//$NON-NLS-1$ //$NON-NLS-2$
+			return EolStreamType.TEXT_LF;
+		}
+
+		// new git system
+		if (attrs.isUnset("text")) {//$NON-NLS-1$
+			return EolStreamType.DIRECT;
+		}
+		String eol = attrs.getValue("eol"); //$NON-NLS-1$
+		if (eol != null)
+			// check-in is always normalized to LF
+			return EolStreamType.TEXT_LF;
+
+		if (attrs.isSet("text")) { //$NON-NLS-1$
+			return EolStreamType.TEXT_LF;
+		}
+
+		if ("auto".equals(attrs.getValue("text"))) { //$NON-NLS-1$ //$NON-NLS-2$
+			return EolStreamType.AUTO_LF;
+		}
+
+		switch (options.getAutoCRLF()) {
+		case TRUE:
+		case INPUT:
+			return EolStreamType.AUTO_LF;
+		case FALSE:
+			return EolStreamType.DIRECT;
+		}
+
+		return EolStreamType.DIRECT;
+	}
+
+	private static EolStreamType checkOutStreamType(WorkingTreeOptions options,
+			Attributes attrs) {
+		// old git system
+		if (attrs.isSet("crlf")) {//$NON-NLS-1$
+			return FORCE_EOL_LF_ON_CHECKOUT ? EolStreamType.TEXT_LF
+					: EolStreamType.DIRECT;
+		} else if (attrs.isUnset("crlf")) {//$NON-NLS-1$
+			return EolStreamType.DIRECT;
+		} else if ("input".equals(attrs.getValue("crlf"))) {//$NON-NLS-1$ //$NON-NLS-2$
+			return EolStreamType.DIRECT;
+		}
+
+		// new git system
+		if (attrs.isUnset("text")) {//$NON-NLS-1$
+			return EolStreamType.DIRECT;
+		}
+		String eol = attrs.getValue("eol"); //$NON-NLS-1$
+		if (eol != null && "crlf".equals(eol)) //$NON-NLS-1$
+			return EolStreamType.TEXT_CRLF;
+		if (eol != null && "lf".equals(eol)) //$NON-NLS-1$
+			return FORCE_EOL_LF_ON_CHECKOUT ? EolStreamType.TEXT_LF
+					: EolStreamType.DIRECT;
+
+		if (attrs.isSet("text")) { //$NON-NLS-1$
+			switch (options.getAutoCRLF()) {
+			case TRUE:
+				return EolStreamType.TEXT_CRLF;
+			default:
+				// no decision
+			}
+			switch (options.getEOL()) {
+			case CRLF:
+				return EolStreamType.TEXT_CRLF;
+			case LF:
+				return FORCE_EOL_LF_ON_CHECKOUT ? EolStreamType.TEXT_LF
+						: EolStreamType.DIRECT;
+			case NATIVE:
+			default:
+				return EolStreamType.DIRECT;
+			}
+		}
+
+		if ("auto".equals(attrs.getValue("text"))) { //$NON-NLS-1$ //$NON-NLS-2$
+			switch (options.getAutoCRLF()) {
+			case TRUE:
+				return EolStreamType.AUTO_CRLF;
+			default:
+				// no decision
+			}
+			switch (options.getEOL()) {
+			case CRLF:
+				return EolStreamType.AUTO_CRLF;
+			case LF:
+				return FORCE_EOL_LF_ON_CHECKOUT ? EolStreamType.TEXT_LF
+						: EolStreamType.DIRECT;
+			case NATIVE:
+			default:
+				return EolStreamType.DIRECT;
+			}
+		}
+
+		switch (options.getAutoCRLF()) {
+		case TRUE:
+			return EolStreamType.AUTO_CRLF;
+		default:
+			// no decision
+		}
+
+		return EolStreamType.DIRECT;
+	}
+
+}