]> source.dussan.org Git - jgit.git/commitdiff
Add EOL stream type detection to TreeWalk 35/60635/24
authorIvan Motsch <ivan.motsch@bsiag.com>
Thu, 25 Feb 2016 14:39:41 +0000 (15:39 +0100)
committerChristian Halstrick <christian.halstrick@sap.com>
Mon, 7 Mar 2016 16:24:32 +0000 (17:24 +0100)
TreeWalk provides the new method getEolStreamType. This new method can
be used with EolStreamTypeUtil in order to create a wrapped InputStream
or OutputStream when reading / writing files. The implementation
implements support for the git configuration options core.crlf, core.eol
and the .gitattributes "text", "eol" and "binary"

CQ: 10896
Bug: 486563
Change-Id: Ie4f6367afc2a6aec1de56faf95120fff0339a358
Signed-off-by: Ivan Motsch <ivan.motsch@bsiag.com>
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
24 files changed:
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java [new file with mode: 0644]
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolStreamTypeUtilTest.java [new file with mode: 0644]
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoLFInputStreamTest.java [new file with mode: 0644]
org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/EolCanonicalizingInputStreamTest.java [deleted file]
org.eclipse.jgit/.settings/.api_filters
org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java
org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java
org.eclipse.jgit/src/org/eclipse/jgit/util/Holder.java [new file with mode: 0644]
org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java
org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java
org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java [new file with mode: 0644]
org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java [new file with mode: 0644]
org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolCanonicalizingInputStream.java
org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java [new file with mode: 0644]

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 (file)
index 0000000..5dd8da5
--- /dev/null
@@ -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 (file)
index 0000000..8ca1b31
--- /dev/null
@@ -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;
+       }
+
+}
index c1b882a6561dbcd56ea61725e6ef9bbc7650d574..5578c03d4a656286b8b8157217ee6bcd33d7c91a 100644 (file)
@@ -65,6 +65,7 @@ import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.api.errors.NoFilepatternException;
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.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 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
                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/util/io/AutoLFInputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoLFInputStreamTest.java
new file mode 100644 (file)
index 0000000..40cac93
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * 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
+ * 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 static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+
+import org.junit.Test;
+
+public class AutoLFInputStreamTest {
+
+       @Test
+       public void testLF() throws IOException {
+               final byte[] bytes = asBytes("1\n2\n3");
+               test(bytes, bytes, false);
+       }
+
+       @Test
+       public void testCR() throws IOException {
+               final byte[] bytes = asBytes("1\r2\r3");
+               test(bytes, bytes, false);
+       }
+
+       @Test
+       public void testCRLF() throws IOException {
+               test(asBytes("1\r\n2\r\n3"), asBytes("1\n2\n3"), false);
+       }
+
+       @Test
+       public void testLFCR() throws IOException {
+               final byte[] bytes = asBytes("1\n\r2\n\r3");
+               test(bytes, bytes, false);
+       }
+
+       @Test
+       public void testEmpty() throws IOException {
+               final byte[] bytes = asBytes("");
+               test(bytes, bytes, false);
+       }
+
+       @Test
+       public void testBinaryDetect() throws IOException {
+               final byte[] bytes = asBytes("1\r\n2\r\n3\0");
+               test(bytes, bytes, true);
+       }
+
+       @Test
+       public void testBinaryDontDetect() throws IOException {
+               test(asBytes("1\r\n2\r\n3\0"), asBytes("1\n2\n3\0"), false);
+       }
+
+       private static void test(byte[] input, byte[] expected,
+                       boolean detectBinary) throws IOException {
+               final InputStream bis1 = new ByteArrayInputStream(input);
+               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);
+                       index1++;
+               }
+
+               assertEquals(expected.length, index1);
+
+               for (int bufferSize = 1; bufferSize < 10; bufferSize++) {
+                       final byte[] buffer = new byte[bufferSize];
+                       final InputStream bis2 = new ByteArrayInputStream(input);
+                       final InputStream cis2 = new AutoLFInputStream(bis2, detectBinary);
+
+                       int read = 0;
+                       for (int readNow = cis2.read(buffer, 0, buffer.length); readNow != -1
+                                       && read < expected.length; readNow = cis2.read(buffer, 0,
+                                       buffer.length)) {
+                               for (int index2 = 0; index2 < readNow; index2++) {
+                                       assertEquals(expected[read + index2], buffer[index2]);
+                               }
+                               read += readNow;
+                       }
+
+                       assertEquals(expected.length, read);
+                       cis2.close();
+               }
+               cis1.close();
+       }
+
+       private static byte[] asBytes(String in) {
+               try {
+                       return in.getBytes("UTF-8");
+               } catch (UnsupportedEncodingException ex) {
+                       throw new AssertionError();
+               }
+       }
+}
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
deleted file mode 100644 (file)
index ed2a4f2..0000000
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2010, Marc Strapetz <marc.strapetz@syntevo.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 static org.junit.Assert.assertEquals;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-
-import org.junit.Test;
-
-public class EolCanonicalizingInputStreamTest {
-
-       @Test
-       public void testLF() throws IOException {
-               final byte[] bytes = asBytes("1\n2\n3");
-               test(bytes, bytes, false);
-       }
-
-       @Test
-       public void testCR() throws IOException {
-               final byte[] bytes = asBytes("1\r2\r3");
-               test(bytes, bytes, false);
-       }
-
-       @Test
-       public void testCRLF() throws IOException {
-               test(asBytes("1\r\n2\r\n3"), asBytes("1\n2\n3"), false);
-       }
-
-       @Test
-       public void testLFCR() throws IOException {
-               final byte[] bytes = asBytes("1\n\r2\n\r3");
-               test(bytes, bytes, false);
-       }
-
-       @Test
-       public void testEmpty() throws IOException {
-               final byte[] bytes = asBytes("");
-               test(bytes, bytes, false);
-       }
-
-       @Test
-       public void testBinaryDetect() throws IOException {
-               final byte[] bytes = asBytes("1\r\n2\r\n3\0");
-               test(bytes, bytes, true);
-       }
-
-       @Test
-       public void testBinaryDontDetect() throws IOException {
-               test(asBytes("1\r\n2\r\n3\0"), asBytes("1\n2\n3\0"), false);
-       }
-
-       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);
-               int index1 = 0;
-               for (int b = cis1.read(); b != -1; b = cis1.read()) {
-                       assertEquals(expected[index1], (byte) b);
-                       index1++;
-               }
-
-               assertEquals(expected.length, index1);
-
-               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);
-
-                       int read = 0;
-                       for (int readNow = cis2.read(buffer, 0, buffer.length); readNow != -1
-                                       && read < expected.length; readNow = cis2.read(buffer, 0,
-                                       buffer.length)) {
-                               for (int index2 = 0; index2 < readNow; index2++) {
-                                       assertEquals(expected[read + index2], buffer[index2]);
-                               }
-                               read += readNow;
-                       }
-
-                       assertEquals(expected.length, read);
-                       cis2.close();
-               }
-               cis1.close();
-       }
-
-       private static byte[] asBytes(String in) {
-               try {
-                       return in.getBytes("UTF-8");
-               } catch (UnsupportedEncodingException ex) {
-                       throw new AssertionError();
-               }
-       }
-}
index a5000dd6bd4228b0f337ae0f558eea0044bc8e2a..c0dbc779f30cdc8052f725d6b0a5ec6ff3c6acb9 100644 (file)
             </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>
