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 49279e6e5..3e6ca6220 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 nodeAttrs) { - if (attributeNode == null) + private void assertAttributesNode(String pathName, + AttributesNode attributesNode, List nodeAttrs) { + if (attributesNode == null) assertTrue(nodeAttrs == null || nodeAttrs.isEmpty()); else { Map entryAttributes = new LinkedHashMap(); - 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 similarity index 99% rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeNodeTest.java rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java index ea250369a..d82baaa36 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 64b0535d6..bcf17174b 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. emptyList(), - Collections. emptyList(), - Collections. emptyList()); + assertIteration(type, pathName, Collections. emptyList()); } private void assertIteration(FileMode type, String pathName, - List nodeAttrs, List infoAttrs, - List globalAttrs) + List 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 nodeAttrs) { - if (attributeNode == null) + private void assertAttributesNode(String pathName, + AttributesNode attributesNode, List nodeAttrs) { + if (attributesNode == null) assertTrue(nodeAttrs == null || nodeAttrs.isEmpty()); else { Map entryAttributes = new LinkedHashMap(); - 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 000000000..c3d5e8752 --- /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. + *

+ * In this test we changed the content of the attribute files in the working + * tree compared to the one in the index. + *

+ * + * @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 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. + *

+ * In this use case files are present in both the working tree and the index + *

+ * + * @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. + *

+ * In this test all file are present in both the working tree and the index. + *

+ * + * @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. + *

+ * In this test all file are present only in the working tree. + *

+ * + * @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. 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 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. emptySet(), + Collections. emptySet()); + } + + /** + * Assert that an entry; + *
    + *
  • Has the correct type
  • + *
  • Exist in the tree walk
  • + *
  • Has the expected attributes on a checkin operation
  • + *
  • Has the expected attributes on a checkout operation
  • + *
+ * + * @param type + * @param pathName + * @param checkinAttributes + * @param checkoutAttributes + * @throws IOException + */ + private void assertEntry(FileMode type, String pathName, + Set checkinAttributes, Set 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 asSet(Collection attributes) { + Set 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 asSet(Attribute... attrs) { + HashSet result = new HashSet(); + 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 ed330b151..c7a96a2f2 100644 --- a/org.eclipse.jgit/.settings/.api_filters +++ b/org.eclipse.jgit/.settings/.api_filters @@ -14,6 +14,20 @@ + + + + + + + + + + + + + + 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 de6c32a80..ae297a643 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 { 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 { 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 6174d48d3..53e18df47 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 { 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 000000000..6f2ebad67 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2014, Arthur Daussy + * 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 000000000..8f23a83f7 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015, Christian Halstrick + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.attributes; + +import java.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 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 6d9a32db9..92cdf391c 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 paths = new ArrayList(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 122f6d3d1..0d5fd0f85 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. 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 995621ee3..490cbcaa8 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 Arthur Daussy + * + */ + 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 000000000..454d3bff6 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2014, Arthur Daussy + * Copyright (C) 2015, Christian Halstrick + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.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 000000000..eb53434b7 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2014, Arthur Daussy + * Copyright (C) 2015, Christian Halstrick + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.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 3fde2f919..281cde875 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 d4c72cb9c..eda02dea4 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; @@ -209,6 +210,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 */ 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 2d6acbddf..350f56396 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 06e828419..7f7a5c393 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,9 +92,38 @@ 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. *

@@ -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 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}. + *

+ * 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. + *

+ * + * @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. + * + *

Git attribute computation

+ * + *
    + *
  • Get the attributes matching the current path entry from the info file + * (see {@link AttributesNodeProvider#getInfoAttributesNode()}).
  • + *
  • 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.
  • + *
  • In the end, completes the list of matching attributes using the + * global attribute file define in the configuration (see + * {@link AttributesNodeProvider#getGlobalAttributesNode()})
  • + * + *
+ * + * + *

Iterator constraints

+ * + *

+ * 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}. + *

+ * + * @return a {@link Set} of {@link Attribute}s that match the current entry. + * @since 4.2 + */ + public Map 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. emptyMap(); + } + + String path = currentHead.getEntryPathString(); + final boolean isDir = FileMode.TREE.equals(currentHead.mode); + Map attributes = new LinkedHashMap(); + 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 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 getParent(T current, + Class type) { + if (current != null) { + AbstractTreeIterator parent = current.parent; + if (type.isInstance(parent)) { + return type.cast(parent); + } + } + return null; + } + + private T getTree(Class 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. + *

+ * This method implements the fallback mechanism between the index and the + * working tree depending on the operation type + *

+ * + * @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 73ab04f9c..e36ce0faf 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_CMP = new Comparator() { @@ -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. */