Add built-in LFS clean filter

Adds a JGit built-in implementation of the "git lfs clean" filter. This
filter should do the same as the one described in [1]. But since this
filter is written in Java and can be called by JGit without forking new
processes it should be much faster

[1]
https://github.com/github/git-lfs/blob/master/docs/man/git-lfs-clean.1.ronn

Change-Id: If60e387e97870245b4bd765eda6717eb84cffb1d
This commit is contained in:
Christian Halstrick 2016-06-27 16:00:09 +02:00 committed by Matthias Sohn
parent b70f3a7457
commit 45ee55d0d9
12 changed files with 616 additions and 7 deletions

View File

@ -5,11 +5,13 @@ Bundle-SymbolicName: org.eclipse.jgit.lfs
Bundle-Version: 4.6.0.qualifier
Bundle-Localization: plugin
Bundle-Vendor: %provider_name
Export-Package: org.eclipse.jgit.lfs.errors;version="4.6.0",
Export-Package: org.eclipse.jgit.lfs;version="4.6.0",
org.eclipse.jgit.lfs.errors;version="4.6.0",
org.eclipse.jgit.lfs.internal;version="4.6.0";x-friends:="org.eclipse.jgit.lfs.test",
org.eclipse.jgit.lfs.lib;version="4.6.0"
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Import-Package: org.eclipse.jgit.internal.storage.file;version="[4.6.0,4.7.0)",
Import-Package: org.eclipse.jgit.attributes;version="[4.6.0,4.7.0)",
org.eclipse.jgit.internal.storage.file;version="[4.6.0,4.7.0)",
org.eclipse.jgit.lib;version="[4.6.0,4.7.0)",
org.eclipse.jgit.nls;version="[4.6.0,4.7.0)",
org.eclipse.jgit.util;version="[4.6.0,4.7.0)"

View File

@ -1,7 +1,8 @@
incorrectLONG_OBJECT_ID_LENGTH=Incorrect LONG_OBJECT_ID_LENGTH.
inconsistentMediafileLength=mediafile {0} has unexpected length; expected {1} but found {2}.
invalidLongId=Invalid id: {0}
invalidLongIdLength=Invalid id length {0}; should be {1}
requiredHashFunctionNotAvailable=Required hash function {0} not available.
repositoryNotFound=Repository {0} not found
repositoryReadOnly=Repository {0} is read-only
lfsUnavailable=LFS is not available for repository {0}
lfsUnavailable=LFS is not available for repository {0}

View File