index a83814eb46a13e5fc2c36bd9249cf6e2ef0fc277..d803efd6493bc170dd899dc428710fcb07bbe64f 100644 (file)
@@ -66,7 +66,7 @@ import org.eclipse.jgit.lib.Repository;
 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 @@ public class BlameCommand extends GitCommand<BlameResult> {
                        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
index 4f918fa3570388350f81571ad40da877a44e2548..c37c317c513469933441562f572bc27ff6fc96f1 100644 (file)
@@ -59,6 +59,7 @@ import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
 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.errors.UnmergedPathException;
 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 @@ public class CheckoutCommand extends GitCommand<Ref> {
                        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 @@ public class CheckoutCommand extends GitCommand<Ref> {
                        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 @@ public class CheckoutCommand extends GitCommand<Ref> {
                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,
index 8ef550871f419ca9883cf6c470060f11403bd2ee..1699b9f3d7d27cf5d077ef19e27ac5ce5aecd0db 100644 (file)
@@ -54,11 +54,13 @@ import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
 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 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
                                        // 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 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
                                        }
                                }
 
-                               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,
index 2cdaf240192444801412ef1f4ae1b6bd3ee6fee1..ef32ac929ac67cb8b3db416abbd2f57121a01c5b 100644 (file)
@@ -245,12 +245,14 @@ public class StashCreateCommand extends GitCommand<RevCommit> {
                        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)));
 
