summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArthur Daussy <arthur.daussy@obeo.fr>2014-10-31 17:46:36 +0100
committerMatthias Sohn <matthias.sohn@sap.com>2015-11-18 00:28:45 +0100
commit12280c02dbb8e4ac10893fbbd415be757afab4c1 (patch)
tree65724c985ead6d5e7c668ceeb30f6d2d61e29d45
parent2f6959d9f31faadcc50b5b0ded25f5e5a11befa4 (diff)
downloadjgit-12280c02dbb8e4ac10893fbbd415be757afab4c1.tar.gz
jgit-12280c02dbb8e4ac10893fbbd415be757afab4c1.zip
Adds the git attributes computation on the treewalk
Adds the getAttributes feature to the tree walk. The computation of attributes needs to be done by the TreeWalk since it needs both a WorkingTreeIterator and a DirCacheIterator. Bug: 342372 CQ: 9120 Change-Id: I5e33257fd8c9895869a128bad3fd1e720409d361 Signed-off-by: Arthur Daussy <arthur.daussy@obeo.fr> Signed-off-by: Christian Halstrick <christian.halstrick@sap.com>
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java12
-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)2
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java47
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java865
-rw-r--r--org.eclipse.jgit/.settings/.api_filters14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java81
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java57
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java37
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java63
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java87
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java80
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java270
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java88
19 files changed, 1609 insertions, 116 deletions
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..3e6ca62202 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
@@ -243,23 +243,23 @@ 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(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(String pathName,
+ AttributesNode attributesNode, List<Attribute> nodeAttrs) {
+ if (attributesNode == null)
assertTrue(nodeAttrs == null || nodeAttrs.isEmpty());
else {
Map<String, Attribute> entryAttributes = new LinkedHashMap<String, Attribute>();
- attributeNode.getAttributes(pathName, false, entryAttributes);
+ attributesNode.getAttributes(pathName, false, entryAttributes);
if (nodeAttrs != null && !nodeAttrs.isEmpty()) {
for (Attribute attribute : nodeAttrs) {
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..d82baaa36e 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
@@ -60,7 +60,7 @@ import org.junit.Test;
/**
* Test {@link AttributesNode}
*/
-public class AttributeNodeTest {
+public class AttributesNodeTest {
private static final Attribute A_SET_ATTR = new Attribute("A", SET);
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..bcf17174b8 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
@@ -76,14 +76,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
@@ -112,25 +108,19 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase {
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));
+ assertIteration(F, "global.txt", asList(EOL_LF));
+ assertIteration(F, "readme.txt", asList(EOL_LF));
assertIteration(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(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(F, "windows.file", null, asList(EOL_CRLF), null);
- assertIteration(F, "windows.txt", asList(EOL_LF), asList(EOL_CRLF),
- asList(CUSTOM_VALUE));
+ assertIteration(F, "windows.file", null);
+ assertIteration(F, "windows.txt", asList(EOL_LF));
endWalk();
}
@@ -212,14 +202,11 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase {
private void assertIteration(FileMode type, String pathName)
throws IOException {
- assertIteration(type, pathName, Collections.<Attribute> emptyList(),
- Collections.<Attribute> emptyList(),
- Collections.<Attribute> emptyList());
+ assertIteration(type, pathName, Collections.<Attribute> emptyList());
}
private void assertIteration(FileMode type, String pathName,
- List<Attribute> nodeAttrs, List<Attribute> infoAttrs,
- List<Attribute> globalAttrs)
+ List<Attribute> nodeAttrs)
throws IOException {
assertTrue("walk has entry", walk.next());
assertEquals(pathName, walk.getPathString());
@@ -227,25 +214,21 @@ 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(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(String pathName,
+ AttributesNode attributesNode, List<Attribute> nodeAttrs) {
+ if (attributesNode == null)
assertTrue(nodeAttrs == null || nodeAttrs.isEmpty());
else {
Map<String, Attribute> entryAttributes = new LinkedHashMap<String, Attribute>();
- attributeNode.getAttributes(pathName, false, entryAttributes);
+ attributesNode.getAttributes(pathName, false, entryAttributes);
if (nodeAttrs != null && !nodeAttrs.isEmpty()) {
for (Attribute attribute : nodeAttrs) {
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..c3d5e8752c
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java
@@ -0,0 +1,865 @@
+/*
+ * Copyright (C) 2014, Obeo.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.attributes;
+
+import 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 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 {
+ 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<File>();
+ 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().values()));
+ assertEquals(checkoutAttributes, asSet(walk.getAttributes().values()));
+
+ if (D.equals(type)) {
+ walk.enterSubtree();
+ ci_walk.enterSubtree();
+ }
+ }
+
+ private static Set<Attribute> asSet(Collection<Attribute> attributes) {
+ Set<Attribute> ret = new HashSet<Attribute>();
+ 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<Attribute>();
+ for (Attribute attr : attrs)
+ result.add(attr);
+ 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/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index ed330b1517..c7a96a2f25 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -14,6 +14,20 @@
</message_arguments>
</filter>
</resource>
+ <resource path="src/org/eclipse/jgit/lib/Repository.java" type="org.eclipse.jgit.lib.Repository">
+ <filter comment="Only implementors of Repository are affected. That should be allowed" id="336695337">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.lib.Repository"/>
+ <message_argument value="createAttributesNodeProvider()"/>
+ </message_arguments>
+ </filter>
+ <filter id="336695337">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.lib.Repository"/>
+ <message_argument value="newAttributesNodeProvider()"/>
+ </message_arguments>
+ </filter>
+ </resource>
<resource path="src/org/eclipse/jgit/transport/PushCertificate.java" type="org.eclipse.jgit.transport.PushCertificate">
<filter comment="PushCertificate wasn't really usable in 4.0" id="338722907">
<message_arguments>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
index de6c32a808..ae297a6438 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
@@ -63,6 +63,7 @@ import org.eclipse.jgit.lib.ObjectInserter;
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.eclipse.jgit.treewalk.WorkingTreeIterator;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
@@ -139,6 +140,7 @@ public class AddCommand extends GitCommand<DirCache> {
try (ObjectInserter inserter = repo.newObjectInserter();
final TreeWalk tw = new TreeWalk(repo)) {
+ tw.setOperationType(OperationType.CHECKIN_OP);
dc = repo.lockDirCache();
DirCacheIterator c;
@@ -146,6 +148,7 @@ public class AddCommand extends GitCommand<DirCache> {
tw.addTree(new DirCacheBuildIterator(builder));
if (workingTreeIterator == null)
workingTreeIterator = new FileTreeIterator(repo);
+ workingTreeIterator.setDirCacheIterator(tw, 0);
tw.addTree(workingTreeIterator);
tw.setRecursive(true);
if (!addAll)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
index 6174d48d3a..53e18df479 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
@@ -86,6 +86,7 @@ import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
import org.eclipse.jgit.util.ChangeIdUtil;
/**
@@ -328,6 +329,7 @@ public class CommitCommand extends GitCommand<RevCommit> {
boolean emptyCommit = true;
try (TreeWalk treeWalk = new TreeWalk(repo)) {
+ treeWalk.setOperationType(OperationType.CHECKIN_OP);
int dcIdx = treeWalk
.addTree(new DirCacheBuildIterator(existingBuilder));
int fIdx = treeWalk.addTree(new FileTreeIterator(repo));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java
new file mode 100644
index 0000000000..6f2ebad677
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2014, Arthur Daussy <arthur.daussy@obeo.fr>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.attributes;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.CoreConfig;
+
+/**
+ * An interface used to retrieve the global and info {@link AttributesNode}s.
+ *
+ * @since 4.2
+ *
+ */
+public interface AttributesNodeProvider {
+
+ /**
+ * Retrieve the {@link AttributesNode} that holds the information located
+ * in $GIT_DIR/info/attributes file.
+ *
+ * @return the {@link AttributesNode} that holds the information located in
+ * $GIT_DIR/info/attributes file.
+ * @throws IOException
+ * if an error is raised while parsing the attributes file
+ */
+ public AttributesNode getInfoAttributesNode() throws IOException;
+
+ /**
+ * Retrieve the {@link AttributesNode} that holds the information located
+ * in the global gitattributes file.
+ *
+ * @return the {@link AttributesNode} that holds the information located in
+ * the global gitattributes file.
+ * @throws IOException
+ * IOException if an error is raised while parsing the
+ * attributes file
+ * @see CoreConfig#getAttributesFile()
+ */
+ public AttributesNode getGlobalAttributesNode() throws IOException;
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java
new file mode 100644
index 0000000000..8f23a83f78
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015, Christian Halstrick <christian.halstrick@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.attributes;
+
+import java.util.Map;
+
+/**
+ * Interface for classes which provide git attributes
+ *
+ * @since 4.2
+ */
+public interface AttributesProvider {
+ /**
+ * @return the currently active attributes by attribute key
+ */
+ public Map<String, Attribute> getAttributes();
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
index 6d9a32db92..92cdf391c1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
@@ -76,6 +76,7 @@ import org.eclipse.jgit.lib.ObjectInserter;
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.eclipse.jgit.treewalk.filter.PathFilterGroup;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.IO;
@@ -963,6 +964,7 @@ public class DirCache {
private void updateSmudgedEntries() throws IOException {
List<String> paths = new ArrayList<String>(128);
try (TreeWalk walk = new TreeWalk(repository)) {
+ walk.setOperationType(OperationType.CHECKIN_OP);
for (int i = 0; i < entryCnt; i++)
if (sortedEntries[i].isSmudged())
paths.add(sortedEntries[i].getPathString());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java
index 122f6d3d19..0d5fd0f859 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java
@@ -44,8 +44,13 @@
package org.eclipse.jgit.internal.storage.dfs;
import java.io.IOException;
+import java.io.InputStream;
import java.text.MessageFormat;
+import java.util.Collections;
+import org.eclipse.jgit.attributes.AttributesNode;
+import org.eclipse.jgit.attributes.AttributesNodeProvider;
+import org.eclipse.jgit.attributes.AttributesRule;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.RefUpdate;
@@ -126,4 +131,36 @@ public abstract class DfsRepository extends Repository {
public ReflogReader getReflogReader(String refName) throws IOException {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public AttributesNodeProvider createAttributesNodeProvider() {
+ // TODO Check if the implementation used in FileRepository can be used
+ // for this kind of repository
+ return new EmptyAttributesNodeProvider();
+ }
+
+ private static class EmptyAttributesNodeProvider implements
+ AttributesNodeProvider {
+ private EmptyAttributesNode emptyAttributesNode = new EmptyAttributesNode();
+
+ public AttributesNode getInfoAttributesNode() throws IOException {
+ return emptyAttributesNode;
+ }
+
+ public AttributesNode getGlobalAttributesNode() throws IOException {
+ return emptyAttributesNode;
+ }
+
+ private static class EmptyAttributesNode extends AttributesNode {
+
+ public EmptyAttributesNode() {
+ super(Collections.<AttributesRule> emptyList());
+ }
+
+ @Override
+ public void parse(InputStream in) throws IOException {
+ // Do nothing
+ }
+ }
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
index 995621ee3e..490cbcaa81 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
@@ -49,11 +49,15 @@ package org.eclipse.jgit.internal.storage.file;
import static org.eclipse.jgit.lib.RefDatabase.ALL;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.HashSet;
import java.util.Set;
+import org.eclipse.jgit.attributes.AttributesNode;
+import org.eclipse.jgit.attributes.AttributesNodeProvider;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.events.ConfigChangedEvent;
import org.eclipse.jgit.events.ConfigChangedListener;
@@ -479,4 +483,63 @@ public class FileRepository extends Repository {
return new ReflogReaderImpl(this, ref.getName());
return null;
}
+
+ @Override
+ public AttributesNodeProvider createAttributesNodeProvider() {
+ return new AttributesNodeProviderImpl(this);
+ }
+
+ /**
+ * Implementation a {@link AttributesNodeProvider} for a
+ * {@link FileRepository}.
+ *
+ * @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a>
+ *
+ */
+ static class AttributesNodeProviderImpl implements
+ AttributesNodeProvider {
+
+ private AttributesNode infoAttributesNode;
+
+ private AttributesNode globalAttributesNode;
+
+ /**
+ * Constructor.
+ *
+ * @param repo
+ * {@link Repository} that will provide the attribute nodes.
+ */
+ protected AttributesNodeProviderImpl(Repository repo) {
+ infoAttributesNode = new InfoAttributesNode(repo);
+ globalAttributesNode = new GlobalAttributesNode(repo);
+ }
+
+ public AttributesNode getInfoAttributesNode() throws IOException {
+ if (infoAttributesNode instanceof InfoAttributesNode)
+ infoAttributesNode = ((InfoAttributesNode) infoAttributesNode)
+ .load();
+ return infoAttributesNode;
+ }
+
+ public AttributesNode getGlobalAttributesNode() throws IOException {
+ if (globalAttributesNode instanceof GlobalAttributesNode)
+ globalAttributesNode = ((GlobalAttributesNode) globalAttributesNode)
+ .load();
+ return globalAttributesNode;
+ }
+
+ static void loadRulesFromFile(AttributesNode r, File attrs)
+ throws FileNotFoundException, IOException {
+ if (attrs.exists()) {
+ FileInputStream in = new FileInputStream(attrs);
+ try {
+ r.parse(in);
+ } finally {
+ in.close();
+ }
+ }
+ }
+
+ }
+
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java
new file mode 100644
index 0000000000..454d3bff69
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2014, Arthur Daussy <arthur.daussy@obeo.fr>
+ * Copyright (C) 2015, Christian Halstrick <christian.halstrick@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.internal.storage.file;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.jgit.attributes.AttributesNode;
+import org.eclipse.jgit.lib.CoreConfig;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.util.FS;
+
+/** Attribute node loaded from global system-wide file. */
+public class GlobalAttributesNode extends AttributesNode {
+ final Repository repository;
+
+ /**
+ * @param repository
+ */
+ public GlobalAttributesNode(Repository repository) {
+ this.repository = repository;
+ }
+
+ /**
+ * @return the attributes node
+ * @throws IOException
+ */
+ public AttributesNode load() throws IOException {
+ AttributesNode r = new AttributesNode();
+
+ FS fs = repository.getFS();
+ String path = repository.getConfig().get(CoreConfig.KEY)
+ .getAttributesFile();
+ if (path != null) {
+ File attributesFile;
+ if (path.startsWith("~/")) { //$NON-NLS-1$
+ attributesFile = fs.resolve(fs.userHome(),
+ path.substring(2));
+ } else {
+ attributesFile = fs.resolve(null, path);
+ }
+ FileRepository.AttributesNodeProviderImpl.loadRulesFromFile(r, attributesFile);
+ }
+ return r.getRules().isEmpty() ? null : r;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java
new file mode 100644
index 0000000000..eb53434b74
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2014, Arthur Daussy <arthur.daussy@obeo.fr>
+ * Copyright (C) 2015, Christian Halstrick <christian.halstrick@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.internal.storage.file;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.jgit.attributes.AttributesNode;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.util.FS;
+
+/** Attribute node loaded from the $GIT_DIR/info/attributes file. */
+public class InfoAttributesNode extends AttributesNode {
+ final Repository repository;
+
+ /**
+ * @param repository
+ */
+ public InfoAttributesNode(Repository repository) {
+ this.repository = repository;
+ }
+
+ /**
+ * @return the attributes node
+ * @throws IOException
+ */
+ public AttributesNode load() throws IOException {
+ AttributesNode r = new AttributesNode();
+
+ FS fs = repository.getFS();
+
+ File attributes = fs.resolve(repository.getDirectory(),
+ "info/attributes"); //$NON-NLS-1$
+ FileRepository.AttributesNodeProviderImpl.loadRulesFromFile(r, attributes);
+
+ return r.getRules().isEmpty() ? null : r;
+ }
+
+} \ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
index 3fde2f919b..281cde8750 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
@@ -74,6 +74,7 @@ import org.eclipse.jgit.treewalk.EmptyTreeIterator;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
import org.eclipse.jgit.treewalk.filter.IndexDiffFilter;
import org.eclipse.jgit.treewalk.filter.SkipWorkTreeFilter;
@@ -403,6 +404,7 @@ public class IndexDiff {
dirCache = repository.readDirCache();
try (TreeWalk treeWalk = new TreeWalk(repository)) {
+ treeWalk.setOperationType(OperationType.CHECKIN_OP);
treeWalk.setRecursive(true);
// add the trees (tree, dirchache, workdir)
if (tree != null)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
index d4c72cb9cb..eda02dea4e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -64,6 +64,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
+import org.eclipse.jgit.attributes.AttributesNodeProvider;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.errors.AmbiguousObjectException;
import org.eclipse.jgit.errors.CorruptObjectException;
@@ -210,6 +211,16 @@ public abstract class Repository implements AutoCloseable {
public abstract StoredConfig getConfig();
/**
+ * @return a new {@link AttributesNodeProvider}. This
+ * {@link AttributesNodeProvider} is lazy loaded only once. It means
+ * that it will not be updated after loading. Prefer creating new
+ * instance for each use.
+ * @since 4.2
+ */
+ public abstract AttributesNodeProvider createAttributesNodeProvider();
+
+
+ /**
* @return the used file system abstraction
*/
public FS getFS() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
index 2d6acbddf0..350f563964 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
@@ -96,7 +96,7 @@ public class NameConflictTreeWalk extends TreeWalk {
* the repository the walker will obtain data from.
*/
public NameConflictTreeWalk(final Repository repo) {
- this(repo.newObjectReader());
+ super(repo);
}
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
index 06e828419d..7f7a5c3938 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
@@ -45,7 +45,17 @@
package org.eclipse.jgit.treewalk;
import java.io.IOException;
-
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.attributes.Attribute;
+import org.eclipse.jgit.attributes.AttributesNode;
+import org.eclipse.jgit.attributes.AttributesNodeProvider;
+import org.eclipse.jgit.attributes.AttributesProvider;
+import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
@@ -82,10 +92,39 @@ import org.eclipse.jgit.util.RawParseUtils;
* Multiple simultaneous TreeWalk instances per {@link Repository} are
* permitted, even from concurrent threads.
*/
-public class TreeWalk implements AutoCloseable {
+public class TreeWalk implements AutoCloseable, AttributesProvider {
private static final AbstractTreeIterator[] NO_TREES = {};
/**
+ * @since 4.2
+ */
+ public static enum OperationType {
+ /**
+ * Represents a checkout operation (for example a checkout or reset
+ * operation).
+ */
+ CHECKOUT_OP,
+
+ /**
+ * Represents a checkin operation (for example an add operation)
+ */
+ CHECKIN_OP
+ }
+
+ /**
+ * Type of operation you want to retrieve the git attributes for.
+ */
+ private OperationType operationType = OperationType.CHECKOUT_OP;
+
+ /**
+ * @param operationType
+ * @since 4.2
+ */
+ public void setOperationType(OperationType operationType) {
+ this.operationType = operationType;
+ }
+
+ /**
* Open a tree walk and filter to exactly one path.
* <p>
* The returned tree walk is already positioned on the requested path, so
@@ -213,8 +252,13 @@ public class TreeWalk implements AutoCloseable {
private boolean postChildren;
+ private AttributesNodeProvider attributesNodeProvider;
+
AbstractTreeIterator currentHead;
+ /** Cached attribute for the current entry */
+ private Map<String, Attribute> attrs = null;
+
/**
* Create a new tree walker for a given repository.
*
@@ -225,6 +269,7 @@ public class TreeWalk implements AutoCloseable {
*/
public TreeWalk(final Repository repo) {
this(repo.newObjectReader(), true);
+ attributesNodeProvider = repo.createAttributesNodeProvider();
}
/**
@@ -356,8 +401,29 @@ public class TreeWalk implements AutoCloseable {
postOrderTraversal = b;
}
+ /**
+ * Sets the {@link AttributesNodeProvider} for this {@link TreeWalk}.
+ * <p>
+ * This is a requirement for a correct computation of the git attributes.
+ * If this {@link TreeWalk} has been built using
+ * {@link #TreeWalk(Repository)} constructor, the
+ * {@link AttributesNodeProvider} has already been set. Indeed,the
+ * {@link Repository} can provide an {@link AttributesNodeProvider} using
+ * {@link Repository#createAttributesNodeProvider()} method. Otherwise you
+ * should provide one.
+ * </p>
+ *
+ * @see Repository#createAttributesNodeProvider()
+ * @param provider
+ * @since 4.2
+ */
+ public void setAttributesNodeProvider(AttributesNodeProvider provider) {
+ attributesNodeProvider = provider;
+ }
+
/** Reset this walker so new tree iterators can be added to it. */
public void reset() {
+ attrs = null;
trees = NO_TREES;
advance = false;
depth = 0;
@@ -401,6 +467,7 @@ public class TreeWalk implements AutoCloseable {
advance = false;
depth = 0;
+ attrs = null;
}
/**
@@ -450,6 +517,7 @@ public class TreeWalk implements AutoCloseable {
trees = r;
advance = false;
depth = 0;
+ attrs = null;
}
/**
@@ -546,6 +614,7 @@ public class TreeWalk implements AutoCloseable {
public boolean next() throws MissingObjectException,
IncorrectObjectTypeException, CorruptObjectException, IOException {
try {
+ attrs = null;
if (advance) {
advance = false;
postChildren = false;
@@ -915,6 +984,7 @@ public class TreeWalk implements AutoCloseable {
*/
public void enterSubtree() throws MissingObjectException,
IncorrectObjectTypeException, CorruptObjectException, IOException {
+ attrs = null;
final AbstractTreeIterator ch = currentHead;
final AbstractTreeIterator[] tmp = new AbstractTreeIterator[trees.length];
for (int i = 0; i < trees.length; i++) {
@@ -1008,4 +1078,200 @@ public class TreeWalk implements AutoCloseable {
static String pathOf(final byte[] buf, int pos, int end) {
return RawParseUtils.decode(Constants.CHARSET, buf, pos, end);
}
+
+ /**
+ * Retrieve the git attributes for the current entry.
+ *
+ * <h4>Git attribute computation</h4>
+ *
+ * <ul>
+ * <li>Get the attributes matching the current path entry from the info file
+ * (see {@link AttributesNodeProvider#getInfoAttributesNode()}).</li>
+ * <li>Completes the list of attributes using the .gitattributes files
+ * located on the current path (the further the directory that contains
+ * .gitattributes is from the path in question, the lower its precedence).
+ * For a checkin operation, it will look first on the working tree (if any).
+ * If there is no attributes file, it will fallback on the index. For a
+ * checkout operation, it will first use the index entry and then fallback
+ * on the working tree if none.</li>
+ * <li>In the end, completes the list of matching attributes using the
+ * global attribute file define in the configuration (see
+ * {@link AttributesNodeProvider#getGlobalAttributesNode()})</li>
+ *
+ * </ul>
+ *
+ *
+ * <h4>Iterator constraints</h4>
+ *
+ * <p>
+ * In order to have a correct list of attributes for the current entry, this
+ * {@link TreeWalk} requires to have at least one
+ * {@link AttributesNodeProvider} and a {@link DirCacheIterator} set up. An
+ * {@link AttributesNodeProvider} is used to retrieve the attributes from
+ * the info attributes file and the global attributes file. The
+ * {@link DirCacheIterator} is used to retrieve the .gitattributes files
+ * stored in the index. A {@link WorkingTreeIterator} can also be provided
+ * to access the local version of the .gitattributes files. If none is
+ * provided it will fallback on the {@link DirCacheIterator}.
+ * </p>
+ *
+ * @return a {@link Set} of {@link Attribute}s that match the current entry.
+ * @since 4.2
+ */
+ public Map<String, Attribute> getAttributes() {
+ if (attrs != null)
+ return attrs;
+
+ if (attributesNodeProvider == null) {
+ // The work tree should have a AttributesNodeProvider to be able to
+ // retrieve the info and global attributes node
+ throw new IllegalStateException(
+ "The tree walk should have one AttributesNodeProvider set in order to compute the git attributes."); //$NON-NLS-1$
+ }
+
+ WorkingTreeIterator workingTreeIterator = getTree(WorkingTreeIterator.class);
+ DirCacheIterator dirCacheIterator = getTree(DirCacheIterator.class);
+
+ if (workingTreeIterator == null && dirCacheIterator == null) {
+ // Can not retrieve the attributes without at least one of the above
+ // iterators.
+ return Collections.<String, Attribute> emptyMap();
+ }
+
+ String path = currentHead.getEntryPathString();
+ final boolean isDir = FileMode.TREE.equals(currentHead.mode);
+ Map<String, Attribute> attributes = new LinkedHashMap<String, Attribute>();
+ try {
+ // Gets the info attributes
+ AttributesNode infoNodeAttr = attributesNodeProvider
+ .getInfoAttributesNode();
+ if (infoNodeAttr != null) {
+ infoNodeAttr.getAttributes(path, isDir, attributes);
+ }
+
+
+ // Gets the attributes located on the current entry path
+ getPerDirectoryEntryAttributes(path, isDir, operationType,
+ workingTreeIterator, dirCacheIterator,
+ attributes);
+
+ // Gets the attributes located in the global attribute file
+ AttributesNode globalNodeAttr = attributesNodeProvider
+ .getGlobalAttributesNode();
+ if (globalNodeAttr != null) {
+ globalNodeAttr.getAttributes(path, isDir, attributes);
+ }
+ } catch (IOException e) {
+ throw new JGitInternalException("Error while parsing attributes", e); //$NON-NLS-1$
+ }
+ return attributes;
+ }
+
+ /**
+ * Get the attributes located on the current entry path.
+ *
+ * @param path
+ * current entry path
+ * @param isDir
+ * holds true if the current entry is a directory
+ * @param opType
+ * type of operation
+ * @param workingTreeIterator
+ * a {@link WorkingTreeIterator} matching the current entry
+ * @param dirCacheIterator
+ * a {@link DirCacheIterator} matching the current entry
+ * @param attributes
+ * Non null map holding the existing attributes. This map will be
+ * augmented with new entry. None entry will be overrided.
+ * @throws IOException
+ * It raises an {@link IOException} if a problem appears while
+ * parsing one on the attributes file.
+ */
+ private void getPerDirectoryEntryAttributes(String path, boolean isDir,
+ OperationType opType, WorkingTreeIterator workingTreeIterator,
+ DirCacheIterator dirCacheIterator, Map<String, Attribute> attributes)
+ throws IOException {
+ // Prevents infinite recurrence
+ if (workingTreeIterator != null || dirCacheIterator != null) {
+ AttributesNode currentAttributesNode = getCurrentAttributesNode(
+ opType, workingTreeIterator, dirCacheIterator);
+ if (currentAttributesNode != null) {
+ currentAttributesNode.getAttributes(path, isDir, attributes);
+ }
+ getPerDirectoryEntryAttributes(path, isDir, opType,
+ getParent(workingTreeIterator, WorkingTreeIterator.class),
+ getParent(dirCacheIterator, DirCacheIterator.class),
+ attributes);
+ }
+ }
+
+ private <T extends AbstractTreeIterator> T getParent(T current,
+ Class<T> type) {
+ if (current != null) {
+ AbstractTreeIterator parent = current.parent;
+ if (type.isInstance(parent)) {
+ return type.cast(parent);
+ }
+ }
+ return null;
+ }
+
+ private <T> T getTree(Class<T> type) {
+ for (int i = 0; i < trees.length; i++) {
+ AbstractTreeIterator tree = trees[i];
+ if (type.isInstance(tree)) {
+ return type.cast(tree);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the {@link AttributesNode} for the current entry.
+ * <p>
+ * This method implements the fallback mechanism between the index and the
+ * working tree depending on the operation type
+ * </p>
+ *
+ * @param opType
+ * @param workingTreeIterator
+ * @param dirCacheIterator
+ * @return a {@link AttributesNode} of the current entry,
+ * {@link NullPointerException} otherwise.
+ * @throws IOException
+ * It raises an {@link IOException} if a problem appears while
+ * parsing one on the attributes file.
+ */
+ private AttributesNode getCurrentAttributesNode(OperationType opType,
+ WorkingTreeIterator workingTreeIterator,
+ DirCacheIterator dirCacheIterator) throws IOException {
+ AttributesNode attributesNode = null;
+ switch (opType) {
+ case CHECKIN_OP:
+ if (workingTreeIterator != null) {
+ attributesNode = workingTreeIterator.getEntryAttributesNode();
+ }
+ if (attributesNode == null && dirCacheIterator != null) {
+ attributesNode = dirCacheIterator
+ .getEntryAttributesNode(getObjectReader());
+ }
+ break;
+ case CHECKOUT_OP:
+ if (dirCacheIterator != null) {
+ attributesNode = dirCacheIterator
+ .getEntryAttributesNode(getObjectReader());
+ }
+ if (attributesNode == null && workingTreeIterator != null) {
+ attributesNode = workingTreeIterator.getEntryAttributesNode();
+ }
+ break;
+ default:
+ throw new IllegalStateException(
+ "The only supported operation types are:" //$NON-NLS-1$
+ + OperationType.CHECKIN_OP + "," //$NON-NLS-1$
+ + OperationType.CHECKOUT_OP);
+ }
+
+ return attributesNode;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
index 73ab04f9cb..e36ce0faf5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -74,6 +74,8 @@ import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.ignore.FastIgnoreRule;
import org.eclipse.jgit.ignore.IgnoreNode;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.file.GlobalAttributesNode;
+import org.eclipse.jgit.internal.storage.file.InfoAttributesNode;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.CoreConfig;
import org.eclipse.jgit.lib.CoreConfig.CheckStat;
@@ -150,14 +152,14 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
* Holds the {@link AttributesNode} that is stored in
* $GIT_DIR/info/attributes file.
*/
- private AttributesNode infoAttributeNode;
+ private AttributesNode infoAttributesNode;
/**
* Holds the {@link AttributesNode} that is stored in global attribute file.
*
* @see CoreConfig#getAttributesFile()
*/
- private AttributesNode globalAttributeNode;
+ private AttributesNode globalAttributesNode;
/**
* Create a new iterator with no parent.
@@ -202,8 +204,8 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
protected WorkingTreeIterator(final WorkingTreeIterator p) {
super(p);
state = p.state;
- infoAttributeNode = p.infoAttributeNode;
- globalAttributeNode = p.globalAttributeNode;
+ infoAttributesNode = p.infoAttributesNode;
+ globalAttributesNode = p.globalAttributesNode;
}
/**
@@ -224,9 +226,9 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
entry = null;
ignoreNode = new RootIgnoreNode(entry, repo);
- infoAttributeNode = new InfoAttributesNode(repo);
+ infoAttributesNode = new InfoAttributesNode(repo);
- globalAttributeNode = new GlobalAttributesNode(repo);
+ globalAttributesNode = new GlobalAttributesNode(repo);
}
/**
@@ -678,9 +680,9 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
* @since 3.7
*/
public AttributesNode getInfoAttributesNode() throws IOException {
- if (infoAttributeNode instanceof InfoAttributesNode)
- infoAttributeNode = ((InfoAttributesNode) infoAttributeNode).load();
- return infoAttributeNode;
+ if (infoAttributesNode instanceof InfoAttributesNode)
+ infoAttributesNode = ((InfoAttributesNode) infoAttributesNode).load();
+ return infoAttributesNode;
}
/**
@@ -696,10 +698,10 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
* @since 3.7
*/
public AttributesNode getGlobalAttributesNode() throws IOException {
- if (globalAttributeNode instanceof GlobalAttributesNode)
- globalAttributeNode = ((GlobalAttributesNode) globalAttributeNode)
+ if (globalAttributesNode instanceof GlobalAttributesNode)
+ globalAttributesNode = ((GlobalAttributesNode) globalAttributesNode)
.load();
- return globalAttributeNode;
+ return globalAttributesNode;
}
private static final Comparator<Entry> ENTRY_CMP = new Comparator<Entry>() {
@@ -1296,68 +1298,6 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
}
}
- /**
- * Attributes node loaded from global system-wide file.
- */
- private static class GlobalAttributesNode extends AttributesNode {
- final Repository repository;
-
- GlobalAttributesNode(Repository repository) {
- this.repository = repository;
- }
-
- AttributesNode load() throws IOException {
- AttributesNode r = new AttributesNode();
-
- FS fs = repository.getFS();
- String path = repository.getConfig().get(CoreConfig.KEY)
- .getAttributesFile();
- if (path != null) {
- File attributesFile;
- if (path.startsWith("~/")) //$NON-NLS-1$
- attributesFile = fs.resolve(fs.userHome(),
- path.substring(2));
- else
- attributesFile = fs.resolve(null, path);
- loadRulesFromFile(r, attributesFile);
- }
- return r.getRules().isEmpty() ? null : r;
- }
- }
-
- /** Magic type indicating there may be rules for the top level. */
- private static class InfoAttributesNode extends AttributesNode {
- final Repository repository;
-
- InfoAttributesNode(Repository repository) {
- this.repository = repository;
- }
-
- AttributesNode load() throws IOException {
- AttributesNode r = new AttributesNode();
-
- FS fs = repository.getFS();
-
- File attributes = fs.resolve(repository.getDirectory(),
- "info/attributes"); //$NON-NLS-1$
- loadRulesFromFile(r, attributes);
-
- return r.getRules().isEmpty() ? null : r;
- }
-
- }
-
- private static void loadRulesFromFile(AttributesNode r, File attrs)
- throws FileNotFoundException, IOException {
- if (attrs.exists()) {
- FileInputStream in = new FileInputStream(attrs);
- try {
- r.parse(in);
- } finally {
- in.close();
- }
- }
- }
private static final class IteratorState {
/** Options used to process the working tree. */