From 12280c02dbb8e4ac10893fbbd415be757afab4c1 Mon Sep 17 00:00:00 2001 From: Arthur Daussy Date: Fri, 31 Oct 2014 17:46:36 +0100 Subject: [PATCH] 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 Signed-off-by: Christian Halstrick --- .../AttributesNodeDirCacheIteratorTest.java | 12 +- ...eNodeTest.java => AttributesNodeTest.java} | 2 +- ...AttributesNodeWorkingTreeIteratorTest.java | 47 +- .../attributes/TreeWalkAttributeTest.java | 865 ++++++++++++++++++ org.eclipse.jgit/.settings/.api_filters | 14 + .../src/org/eclipse/jgit/api/AddCommand.java | 3 + .../org/eclipse/jgit/api/CommitCommand.java | 2 + .../attributes/AttributesNodeProvider.java | 81 ++ .../jgit/attributes/AttributesProvider.java | 57 ++ .../org/eclipse/jgit/dircache/DirCache.java | 2 + .../internal/storage/dfs/DfsRepository.java | 37 + .../internal/storage/file/FileRepository.java | 63 ++ .../storage/file/GlobalAttributesNode.java | 87 ++ .../storage/file/InfoAttributesNode.java | 80 ++ .../src/org/eclipse/jgit/lib/IndexDiff.java | 2 + .../src/org/eclipse/jgit/lib/Repository.java | 11 + .../jgit/treewalk/NameConflictTreeWalk.java | 2 +- .../org/eclipse/jgit/treewalk/TreeWalk.java | 270 +++++- .../jgit/treewalk/WorkingTreeIterator.java | 88 +- 19 files changed, 1609 insertions(+), 116 deletions(-) rename org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/{AttributeNodeTest.java => AttributesNodeTest.java} (99%) create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java 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. */