TemporaryBuffer.LocalFile: use Files.newInputStream() On Unix, FileInputStream.skip() uses lseek under the hood and allows skipping beyond EOF, which makes WorkingTreeIterator.computeLength() go into an endless loop. Files.newInputStream() returns a sun.nio.ch.ChannelInputStream, which for files does not skip beyond EOF, avoiding this endless loop. At the point WorkingTreeIterator.computeLength() is called, the input stream is either a CR/LF-processing stream, which extend InputStream, which actually uses reads to implement skipping and thus doesn't skip beyond EOF, or a BlockInputStream, which also does not skip beyond the content length, or a stream obtained from TemporaryBuffer.LocalFile. Bug: jgit-261 Change-Id: I2e1bfe04c308220a1c2d819e6414a6ff7b2f9c34
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FilterCommandsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FilterCommandsTest.java index 0513da2..f7dab12 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FilterCommandsTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FilterCommandsTest.java
@@ -16,8 +16,11 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.file.Files; import java.util.HashSet; +import java.util.Random; import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.MergeResult; @@ -70,6 +73,33 @@ public int run() throws IOException { } } + private static class NoOpCommandFactory implements FilterCommandFactory { + + @Override + public FilterCommand create(Repository db, InputStream in, + OutputStream out) throws IOException { + return new FilterCommand(in, out) { + + private byte[] buf = new byte[32 * 1024]; + + @Override + public int run() throws IOException { + int n = in.read(buf); + if (n < 0) { + in.close(); + out.close(); + return -1; + } + if (n > 0) { + out.write(buf, 0, n); + } + return n; + } + + }; + } + } + @Override @Before public void setUp() throws Exception { @@ -149,6 +179,46 @@ public void testBuiltinCleanFilter() throws Exception { } @Test + public void testCleanLargeFile() throws Exception { + String builtinCommandName = "jgit://builtin/test/clean"; + FilterCommandRegistry.register(builtinCommandName, + new NoOpCommandFactory()); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "test", "clean", builtinCommandName); + config.save(); + + writeTrashFile(".gitattributes", "*.bin filter=test"); + git.add().addFilepattern(".gitattributes").call(); + git.commit().setMessage("add filter").call(); + + File f = new File(git.getRepository().getWorkTree(), "a.bin"); + long length = 0; + try (OutputStream out = Files.newOutputStream(f.toPath())) { + byte[] buf = new byte[8 * 1024]; + Random r = ThreadLocalRandom.current(); + while (length < TemporaryBuffer.DEFAULT_IN_CORE_LIMIT) { + r.nextBytes(buf); + buf[0] = 0; + out.write(buf); + length += buf.length; + } + // Write a few bytes extra, just so that we don't have a + // multiple of 8k, and we're beyond the in-memory limit. + r.nextBytes(buf); + out.write(buf, 0, 19); + length += 19; + } + + fsTick(f); + git.add().addFilepattern("a.bin").call(); + assertEquals( + "[.gitattributes, mode:100644, length:17]" + + "[Test.txt, mode:100644, length:11]" + + "[a.bin, mode:100644, length:" + length + "]", + indexState(LENGTH)); + } + + @Test public void testBuiltinSmudgeFilter() throws IOException, GitAPIException { String builtinCommandName = "jgit://builtin/test/smudge"; FilterCommandRegistry.register(builtinCommandName,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java index 0f45162..fdb2679 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java
@@ -15,10 +15,12 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UncheckedIOException; +import java.nio.file.Files; import java.util.ArrayList; import org.eclipse.jgit.internal.JGitText; @@ -556,9 +558,10 @@ public void writeTo(OutputStream os, ProgressMonitor pm) @Override public InputStream openInputStream() throws IOException { - if (onDiskFile == null) + if (onDiskFile == null) { return super.openInputStream(); - return new FileInputStream(onDiskFile); + } + return Files.newInputStream(onDiskFile.toPath()); } @Override @@ -566,7 +569,8 @@ public InputStream openInputStreamWithAutoDestroy() throws IOException { if (onDiskFile == null) { return super.openInputStreamWithAutoDestroy(); } - return new FileInputStream(onDiskFile) { + return new FilterInputStream( + Files.newInputStream(onDiskFile.toPath())) { @Override public void close() throws IOException { super.close();