diff options
author | Ivan Motsch <ivan.motsch@bsiag.com> | 2016-02-25 15:39:41 +0100 |
---|---|---|
committer | Christian Halstrick <christian.halstrick@sap.com> | 2016-03-07 17:24:32 +0100 |
commit | b811e4399ea578a07595bac790ad619b9fcb1300 (patch) | |
tree | f250d10e959185a6162f5748a5afcd7fe06ad239 /org.eclipse.jgit/src/org/eclipse/jgit | |
parent | 846ef78a02edceb99940d7aa92dcd2462a85c602 (diff) | |
download | jgit-b811e4399ea578a07595bac790ad619b9fcb1300.tar.gz jgit-b811e4399ea578a07595bac790ad619b9fcb1300.zip |
Add EOL stream type detection to TreeWalk
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>
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit')
18 files changed, 1106 insertions, 251 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java index a83814eb46..d803efd649 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java @@ -66,7 +66,7 @@ import org.eclipse.jgit.lib.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 diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java index 4f918fa357..c37c317c51 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java @@ -59,6 +59,7 @@ import org.eclipse.jgit.api.errors.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, diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java index 8ef550871f..1699b9f3d7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java @@ -54,11 +54,13 @@ import org.eclipse.jgit.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, diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java index 2cdaf24019..ef32ac929a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java @@ -245,12 +245,14 @@ 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))); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java index a1e1d15ac6..3fcaa38395 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java @@ -62,6 +62,7 @@ import org.eclipse.jgit.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()) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java index a89bcee730..054d193017 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java @@ -107,6 +107,13 @@ 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"; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java index 5a7634a6f1..83efd43aa0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java @@ -76,6 +76,46 @@ public class CoreConfig { } /** + * Permissible values for {@code core.eol}. + * <p> + * https://git-scm.com/docs/gitattributes + * + * @since 4.3 + */ + public static enum EOL { + /** checkin with LF, checkout with CRLF. */ + CRLF, + + /** checkin with LF, checkout without conversion. */ + LF, + + /** use the platform's native line ending. */ + NATIVE; + } + + /** + * EOL stream conversion protocol + * + * @since 4.3 + */ + public static enum EolStreamType { + /** convert to CRLF without binary detection */ + TEXT_CRLF, + + /** convert to LF without binary detection */ + TEXT_LF, + + /** convert to CRLF with binary detection */ + AUTO_CRLF, + + /** convert to LF with binary detection */ + AUTO_LF, + + /** do not convert */ + DIRECT; + } + + /** * Permissible values for {@code core.checkstat} * * @since 3.0 diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java index d2195a874c..b9293ebfb6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java @@ -104,6 +104,19 @@ public class NameConflictTreeWalk extends TreeWalk { /** * Create a new tree walker for a given repository. * + * @param repo + * the repository the walker will obtain data from. + * @param or + * the reader the walker will obtain tree data from. + * @since 4.3 + */ + public NameConflictTreeWalk(Repository repo, final ObjectReader or) { + super(repo, or); + } + + /** + * Create a new tree walker for a given repository. + * * @param or * the reader the walker will obtain tree data from. */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java index 4775e96175..aecbac11ea 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java @@ -49,6 +49,7 @@ import java.util.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; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java index 0d617ee7f9..ca8f9aa373 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java @@ -77,8 +77,8 @@ import org.eclipse.jgit.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(); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java index a6dccce031..a8990b1e95 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java @@ -48,6 +48,7 @@ import org.eclipse.jgit.lib.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, @@ -95,6 +100,15 @@ public class WorkingTreeOptions { } /** + * @return how text line endings should be normalized. + * + * @since 4.3 + */ + public EOL getEOL() { + return eol; + } + + /** * @return how stat data is compared * @since 3.0 */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Holder.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Holder.java new file mode 100644 index 0000000000..3563e1bf1d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Holder.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com> + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.util; + +/** + * Holder of an object. + * + * @param <T> + * the type of value held by this {@link Holder} + * + * @since 4.3 + */ +public class Holder<T> { + private T value; + + /** + * @param value + * is the initial value that is {@link #set(Object)} + */ + public Holder(T value) { + set(value); + } + + /** + * @return the value held by this {@link Holder} + */ + public T get() { + return value; + } + + /** + * @param value + * to be set as new value held by this {@link Holder} + */ + public void set(T value) { + this.value = value; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java index 98c5477de1..30f9ce95fc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java @@ -50,7 +50,7 @@ import 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. * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java index f05da1c73c..3a72f7e1dc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java @@ -50,8 +50,11 @@ 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 index 0000000000..6e33f99127 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2010, 2013 Marc Strapetz <marc.strapetz@syntevo.com> + * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.util.io; + +import java.io.IOException; +import java.io.InputStream; + +import org.eclipse.jgit.diff.RawText; + +/** + * An InputStream that normalizes CRLF to LF. + * + * Existing single CR are not changed to LF, but retained as is. + * + * Optionally, a binary check on the first 8000 bytes is performed and in case + * of binary files, canonicalization is turned off (for the complete file). + * <p> + * This is the former EolCanonicalizingInputStream with a new name in order to + * have same naming for all LF / CRLF streams + * + * @since 4.3 + */ +public class AutoLFInputStream extends InputStream { + private final byte[] single = new byte[1]; + + private final byte[] buf = new byte[8096]; + + private final InputStream in; + + private int cnt; + + private int ptr; + + private boolean isBinary; + + private boolean detectBinary; + + private boolean abortIfBinary; + + /** + * A special exception thrown when {@link AutoLFInputStream} is told to + * throw an exception when attempting to read a binary file. The exception + * may be thrown at any stage during reading. + * + * @since 3.3 + */ + public static class IsBinaryException extends IOException { + private static final long serialVersionUID = 1L; + + IsBinaryException() { + super(); + } + } + + /** + * Creates a new InputStream, wrapping the specified stream + * + * @param in + * raw input stream + * @param detectBinary + * whether binaries should be detected + * @since 2.0 + */ + public AutoLFInputStream(InputStream in, boolean detectBinary) { + this(in, detectBinary, false); + } + + /** + * Creates a new InputStream, wrapping the specified stream + * + * @param in + * raw input stream + * @param detectBinary + * whether binaries should be detected + * @param abortIfBinary + * throw an IOException if the file is binary + * @since 3.3 + */ + public AutoLFInputStream(InputStream in, boolean detectBinary, + boolean abortIfBinary) { + this.in = in; + this.detectBinary = detectBinary; + this.abortIfBinary = abortIfBinary; + } + + @Override + public int read() throws IOException { + final int read = read(single, 0, 1); + return read == 1 ? single[0] & 0xff : -1; + } + + @Override + public int read(byte[] bs, final int off, final int len) + throws IOException { + if (len == 0) + return 0; + + if (cnt == -1) + return -1; + + int i = off; + final int end = off + len; + + while (i < end) { + if (ptr == cnt && !fillBuffer()) { + break; + } + + byte b = buf[ptr++]; + if (isBinary || b != '\r') { + // Logic for binary files ends here + bs[i++] = b; + continue; + } + + if (ptr == cnt && !fillBuffer()) { + bs[i++] = '\r'; + break; + } + + if (buf[ptr] == '\n') { + bs[i++] = '\n'; + ptr++; + } else + bs[i++] = '\r'; + } + + return i == off ? -1 : i - off; + } + + /** + * @return true if the stream has detected as a binary so far + * @since 3.3 + */ + public boolean isBinary() { + return isBinary; + } + + @Override + public void close() throws IOException { + in.close(); + } + + private boolean fillBuffer() throws IOException { + cnt = in.read(buf, 0, buf.length); + if (cnt < 1) + return false; + if (detectBinary) { + isBinary = RawText.isBinary(buf, cnt); + detectBinary = false; + if (isBinary && abortIfBinary) + throw new IsBinaryException(); + } + ptr = 0; + return true; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java new file mode 100644 index 0000000000..c932b00f3c --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com> + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.util.io; + +import java.io.IOException; +import java.io.OutputStream; + +import org.eclipse.jgit.diff.RawText; + +/** + * An OutputStream that reduces CRLF to LF. + * + * Existing single CR are not changed to LF, but retained as is. + * + * A binary check on the first 8000 bytes is performed and in case of binary + * files, canonicalization is turned off (for the complete file). + * + * @since 4.3 + */ +public class AutoLFOutputStream extends OutputStream { + + static final int BUFFER_SIZE = 8000; + + private final OutputStream out; + + private int buf = -1; + + private byte[] binbuf = new byte[BUFFER_SIZE]; + + private byte[] onebytebuf = new byte[1]; + + private int binbufcnt = 0; + + private boolean detectBinary; + + private boolean isBinary; + + /** + * @param out + */ + public AutoLFOutputStream(OutputStream out) { + this(out, true); + } + + /** + * @param out + * @param detectBinary + * whether binaries should be detected + */ + public AutoLFOutputStream(OutputStream out, boolean detectBinary) { + this.out = out; + this.detectBinary = detectBinary; + } + + @Override + public void write(int b) throws IOException { + onebytebuf[0] = (byte) b; + write(onebytebuf, 0, 1); + } + + @Override + public void write(byte[] b) throws IOException { + int overflow = buffer(b, 0, b.length); + if (overflow > 0) { + write(b, b.length - overflow, overflow); + } + } + + @Override + public void write(byte[] b, final int startOff, final int startLen) + throws IOException { + final int overflow = buffer(b, startOff, startLen); + if (overflow < 0) { + return; + } + final int off = startOff + startLen - overflow; + final int len = overflow; + if (len == 0) { + return; + } + int lastw = off; + if (isBinary) { + out.write(b, off, len); + return; + } + for (int i = off; i < off + len; ++i) { + final byte c = b[i]; + if (c == '\r') { + // skip write r but backlog r + if (lastw < i) { + out.write(b, lastw, i - lastw); + } + lastw = i + 1; + buf = '\r'; + } else if (c == '\n') { + if (buf == '\r') { + out.write('\n'); + lastw = i + 1; + buf = -1; + } else { + if (lastw < i + 1) { + out.write(b, lastw, i + 1 - lastw); + } + lastw = i + 1; + } + } else { + if (buf == '\r') { + out.write('\r'); + lastw = i; + } + buf = -1; + } + } + if (lastw < off + len) { + out.write(b, lastw, off + len - lastw); + } + } + + private int buffer(byte[] b, int off, int len) throws IOException { + if (binbufcnt > binbuf.length) { + return len; + } + int copy = Math.min(binbuf.length - binbufcnt, len); + System.arraycopy(b, off, binbuf, binbufcnt, copy); + binbufcnt += copy; + int remaining = len - copy; + if (remaining > 0) { + decideMode(); + } + return remaining; + } + + private void decideMode() throws IOException { + if (detectBinary) { + isBinary = RawText.isBinary(binbuf, binbufcnt); + detectBinary = false; + } + int cachedLen = binbufcnt; + binbufcnt = binbuf.length + 1; // full! + write(binbuf, 0, cachedLen); + } + + @Override + public void flush() throws IOException { + if (binbufcnt <= binbuf.length) { + decideMode(); + } + out.flush(); + } + + @Override + public void close() throws IOException { + flush(); + if (buf == '\r') { + out.write(buf); + buf = -1; + } + out.close(); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolCanonicalizingInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolCanonicalizingInputStream.java index 98485e9090..ee729e893e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolCanonicalizingInputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolCanonicalizingInputStream.java @@ -46,46 +46,16 @@ 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 index 0000000000..c95992fbc2 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com> + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.util.io; + +import java.io.InputStream; +import java.io.OutputStream; + +import org.eclipse.jgit.attributes.Attributes; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.CoreConfig.EolStreamType; +import org.eclipse.jgit.treewalk.TreeWalk.OperationType; +import org.eclipse.jgit.treewalk.WorkingTreeOptions; + +/** + * Utility used to create input and output stream wrappers for + * {@link EolStreamType} + * + * @since 4.3 + */ +public final class EolStreamTypeUtil { + private static final boolean FORCE_EOL_LF_ON_CHECKOUT = false; + + private EolStreamTypeUtil() { + } + + /** + * Convenience method used to detect if CRLF conversion has been configured + * using the + * <ul> + * <li>global repo options</li> + * <li>global attributes</li> + * <li>info attributes</li> + * <li>working tree .gitattributes</li> + * + * @param op + * is the {@link OperationType} of the current traversal + * @param options + * are the {@link Config} options with key + * {@link WorkingTreeOptions#KEY} + * @param attrs + * are the {@link Attributes} of the file for which the + * {@link EolStreamType} is to be detected + * + * @return the stream conversion {@link EolStreamType} to be performed for + * the selected {@link OperationType} + */ + public static EolStreamType detectStreamType(OperationType op, + WorkingTreeOptions options, Attributes attrs) { + switch (op) { + case CHECKIN_OP: + return checkInStreamType(options, attrs); + case CHECKOUT_OP: + return checkOutStreamType(options, attrs); + default: + throw new IllegalArgumentException("unknown OperationType " + op); //$NON-NLS-1$ + } + } + + /** + * @param in + * original stream + * @param conversion + * to be performed + * @return the converted stream depending on {@link EolStreamType} + */ + public static InputStream wrapInputStream(InputStream in, + EolStreamType conversion) { + switch (conversion) { + case TEXT_CRLF: + return new AutoCRLFInputStream(in, false); + case TEXT_LF: + return new AutoLFInputStream(in, false); + case AUTO_CRLF: + return new AutoCRLFInputStream(in, true); + case AUTO_LF: + return new AutoLFInputStream(in, true); + default: + return in; + } + } + + /** + * @param out + * original stream + * @param conversion + * to be performed + * @return the converted stream depending on {@link EolStreamType} + */ + public static OutputStream wrapOutputStream(OutputStream out, + EolStreamType conversion) { + switch (conversion) { + case TEXT_CRLF: + return new AutoCRLFOutputStream(out, false); + case AUTO_CRLF: + return new AutoCRLFOutputStream(out, true); + case TEXT_LF: + return new AutoLFOutputStream(out, false); + case AUTO_LF: + return new AutoLFOutputStream(out, true); + default: + return out; + } + } + + private static EolStreamType checkInStreamType(WorkingTreeOptions options, + Attributes attrs) { + // old git system + if (attrs.isSet("crlf")) {//$NON-NLS-1$ + return EolStreamType.TEXT_LF; + } else if (attrs.isUnset("crlf")) {//$NON-NLS-1$ + return EolStreamType.DIRECT; + } else if ("input".equals(attrs.getValue("crlf"))) {//$NON-NLS-1$ //$NON-NLS-2$ + return EolStreamType.TEXT_LF; + } + + // new git system + if (attrs.isUnset("text")) {//$NON-NLS-1$ + return EolStreamType.DIRECT; + } + String eol = attrs.getValue("eol"); //$NON-NLS-1$ + if (eol != null) + // check-in is always normalized to LF + return EolStreamType.TEXT_LF; + + if (attrs.isSet("text")) { //$NON-NLS-1$ + return EolStreamType.TEXT_LF; + } + + if ("auto".equals(attrs.getValue("text"))) { //$NON-NLS-1$ //$NON-NLS-2$ + return EolStreamType.AUTO_LF; + } + + switch (options.getAutoCRLF()) { + case TRUE: + case INPUT: + return EolStreamType.AUTO_LF; + case FALSE: + return EolStreamType.DIRECT; + } + + return EolStreamType.DIRECT; + } + + private static EolStreamType checkOutStreamType(WorkingTreeOptions options, + Attributes attrs) { + // old git system + if (attrs.isSet("crlf")) {//$NON-NLS-1$ + return FORCE_EOL_LF_ON_CHECKOUT ? EolStreamType.TEXT_LF + : EolStreamType.DIRECT; + } else if (attrs.isUnset("crlf")) {//$NON-NLS-1$ + return EolStreamType.DIRECT; + } else if ("input".equals(attrs.getValue("crlf"))) {//$NON-NLS-1$ //$NON-NLS-2$ + return EolStreamType.DIRECT; + } + + // new git system + if (attrs.isUnset("text")) {//$NON-NLS-1$ + return EolStreamType.DIRECT; + } + String eol = attrs.getValue("eol"); //$NON-NLS-1$ + if (eol != null && "crlf".equals(eol)) //$NON-NLS-1$ + return EolStreamType.TEXT_CRLF; + if (eol != null && "lf".equals(eol)) //$NON-NLS-1$ + return FORCE_EOL_LF_ON_CHECKOUT ? EolStreamType.TEXT_LF + : EolStreamType.DIRECT; + + if (attrs.isSet("text")) { //$NON-NLS-1$ + switch (options.getAutoCRLF()) { + case TRUE: + return EolStreamType.TEXT_CRLF; + default: + // no decision + } + switch (options.getEOL()) { + case CRLF: + return EolStreamType.TEXT_CRLF; + case LF: + return FORCE_EOL_LF_ON_CHECKOUT ? EolStreamType.TEXT_LF + : EolStreamType.DIRECT; + case NATIVE: + default: + return EolStreamType.DIRECT; + } + } + + if ("auto".equals(attrs.getValue("text"))) { //$NON-NLS-1$ //$NON-NLS-2$ + switch (options.getAutoCRLF()) { + case TRUE: + return EolStreamType.AUTO_CRLF; + default: + // no decision + } + switch (options.getEOL()) { + case CRLF: + return EolStreamType.AUTO_CRLF; + case LF: + return FORCE_EOL_LF_ON_CHECKOUT ? EolStreamType.TEXT_LF + : EolStreamType.DIRECT; + case NATIVE: + default: + return EolStreamType.DIRECT; + } + } + + switch (options.getAutoCRLF()) { + case TRUE: + return EolStreamType.AUTO_CRLF; + default: + // no decision + } + + return EolStreamType.DIRECT; + } + +} |