Browse Source

Merge "Support more of AutoCRLF"

stable-1.3
Christian Halstrick 13 years ago committed by Code Review
parent
commit
a9892fba46
  1. 48
      org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
  2. 33
      org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java
  3. 41
      org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
  4. 123
      org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFOutputStreamTest.java
  5. 6
      org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/EolCanonicalizingInputStreamTest.java
  6. 15
      org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
  7. 4
      org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java
  8. 11
      org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
  9. 33
      org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java
  10. 187
      org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java
  11. 16
      org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolCanonicalizingInputStream.java

48
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java

@ -110,6 +110,54 @@ public class AddCommandTest extends RepositoryTestCase {
indexState(CONTENT)); 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 @Test
public void testAddExistingSingleFileInSubDir() throws IOException, NoFilepatternException { public void testAddExistingSingleFileInSubDir() throws IOException, NoFilepatternException {
FileUtils.mkdir(new File(db.getWorkTree(), "sub")); FileUtils.mkdir(new File(db.getWorkTree(), "sub"));

33
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.DirCache;
import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.RepositoryTestCase; import org.eclipse.jgit.lib.RepositoryTestCase;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -200,4 +202,35 @@ public class PathCheckoutCommandTest extends RepositoryTestCase {
r.release(); 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 <FILE1>
File written = writeTrashFile(FILE1, "4\r\n4");
assertEquals("4\r\n4", read(written));
// "git add <FILE1>"
git.add().addFilepattern(FILE1).call();
// "git commit -m 'CRLF'"
git.commit().setMessage("CRLF").call();
// edit <FILE1>
written = writeTrashFile(FILE1, "4\n4");
assertEquals("4\n4", read(written));
// "git add <FILE1>"
git.add().addFilepattern(FILE1).call();
// "git checkout -- <FILE1>
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());
}
} }

41
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java

@ -40,6 +40,7 @@
*/ */
package org.eclipse.jgit.lib; package org.eclipse.jgit.lib;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; 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 @Test
public void testCheckoutUncachedChanges() throws IOException { public void testCheckoutUncachedChanges() throws IOException {
setupCase(mk("foo"), mk("foo"), mk("foo")); setupCase(mk("foo"), mk("foo"), mk("foo"));
@ -931,9 +967,8 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
offset += numRead; offset += numRead;
} }
is.close(); is.close();
assertTrue("unexpected content for path " + path assertArrayEquals("unexpected content for path " + path
+ " in workDir. Expected: <" + expectedValue + ">", + " in workDir. ", buffer, i.get(path).getBytes());
Arrays.equals(buffer, i.get(path).getBytes()));
nrFiles++; nrFiles++;
} }
} }

123
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();
}
}

6
org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/EolCanonicalizingInputStreamTest.java

@ -77,6 +77,12 @@ public class EolCanonicalizingInputStreamTest {
test(bytes, bytes); 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 { private void test(byte[] input, byte[] expected) throws IOException {
final InputStream bis1 = new ByteArrayInputStream(input); final InputStream bis1 = new ByteArrayInputStream(input);
final InputStream cis1 = new EolCanonicalizingInputStream(bis1); final InputStream cis1 = new EolCanonicalizingInputStream(bis1);

15
org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java vendored

@ -45,6 +45,7 @@ package org.eclipse.jgit.dircache;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; 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.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.EmptyTreeIterator; 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.treewalk.filter.PathFilter;
import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils; 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. * 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()); ObjectLoader ol = or.open(entry.getObjectId());
File parentDir = f.getParentFile(); File parentDir = f.getParentFile();
File tmpFile = File.createTempFile("._" + f.getName(), null, parentDir); 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 { try {
ol.copyTo(channel); ol.copyTo(channel);
} finally { } finally {
channel.close(); channel.close();
} }
FS fs = repo.getFS(); FS fs = repo.getFS();
WorkingTreeOptions opt = repo.getConfig().get(WorkingTreeOptions.KEY);
if (opt.isFileMode() && fs.supportsExecute()) { if (opt.isFileMode() && fs.supportsExecute()) {
if (FileMode.EXECUTABLE_FILE.equals(entry.getRawMode())) { if (FileMode.EXECUTABLE_FILE.equals(entry.getRawMode())) {
if (!fs.canExecute(tmpFile)) if (!fs.canExecute(tmpFile))
@ -954,6 +962,9 @@ public class DirCacheCheckout {
} }
} }
entry.setLastModified(f.lastModified()); entry.setLastModified(f.lastModified());
if (opt.getAutoCRLF() != AutoCRLF.FALSE)
entry.setLength(f.length()); // AutoCRLF wants on-disk-size
else
entry.setLength((int) ol.getSize()); entry.setLength((int) ol.getSize());
} }
} }

4
org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java

@ -97,8 +97,8 @@ class ObjectDirectoryInserter extends ObjectInserter {
throws IOException { throws IOException {
if (len <= buffer().length) { if (len <= buffer().length) {
byte[] buf = buffer(); byte[] buf = buffer();
IO.readFully(is, buf, 0, (int) len); int actLen = IO.readFully(is, buf, 0);
return insert(type, buf, 0, (int) len); return insert(type, buf, 0, actLen);
} else { } else {
MessageDigest md = digest(); MessageDigest md = digest();

11
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.ignore.IgnoreRule;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.CoreConfig; import org.eclipse.jgit.lib.CoreConfig;
import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository; 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); return new EolCanonicalizingInputStream(in);
} }
@ -498,7 +499,13 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
* the file could not be opened for reading. * the file could not be opened for reading.
*/ */
public InputStream openEntryStream() throws IOException { 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;
} }
/** /**

33
org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java

@ -142,7 +142,13 @@ public class IO {
throw new IOException(MessageFormat.format( throw new IOException(MessageFormat.format(
JGitText.get().fileIsTooLarge, path)); JGitText.get().fileIsTooLarge, path));
final byte[] buf = new byte[(int) sz]; 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; return buf;
} finally { } finally {
try { try {
@ -253,6 +259,31 @@ public class IO {
return cnt != 0 ? cnt : -1; 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. * Skip an entire region of an input stream.
* <p> * <p>

187
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.
* <p>
* 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();
}
}

16
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.IOException;
import java.io.InputStream; 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! * Note: Make sure to apply this InputStream only to text files!
*/ */
@ -62,6 +65,10 @@ public class EolCanonicalizingInputStream extends InputStream {
private int ptr; private int ptr;
private boolean isBinary;
private boolean modeDetected;
/** /**
* Creates a new InputStream, wrapping the specified stream * Creates a new InputStream, wrapping the specified stream
* *
@ -95,7 +102,8 @@ public class EolCanonicalizingInputStream extends InputStream {
} }
byte b = buf[ptr++]; byte b = buf[ptr++];
if (b != '\r') { if (isBinary || b != '\r') {
// Logic for binary files ends here
bs[off++] = b; bs[off++] = b;
continue; continue;
} }
@ -124,6 +132,10 @@ public class EolCanonicalizingInputStream extends InputStream {
cnt = in.read(buf, 0, buf.length); cnt = in.read(buf, 0, buf.length);
if (cnt < 1) if (cnt < 1)
return false; return false;
if (!modeDetected) {
isBinary = RawText.isBinary(buf, cnt);
modeDetected = true;
}
ptr = 0; ptr = 0;
return true; return true;
} }

Loading…
Cancel
Save