Adds the getAttributes feature to the tree walk. The computation of attributes needs to be done by the TreeWalk since it needs both a WorkingTreeIterator and a DirCacheIterator. Bug: 342372 CQ: 9120 Change-Id: I5e33257fd8c9895869a128bad3fd1e720409d361 Signed-off-by: Arthur Daussy <arthur.daussy@obeo.fr> Signed-off-by: Christian Halstrick <christian.halstrick@sap.com>tags/v4.2.0.201601211800-r
@@ -243,23 +243,23 @@ public class AttributesNodeDirCacheIteratorTest extends RepositoryTestCase { | |||
DirCacheIterator itr = walk.getTree(0, DirCacheIterator.class); | |||
assertNotNull("has tree", itr); | |||
AttributesNode attributeNode = itr.getEntryAttributesNode(db | |||
AttributesNode attributesNode = itr.getEntryAttributesNode(db | |||
.newObjectReader()); | |||
assertAttributeNode(pathName, attributeNode, nodeAttrs); | |||
assertAttributesNode(pathName, attributesNode, nodeAttrs); | |||
if (D.equals(type)) | |||
walk.enterSubtree(); | |||
} | |||
private void assertAttributeNode(String pathName, | |||
AttributesNode attributeNode, List<Attribute> nodeAttrs) { | |||
if (attributeNode == null) | |||
private void assertAttributesNode(String pathName, | |||
AttributesNode attributesNode, List<Attribute> nodeAttrs) { | |||
if (attributesNode == null) | |||
assertTrue(nodeAttrs == null || nodeAttrs.isEmpty()); | |||
else { | |||
Map<String, Attribute> entryAttributes = new LinkedHashMap<String, Attribute>(); | |||
attributeNode.getAttributes(pathName, false, entryAttributes); | |||
attributesNode.getAttributes(pathName, false, entryAttributes); | |||
if (nodeAttrs != null && !nodeAttrs.isEmpty()) { | |||
for (Attribute attribute : nodeAttrs) { |
@@ -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); | |||
@@ -76,14 +76,10 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase { | |||
private static final FileMode F = FileMode.REGULAR_FILE; | |||
private static Attribute EOL_CRLF = new Attribute("eol", "crlf"); | |||
private static Attribute EOL_LF = new Attribute("eol", "lf"); | |||
private static Attribute DELTA_UNSET = new Attribute("delta", State.UNSET); | |||
private static Attribute CUSTOM_VALUE = new Attribute("custom", "value"); | |||
private TreeWalk walk; | |||
@Test | |||
@@ -112,25 +108,19 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase { | |||
walk = beginWalk(); | |||
assertIteration(F, ".gitattributes"); | |||
assertIteration(F, "global.txt", asList(EOL_LF), null, | |||
asList(CUSTOM_VALUE)); | |||
assertIteration(F, "readme.txt", asList(EOL_LF), null, | |||
asList(CUSTOM_VALUE)); | |||
assertIteration(F, "global.txt", asList(EOL_LF)); | |||
assertIteration(F, "readme.txt", asList(EOL_LF)); | |||
assertIteration(D, "src"); | |||
assertIteration(D, "src/config"); | |||
assertIteration(F, "src/config/.gitattributes"); | |||
assertIteration(F, "src/config/readme.txt", asList(DELTA_UNSET), null, | |||
asList(CUSTOM_VALUE)); | |||
assertIteration(F, "src/config/windows.file", null, asList(EOL_CRLF), | |||
null); | |||
assertIteration(F, "src/config/windows.txt", asList(DELTA_UNSET), | |||
asList(EOL_CRLF), asList(CUSTOM_VALUE)); | |||
assertIteration(F, "src/config/readme.txt", asList(DELTA_UNSET)); | |||
assertIteration(F, "src/config/windows.file", null); | |||
assertIteration(F, "src/config/windows.txt", asList(DELTA_UNSET)); | |||
assertIteration(F, "windows.file", null, asList(EOL_CRLF), null); | |||
assertIteration(F, "windows.txt", asList(EOL_LF), asList(EOL_CRLF), | |||
asList(CUSTOM_VALUE)); | |||
assertIteration(F, "windows.file", null); | |||
assertIteration(F, "windows.txt", asList(EOL_LF)); | |||
endWalk(); | |||
} | |||
@@ -212,14 +202,11 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase { | |||
private void assertIteration(FileMode type, String pathName) | |||
throws IOException { | |||
assertIteration(type, pathName, Collections.<Attribute> emptyList(), | |||
Collections.<Attribute> emptyList(), | |||
Collections.<Attribute> emptyList()); | |||
assertIteration(type, pathName, Collections.<Attribute> emptyList()); | |||
} | |||
private void assertIteration(FileMode type, String pathName, | |||
List<Attribute> nodeAttrs, List<Attribute> infoAttrs, | |||
List<Attribute> globalAttrs) | |||
List<Attribute> nodeAttrs) | |||
throws IOException { | |||
assertTrue("walk has entry", walk.next()); | |||
assertEquals(pathName, walk.getPathString()); | |||
@@ -227,25 +214,21 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase { | |||
WorkingTreeIterator itr = walk.getTree(0, WorkingTreeIterator.class); | |||
assertNotNull("has tree", itr); | |||
AttributesNode attributeNode = itr.getEntryAttributesNode(); | |||
assertAttributeNode(pathName, attributeNode, nodeAttrs); | |||
AttributesNode infoAttributeNode = itr.getInfoAttributesNode(); | |||
assertAttributeNode(pathName, infoAttributeNode, infoAttrs); | |||
AttributesNode globalAttributeNode = itr.getGlobalAttributesNode(); | |||
assertAttributeNode(pathName, globalAttributeNode, globalAttrs); | |||
AttributesNode attributesNode = itr.getEntryAttributesNode(); | |||
assertAttributesNode(pathName, attributesNode, nodeAttrs); | |||
if (D.equals(type)) | |||
walk.enterSubtree(); | |||
} | |||
private void assertAttributeNode(String pathName, | |||
AttributesNode attributeNode, List<Attribute> nodeAttrs) { | |||
if (attributeNode == null) | |||
private void assertAttributesNode(String pathName, | |||
AttributesNode attributesNode, List<Attribute> nodeAttrs) { | |||
if (attributesNode == null) | |||
assertTrue(nodeAttrs == null || nodeAttrs.isEmpty()); | |||
else { | |||
Map<String, Attribute> entryAttributes = new LinkedHashMap<String, Attribute>(); | |||
attributeNode.getAttributes(pathName, false, entryAttributes); | |||
attributesNode.getAttributes(pathName, false, entryAttributes); | |||
if (nodeAttrs != null && !nodeAttrs.isEmpty()) { | |||
for (Attribute attribute : nodeAttrs) { |
@@ -0,0 +1,865 @@ | |||
/* | |||
* Copyright (C) 2014, Obeo. | |||
* and other copyright owners as documented in the project's IP log. | |||
* | |||
* This program and the accompanying materials are made available | |||
* under the terms of the Eclipse Distribution License v1.0 which | |||
* accompanies this distribution, is reproduced below, and is | |||
* available at http://www.eclipse.org/org/documents/edl-v10.php | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or | |||
* without modification, are permitted provided that the following | |||
* conditions are met: | |||
* | |||
* - Redistributions of source code must retain the above copyright | |||
* notice, this list of conditions and the following disclaimer. | |||
* | |||
* - Redistributions in binary form must reproduce the above | |||
* copyright notice, this list of conditions and the following | |||
* disclaimer in the documentation and/or other materials provided | |||
* with the distribution. | |||
* | |||
* - Neither the name of the Eclipse Foundation, Inc. nor the | |||
* names of its contributors may be used to endorse or promote | |||
* products derived from this software without specific prior | |||
* written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | |||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, | |||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
package org.eclipse.jgit.attributes; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertFalse; | |||
import static org.junit.Assert.assertTrue; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.util.ArrayList; | |||
import java.util.Collection; | |||
import java.util.Collections; | |||
import java.util.HashSet; | |||
import java.util.List; | |||
import java.util.Set; | |||
import org.eclipse.jgit.api.Git; | |||
import org.eclipse.jgit.api.errors.GitAPIException; | |||
import org.eclipse.jgit.api.errors.NoFilepatternException; | |||
import org.eclipse.jgit.attributes.Attribute.State; | |||
import org.eclipse.jgit.dircache.DirCacheIterator; | |||
import org.eclipse.jgit.errors.NoWorkTreeException; | |||
import org.eclipse.jgit.junit.JGitTestUtil; | |||
import org.eclipse.jgit.junit.RepositoryTestCase; | |||
import org.eclipse.jgit.lib.FileMode; | |||
import org.eclipse.jgit.lib.Repository; | |||
import org.eclipse.jgit.treewalk.FileTreeIterator; | |||
import org.eclipse.jgit.treewalk.TreeWalk; | |||
import org.eclipse.jgit.treewalk.TreeWalk.OperationType; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
/** | |||
* Tests the attributes are correctly computed in a {@link TreeWalk}. | |||
* | |||
* @see TreeWalk#getAttributes() | |||
*/ | |||
public class TreeWalkAttributeTest extends RepositoryTestCase { | |||
private static final FileMode M = FileMode.MISSING; | |||
private static final FileMode D = FileMode.TREE; | |||
private static final FileMode F = FileMode.REGULAR_FILE; | |||
private static Attribute EOL_CRLF = new Attribute("eol", "crlf"); | |||
private static Attribute EOL_LF = new Attribute("eol", "lf"); | |||
private static Attribute TEXT_SET = new Attribute("text", State.SET); | |||
private static Attribute TEXT_UNSET = new Attribute("text", State.UNSET); | |||
private static Attribute DELTA_UNSET = new Attribute("delta", State.UNSET); | |||
private static Attribute DELTA_SET = new Attribute("delta", State.SET); | |||
private static Attribute CUSTOM_GLOBAL = new Attribute("custom", "global"); | |||
private static Attribute CUSTOM_INFO = new Attribute("custom", "info"); | |||
private static Attribute CUSTOM_ROOT = new Attribute("custom", "root"); | |||
private static Attribute CUSTOM_PARENT = new Attribute("custom", "parent"); | |||
private static Attribute CUSTOM_CURRENT = new Attribute("custom", "current"); | |||
private static Attribute CUSTOM2_UNSET = new Attribute("custom2", | |||
State.UNSET); | |||
private static Attribute CUSTOM2_SET = new Attribute("custom2", State.SET); | |||
private TreeWalk walk; | |||
private TreeWalk ci_walk; | |||
private Git git; | |||
private File customAttributeFile; | |||
@Override | |||
@Before | |||
public void setUp() throws Exception { | |||
super.setUp(); | |||
git = new Git(db); | |||
} | |||
@Override | |||
@After | |||
public void tearDown() throws Exception { | |||
super.tearDown(); | |||
if (customAttributeFile != null) | |||
customAttributeFile.delete(); | |||
} | |||
/** | |||
* Checks that the attributes are computed correctly depending on the | |||
* operation type. | |||
* <p> | |||
* In this test we changed the content of the attribute files in the working | |||
* tree compared to the one in the index. | |||
* </p> | |||
* | |||
* @throws IOException | |||
* @throws NoFilepatternException | |||
* @throws GitAPIException | |||
*/ | |||
@Test | |||
public void testCheckinCheckoutDifferences() throws IOException, | |||
NoFilepatternException, GitAPIException { | |||
writeGlobalAttributeFile("globalAttributesFile", "*.txt -custom2"); | |||
writeAttributesFile(".git/info/attributes", "*.txt eol=crlf"); | |||
writeAttributesFile(".gitattributes", "*.txt custom=root"); | |||
writeAttributesFile("level1/.gitattributes", "*.txt text"); | |||
writeAttributesFile("level1/level2/.gitattributes", "*.txt -delta"); | |||
writeTrashFile("l0.txt", ""); | |||
writeTrashFile("level1/l1.txt", ""); | |||
writeTrashFile("level1/level2/l2.txt", ""); | |||
git.add().addFilepattern(".").call(); | |||
beginWalk(); | |||
// Modify all attributes | |||
writeGlobalAttributeFile("globalAttributesFile", "*.txt custom2"); | |||
writeAttributesFile(".git/info/attributes", "*.txt eol=lf"); | |||
writeAttributesFile(".gitattributes", "*.txt custom=info"); | |||
writeAttributesFile("level1/.gitattributes", "*.txt -text"); | |||
writeAttributesFile("level1/level2/.gitattributes", "*.txt delta"); | |||
assertEntry(F, ".gitattributes"); | |||
assertEntry(F, "l0.txt", asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET), | |||
asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET)); | |||
assertEntry(D, "level1"); | |||
assertEntry(F, "level1/.gitattributes"); | |||
assertEntry(F, "level1/l1.txt", | |||
asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET, TEXT_UNSET), | |||
asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET, TEXT_SET)); | |||
assertEntry(D, "level1/level2"); | |||
assertEntry(F, "level1/level2/.gitattributes"); | |||
assertEntry(F, "level1/level2/l2.txt", | |||
asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET, TEXT_UNSET, DELTA_SET), | |||
asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET, TEXT_SET, DELTA_UNSET)); | |||
endWalk(); | |||
} | |||
/** | |||
* Checks that the index is used as fallback when the git attributes file | |||
* are missing in the working tree. | |||
* | |||
* @throws IOException | |||
* @throws NoFilepatternException | |||
* @throws GitAPIException | |||
*/ | |||
@Test | |||
public void testIndexOnly() throws IOException, NoFilepatternException, | |||
GitAPIException { | |||
List<File> attrFiles = new ArrayList<File>(); | |||
attrFiles.add(writeGlobalAttributeFile("globalAttributesFile", | |||
"*.txt -custom2")); | |||
attrFiles.add(writeAttributesFile(".git/info/attributes", | |||
"*.txt eol=crlf")); | |||
attrFiles | |||
.add(writeAttributesFile(".gitattributes", "*.txt custom=root")); | |||
attrFiles | |||
.add(writeAttributesFile("level1/.gitattributes", "*.txt text")); | |||
attrFiles.add(writeAttributesFile("level1/level2/.gitattributes", | |||
"*.txt -delta")); | |||
writeTrashFile("l0.txt", ""); | |||
writeTrashFile("level1/l1.txt", ""); | |||
writeTrashFile("level1/level2/l2.txt", ""); | |||
git.add().addFilepattern(".").call(); | |||
// Modify all attributes | |||
for (File attrFile : attrFiles) | |||
attrFile.delete(); | |||
beginWalk(); | |||
assertEntry(M, ".gitattributes"); | |||
assertEntry(F, "l0.txt", asSet(CUSTOM_ROOT)); | |||
assertEntry(D, "level1"); | |||
assertEntry(M, "level1/.gitattributes"); | |||
assertEntry(F, "level1/l1.txt", | |||
asSet(CUSTOM_ROOT, TEXT_SET)); | |||
assertEntry(D, "level1/level2"); | |||
assertEntry(M, "level1/level2/.gitattributes"); | |||
assertEntry(F, "level1/level2/l2.txt", | |||
asSet(CUSTOM_ROOT, TEXT_SET, DELTA_UNSET)); | |||
endWalk(); | |||
} | |||
/** | |||
* Check that we search in the working tree for attributes although the file | |||
* we are currently inspecting does not exist anymore in the working tree. | |||
* | |||
* @throws IOException | |||
* @throws NoFilepatternException | |||
* @throws GitAPIException | |||
*/ | |||
@Test | |||
public void testIndexOnly2() | |||
throws IOException, NoFilepatternException, GitAPIException { | |||
File l2 = writeTrashFile("level1/level2/l2.txt", ""); | |||
writeTrashFile("level1/level2/l1.txt", ""); | |||
git.add().addFilepattern(".").call(); | |||
writeAttributesFile(".gitattributes", "*.txt custom=root"); | |||
assertTrue(l2.delete()); | |||
beginWalk(); | |||
assertEntry(F, ".gitattributes"); | |||
assertEntry(D, "level1"); | |||
assertEntry(D, "level1/level2"); | |||
assertEntry(F, "level1/level2/l1.txt", asSet(CUSTOM_ROOT)); | |||
assertEntry(M, "level1/level2/l2.txt", asSet(CUSTOM_ROOT)); | |||
endWalk(); | |||
} | |||
/** | |||
* Basic test for git attributes. | |||
* <p> | |||
* In this use case files are present in both the working tree and the index | |||
* </p> | |||
* | |||
* @throws IOException | |||
* @throws NoFilepatternException | |||
* @throws GitAPIException | |||
*/ | |||
@Test | |||
public void testRules() throws IOException, NoFilepatternException, | |||
GitAPIException { | |||
writeAttributesFile(".git/info/attributes", "windows* eol=crlf"); | |||
writeAttributesFile(".gitattributes", "*.txt eol=lf"); | |||
writeTrashFile("windows.file", ""); | |||
writeTrashFile("windows.txt", ""); | |||
writeTrashFile("readme.txt", ""); | |||
writeAttributesFile("src/config/.gitattributes", "*.txt -delta"); | |||
writeTrashFile("src/config/readme.txt", ""); | |||
writeTrashFile("src/config/windows.file", ""); | |||
writeTrashFile("src/config/windows.txt", ""); | |||
beginWalk(); | |||
git.add().addFilepattern(".").call(); | |||
assertEntry(F, ".gitattributes"); | |||
assertEntry(F, "readme.txt", asSet(EOL_LF)); | |||
assertEntry(D, "src"); | |||
assertEntry(D, "src/config"); | |||
assertEntry(F, "src/config/.gitattributes"); | |||
assertEntry(F, "src/config/readme.txt", asSet(DELTA_UNSET, EOL_LF)); | |||
assertEntry(F, "src/config/windows.file", asSet(EOL_CRLF)); | |||
assertEntry(F, "src/config/windows.txt", asSet(DELTA_UNSET, EOL_CRLF)); | |||
assertEntry(F, "windows.file", asSet(EOL_CRLF)); | |||
assertEntry(F, "windows.txt", asSet(EOL_CRLF)); | |||
endWalk(); | |||
} | |||
/** | |||
* Checks that if there is no .gitattributes file in the repository | |||
* everything still work fine. | |||
* | |||
* @throws IOException | |||
*/ | |||
@Test | |||
public void testNoAttributes() throws IOException { | |||
writeTrashFile("l0.txt", ""); | |||
writeTrashFile("level1/l1.txt", ""); | |||
writeTrashFile("level1/level2/l2.txt", ""); | |||
beginWalk(); | |||
assertEntry(F, "l0.txt"); | |||
assertEntry(D, "level1"); | |||
assertEntry(F, "level1/l1.txt"); | |||
assertEntry(D, "level1/level2"); | |||
assertEntry(F, "level1/level2/l2.txt"); | |||
endWalk(); | |||
} | |||
/** | |||
* Checks that an empty .gitattribute file does not return incorrect value. | |||
* | |||
* @throws IOException | |||
*/ | |||
@Test | |||
public void testEmptyGitAttributeFile() throws IOException { | |||
writeAttributesFile(".git/info/attributes", ""); | |||
writeTrashFile("l0.txt", ""); | |||
writeAttributesFile(".gitattributes", ""); | |||
writeTrashFile("level1/l1.txt", ""); | |||
writeTrashFile("level1/level2/l2.txt", ""); | |||
beginWalk(); | |||
assertEntry(F, ".gitattributes"); | |||
assertEntry(F, "l0.txt"); | |||
assertEntry(D, "level1"); | |||
assertEntry(F, "level1/l1.txt"); | |||
assertEntry(D, "level1/level2"); | |||
assertEntry(F, "level1/level2/l2.txt"); | |||
endWalk(); | |||
} | |||
@Test | |||
public void testNoMatchingAttributes() throws IOException { | |||
writeAttributesFile(".git/info/attributes", "*.java delta"); | |||
writeAttributesFile(".gitattributes", "*.java -delta"); | |||
writeAttributesFile("levelA/.gitattributes", "*.java eol=lf"); | |||
writeAttributesFile("levelB/.gitattributes", "*.txt eol=lf"); | |||
writeTrashFile("levelA/lA.txt", ""); | |||
beginWalk(); | |||
assertEntry(F, ".gitattributes"); | |||
assertEntry(D, "levelA"); | |||
assertEntry(F, "levelA/.gitattributes"); | |||
assertEntry(F, "levelA/lA.txt"); | |||
assertEntry(D, "levelB"); | |||
assertEntry(F, "levelB/.gitattributes"); | |||
endWalk(); | |||
} | |||
/** | |||
* Checks that $GIT_DIR/info/attributes file has the highest precedence. | |||
* | |||
* @throws IOException | |||
*/ | |||
@Test | |||
public void testPrecedenceInfo() throws IOException { | |||
writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global"); | |||
writeAttributesFile(".git/info/attributes", "*.txt custom=info"); | |||
writeAttributesFile(".gitattributes", "*.txt custom=root"); | |||
writeAttributesFile("level1/.gitattributes", "*.txt custom=parent"); | |||
writeAttributesFile("level1/level2/.gitattributes", | |||
"*.txt custom=current"); | |||
writeTrashFile("level1/level2/file.txt", ""); | |||
beginWalk(); | |||
assertEntry(F, ".gitattributes"); | |||
assertEntry(D, "level1"); | |||
assertEntry(F, "level1/.gitattributes"); | |||
assertEntry(D, "level1/level2"); | |||
assertEntry(F, "level1/level2/.gitattributes"); | |||
assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_INFO)); | |||
endWalk(); | |||
} | |||
/** | |||
* Checks that a subfolder ".gitattributes" file has precedence over its | |||
* parent. | |||
* | |||
* @throws IOException | |||
*/ | |||
@Test | |||
public void testPrecedenceCurrent() throws IOException { | |||
writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global"); | |||
writeAttributesFile(".gitattributes", "*.txt custom=root"); | |||
writeAttributesFile("level1/.gitattributes", "*.txt custom=parent"); | |||
writeAttributesFile("level1/level2/.gitattributes", | |||
"*.txt custom=current"); | |||
writeTrashFile("level1/level2/file.txt", ""); | |||
beginWalk(); | |||
assertEntry(F, ".gitattributes"); | |||
assertEntry(D, "level1"); | |||
assertEntry(F, "level1/.gitattributes"); | |||
assertEntry(D, "level1/level2"); | |||
assertEntry(F, "level1/level2/.gitattributes"); | |||
assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_CURRENT)); | |||
endWalk(); | |||
} | |||
/** | |||
* Checks that the parent ".gitattributes" file is used as fallback. | |||
* | |||
* @throws IOException | |||
*/ | |||
@Test | |||
public void testPrecedenceParent() throws IOException { | |||
writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global"); | |||
writeAttributesFile(".gitattributes", "*.txt custom=root"); | |||
writeAttributesFile("level1/.gitattributes", "*.txt custom=parent"); | |||
writeTrashFile("level1/level2/file.txt", ""); | |||
beginWalk(); | |||
assertEntry(F, ".gitattributes"); | |||
assertEntry(D, "level1"); | |||
assertEntry(F, "level1/.gitattributes"); | |||
assertEntry(D, "level1/level2"); | |||
assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_PARENT)); | |||
endWalk(); | |||
} | |||
/** | |||
* Checks that the grand parent ".gitattributes" file is used as fallback. | |||
* | |||
* @throws IOException | |||
*/ | |||
@Test | |||
public void testPrecedenceRoot() throws IOException { | |||
writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global"); | |||
writeAttributesFile(".gitattributes", "*.txt custom=root"); | |||
writeTrashFile("level1/level2/file.txt", ""); | |||
beginWalk(); | |||
assertEntry(F, ".gitattributes"); | |||
assertEntry(D, "level1"); | |||
assertEntry(D, "level1/level2"); | |||
assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_ROOT)); | |||
endWalk(); | |||
} | |||
/** | |||
* Checks that the global attribute file is used as fallback. | |||
* | |||
* @throws IOException | |||
*/ | |||
@Test | |||
public void testPrecedenceGlobal() throws IOException { | |||
writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global"); | |||
writeTrashFile("level1/level2/file.txt", ""); | |||
beginWalk(); | |||
assertEntry(D, "level1"); | |||
assertEntry(D, "level1/level2"); | |||
assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_GLOBAL)); | |||
endWalk(); | |||
} | |||
/** | |||
* Checks the precedence on a hierarchy with multiple attributes. | |||
* <p> | |||
* In this test all file are present in both the working tree and the index. | |||
* </p> | |||
* | |||
* @throws IOException | |||
* @throws GitAPIException | |||
* @throws NoFilepatternException | |||
*/ | |||
@Test | |||
public void testHierarchyBothIterator() throws IOException, | |||
NoFilepatternException, GitAPIException { | |||
writeAttributesFile(".git/info/attributes", "*.global eol=crlf"); | |||
writeAttributesFile(".gitattributes", "*.local eol=lf"); | |||
writeAttributesFile("level1/.gitattributes", "*.local text"); | |||
writeAttributesFile("level1/level2/.gitattributes", "*.local -text"); | |||
writeTrashFile("l0.global", ""); | |||
writeTrashFile("l0.local", ""); | |||
writeTrashFile("level1/l1.global", ""); | |||
writeTrashFile("level1/l1.local", ""); | |||
writeTrashFile("level1/level2/l2.global", ""); | |||
writeTrashFile("level1/level2/l2.local", ""); | |||
beginWalk(); | |||
git.add().addFilepattern(".").call(); | |||
assertEntry(F, ".gitattributes"); | |||
assertEntry(F, "l0.global", asSet(EOL_CRLF)); | |||
assertEntry(F, "l0.local", asSet(EOL_LF)); | |||
assertEntry(D, "level1"); | |||
assertEntry(F, "level1/.gitattributes"); | |||
assertEntry(F, "level1/l1.global", asSet(EOL_CRLF)); | |||
assertEntry(F, "level1/l1.local", asSet(EOL_LF, TEXT_SET)); | |||
assertEntry(D, "level1/level2"); | |||
assertEntry(F, "level1/level2/.gitattributes"); | |||
assertEntry(F, "level1/level2/l2.global", asSet(EOL_CRLF)); | |||
assertEntry(F, "level1/level2/l2.local", asSet(EOL_LF, TEXT_UNSET)); | |||
endWalk(); | |||
} | |||
/** | |||
* Checks the precedence on a hierarchy with multiple attributes. | |||
* <p> | |||
* In this test all file are present only in the working tree. | |||
* </p> | |||
* | |||
* @throws IOException | |||
* @throws GitAPIException | |||
* @throws NoFilepatternException | |||
*/ | |||
@Test | |||
public void testHierarchyWorktreeOnly() | |||
throws IOException, NoFilepatternException, GitAPIException { | |||
writeAttributesFile(".git/info/attributes", "*.global eol=crlf"); | |||
writeAttributesFile(".gitattributes", "*.local eol=lf"); | |||
writeAttributesFile("level1/.gitattributes", "*.local text"); | |||
writeAttributesFile("level1/level2/.gitattributes", "*.local -text"); | |||
writeTrashFile("l0.global", ""); | |||
writeTrashFile("l0.local", ""); | |||
writeTrashFile("level1/l1.global", ""); | |||
writeTrashFile("level1/l1.local", ""); | |||
writeTrashFile("level1/level2/l2.global", ""); | |||
writeTrashFile("level1/level2/l2.local", ""); | |||
beginWalk(); | |||
assertEntry(F, ".gitattributes"); | |||
assertEntry(F, "l0.global", asSet(EOL_CRLF)); | |||
assertEntry(F, "l0.local", asSet(EOL_LF)); | |||
assertEntry(D, "level1"); | |||
assertEntry(F, "level1/.gitattributes"); | |||
assertEntry(F, "level1/l1.global", asSet(EOL_CRLF)); | |||
assertEntry(F, "level1/l1.local", asSet(EOL_LF, TEXT_SET)); | |||
assertEntry(D, "level1/level2"); | |||
assertEntry(F, "level1/level2/.gitattributes"); | |||
assertEntry(F, "level1/level2/l2.global", asSet(EOL_CRLF)); | |||
assertEntry(F, "level1/level2/l2.local", asSet(EOL_LF, TEXT_UNSET)); | |||
endWalk(); | |||
} | |||
/** | |||
* Checks that the list of attributes is an aggregation of all the | |||
* attributes from the attributes files hierarchy. | |||
* | |||
* @throws IOException | |||
*/ | |||
@Test | |||
public void testAggregation() throws IOException { | |||
writeGlobalAttributeFile("globalAttributesFile", "*.txt -custom2"); | |||
writeAttributesFile(".git/info/attributes", "*.txt eol=crlf"); | |||
writeAttributesFile(".gitattributes", "*.txt custom=root"); | |||
writeAttributesFile("level1/.gitattributes", "*.txt text"); | |||
writeAttributesFile("level1/level2/.gitattributes", "*.txt -delta"); | |||
writeTrashFile("l0.txt", ""); | |||
writeTrashFile("level1/l1.txt", ""); | |||
writeTrashFile("level1/level2/l2.txt", ""); | |||
beginWalk(); | |||
assertEntry(F, ".gitattributes"); | |||
assertEntry(F, "l0.txt", asSet(EOL_CRLF, CUSTOM_ROOT, CUSTOM2_UNSET)); | |||
assertEntry(D, "level1"); | |||
assertEntry(F, "level1/.gitattributes"); | |||
assertEntry(F, "level1/l1.txt", | |||
asSet(EOL_CRLF, CUSTOM_ROOT, TEXT_SET, CUSTOM2_UNSET)); | |||
assertEntry(D, "level1/level2"); | |||
assertEntry(F, "level1/level2/.gitattributes"); | |||
assertEntry( | |||
F, | |||
"level1/level2/l2.txt", | |||
asSet(EOL_CRLF, CUSTOM_ROOT, TEXT_SET, DELTA_UNSET, | |||
CUSTOM2_UNSET)); | |||
endWalk(); | |||
} | |||
/** | |||
* Checks that the last entry in .gitattributes is used if 2 lines match the | |||
* same attribute | |||
* | |||
* @throws IOException | |||
*/ | |||
@Test | |||
public void testOverriding() throws IOException { | |||
writeAttributesFile(".git/info/attributes",// | |||
// | |||
"*.txt custom=current",// | |||
"*.txt custom=parent",// | |||
"*.txt custom=root",// | |||
"*.txt custom=info", | |||
// | |||
"*.txt delta",// | |||
"*.txt -delta", | |||
// | |||
"*.txt eol=lf",// | |||
"*.txt eol=crlf", | |||
// | |||
"*.txt text",// | |||
"*.txt -text"); | |||
writeTrashFile("l0.txt", ""); | |||
beginWalk(); | |||
assertEntry(F, "l0.txt", | |||
asSet(TEXT_UNSET, EOL_CRLF, DELTA_UNSET, CUSTOM_INFO)); | |||
endWalk(); | |||
} | |||
/** | |||
* Checks that the last value of an attribute is used if in the same line an | |||
* attribute is defined several time. | |||
* | |||
* @throws IOException | |||
*/ | |||
@Test | |||
public void testOverriding2() throws IOException { | |||
writeAttributesFile(".git/info/attributes", | |||
"*.txt custom=current custom=parent custom=root custom=info",// | |||
"*.txt delta -delta",// | |||
"*.txt eol=lf eol=crlf",// | |||
"*.txt text -text"); | |||
writeTrashFile("l0.txt", ""); | |||
beginWalk(); | |||
assertEntry(F, "l0.txt", | |||
asSet(TEXT_UNSET, EOL_CRLF, DELTA_UNSET, CUSTOM_INFO)); | |||
endWalk(); | |||
} | |||
@Test | |||
public void testRulesInherited() throws Exception { | |||
writeAttributesFile(".gitattributes", "**/*.txt eol=lf"); | |||
writeTrashFile("src/config/readme.txt", ""); | |||
writeTrashFile("src/config/windows.file", ""); | |||
beginWalk(); | |||
assertEntry(F, ".gitattributes"); | |||
assertEntry(D, "src"); | |||
assertEntry(D, "src/config"); | |||
assertEntry(F, "src/config/readme.txt", asSet(EOL_LF)); | |||
assertEntry(F, "src/config/windows.file", | |||
Collections.<Attribute> emptySet()); | |||
endWalk(); | |||
} | |||
private void beginWalk() throws NoWorkTreeException, IOException { | |||
walk = new TreeWalk(db); | |||
walk.addTree(new FileTreeIterator(db)); | |||
walk.addTree(new DirCacheIterator(db.readDirCache())); | |||
ci_walk = new TreeWalk(db); | |||
ci_walk.setOperationType(OperationType.CHECKIN_OP); | |||
ci_walk.addTree(new FileTreeIterator(db)); | |||
ci_walk.addTree(new DirCacheIterator(db.readDirCache())); | |||
} | |||
/** | |||
* Assert an entry in which checkin and checkout attributes are expected to | |||
* be the same. | |||
* | |||
* @param type | |||
* @param pathName | |||
* @param forBothOperaiton | |||
* @throws IOException | |||
*/ | |||
private void assertEntry(FileMode type, String pathName, | |||
Set<Attribute> forBothOperaiton) throws IOException { | |||
assertEntry(type, pathName, forBothOperaiton, forBothOperaiton); | |||
} | |||
/** | |||
* Assert an entry with no attribute expected. | |||
* | |||
* @param type | |||
* @param pathName | |||
* @throws IOException | |||
*/ | |||
private void assertEntry(FileMode type, String pathName) throws IOException { | |||
assertEntry(type, pathName, Collections.<Attribute> emptySet(), | |||
Collections.<Attribute> emptySet()); | |||
} | |||
/** | |||
* Assert that an entry; | |||
* <ul> | |||
* <li>Has the correct type</li> | |||
* <li>Exist in the tree walk</li> | |||
* <li>Has the expected attributes on a checkin operation</li> | |||
* <li>Has the expected attributes on a checkout operation</li> | |||
* </ul> | |||
* | |||
* @param type | |||
* @param pathName | |||
* @param checkinAttributes | |||
* @param checkoutAttributes | |||
* @throws IOException | |||
*/ | |||
private void assertEntry(FileMode type, String pathName, | |||
Set<Attribute> checkinAttributes, Set<Attribute> checkoutAttributes) | |||
throws IOException { | |||
assertTrue("walk has entry", walk.next()); | |||
assertTrue("walk has entry", ci_walk.next()); | |||
assertEquals(pathName, walk.getPathString()); | |||
assertEquals(type, walk.getFileMode(0)); | |||
assertEquals(checkinAttributes, | |||
asSet(ci_walk.getAttributes().values())); | |||
assertEquals(checkoutAttributes, asSet(walk.getAttributes().values())); | |||
if (D.equals(type)) { | |||
walk.enterSubtree(); | |||
ci_walk.enterSubtree(); | |||
} | |||
} | |||
private static Set<Attribute> asSet(Collection<Attribute> attributes) { | |||
Set<Attribute> ret = new HashSet<Attribute>(); | |||
for (Attribute a : attributes) { | |||
ret.add(a); | |||
} | |||
return (ret); | |||
} | |||
private File writeAttributesFile(String name, String... rules) | |||
throws IOException { | |||
StringBuilder data = new StringBuilder(); | |||
for (String line : rules) | |||
data.append(line + "\n"); | |||
return writeTrashFile(name, data.toString()); | |||
} | |||
/** | |||
* Creates an attributes file and set its location in the git configuration. | |||
* | |||
* @param fileName | |||
* @param attributes | |||
* @return The attribute file | |||
* @throws IOException | |||
* @see Repository#getConfig() | |||
*/ | |||
private File writeGlobalAttributeFile(String fileName, String... attributes) | |||
throws IOException { | |||
customAttributeFile = File.createTempFile("tmp_", fileName, null); | |||
customAttributeFile.deleteOnExit(); | |||
StringBuilder attributesFileContent = new StringBuilder(); | |||
for (String attr : attributes) { | |||
attributesFileContent.append(attr).append("\n"); | |||
} | |||
JGitTestUtil.write(customAttributeFile, | |||
attributesFileContent.toString()); | |||
db.getConfig().setString("core", null, "attributesfile", | |||
customAttributeFile.getAbsolutePath()); | |||
return customAttributeFile; | |||
} | |||
static Set<Attribute> asSet(Attribute... attrs) { | |||
HashSet<Attribute> result = new HashSet<Attribute>(); | |||
for (Attribute attr : attrs) | |||
result.add(attr); | |||
return result; | |||
} | |||
private void endWalk() throws IOException { | |||
assertFalse("Not all files tested", walk.next()); | |||
assertFalse("Not all files tested", ci_walk.next()); | |||
} | |||
} |
@@ -14,6 +14,20 @@ | |||
</message_arguments> | |||
</filter> | |||
</resource> | |||
<resource path="src/org/eclipse/jgit/lib/Repository.java" type="org.eclipse.jgit.lib.Repository"> | |||
<filter comment="Only implementors of Repository are affected. That should be allowed" id="336695337"> | |||
<message_arguments> | |||
<message_argument value="org.eclipse.jgit.lib.Repository"/> | |||
<message_argument value="createAttributesNodeProvider()"/> | |||
</message_arguments> | |||
</filter> | |||
<filter id="336695337"> | |||
<message_arguments> | |||
<message_argument value="org.eclipse.jgit.lib.Repository"/> | |||
<message_argument value="newAttributesNodeProvider()"/> | |||
</message_arguments> | |||
</filter> | |||
</resource> | |||
<resource path="src/org/eclipse/jgit/transport/PushCertificate.java" type="org.eclipse.jgit.transport.PushCertificate"> | |||
<filter comment="PushCertificate wasn't really usable in 4.0" id="338722907"> | |||
<message_arguments> |
@@ -63,6 +63,7 @@ import org.eclipse.jgit.lib.ObjectInserter; | |||
import org.eclipse.jgit.lib.Repository; | |||
import org.eclipse.jgit.treewalk.FileTreeIterator; | |||
import org.eclipse.jgit.treewalk.TreeWalk; | |||
import org.eclipse.jgit.treewalk.TreeWalk.OperationType; | |||
import org.eclipse.jgit.treewalk.WorkingTreeIterator; | |||
import org.eclipse.jgit.treewalk.filter.PathFilterGroup; | |||
@@ -139,6 +140,7 @@ public class AddCommand extends GitCommand<DirCache> { | |||
try (ObjectInserter inserter = repo.newObjectInserter(); | |||
final TreeWalk tw = new TreeWalk(repo)) { | |||
tw.setOperationType(OperationType.CHECKIN_OP); | |||
dc = repo.lockDirCache(); | |||
DirCacheIterator c; | |||
@@ -146,6 +148,7 @@ public class AddCommand extends GitCommand<DirCache> { | |||
tw.addTree(new DirCacheBuildIterator(builder)); | |||
if (workingTreeIterator == null) | |||
workingTreeIterator = new FileTreeIterator(repo); | |||
workingTreeIterator.setDirCacheIterator(tw, 0); | |||
tw.addTree(workingTreeIterator); | |||
tw.setRecursive(true); | |||
if (!addAll) |
@@ -86,6 +86,7 @@ import org.eclipse.jgit.revwalk.RevWalk; | |||
import org.eclipse.jgit.treewalk.CanonicalTreeParser; | |||
import org.eclipse.jgit.treewalk.FileTreeIterator; | |||
import org.eclipse.jgit.treewalk.TreeWalk; | |||
import org.eclipse.jgit.treewalk.TreeWalk.OperationType; | |||
import org.eclipse.jgit.util.ChangeIdUtil; | |||
/** | |||
@@ -328,6 +329,7 @@ public class CommitCommand extends GitCommand<RevCommit> { | |||
boolean emptyCommit = true; | |||
try (TreeWalk treeWalk = new TreeWalk(repo)) { | |||
treeWalk.setOperationType(OperationType.CHECKIN_OP); | |||
int dcIdx = treeWalk | |||
.addTree(new DirCacheBuildIterator(existingBuilder)); | |||
int fIdx = treeWalk.addTree(new FileTreeIterator(repo)); |
@@ -0,0 +1,81 @@ | |||
/* | |||
* Copyright (C) 2014, Arthur Daussy <arthur.daussy@obeo.fr> | |||
* and other copyright owners as documented in the project's IP log. | |||
* | |||
* This program and the accompanying materials are made available | |||
* under the terms of the Eclipse Distribution License v1.0 which | |||
* accompanies this distribution, is reproduced below, and is | |||
* available at http://www.eclipse.org/org/documents/edl-v10.php | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or | |||
* without modification, are permitted provided that the following | |||
* conditions are met: | |||
* | |||
* - Redistributions of source code must retain the above copyright | |||
* notice, this list of conditions and the following disclaimer. | |||
* | |||
* - Redistributions in binary form must reproduce the above | |||
* copyright notice, this list of conditions and the following | |||
* disclaimer in the documentation and/or other materials provided | |||
* with the distribution. | |||
* | |||
* - Neither the name of the Eclipse Foundation, Inc. nor the | |||
* names of its contributors may be used to endorse or promote | |||
* products derived from this software without specific prior | |||
* written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | |||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, | |||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
package org.eclipse.jgit.attributes; | |||
import java.io.IOException; | |||
import org.eclipse.jgit.lib.CoreConfig; | |||
/** | |||
* An interface used to retrieve the global and info {@link AttributesNode}s. | |||
* | |||
* @since 4.2 | |||
* | |||
*/ | |||
public interface AttributesNodeProvider { | |||
/** | |||
* Retrieve the {@link AttributesNode} that holds the information located | |||
* in $GIT_DIR/info/attributes file. | |||
* | |||
* @return the {@link AttributesNode} that holds the information located in | |||
* $GIT_DIR/info/attributes file. | |||
* @throws IOException | |||
* if an error is raised while parsing the attributes file | |||
*/ | |||
public AttributesNode getInfoAttributesNode() throws IOException; | |||
/** | |||
* Retrieve the {@link AttributesNode} that holds the information located | |||
* in the global gitattributes file. | |||
* | |||
* @return the {@link AttributesNode} that holds the information located in | |||
* the global gitattributes file. | |||
* @throws IOException | |||
* IOException if an error is raised while parsing the | |||
* attributes file | |||
* @see CoreConfig#getAttributesFile() | |||
*/ | |||
public AttributesNode getGlobalAttributesNode() throws IOException; | |||
} |
@@ -0,0 +1,57 @@ | |||
/* | |||
* Copyright (C) 2015, Christian Halstrick <christian.halstrick@sap.com> | |||
* and other copyright owners as documented in the project's IP log. | |||
* | |||
* This program and the accompanying materials are made available | |||
* under the terms of the Eclipse Distribution License v1.0 which | |||
* accompanies this distribution, is reproduced below, and is | |||
* available at http://www.eclipse.org/org/documents/edl-v10.php | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or | |||
* without modification, are permitted provided that the following | |||
* conditions are met: | |||
* | |||
* - Redistributions of source code must retain the above copyright | |||
* notice, this list of conditions and the following disclaimer. | |||
* | |||
* - Redistributions in binary form must reproduce the above | |||
* copyright notice, this list of conditions and the following | |||
* disclaimer in the documentation and/or other materials provided | |||
* with the distribution. | |||
* | |||
* - Neither the name of the Eclipse Foundation, Inc. nor the | |||
* names of its contributors may be used to endorse or promote | |||
* products derived from this software without specific prior | |||
* written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | |||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, | |||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
package org.eclipse.jgit.attributes; | |||
import java.util.Map; | |||
/** | |||
* Interface for classes which provide git attributes | |||
* | |||
* @since 4.2 | |||
*/ | |||
public interface AttributesProvider { | |||
/** | |||
* @return the currently active attributes by attribute key | |||
*/ | |||
public Map<String, Attribute> getAttributes(); | |||
} |
@@ -76,6 +76,7 @@ import org.eclipse.jgit.lib.ObjectInserter; | |||
import org.eclipse.jgit.lib.Repository; | |||
import org.eclipse.jgit.treewalk.FileTreeIterator; | |||
import org.eclipse.jgit.treewalk.TreeWalk; | |||
import org.eclipse.jgit.treewalk.TreeWalk.OperationType; | |||
import org.eclipse.jgit.treewalk.filter.PathFilterGroup; | |||
import org.eclipse.jgit.util.FS; | |||
import org.eclipse.jgit.util.IO; | |||
@@ -963,6 +964,7 @@ public class DirCache { | |||
private void updateSmudgedEntries() throws IOException { | |||
List<String> paths = new ArrayList<String>(128); | |||
try (TreeWalk walk = new TreeWalk(repository)) { | |||
walk.setOperationType(OperationType.CHECKIN_OP); | |||
for (int i = 0; i < entryCnt; i++) | |||
if (sortedEntries[i].isSmudged()) | |||
paths.add(sortedEntries[i].getPathString()); |
@@ -44,8 +44,13 @@ | |||
package org.eclipse.jgit.internal.storage.dfs; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.text.MessageFormat; | |||
import java.util.Collections; | |||
import org.eclipse.jgit.attributes.AttributesNode; | |||
import org.eclipse.jgit.attributes.AttributesNodeProvider; | |||
import org.eclipse.jgit.attributes.AttributesRule; | |||
import org.eclipse.jgit.internal.JGitText; | |||
import org.eclipse.jgit.lib.Constants; | |||
import org.eclipse.jgit.lib.RefUpdate; | |||
@@ -126,4 +131,36 @@ public abstract class DfsRepository extends Repository { | |||
public ReflogReader getReflogReader(String refName) throws IOException { | |||
throw new UnsupportedOperationException(); | |||
} | |||
@Override | |||
public AttributesNodeProvider createAttributesNodeProvider() { | |||
// TODO Check if the implementation used in FileRepository can be used | |||
// for this kind of repository | |||
return new EmptyAttributesNodeProvider(); | |||
} | |||
private static class EmptyAttributesNodeProvider implements | |||
AttributesNodeProvider { | |||
private EmptyAttributesNode emptyAttributesNode = new EmptyAttributesNode(); | |||
public AttributesNode getInfoAttributesNode() throws IOException { | |||
return emptyAttributesNode; | |||
} | |||
public AttributesNode getGlobalAttributesNode() throws IOException { | |||
return emptyAttributesNode; | |||
} | |||
private static class EmptyAttributesNode extends AttributesNode { | |||
public EmptyAttributesNode() { | |||
super(Collections.<AttributesRule> emptyList()); | |||
} | |||
@Override | |||
public void parse(InputStream in) throws IOException { | |||
// Do nothing | |||
} | |||
} | |||
} | |||
} |
@@ -49,11 +49,15 @@ package org.eclipse.jgit.internal.storage.file; | |||
import static org.eclipse.jgit.lib.RefDatabase.ALL; | |||
import java.io.File; | |||
import java.io.FileInputStream; | |||
import java.io.FileNotFoundException; | |||
import java.io.IOException; | |||
import java.text.MessageFormat; | |||
import java.util.HashSet; | |||
import java.util.Set; | |||
import org.eclipse.jgit.attributes.AttributesNode; | |||
import org.eclipse.jgit.attributes.AttributesNodeProvider; | |||
import org.eclipse.jgit.errors.ConfigInvalidException; | |||
import org.eclipse.jgit.events.ConfigChangedEvent; | |||
import org.eclipse.jgit.events.ConfigChangedListener; | |||
@@ -479,4 +483,63 @@ public class FileRepository extends Repository { | |||
return new ReflogReaderImpl(this, ref.getName()); | |||
return null; | |||
} | |||
@Override | |||
public AttributesNodeProvider createAttributesNodeProvider() { | |||
return new AttributesNodeProviderImpl(this); | |||
} | |||
/** | |||
* Implementation a {@link AttributesNodeProvider} for a | |||
* {@link FileRepository}. | |||
* | |||
* @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a> | |||
* | |||
*/ | |||
static class AttributesNodeProviderImpl implements | |||
AttributesNodeProvider { | |||
private AttributesNode infoAttributesNode; | |||
private AttributesNode globalAttributesNode; | |||
/** | |||
* Constructor. | |||
* | |||
* @param repo | |||
* {@link Repository} that will provide the attribute nodes. | |||
*/ | |||
protected AttributesNodeProviderImpl(Repository repo) { | |||
infoAttributesNode = new InfoAttributesNode(repo); | |||
globalAttributesNode = new GlobalAttributesNode(repo); | |||
} | |||
public AttributesNode getInfoAttributesNode() throws IOException { | |||
if (infoAttributesNode instanceof InfoAttributesNode) | |||
infoAttributesNode = ((InfoAttributesNode) infoAttributesNode) | |||
.load(); | |||
return infoAttributesNode; | |||
} | |||
public AttributesNode getGlobalAttributesNode() throws IOException { | |||
if (globalAttributesNode instanceof GlobalAttributesNode) | |||
globalAttributesNode = ((GlobalAttributesNode) globalAttributesNode) | |||
.load(); | |||
return globalAttributesNode; | |||
} | |||
static void loadRulesFromFile(AttributesNode r, File attrs) | |||
throws FileNotFoundException, IOException { | |||
if (attrs.exists()) { | |||
FileInputStream in = new FileInputStream(attrs); | |||
try { | |||
r.parse(in); | |||
} finally { | |||
in.close(); | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,87 @@ | |||
/* | |||
* Copyright (C) 2014, Arthur Daussy <arthur.daussy@obeo.fr> | |||
* Copyright (C) 2015, Christian Halstrick <christian.halstrick@sap.com> | |||
* and other copyright owners as documented in the project's IP log. | |||
* | |||
* This program and the accompanying materials are made available | |||
* under the terms of the Eclipse Distribution License v1.0 which | |||
* accompanies this distribution, is reproduced below, and is | |||
* available at http://www.eclipse.org/org/documents/edl-v10.php | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or | |||
* without modification, are permitted provided that the following | |||
* conditions are met: | |||
* | |||
* - Redistributions of source code must retain the above copyright | |||
* notice, this list of conditions and the following disclaimer. | |||
* | |||
* - Redistributions in binary form must reproduce the above | |||
* copyright notice, this list of conditions and the following | |||
* disclaimer in the documentation and/or other materials provided | |||
* with the distribution. | |||
* | |||
* - Neither the name of the Eclipse Foundation, Inc. nor the | |||
* names of its contributors may be used to endorse or promote | |||
* products derived from this software without specific prior | |||
* written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | |||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, | |||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
package org.eclipse.jgit.internal.storage.file; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import org.eclipse.jgit.attributes.AttributesNode; | |||
import org.eclipse.jgit.lib.CoreConfig; | |||
import org.eclipse.jgit.lib.Repository; | |||
import org.eclipse.jgit.util.FS; | |||
/** Attribute node loaded from global system-wide file. */ | |||
public class GlobalAttributesNode extends AttributesNode { | |||
final Repository repository; | |||
/** | |||
* @param repository | |||
*/ | |||
public GlobalAttributesNode(Repository repository) { | |||
this.repository = repository; | |||
} | |||
/** | |||
* @return the attributes node | |||
* @throws IOException | |||
*/ | |||
public AttributesNode load() throws IOException { | |||
AttributesNode r = new AttributesNode(); | |||
FS fs = repository.getFS(); | |||
String path = repository.getConfig().get(CoreConfig.KEY) | |||
.getAttributesFile(); | |||
if (path != null) { | |||
File attributesFile; | |||
if (path.startsWith("~/")) { //$NON-NLS-1$ | |||
attributesFile = fs.resolve(fs.userHome(), | |||
path.substring(2)); | |||
} else { | |||
attributesFile = fs.resolve(null, path); | |||
} | |||
FileRepository.AttributesNodeProviderImpl.loadRulesFromFile(r, attributesFile); | |||
} | |||
return r.getRules().isEmpty() ? null : r; | |||
} | |||
} |
@@ -0,0 +1,80 @@ | |||
/* | |||
* Copyright (C) 2014, Arthur Daussy <arthur.daussy@obeo.fr> | |||
* Copyright (C) 2015, Christian Halstrick <christian.halstrick@sap.com> | |||
* and other copyright owners as documented in the project's IP log. | |||
* | |||
* This program and the accompanying materials are made available | |||
* under the terms of the Eclipse Distribution License v1.0 which | |||
* accompanies this distribution, is reproduced below, and is | |||
* available at http://www.eclipse.org/org/documents/edl-v10.php | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or | |||
* without modification, are permitted provided that the following | |||
* conditions are met: | |||
* | |||
* - Redistributions of source code must retain the above copyright | |||
* notice, this list of conditions and the following disclaimer. | |||
* | |||
* - Redistributions in binary form must reproduce the above | |||
* copyright notice, this list of conditions and the following | |||
* disclaimer in the documentation and/or other materials provided | |||
* with the distribution. | |||
* | |||
* - Neither the name of the Eclipse Foundation, Inc. nor the | |||
* names of its contributors may be used to endorse or promote | |||
* products derived from this software without specific prior | |||
* written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | |||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, | |||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
package org.eclipse.jgit.internal.storage.file; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import org.eclipse.jgit.attributes.AttributesNode; | |||
import org.eclipse.jgit.lib.Repository; | |||
import org.eclipse.jgit.util.FS; | |||
/** Attribute node loaded from the $GIT_DIR/info/attributes file. */ | |||
public class InfoAttributesNode extends AttributesNode { | |||
final Repository repository; | |||
/** | |||
* @param repository | |||
*/ | |||
public InfoAttributesNode(Repository repository) { | |||
this.repository = repository; | |||
} | |||
/** | |||
* @return the attributes node | |||
* @throws IOException | |||
*/ | |||
public AttributesNode load() throws IOException { | |||
AttributesNode r = new AttributesNode(); | |||
FS fs = repository.getFS(); | |||
File attributes = fs.resolve(repository.getDirectory(), | |||
"info/attributes"); //$NON-NLS-1$ | |||
FileRepository.AttributesNodeProviderImpl.loadRulesFromFile(r, attributes); | |||
return r.getRules().isEmpty() ? null : r; | |||
} | |||
} |
@@ -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) |
@@ -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 | |||
*/ |
@@ -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); | |||
} | |||
/** |
@@ -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. | |||
* <p> | |||
@@ -213,8 +252,13 @@ public class TreeWalk implements AutoCloseable { | |||
private boolean postChildren; | |||
private AttributesNodeProvider attributesNodeProvider; | |||
AbstractTreeIterator currentHead; | |||
/** Cached attribute for the current entry */ | |||
private Map<String, Attribute> attrs = null; | |||
/** | |||
* Create a new tree walker for a given repository. | |||
* | |||
@@ -225,6 +269,7 @@ public class TreeWalk implements AutoCloseable { | |||
*/ | |||
public TreeWalk(final Repository repo) { | |||
this(repo.newObjectReader(), true); | |||
attributesNodeProvider = repo.createAttributesNodeProvider(); | |||
} | |||
/** | |||
@@ -356,8 +401,29 @@ public class TreeWalk implements AutoCloseable { | |||
postOrderTraversal = b; | |||
} | |||
/** | |||
* Sets the {@link AttributesNodeProvider} for this {@link TreeWalk}. | |||
* <p> | |||
* This is a requirement for a correct computation of the git attributes. | |||
* If this {@link TreeWalk} has been built using | |||
* {@link #TreeWalk(Repository)} constructor, the | |||
* {@link AttributesNodeProvider} has already been set. Indeed,the | |||
* {@link Repository} can provide an {@link AttributesNodeProvider} using | |||
* {@link Repository#createAttributesNodeProvider()} method. Otherwise you | |||
* should provide one. | |||
* </p> | |||
* | |||
* @see Repository#createAttributesNodeProvider() | |||
* @param provider | |||
* @since 4.2 | |||
*/ | |||
public void setAttributesNodeProvider(AttributesNodeProvider provider) { | |||
attributesNodeProvider = provider; | |||
} | |||
/** Reset this walker so new tree iterators can be added to it. */ | |||
public void reset() { | |||
attrs = null; | |||
trees = NO_TREES; | |||
advance = false; | |||
depth = 0; | |||
@@ -401,6 +467,7 @@ public class TreeWalk implements AutoCloseable { | |||
advance = false; | |||
depth = 0; | |||
attrs = null; | |||
} | |||
/** | |||
@@ -450,6 +517,7 @@ public class TreeWalk implements AutoCloseable { | |||
trees = r; | |||
advance = false; | |||
depth = 0; | |||
attrs = null; | |||
} | |||
/** | |||
@@ -546,6 +614,7 @@ public class TreeWalk implements AutoCloseable { | |||
public boolean next() throws MissingObjectException, | |||
IncorrectObjectTypeException, CorruptObjectException, IOException { | |||
try { | |||
attrs = null; | |||
if (advance) { | |||
advance = false; | |||
postChildren = false; | |||
@@ -915,6 +984,7 @@ public class TreeWalk implements AutoCloseable { | |||
*/ | |||
public void enterSubtree() throws MissingObjectException, | |||
IncorrectObjectTypeException, CorruptObjectException, IOException { | |||
attrs = null; | |||
final AbstractTreeIterator ch = currentHead; | |||
final AbstractTreeIterator[] tmp = new AbstractTreeIterator[trees.length]; | |||
for (int i = 0; i < trees.length; i++) { | |||
@@ -1008,4 +1078,200 @@ public class TreeWalk implements AutoCloseable { | |||
static String pathOf(final byte[] buf, int pos, int end) { | |||
return RawParseUtils.decode(Constants.CHARSET, buf, pos, end); | |||
} | |||
/** | |||
* Retrieve the git attributes for the current entry. | |||
* | |||
* <h4>Git attribute computation</h4> | |||
* | |||
* <ul> | |||
* <li>Get the attributes matching the current path entry from the info file | |||
* (see {@link AttributesNodeProvider#getInfoAttributesNode()}).</li> | |||
* <li>Completes the list of attributes using the .gitattributes files | |||
* located on the current path (the further the directory that contains | |||
* .gitattributes is from the path in question, the lower its precedence). | |||
* For a checkin operation, it will look first on the working tree (if any). | |||
* If there is no attributes file, it will fallback on the index. For a | |||
* checkout operation, it will first use the index entry and then fallback | |||
* on the working tree if none.</li> | |||
* <li>In the end, completes the list of matching attributes using the | |||
* global attribute file define in the configuration (see | |||
* {@link AttributesNodeProvider#getGlobalAttributesNode()})</li> | |||
* | |||
* </ul> | |||
* | |||
* | |||
* <h4>Iterator constraints</h4> | |||
* | |||
* <p> | |||
* In order to have a correct list of attributes for the current entry, this | |||
* {@link TreeWalk} requires to have at least one | |||
* {@link AttributesNodeProvider} and a {@link DirCacheIterator} set up. An | |||
* {@link AttributesNodeProvider} is used to retrieve the attributes from | |||
* the info attributes file and the global attributes file. The | |||
* {@link DirCacheIterator} is used to retrieve the .gitattributes files | |||
* stored in the index. A {@link WorkingTreeIterator} can also be provided | |||
* to access the local version of the .gitattributes files. If none is | |||
* provided it will fallback on the {@link DirCacheIterator}. | |||
* </p> | |||
* | |||
* @return a {@link Set} of {@link Attribute}s that match the current entry. | |||
* @since 4.2 | |||
*/ | |||
public Map<String, Attribute> getAttributes() { | |||
if (attrs != null) | |||
return attrs; | |||
if (attributesNodeProvider == null) { | |||
// The work tree should have a AttributesNodeProvider to be able to | |||
// retrieve the info and global attributes node | |||
throw new IllegalStateException( | |||
"The tree walk should have one AttributesNodeProvider set in order to compute the git attributes."); //$NON-NLS-1$ | |||
} | |||
WorkingTreeIterator workingTreeIterator = getTree(WorkingTreeIterator.class); | |||
DirCacheIterator dirCacheIterator = getTree(DirCacheIterator.class); | |||
if (workingTreeIterator == null && dirCacheIterator == null) { | |||
// Can not retrieve the attributes without at least one of the above | |||
// iterators. | |||
return Collections.<String, Attribute> emptyMap(); | |||
} | |||
String path = currentHead.getEntryPathString(); | |||
final boolean isDir = FileMode.TREE.equals(currentHead.mode); | |||
Map<String, Attribute> attributes = new LinkedHashMap<String, Attribute>(); | |||
try { | |||
// Gets the info attributes | |||
AttributesNode infoNodeAttr = attributesNodeProvider | |||
.getInfoAttributesNode(); | |||
if (infoNodeAttr != null) { | |||
infoNodeAttr.getAttributes(path, isDir, attributes); | |||
} | |||
// Gets the attributes located on the current entry path | |||
getPerDirectoryEntryAttributes(path, isDir, operationType, | |||
workingTreeIterator, dirCacheIterator, | |||
attributes); | |||
// Gets the attributes located in the global attribute file | |||
AttributesNode globalNodeAttr = attributesNodeProvider | |||
.getGlobalAttributesNode(); | |||
if (globalNodeAttr != null) { | |||
globalNodeAttr.getAttributes(path, isDir, attributes); | |||
} | |||
} catch (IOException e) { | |||
throw new JGitInternalException("Error while parsing attributes", e); //$NON-NLS-1$ | |||
} | |||
return attributes; | |||
} | |||
/** | |||
* Get the attributes located on the current entry path. | |||
* | |||
* @param path | |||
* current entry path | |||
* @param isDir | |||
* holds true if the current entry is a directory | |||
* @param opType | |||
* type of operation | |||
* @param workingTreeIterator | |||
* a {@link WorkingTreeIterator} matching the current entry | |||
* @param dirCacheIterator | |||
* a {@link DirCacheIterator} matching the current entry | |||
* @param attributes | |||
* Non null map holding the existing attributes. This map will be | |||
* augmented with new entry. None entry will be overrided. | |||
* @throws IOException | |||
* It raises an {@link IOException} if a problem appears while | |||
* parsing one on the attributes file. | |||
*/ | |||
private void getPerDirectoryEntryAttributes(String path, boolean isDir, | |||
OperationType opType, WorkingTreeIterator workingTreeIterator, | |||
DirCacheIterator dirCacheIterator, Map<String, Attribute> attributes) | |||
throws IOException { | |||
// Prevents infinite recurrence | |||
if (workingTreeIterator != null || dirCacheIterator != null) { | |||
AttributesNode currentAttributesNode = getCurrentAttributesNode( | |||
opType, workingTreeIterator, dirCacheIterator); | |||
if (currentAttributesNode != null) { | |||
currentAttributesNode.getAttributes(path, isDir, attributes); | |||
} | |||
getPerDirectoryEntryAttributes(path, isDir, opType, | |||
getParent(workingTreeIterator, WorkingTreeIterator.class), | |||
getParent(dirCacheIterator, DirCacheIterator.class), | |||
attributes); | |||
} | |||
} | |||
private <T extends AbstractTreeIterator> T getParent(T current, | |||
Class<T> type) { | |||
if (current != null) { | |||
AbstractTreeIterator parent = current.parent; | |||
if (type.isInstance(parent)) { | |||
return type.cast(parent); | |||
} | |||
} | |||
return null; | |||
} | |||
private <T> T getTree(Class<T> type) { | |||
for (int i = 0; i < trees.length; i++) { | |||
AbstractTreeIterator tree = trees[i]; | |||
if (type.isInstance(tree)) { | |||
return type.cast(tree); | |||
} | |||
} | |||
return null; | |||
} | |||
/** | |||
* Get the {@link AttributesNode} for the current entry. | |||
* <p> | |||
* This method implements the fallback mechanism between the index and the | |||
* working tree depending on the operation type | |||
* </p> | |||
* | |||
* @param opType | |||
* @param workingTreeIterator | |||
* @param dirCacheIterator | |||
* @return a {@link AttributesNode} of the current entry, | |||
* {@link NullPointerException} otherwise. | |||
* @throws IOException | |||
* It raises an {@link IOException} if a problem appears while | |||
* parsing one on the attributes file. | |||
*/ | |||
private AttributesNode getCurrentAttributesNode(OperationType opType, | |||
WorkingTreeIterator workingTreeIterator, | |||
DirCacheIterator dirCacheIterator) throws IOException { | |||
AttributesNode attributesNode = null; | |||
switch (opType) { | |||
case CHECKIN_OP: | |||
if (workingTreeIterator != null) { | |||
attributesNode = workingTreeIterator.getEntryAttributesNode(); | |||
} | |||
if (attributesNode == null && dirCacheIterator != null) { | |||
attributesNode = dirCacheIterator | |||
.getEntryAttributesNode(getObjectReader()); | |||
} | |||
break; | |||
case CHECKOUT_OP: | |||
if (dirCacheIterator != null) { | |||
attributesNode = dirCacheIterator | |||
.getEntryAttributesNode(getObjectReader()); | |||
} | |||
if (attributesNode == null && workingTreeIterator != null) { | |||
attributesNode = workingTreeIterator.getEntryAttributesNode(); | |||
} | |||
break; | |||
default: | |||
throw new IllegalStateException( | |||
"The only supported operation types are:" //$NON-NLS-1$ | |||
+ OperationType.CHECKIN_OP + "," //$NON-NLS-1$ | |||
+ OperationType.CHECKOUT_OP); | |||
} | |||
return attributesNode; | |||
} | |||
} |
@@ -74,6 +74,8 @@ import org.eclipse.jgit.errors.NoWorkTreeException; | |||
import org.eclipse.jgit.ignore.FastIgnoreRule; | |||
import org.eclipse.jgit.ignore.IgnoreNode; | |||
import org.eclipse.jgit.internal.JGitText; | |||
import org.eclipse.jgit.internal.storage.file.GlobalAttributesNode; | |||
import org.eclipse.jgit.internal.storage.file.InfoAttributesNode; | |||
import org.eclipse.jgit.lib.Constants; | |||
import org.eclipse.jgit.lib.CoreConfig; | |||
import org.eclipse.jgit.lib.CoreConfig.CheckStat; | |||
@@ -150,14 +152,14 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { | |||
* Holds the {@link AttributesNode} that is stored in | |||
* $GIT_DIR/info/attributes file. | |||
*/ | |||
private AttributesNode infoAttributeNode; | |||
private AttributesNode infoAttributesNode; | |||
/** | |||
* Holds the {@link AttributesNode} that is stored in global attribute file. | |||
* | |||
* @see CoreConfig#getAttributesFile() | |||
*/ | |||
private AttributesNode globalAttributeNode; | |||
private AttributesNode globalAttributesNode; | |||
/** | |||
* Create a new iterator with no parent. | |||
@@ -202,8 +204,8 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { | |||
protected WorkingTreeIterator(final WorkingTreeIterator p) { | |||
super(p); | |||
state = p.state; | |||
infoAttributeNode = p.infoAttributeNode; | |||
globalAttributeNode = p.globalAttributeNode; | |||
infoAttributesNode = p.infoAttributesNode; | |||
globalAttributesNode = p.globalAttributesNode; | |||
} | |||
/** | |||
@@ -224,9 +226,9 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { | |||
entry = null; | |||
ignoreNode = new RootIgnoreNode(entry, repo); | |||
infoAttributeNode = new InfoAttributesNode(repo); | |||
infoAttributesNode = new InfoAttributesNode(repo); | |||
globalAttributeNode = new GlobalAttributesNode(repo); | |||
globalAttributesNode = new GlobalAttributesNode(repo); | |||
} | |||
/** | |||
@@ -678,9 +680,9 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { | |||
* @since 3.7 | |||
*/ | |||
public AttributesNode getInfoAttributesNode() throws IOException { | |||
if (infoAttributeNode instanceof InfoAttributesNode) | |||
infoAttributeNode = ((InfoAttributesNode) infoAttributeNode).load(); | |||
return infoAttributeNode; | |||
if (infoAttributesNode instanceof InfoAttributesNode) | |||
infoAttributesNode = ((InfoAttributesNode) infoAttributesNode).load(); | |||
return infoAttributesNode; | |||
} | |||
/** | |||
@@ -696,10 +698,10 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { | |||
* @since 3.7 | |||
*/ | |||
public AttributesNode getGlobalAttributesNode() throws IOException { | |||
if (globalAttributeNode instanceof GlobalAttributesNode) | |||
globalAttributeNode = ((GlobalAttributesNode) globalAttributeNode) | |||
if (globalAttributesNode instanceof GlobalAttributesNode) | |||
globalAttributesNode = ((GlobalAttributesNode) globalAttributesNode) | |||
.load(); | |||
return globalAttributeNode; | |||
return globalAttributesNode; | |||
} | |||
private static final Comparator<Entry> ENTRY_CMP = new Comparator<Entry>() { | |||
@@ -1296,68 +1298,6 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { | |||
} | |||
} | |||
/** | |||
* Attributes node loaded from global system-wide file. | |||
*/ | |||
private static class GlobalAttributesNode extends AttributesNode { | |||
final Repository repository; | |||
GlobalAttributesNode(Repository repository) { | |||
this.repository = repository; | |||
} | |||
AttributesNode load() throws IOException { | |||
AttributesNode r = new AttributesNode(); | |||
FS fs = repository.getFS(); | |||
String path = repository.getConfig().get(CoreConfig.KEY) | |||
.getAttributesFile(); | |||
if (path != null) { | |||
File attributesFile; | |||
if (path.startsWith("~/")) //$NON-NLS-1$ | |||
attributesFile = fs.resolve(fs.userHome(), | |||
path.substring(2)); | |||
else | |||
attributesFile = fs.resolve(null, path); | |||
loadRulesFromFile(r, attributesFile); | |||
} | |||
return r.getRules().isEmpty() ? null : r; | |||
} | |||
} | |||
/** Magic type indicating there may be rules for the top level. */ | |||
private static class InfoAttributesNode extends AttributesNode { | |||
final Repository repository; | |||
InfoAttributesNode(Repository repository) { | |||
this.repository = repository; | |||
} | |||
AttributesNode load() throws IOException { | |||
AttributesNode r = new AttributesNode(); | |||
FS fs = repository.getFS(); | |||
File attributes = fs.resolve(repository.getDirectory(), | |||
"info/attributes"); //$NON-NLS-1$ | |||
loadRulesFromFile(r, attributes); | |||
return r.getRules().isEmpty() ? null : r; | |||
} | |||
} | |||
private static void loadRulesFromFile(AttributesNode r, File attrs) | |||
throws FileNotFoundException, IOException { | |||
if (attrs.exists()) { | |||
FileInputStream in = new FileInputStream(attrs); | |||
try { | |||
r.parse(in); | |||
} finally { | |||
in.close(); | |||
} | |||
} | |||
} | |||
private static final class IteratorState { | |||
/** Options used to process the working tree. */ |