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:
parent
75697adc5a
commit
5d9f595eb8
|
@ -282,6 +282,19 @@ public static String lookup(Object l, String nameTemplate,
|
||||||
return name;
|
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
|
* Waits until it is guaranteed that a subsequent file modification has a
|
||||||
* younger modification timestamp than the modification timestamp of the
|
* younger modification timestamp than the modification timestamp of the
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -52,11 +53,13 @@
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.api.errors.FilterFailedException;
|
||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
import org.eclipse.jgit.api.errors.NoFilepatternException;
|
import org.eclipse.jgit.api.errors.NoFilepatternException;
|
||||||
import org.eclipse.jgit.dircache.DirCache;
|
import org.eclipse.jgit.dircache.DirCache;
|
||||||
import org.eclipse.jgit.dircache.DirCacheBuilder;
|
import org.eclipse.jgit.dircache.DirCacheBuilder;
|
||||||
import org.eclipse.jgit.dircache.DirCacheEntry;
|
import org.eclipse.jgit.dircache.DirCacheEntry;
|
||||||
|
import org.eclipse.jgit.junit.JGitTestUtil;
|
||||||
import org.eclipse.jgit.junit.RepositoryTestCase;
|
import org.eclipse.jgit.junit.RepositoryTestCase;
|
||||||
import org.eclipse.jgit.lib.ConfigConstants;
|
import org.eclipse.jgit.lib.ConfigConstants;
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
|
@ -111,6 +114,191 @@ public void testAddExistingSingleFile() throws IOException, GitAPIException {
|
||||||
indexState(CONTENT));
|
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
|
@Test
|
||||||
public void testAddExistingSingleSmallFileWithNewLine() throws IOException,
|
public void testAddExistingSingleSmallFileWithNewLine() throws IOException,
|
||||||
GitAPIException {
|
GitAPIException {
|
||||||
|
|
|
@ -281,6 +281,8 @@ fileCannotBeDeleted=File cannot be deleted: {0}
|
||||||
fileIsTooBigForThisConvenienceMethod=File is too big for this convenience method ({0} bytes).
|
fileIsTooBigForThisConvenienceMethod=File is too big for this convenience method ({0} bytes).
|
||||||
fileIsTooLarge=File is too large: {0}
|
fileIsTooLarge=File is too large: {0}
|
||||||
fileModeNotSetForPath=FileMode not set for path {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
|
findingGarbage=Finding garbage
|
||||||
flagIsDisposed={0} is disposed.
|
flagIsDisposed={0} is disposed.
|
||||||
flagNotFromThis={0} not from this.
|
flagNotFromThis={0} not from this.
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.api.errors.FilterFailedException;
|
||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
import org.eclipse.jgit.api.errors.JGitInternalException;
|
import org.eclipse.jgit.api.errors.JGitInternalException;
|
||||||
import org.eclipse.jgit.api.errors.NoFilepatternException;
|
import org.eclipse.jgit.api.errors.NoFilepatternException;
|
||||||
|
@ -211,6 +212,9 @@ else if (!(path.equals(lastAddedFile))) {
|
||||||
builder.commit();
|
builder.commit();
|
||||||
setCallable(false);
|
setCallable(false);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
Throwable cause = e.getCause();
|
||||||
|
if (cause != null && cause instanceof FilterFailedException)
|
||||||
|
throw (FilterFailedException) cause;
|
||||||
throw new JGitInternalException(
|
throw new JGitInternalException(
|
||||||
JGitText.get().exceptionCaughtDuringExecutionOfAddCommand, e);
|
JGitText.get().exceptionCaughtDuringExecutionOfAddCommand, e);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -332,7 +332,9 @@ private DirCache createTemporaryIndex(ObjectId headId, DirCache index,
|
||||||
treeWalk.setOperationType(OperationType.CHECKIN_OP);
|
treeWalk.setOperationType(OperationType.CHECKIN_OP);
|
||||||
int dcIdx = treeWalk
|
int dcIdx = treeWalk
|
||||||
.addTree(new DirCacheBuildIterator(existingBuilder));
|
.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;
|
int hIdx = -1;
|
||||||
if (headId != null)
|
if (headId != null)
|
||||||
hIdx = treeWalk.addTree(rw.parseTree(headId));
|
hIdx = treeWalk.addTree(rw.parseTree(headId));
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -983,6 +983,7 @@ private void updateSmudgedEntries() throws IOException {
|
||||||
FileTreeIterator fIter = new FileTreeIterator(repository);
|
FileTreeIterator fIter = new FileTreeIterator(repository);
|
||||||
walk.addTree(iIter);
|
walk.addTree(iIter);
|
||||||
walk.addTree(fIter);
|
walk.addTree(fIter);
|
||||||
|
fIter.setDirCacheIterator(walk, 0);
|
||||||
walk.setRecursive(true);
|
walk.setRecursive(true);
|
||||||
while (walk.next()) {
|
while (walk.next()) {
|
||||||
iIter = walk.getTree(0, DirCacheIterator.class);
|
iIter = walk.getTree(0, DirCacheIterator.class);
|
||||||
|
|
|
@ -340,6 +340,8 @@ public static JGitText get() {
|
||||||
/***/ public String fileIsTooBigForThisConvenienceMethod;
|
/***/ public String fileIsTooBigForThisConvenienceMethod;
|
||||||
/***/ public String fileIsTooLarge;
|
/***/ public String fileIsTooLarge;
|
||||||
/***/ public String fileModeNotSetForPath;
|
/***/ public String fileModeNotSetForPath;
|
||||||
|
/***/ public String filterExecutionFailed;
|
||||||
|
/***/ public String filterExecutionFailedRc;
|
||||||
/***/ public String findingGarbage;
|
/***/ public String findingGarbage;
|
||||||
/***/ public String flagIsDisposed;
|
/***/ public String flagIsDisposed;
|
||||||
/***/ public String flagNotFromThis;
|
/***/ public String flagNotFromThis;
|
||||||
|
|
|
@ -370,6 +370,20 @@ public final class Constants {
|
||||||
*/
|
*/
|
||||||
public static final String DOT_GIT_ATTRIBUTES = ".gitattributes";
|
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 */
|
/** Name of the ignore file */
|
||||||
public static final String DOT_GIT_IGNORE = ".gitignore";
|
public static final String DOT_GIT_IGNORE = ".gitignore";
|
||||||
|
|
||||||
|
|
|
@ -413,6 +413,7 @@ public boolean diff(final ProgressMonitor monitor, int estWorkTreeSize,
|
||||||
treeWalk.addTree(new EmptyTreeIterator());
|
treeWalk.addTree(new EmptyTreeIterator());
|
||||||
treeWalk.addTree(new DirCacheIterator(dirCache));
|
treeWalk.addTree(new DirCacheIterator(dirCache));
|
||||||
treeWalk.addTree(initialWorkingTreeIterator);
|
treeWalk.addTree(initialWorkingTreeIterator);
|
||||||
|
initialWorkingTreeIterator.setDirCacheIterator(treeWalk, 1);
|
||||||
Collection<TreeFilter> filters = new ArrayList<TreeFilter>(4);
|
Collection<TreeFilter> filters = new ArrayList<TreeFilter>(4);
|
||||||
|
|
||||||
if (monitor != null) {
|
if (monitor != null) {
|
||||||
|
|
|
@ -45,22 +45,25 @@
|
||||||
package org.eclipse.jgit.treewalk;
|
package org.eclipse.jgit.treewalk;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.eclipse.jgit.annotations.Nullable;
|
import org.eclipse.jgit.annotations.Nullable;
|
||||||
import org.eclipse.jgit.api.errors.JGitInternalException;
|
import org.eclipse.jgit.api.errors.JGitInternalException;
|
||||||
import org.eclipse.jgit.attributes.Attribute;
|
import org.eclipse.jgit.attributes.Attribute;
|
||||||
|
import org.eclipse.jgit.attributes.Attribute.State;
|
||||||
import org.eclipse.jgit.attributes.Attributes;
|
import org.eclipse.jgit.attributes.Attributes;
|
||||||
import org.eclipse.jgit.attributes.AttributesNode;
|
import org.eclipse.jgit.attributes.AttributesNode;
|
||||||
import org.eclipse.jgit.attributes.AttributesNodeProvider;
|
import org.eclipse.jgit.attributes.AttributesNodeProvider;
|
||||||
import org.eclipse.jgit.attributes.AttributesProvider;
|
import org.eclipse.jgit.attributes.AttributesProvider;
|
||||||
import org.eclipse.jgit.attributes.Attribute.State;
|
|
||||||
import org.eclipse.jgit.dircache.DirCacheIterator;
|
import org.eclipse.jgit.dircache.DirCacheIterator;
|
||||||
import org.eclipse.jgit.errors.CorruptObjectException;
|
import org.eclipse.jgit.errors.CorruptObjectException;
|
||||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
||||||
import org.eclipse.jgit.errors.MissingObjectException;
|
import org.eclipse.jgit.errors.MissingObjectException;
|
||||||
import org.eclipse.jgit.errors.StopWalkException;
|
import org.eclipse.jgit.errors.StopWalkException;
|
||||||
import org.eclipse.jgit.lib.AnyObjectId;
|
import org.eclipse.jgit.lib.AnyObjectId;
|
||||||
|
import org.eclipse.jgit.lib.Config;
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.FileMode;
|
import org.eclipse.jgit.lib.FileMode;
|
||||||
import org.eclipse.jgit.lib.MutableObjectId;
|
import org.eclipse.jgit.lib.MutableObjectId;
|
||||||
|
@ -70,6 +73,7 @@
|
||||||
import org.eclipse.jgit.revwalk.RevTree;
|
import org.eclipse.jgit.revwalk.RevTree;
|
||||||
import org.eclipse.jgit.treewalk.filter.PathFilter;
|
import org.eclipse.jgit.treewalk.filter.PathFilter;
|
||||||
import org.eclipse.jgit.treewalk.filter.TreeFilter;
|
import org.eclipse.jgit.treewalk.filter.TreeFilter;
|
||||||
|
import org.eclipse.jgit.util.QuotedString;
|
||||||
import org.eclipse.jgit.util.RawParseUtils;
|
import org.eclipse.jgit.util.RawParseUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -116,6 +120,12 @@ public static enum OperationType {
|
||||||
*/
|
*/
|
||||||
private OperationType operationType = OperationType.CHECKOUT_OP;
|
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
|
* @param operationType
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
|
@ -259,6 +269,8 @@ public static TreeWalk forPath(final Repository db, final String path,
|
||||||
/** Cached attribute for the current entry */
|
/** Cached attribute for the current entry */
|
||||||
private Attributes attrs = null;
|
private Attributes attrs = null;
|
||||||
|
|
||||||
|
private Config config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new tree walker for a given repository.
|
* 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) {
|
public TreeWalk(final Repository repo) {
|
||||||
this(repo.newObjectReader(), true);
|
this(repo.newObjectReader(), true);
|
||||||
|
config = repo.getConfig();
|
||||||
attributesNodeProvider = repo.createAttributesNodeProvider();
|
attributesNodeProvider = repo.createAttributesNodeProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1308,4 +1321,66 @@ private static AttributesNode getAttributesNode(AttributesNode value,
|
||||||
AttributesNode defaultValue) {
|
AttributesNode defaultValue) {
|
||||||
return (value == null) ? defaultValue : value;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.api.errors.FilterFailedException;
|
||||||
import org.eclipse.jgit.attributes.AttributesNode;
|
import org.eclipse.jgit.attributes.AttributesNode;
|
||||||
import org.eclipse.jgit.attributes.AttributesRule;
|
import org.eclipse.jgit.attributes.AttributesRule;
|
||||||
import org.eclipse.jgit.diff.RawText;
|
import org.eclipse.jgit.diff.RawText;
|
||||||
|
@ -76,6 +77,7 @@
|
||||||
import org.eclipse.jgit.internal.JGitText;
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.CoreConfig;
|
import org.eclipse.jgit.lib.CoreConfig;
|
||||||
|
import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
|
||||||
import org.eclipse.jgit.lib.CoreConfig.CheckStat;
|
import org.eclipse.jgit.lib.CoreConfig.CheckStat;
|
||||||
import org.eclipse.jgit.lib.CoreConfig.SymLinks;
|
import org.eclipse.jgit.lib.CoreConfig.SymLinks;
|
||||||
import org.eclipse.jgit.lib.FileMode;
|
import org.eclipse.jgit.lib.FileMode;
|
||||||
|
@ -85,6 +87,7 @@
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.submodule.SubmoduleWalk;
|
import org.eclipse.jgit.submodule.SubmoduleWalk;
|
||||||
import org.eclipse.jgit.util.FS;
|
import org.eclipse.jgit.util.FS;
|
||||||
|
import org.eclipse.jgit.util.FS.ExecutionResult;
|
||||||
import org.eclipse.jgit.util.IO;
|
import org.eclipse.jgit.util.IO;
|
||||||
import org.eclipse.jgit.util.RawParseUtils;
|
import org.eclipse.jgit.util.RawParseUtils;
|
||||||
import org.eclipse.jgit.util.io.EolCanonicalizingInputStream;
|
import org.eclipse.jgit.util.io.EolCanonicalizingInputStream;
|
||||||
|
@ -101,6 +104,8 @@
|
||||||
* @see FileTreeIterator
|
* @see FileTreeIterator
|
||||||
*/
|
*/
|
||||||
public abstract class WorkingTreeIterator extends AbstractTreeIterator {
|
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[])}. */
|
/** An empty entry array, suitable for {@link #init(Entry[])}. */
|
||||||
protected static final Entry[] EOF = {};
|
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. */
|
/** If there is a .gitignore file present, the parsed rules from it. */
|
||||||
private IgnoreNode ignoreNode;
|
private IgnoreNode ignoreNode;
|
||||||
|
|
||||||
|
private String cleanFilterCommand;
|
||||||
|
|
||||||
/** Repository that is the root level being iterated over */
|
/** Repository that is the root level being iterated over */
|
||||||
protected Repository repository;
|
protected Repository repository;
|
||||||
|
|
||||||
|
@ -186,6 +193,7 @@ protected WorkingTreeIterator(final String prefix,
|
||||||
protected WorkingTreeIterator(final WorkingTreeIterator p) {
|
protected WorkingTreeIterator(final WorkingTreeIterator p) {
|
||||||
super(p);
|
super(p);
|
||||||
state = p.state;
|
state = p.state;
|
||||||
|
repository = p.repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -348,7 +356,8 @@ private byte[] idBufferBlob(final Entry e) {
|
||||||
|
|
||||||
private InputStream possiblyFilteredInputStream(final Entry e,
|
private InputStream possiblyFilteredInputStream(final Entry e,
|
||||||
final InputStream is, final long len) throws IOException {
|
final InputStream is, final long len) throws IOException {
|
||||||
if (!mightNeedCleaning()) {
|
boolean mightNeedCleaning = mightNeedCleaning();
|
||||||
|
if (!mightNeedCleaning) {
|
||||||
canonLen = len;
|
canonLen = len;
|
||||||
return is;
|
return is;
|
||||||
}
|
}
|
||||||
|
@ -366,7 +375,8 @@ private InputStream possiblyFilteredInputStream(final Entry e,
|
||||||
return new ByteArrayInputStream(raw, 0, n);
|
return new ByteArrayInputStream(raw, 0, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isBinary(e)) {
|
// TODO: fix autocrlf causing mightneedcleaning
|
||||||
|
if (!mightNeedCleaning && isBinary(e)) {
|
||||||
canonLen = len;
|
canonLen = len;
|
||||||
return is;
|
return is;
|
||||||
}
|
}
|
||||||
|
@ -390,10 +400,12 @@ private static void safeClose(final InputStream in) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean mightNeedCleaning() {
|
private boolean mightNeedCleaning() throws IOException {
|
||||||
switch (getOptions().getAutoCRLF()) {
|
switch (getOptions().getAutoCRLF()) {
|
||||||
case FALSE:
|
case FALSE:
|
||||||
default:
|
default:
|
||||||
|
if (getCleanFilterCommand() != null)
|
||||||
|
return true;
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
case TRUE:
|
case TRUE:
|
||||||
|
@ -415,8 +427,7 @@ private static boolean isBinary(Entry entry) throws IOException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ByteBuffer filterClean(byte[] src, int n)
|
private ByteBuffer filterClean(byte[] src, int n) throws IOException {
|
||||||
throws IOException {
|
|
||||||
InputStream in = new ByteArrayInputStream(src);
|
InputStream in = new ByteArrayInputStream(src);
|
||||||
try {
|
try {
|
||||||
return IO.readWholeStream(filterClean(in), n);
|
return IO.readWholeStream(filterClean(in), n);
|
||||||
|
@ -425,8 +436,42 @@ private static ByteBuffer filterClean(byte[] src, int n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static InputStream filterClean(InputStream in) {
|
private InputStream filterClean(InputStream in) throws IOException {
|
||||||
return new EolCanonicalizingInputStream(in, true);
|
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);
|
System.arraycopy(e.encodedName, 0, path, pathOffset, nameLen);
|
||||||
pathLen = pathOffset + nameLen;
|
pathLen = pathOffset + nameLen;
|
||||||
canonLen = -1;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -246,6 +246,37 @@ public byte[] toByteArray() throws IOException {
|
||||||
return out;
|
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.
|
* Send this buffer to an output stream.
|
||||||
* <p>
|
* <p>
|
||||||
|
|
Loading…
Reference in New Issue