@ -0,0 +1,176 @@
/*
* Copyright (C) 2016, 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.lfs;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.DigestOutputStream;
import org.eclipse.jgit.attributes.FilterCommand;
import org.eclipse.jgit.attributes.FilterCommandFactory;
import org.eclipse.jgit.attributes.FilterCommandRegistry;
import org.eclipse.jgit.lfs.errors.CorruptMediaFile;
import org.eclipse.jgit.lfs.lib.Constants;
import org.eclipse.jgit.lfs.lib.LongObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.FileUtils;
/**
* Built-in LFS clean filter
*
* When new content is about to be added to the git repository and this filter
* is configured for that content, then this filter will replace the original
* content with content of a so-called LFS pointer file. The pointer file
* content will then be added to the git repository. Additionally this filter
* writes the original content in a so-called 'media file' to '.git/lfs/objects/
* <first-two-characters-of-contentid>/<rest-of-contentid>'
*
* @see <a href="https://github.com/github/git-lfs/blob/master/docs/spec.md">Git
* LFS Specification</a>
* @since 4.6
*/
public class CleanFilter extends FilterCommand {
/**
* The factory is responsible for creating instances of {@link CleanFilter}
*/
public final static FilterCommandFactory FACTORY = new FilterCommandFactory() {
@Override
public FilterCommand create(Repository db, InputStream in,
OutputStream out) throws IOException {
return new CleanFilter(db, in, out);
}
};
/**
* Registers this filter by calling
* {@link FilterCommandRegistry#register(String, FilterCommandFactory)}
*/
public final static void register() {
FilterCommandRegistry.register(
org.eclipse.jgit.lib.Constants.BUILTIN_FILTER_PREFIX
+ "lfs/clean", //$NON-NLS-1$
FACTORY);
}
// The OutputStream to a temporary file which will be renamed to mediafile
// when the operation succeeds
private OutputStream tmpOut;
// Used to compute the hash for the original content
private DigestOutputStream dOut;
private Lfs lfsUtil;
// the size of the original content
private long size;
// a temporary file into which the original content is written. When no
// errors occur this file will be renamed to the mediafile
private Path tmpFile;
/**
* @param db
* the repository
* @param in
* an {@link InputStream} providing the original content
* @param out
* the {@link OutputStream} into which the content of the pointer
* file should be written. That's the content which will be added
* to the git repository
* @throws IOException
* when the creation of the temporary file fails or when no
* {@link OutputStream} for this file can be created
*/
public CleanFilter(Repository db, InputStream in, OutputStream out)
throws IOException {
super(in, out);
lfsUtil = new Lfs(db.getDirectory().toPath().resolve("lfs")); //$NON-NLS-1$
Files.createDirectories(lfsUtil.getLfsTmpDir());
tmpFile = lfsUtil.createTmpFile();
tmpOut = Files.newOutputStream(tmpFile,
StandardOpenOption.CREATE);
this.dOut = new DigestOutputStream(
tmpOut,
Constants.newMessageDigest());
}
public int run() throws IOException {
try {
int b = in.read();
if (b != -1) {
dOut.write(b);
size++;
return 1;
} else {
dOut.close();
tmpOut.close();
LongObjectId loid = LongObjectId
.fromRaw(dOut.getMessageDigest().digest());
Path mediaFile = lfsUtil.getMediaFile(loid);
if (Files.isRegularFile(mediaFile)) {
long fsSize = Files.size(mediaFile);
if (fsSize != size) {
throw new CorruptMediaFile(mediaFile, size, fsSize);
}
} else {
FileUtils.mkdirs(mediaFile.getParent().toFile(), true);
FileUtils.rename(tmpFile.toFile(), mediaFile.toFile());
}
LfsPointer lfsPointer = new LfsPointer(loid, size);
lfsPointer.encode(out);
out.close();
return -1;
}
} catch (IOException e) {
out.close();
dOut.close();
tmpOut.close();
throw e;
}
}
}

View File

@ -0,0 +1,124 @@
/*
* Copyright (C) 2016, 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.lfs;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.eclipse.jgit.lfs.lib.LongObjectId;
/**
* Class which represents the lfs folder hierarchy inside a .git folder
*
* @since 4.6
*/
public class Lfs {
private Path root;
private Path objDir;
private Path tmpDir;
/**
* @param root
* the path to the LFS media directory. Will be "<repo>/.git/lfs"
*/
public Lfs(Path root) {
this.root = root;
}
/**
* @return the path to the LFS directory
*/
public Path getLfsRoot() {
return root;
}
/**
* @return the path to the temp directory used by LFS. Will be
* "<repo>/.git/lfs/tmp"
*/
public Path getLfsTmpDir() {
if (tmpDir == null) {
tmpDir = root.resolve("tmp"); //$NON-NLS-1$
}
return tmpDir;
}
/**
* @return the path to the object directory used by LFS. Will be
* "<repo>/.git/lfs/objects"
*/
public Path getLfsObjDir() {
if (objDir == null) {
objDir = root.resolve("objects"); //$NON-NLS-1$
}
return objDir;
}
/**
* @param id
* the id of the mediafile
* @return the file which stores the original content. This will be files
* underneath
* "<repo>/.git/lfs/objects/<firstTwoLettersOfID>/<remainingLettersOfID>"
*/
public Path getMediaFile(LongObjectId id) {
String idStr = LongObjectId.toString(id);
return getLfsObjDir().resolve(idStr.substring(0, 2))
.resolve(idStr.substring(2));
}
/**
* Create a new temp file in the LFS directory
*
* @return a new temporary file in the LFS directory
* @throws IOException
* when the temp file could not be created
*/
public Path createTmpFile() throws IOException {
return Files.createTempFile(getLfsTmpDir(), null, null);
}
}

View File