index a1e1d15ac656b88cea470e6d70323f428bb87d50..3fcaa383952b01333572104c4774461d897fd579 100644 (file)
@@ -62,6 +62,7 @@ import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
+import org.eclipse.jgit.lib.CoreConfig.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.FS.ExecutionResult;
 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 @@ public class DirCacheCheckout {
        /**
         * @return a list of updated paths and smudgeFilterCommands
         */
-       public Map<String, String> getUpdated() {
+       public Map<String, CheckoutMetadata> getUpdated() {
                return updated;
        }
 
@@ -450,11 +478,12 @@ public class DirCacheCheckout {
                        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 @@ public class DirCacheCheckout {
        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 @@ public class DirCacheCheckout {
         * @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 @@ public class DirCacheCheckout {
 
                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 @@ public class DirCacheCheckout {
                                }
                        } 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 @@ public class DirCacheCheckout {
                // 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()) {
index a89bcee730b9ab8f3546b1c15e283b70c75a8b63..054d1930170ce6f813cc45de37315020d670e84e 100644 (file)
@@ -107,6 +107,13 @@ public class ConfigConstants {
        /** 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";
 
index 5a7634a6f17e01e4ba974d28efcb92d9e98096ba..83efd43aa0edd36eff0b7e1e7a468f79f2be705c 100644 (file)
@@ -75,6 +75,46 @@ public class CoreConfig {
                INPUT;
        }
 
+       /**
+        * 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}
         *
index d2195a874c34c1bc444e6d02e344aac56b7f04e8..b9293ebfb6cd4b661b71c4e677a81d5ee8a16670 100644 (file)
@@ -101,6 +101,19 @@ public class NameConflictTreeWalk extends TreeWalk {
                super(repo);
        }
 
+       /**
+        * 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.
         *
index 4775e9617576a58039d20aa85d22c36dd0bc306d..aecbac11ea4d5e985bda28a2700bc21a7fbde782 100644 (file)
@@ -49,6 +49,7 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 
+import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.api.errors.JGitInternalException;
 import org.eclipse.jgit.attributes.Attribute;
 import org.eclipse.jgit.attributes.Attributes;
@@ -64,6 +65,7 @@ import org.eclipse.jgit.errors.StopWalkException;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.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.PathFilter;
 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 class TreeWalk implements AutoCloseable, AttributesProvider {
        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 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
                        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 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
         *            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 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
         *            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 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
                }
        }
 
+       /**
+        * @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;
index 0d617ee7f934f5bd484b4fb3f58f4dfa83c66bef..ca8f9aa3732bb75a1d90cc8e2bad0bca9e8ebbfa 100644 (file)
@@ -77,8 +77,8 @@ import org.eclipse.jgit.ignore.IgnoreNode;
 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.lib.Repository;
 import org.eclipse.jgit.submodule.SubmoduleWalk;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FS.ExecutionResult;
+import org.eclipse.jgit.util.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 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
        /** 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 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
 
        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 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
                        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 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
                }
        }
 
-       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 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
                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 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
                System.arraycopy(e.encodedName, 0, path, pathOffset, nameLen);
                pathLen = pathOffset + nameLen;
                canonLen = -1;
-               cleanFilterCommand = null;
+               cleanFilterCommandHolder = null;
+               eolStreamTypeHolder = null;
        }
 
        /**
@@ -594,10 +588,11 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
         */
        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 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
                        // 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 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
 
                                        // 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 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
         * @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 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 cleanFilterCommand;
+               return eolStreamTypeHolder.get();
        }
 }
index a6dccce0312642b1069838384e9111e89757cddf..a8990b1e951392d1ac8f60809c5ede4009bc82e1 100644 (file)
@@ -48,6 +48,7 @@ import org.eclipse.jgit.lib.ConfigConstants;
 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 @@ public class WorkingTreeOptions {
 
        private final AutoCRLF autoCRLF;
 
+       private final EOL eol;
+
        private final CheckStat checkStat;
 
        private final SymLinks symlinks;
@@ -75,6 +78,8 @@ public class WorkingTreeOptions {
                                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,
@@ -94,6 +99,15 @@ public class WorkingTreeOptions {
                return autoCRLF;
        }
 
+       /**
+        * @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 (file)
index 0000000..3563e1b
--- /dev/null
@@ -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;
+       }
+}
index 98c5477de14ec45f03ef5995d9dc6be5d5a48a18..30f9ce95fc6d6253b2a0943eaa31fb81cbf6139b 100644 (file)
@@ -50,7 +50,7 @@ import java.io.InputStream;
 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.
  *
index f05da1c73cfc76a6461c0902e5b095df2a23dd57..3a72f7e1dc5f7523f5ad49334325b4b57ca7eb22 100644 (file)
@@ -50,8 +50,11 @@ 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.
+ *
+ * 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 @@ public class AutoCRLFOutputStream extends OutputStream {
 
        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 @@ public class AutoCRLFOutputStream extends OutputStream {
        }
 
        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 (file)
index 0000000..6e33f99
--- /dev/null
@@ -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 (file)
index 0000000..c932b00
--- /dev/null
@@ -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();
+       }
+}
index 98485e90901bf46f9c19132159f3efea46918889..ee729e893e3537c8bbc179687e4062f3ac7ae24d 100644 (file)
@@ -46,46 +46,16 @@ 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'.
  *
- * 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 @@ public class EolCanonicalizingInputStream extends InputStream {
         *            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 @@ public class EolCanonicalizingInputStream extends InputStream {
         *            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;
-       }
-
-       @Override
-       public void close() throws IOException {
-               in.close();
-       }
+       public static class IsBinaryException extends IOException {
+               private static final long serialVersionUID = 1L;
 
-       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 (file)
index 0000000..c95992f
--- /dev/null
@@ -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;
+       }
+
+}