aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes')
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeFileTests.java139
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeTest.java43
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java618
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java198
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java174
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java (renamed from org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeNodeTest.java)114
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java182
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java383
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java839
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/merge/MergeGitAttributeTest.java581
10 files changed, 2869 insertions, 402 deletions
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeFileTests.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeFileTests.java
new file mode 100644
index 0000000000..5d05a98d66
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeFileTests.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.attributes;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.Arrays;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.ResetCommand.ResetType;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.junit.Test;
+
+/**
+ * End-to-end tests for some attribute combinations. Writes files, commit them,
+ * examines the index, deletes the files, performs a hard reset and checks file
+ * contents again.
+ */
+public class AttributeFileTests extends RepositoryTestCase {
+
+ @Test
+ public void testTextAutoCoreEolCoreAutoCrLfInput() throws Exception {
+ FileBasedConfig cfg = db.getConfig();
+ cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF, false);
+ cfg.save();
+ final String content = "Line1\nLine2\n";
+ try (Git git = Git.wrap(db)) {
+ writeTrashFile(".gitattributes", "* text=auto");
+ File dummy = writeTrashFile("dummy.txt", content);
+ git.add().addFilepattern(".").call();
+ git.commit().setMessage("Commit with LF").call();
+ assertEquals("Unexpected index state",
+ "[.gitattributes, mode:100644, content:* text=auto]"
+ + "[dummy.txt, mode:100644, content:" + content
+ + ']',
+ indexState(CONTENT));
+ assertTrue("Should be able to delete " + dummy, dummy.delete());
+ cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_EOL, "crlf");
+ cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF, "input");
+ cfg.save();
+ git.reset().setMode(ResetType.HARD).call();
+ assertTrue("File " + dummy + "should exist", dummy.isFile());
+ String textFile = RawParseUtils.decode(IO.readFully(dummy, 512));
+ assertEquals("Unexpected text content", content, textFile);
+ }
+ }
+
+ @Test
+ public void testTextAutoEolLf() throws Exception {
+ writeTrashFile(".gitattributes", "* text=auto eol=lf");
+ performTest("Test\r\nFile", "Test\nFile", "Test\nFile");
+ }
+
+ @Test
+ public void testTextAutoEolCrLf() throws Exception {
+ writeTrashFile(".gitattributes", "* text=auto eol=crlf");
+ performTest("Test\r\nFile", "Test\nFile", "Test\r\nFile");
+ }
+
+ private void performTest(String initial, String index, String finalText)
+ throws Exception {
+ File dummy = writeTrashFile("dummy.foo", initial);
+ byte[] data = readTestResource("add.png");
+ assertTrue("Expected some binary data", data.length > 100);
+ File binary = writeTrashFile("add.png", "");
+ Files.write(binary.toPath(), data);
+ try (Git git = Git.wrap(db)) {
+ git.add().addFilepattern(".").call();
+ git.commit().setMessage("test commit").call();
+ // binary should be unchanged, dummy should match "index"
+ verifyIndexContent("dummy.foo",
+ index.getBytes(StandardCharsets.UTF_8));
+ verifyIndexContent("add.png", data);
+ assertTrue("Should be able to delete " + dummy, dummy.delete());
+ assertTrue("Should be able to delete " + binary, binary.delete());
+ git.reset().setMode(ResetType.HARD).call();
+ assertTrue("File " + dummy + " should exist", dummy.isFile());
+ assertTrue("File " + binary + " should exist", binary.isFile());
+ // binary should be unchanged, dummy should match "finalText"
+ String textFile = RawParseUtils.decode(IO.readFully(dummy, 512));
+ assertEquals("Unexpected text content", finalText, textFile);
+ byte[] binaryFile = IO.readFully(binary, 512);
+ assertArrayEquals("Unexpected binary content", data, binaryFile);
+ }
+ }
+
+ private byte[] readTestResource(String name) throws Exception {
+ try (InputStream in = new BufferedInputStream(
+ getClass().getResourceAsStream(name))) {
+ byte[] data = new byte[512];
+ int read = in.read(data);
+ if (read == data.length) {
+ return data;
+ }
+ return Arrays.copyOf(data, read);
+ }
+ }
+
+ private void verifyIndexContent(String path, byte[] expectedContent)
+ throws Exception {
+ DirCache dc = db.readDirCache();
+ for (int i = 0; i < dc.getEntryCount(); ++i) {
+ DirCacheEntry entry = dc.getEntry(i);
+ if (path.equals(entry.getPathString())) {
+ byte[] data = db.open(entry.getObjectId(), Constants.OBJ_BLOB)
+ .getCachedBytes();
+ assertArrayEquals("Unexpected index content for " + path,
+ expectedContent, data);
+ return;
+ }
+ }
+ fail("Path not found in index: " + path);
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeTest.java
index 93b954fa9a..9f9ba0de8e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeTest.java
@@ -1,45 +1,12 @@
/*
* Copyright (C) 2010, Marc Strapetz <marc.strapetz@syntevo.com>
- * Copyright (C) 2013, Gunnar Wagenknecht
- * and other copyright owners as documented in the project's IP log.
+ * Copyright (C) 2013, Gunnar Wagenknecht and others
*
- * 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
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://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.
+ * SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.attributes;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java
new file mode 100644
index 0000000000..c41dd81add
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java
@@ -0,0 +1,618 @@
+/*
+ * Copyright (C) 2015, 2017 Ivan Motsch <ivan.motsch@bsiag.com> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.attributes;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.junit.Test;
+
+/**
+ * Tests {@link AttributesHandler}
+ */
+public class AttributesHandlerTest extends RepositoryTestCase {
+ private static final FileMode D = FileMode.TREE;
+
+ private static final FileMode F = FileMode.REGULAR_FILE;
+
+ @Test
+ public void testExpandNonMacro1() throws Exception {
+ setupRepo(null, null, null, "*.txt text");
+
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/.gitattributes");
+ assertIteration(walk, F, "sub/a.txt", attrs("text"));
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testExpandNonMacro2() throws Exception {
+ setupRepo(null, null, null, "*.txt -text");
+
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/.gitattributes");
+ assertIteration(walk, F, "sub/a.txt", attrs("-text"));
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testExpandNonMacro3() throws Exception {
+ setupRepo(null, null, null, "*.txt !text");
+
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/.gitattributes");
+ assertIteration(walk, F, "sub/a.txt", attrs(""));
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testExpandNonMacro4() throws Exception {
+ setupRepo(null, null, null, "*.txt text=auto");
+
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/.gitattributes");
+ assertIteration(walk, F, "sub/a.txt", attrs("text=auto"));
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testExpandBuiltInMacro1() throws Exception {
+ setupRepo(null, null, null, "*.txt binary");
+
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/.gitattributes");
+ assertIteration(walk, F, "sub/a.txt",
+ attrs("binary -diff -merge -text"));
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testExpandBuiltInMacro2() throws Exception {
+ setupRepo(null, null, null, "*.txt -binary");
+
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/.gitattributes");
+ assertIteration(walk, F, "sub/a.txt",
+ attrs("-binary diff merge text"));
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testExpandBuiltInMacro3() throws Exception {
+ setupRepo(null, null, null, "*.txt !binary");
+
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/.gitattributes");
+ assertIteration(walk, F, "sub/a.txt", attrs(""));
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testCustomGlobalMacro1() throws Exception {
+ setupRepo(
+ "[attr]foo a -b !c d=e", null, null, "*.txt foo");
+
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/.gitattributes");
+ assertIteration(walk, F, "sub/a.txt", attrs("foo a -b d=e"));
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testCustomGlobalMacro2() throws Exception {
+ setupRepo("[attr]foo a -b !c d=e", null, null, "*.txt -foo");
+
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/.gitattributes");
+ assertIteration(walk, F, "sub/a.txt", attrs("-foo -a b d=e"));
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testCustomGlobalMacro3() throws Exception {
+ setupRepo("[attr]foo a -b !c d=e", null, null, "*.txt !foo");
+
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/.gitattributes");
+ assertIteration(walk, F, "sub/a.txt", attrs(""));
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testCustomGlobalMacro4() throws Exception {
+ setupRepo("[attr]foo a -b !c d=e", null, null, "*.txt foo=bar");
+
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/.gitattributes");
+ assertIteration(walk, F, "sub/a.txt", attrs("foo=bar a -b d=bar"));
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testInfoOverridesGlobal() throws Exception {
+ setupRepo("[attr]foo bar1",
+ "[attr]foo bar2", null, "*.txt foo");
+
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/.gitattributes");
+ assertIteration(walk, F, "sub/a.txt", attrs("foo bar2"));
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testWorkDirRootOverridesGlobal() throws Exception {
+ setupRepo("[attr]foo bar1",
+ null,
+ "[attr]foo bar3", "*.txt foo");
+
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/.gitattributes");
+ assertIteration(walk, F, "sub/a.txt", attrs("foo bar3"));
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testInfoOverridesWorkDirRoot() throws Exception {
+ setupRepo("[attr]foo bar1",
+ "[attr]foo bar2", "[attr]foo bar3", "*.txt foo");
+
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/.gitattributes");
+ assertIteration(walk, F, "sub/a.txt", attrs("foo bar2"));
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testRecursiveMacro() throws Exception {
+ setupRepo(
+ "[attr]foo x bar -foo",
+ null, null, "*.txt foo");
+
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/.gitattributes");
+ assertIteration(walk, F, "sub/a.txt", attrs("foo x bar"));
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testCyclicMacros() throws Exception {
+ setupRepo(
+ "[attr]foo x -bar\n[attr]bar y -foo", null, null, "*.txt foo");
+
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/.gitattributes");
+ assertIteration(walk, F, "sub/a.txt", attrs("foo x -bar -y"));
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testRelativePaths() throws Exception {
+ setupRepo("sub/ global", "sub/** init",
+ "sub/** top_sub\n*.txt top",
+ "sub/** subsub\nsub/ subsub2\n*.txt foo");
+ // The last sub/** is in sub/.gitattributes. It must not
+ // apply to any of the files here. It would match for a
+ // further subdirectory sub/sub. The sub/ rules must match
+ // only for directories.
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
+ assertIteration(walk, D, "sub", attrs("global"));
+ assertIteration(walk, F, "sub/.gitattributes",
+ attrs("init top_sub"));
+ assertIteration(walk, F, "sub/a.txt",
+ attrs("init foo top top_sub"));
+ assertFalse("Not all files tested", walk.next());
+ }
+ // All right, let's see that they *do* apply in sub/sub:
+ writeTrashFile("sub/sub/b.txt", "b");
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
+ assertIteration(walk, D, "sub", attrs("global"));
+ assertIteration(walk, F, "sub/.gitattributes",
+ attrs("init top_sub"));
+ assertIteration(walk, F, "sub/a.txt",
+ attrs("init foo top top_sub"));
+ assertIteration(walk, D, "sub/sub",
+ attrs("init subsub2 top_sub global"));
+ assertIteration(walk, F, "sub/sub/b.txt",
+ attrs("init foo subsub top top_sub"));
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testNestedMatchNot() throws Exception {
+ setupRepo(null, null, "*.xml xml\n*.jar jar", null);
+ writeTrashFile("foo.xml/bar.jar", "b");
+ writeTrashFile("foo.xml/bar.xml", "bx");
+ writeTrashFile("sub/b.jar", "bj");
+ writeTrashFile("sub/b.xml", "bx");
+ // On foo.xml/bar.jar we must not have 'xml'
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
+ assertIteration(walk, D, "foo.xml", attrs("xml"));
+ assertIteration(walk, F, "foo.xml/bar.jar", attrs("jar"));
+ assertIteration(walk, F, "foo.xml/bar.xml", attrs("xml"));
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/a.txt");
+ assertIteration(walk, F, "sub/b.jar", attrs("jar"));
+ assertIteration(walk, F, "sub/b.xml", attrs("xml"));
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testNestedMatch() throws Exception {
+ // See also CGitAttributeTest.testNestedMatch()
+ setupRepo(null, null, "foo/ xml\nsub/foo/ sub\n*.jar jar", null);
+ writeTrashFile("foo/bar.jar", "b");
+ writeTrashFile("foo/bar.xml", "bx");
+ writeTrashFile("sub/b.jar", "bj");
+ writeTrashFile("sub/b.xml", "bx");
+ writeTrashFile("sub/foo/b.jar", "bf");
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
+ assertIteration(walk, D, "foo", attrs("xml"));
+ assertIteration(walk, F, "foo/bar.jar", attrs("jar"));
+ assertIteration(walk, F, "foo/bar.xml");
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/a.txt");
+ assertIteration(walk, F, "sub/b.jar", attrs("jar"));
+ assertIteration(walk, F, "sub/b.xml");
+ assertIteration(walk, D, "sub/foo", attrs("sub xml"));
+ assertIteration(walk, F, "sub/foo/b.jar", attrs("jar"));
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testNestedMatchRecursive() throws Exception {
+ setupRepo(null, null, "foo/** xml\n*.jar jar", null);
+ writeTrashFile("foo/bar.jar", "b");
+ writeTrashFile("foo/bar.xml", "bx");
+ writeTrashFile("sub/b.jar", "bj");
+ writeTrashFile("sub/b.xml", "bx");
+ writeTrashFile("sub/foo/b.jar", "bf");
+ // On foo.xml/bar.jar we must not have 'xml'
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
+ assertIteration(walk, D, "foo");
+ assertIteration(walk, F, "foo/bar.jar", attrs("jar xml"));
+ assertIteration(walk, F, "foo/bar.xml", attrs("xml"));
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/a.txt");
+ assertIteration(walk, F, "sub/b.jar", attrs("jar"));
+ assertIteration(walk, F, "sub/b.xml");
+ assertIteration(walk, D, "sub/foo");
+ assertIteration(walk, F, "sub/foo/b.jar", attrs("jar"));
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testStarMatchOnSlashNot() throws Exception {
+ setupRepo(null, null, "s*xt bar", null);
+ writeTrashFile("sub/a.txt", "1");
+ writeTrashFile("foo/sext", "2");
+ writeTrashFile("foo/s.txt", "3");
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
+ assertIteration(walk, D, "foo");
+ assertIteration(walk, F, "foo/s.txt", attrs("bar"));
+ assertIteration(walk, F, "foo/sext", attrs("bar"));
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/a.txt");
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testPrefixMatchNot() throws Exception {
+ setupRepo(null, null, "sub/new bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/a.txt");
+ assertIteration(walk, D, "sub/new", attrs("bar"));
+ assertIteration(walk, F, "sub/new/foo.txt");
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testComplexPathMatch() throws Exception {
+ setupRepo(null, null, "s[t-v]b/n[de]w bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("sub/ndw", "2");
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/a.txt");
+ assertIteration(walk, F, "sub/ndw", attrs("bar"));
+ assertIteration(walk, D, "sub/new", attrs("bar"));
+ assertIteration(walk, F, "sub/new/foo.txt");
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testStarPathMatch() throws Exception {
+ setupRepo(null, null, "sub/new/* bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("sub/new/lower/foo.txt", "2");
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/a.txt");
+ assertIteration(walk, D, "sub/new");
+ assertIteration(walk, F, "sub/new/foo.txt", attrs("bar"));
+ assertIteration(walk, D, "sub/new/lower", attrs("bar"));
+ assertIteration(walk, F, "sub/new/lower/foo.txt");
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testDirectoryMatchSubSimple() throws Exception {
+ setupRepo(null, null, "sub/new/ bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("foo/sub/new/foo.txt", "2");
+ writeTrashFile("sub/sub/new/foo.txt", "3");
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
+ assertIteration(walk, D, "foo");
+ assertIteration(walk, D, "foo/sub");
+ assertIteration(walk, D, "foo/sub/new");
+ assertIteration(walk, F, "foo/sub/new/foo.txt");
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/a.txt");
+ assertIteration(walk, D, "sub/new", attrs("bar"));
+ assertIteration(walk, F, "sub/new/foo.txt");
+ assertIteration(walk, D, "sub/sub");
+ assertIteration(walk, D, "sub/sub/new");
+ assertIteration(walk, F, "sub/sub/new/foo.txt");
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursive() throws Exception {
+ setupRepo(null, null, "**/sub/new/ bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("foo/sub/new/foo.txt", "2");
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
+ assertIteration(walk, D, "foo");
+ assertIteration(walk, D, "foo/sub");
+ assertIteration(walk, D, "foo/sub/new", attrs("bar"));
+ assertIteration(walk, F, "foo/sub/new/foo.txt");
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/a.txt");
+ assertIteration(walk, D, "sub/new", attrs("bar"));
+ assertIteration(walk, F, "sub/new/foo.txt");
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack() throws Exception {
+ setupRepo(null, null, "**/sub/new/ bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("foo/sub/new/foo.txt", "2");
+ writeTrashFile("sub/sub/new/foo.txt", "3");
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
+ assertIteration(walk, D, "foo");
+ assertIteration(walk, D, "foo/sub");
+ assertIteration(walk, D, "foo/sub/new", attrs("bar"));
+ assertIteration(walk, F, "foo/sub/new/foo.txt");
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/a.txt");
+ assertIteration(walk, D, "sub/new", attrs("bar"));
+ assertIteration(walk, F, "sub/new/foo.txt");
+ assertIteration(walk, D, "sub/sub");
+ assertIteration(walk, D, "sub/sub/new", attrs("bar"));
+ assertIteration(walk, F, "sub/sub/new/foo.txt");
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack2() throws Exception {
+ setupRepo(null, null, "**/**/sub/new/ bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("foo/sub/new/foo.txt", "2");
+ writeTrashFile("sub/sub/new/foo.txt", "3");
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
+ assertIteration(walk, D, "foo");
+ assertIteration(walk, D, "foo/sub");
+ assertIteration(walk, D, "foo/sub/new", attrs("bar"));
+ assertIteration(walk, F, "foo/sub/new/foo.txt");
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/a.txt");
+ assertIteration(walk, D, "sub/new", attrs("bar"));
+ assertIteration(walk, F, "sub/new/foo.txt");
+ assertIteration(walk, D, "sub/sub");
+ assertIteration(walk, D, "sub/sub/new", attrs("bar"));
+ assertIteration(walk, F, "sub/sub/new/foo.txt");
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testDirectoryMatchSubComplex() throws Exception {
+ setupRepo(null, null, "s[uv]b/n*/ bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("foo/sub/new/foo.txt", "2");
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
+ assertIteration(walk, D, "foo");
+ assertIteration(walk, D, "foo/sub");
+ assertIteration(walk, D, "foo/sub/new");
+ assertIteration(walk, F, "foo/sub/new/foo.txt");
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/a.txt");
+ assertIteration(walk, D, "sub/new", attrs("bar"));
+ assertIteration(walk, F, "sub/new/foo.txt");
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ @Test
+ public void testDirectoryMatch() throws Exception {
+ setupRepo(null, null, "new/ bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("foo/sub/new/foo.txt", "2");
+ writeTrashFile("foo/new", "3");
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
+ assertIteration(walk, D, "foo");
+ assertIteration(walk, F, "foo/new");
+ assertIteration(walk, D, "foo/sub");
+ assertIteration(walk, D, "foo/sub/new", attrs("bar"));
+ assertIteration(walk, F, "foo/sub/new/foo.txt");
+ assertIteration(walk, D, "sub");
+ assertIteration(walk, F, "sub/a.txt");
+ assertIteration(walk, D, "sub/new", attrs("bar"));
+ assertIteration(walk, F, "sub/new/foo.txt");
+ assertFalse("Not all files tested", walk.next());
+ }
+ }
+
+ private static Collection<Attribute> attrs(String s) {
+ return new AttributesRule("*", s).getAttributes();
+ }
+
+ private void assertIteration(TreeWalk walk, FileMode type, String pathName)
+ throws IOException {
+ assertIteration(walk, type, pathName,
+ Collections.<Attribute> emptyList());
+ }
+
+ private void assertIteration(TreeWalk walk, FileMode type, String pathName,
+ Collection<Attribute> expectedAttrs) throws IOException {
+ assertTrue("walk has entry", walk.next());
+ assertEquals(pathName, walk.getPathString());
+ assertEquals(type, walk.getFileMode(0));
+
+ if (expectedAttrs != null) {
+ assertEquals(new ArrayList<>(expectedAttrs),
+ new ArrayList<>(walk.getAttributes().getAll()));
+ }
+
+ if (D.equals(type))
+ walk.enterSubtree();
+ }
+
+ /**
+ * @param globalAttributesContent
+ * @param infoAttributesContent
+ * @param rootAttributesContent
+ * @param subDirAttributesContent
+ * @throws Exception
+ * Setup a repo with .gitattributes files and a test file
+ * sub/a.txt
+ */
+ private void setupRepo(
+ String globalAttributesContent,
+ String infoAttributesContent, String rootAttributesContent, String subDirAttributesContent)
+ throws Exception {
+ FileBasedConfig config = db.getConfig();
+ if (globalAttributesContent != null) {
+ File f = new File(db.getDirectory(), "global/attributes");
+ write(f, globalAttributesContent);
+ config.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_ATTRIBUTESFILE,
+ f.getAbsolutePath());
+
+ }
+ if (infoAttributesContent != null) {
+ File f = new File(db.getCommonDirectory(), Constants.INFO_ATTRIBUTES);
+ write(f, infoAttributesContent);
+ }
+ config.save();
+
+ if (rootAttributesContent != null) {
+ writeAttributesFile(Constants.DOT_GIT_ATTRIBUTES,
+ rootAttributesContent);
+ }
+
+ if (subDirAttributesContent != null) {
+ writeAttributesFile("sub/" + Constants.DOT_GIT_ATTRIBUTES,
+ subDirAttributesContent);
+ }
+
+ writeTrashFile("sub/a.txt", "a");
+ }
+
+ private void writeAttributesFile(String name, String... rules)
+ throws IOException {
+ StringBuilder data = new StringBuilder();
+ for (String line : rules)
+ data.append(line + "\n");
+ writeTrashFile(name, data.toString());
+ }
+
+ private TreeWalk beginWalk() {
+ TreeWalk newWalk = new TreeWalk(db);
+ newWalk.addTree(new FileTreeIterator(db));
+ return newWalk;
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java
index 9f82b8a1e9..6109d78184 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java
@@ -1,44 +1,11 @@
/*
- * Copyright (C) 2010, Red Hat Inc.
- * and other copyright owners as documented in the project's IP log.
+ * Copyright (C) 2010, Red Hat Inc. and others
*
- * 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
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://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.
+ * SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.attributes;
@@ -109,16 +76,16 @@ public class AttributesMatcherTest {
pattern = "/src/ne?";
assertMatched(pattern, "/src/new/");
assertMatched(pattern, "/src/new");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/src/new/a/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/src/new/a/a.c");
assertNotMatched(pattern, "/src/new.c");
//Test name-only fnmatcher matches
pattern = "ne?";
assertMatched(pattern, "/src/new/");
assertMatched(pattern, "/src/new");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/src/new/a/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/src/new/a/a.c");
assertMatched(pattern, "/neb");
assertNotMatched(pattern, "/src/new.c");
}
@@ -169,16 +136,16 @@ public class AttributesMatcherTest {
pattern = "/src/ne?";
assertMatched(pattern, "src/new/");
assertMatched(pattern, "src/new");
- assertMatched(pattern, "src/new/a.c");
- assertMatched(pattern, "src/new/a/a.c");
+ assertNotMatched(pattern, "src/new/a.c");
+ assertNotMatched(pattern, "src/new/a/a.c");
assertNotMatched(pattern, "src/new.c");
//Test name-only fnmatcher matches
pattern = "ne?";
assertMatched(pattern, "src/new/");
assertMatched(pattern, "src/new");
- assertMatched(pattern, "src/new/a.c");
- assertMatched(pattern, "src/new/a/a.c");
+ assertNotMatched(pattern, "src/new/a.c");
+ assertNotMatched(pattern, "src/new/a/a.c");
assertMatched(pattern, "neb");
assertNotMatched(pattern, "src/new.c");
}
@@ -197,35 +164,50 @@ public class AttributesMatcherTest {
pattern = "/src/new";
assertMatched(pattern, "/src/new/");
assertMatched(pattern, "/src/new");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/src/new/a/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/src/new/a/a.c");
assertNotMatched(pattern, "/src/new.c");
//Test child directory is matched, slash after name
pattern = "/src/new/";
assertMatched(pattern, "/src/new/");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/src/new/a/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/src/new/a/a.c");
assertNotMatched(pattern, "/src/new");
assertNotMatched(pattern, "/src/new.c");
//Test directory is matched by name only
pattern = "b1";
- assertMatched(pattern, "/src/new/a/b1/a.c");
+ assertNotMatched(pattern, "/src/new/a/b1/a.c");
assertNotMatched(pattern, "/src/new/a/b2/file.c");
assertNotMatched(pattern, "/src/new/a/bb1/file.c");
assertNotMatched(pattern, "/src/new/a/file.c");
+ assertNotMatched(pattern, "/src/new/a/bb1");
+ assertMatched(pattern, "/src/new/a/b1");
}
@Test
public void testTrailingSlash() {
String pattern = "/src/";
assertMatched(pattern, "/src/");
- assertMatched(pattern, "/src/new");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/src/a.c");
+ assertNotMatched(pattern, "/src/new");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/src/a.c");
assertNotMatched(pattern, "/src");
assertNotMatched(pattern, "/srcA/");
+
+ pattern = "src/";
+ assertMatched(pattern, "src/");
+ assertMatched(pattern, "/src/");
+ assertNotMatched(pattern, "src");
+ assertNotMatched(pattern, "/src/new");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/src/a.c");
+ assertNotMatched(pattern, "foo/src/a.c");
+ assertNotMatched(pattern, "foo/src/bar/a.c");
+ assertNotMatched(pattern, "foo/src/bar/src");
+ assertMatched(pattern, "foo/src/");
+ assertMatched(pattern, "foo/src/bar/src/");
}
@Test
@@ -239,51 +221,58 @@ public class AttributesMatcherTest {
assertMatched(pattern, "/src/test.stp");
assertNotMatched(pattern, "/test.stp1");
assertNotMatched(pattern, "/test.astp");
+ assertNotMatched(pattern, "test.stp/foo.bar");
+ assertMatched(pattern, "test.stp");
+ assertMatched(pattern, "test.stp/");
+ assertMatched(pattern, "test.stp/test.stp");
//Test matches for name-only, applies to file name or folder name
pattern = "src";
assertMatched(pattern, "/src");
assertMatched(pattern, "/src/");
- assertMatched(pattern, "/src/a.c");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/new/src/a.c");
+ assertNotMatched(pattern, "/src/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/new/src/a.c");
assertMatched(pattern, "/file/src");
//Test matches for name-only, applies only to folder names
pattern = "src/";
- assertMatched(pattern, "/src/");
- assertMatched(pattern, "/src/a.c");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/new/src/a.c");
+ assertNotMatched(pattern, "/src/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/new/src/a.c");
assertNotMatched(pattern, "/src");
assertNotMatched(pattern, "/file/src");
+ assertMatched(pattern, "/file/src/");
//Test matches for name-only, applies to file name or folder name
//With a small wildcard
pattern = "?rc";
- assertMatched(pattern, "/src/a.c");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/new/src/a.c");
+ assertNotMatched(pattern, "/src/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/new/src/a.c");
+ assertMatched(pattern, "/new/src/");
assertMatched(pattern, "/file/src");
assertMatched(pattern, "/src/");
//Test matches for name-only, applies to file name or folder name
//With a small wildcard
pattern = "?r[a-c]";
- assertMatched(pattern, "/src/a.c");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/new/src/a.c");
+ assertNotMatched(pattern, "/src/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/new/src/a.c");
assertMatched(pattern, "/file/src");
assertMatched(pattern, "/src/");
- assertMatched(pattern, "/srb/a.c");
- assertMatched(pattern, "/grb/new/a.c");
- assertMatched(pattern, "/new/crb/a.c");
+ assertNotMatched(pattern, "/srb/a.c");
+ assertNotMatched(pattern, "/grb/new/a.c");
+ assertNotMatched(pattern, "/new/crb/a.c");
assertMatched(pattern, "/file/3rb");
assertMatched(pattern, "/xrb/");
- assertMatched(pattern, "/3ra/a.c");
- assertMatched(pattern, "/5ra/new/a.c");
- assertMatched(pattern, "/new/1ra/a.c");
+ assertNotMatched(pattern, "/3ra/a.c");
+ assertNotMatched(pattern, "/5ra/new/a.c");
+ assertNotMatched(pattern, "/new/1ra/a.c");
+ assertNotMatched(pattern, "/new/1ra/a.c/");
assertMatched(pattern, "/file/dra");
+ assertMatched(pattern, "/file/dra/");
assertMatched(pattern, "/era/");
assertNotMatched(pattern, "/crg");
assertNotMatched(pattern, "/cr3");
@@ -293,28 +282,28 @@ public class AttributesMatcherTest {
public void testGetters() {
AttributesRule r = new AttributesRule("/pattern/", "");
assertFalse(r.isNameOnly());
- assertTrue(r.dirOnly());
+ assertTrue(r.isDirOnly());
assertNotNull(r.getAttributes());
assertTrue(r.getAttributes().isEmpty());
assertEquals(r.getPattern(), "/pattern");
r = new AttributesRule("/patter?/", "");
assertFalse(r.isNameOnly());
- assertTrue(r.dirOnly());
+ assertTrue(r.isDirOnly());
assertNotNull(r.getAttributes());
assertTrue(r.getAttributes().isEmpty());
assertEquals(r.getPattern(), "/patter?");
r = new AttributesRule("patt*", "");
assertTrue(r.isNameOnly());
- assertFalse(r.dirOnly());
+ assertFalse(r.isDirOnly());
assertNotNull(r.getAttributes());
assertTrue(r.getAttributes().isEmpty());
assertEquals(r.getPattern(), "patt*");
r = new AttributesRule("pattern", "attribute1");
assertTrue(r.isNameOnly());
- assertFalse(r.dirOnly());
+ assertFalse(r.isDirOnly());
assertNotNull(r.getAttributes());
assertFalse(r.getAttributes().isEmpty());
assertEquals(r.getAttributes().size(), 1);
@@ -322,28 +311,28 @@ public class AttributesMatcherTest {
r = new AttributesRule("pattern", "attribute1 -attribute2");
assertTrue(r.isNameOnly());
- assertFalse(r.dirOnly());
+ assertFalse(r.isDirOnly());
assertNotNull(r.getAttributes());
assertEquals(r.getAttributes().size(), 2);
assertEquals(r.getPattern(), "pattern");
r = new AttributesRule("pattern", "attribute1 \t-attribute2 \t");
assertTrue(r.isNameOnly());
- assertFalse(r.dirOnly());
+ assertFalse(r.isDirOnly());
assertNotNull(r.getAttributes());
assertEquals(r.getAttributes().size(), 2);
assertEquals(r.getPattern(), "pattern");
r = new AttributesRule("pattern", "attribute1\t-attribute2\t");
assertTrue(r.isNameOnly());
- assertFalse(r.dirOnly());
+ assertFalse(r.isDirOnly());
assertNotNull(r.getAttributes());
assertEquals(r.getAttributes().size(), 2);
assertEquals(r.getPattern(), "pattern");
r = new AttributesRule("pattern", "attribute1\t -attribute2\t ");
assertTrue(r.isNameOnly());
- assertFalse(r.dirOnly());
+ assertFalse(r.isDirOnly());
assertNotNull(r.getAttributes());
assertEquals(r.getAttributes().size(), 2);
assertEquals(r.getPattern(), "pattern");
@@ -351,7 +340,7 @@ public class AttributesMatcherTest {
r = new AttributesRule("pattern",
"attribute1 -attribute2 attribute3=value ");
assertTrue(r.isNameOnly());
- assertFalse(r.dirOnly());
+ assertFalse(r.isDirOnly());
assertNotNull(r.getAttributes());
assertEquals(r.getAttributes().size(), 3);
assertEquals(r.getPattern(), "pattern");
@@ -360,6 +349,47 @@ public class AttributesMatcherTest {
assertEquals(r.getAttributes().get(2).toString(), "attribute3=value");
}
+ @Test
+ public void testBracketsInGroup() {
+ //combinations of brackets in brackets, escaped and not
+
+ String[] patterns = new String[]{"[[\\]]", "[\\[\\]]"};
+ for (String pattern : patterns) {
+ assertNotMatched(pattern, "");
+ assertNotMatched(pattern, "[]");
+ assertNotMatched(pattern, "][");
+ assertNotMatched(pattern, "[\\[]");
+ assertNotMatched(pattern, "[[]");
+ assertNotMatched(pattern, "[[]]");
+ assertNotMatched(pattern, "[\\[\\]]");
+
+ assertMatched(pattern, "[");
+ assertMatched(pattern, "]");
+ }
+
+ patterns = new String[]{"[[]]", "[\\[]]"};
+ for (String pattern : patterns) {
+ assertNotMatched(pattern, "");
+ assertMatched(pattern, "[]");
+ assertNotMatched(pattern, "][");
+ assertNotMatched(pattern, "[\\[]");
+ assertNotMatched(pattern, "[[]");
+ assertNotMatched(pattern, "[[]]");
+ assertNotMatched(pattern, "[\\[\\]]");
+
+ assertNotMatched(pattern, "[");
+ assertNotMatched(pattern, "]");
+ }
+ }
+
+ @Test
+ public void testFileNameWithLineTerminator() {
+ assertMatched("a?", "a\r");
+ assertMatched("a?", "dir/a\r");
+ assertMatched("*a", "\ra");
+ assertMatched("dir/*a*", "dir/\ra\r");
+ }
+
/**
* Check for a match. If target ends with "/", match will assume that the
* target is meant to be a directory.
@@ -369,7 +399,7 @@ public class AttributesMatcherTest {
* @param target
* Target file path relative to repository's GIT_DIR
*/
- public void assertMatched(String pattern, String target) {
+ private void assertMatched(String pattern, String target) {
boolean value = match(pattern, target);
assertTrue("Expected a match for: " + pattern + " with: " + target,
value);
@@ -384,7 +414,7 @@ public class AttributesMatcherTest {
* @param target
* Target file path relative to repository's GIT_DIR
*/
- public void assertNotMatched(String pattern, String target) {
+ private void assertNotMatched(String pattern, String target) {
boolean value = match(pattern, target);
assertFalse("Expected no match for: " + pattern + " with: " + target,
value);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java
index 49279e6e5a..35b953320e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java
@@ -1,44 +1,11 @@
/*
- * Copyright (C) 2010, Red Hat Inc.
- * and other copyright owners as documented in the project's IP log.
+ * Copyright (C) 2010, Red Hat Inc. and others
*
- * 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
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://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.
+ * SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.attributes;
@@ -52,21 +19,20 @@ import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.Collections;
-import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.attributes.Attribute.State;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.junit.Before;
import org.junit.Test;
/**
- * Tests attributes node behavior on the the index.
+ * Tests attributes node behavior on the index.
*/
public class AttributesNodeDirCacheIteratorTest extends RepositoryTestCase {
@@ -80,8 +46,6 @@ public class AttributesNodeDirCacheIteratorTest extends RepositoryTestCase {
private Git git;
- private TreeWalk walk;
-
@Override
@Before
public void setUp() throws Exception {
@@ -107,23 +71,25 @@ public class AttributesNodeDirCacheIteratorTest extends RepositoryTestCase {
// Adds file to index
git.add().addFilepattern(".").call();
- walk = beginWalk();
-
- assertIteration(F, ".gitattributes");
- assertIteration(F, "readme.txt", asList(EOL_LF));
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
+ assertIteration(walk, F, "readme.txt", asList(EOL_LF));
- assertIteration(D, "src");
+ assertIteration(walk, D, "src");
- assertIteration(D, "src/config");
- assertIteration(F, "src/config/.gitattributes");
- assertIteration(F, "src/config/readme.txt", asList(DELTA_UNSET));
- assertIteration(F, "src/config/windows.file", null);
- assertIteration(F, "src/config/windows.txt", asList(DELTA_UNSET));
+ assertIteration(walk, D, "src/config");
+ assertIteration(walk, F, "src/config/.gitattributes");
+ assertIteration(walk, F, "src/config/readme.txt",
+ asList(DELTA_UNSET));
+ assertIteration(walk, F, "src/config/windows.file", null);
+ assertIteration(walk, F, "src/config/windows.txt",
+ asList(DELTA_UNSET));
- assertIteration(F, "windows.file", null);
- assertIteration(F, "windows.txt", asList(EOL_LF));
+ assertIteration(walk, F, "windows.file", null);
+ assertIteration(walk, F, "windows.txt", asList(EOL_LF));
- endWalk();
+ assertFalse("Not all files tested", walk.next());
+ }
}
/**
@@ -140,17 +106,18 @@ public class AttributesNodeDirCacheIteratorTest extends RepositoryTestCase {
// Adds file to index
git.add().addFilepattern(".").call();
- walk = beginWalk();
- assertIteration(F, "l0.txt");
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, "l0.txt");
- assertIteration(D, "level1");
- assertIteration(F, "level1/l1.txt");
+ assertIteration(walk, D, "level1");
+ assertIteration(walk, F, "level1/l1.txt");
- assertIteration(D, "level1/level2");
- assertIteration(F, "level1/level2/l2.txt");
+ assertIteration(walk, D, "level1/level2");
+ assertIteration(walk, F, "level1/level2/l2.txt");
- endWalk();
+ assertFalse("Not all files tested", walk.next());
+ }
}
/**
@@ -168,18 +135,19 @@ public class AttributesNodeDirCacheIteratorTest extends RepositoryTestCase {
// Adds file to index
git.add().addFilepattern(".").call();
- walk = beginWalk();
- assertIteration(F, ".gitattributes");
- assertIteration(F, "l0.txt");
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
+ assertIteration(walk, F, "l0.txt");
- assertIteration(D, "level1");
- assertIteration(F, "level1/l1.txt");
+ assertIteration(walk, D, "level1");
+ assertIteration(walk, F, "level1/l1.txt");
- assertIteration(D, "level1/level2");
- assertIteration(F, "level1/level2/l2.txt");
+ assertIteration(walk, D, "level1/level2");
+ assertIteration(walk, F, "level1/level2/l2.txt");
- endWalk();
+ assertFalse("Not all files tested", walk.next());
+ }
}
@Test
@@ -193,18 +161,19 @@ public class AttributesNodeDirCacheIteratorTest extends RepositoryTestCase {
// Adds file to index
git.add().addFilepattern(".").call();
- walk = beginWalk();
- assertIteration(F, ".gitattributes");
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
- assertIteration(D, "levelA");
- assertIteration(F, "levelA/.gitattributes");
- assertIteration(F, "levelA/lA.txt");
+ assertIteration(walk, D, "levelA");
+ assertIteration(walk, F, "levelA/.gitattributes");
+ assertIteration(walk, F, "levelA/lA.txt");
- assertIteration(D, "levelB");
- assertIteration(F, "levelB/.gitattributes");
+ assertIteration(walk, D, "levelB");
+ assertIteration(walk, F, "levelB/.gitattributes");
- endWalk();
+ assertFalse("Not all files tested", walk.next());
+ }
}
@Test
@@ -217,25 +186,27 @@ public class AttributesNodeDirCacheIteratorTest extends RepositoryTestCase {
// Adds file to index
git.add().addFilepattern(".").call();
- walk = beginWalk();
- assertIteration(F, "gitattributes");
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, "gitattributes");
- assertIteration(F, "l0.txt");
+ assertIteration(walk, F, "l0.txt");
- assertIteration(D, "levelA");
- assertIteration(F, "levelA/file.gitattributes");
- assertIteration(F, "levelA/lA.txt");
+ assertIteration(walk, D, "levelA");
+ assertIteration(walk, F, "levelA/file.gitattributes");
+ assertIteration(walk, F, "levelA/lA.txt");
- endWalk();
+ assertFalse("Not all files tested", walk.next());
+ }
}
- private void assertIteration(FileMode type, String pathName)
+ private void assertIteration(TreeWalk walk, FileMode type, String pathName)
throws IOException {
- assertIteration(type, pathName, Collections.<Attribute> emptyList());
+ assertIteration(walk, type, pathName,
+ Collections.<Attribute> emptyList());
}
- private void assertIteration(FileMode type, String pathName,
+ private void assertIteration(TreeWalk walk, FileMode type, String pathName,
List<Attribute> nodeAttrs) throws IOException {
assertTrue("walk has entry", walk.next());
assertEquals(pathName, walk.getPathString());
@@ -243,27 +214,32 @@ public class AttributesNodeDirCacheIteratorTest extends RepositoryTestCase {
DirCacheIterator itr = walk.getTree(0, DirCacheIterator.class);
assertNotNull("has tree", itr);
- AttributesNode attributeNode = itr.getEntryAttributesNode(db
+ AttributesNode attributesNode = itr.getEntryAttributesNode(db
.newObjectReader());
- assertAttributeNode(pathName, attributeNode, nodeAttrs);
+ assertAttributesNode(walk, pathName, attributesNode, nodeAttrs);
if (D.equals(type))
walk.enterSubtree();
}
- private void assertAttributeNode(String pathName,
- AttributesNode attributeNode, List<Attribute> nodeAttrs) {
- if (attributeNode == null)
+ private void assertAttributesNode(TreeWalk walk, String pathName,
+ AttributesNode attributesNode, List<Attribute> nodeAttrs)
+ throws IOException {
+ if (attributesNode == null)
assertTrue(nodeAttrs == null || nodeAttrs.isEmpty());
else {
- Map<String, Attribute> entryAttributes = new LinkedHashMap<String, Attribute>();
- attributeNode.getAttributes(pathName, false, entryAttributes);
+ Attributes entryAttributes = new Attributes();
+ new AttributesHandler(walk,
+ () -> walk.getTree(CanonicalTreeParser.class))
+ .mergeAttributes(attributesNode, pathName, false,
+ entryAttributes);
if (nodeAttrs != null && !nodeAttrs.isEmpty()) {
for (Attribute attribute : nodeAttrs) {
- assertThat(entryAttributes.values(), hasItem(attribute));
+ assertThat(entryAttributes.getAll(),
+ hasItem(attribute));
}
} else {
assertTrue(
@@ -289,8 +265,4 @@ public class AttributesNodeDirCacheIteratorTest extends RepositoryTestCase {
newWalk.addTree(new DirCacheIterator(db.readDirCache()));
return newWalk;
}
-
- private void endWalk() throws IOException {
- assertFalse("Not all files tested", walk.next());
- }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeNodeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
index ea250369a0..dbbcb75da9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeNodeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
@@ -1,47 +1,15 @@
/*
- * Copyright (C) 2014, Obeo.
- * and other copyright owners as documented in the project's IP log.
+ * Copyright (C) 2014, Obeo. and others
*
- * 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
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://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.
+ * SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.attributes;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.attributes.Attribute.State.SET;
import static org.eclipse.jgit.attributes.Attribute.State.UNSET;
import static org.junit.Assert.assertEquals;
@@ -49,18 +17,20 @@ import static org.junit.Assert.assertEquals;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Set;
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
+import org.eclipse.jgit.treewalk.TreeWalk;
import org.junit.After;
import org.junit.Test;
/**
* Test {@link AttributesNode}
*/
-public class AttributeNodeTest {
+public class AttributesNodeTest {
+ private static final TreeWalk DUMMY_WALK = new TreeWalk(
+ new InMemoryRepository(new DfsRepositoryDescription("FooBar")));
private static final Attribute A_SET_ATTR = new Attribute("A", SET);
@@ -87,7 +57,7 @@ public class AttributeNodeTest {
String attributeFileContent = "*.type1 A -B C=value\n"
+ "*.type2 -A B C=value2";
- is = new ByteArrayInputStream(attributeFileContent.getBytes());
+ is = new ByteArrayInputStream(attributeFileContent.getBytes(UTF_8));
AttributesNode node = new AttributesNode();
node.parse(is);
assertAttribute("file.type1", node,
@@ -101,21 +71,21 @@ public class AttributeNodeTest {
String attributeFileContent = "!*.type1 A -B C=value\n"
+ "!*.type2 -A B C=value2";
- is = new ByteArrayInputStream(attributeFileContent.getBytes());
+ is = new ByteArrayInputStream(attributeFileContent.getBytes(UTF_8));
AttributesNode node = new AttributesNode();
node.parse(is);
- assertAttribute("file.type1", node, Collections.<Attribute> emptySet());
- assertAttribute("file.type2", node, Collections.<Attribute> emptySet());
+ assertAttribute("file.type1", node, new Attributes());
+ assertAttribute("file.type2", node, new Attributes());
}
@Test
public void testEmptyNegativeAttributeKey() throws IOException {
String attributeFileContent = "*.type1 - \n" //
+ "*.type2 - -A";
- is = new ByteArrayInputStream(attributeFileContent.getBytes());
+ is = new ByteArrayInputStream(attributeFileContent.getBytes(UTF_8));
AttributesNode node = new AttributesNode();
node.parse(is);
- assertAttribute("file.type1", node, Collections.<Attribute> emptySet());
+ assertAttribute("file.type1", node, new Attributes());
assertAttribute("file.type2", node, asSet(A_UNSET_ATTR));
}
@@ -124,11 +94,11 @@ public class AttributeNodeTest {
String attributeFileContent = "*.type1 = \n" //
+ "*.type2 =value\n"//
+ "*.type3 attr=\n";
- is = new ByteArrayInputStream(attributeFileContent.getBytes());
+ is = new ByteArrayInputStream(attributeFileContent.getBytes(UTF_8));
AttributesNode node = new AttributesNode();
node.parse(is);
- assertAttribute("file.type1", node, Collections.<Attribute> emptySet());
- assertAttribute("file.type2", node, Collections.<Attribute> emptySet());
+ assertAttribute("file.type1", node, new Attributes());
+ assertAttribute("file.type2", node, new Attributes());
assertAttribute("file.type3", node, asSet(new Attribute("attr", "")));
}
@@ -139,7 +109,7 @@ public class AttributeNodeTest {
+ " \n" //
+ "*.type2 -A B C=value2";
- is = new ByteArrayInputStream(attributeFileContent.getBytes());
+ is = new ByteArrayInputStream(attributeFileContent.getBytes(UTF_8));
AttributesNode node = new AttributesNode();
node.parse(is);
assertAttribute("file.type1", node,
@@ -155,7 +125,7 @@ public class AttributeNodeTest {
+ "*.type3 \t\t B\n" //
+ "*.type3\t-A";//
- is = new ByteArrayInputStream(attributeFileContent.getBytes());
+ is = new ByteArrayInputStream(attributeFileContent.getBytes(UTF_8));
AttributesNode node = new AttributesNode();
node.parse(is);
assertAttribute("file.type1", node,
@@ -165,18 +135,36 @@ public class AttributeNodeTest {
assertAttribute("file.type3", node, asSet(A_UNSET_ATTR, B_SET_ATTR));
}
+ @Test
+ public void testDoubleAsteriskAtEnd() throws IOException {
+ String attributeFileContent = "dir/** \tA -B\tC=value";
+
+ is = new ByteArrayInputStream(attributeFileContent.getBytes(UTF_8));
+ AttributesNode node = new AttributesNode();
+ node.parse(is);
+ assertAttribute("dir", node,
+ asSet(new Attribute[]{}));
+ assertAttribute("dir/", node,
+ asSet(new Attribute[]{}));
+ assertAttribute("dir/file.type1", node,
+ asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR));
+ assertAttribute("dir/sub/", node,
+ asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR));
+ assertAttribute("dir/sub/file.type1", node,
+ asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR));
+ }
+
private void assertAttribute(String path, AttributesNode node,
- Set<Attribute> attrs) {
- HashMap<String, Attribute> attributes = new HashMap<String, Attribute>();
- node.getAttributes(path, false, attributes);
- assertEquals(attrs, new HashSet<Attribute>(attributes.values()));
+ Attributes attrs) throws IOException {
+ Attributes attributes = new Attributes();
+ new AttributesHandler(DUMMY_WALK,
+ () -> DUMMY_WALK.getTree(CanonicalTreeParser.class))
+ .mergeAttributes(node, path, false, attributes);
+ assertEquals(attrs, attributes);
}
- static Set<Attribute> asSet(Attribute... attrs) {
- Set<Attribute> result = new HashSet<Attribute>();
- for (Attribute attr : attrs)
- result.add(attr);
- return result;
+ static Attributes asSet(Attribute... attrs) {
+ return new Attributes(attrs);
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java
index 64b0535d6a..c6c91386a2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java
@@ -1,44 +1,11 @@
/*
- * Copyright (C) 2014, Obeo.
- * and other copyright owners as documented in the project's IP log.
+ * Copyright (C) 2014, Obeo. and others
*
- * 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
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://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.
+ * SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.attributes;
@@ -53,15 +20,13 @@ import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
-import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
import org.eclipse.jgit.attributes.Attribute.State;
-import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
@@ -76,16 +41,10 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase {
private static final FileMode F = FileMode.REGULAR_FILE;
- private static Attribute EOL_CRLF = new Attribute("eol", "crlf");
-
private static Attribute EOL_LF = new Attribute("eol", "lf");
private static Attribute DELTA_UNSET = new Attribute("delta", State.UNSET);
- private static Attribute CUSTOM_VALUE = new Attribute("custom", "value");
-
- private TreeWalk walk;
-
@Test
public void testRules() throws Exception {
@@ -109,30 +68,26 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase {
writeTrashFile("src/config/windows.file", "");
writeTrashFile("src/config/windows.txt", "");
- walk = beginWalk();
-
- assertIteration(F, ".gitattributes");
- assertIteration(F, "global.txt", asList(EOL_LF), null,
- asList(CUSTOM_VALUE));
- assertIteration(F, "readme.txt", asList(EOL_LF), null,
- asList(CUSTOM_VALUE));
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
+ assertIteration(walk, F, "global.txt", asList(EOL_LF));
+ assertIteration(walk, F, "readme.txt", asList(EOL_LF));
- assertIteration(D, "src");
+ assertIteration(walk, D, "src");
- assertIteration(D, "src/config");
- assertIteration(F, "src/config/.gitattributes");
- assertIteration(F, "src/config/readme.txt", asList(DELTA_UNSET), null,
- asList(CUSTOM_VALUE));
- assertIteration(F, "src/config/windows.file", null, asList(EOL_CRLF),
- null);
- assertIteration(F, "src/config/windows.txt", asList(DELTA_UNSET),
- asList(EOL_CRLF), asList(CUSTOM_VALUE));
+ assertIteration(walk, D, "src/config");
+ assertIteration(walk, F, "src/config/.gitattributes");
+ assertIteration(walk, F, "src/config/readme.txt",
+ asList(DELTA_UNSET));
+ assertIteration(walk, F, "src/config/windows.file", null);
+ assertIteration(walk, F, "src/config/windows.txt",
+ asList(DELTA_UNSET));
- assertIteration(F, "windows.file", null, asList(EOL_CRLF), null);
- assertIteration(F, "windows.txt", asList(EOL_LF), asList(EOL_CRLF),
- asList(CUSTOM_VALUE));
+ assertIteration(walk, F, "windows.file", null);
+ assertIteration(walk, F, "windows.txt", asList(EOL_LF));
- endWalk();
+ assertFalse("Not all files tested", walk.next());
+ }
}
/**
@@ -147,17 +102,17 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase {
writeTrashFile("level1/l1.txt", "");
writeTrashFile("level1/level2/l2.txt", "");
- walk = beginWalk();
-
- assertIteration(F, "l0.txt");
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, "l0.txt");
- assertIteration(D, "level1");
- assertIteration(F, "level1/l1.txt");
+ assertIteration(walk, D, "level1");
+ assertIteration(walk, F, "level1/l1.txt");
- assertIteration(D, "level1/level2");
- assertIteration(F, "level1/level2/l2.txt");
+ assertIteration(walk, D, "level1/level2");
+ assertIteration(walk, F, "level1/level2/l2.txt");
- endWalk();
+ assertFalse("Not all files tested", walk.next());
+ }
}
/**
@@ -173,18 +128,18 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase {
writeTrashFile("level1/l1.txt", "");
writeTrashFile("level1/level2/l2.txt", "");
- walk = beginWalk();
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
+ assertIteration(walk, F, "l0.txt");
- assertIteration(F, ".gitattributes");
- assertIteration(F, "l0.txt");
+ assertIteration(walk, D, "level1");
+ assertIteration(walk, F, "level1/l1.txt");
- assertIteration(D, "level1");
- assertIteration(F, "level1/l1.txt");
+ assertIteration(walk, D, "level1/level2");
+ assertIteration(walk, F, "level1/level2/l2.txt");
- assertIteration(D, "level1/level2");
- assertIteration(F, "level1/level2/l2.txt");
-
- endWalk();
+ assertFalse("Not all files tested", walk.next());
+ }
}
@Test
@@ -196,30 +151,28 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase {
writeTrashFile("levelA/lA.txt", "");
- walk = beginWalk();
+ try (TreeWalk walk = beginWalk()) {
+ assertIteration(walk, F, ".gitattributes");
- assertIteration(F, ".gitattributes");
+ assertIteration(walk, D, "levelA");
+ assertIteration(walk, F, "levelA/.gitattributes");
+ assertIteration(walk, F, "levelA/lA.txt");
- assertIteration(D, "levelA");
- assertIteration(F, "levelA/.gitattributes");
- assertIteration(F, "levelA/lA.txt");
+ assertIteration(walk, D, "levelB");
+ assertIteration(walk, F, "levelB/.gitattributes");
- assertIteration(D, "levelB");
- assertIteration(F, "levelB/.gitattributes");
-
- endWalk();
+ assertFalse("Not all files tested", walk.next());
+ }
}
- private void assertIteration(FileMode type, String pathName)
+ private void assertIteration(TreeWalk walk, FileMode type, String pathName)
throws IOException {
- assertIteration(type, pathName, Collections.<Attribute> emptyList(),
- Collections.<Attribute> emptyList(),
+ assertIteration(walk, type, pathName,
Collections.<Attribute> emptyList());
}
- private void assertIteration(FileMode type, String pathName,
- List<Attribute> nodeAttrs, List<Attribute> infoAttrs,
- List<Attribute> globalAttrs)
+ private void assertIteration(TreeWalk walk, FileMode type, String pathName,
+ List<Attribute> nodeAttrs)
throws IOException {
assertTrue("walk has entry", walk.next());
assertEquals(pathName, walk.getPathString());
@@ -227,29 +180,30 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase {
WorkingTreeIterator itr = walk.getTree(0, WorkingTreeIterator.class);
assertNotNull("has tree", itr);
- AttributesNode attributeNode = itr.getEntryAttributesNode();
- assertAttributeNode(pathName, attributeNode, nodeAttrs);
- AttributesNode infoAttributeNode = itr.getInfoAttributesNode();
- assertAttributeNode(pathName, infoAttributeNode, infoAttrs);
- AttributesNode globalAttributeNode = itr.getGlobalAttributesNode();
- assertAttributeNode(pathName, globalAttributeNode, globalAttrs);
+ AttributesNode attributesNode = itr.getEntryAttributesNode();
+ assertAttributesNode(walk, pathName, attributesNode, nodeAttrs);
if (D.equals(type))
walk.enterSubtree();
}
- private void assertAttributeNode(String pathName,
- AttributesNode attributeNode, List<Attribute> nodeAttrs) {
- if (attributeNode == null)
+ private void assertAttributesNode(TreeWalk walk, String pathName,
+ AttributesNode attributesNode, List<Attribute> nodeAttrs)
+ throws IOException {
+ if (attributesNode == null)
assertTrue(nodeAttrs == null || nodeAttrs.isEmpty());
else {
- Map<String, Attribute> entryAttributes = new LinkedHashMap<String, Attribute>();
- attributeNode.getAttributes(pathName, false, entryAttributes);
+ Attributes entryAttributes = new Attributes();
+ new AttributesHandler(walk,
+ () -> walk.getTree(CanonicalTreeParser.class))
+ .mergeAttributes(attributesNode, pathName, false,
+ entryAttributes);
if (nodeAttrs != null && !nodeAttrs.isEmpty()) {
for (Attribute attribute : nodeAttrs) {
- assertThat(entryAttributes.values(), hasItem(attribute));
+ assertThat(entryAttributes.getAll(),
+ hasItem(attribute));
}
} else {
assertTrue(
@@ -270,13 +224,9 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase {
writeTrashFile(name, data.toString());
}
- private TreeWalk beginWalk() throws CorruptObjectException {
+ private TreeWalk beginWalk() {
TreeWalk newWalk = new TreeWalk(db);
newWalk.addTree(new FileTreeIterator(db));
return newWalk;
}
-
- private void endWalk() throws IOException {
- assertFalse("Not all files tested", walk.next());
- }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java
new file mode 100644
index 0000000000..562a515721
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2017 Thomas Wolf <thomas.wolf@paranor.ch> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.attributes;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.NotIgnoredFilter;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FS.ExecutionResult;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.TemporaryBuffer;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests that verify that the attributes of files in a repository are the same
+ * in JGit and in C-git.
+ */
+public class CGitAttributesTest extends RepositoryTestCase {
+
+ @Before
+ public void initRepo() throws IOException {
+ // Because we run C-git, we must ensure that global or user exclude
+ // files cannot influence the tests. So we set core.excludesFile to an
+ // empty file inside the repository.
+ StoredConfig config = db.getConfig();
+ File fakeUserGitignore = writeTrashFile(".fake_user_gitignore", "");
+ config.setString("core", null, "excludesFile",
+ fakeUserGitignore.getAbsolutePath());
+ // Disable case-insensitivity -- JGit doesn't handle that yet.
+ config.setBoolean("core", null, "ignoreCase", false);
+ // And try to switch off the global attributes file, too.
+ config.setString("core", null, "attributesFile",
+ fakeUserGitignore.getAbsolutePath());
+ config.save();
+ }
+
+ private void createFiles(String... paths) throws IOException {
+ for (String path : paths) {
+ writeTrashFile(path, "x");
+ }
+ }
+
+ private String toString(TemporaryBuffer b) throws IOException {
+ return RawParseUtils.decode(b.toByteArray());
+ }
+
+ private Attribute fromString(String key, String value) {
+ if ("set".equals(value)) {
+ return new Attribute(key, Attribute.State.SET);
+ }
+ if ("unset".equals(value)) {
+ return new Attribute(key, Attribute.State.UNSET);
+ }
+ if ("unspecified".equals(value)) {
+ return new Attribute(key, Attribute.State.UNSPECIFIED);
+ }
+ return new Attribute(key, value);
+ }
+
+ private LinkedHashMap<String, Attributes> cgitAttributes(
+ Set<String> allFiles) throws Exception {
+ FS fs = db.getFS();
+ StringBuilder input = new StringBuilder();
+ for (String filename : allFiles) {
+ input.append(filename).append('\n');
+ }
+ ProcessBuilder builder = fs.runInShell("git",
+ new String[] { "check-attr", "--stdin", "--all" });
+ builder.directory(db.getWorkTree());
+ builder.environment().put("HOME", fs.userHome().getAbsolutePath());
+ ExecutionResult result = fs.execute(builder, new ByteArrayInputStream(
+ input.toString().getBytes(UTF_8)));
+ String errorOut = toString(result.getStderr());
+ assertEquals("External git failed", "exit 0\n",
+ "exit " + result.getRc() + '\n' + errorOut);
+ LinkedHashMap<String, Attributes> map = new LinkedHashMap<>();
+ try (BufferedReader r = new BufferedReader(new InputStreamReader(
+ new BufferedInputStream(result.getStdout().openInputStream()),
+ UTF_8))) {
+ r.lines().forEach(line -> {
+ // Parse the line and add to result map
+ int i = line.indexOf(':');
+ String path = line.substring(0, i).trim();
+ int start = i + 1;
+ i = line.indexOf(':', start);
+ String key = line.substring(start, i).trim();
+ String value = line.substring(i + 1).trim();
+ Attribute attr = fromString(key, value);
+ Attributes attrs = map.get(path);
+ if (attrs == null) {
+ attrs = new Attributes(attr);
+ map.put(path, attrs);
+ } else {
+ attrs.put(attr);
+ }
+ });
+ }
+ return map;
+ }
+
+ private LinkedHashMap<String, Attributes> jgitAttributes()
+ throws IOException {
+ // Do a tree walk and return a list of all files and directories with
+ // their attributes
+ LinkedHashMap<String, Attributes> result = new LinkedHashMap<>();
+ try (TreeWalk walk = new TreeWalk(db)) {
+ walk.addTree(new FileTreeIterator(db));
+ walk.setFilter(new NotIgnoredFilter(0));
+ while (walk.next()) {
+ String path = walk.getPathString();
+ if (walk.isSubtree() && !path.endsWith("/")) {
+ // git check-attr expects directory paths to end with a
+ // slash
+ path += '/';
+ }
+ Attributes attrs = walk.getAttributes();
+ if (attrs != null && !attrs.isEmpty()) {
+ result.put(path, attrs);
+ } else {
+ result.put(path, null);
+ }
+ if (walk.isSubtree()) {
+ walk.enterSubtree();
+ }
+ }
+ }
+ return result;
+ }
+
+ private void assertSameAsCGit() throws Exception {
+ LinkedHashMap<String, Attributes> jgit = jgitAttributes();
+ LinkedHashMap<String, Attributes> cgit = cgitAttributes(jgit.keySet());
+ // remove all without attributes
+ Iterator<Map.Entry<String, Attributes>> iterator = jgit.entrySet()
+ .iterator();
+ while (iterator.hasNext()) {
+ Map.Entry<String, Attributes> entry = iterator.next();
+ if (entry.getValue() == null) {
+ iterator.remove();
+ }
+ }
+ assertArrayEquals("JGit attributes differ from C git",
+ cgit.entrySet().toArray(), jgit.entrySet().toArray());
+ }
+
+ @Test
+ public void testBug508568() throws Exception {
+ createFiles("foo.xml/bar.jar", "sub/foo.xml/bar.jar");
+ writeTrashFile(".gitattributes", "*.xml xml\n" + "*.jar jar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testRelativePath() throws Exception {
+ createFiles("sub/foo.txt");
+ writeTrashFile("sub/.gitattributes", "sub/** sub\n" + "*.txt txt\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testRelativePaths() throws Exception {
+ createFiles("sub/foo.txt", "sub/sub/bar", "foo/sub/a.txt",
+ "foo/sub/bar/a.tmp");
+ writeTrashFile(".gitattributes", "sub/** sub\n" + "*.txt txt\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testNestedMatchNot() throws Exception {
+ createFiles("foo.xml/bar.jar", "foo.xml/bar.xml", "sub/b.jar",
+ "sub/b.xml");
+ writeTrashFile("sub/.gitattributes", "*.xml xml\n" + "*.jar jar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testNestedMatch() throws Exception {
+ // This is an interesting test. At the time of this writing, the
+ // gitignore documentation says: "In other words, foo/ will match a
+ // directory foo AND PATHS UNDERNEATH IT, but will not match a regular
+ // file or a symbolic link foo". (Emphasis added.) And gitattributes is
+ // supposed to follow the same rules. But the documentation appears to
+ // lie: C-git will *not* apply the attribute "xml" to *any* files in
+ // any subfolder "foo" here. It will only apply the "jar" attribute
+ // to the three *.jar files.
+ //
+ // The point is probably that ignores are handled top-down, and once a
+ // directory "foo" is matched (here: on paths "foo" and "sub/foo" by
+ // pattern "foo/"), the directory is excluded and the gitignore
+ // documentation also says: "It is not possible to re-include a file if
+ // a parent directory of that file is excluded." So once the pattern
+ // "foo/" has matched, it appears as if everything beneath would also be
+ // matched.
+ //
+ // But not so for gitattributes! The foo/ rule only matches the
+ // directory itself, but not anything beneath.
+ createFiles("foo/bar.jar", "foo/bar.xml", "sub/b.jar", "sub/b.xml",
+ "sub/foo/b.jar");
+ writeTrashFile(".gitattributes",
+ "foo/ xml\n" + "sub/foo/ sub\n" + "*.jar jar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testNestedMatchWithWildcard() throws Exception {
+ // See above.
+ createFiles("foo/bar.jar", "foo/bar.xml", "sub/b.jar", "sub/b.xml",
+ "sub/foo/b.jar");
+ writeTrashFile(".gitattributes",
+ "**/foo/ xml\n" + "*/foo/ sub\n" + "*.jar jar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testNestedMatchRecursive() throws Exception {
+ createFiles("foo/bar.jar", "foo/bar.xml", "sub/b.jar", "sub/b.xml",
+ "sub/foo/b.jar");
+ writeTrashFile(".gitattributes", "foo/** xml\n" + "*.jar jar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testStarMatchOnSlashNot() throws Exception {
+ createFiles("sub/a.txt", "foo/sext", "foo/s.txt");
+ writeTrashFile(".gitattributes", "s*xt bar");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testPrefixMatchNot() throws Exception {
+ createFiles("src/new/foo.txt");
+ writeTrashFile(".gitattributes", "src/new bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testComplexPathMatchNot() throws Exception {
+ createFiles("src/new/foo.txt", "src/ndw");
+ writeTrashFile(".gitattributes", "s[p-s]c/n[de]w bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testStarPathMatchNot() throws Exception {
+ createFiles("src/new/foo.txt", "src/ndw");
+ writeTrashFile(".gitattributes", "src/* bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubSimple() throws Exception {
+ createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");
+ writeTrashFile(".gitattributes", "src/new/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursive() throws Exception {
+ createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");
+ writeTrashFile(".gitattributes", "**/src/new/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack() throws Exception {
+ createFiles("src/new/foo.txt", "src/src/new/foo.txt");
+ writeTrashFile(".gitattributes", "**/src/new/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack2() throws Exception {
+ createFiles("src/new/foo.txt", "src/src/new/foo.txt");
+ writeTrashFile(".gitattributes", "**/**/src/new/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack3() throws Exception {
+ createFiles("src/new/src/new/foo.txt",
+ "foo/src/new/bar/src/new/foo.txt");
+ writeTrashFile(".gitattributes", "**/src/new/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack4() throws Exception {
+ createFiles("src/src/src/new/foo.txt",
+ "foo/src/src/bar/src/new/foo.txt");
+ writeTrashFile(".gitattributes", "**/src/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack5() throws Exception {
+ createFiles("x/a/a/b/foo.txt", "x/y/z/b/a/b/foo.txt",
+ "x/y/a/a/a/a/b/foo.txt", "x/y/a/a/a/a/b/a/b/foo.txt");
+ writeTrashFile(".gitattributes", "**/*/a/b bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack6() throws Exception {
+ createFiles("x/a/a/b/foo.txt", "x/y/a/b/a/b/foo.txt");
+ writeTrashFile(".gitattributes", "**/*/**/a/b bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryWildmatchDoesNotMatchFiles1() throws Exception {
+ createFiles("a", "dir/b", "dir/sub/c");
+ writeTrashFile(".gitattributes", "**/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryWildmatchDoesNotMatchFiles2() throws Exception {
+ createFiles("a", "dir/b", "dir/sub/c");
+ writeTrashFile(".gitattributes", "**/**/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryWildmatchDoesNotMatchFiles3() throws Exception {
+ createFiles("a", "x/b", "sub/x/c", "sub/x/d/e");
+ writeTrashFile(".gitattributes", "x/**/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryWildmatchDoesNotMatchFiles4() throws Exception {
+ createFiles("a", "dir/x", "dir/sub1/x", "dir/sub2/x/y");
+ writeTrashFile(".gitattributes", "x/**/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubComplex() throws Exception {
+ createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");
+ writeTrashFile(".gitattributes", "s[rs]c/n*/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatch() throws Exception {
+ createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");
+ writeTrashFile(".gitattributes", "new/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testBracketsInGroup() throws Exception {
+ createFiles("[", "]", "[]", "][", "[[]", "[]]", "[[]]");
+ writeTrashFile(".gitattributes", "[[]] bar1\n" + "[\\[]] bar2\n"
+ + "[[\\]] bar3\n" + "[\\[\\]] bar4\n");
+ assertSameAsCGit();
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java
new file mode 100644
index 0000000000..698fdb31a8
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java
@@ -0,0 +1,839 @@
+/*
+ * Copyright (C) 2014, Obeo. and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.attributes;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.NoFilepatternException;
+import org.eclipse.jgit.attributes.Attribute.State;
+import org.eclipse.jgit.dircache.DirCacheIterator;
+import org.eclipse.jgit.errors.NoWorkTreeException;
+import org.eclipse.jgit.junit.JGitTestUtil;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests the attributes are correctly computed in a {@link TreeWalk}.
+ *
+ * @see TreeWalk#getAttributes()
+ */
+public class TreeWalkAttributeTest extends RepositoryTestCase {
+
+ private static final FileMode M = FileMode.MISSING;
+
+ private static final FileMode D = FileMode.TREE;
+
+ private static final FileMode F = FileMode.REGULAR_FILE;
+
+ private static Attribute EOL_CRLF = new Attribute("eol", "crlf");
+
+ private static Attribute EOL_LF = new Attribute("eol", "lf");
+
+ private static Attribute TEXT_SET = new Attribute("text", State.SET);
+
+ private static Attribute TEXT_UNSET = new Attribute("text", State.UNSET);
+
+ private static Attribute DELTA_UNSET = new Attribute("delta", State.UNSET);
+
+ private static Attribute DELTA_SET = new Attribute("delta", State.SET);
+
+ private static Attribute CUSTOM_GLOBAL = new Attribute("custom", "global");
+
+ private static Attribute CUSTOM_INFO = new Attribute("custom", "info");
+
+ private static Attribute CUSTOM_ROOT = new Attribute("custom", "root");
+
+ private static Attribute CUSTOM_PARENT = new Attribute("custom", "parent");
+
+ private static Attribute CUSTOM_CURRENT = new Attribute("custom", "current");
+
+ private static Attribute CUSTOM2_UNSET = new Attribute("custom2",
+ State.UNSET);
+
+ private static Attribute CUSTOM2_SET = new Attribute("custom2", State.SET);
+
+ private TreeWalk walk;
+
+ private TreeWalk ci_walk;
+
+ private Git git;
+
+ private File customAttributeFile;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ git = new Git(db);
+ }
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ if (walk != null) {
+ walk.close();
+ }
+ if (ci_walk != null) {
+ ci_walk.close();
+ }
+ super.tearDown();
+ if (customAttributeFile != null)
+ customAttributeFile.delete();
+ }
+
+ /**
+ * Checks that the attributes are computed correctly depending on the
+ * operation type.
+ * <p>
+ * In this test we changed the content of the attribute files in the working
+ * tree compared to the one in the index.
+ * </p>
+ *
+ * @throws IOException
+ * @throws NoFilepatternException
+ * @throws GitAPIException
+ */
+ @Test
+ public void testCheckinCheckoutDifferences() throws IOException,
+ NoFilepatternException, GitAPIException {
+
+ writeGlobalAttributeFile("globalAttributesFile", "*.txt -custom2");
+ writeAttributesFile(".git/info/attributes", "*.txt eol=crlf");
+ writeAttributesFile(".gitattributes", "*.txt custom=root");
+ writeAttributesFile("level1/.gitattributes", "*.txt text");
+ writeAttributesFile("level1/level2/.gitattributes", "*.txt -delta");
+
+ writeTrashFile("l0.txt", "");
+
+ writeTrashFile("level1/l1.txt", "");
+
+ writeTrashFile("level1/level2/l2.txt", "");
+
+ git.add().addFilepattern(".").call();
+
+ beginWalk();
+
+ // Modify all attributes
+ writeGlobalAttributeFile("globalAttributesFile", "*.txt custom2");
+ writeAttributesFile(".git/info/attributes", "*.txt eol=lf");
+ writeAttributesFile(".gitattributes", "*.txt custom=info");
+ writeAttributesFile("level1/.gitattributes", "*.txt -text");
+ writeAttributesFile("level1/level2/.gitattributes", "*.txt delta");
+
+ assertEntry(F, ".gitattributes");
+ assertEntry(F, "l0.txt", asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET),
+ asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET));
+
+ assertEntry(D, "level1");
+ assertEntry(F, "level1/.gitattributes");
+ assertEntry(F, "level1/l1.txt",
+ asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET, TEXT_UNSET),
+ asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET, TEXT_SET));
+
+ assertEntry(D, "level1/level2");
+ assertEntry(F, "level1/level2/.gitattributes");
+ assertEntry(F, "level1/level2/l2.txt",
+ asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET, TEXT_UNSET, DELTA_SET),
+ asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET, TEXT_SET, DELTA_UNSET));
+
+ endWalk();
+ }
+
+ /**
+ * Checks that the index is used as fallback when the git attributes file
+ * are missing in the working tree.
+ *
+ * @throws IOException
+ * @throws NoFilepatternException
+ * @throws GitAPIException
+ */
+ @Test
+ public void testIndexOnly() throws IOException, NoFilepatternException,
+ GitAPIException {
+ List<File> attrFiles = new ArrayList<>();
+ attrFiles.add(writeGlobalAttributeFile("globalAttributesFile",
+ "*.txt -custom2"));
+ attrFiles.add(writeAttributesFile(".git/info/attributes",
+ "*.txt eol=crlf"));
+ attrFiles
+ .add(writeAttributesFile(".gitattributes", "*.txt custom=root"));
+ attrFiles
+ .add(writeAttributesFile("level1/.gitattributes", "*.txt text"));
+ attrFiles.add(writeAttributesFile("level1/level2/.gitattributes",
+ "*.txt -delta"));
+
+ writeTrashFile("l0.txt", "");
+
+ writeTrashFile("level1/l1.txt", "");
+
+ writeTrashFile("level1/level2/l2.txt", "");
+
+ git.add().addFilepattern(".").call();
+
+ // Modify all attributes
+ for (File attrFile : attrFiles)
+ attrFile.delete();
+
+ beginWalk();
+
+ assertEntry(M, ".gitattributes");
+ assertEntry(F, "l0.txt", asSet(CUSTOM_ROOT));
+
+ assertEntry(D, "level1");
+ assertEntry(M, "level1/.gitattributes");
+ assertEntry(F, "level1/l1.txt",
+
+ asSet(CUSTOM_ROOT, TEXT_SET));
+
+ assertEntry(D, "level1/level2");
+ assertEntry(M, "level1/level2/.gitattributes");
+ assertEntry(F, "level1/level2/l2.txt",
+
+ asSet(CUSTOM_ROOT, TEXT_SET, DELTA_UNSET));
+
+ endWalk();
+ }
+
+ /**
+ * Check that we search in the working tree for attributes although the file
+ * we are currently inspecting does not exist anymore in the working tree.
+ *
+ * @throws IOException
+ * @throws NoFilepatternException
+ * @throws GitAPIException
+ */
+ @Test
+ public void testIndexOnly2()
+ throws IOException, NoFilepatternException, GitAPIException {
+ File l2 = writeTrashFile("level1/level2/l2.txt", "");
+ writeTrashFile("level1/level2/l1.txt", "");
+
+ git.add().addFilepattern(".").call();
+
+ writeAttributesFile(".gitattributes", "*.txt custom=root");
+ assertTrue(l2.delete());
+
+ beginWalk();
+
+ assertEntry(F, ".gitattributes");
+ assertEntry(D, "level1");
+ assertEntry(D, "level1/level2");
+ assertEntry(F, "level1/level2/l1.txt", asSet(CUSTOM_ROOT));
+ assertEntry(M, "level1/level2/l2.txt", asSet(CUSTOM_ROOT));
+
+ endWalk();
+ }
+
+ /**
+ * Basic test for git attributes.
+ * <p>
+ * In this use case files are present in both the working tree and the index
+ * </p>
+ *
+ * @throws IOException
+ * @throws NoFilepatternException
+ * @throws GitAPIException
+ */
+ @Test
+ public void testRules() throws IOException, NoFilepatternException,
+ GitAPIException {
+ writeAttributesFile(".git/info/attributes", "windows* eol=crlf");
+
+ writeAttributesFile(".gitattributes", "*.txt eol=lf");
+ writeTrashFile("windows.file", "");
+ writeTrashFile("windows.txt", "");
+ writeTrashFile("readme.txt", "");
+
+ writeAttributesFile("src/config/.gitattributes", "*.txt -delta");
+ writeTrashFile("src/config/readme.txt", "");
+ writeTrashFile("src/config/windows.file", "");
+ writeTrashFile("src/config/windows.txt", "");
+
+ beginWalk();
+
+ git.add().addFilepattern(".").call();
+
+ assertEntry(F, ".gitattributes");
+ assertEntry(F, "readme.txt", asSet(EOL_LF));
+
+ assertEntry(D, "src");
+ assertEntry(D, "src/config");
+ assertEntry(F, "src/config/.gitattributes");
+ assertEntry(F, "src/config/readme.txt", asSet(DELTA_UNSET, EOL_LF));
+ assertEntry(F, "src/config/windows.file", asSet(EOL_CRLF));
+ assertEntry(F, "src/config/windows.txt", asSet(DELTA_UNSET, EOL_CRLF));
+
+ assertEntry(F, "windows.file", asSet(EOL_CRLF));
+ assertEntry(F, "windows.txt", asSet(EOL_CRLF));
+
+ endWalk();
+ }
+
+ /**
+ * Checks that if there is no .gitattributes file in the repository
+ * everything still work fine.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testNoAttributes() throws IOException {
+ writeTrashFile("l0.txt", "");
+ writeTrashFile("level1/l1.txt", "");
+ writeTrashFile("level1/level2/l2.txt", "");
+
+ beginWalk();
+
+ assertEntry(F, "l0.txt");
+
+ assertEntry(D, "level1");
+ assertEntry(F, "level1/l1.txt");
+
+ assertEntry(D, "level1/level2");
+ assertEntry(F, "level1/level2/l2.txt");
+
+ endWalk();
+ }
+
+ /**
+ * Checks that an empty .gitattribute file does not return incorrect value.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testEmptyGitAttributeFile() throws IOException {
+ writeAttributesFile(".git/info/attributes", "");
+ writeTrashFile("l0.txt", "");
+ writeAttributesFile(".gitattributes", "");
+ writeTrashFile("level1/l1.txt", "");
+ writeTrashFile("level1/level2/l2.txt", "");
+
+ beginWalk();
+
+ assertEntry(F, ".gitattributes");
+ assertEntry(F, "l0.txt");
+
+ assertEntry(D, "level1");
+ assertEntry(F, "level1/l1.txt");
+
+ assertEntry(D, "level1/level2");
+ assertEntry(F, "level1/level2/l2.txt");
+
+ endWalk();
+ }
+
+ @Test
+ public void testNoMatchingAttributes() throws IOException {
+ writeAttributesFile(".git/info/attributes", "*.java delta");
+ writeAttributesFile(".gitattributes", "*.java -delta");
+ writeAttributesFile("levelA/.gitattributes", "*.java eol=lf");
+ writeAttributesFile("levelB/.gitattributes", "*.txt eol=lf");
+
+ writeTrashFile("levelA/lA.txt", "");
+
+ beginWalk();
+
+ assertEntry(F, ".gitattributes");
+
+ assertEntry(D, "levelA");
+ assertEntry(F, "levelA/.gitattributes");
+ assertEntry(F, "levelA/lA.txt");
+
+ assertEntry(D, "levelB");
+ assertEntry(F, "levelB/.gitattributes");
+
+ endWalk();
+ }
+
+ /**
+ * Checks that $GIT_DIR/info/attributes file has the highest precedence.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testPrecedenceInfo() throws IOException {
+ writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
+ writeAttributesFile(".git/info/attributes", "*.txt custom=info");
+ writeAttributesFile(".gitattributes", "*.txt custom=root");
+ writeAttributesFile("level1/.gitattributes", "*.txt custom=parent");
+ writeAttributesFile("level1/level2/.gitattributes",
+ "*.txt custom=current");
+
+ writeTrashFile("level1/level2/file.txt", "");
+
+ beginWalk();
+
+ assertEntry(F, ".gitattributes");
+
+ assertEntry(D, "level1");
+ assertEntry(F, "level1/.gitattributes");
+
+ assertEntry(D, "level1/level2");
+ assertEntry(F, "level1/level2/.gitattributes");
+ assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_INFO));
+
+ endWalk();
+ }
+
+ /**
+ * Checks that a subfolder ".gitattributes" file has precedence over its
+ * parent.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testPrecedenceCurrent() throws IOException {
+ writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
+ writeAttributesFile(".gitattributes", "*.txt custom=root");
+ writeAttributesFile("level1/.gitattributes", "*.txt custom=parent");
+ writeAttributesFile("level1/level2/.gitattributes",
+ "*.txt custom=current");
+
+ writeTrashFile("level1/level2/file.txt", "");
+
+ beginWalk();
+
+ assertEntry(F, ".gitattributes");
+
+ assertEntry(D, "level1");
+ assertEntry(F, "level1/.gitattributes");
+
+ assertEntry(D, "level1/level2");
+ assertEntry(F, "level1/level2/.gitattributes");
+ assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_CURRENT));
+
+ endWalk();
+ }
+
+ /**
+ * Checks that the parent ".gitattributes" file is used as fallback.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testPrecedenceParent() throws IOException {
+ writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
+ writeAttributesFile(".gitattributes", "*.txt custom=root");
+ writeAttributesFile("level1/.gitattributes", "*.txt custom=parent");
+
+ writeTrashFile("level1/level2/file.txt", "");
+
+ beginWalk();
+
+ assertEntry(F, ".gitattributes");
+
+ assertEntry(D, "level1");
+ assertEntry(F, "level1/.gitattributes");
+
+ assertEntry(D, "level1/level2");
+ assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_PARENT));
+
+ endWalk();
+ }
+
+ /**
+ * Checks that the grand parent ".gitattributes" file is used as fallback.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testPrecedenceRoot() throws IOException {
+ writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
+ writeAttributesFile(".gitattributes", "*.txt custom=root");
+
+ writeTrashFile("level1/level2/file.txt", "");
+
+ beginWalk();
+
+ assertEntry(F, ".gitattributes");
+
+ assertEntry(D, "level1");
+
+ assertEntry(D, "level1/level2");
+ assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_ROOT));
+
+ endWalk();
+ }
+
+ /**
+ * Checks that the global attribute file is used as fallback.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testPrecedenceGlobal() throws IOException {
+ writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
+
+ writeTrashFile("level1/level2/file.txt", "");
+
+ beginWalk();
+
+ assertEntry(D, "level1");
+
+ assertEntry(D, "level1/level2");
+ assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_GLOBAL));
+
+ endWalk();
+ }
+
+ /**
+ * Checks the precedence on a hierarchy with multiple attributes.
+ * <p>
+ * In this test all file are present in both the working tree and the index.
+ * </p>
+ *
+ * @throws IOException
+ * @throws GitAPIException
+ * @throws NoFilepatternException
+ */
+ @Test
+ public void testHierarchyBothIterator() throws IOException,
+ NoFilepatternException, GitAPIException {
+ writeAttributesFile(".git/info/attributes", "*.global eol=crlf");
+ writeAttributesFile(".gitattributes", "*.local eol=lf");
+ writeAttributesFile("level1/.gitattributes", "*.local text");
+ writeAttributesFile("level1/level2/.gitattributes", "*.local -text");
+
+ writeTrashFile("l0.global", "");
+ writeTrashFile("l0.local", "");
+
+ writeTrashFile("level1/l1.global", "");
+ writeTrashFile("level1/l1.local", "");
+
+ writeTrashFile("level1/level2/l2.global", "");
+ writeTrashFile("level1/level2/l2.local", "");
+
+ beginWalk();
+
+ git.add().addFilepattern(".").call();
+
+ assertEntry(F, ".gitattributes");
+ assertEntry(F, "l0.global", asSet(EOL_CRLF));
+ assertEntry(F, "l0.local", asSet(EOL_LF));
+
+ assertEntry(D, "level1");
+ assertEntry(F, "level1/.gitattributes");
+ assertEntry(F, "level1/l1.global", asSet(EOL_CRLF));
+ assertEntry(F, "level1/l1.local", asSet(EOL_LF, TEXT_SET));
+
+ assertEntry(D, "level1/level2");
+ assertEntry(F, "level1/level2/.gitattributes");
+ assertEntry(F, "level1/level2/l2.global", asSet(EOL_CRLF));
+ assertEntry(F, "level1/level2/l2.local", asSet(EOL_LF, TEXT_UNSET));
+
+ endWalk();
+
+ }
+
+ /**
+ * Checks the precedence on a hierarchy with multiple attributes.
+ * <p>
+ * In this test all file are present only in the working tree.
+ * </p>
+ *
+ * @throws IOException
+ * @throws GitAPIException
+ * @throws NoFilepatternException
+ */
+ @Test
+ public void testHierarchyWorktreeOnly()
+ throws IOException, NoFilepatternException, GitAPIException {
+ writeAttributesFile(".git/info/attributes", "*.global eol=crlf");
+ writeAttributesFile(".gitattributes", "*.local eol=lf");
+ writeAttributesFile("level1/.gitattributes", "*.local text");
+ writeAttributesFile("level1/level2/.gitattributes", "*.local -text");
+
+ writeTrashFile("l0.global", "");
+ writeTrashFile("l0.local", "");
+
+ writeTrashFile("level1/l1.global", "");
+ writeTrashFile("level1/l1.local", "");
+
+ writeTrashFile("level1/level2/l2.global", "");
+ writeTrashFile("level1/level2/l2.local", "");
+
+ beginWalk();
+
+ assertEntry(F, ".gitattributes");
+ assertEntry(F, "l0.global", asSet(EOL_CRLF));
+ assertEntry(F, "l0.local", asSet(EOL_LF));
+
+ assertEntry(D, "level1");
+ assertEntry(F, "level1/.gitattributes");
+ assertEntry(F, "level1/l1.global", asSet(EOL_CRLF));
+ assertEntry(F, "level1/l1.local", asSet(EOL_LF, TEXT_SET));
+
+ assertEntry(D, "level1/level2");
+ assertEntry(F, "level1/level2/.gitattributes");
+ assertEntry(F, "level1/level2/l2.global", asSet(EOL_CRLF));
+ assertEntry(F, "level1/level2/l2.local", asSet(EOL_LF, TEXT_UNSET));
+
+ endWalk();
+
+ }
+
+ /**
+ * Checks that the list of attributes is an aggregation of all the
+ * attributes from the attributes files hierarchy.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testAggregation() throws IOException {
+ writeGlobalAttributeFile("globalAttributesFile", "*.txt -custom2");
+ writeAttributesFile(".git/info/attributes", "*.txt eol=crlf");
+ writeAttributesFile(".gitattributes", "*.txt custom=root");
+ writeAttributesFile("level1/.gitattributes", "*.txt text");
+ writeAttributesFile("level1/level2/.gitattributes", "*.txt -delta");
+
+ writeTrashFile("l0.txt", "");
+
+ writeTrashFile("level1/l1.txt", "");
+
+ writeTrashFile("level1/level2/l2.txt", "");
+
+ beginWalk();
+
+ assertEntry(F, ".gitattributes");
+ assertEntry(F, "l0.txt", asSet(EOL_CRLF, CUSTOM_ROOT, CUSTOM2_UNSET));
+
+ assertEntry(D, "level1");
+ assertEntry(F, "level1/.gitattributes");
+ assertEntry(F, "level1/l1.txt",
+ asSet(EOL_CRLF, CUSTOM_ROOT, TEXT_SET, CUSTOM2_UNSET));
+
+ assertEntry(D, "level1/level2");
+ assertEntry(F, "level1/level2/.gitattributes");
+ assertEntry(
+ F,
+ "level1/level2/l2.txt",
+ asSet(EOL_CRLF, CUSTOM_ROOT, TEXT_SET, DELTA_UNSET,
+ CUSTOM2_UNSET));
+
+ endWalk();
+
+ }
+
+ /**
+ * Checks that the last entry in .gitattributes is used if 2 lines match the
+ * same attribute
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testOverriding() throws IOException {
+ writeAttributesFile(".git/info/attributes",//
+ //
+ "*.txt custom=current",//
+ "*.txt custom=parent",//
+ "*.txt custom=root",//
+ "*.txt custom=info",
+ //
+ "*.txt delta",//
+ "*.txt -delta",
+ //
+ "*.txt eol=lf",//
+ "*.txt eol=crlf",
+ //
+ "*.txt text",//
+ "*.txt -text");
+
+ writeTrashFile("l0.txt", "");
+ beginWalk();
+
+ assertEntry(F, "l0.txt",
+ asSet(TEXT_UNSET, EOL_CRLF, DELTA_UNSET, CUSTOM_INFO));
+
+ endWalk();
+ }
+
+ /**
+ * Checks that the last value of an attribute is used if in the same line an
+ * attribute is defined several time.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testOverriding2() throws IOException {
+ writeAttributesFile(".git/info/attributes",
+ "*.txt custom=current custom=parent custom=root custom=info",//
+ "*.txt delta -delta",//
+ "*.txt eol=lf eol=crlf",//
+ "*.txt text -text");
+ writeTrashFile("l0.txt", "");
+ beginWalk();
+
+ assertEntry(F, "l0.txt",
+ asSet(TEXT_UNSET, EOL_CRLF, DELTA_UNSET, CUSTOM_INFO));
+
+ endWalk();
+ }
+
+ @Test
+ public void testRulesInherited() throws Exception {
+ writeAttributesFile(".gitattributes", "**/*.txt eol=lf");
+ writeTrashFile("src/config/readme.txt", "");
+ writeTrashFile("src/config/windows.file", "");
+
+ beginWalk();
+
+ assertEntry(F, ".gitattributes");
+ assertEntry(D, "src");
+ assertEntry(D, "src/config");
+
+ assertEntry(F, "src/config/readme.txt", asSet(EOL_LF));
+ assertEntry(F, "src/config/windows.file",
+ Collections.<Attribute> emptySet());
+
+ endWalk();
+ }
+
+ private void beginWalk() throws NoWorkTreeException, IOException {
+ walk = new TreeWalk(db);
+ walk.addTree(new FileTreeIterator(db));
+ walk.addTree(new DirCacheIterator(db.readDirCache()));
+
+ ci_walk = new TreeWalk(db);
+ ci_walk.setOperationType(OperationType.CHECKIN_OP);
+ ci_walk.addTree(new FileTreeIterator(db));
+ ci_walk.addTree(new DirCacheIterator(db.readDirCache()));
+ }
+
+ /**
+ * Assert an entry in which checkin and checkout attributes are expected to
+ * be the same.
+ *
+ * @param type
+ * @param pathName
+ * @param forBothOperaiton
+ * @throws IOException
+ */
+ private void assertEntry(FileMode type, String pathName,
+ Set<Attribute> forBothOperaiton) throws IOException {
+ assertEntry(type, pathName, forBothOperaiton, forBothOperaiton);
+ }
+
+ /**
+ * Assert an entry with no attribute expected.
+ *
+ * @param type
+ * @param pathName
+ * @throws IOException
+ */
+ private void assertEntry(FileMode type, String pathName) throws IOException {
+ assertEntry(type, pathName, Collections.<Attribute> emptySet(),
+ Collections.<Attribute> emptySet());
+ }
+
+ /**
+ * Assert that an entry;
+ * <ul>
+ * <li>Has the correct type</li>
+ * <li>Exist in the tree walk</li>
+ * <li>Has the expected attributes on a checkin operation</li>
+ * <li>Has the expected attributes on a checkout operation</li>
+ * </ul>
+ *
+ * @param type
+ * @param pathName
+ * @param checkinAttributes
+ * @param checkoutAttributes
+ * @throws IOException
+ */
+ private void assertEntry(FileMode type, String pathName,
+ Set<Attribute> checkinAttributes, Set<Attribute> checkoutAttributes)
+ throws IOException {
+ assertTrue("walk has entry", walk.next());
+ assertTrue("walk has entry", ci_walk.next());
+ assertEquals(pathName, walk.getPathString());
+ assertEquals(type, walk.getFileMode(0));
+
+ assertEquals(checkinAttributes,
+ asSet(ci_walk.getAttributes().getAll()));
+ assertEquals(checkoutAttributes,
+ asSet(walk.getAttributes().getAll()));
+
+ if (D.equals(type)) {
+ walk.enterSubtree();
+ ci_walk.enterSubtree();
+ }
+ }
+
+ private static Set<Attribute> asSet(Collection<Attribute> attributes) {
+ Set<Attribute> ret = new HashSet<>();
+ for (Attribute a : attributes) {
+ ret.add(a);
+ }
+ return ret;
+ }
+
+ private File writeAttributesFile(String name, String... rules)
+ throws IOException {
+ StringBuilder data = new StringBuilder();
+ for (String line : rules)
+ data.append(line + "\n");
+ return writeTrashFile(name, data.toString());
+ }
+
+ /**
+ * Creates an attributes file and set its locationĀ in the git configuration.
+ *
+ * @param fileName
+ * @param attributes
+ * @return The attribute file
+ * @throws IOException
+ * @see Repository#getConfig()
+ */
+ private File writeGlobalAttributeFile(String fileName, String... attributes)
+ throws IOException {
+ customAttributeFile = File.createTempFile("tmp_", fileName, null);
+ customAttributeFile.deleteOnExit();
+ StringBuilder attributesFileContent = new StringBuilder();
+ for (String attr : attributes) {
+ attributesFileContent.append(attr).append("\n");
+ }
+ JGitTestUtil.write(customAttributeFile,
+ attributesFileContent.toString());
+ db.getConfig().setString("core", null, "attributesfile",
+ customAttributeFile.getAbsolutePath());
+ return customAttributeFile;
+ }
+
+ static Set<Attribute> asSet(Attribute... attrs) {
+ HashSet<Attribute> result = new HashSet<>();
+ result.addAll(Arrays.asList(attrs));
+ return result;
+ }
+
+ private void endWalk() throws IOException {
+ assertFalse("Not all files tested", walk.next());
+ assertFalse("Not all files tested", ci_walk.next());
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/merge/MergeGitAttributeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/merge/MergeGitAttributeTest.java
new file mode 100644
index 0000000000..ac30c6c526
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/merge/MergeGitAttributeTest.java
@@ -0,0 +1,581 @@
+/*
+ * Copyright (C) 2017, Obeo (mathieu.cartaud@obeo.fr) and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.attributes.merge;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.util.function.Consumer;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.MergeResult;
+import org.eclipse.jgit.api.MergeResult.MergeStatus;
+import org.eclipse.jgit.api.errors.CheckoutConflictException;
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.InvalidMergeHeadsException;
+import org.eclipse.jgit.api.errors.NoFilepatternException;
+import org.eclipse.jgit.api.errors.NoHeadException;
+import org.eclipse.jgit.api.errors.NoMessageException;
+import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
+import org.eclipse.jgit.attributes.Attribute;
+import org.eclipse.jgit.attributes.Attributes;
+import org.eclipse.jgit.errors.NoWorkTreeException;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.PathFilter;
+import org.junit.Test;
+
+public class MergeGitAttributeTest extends RepositoryTestCase {
+
+ private static final String REFS_HEADS_RIGHT = "refs/heads/right";
+
+ private static final String REFS_HEADS_MASTER = "refs/heads/master";
+
+ private static final String REFS_HEADS_LEFT = "refs/heads/left";
+
+ private static final String DISABLE_CHECK_BRANCH = "refs/heads/disabled_checked";
+
+ private static final String ENABLE_CHECKED_BRANCH = "refs/heads/enabled_checked";
+
+ private static final String ENABLED_CHECKED_GIF = "enabled_checked.gif";
+
+ public Git createRepositoryBinaryConflict(Consumer<Git> initialCommit,
+ Consumer<Git> leftCommit, Consumer<Git> rightCommit)
+ throws NoFilepatternException, GitAPIException, NoWorkTreeException,
+ IOException {
+ // Set up a git whith conflict commits on images
+ Git git = new Git(db);
+
+ // First commit
+ initialCommit.accept(git);
+ git.add().addFilepattern(".").call();
+ RevCommit firstCommit = git.commit().setAll(true)
+ .setMessage("initial commit adding git attribute file").call();
+
+ // Create branch and add an icon Checked_Boxe (enabled_checked)
+ createBranch(firstCommit, REFS_HEADS_LEFT);
+ checkoutBranch(REFS_HEADS_LEFT);
+ leftCommit.accept(git);
+ git.add().addFilepattern(".").call();
+ git.commit().setMessage("Left").call();
+
+ // Create a second branch from master Unchecked_Boxe
+ checkoutBranch(REFS_HEADS_MASTER);
+ createBranch(firstCommit, REFS_HEADS_RIGHT);
+ checkoutBranch(REFS_HEADS_RIGHT);
+ rightCommit.accept(git);
+ git.add().addFilepattern(".").call();
+ git.commit().setMessage("Right").call();
+
+ checkoutBranch(REFS_HEADS_LEFT);
+ return git;
+
+ }
+
+ @Test
+ public void mergeTextualFile_NoAttr() throws NoWorkTreeException,
+ NoFilepatternException, GitAPIException, IOException {
+ try (Git git = createRepositoryBinaryConflict(g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n");
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ })) {
+ checkoutBranch(REFS_HEADS_LEFT);
+ // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
+
+ MergeResult mergeResult = git.merge()
+ .include(git.getRepository().resolve(REFS_HEADS_RIGHT))
+ .call();
+ assertEquals(MergeStatus.MERGED, mergeResult.getMergeStatus());
+
+ assertNull(mergeResult.getConflicts());
+
+ // Check that the image was not modified (not conflict marker added)
+ String result = read(
+ writeTrashFile("res.cat", "A\n" + "E\n" + "C\n" + "F\n"));
+ assertEquals(result, read(git.getRepository().getWorkTree().toPath()
+ .resolve("main.cat").toFile()));
+ }
+ }
+
+ @Test
+ public void mergeTextualFile_UnsetMerge_Conflict()
+ throws NoWorkTreeException, NoFilepatternException, GitAPIException,
+ IOException {
+ try (Git git = createRepositoryBinaryConflict(g -> {
+ try {
+ writeTrashFile(".gitattributes", "*.cat -merge");
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n");
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ })) {
+ // Check that the merge attribute is unset
+ assertAddMergeAttributeUnset(REFS_HEADS_LEFT, "main.cat");
+ assertAddMergeAttributeUnset(REFS_HEADS_RIGHT, "main.cat");
+
+ checkoutBranch(REFS_HEADS_LEFT);
+ // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
+
+ String catContent = read(git.getRepository().getWorkTree().toPath()
+ .resolve("main.cat").toFile());
+
+ MergeResult mergeResult = git.merge()
+ .include(git.getRepository().resolve(REFS_HEADS_RIGHT))
+ .call();
+ assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
+
+ // Check that the image was not modified (not conflict marker added)
+ assertEquals(catContent, read(git.getRepository().getWorkTree()
+ .toPath().resolve("main.cat").toFile()));
+ }
+ }
+
+ @Test
+ public void mergeTextualFile_UnsetMerge_NoConflict()
+ throws NoWorkTreeException, NoFilepatternException, GitAPIException,
+ IOException {
+ try (Git git = createRepositoryBinaryConflict(g -> {
+ try {
+ writeTrashFile(".gitattributes", "*.txt -merge");
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n");
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ })) {
+ // Check that the merge attribute is unset
+ assertAddMergeAttributeUndefined(REFS_HEADS_LEFT, "main.cat");
+ assertAddMergeAttributeUndefined(REFS_HEADS_RIGHT, "main.cat");
+
+ checkoutBranch(REFS_HEADS_LEFT);
+ // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
+
+ MergeResult mergeResult = git.merge()
+ .include(git.getRepository().resolve(REFS_HEADS_RIGHT))
+ .call();
+ assertEquals(MergeStatus.MERGED, mergeResult.getMergeStatus());
+
+ // Check that the image was not modified (not conflict marker added)
+ String result = read(
+ writeTrashFile("res.cat", "A\n" + "E\n" + "C\n" + "F\n"));
+ assertEquals(result, read(git.getRepository().getWorkTree()
+ .toPath().resolve("main.cat").toFile()));
+ }
+ }
+
+ @Test
+ public void mergeTextualFile_SetBinaryMerge_Conflict()
+ throws NoWorkTreeException, NoFilepatternException, GitAPIException,
+ IOException {
+ try (Git git = createRepositoryBinaryConflict(g -> {
+ try {
+ writeTrashFile(".gitattributes", "*.cat merge=binary");
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n");
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ })) {
+ // Check that the merge attribute is set to binary
+ assertAddMergeAttributeCustom(REFS_HEADS_LEFT, "main.cat",
+ "binary");
+ assertAddMergeAttributeCustom(REFS_HEADS_RIGHT, "main.cat",
+ "binary");
+
+ checkoutBranch(REFS_HEADS_LEFT);
+ // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
+
+ String catContent = read(git.getRepository().getWorkTree().toPath()
+ .resolve("main.cat").toFile());
+
+ MergeResult mergeResult = git.merge()
+ .include(git.getRepository().resolve(REFS_HEADS_RIGHT))
+ .call();
+ assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
+
+ // Check that the image was not modified (not conflict marker added)
+ assertEquals(catContent, read(git.getRepository().getWorkTree()
+ .toPath().resolve("main.cat").toFile()));
+ }
+ }
+
+ @Test
+ public void mergeTextualFile_SetUnionMerge() throws NoWorkTreeException,
+ NoFilepatternException, GitAPIException, IOException {
+ try (Git git = createRepositoryBinaryConflict(g -> {
+ try {
+ writeTrashFile(".gitattributes", "*.cat merge=union");
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "G\n" + "C\n" + "F\n");
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ })) {
+ // Check that the merge attribute is set to union
+ assertAddMergeAttributeCustom(REFS_HEADS_LEFT, "main.cat", "union");
+ assertAddMergeAttributeCustom(REFS_HEADS_RIGHT, "main.cat",
+ "union");
+
+ checkoutBranch(REFS_HEADS_LEFT);
+ // Merge refs/heads/left -> refs/heads/right
+
+ MergeResult mergeResult = git.merge()
+ .include(git.getRepository().resolve(REFS_HEADS_RIGHT))
+ .call();
+ assertEquals(MergeStatus.MERGED, mergeResult.getMergeStatus());
+
+ // Check that the file is the union of both branches (no conflict
+ // marker added)
+ String result = read(writeTrashFile("res.cat",
+ "A\n" + "G\n" + "E\n" + "C\n" + "F\n"));
+ assertEquals(result, read(git.getRepository().getWorkTree().toPath()
+ .resolve("main.cat").toFile()));
+ }
+ }
+
+ @Test
+ public void mergeBinaryFile_NoAttr_Conflict() throws IllegalStateException,
+ IOException, NoHeadException, ConcurrentRefUpdateException,
+ CheckoutConflictException, InvalidMergeHeadsException,
+ WrongRepositoryStateException, NoMessageException, GitAPIException {
+
+ RevCommit disableCheckedCommit;
+ // Set up a git with conflict commits on images
+ try (Git git = new Git(db)) {
+ // First commit
+ write(new File(db.getWorkTree(), ".gitattributes"), "");
+ git.add().addFilepattern(".gitattributes").call();
+ RevCommit firstCommit = git.commit()
+ .setMessage("initial commit adding git attribute file")
+ .call();
+
+ // Create branch and add an icon Checked_Boxe (enabled_checked)
+ createBranch(firstCommit, ENABLE_CHECKED_BRANCH);
+ checkoutBranch(ENABLE_CHECKED_BRANCH);
+ copy(ENABLED_CHECKED_GIF, ENABLED_CHECKED_GIF, "");
+ git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
+ git.commit().setMessage("enabled_checked commit").call();
+
+ // Create a second branch from master Unchecked_Boxe
+ checkoutBranch(REFS_HEADS_MASTER);
+ createBranch(firstCommit, DISABLE_CHECK_BRANCH);
+ checkoutBranch(DISABLE_CHECK_BRANCH);
+ copy("disabled_checked.gif", ENABLED_CHECKED_GIF, "");
+ git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
+ disableCheckedCommit = git.commit()
+ .setMessage("disabled_checked commit").call();
+
+ // Check that the merge attribute is unset
+ assertAddMergeAttributeUndefined(ENABLE_CHECKED_BRANCH,
+ ENABLED_CHECKED_GIF);
+ assertAddMergeAttributeUndefined(DISABLE_CHECK_BRANCH,
+ ENABLED_CHECKED_GIF);
+
+ checkoutBranch(ENABLE_CHECKED_BRANCH);
+ // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
+ MergeResult mergeResult = git.merge().include(disableCheckedCommit)
+ .call();
+ assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
+
+ // Check that the image was not modified (no conflict marker added)
+ try (FileInputStream mergeResultFile = new FileInputStream(
+ db.getWorkTree().toPath().resolve(ENABLED_CHECKED_GIF)
+ .toFile())) {
+ assertTrue(contentEquals(
+ getClass().getResourceAsStream(ENABLED_CHECKED_GIF),
+ mergeResultFile));
+ }
+ }
+ }
+
+ @Test
+ public void mergeBinaryFile_UnsetMerge_Conflict()
+ throws IllegalStateException,
+ IOException, NoHeadException, ConcurrentRefUpdateException,
+ CheckoutConflictException, InvalidMergeHeadsException,
+ WrongRepositoryStateException, NoMessageException, GitAPIException {
+
+ RevCommit disableCheckedCommit;
+ // Set up a git whith conflict commits on images
+ try (Git git = new Git(db)) {
+ // First commit
+ write(new File(db.getWorkTree(), ".gitattributes"), "*.gif -merge");
+ git.add().addFilepattern(".gitattributes").call();
+ RevCommit firstCommit = git.commit()
+ .setMessage("initial commit adding git attribute file")
+ .call();
+
+ // Create branch and add an icon Checked_Boxe (enabled_checked)
+ createBranch(firstCommit, ENABLE_CHECKED_BRANCH);
+ checkoutBranch(ENABLE_CHECKED_BRANCH);
+ copy(ENABLED_CHECKED_GIF, ENABLED_CHECKED_GIF, "");
+ git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
+ git.commit().setMessage("enabled_checked commit").call();
+
+ // Create a second branch from master Unchecked_Boxe
+ checkoutBranch(REFS_HEADS_MASTER);
+ createBranch(firstCommit, DISABLE_CHECK_BRANCH);
+ checkoutBranch(DISABLE_CHECK_BRANCH);
+ copy("disabled_checked.gif", ENABLED_CHECKED_GIF, "");
+ git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
+ disableCheckedCommit = git.commit()
+ .setMessage("disabled_checked commit").call();
+
+ // Check that the merge attribute is unset
+ assertAddMergeAttributeUnset(ENABLE_CHECKED_BRANCH,
+ ENABLED_CHECKED_GIF);
+ assertAddMergeAttributeUnset(DISABLE_CHECK_BRANCH,
+ ENABLED_CHECKED_GIF);
+
+ checkoutBranch(ENABLE_CHECKED_BRANCH);
+ // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
+ MergeResult mergeResult = git.merge().include(disableCheckedCommit)
+ .call();
+ assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
+
+ // Check that the image was not modified (not conflict marker added)
+ try (FileInputStream mergeResultFile = new FileInputStream(
+ db.getWorkTree().toPath().resolve(ENABLED_CHECKED_GIF)
+ .toFile())) {
+ assertTrue(contentEquals(
+ getClass().getResourceAsStream(ENABLED_CHECKED_GIF),
+ mergeResultFile));
+ }
+ }
+ }
+
+ @Test
+ public void mergeBinaryFile_SetMerge_Conflict()
+ throws IllegalStateException, IOException, NoHeadException,
+ ConcurrentRefUpdateException, CheckoutConflictException,
+ InvalidMergeHeadsException, WrongRepositoryStateException,
+ NoMessageException, GitAPIException {
+
+ RevCommit disableCheckedCommit;
+ // Set up a git whith conflict commits on images
+ try (Git git = new Git(db)) {
+ // First commit
+ write(new File(db.getWorkTree(), ".gitattributes"), "*.gif merge");
+ git.add().addFilepattern(".gitattributes").call();
+ RevCommit firstCommit = git.commit()
+ .setMessage("initial commit adding git attribute file")
+ .call();
+
+ // Create branch and add an icon Checked_Boxe (enabled_checked)
+ createBranch(firstCommit, ENABLE_CHECKED_BRANCH);
+ checkoutBranch(ENABLE_CHECKED_BRANCH);
+ copy(ENABLED_CHECKED_GIF, ENABLED_CHECKED_GIF, "");
+ git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
+ git.commit().setMessage("enabled_checked commit").call();
+
+ // Create a second branch from master Unchecked_Boxe
+ checkoutBranch(REFS_HEADS_MASTER);
+ createBranch(firstCommit, DISABLE_CHECK_BRANCH);
+ checkoutBranch(DISABLE_CHECK_BRANCH);
+ copy("disabled_checked.gif", ENABLED_CHECKED_GIF, "");
+ git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
+ disableCheckedCommit = git.commit()
+ .setMessage("disabled_checked commit").call();
+
+ // Check that the merge attribute is set
+ assertAddMergeAttributeSet(ENABLE_CHECKED_BRANCH,
+ ENABLED_CHECKED_GIF);
+ assertAddMergeAttributeSet(DISABLE_CHECK_BRANCH,
+ ENABLED_CHECKED_GIF);
+
+ checkoutBranch(ENABLE_CHECKED_BRANCH);
+ // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
+ MergeResult mergeResult = git.merge().include(disableCheckedCommit)
+ .call();
+ assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
+
+ // Check that the image was not modified (not conflict marker added)
+ try (FileInputStream mergeResultFile = new FileInputStream(
+ db.getWorkTree().toPath().resolve(ENABLED_CHECKED_GIF)
+ .toFile())) {
+ assertTrue(contentEquals(
+ getClass().getResourceAsStream(ENABLED_CHECKED_GIF),
+ mergeResultFile));
+ }
+ }
+ }
+
+ /*
+ * Copied from org.apache.commons.io.IOUtils
+ */
+ private boolean contentEquals(InputStream input1, InputStream input2)
+ throws IOException {
+ if (input1 == input2) {
+ return true;
+ }
+ if (!(input1 instanceof BufferedInputStream)) {
+ input1 = new BufferedInputStream(input1);
+ }
+ if (!(input2 instanceof BufferedInputStream)) {
+ input2 = new BufferedInputStream(input2);
+ }
+
+ int ch = input1.read();
+ while (-1 != ch) {
+ final int ch2 = input2.read();
+ if (ch != ch2) {
+ return false;
+ }
+ ch = input1.read();
+ }
+
+ final int ch2 = input2.read();
+ return ch2 == -1;
+ }
+
+ private void assertAddMergeAttributeUnset(String branch, String fileName)
+ throws IllegalStateException, IOException {
+ checkoutBranch(branch);
+
+ try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) {
+ treeWaklEnableChecked.addTree(new FileTreeIterator(db));
+ treeWaklEnableChecked.setFilter(PathFilter.create(fileName));
+
+ assertTrue(treeWaklEnableChecked.next());
+ Attributes attributes = treeWaklEnableChecked.getAttributes();
+ Attribute mergeAttribute = attributes.get("merge");
+ assertNotNull(mergeAttribute);
+ assertEquals(Attribute.State.UNSET, mergeAttribute.getState());
+ }
+ }
+
+ private void assertAddMergeAttributeSet(String branch, String fileName)
+ throws IllegalStateException, IOException {
+ checkoutBranch(branch);
+
+ try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) {
+ treeWaklEnableChecked.addTree(new FileTreeIterator(db));
+ treeWaklEnableChecked.setFilter(PathFilter.create(fileName));
+
+ assertTrue(treeWaklEnableChecked.next());
+ Attributes attributes = treeWaklEnableChecked.getAttributes();
+ Attribute mergeAttribute = attributes.get("merge");
+ assertNotNull(mergeAttribute);
+ assertEquals(Attribute.State.SET, mergeAttribute.getState());
+ }
+ }
+
+ private void assertAddMergeAttributeUndefined(String branch,
+ String fileName) throws IllegalStateException, IOException {
+ checkoutBranch(branch);
+
+ try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) {
+ treeWaklEnableChecked.addTree(new FileTreeIterator(db));
+ treeWaklEnableChecked.setFilter(PathFilter.create(fileName));
+
+ assertTrue(treeWaklEnableChecked.next());
+ Attributes attributes = treeWaklEnableChecked.getAttributes();
+ Attribute mergeAttribute = attributes.get("merge");
+ assertNull(mergeAttribute);
+ }
+ }
+
+ private void assertAddMergeAttributeCustom(String branch, String fileName,
+ String value) throws IllegalStateException, IOException {
+ checkoutBranch(branch);
+
+ try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) {
+ treeWaklEnableChecked.addTree(new FileTreeIterator(db));
+ treeWaklEnableChecked.setFilter(PathFilter.create(fileName));
+
+ assertTrue(treeWaklEnableChecked.next());
+ Attributes attributes = treeWaklEnableChecked.getAttributes();
+ Attribute mergeAttribute = attributes.get("merge");
+ assertNotNull(mergeAttribute);
+ assertEquals(Attribute.State.CUSTOM, mergeAttribute.getState());
+ assertEquals(value, mergeAttribute.getValue());
+ }
+ }
+
+ private void copy(String resourcePath, String resourceNewName,
+ String pathInRepo) throws IOException {
+ InputStream input = getClass().getResourceAsStream(resourcePath);
+ Files.copy(input, db.getWorkTree().toPath().resolve(pathInRepo)
+ .resolve(resourceNewName));
+ }
+
+}