Add support for clean filters

When filters are defined for certain paths in gitattributes make
sure that clean filters are processed when adding new content to the
object database.

Change-Id: Iffd72914cec5b434ba4d0de232e285b7492db868
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
This commit is contained in:
Christian Halstrick 2015-10-28 13:25:09 +01:00 committed by Matthias Sohn
parent 75697adc5a
commit 5d9f595eb8
13 changed files with 546 additions and 9 deletions

View File

@ -282,6 +282,19 @@ public static String lookup(Object l, String nameTemplate,
return name;
}
/**
* Replaces '\' by '/'
*
* @param str
* the string in which backslashes should be replaced
* @return the resulting string with slashes
* @since 4.2
*/
public static String slashify(String str) {
str = str.replace('\\', '/');
return str;
}
/**
* Waits until it is guaranteed that a subsequent file modification has a
* younger modification timestamp than the modification timestamp of the

View File

@ -45,6 +45,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
@ -52,11 +53,13 @@
import java.io.IOException;
import java.io.PrintWriter;
import org.eclipse.jgit.api.errors.FilterFailedException;
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.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
@ -111,6 +114,191 @@ public void testAddExistingSingleFile() throws IOException, GitAPIException {
indexState(CONTENT));
}
@Test
public void testCleanFilter() throws IOException,
GitAPIException {
writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
writeTrashFile("src/a.tmp", "foo");
// Caution: we need a trailing '\n' since sed on mac always appends
// linefeeds if missing
writeTrashFile("src/a.txt", "foo\n");
File script = writeTempFile("sed s/o/e/g");
Git git = new Git(db);
StoredConfig config = git.getRepository().getConfig();
config.setString("filter", "tstFilter", "clean",
"sh " + slashify(script.getPath()));
config.save();
git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp")
.call();
assertEquals(
"[src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:fee\n]",
indexState(CONTENT));
}
@Test
public void testCleanFilterEnvironment()
throws IOException, GitAPIException {
writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
writeTrashFile("src/a.txt", "foo");
File script = writeTempFile("echo $GIT_DIR; echo 1 >xyz");
Git git = new Git(db);
StoredConfig config = git.getRepository().getConfig();
config.setString("filter", "tstFilter", "clean",
"sh " + slashify(script.getPath()));
config.save();
git.add().addFilepattern("src/a.txt").call();
String gitDir = db.getDirectory().getAbsolutePath();
assertEquals("[src/a.txt, mode:100644, content:" + gitDir
+ "\n]", indexState(CONTENT));
assertTrue(new File(db.getWorkTree(), "xyz").exists());
}
@Test
public void testMultipleCleanFilter() throws IOException, GitAPIException {
writeTrashFile(".gitattributes",
"*.txt filter=tstFilter\n*.tmp filter=tstFilter2");
// Caution: we need a trailing '\n' since sed on mac always appends
// linefeeds if missing
writeTrashFile("src/a.tmp", "foo\n");
writeTrashFile("src/a.txt", "foo\n");
File script = writeTempFile("sed s/o/e/g");
File script2 = writeTempFile("sed s/f/x/g");
Git git = new Git(db);
StoredConfig config = git.getRepository().getConfig();
config.setString("filter", "tstFilter", "clean",
"sh " + slashify(script.getPath()));
config.setString("filter", "tstFilter2", "clean",
"sh " + slashify(script2.getPath()));
config.save();
git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp")
.call();
assertEquals(
"[src/a.tmp, mode:100644, content:xoo\n][src/a.txt, mode:100644, content:fee\n]",
indexState(CONTENT));
// TODO: multiple clean filters for one file???
}
/**
* The path of an added file name contains ';' and afterwards malicious
* commands. Make sure when calling filter commands to properly escape the
* filenames
*
* @throws IOException
* @throws GitAPIException
*/
@Test
public void testCommandInjection() throws IOException, GitAPIException {
// Caution: we need a trailing '\n' since sed on mac always appends
// linefeeds if missing
writeTrashFile("; echo virus", "foo\n");
File script = writeTempFile("sed s/o/e/g");
Git git = new Git(db);
StoredConfig config = git.getRepository().getConfig();
config.setString("filter", "tstFilter", "clean",
"sh " + slashify(script.getPath()) + " %f");
writeTrashFile(".gitattributes", "* filter=tstFilter");
git.add().addFilepattern("; echo virus").call();
// Without proper escaping the content would be "feovirus". The sed
// command and the "echo virus" would contribute to the content
assertEquals("[; echo virus, mode:100644, content:fee\n]",
indexState(CONTENT));
}
@Test
public void testBadCleanFilter() throws IOException, GitAPIException {
writeTrashFile("a.txt", "foo");
File script = writeTempFile("sedfoo s/o/e/g");
Git git = new Git(db);
StoredConfig config = git.getRepository().getConfig();
config.setString("filter", "tstFilter", "clean",
"sh " + script.getPath());
config.save();
writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
try {
git.add().addFilepattern("a.txt").call();
fail("Didn't received the expected exception");
} catch (FilterFailedException e) {
assertEquals(127, e.getReturnCode());
}
}
@Test
public void testBadCleanFilter2() throws IOException, GitAPIException {
writeTrashFile("a.txt", "foo");
File script = writeTempFile("sed s/o/e/g");
Git git = new Git(db);
StoredConfig config = git.getRepository().getConfig();
config.setString("filter", "tstFilter", "clean",
"shfoo " + script.getPath());
config.save();
writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
try {
git.add().addFilepattern("a.txt").call();
fail("Didn't received the expected exception");
} catch (FilterFailedException e) {
assertEquals(127, e.getReturnCode());
}
}
@Test
public void testCleanFilterReturning12() throws IOException,
GitAPIException {
writeTrashFile("a.txt", "foo");
File script = writeTempFile("exit 12");
Git git = new Git(db);
StoredConfig config = git.getRepository().getConfig();
config.setString("filter", "tstFilter", "clean",
"sh " + slashify(script.getPath()));
config.save();
writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
try {
git.add().addFilepattern("a.txt").call();
fail("Didn't received the expected exception");
} catch (FilterFailedException e) {
assertEquals(12, e.getReturnCode());
}
}
@Test
public void testNotApplicableFilter() throws IOException, GitAPIException {
writeTrashFile("a.txt", "foo");
File script = writeTempFile("sed s/o/e/g");
Git git = new Git(db);
StoredConfig config = git.getRepository().getConfig();
config.setString("filter", "tstFilter", "something",
"sh " + script.getPath());
config.save();
writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
git.add().addFilepattern("a.txt").call();
assertEquals("[a.txt, mode:100644, content:foo]", indexState(CONTENT));
}
private File writeTempFile(String body) throws IOException {
File f = File.createTempFile("AddCommandTest_", "");
JGitTestUtil.write(f, body);
return f;
}
@Test
public void testAddExistingSingleSmallFileWithNewLine() throws IOException,
GitAPIException {

View File

@ -281,6 +281,8 @@ fileCannotBeDeleted=File cannot be deleted: {0}
fileIsTooBigForThisConvenienceMethod=File is too big for this convenience method ({0} bytes).
fileIsTooLarge=File is too large: {0}
fileModeNotSetForPath=FileMode not set for path {0}
filterExecutionFailed=Execution of filter command ''{0}'' on file ''{1}'' failed
filterExecutionFailedRc=Execution of filter command ''{0}'' on file ''{1}'' failed with return code ''{2}'', message on stderr: ''{3}''
findingGarbage=Finding garbage
flagIsDisposed={0} is disposed.
flagNotFromThis={0} not from this.

View File

@ -48,6 +48,7 @@
import java.util.Collection;
import java.util.LinkedList;
import org.eclipse.jgit.api.errors.FilterFailedException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.NoFilepatternException;
@ -211,6 +212,9 @@ else if (!(path.equals(lastAddedFile))) {
builder.commit();
setCallable(false);
} catch (IOException e) {
Throwable cause = e.getCause();
if (cause != null && cause instanceof FilterFailedException)
throw (FilterFailedException) cause;
throw new JGitInternalException(
JGitText.get().exceptionCaughtDuringExecutionOfAddCommand, e);
} finally {

View File

@ -332,7 +332,9 @@ private DirCache createTemporaryIndex(ObjectId headId, DirCache index,
treeWalk.setOperationType(OperationType.CHECKIN_OP);
int dcIdx = treeWalk
.addTree(new DirCacheBuildIterator(existingBuilder));
int fIdx = treeWalk.addTree(new FileTreeIterator(repo));
FileTreeIterator fti = new FileTreeIterator(repo);
fti.setDirCacheIterator(treeWalk, 0);
int fIdx = treeWalk.addTree(fti);
int hIdx = -1;
if (headId != null)
hIdx = treeWalk.addTree(rw.parseTree(headId));

View File

@ -0,0 +1,144 @@
/*
* Copyright (C) 2015, Christian Halstrick <christian.halstrick@sap.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.api.errors;
import java.text.MessageFormat;
import org.eclipse.jgit.internal.JGitText;
/**
* Exception thrown when the execution of a filter command failed
*
* @since 4.2
*/
public class FilterFailedException extends GitAPIException {
private static final long serialVersionUID = 1L;
private String filterCommand;
private String path;
private byte[] stdout;
private String stderr;
private int rc;
/**
* Thrown if during execution of filter command an exception occurred
*
* @param cause
* the exception
* @param filterCommand
* the command which failed
* @param path
* the path processed by the filter
*/
public FilterFailedException(Exception cause, String filterCommand,
String path) {
super(MessageFormat.format(JGitText.get().filterExecutionFailed,
filterCommand, path), cause);
this.filterCommand = filterCommand;
this.path = path;
}
/**
* Thrown if a filter command returns a non-zero return code
*
* @param rc
* the return code
* @param filterCommand
* the command which failed
* @param path
* the path processed by the filter
* @param stdout
* the output the filter generated so far. This should be limited
* to reasonable size.
* @param stderr
* the stderr output of the filter
*/
@SuppressWarnings("boxing")
public FilterFailedException(int rc, String filterCommand, String path,
byte[] stdout, String stderr) {
super(MessageFormat.format(JGitText.get().filterExecutionFailedRc,
filterCommand, path, rc, stderr));
this.rc = rc;
this.filterCommand = filterCommand;
this.path = path;
this.stdout = stdout;
this.stderr = stderr;
}
/**
* @return the filterCommand
*/
public String getFilterCommand() {
return filterCommand;
}
/**
* @return the path of the file processed by the filter command
*/
public String getPath() {
return path;
}
/**
* @return the output generated by the filter command. Might be truncated to
* limit memory consumption.
*/
public byte[] getOutput() {
return stdout;
}
/**
* @return the error output returned by the filter command
*/
public String getError() {
return stderr;
}
/**
* @return the return code returned by the filter command
*/
public int getReturnCode() {
return rc;
}
}

View File

@ -983,6 +983,7 @@ private void updateSmudgedEntries() throws IOException {
FileTreeIterator fIter = new FileTreeIterator(repository);
walk.addTree(iIter);
walk.addTree(fIter);
fIter.setDirCacheIterator(walk, 0);
walk.setRecursive(true);
while (walk.next()) {
iIter = walk.getTree(0, DirCacheIterator.class);

View File

@ -340,6 +340,8 @@ public static JGitText get() {
/***/ public String fileIsTooBigForThisConvenienceMethod;
/***/ public String fileIsTooLarge;
/***/ public String fileModeNotSetForPath;
/***/ public String filterExecutionFailed;
/***/ public String filterExecutionFailedRc;
/***/ public String findingGarbage;
/***/ public String flagIsDisposed;
/***/ public String flagNotFromThis;

View File

@ -370,6 +370,20 @@ public final class Constants {
*/
public static final String DOT_GIT_ATTRIBUTES = ".gitattributes";
/**
* Key for filters in .gitattributes
*
* @since 4.2
*/
public static final String ATTR_FILTER = "filter";
/**
* clean command name, used to call filter driver
*
* @since 4.2
*/
public static final String ATTR_FILTER_TYPE_CLEAN = "clean";
/** Name of the ignore file */
public static final String DOT_GIT_IGNORE = ".gitignore";

View File

@ -413,6 +413,7 @@ public boolean diff(final ProgressMonitor monitor, int estWorkTreeSize,
treeWalk.addTree(new EmptyTreeIterator());
treeWalk.addTree(new DirCacheIterator(dirCache));
treeWalk.addTree(initialWorkingTreeIterator);
initialWorkingTreeIterator.setDirCacheIterator(treeWalk, 1);
Collection<TreeFilter> filters = new ArrayList<TreeFilter>(4);
if (monitor != null) {

View File

@ -45,22 +45,25 @@
package org.eclipse.jgit.treewalk;
import java.io.IOException;
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.Attribute.State;
import org.eclipse.jgit.attributes.Attributes;
import org.eclipse.jgit.attributes.AttributesNode;
import org.eclipse.jgit.attributes.AttributesNodeProvider;
import org.eclipse.jgit.attributes.AttributesProvider;
import org.eclipse.jgit.attributes.Attribute.State;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
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.FileMode;
import org.eclipse.jgit.lib.MutableObjectId;
@ -70,6 +73,7 @@
import org.eclipse.jgit.revwalk.RevTree;
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;
/**
@ -116,6 +120,12 @@ public static enum OperationType {
*/
private OperationType operationType = OperationType.CHECKOUT_OP;
/**
* The filter command as defined in gitattributes. The keys are
* filterName+"."+filterCommandType. E.g. "lfs.clean"
*/
private Map<String, String> filterCommandsByNameDotType = new HashMap<String, String>();
/**
* @param operationType
* @since 4.2
@ -259,6 +269,8 @@ public static TreeWalk forPath(final Repository db, final String path,
/** Cached attribute for the current entry */
private Attributes attrs = null;
private Config config;
/**
* Create a new tree walker for a given repository.
*
@ -269,6 +281,7 @@ public static TreeWalk forPath(final Repository db, final String path,
*/
public TreeWalk(final Repository repo) {
this(repo.newObjectReader(), true);
config = repo.getConfig();
attributesNodeProvider = repo.createAttributesNodeProvider();
}
@ -1308,4 +1321,66 @@ private static AttributesNode getAttributesNode(AttributesNode value,
AttributesNode defaultValue) {
return (value == null) ? defaultValue : value;
}
/**
* Inspect config and attributes to return a filtercommand applicable for
* the current path
*
* @param filterCommandType
* which type of filterCommand should be executed. E.g. "clean",
* "smudge"
* @return a filter command
* @throws IOException
* @since 4.2
*/
public String getFilterCommand(String filterCommandType)
throws IOException {
Attributes attributes = getAttributes();
Attribute f = attributes.get(Constants.ATTR_FILTER);
if (f == null) {
return null;
}
String filterValue = f.getValue();
if (filterValue == null) {
return null;
}
String filterCommand = getFilterCommandDefinition(filterValue,
filterCommandType);
if (filterCommand == null) {
return null;
}
return filterCommand.replaceAll("%f", //$NON-NLS-1$
QuotedString.BOURNE.quote((getPathString())));
}
/**
* Get the filter command how it is defined in gitconfig. The returned
* string may contain "%f" which needs to be replaced by the current path
* before executing the filter command. These filter definitions are cached
* for better performance.
*
* @param filterDriverName
* The name of the filter driver as it is referenced in the
* gitattributes file. E.g. "lfs". For each filter driver there
* may be many commands defined in the .gitconfig
* @param filterCommandType
* The type of the filter command for a specific filter driver.
* May be "clean" or "smudge".
* @return the definition of the command to be executed for this filter
* driver and filter command
*/
private String getFilterCommandDefinition(String filterDriverName,
String filterCommandType) {
String key = filterDriverName + "." + filterCommandType; //$NON-NLS-1$
String filterCommand = filterCommandsByNameDotType.get(key);
if (filterCommand != null)
return filterCommand;
filterCommand = config.getString(Constants.ATTR_FILTER,
filterDriverName, filterCommandType);
if (filterCommand != null)
filterCommandsByNameDotType.put(key, filterCommand);
return filterCommand;
}
}

View File

@ -62,6 +62,7 @@
import java.util.Collections;
import java.util.Comparator;
import org.eclipse.jgit.api.errors.FilterFailedException;
import org.eclipse.jgit.attributes.AttributesNode;
import org.eclipse.jgit.attributes.AttributesRule;
import org.eclipse.jgit.diff.RawText;
@ -76,6 +77,7 @@
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.SymLinks;
import org.eclipse.jgit.lib.FileMode;
@ -85,6 +87,7 @@
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.IO;
import org.eclipse.jgit.util.RawParseUtils;
import org.eclipse.jgit.util.io.EolCanonicalizingInputStream;
@ -101,6 +104,8 @@
* @see FileTreeIterator
*/
public abstract class WorkingTreeIterator extends AbstractTreeIterator {
private static final int MAX_EXCEPTION_TEXT_SIZE = 10 * 1024;
/** An empty entry array, suitable for {@link #init(Entry[])}. */
protected static final Entry[] EOF = {};
@ -134,6 +139,8 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
/** If there is a .gitignore file present, the parsed rules from it. */
private IgnoreNode ignoreNode;
private String cleanFilterCommand;
/** Repository that is the root level being iterated over */
protected Repository repository;
@ -186,6 +193,7 @@ protected WorkingTreeIterator(final String prefix,
protected WorkingTreeIterator(final WorkingTreeIterator p) {
super(p);
state = p.state;
repository = p.repository;
}
/**
@ -348,7 +356,8 @@ private byte[] idBufferBlob(final Entry e) {
private InputStream possiblyFilteredInputStream(final Entry e,
final InputStream is, final long len) throws IOException {
if (!mightNeedCleaning()) {
boolean mightNeedCleaning = mightNeedCleaning();
if (!mightNeedCleaning) {
canonLen = len;
return is;
}
@ -366,7 +375,8 @@ private InputStream possiblyFilteredInputStream(final Entry e,
return new ByteArrayInputStream(raw, 0, n);
}
if (isBinary(e)) {
// TODO: fix autocrlf causing mightneedcleaning
if (!mightNeedCleaning && isBinary(e)) {
canonLen = len;
return is;
}
@ -390,10 +400,12 @@ private static void safeClose(final InputStream in) {
}
}
private boolean mightNeedCleaning() {
private boolean mightNeedCleaning() throws IOException {
switch (getOptions().getAutoCRLF()) {
case FALSE:
default:
if (getCleanFilterCommand() != null)
return true;
return false;
case TRUE:
@ -415,8 +427,7 @@ private static boolean isBinary(Entry entry) throws IOException {
}
}
private static ByteBuffer filterClean(byte[] src, int n)
throws IOException {
private ByteBuffer filterClean(byte[] src, int n) throws IOException {
InputStream in = new ByteArrayInputStream(src);
try {
return IO.readWholeStream(filterClean(in), n);
@ -425,8 +436,42 @@ private static ByteBuffer filterClean(byte[] src, int n)
}
}
private static InputStream filterClean(InputStream in) {
return new EolCanonicalizingInputStream(in, true);
private InputStream filterClean(InputStream in) throws IOException {
in = handleAutoCRLF(in);
String filterCommand = getCleanFilterCommand();
if (filterCommand != null) {
FS fs = repository.getFS();
ProcessBuilder filterProcessBuilder = fs.runInShell(filterCommand,
new String[0]);
filterProcessBuilder.directory(repository.getWorkTree());
filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY,
repository.getDirectory().getAbsolutePath());
ExecutionResult result;
try {
result = fs.execute(filterProcessBuilder, in);
} catch (IOException | InterruptedException e) {
throw new IOException(new FilterFailedException(e,
filterCommand, getEntryPathString()));
}
int rc = result.getRc();
if (rc != 0) {
throw new IOException(new FilterFailedException(rc,
filterCommand, getEntryPathString(),
result.getStdout().toByteArray(MAX_EXCEPTION_TEXT_SIZE),
RawParseUtils.decode(result.getStderr()
.toByteArray(MAX_EXCEPTION_TEXT_SIZE))));
}
return result.getStdout().openInputStream();
}
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;
}
/**
@ -485,6 +530,7 @@ private void parseEntry() {
System.arraycopy(e.encodedName, 0, path, pathOffset, nameLen);
pathLen = pathOffset + nameLen;
canonLen = -1;
cleanFilterCommand = null;
}
/**
@ -1271,4 +1317,18 @@ void initializeDigestAndReadBuffer() {
}
}
}
/**
* @return the clean filter command for the current entry or
* <code>null</code> if no such command is defined
* @throws IOException
* @since 4.2
*/
public String getCleanFilterCommand() throws IOException {
if (cleanFilterCommand == null && state.walk != null) {
cleanFilterCommand = state.walk
.getFilterCommand(Constants.ATTR_FILTER_TYPE_CLEAN);
}
return cleanFilterCommand;
}
}

View File

@ -246,6 +246,37 @@ public byte[] toByteArray() throws IOException {
return out;
}
/**
* Convert this buffer's contents into a contiguous byte array. If this size
* of the buffer exceeds the limit only return the first {@code limit} bytes
* <p>
* The buffer is only complete after {@link #close()} has been invoked.
*
* @param limit
* the maximum number of bytes to be returned
*
* @return the byte array limited to {@code limit} bytes.
* @throws IOException
* an error occurred reading from a local temporary file
* @throws OutOfMemoryError
* the buffer cannot fit in memory
*
* @since 4.2
*/
public byte[] toByteArray(int limit) throws IOException {
final long len = Math.min(length(), limit);
if (Integer.MAX_VALUE < len)
throw new OutOfMemoryError(
JGitText.get().lengthExceedsMaximumArraySize);
final byte[] out = new byte[(int) len];
int outPtr = 0;
for (final Block b : blocks) {
System.arraycopy(b.buffer, 0, out, outPtr, b.count);
outPtr += b.count;
}
return out;
}
/**
* Send this buffer to an output stream.
* <p>