@ -0,0 +1,121 @@
/*
* Copyright (C) 2016, 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.lfs;
import java.io.OutputStream;
import java.io.PrintStream;
import org.eclipse.jgit.lfs.lib.Constants;
import org.eclipse.jgit.lfs.lib.LongObjectId;
/**
* Represents an LFS pointer file
*
* @since 4.6
*/
public class LfsPointer {
/**
* The version of the LfsPointer file format
*/
public static final String VERSION = "https://git-lfs.github.com/spec/v1"; //$NON-NLS-1$
/**
* The name of the hash function as used in the pointer files. This will
* evaluate to "sha256"
*/
public static final String HASH_FUNCTION_NAME = Constants.LONG_HASH_FUNCTION
.toLowerCase().replace("-", ""); //$NON-NLS-1$ //$NON-NLS-2$
private LongObjectId oid;
private long size;
/**
* @param oid
* the id of the content
* @param size
* the size of the content
*/
public LfsPointer(LongObjectId oid, long size) {
this.oid = oid;
this.size = size;
}
/**
* @return the id of the content
*/
public LongObjectId getOid() {
return oid;
}
/**
* @return the size of the content
*/
public long getSize() {
return size;
}
/**
* Encode this object into the LFS format defined by {@link #VERSION}
*
* @param out
* the {@link OutputStream} into which the encoded data should be
* written
*/
public void encode(OutputStream out) {
try (PrintStream ps = new PrintStream(out)) {
ps.print("version "); //$NON-NLS-1$
ps.println(VERSION);
ps.print("oid " + HASH_FUNCTION_NAME + ":"); //$NON-NLS-1$ //$NON-NLS-2$
ps.println(LongObjectId.toString(oid));
ps.print("size "); //$NON-NLS-1$
ps.println(size);
}
}
@Override
public String toString() {
return "LfsPointer: oid=" + LongObjectId.toString(oid) + ", size=" //$NON-NLS-1$ //$NON-NLS-2$
+ size;
}
}

View File

@ -0,0 +1,100 @@
/*
* Copyright (C) 2016, 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.lfs.errors;
import java.io.IOException;
import java.nio.file.Path;
import java.text.MessageFormat;
import org.eclipse.jgit.lfs.internal.LfsText;
/**
* Thrown when a LFS mediafile is found which doesn't have the expected size
*
* @since 4.6
*/
public class CorruptMediaFile extends IOException {
private static final long serialVersionUID = 1L;
private Path mediaFile;
private long expectedSize;
private long size;
/**
* @param mediaFile
* @param expectedSize
* @param size
*/
@SuppressWarnings("boxing")
public CorruptMediaFile(Path mediaFile, long expectedSize,
long size) {
super(MessageFormat.format(LfsText.get().inconsistentMediafileLength,
mediaFile, expectedSize, size));
this.mediaFile = mediaFile;
this.expectedSize = expectedSize;
this.size = size;
}
/**
* @return the media file which seems to be corrupt
*/
public Path getMediaFile() {
return mediaFile;
}
/**
* @return the expected size of the media file
*/
public long getExpectedSize() {
return expectedSize;
}
/**
* @return the actual size of the media file in the file system
*/
public long getSize() {
return size;
}
}

View File

@ -58,6 +58,7 @@ public static LfsText get() {
}
// @formatter:off
/***/ public String inconsistentMediafileLength;
/***/ public String incorrectLONG_OBJECT_ID_LENGTH;
/***/ public String invalidLongId;
/***/ public String invalidLongIdLength;

View File

