diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FilterCommandsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FilterCommandsTest.java new file mode 100644 index 000000000..5fb894e91 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FilterCommandsTest.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2016, Christian Halstrick + * 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; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.attributes.FilterCommand; +import org.eclipse.jgit.attributes.FilterCommandFactory; +import org.eclipse.jgit.attributes.FilterCommandRegistry; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.revwalk.RevCommit; +import org.junit.Before; +import org.junit.Test; + +public class FilterCommandsTest extends RepositoryTestCase { + private Git git; + + RevCommit initialCommit; + + RevCommit secondCommit; + + class TestCommandFactory implements FilterCommandFactory { + private int prefix; + + public TestCommandFactory(int prefix) { + this.prefix = prefix; + } + + @Override + public FilterCommand create(Repository repo, InputStream in, + final OutputStream out) { + FilterCommand cmd = new FilterCommand(in, out) { + + @Override + public int run() throws IOException { + int b = in.read(); + if (b == -1) { + return b; + } + out.write(prefix); + out.write(b); + return 1; + } + }; + return cmd; + } + } + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + git = new Git(db); + // commit something + writeTrashFile("Test.txt", "Hello world"); + git.add().addFilepattern("Test.txt").call(); + initialCommit = git.commit().setMessage("Initial commit").call(); + + // create a master branch and switch to it + git.branchCreate().setName("test").call(); + RefUpdate rup = db.updateRef(Constants.HEAD); + rup.link("refs/heads/test"); + + // commit something on the test branch + writeTrashFile("Test.txt", "Some change"); + git.add().addFilepattern("Test.txt").call(); + secondCommit = git.commit().setMessage("Second commit").call(); + } + + @Test + public void testBuiltinCleanFilter() + throws IOException, GitAPIException { + String builtinCommandName = "jgit://builtin/test/clean"; + FilterCommandRegistry.register(builtinCommandName, + new TestCommandFactory('c')); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "test", "clean", builtinCommandName); + config.save(); + + writeTrashFile(".gitattributes", "*.txt filter=test"); + git.add().addFilepattern(".gitattributes").call(); + git.commit().setMessage("add filter").call(); + + writeTrashFile("Test.txt", "Hello again"); + git.add().addFilepattern("Test.txt").call(); + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=test][Test.txt, mode:100644, content:cHceclclcoc cacgcacicn]", + indexState(CONTENT)); + + writeTrashFile("Test.bin", "Hello again"); + git.add().addFilepattern("Test.bin").call(); + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=test][Test.bin, mode:100644, content:Hello again][Test.txt, mode:100644, content:cHceclclcoc cacgcacicn]", + indexState(CONTENT)); + + config.setString("filter", "test", "clean", null); + config.save(); + + git.add().addFilepattern("Test.txt").call(); + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=test][Test.bin, mode:100644, content:Hello again][Test.txt, mode:100644, content:Hello again]", + indexState(CONTENT)); + + config.setString("filter", "test", "clean", null); + config.save(); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommand.java new file mode 100644 index 000000000..10be58880 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommand.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2016, Christian Halstrick + * 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.attributes; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * An abstraction for JGit's builtin implementations for hooks and filters. + * Instead of spawning an external processes to start a filter/hook and to pump + * data from/to stdin/stdout these builtin commmands may be used. They are + * constructed by {@link FilterCommandFactory}. + * + * @since 4.6 + */ +public abstract class FilterCommand { + /** + * The {@link InputStream} this command should read from + */ + protected InputStream in; + + /** + * The {@link OutputStream} this command should write to + */ + protected OutputStream out; + + /** + * @param in + * The {@link InputStream} this command should read from + * @param out + * The {@link OutputStream} this command should write to + */ + public FilterCommand(InputStream in, OutputStream out) { + this.in = in; + this.out = out; + } + + /** + * Execute the command. The command is supposed to read data from + * {@link #in} and to write the result to {@link #out}. It returns the + * number of bytes it read from {@link #in}. It should be called in a loop + * until it returns -1 signaling that the {@link InputStream} is completely + * processed. + * + * @return the number of bytes read from the {@link InputStream} or -1. -1 + * means that the {@link InputStream} is completely processed. + * @throws IOException + * when {@link IOException} occured while reading from + * {@link #in} or writing to {@link #out} + * + */ + public abstract int run() throws IOException; +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandFactory.java new file mode 100644 index 000000000..6b973da35 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandFactory.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2016, Christian Halstrick + * 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.attributes; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.eclipse.jgit.lib.Repository; + +/** + * The factory responsible for creating instances of {@link FilterCommand}. + * + * @since 4.6 + */ +public interface FilterCommandFactory { + /** + * Create a new {@link FilterCommand}. + * + * @param db + * the repository this command should work on + * @param in + * the {@link InputStream} this command should read from + * @param out + * the {@link OutputStream} this command should write to + * @return the created {@link FilterCommand} + * @throws IOException + * thrown when the command constructor throws an IOException + */ + public FilterCommand create(Repository db, InputStream in, + OutputStream out) throws IOException; + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandRegistry.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandRegistry.java new file mode 100644 index 000000000..4729f347d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandRegistry.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2016, Matthias Sohn + * 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.attributes; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jgit.lib.Repository; + +/** + * Registry for built-in filters + * + * @since 4.6 + */ +public class FilterCommandRegistry { + private static ConcurrentHashMap filterCommandRegistry = new ConcurrentHashMap<>(); + + /** + * Registers a {@link FilterCommandFactory} responsible for creating + * {@link FilterCommand}s for a certain command name. If the factory f1 is + * registered for the name "jgit://builtin/x" then a call to + * getCommand("jgit://builtin/x", ...) will call + * f1(...) to create a new instance of {@link FilterCommand} + * + * @param filterCommandName + * the command name for which this factory is registered + * @param factory + * the factory responsible for creating {@link FilterCommand}s + * for the specified name + * @return the previous factory associated with commandName, or + * null if there was no mapping for commandName + */ + public static FilterCommandFactory register(String filterCommandName, + FilterCommandFactory factory) { + return filterCommandRegistry.put(filterCommandName, factory); + } + + /** + * Unregisters the {@link FilterCommandFactory} registered for the given + * command name + * + * @param filterCommandName + * the FilterCommandFactory's filter command name + * @return the previous factory associated with filterCommandName, + * or null if there was no mapping for commandName + */ + public static FilterCommandFactory unregister(String filterCommandName) { + return filterCommandRegistry.remove(filterCommandName); + } + + /** + * Checks whether any {@link FilterCommandFactory} is registered for a given + * command name + * + * @param filterCommandName + * the name for which the registry should be checked + * @return true if any factory was registered for the name + */ + public static boolean isRegistered(String filterCommandName) { + return filterCommandRegistry.containsKey(filterCommandName); + } + + /** + * Creates a new {@link FilterCommand} for the given name. A factory must be + * registered for the name in advance. + * + * @param filterCommandName + * The name for which a new {@link FilterCommand} should be + * created + * @param db + * the repository this command should work on + * @param in + * the {@link InputStream} this {@link FilterCommand} should read + * from + * @param out + * the {@link OutputStream} this {@link FilterCommand} should + * write to + * @return the command if a command could be created or null if + * there was no factory registered for that name + * @throws IOException + */ + public static FilterCommand createFilterCommand(String filterCommandName, + Repository db, InputStream in, OutputStream out) + throws IOException { + FilterCommandFactory cf = filterCommandRegistry.get(filterCommandName); + return (cf == null) ? null : cf.create(db, in, out); + } + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java index d30edaf41..ff80672f8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java @@ -391,6 +391,13 @@ public final class Constants { */ public static final String ATTR_FILTER_TYPE_SMUDGE = "smudge"; + /** + * Builtin filter commands start with this prefix + * + * @since 4.6 + */ + public static final String BUILTIN_FILTER_PREFIX = "jgit://builtin/"; + /** Name of the ignore file */ public static final String DOT_GIT_IGNORE = ".gitignore"; 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 9a3fa8060..be4baef05 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java @@ -65,6 +65,8 @@ 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.attributes.FilterCommand; +import org.eclipse.jgit.attributes.FilterCommandRegistry; import org.eclipse.jgit.diff.RawText; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheEntry; @@ -93,6 +95,8 @@ 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.TemporaryBuffer; +import org.eclipse.jgit.util.TemporaryBuffer.LocalFile; import org.eclipse.jgit.util.io.AutoLFInputStream; import org.eclipse.jgit.util.io.EolStreamTypeUtil; @@ -461,6 +465,16 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { in = handleAutoCRLF(in, opType); String filterCommand = getCleanFilterCommand(); if (filterCommand != null) { + if (FilterCommandRegistry.isRegistered(filterCommand)) { + LocalFile buffer = new TemporaryBuffer.LocalFile(null); + FilterCommand command = FilterCommandRegistry + .createFilterCommand(filterCommand, repository, in, + buffer); + while (command.run() != -1) { + // loop as long as command.run() tells there is work to do + } + return buffer.openInputStream(); + } FS fs = repository.getFS(); ProcessBuilder filterProcessBuilder = fs.runInShell(filterCommand, new String[0]);