diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java index 632732db8..632288ec6 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java @@ -110,6 +110,54 @@ public class AddCommandTest extends RepositoryTestCase { indexState(CONTENT)); } + @Test + public void testAddExistingSingleFileWithNewLine() throws IOException, + NoFilepatternException { + File file = new File(db.getWorkTree(), "a.txt"); + FileUtils.createNewFile(file); + PrintWriter writer = new PrintWriter(file); + writer.print("row1\r\nrow2"); + writer.close(); + + Git git = new Git(db); + db.getConfig().setString("core", null, "autocrlf", "false"); + git.add().addFilepattern("a.txt").call(); + assertEquals("[a.txt, mode:100644, content:row1\r\nrow2]", + indexState(CONTENT)); + db.getConfig().setString("core", null, "autocrlf", "true"); + git.add().addFilepattern("a.txt").call(); + assertEquals("[a.txt, mode:100644, content:row1\nrow2]", + indexState(CONTENT)); + db.getConfig().setString("core", null, "autocrlf", "input"); + git.add().addFilepattern("a.txt").call(); + assertEquals("[a.txt, mode:100644, content:row1\nrow2]", + indexState(CONTENT)); + } + + @Test + public void testAddExistingSingleBinaryFile() throws IOException, + NoFilepatternException { + File file = new File(db.getWorkTree(), "a.txt"); + FileUtils.createNewFile(file); + PrintWriter writer = new PrintWriter(file); + writer.print("row1\r\nrow2\u0000"); + writer.close(); + + Git git = new Git(db); + db.getConfig().setString("core", null, "autocrlf", "false"); + git.add().addFilepattern("a.txt").call(); + assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]", + indexState(CONTENT)); + db.getConfig().setString("core", null, "autocrlf", "true"); + git.add().addFilepattern("a.txt").call(); + assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]", + indexState(CONTENT)); + db.getConfig().setString("core", null, "autocrlf", "input"); + git.add().addFilepattern("a.txt").call(); + assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]", + indexState(CONTENT)); + } + @Test public void testAddExistingSingleFileInSubDir() throws IOException, NoFilepatternException { FileUtils.mkdir(new File(db.getWorkTree(), "sub")); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java index 81b6908dc..20a1acbd9 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java @@ -50,8 +50,10 @@ import java.io.IOException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.errors.NoWorkTreeException; +import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevCommit; import org.junit.Before; import org.junit.Test; @@ -200,4 +202,35 @@ public class PathCheckoutCommandTest extends RepositoryTestCase { r.release(); } } + + public void testCheckoutMixedNewlines() throws Exception { + // "git config core.autocrlf true" + StoredConfig config = git.getRepository().getConfig(); + config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOCRLF, true); + config.save(); + // edit + File written = writeTrashFile(FILE1, "4\r\n4"); + assertEquals("4\r\n4", read(written)); + // "git add " + git.add().addFilepattern(FILE1).call(); + // "git commit -m 'CRLF'" + git.commit().setMessage("CRLF").call(); + // edit + written = writeTrashFile(FILE1, "4\n4"); + assertEquals("4\n4", read(written)); + // "git add " + git.add().addFilepattern(FILE1).call(); + // "git checkout -- + git.checkout().addPath(FILE1).call(); + // "git status" => clean + Status status = git.status().call(); + assertEquals(0, status.getAdded().size()); + assertEquals(0, status.getChanged().size()); + assertEquals(0, status.getConflicting().size()); + assertEquals(0, status.getMissing().size()); + assertEquals(0, status.getModified().size()); + assertEquals(0, status.getRemoved().size()); + assertEquals(0, status.getUntracked().size()); + } } 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 f06b37c84..fafd745b5 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 @@ -40,6 +40,7 @@ */ package org.eclipse.jgit.lib; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -878,6 +879,41 @@ public class DirCacheCheckoutTest extends RepositoryTestCase { } } + @Test + public void testCheckoutOutChangesAutoCRLFfalse() throws IOException { + setupCase(mk("foo"), mkmap("foo/bar", "foo\nbar"), mk("foo")); + checkout(); + assertIndex(mkmap("foo/bar", "foo\nbar")); + assertWorkDir(mkmap("foo/bar", "foo\nbar")); + } + + @Test + public void testCheckoutOutChangesAutoCRLFInput() throws IOException { + setupCase(mk("foo"), mkmap("foo/bar", "foo\nbar"), mk("foo")); + db.getConfig().setString("core", null, "autocrlf", "input"); + checkout(); + assertIndex(mkmap("foo/bar", "foo\nbar")); + assertWorkDir(mkmap("foo/bar", "foo\nbar")); + } + + @Test + public void testCheckoutOutChangesAutoCRLFtrue() throws IOException { + setupCase(mk("foo"), mkmap("foo/bar", "foo\nbar"), mk("foo")); + db.getConfig().setString("core", null, "autocrlf", "true"); + checkout(); + assertIndex(mkmap("foo/bar", "foo\nbar")); + assertWorkDir(mkmap("foo/bar", "foo\r\nbar")); + } + + @Test + public void testCheckoutOutChangesAutoCRLFtrueBinary() throws IOException { + setupCase(mk("foo"), mkmap("foo/bar", "foo\nb\u0000ar"), mk("foo")); + db.getConfig().setString("core", null, "autocrlf", "true"); + checkout(); + assertIndex(mkmap("foo/bar", "foo\nb\u0000ar")); + assertWorkDir(mkmap("foo/bar", "foo\nb\u0000ar")); + } + @Test public void testCheckoutUncachedChanges() throws IOException { setupCase(mk("foo"), mk("foo"), mk("foo")); @@ -931,9 +967,8 @@ public class DirCacheCheckoutTest extends RepositoryTestCase { offset += numRead; } is.close(); - assertTrue("unexpected content for path " + path - + " in workDir. Expected: <" + expectedValue + ">", - Arrays.equals(buffer, i.get(path).getBytes())); + assertArrayEquals("unexpected content for path " + path + + " in workDir. ", buffer, i.get(path).getBytes()); nrFiles++; } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFOutputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFOutputStreamTest.java new file mode 100644 index 000000000..b370468f6 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFOutputStreamTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2011, Robin Rosenberg + * 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.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.junit.Assert; +import org.junit.Test; + +public class AutoCRLFOutputStreamTest { + + @Test + public void test() throws IOException { + assertNoCrLf("", ""); + assertNoCrLf("\r", "\r"); + assertNoCrLf("\r\n", "\n"); + assertNoCrLf("\r\n", "\r\n"); + assertNoCrLf("\r\r", "\r\r"); + assertNoCrLf("\r\n\r", "\n\r"); + assertNoCrLf("\r\n\r\r", "\r\n\r\r"); + assertNoCrLf("\r\n\r\n", "\r\n\r\n"); + assertNoCrLf("\r\n\r\n\r", "\n\r\n\r"); + } + + private void assertNoCrLf(String string, String string2) throws IOException { + assertNoCrLfHelper(string, string2); + // \u00e5 = LATIN SMALL LETTER A WITH RING ABOVE + // the byte value is negative + assertNoCrLfHelper("\u00e5" + string, "\u00e5" + string2); + assertNoCrLfHelper("\u00e5" + string + "\u00e5", "\u00e5" + string2 + + "\u00e5"); + assertNoCrLfHelper(string + "\u00e5", string2 + "\u00e5"); + } + + private void assertNoCrLfHelper(String expect, String input) + throws IOException { + byte[] inbytes = input.getBytes(); + byte[] expectBytes = expect.getBytes(); + for (int i = 0; i < 5; ++i) { + byte[] buf = new byte[i]; + InputStream in = new ByteArrayInputStream(inbytes); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + OutputStream out = new AutoCRLFOutputStream(bos); + if (i > 0) { + int n; + while ((n = in.read(buf)) >= 0) { + out.write(buf, 0, n); + } + } else { + int c; + while ((c = in.read()) != -1) + out.write(c); + } + out.flush(); + in.close(); + out.close(); + byte[] actualBytes = bos.toByteArray(); + Assert.assertEquals("bufsize=" + i, encode(expectBytes), + encode(actualBytes)); + } + } + + String encode(byte[] in) { + StringBuilder str = new StringBuilder(); + for (byte b : in) { + if (b < 32) + str.append(0xFF & b); + else { + str.append("'"); + str.append((char) b); + str.append("'"); + } + str.append(' '); + } + return str.toString(); + } + +} 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/EolCanonicalizingInputStreamTest.java index 37660ce72..960ca6241 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/EolCanonicalizingInputStreamTest.java @@ -77,6 +77,12 @@ public class EolCanonicalizingInputStreamTest { test(bytes, bytes); } + @Test + public void testEmpty() throws IOException { + final byte[] bytes = asBytes(""); + test(bytes, bytes); + } + private void test(byte[] input, byte[] expected) throws IOException { final InputStream bis1 = new ByteArrayInputStream(input); final InputStream cis1 = new EolCanonicalizingInputStream(bis1); 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 465f01ada..9b397e88c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java @@ -45,6 +45,7 @@ package org.eclipse.jgit.dircache; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; @@ -62,6 +63,7 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.CoreConfig.AutoCRLF; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.treewalk.EmptyTreeIterator; @@ -73,6 +75,7 @@ import org.eclipse.jgit.treewalk.WorkingTreeOptions; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; +import org.eclipse.jgit.util.io.AutoCRLFOutputStream; /** * This class handles checking out one or two trees merging with the index. @@ -926,14 +929,19 @@ public class DirCacheCheckout { ObjectLoader ol = or.open(entry.getObjectId()); File parentDir = f.getParentFile(); File tmpFile = File.createTempFile("._" + f.getName(), null, parentDir); - FileOutputStream channel = new FileOutputStream(tmpFile); + WorkingTreeOptions opt = repo.getConfig().get(WorkingTreeOptions.KEY); + FileOutputStream rawChannel = new FileOutputStream(tmpFile); + OutputStream channel; + if (opt.getAutoCRLF() == AutoCRLF.TRUE) + channel = new AutoCRLFOutputStream(rawChannel); + else + channel = rawChannel; try { ol.copyTo(channel); } finally { channel.close(); } FS fs = repo.getFS(); - WorkingTreeOptions opt = repo.getConfig().get(WorkingTreeOptions.KEY); if (opt.isFileMode() && fs.supportsExecute()) { if (FileMode.EXECUTABLE_FILE.equals(entry.getRawMode())) { if (!fs.canExecute(tmpFile)) @@ -954,6 +962,9 @@ public class DirCacheCheckout { } } entry.setLastModified(f.lastModified()); - entry.setLength((int) ol.getSize()); + if (opt.getAutoCRLF() != AutoCRLF.FALSE) + entry.setLength(f.length()); // AutoCRLF wants on-disk-size + else + entry.setLength((int) ol.getSize()); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java index 77902dfb4..ffd5c4149 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java @@ -97,8 +97,8 @@ class ObjectDirectoryInserter extends ObjectInserter { throws IOException { if (len <= buffer().length) { byte[] buf = buffer(); - IO.readFully(is, buf, 0, (int) len); - return insert(type, buf, 0, (int) len); + int actLen = IO.readFully(is, buf, 0); + return insert(type, buf, 0, actLen); } else { MessageDigest md = digest(); 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 2a42e05d3..fa57b71ad 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java @@ -72,6 +72,7 @@ import org.eclipse.jgit.ignore.IgnoreNode; import org.eclipse.jgit.ignore.IgnoreRule; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.CoreConfig; +import org.eclipse.jgit.lib.CoreConfig.AutoCRLF; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; @@ -402,7 +403,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { } } - private InputStream filterClean(InputStream in) { + private InputStream filterClean(InputStream in) throws IOException { return new EolCanonicalizingInputStream(in); } @@ -498,7 +499,13 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { * the file could not be opened for reading. */ public InputStream openEntryStream() throws IOException { - return current().openInputStream(); + InputStream rawis = current().openInputStream(); + InputStream is; + if (getOptions().getAutoCRLF() != AutoCRLF.FALSE) + is = new EolCanonicalizingInputStream(rawis); + else + is = rawis; + return is; } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java index 439fe09b5..cafc94057 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java @@ -142,7 +142,13 @@ public class IO { throw new IOException(MessageFormat.format( JGitText.get().fileIsTooLarge, path)); final byte[] buf = new byte[(int) sz]; - IO.readFully(in, buf, 0, buf.length); + int actSz = IO.readFully(in, buf, 0); + + if (actSz == sz) { + byte[] ret = new byte[actSz]; + System.arraycopy(buf, 0, ret, 0, actSz); + return ret; + } return buf; } finally { try { @@ -253,6 +259,31 @@ public class IO { return cnt != 0 ? cnt : -1; } + /** + * Read the entire byte array into memory, unless input is shorter + * + * @param fd + * input stream to read the data from. + * @param dst + * buffer that must be fully populated, [off, off+len). + * @param off + * position within the buffer to start writing to. + * @return number of bytes in buffer or stream, whichever is shortest + * @throws IOException + * there was an error reading from the stream. + */ + public static int readFully(InputStream fd, byte[] dst, int off) + throws IOException { + int r; + int len = 0; + while ((r = fd.read(dst, off, dst.length - off)) >= 0 + && len < dst.length) { + off += r; + len += r; + } + return len; + } + /** * Skip an entire region of an input stream. *

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 new file mode 100644 index 000000000..a4c157390 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2011, Robin Rosenberg + * 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.OutputStream; + +import org.eclipse.jgit.diff.RawText; + +/** + * An OutputStream that expands LF to CRLF. + *

+ * Existing CRLF are not expanded to CRCRLF, but retained as is. + */ +public class AutoCRLFOutputStream extends OutputStream { + + private final OutputStream out; + + private int buf = -1; + + private byte[] binbuf = new byte[8000]; + + private int binbufcnt = 0; + + private boolean isBinary; + + /** + * @param out + */ + public AutoCRLFOutputStream(OutputStream out) { + this.out = out; + } + + @Override + public void write(int b) throws IOException { + int overflow = buffer((byte) b); + if (overflow >= 0) + return; + if (isBinary) { + out.write(b); + return; + } + if (b == '\n') { + if (buf == '\r') { + out.write('\n'); + buf = -1; + } else if (buf == -1) { + out.write('\r'); + out.write('\n'); + buf = -1; + } + } else if (b == '\r') { + out.write(b); + buf = '\r'; + } else { + out.write(b); + buf = -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, int off, int len) throws IOException { + int overflow = buffer(b, off, len); + if (overflow < 0) + return; + off = off + len - overflow; + 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) { + byte c = b[i]; + if (c == '\r') { + buf = '\r'; + } else if (c == '\n') { + if (buf != '\r') { + if (lastw < i) { + out.write(b, lastw, i - lastw); + } + out.write('\r'); + lastw = i; + } + buf = -1; + } else { + buf = -1; + } + } + if (lastw < off + len) { + out.write(b, lastw, off + len - lastw); + } + if (b[off + len - 1] == '\r') + buf = '\r'; + } + + private int buffer(byte b) throws IOException { + if (binbufcnt > binbuf.length) + return 1; + binbuf[binbufcnt++] = b; + if (binbufcnt == binbuf.length) + decideMode(); + return 0; + } + + 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 { + isBinary = RawText.isBinary(binbuf, binbufcnt); + int cachedLen = binbufcnt; + binbufcnt = binbuf.length + 1; // full! + write(binbuf, 0, cachedLen); + } + + @Override + public void flush() throws IOException { + if (binbufcnt < binbuf.length) + decideMode(); + buf = -1; + } + + @Override + public void close() throws IOException { + flush(); + super.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 4bdd2b3e5..387d9b82b 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,8 +46,11 @@ package org.eclipse.jgit.util.io; 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'. + * An input stream which canonicalizes EOLs bytes on the fly to '\n', unless the + * first 8000 bytes indicate the stream is binary. * * Note: Make sure to apply this InputStream only to text files! */ @@ -62,6 +65,10 @@ public class EolCanonicalizingInputStream extends InputStream { private int ptr; + private boolean isBinary; + + private boolean modeDetected; + /** * Creates a new InputStream, wrapping the specified stream * @@ -95,7 +102,8 @@ public class EolCanonicalizingInputStream extends InputStream { } byte b = buf[ptr++]; - if (b != '\r') { + if (isBinary || b != '\r') { + // Logic for binary files ends here bs[off++] = b; continue; } @@ -124,6 +132,10 @@ public class EolCanonicalizingInputStream extends InputStream { cnt = in.read(buf, 0, buf.length); if (cnt < 1) return false; + if (!modeDetected) { + isBinary = RawText.isBinary(buf, cnt); + modeDetected = true; + } ptr = 0; return true; }