@ -55,8 +55,12 @@
**/
@SuppressWarnings("nls")
public final class Constants {
/** Hash function used natively by Git LFS extension for large objects. */
private static final String LONG_HASH_FUNCTION = "SHA-256";
/**
* Hash function used natively by Git LFS extension for large objects.
*
* @since 4.6
*/
public static final String LONG_HASH_FUNCTION = "SHA-256";
/**
* A Git LFS large object hash is 256 bits, i.e. 32 bytes.

View File

@ -39,6 +39,7 @@ Import-Package: javax.servlet;version="[3.1.0,4.0.0)",
org.eclipse.jgit.internal.storage.file;version="[4.6.0,4.7.0)",
org.eclipse.jgit.internal.storage.pack;version="[4.6.0,4.7.0)",
org.eclipse.jgit.internal.storage.reftree;version="[4.6.0,4.7.0)",
org.eclipse.jgit.lfs;version="[4.6.0,4.7.0)",
org.eclipse.jgit.lfs.lib;version="[4.6.0,4.7.0)",
org.eclipse.jgit.lfs.server;version="[4.6.0,4.7.0)",
org.eclipse.jgit.lfs.server.fs;version="[4.6.0,4.7.0)",

View File

@ -57,6 +57,7 @@
import org.eclipse.jgit.awtui.AwtAuthenticator;
import org.eclipse.jgit.awtui.AwtCredentialsProvider;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lfs.CleanFilter;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryBuilder;
import org.eclipse.jgit.pgm.internal.CLIText;
@ -97,6 +98,7 @@ public class Main {
*/
public Main() {
HttpTransport.setConnectionFactory(new HttpClientConnectionFactory());
CleanFilter.register();
}
/**

View File

@ -28,6 +28,7 @@ Import-Package: com.googlecode.javaewah;version="[0.7.9,0.8.0)",
org.eclipse.jgit.internal.storage.pack;version="[4.6.0,4.7.0)",
org.eclipse.jgit.internal.storage.reftree;version="[4.6.0,4.7.0)",
org.eclipse.jgit.junit;version="[4.6.0,4.7.0)",
org.eclipse.jgit.lfs;version="[4.6.0,4.7.0)",
org.eclipse.jgit.lib;version="[4.6.0,4.7.0)",
org.eclipse.jgit.merge;version="[4.6.0,4.7.0)",
org.eclipse.jgit.nls;version="[4.6.0,4.7.0)",

View File

@ -62,6 +62,7 @@
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lfs.CleanFilter;
import org.eclipse.jgit.lib.*;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
@ -70,8 +71,22 @@
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
import org.junit.Test;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;
@RunWith(Theories.class)
public class AddCommandTest extends RepositoryTestCase {
@DataPoints
public static boolean[] smudge = { true, false };
@Override
public void setUp() throws Exception {
CleanFilter.register();
super.setUp();
}
@Test
public void testAddNothing() throws GitAPIException {
@ -110,8 +125,7 @@ public void testAddExistingSingleFile() throws IOException, GitAPIException {
}
@Test
public void testCleanFilter() throws IOException,
GitAPIException {
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
@ -134,6 +148,68 @@ public void testCleanFilter() throws IOException,
}
}
@Theory
public void testBuiltinFilter(boolean doSmudge)
throws IOException,
GitAPIException, InterruptedException {
writeTrashFile(".gitattributes", "*.txt filter=lfs");
writeTrashFile("src/a.tmp", "foo");
// Caution: we need a trailing '\n' since sed on mac always appends
// linefeeds if missing
File script = writeTempFile("sed s/o/e/g");
File f = writeTrashFile("src/a.txt", "foo\n");
try (Git git = new Git(db)) {
if (!doSmudge) {
fsTick(f);
}
git.add().addFilepattern(".gitattributes").call();
StoredConfig config = git.getRepository().getConfig();
config.setString("filter", "lfs", "clean",
"sh " + slashify(script.getPath()));
config.setString("filter", "lfs", "smudge",
"sh " + slashify(script.getPath()));
config.setBoolean("filter", "lfs", "useJGitBuiltin", true);
config.save();
if (!doSmudge) {
fsTick(f);
}
git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp")
.addFilepattern(".gitattributes").call();
assertEquals(
"[.gitattributes, mode:100644, content:*.txt filter=lfs][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c\nsize 4\n]",
indexState(CONTENT));
RevCommit c1 = git.commit().setMessage("c1").call();
assertTrue(git.status().call().isClean());
f = writeTrashFile("src/a.txt", "foobar\n");
if (!doSmudge) {
fsTick(f);
}
git.add().addFilepattern("src/a.txt").call();
git.commit().setMessage("c2").call();
assertTrue(git.status().call().isClean());
assertEquals(
"[.gitattributes, mode:100644, content:*.txt filter=lfs][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f\nsize 7\n]",
indexState(CONTENT));
assertEquals("foobar\n", read("src/a.txt"));
git.checkout().setName(c1.getName()).call();
assertEquals(
"[.gitattributes, mode:100644, content:*.txt filter=lfs][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c\nsize 4\n]",
indexState(CONTENT));
// due to lfs clean filter but dummy smudge filter we expect strange
// content. The smudge filter converts from real content to pointer
// file content (starting with "version ") but the smudge filter
// replaces 'o' by 'e' which results in a text starting with
// "versien "
assertEquals(
"versien https://git-lfs.github.cem/spec/v1\neid sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c\nsize 4\n",
read("src/a.txt"));
}
}
@Test
public void testAttributesWithTreeWalkFilter()
throws IOException, GitAPIException {