Browse Source

Adds the git attributes computation on the treewalk

Adds the getAttributes feature to the tree walk. The computation of
attributes needs to be done by the TreeWalk since it needs both a
WorkingTreeIterator and a DirCacheIterator.

Bug: 342372
CQ: 9120
Change-Id: I5e33257fd8c9895869a128bad3fd1e720409d361
Signed-off-by: Arthur Daussy <arthur.daussy@obeo.fr>
Signed-off-by: Christian Halstrick <christian.halstrick@sap.com>
stable-4.3
Arthur Daussy 10 years ago committed by Matthias Sohn
parent
commit
12280c02db
  1. 12
      org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java
  2. 2
      org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
  3. 47
      org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java
  4. 865
      org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java
  5. 14
      org.eclipse.jgit/.settings/.api_filters
  6. 3
      org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
  7. 2
      org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
  8. 81
      org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java
  9. 57
      org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java
  10. 2
      org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
  11. 37
      org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java
  12. 63
      org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
  13. 87
      org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java
  14. 80
      org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java
  15. 2
      org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
  16. 11
      org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
  17. 2
      org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
  18. 270
      org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
  19. 88
      org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java

12
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); DirCacheIterator itr = walk.getTree(0, DirCacheIterator.class);
assertNotNull("has tree", itr); assertNotNull("has tree", itr);
AttributesNode attributeNode = itr.getEntryAttributesNode(db AttributesNode attributesNode = itr.getEntryAttributesNode(db
.newObjectReader()); .newObjectReader());
assertAttributeNode(pathName, attributeNode, nodeAttrs); assertAttributesNode(pathName, attributesNode, nodeAttrs);
if (D.equals(type)) if (D.equals(type))
walk.enterSubtree(); walk.enterSubtree();
} }
private void assertAttributeNode(String pathName, private void assertAttributesNode(String pathName,
AttributesNode attributeNode, List<Attribute> nodeAttrs) { AttributesNode attributesNode, List<Attribute> nodeAttrs) {
if (attributeNode == null) if (attributesNode == null)
assertTrue(nodeAttrs == null || nodeAttrs.isEmpty()); assertTrue(nodeAttrs == null || nodeAttrs.isEmpty());
else { else {
Map<String, Attribute> entryAttributes = new LinkedHashMap<String, Attribute>(); Map<String, Attribute> entryAttributes = new LinkedHashMap<String, Attribute>();
attributeNode.getAttributes(pathName, false, entryAttributes); attributesNode.getAttributes(pathName, false, entryAttributes);
if (nodeAttrs != null && !nodeAttrs.isEmpty()) { if (nodeAttrs != null && !nodeAttrs.isEmpty()) {
for (Attribute attribute : nodeAttrs) { for (Attribute attribute : nodeAttrs) {

2
org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeNodeTest.java → org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java

@ -60,7 +60,7 @@ import org.junit.Test;
/** /**
* Test {@link AttributesNode} * Test {@link AttributesNode}
*/ */
public class AttributeNodeTest { public class AttributesNodeTest {
private static final Attribute A_SET_ATTR = new Attribute("A", SET); private static final Attribute A_SET_ATTR = new Attribute("A", SET);

47
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 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 EOL_LF = new Attribute("eol", "lf");
private static Attribute DELTA_UNSET = new Attribute("delta", State.UNSET); private static Attribute DELTA_UNSET = new Attribute("delta", State.UNSET);
private static Attribute CUSTOM_VALUE = new Attribute("custom", "value");
private TreeWalk walk; private TreeWalk walk;
@Test @Test
@ -112,25 +108,19 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase {
walk = beginWalk(); walk = beginWalk();
assertIteration(F, ".gitattributes"); assertIteration(F, ".gitattributes");
assertIteration(F, "global.txt", asList(EOL_LF), null, assertIteration(F, "global.txt", asList(EOL_LF));
asList(CUSTOM_VALUE)); assertIteration(F, "readme.txt", asList(EOL_LF));
assertIteration(F, "readme.txt", asList(EOL_LF), null,
asList(CUSTOM_VALUE));
assertIteration(D, "src"); assertIteration(D, "src");
assertIteration(D, "src/config"); assertIteration(D, "src/config");
assertIteration(F, "src/config/.gitattributes"); assertIteration(F, "src/config/.gitattributes");
assertIteration(F, "src/config/readme.txt", asList(DELTA_UNSET), null, assertIteration(F, "src/config/readme.txt", asList(DELTA_UNSET));
asList(CUSTOM_VALUE)); assertIteration(F, "src/config/windows.file", null);
assertIteration(F, "src/config/windows.file", null, asList(EOL_CRLF), assertIteration(F, "src/config/windows.txt", asList(DELTA_UNSET));
null);
assertIteration(F, "src/config/windows.txt", asList(DELTA_UNSET),
asList(EOL_CRLF), asList(CUSTOM_VALUE));
assertIteration(F, "windows.file", null, asList(EOL_CRLF), null); assertIteration(F, "windows.file", null);
assertIteration(F, "windows.txt", asList(EOL_LF), asList(EOL_CRLF), assertIteration(F, "windows.txt", asList(EOL_LF));
asList(CUSTOM_VALUE));
endWalk(); endWalk();
} }
@ -212,14 +202,11 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase {
private void assertIteration(FileMode type, String pathName) private void assertIteration(FileMode type, String pathName)
throws IOException { throws IOException {
assertIteration(type, pathName, Collections.<Attribute> emptyList(), assertIteration(type, pathName, Collections.<Attribute> emptyList());
Collections.<Attribute> emptyList(),
Collections.<Attribute> emptyList());
} }
private void assertIteration(FileMode type, String pathName, private void assertIteration(FileMode type, String pathName,
List<Attribute> nodeAttrs, List<Attribute> infoAttrs, List<Attribute> nodeAttrs)
List<Attribute> globalAttrs)
throws IOException { throws IOException {
assertTrue("walk has entry", walk.next()); assertTrue("walk has entry", walk.next());
assertEquals(pathName, walk.getPathString()); assertEquals(pathName, walk.getPathString());
@ -227,25 +214,21 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase {
WorkingTreeIterator itr = walk.getTree(0, WorkingTreeIterator.class); WorkingTreeIterator itr = walk.getTree(0, WorkingTreeIterator.class);
assertNotNull("has tree", itr); assertNotNull("has tree", itr);
AttributesNode attributeNode = itr.getEntryAttributesNode(); AttributesNode attributesNode = itr.getEntryAttributesNode();
assertAttributeNode(pathName, attributeNode, nodeAttrs); assertAttributesNode(pathName, attributesNode, nodeAttrs);
AttributesNode infoAttributeNode = itr.getInfoAttributesNode();
assertAttributeNode(pathName, infoAttributeNode, infoAttrs);
AttributesNode globalAttributeNode = itr.getGlobalAttributesNode();
assertAttributeNode(pathName, globalAttributeNode, globalAttrs);
if (D.equals(type)) if (D.equals(type))
walk.enterSubtree(); walk.enterSubtree();
} }
private void assertAttributeNode(String pathName, private void assertAttributesNode(String pathName,
AttributesNode attributeNode, List<Attribute> nodeAttrs) { AttributesNode attributesNode, List<Attribute> nodeAttrs) {
if (attributeNode == null) if (attributesNode == null)
assertTrue(nodeAttrs == null || nodeAttrs.isEmpty()); assertTrue(nodeAttrs == null || nodeAttrs.isEmpty());
else { else {
Map<String, Attribute> entryAttributes = new LinkedHashMap<String, Attribute>(); Map<String, Attribute> entryAttributes = new LinkedHashMap<String, Attribute>();
attributeNode.getAttributes(pathName, false, entryAttributes); attributesNode.getAttributes(pathName, false, entryAttributes);
if (nodeAttrs != null && !nodeAttrs.isEmpty()) { if (nodeAttrs != null && !nodeAttrs.isEmpty()) {
for (Attribute attribute : nodeAttrs) { for (Attribute attribute : nodeAttrs) {

865
org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java

@ -0,0 +1,865 @@
/*
* Copyright (C) 2014, Obeo.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.attributes;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.NoFilepatternException;
import org.eclipse.jgit.attributes.Attribute.State;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* Tests the attributes are correctly computed in a {@link TreeWalk}.
*
* @see TreeWalk#getAttributes()
*/
public class TreeWalkAttributeTest extends RepositoryTestCase {
private static final FileMode M = FileMode.MISSING;
private static final FileMode D = FileMode.TREE;
private static final FileMode F = FileMode.REGULAR_FILE;
private static Attribute EOL_CRLF = new Attribute("eol", "crlf");
private static Attribute EOL_LF = new Attribute("eol", "lf");
private static Attribute TEXT_SET = new Attribute("text", State.SET);
private static Attribute TEXT_UNSET = new Attribute("text", State.UNSET);
private static Attribute DELTA_UNSET = new Attribute("delta", State.UNSET);
private static Attribute DELTA_SET = new Attribute("delta", State.SET);
private static Attribute CUSTOM_GLOBAL = new Attribute("custom", "global");
private static Attribute CUSTOM_INFO = new Attribute("custom", "info");
private static Attribute CUSTOM_ROOT = new Attribute("custom", "root");
private static Attribute CUSTOM_PARENT = new Attribute("custom", "parent");
private static Attribute CUSTOM_CURRENT = new Attribute("custom", "current");
private static Attribute CUSTOM2_UNSET = new Attribute("custom2",
State.UNSET);
private static Attribute CUSTOM2_SET = new Attribute("custom2", State.SET);
private TreeWalk walk;
private TreeWalk ci_walk;
private Git git;
private File customAttributeFile;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
git = new Git(db);
}
@Override
@After
public void tearDown() throws Exception {
super.tearDown();
if (customAttributeFile != null)
customAttributeFile.delete();
}
/**
* Checks that the attributes are computed correctly depending on the
* operation type.
* <p>
* In this test we changed the content of the attribute files in the working
* tree compared to the one in the index.
* </p>
*
* @throws IOException
* @throws NoFilepatternException
* @throws GitAPIException
*/
@Test
public void testCheckinCheckoutDifferences() throws IOException,
NoFilepatternException, GitAPIException {
writeGlobalAttributeFile("globalAttributesFile", "*.txt -custom2");
writeAttributesFile(".git/info/attributes", "*.txt eol=crlf");
writeAttributesFile(".gitattributes", "*.txt custom=root");
writeAttributesFile("level1/.gitattributes", "*.txt text");
writeAttributesFile("level1/level2/.gitattributes", "*.txt -delta");
writeTrashFile("l0.txt", "");
writeTrashFile("level1/l1.txt", "");
writeTrashFile("level1/level2/l2.txt", "");
git.add().addFilepattern(".").call();
beginWalk();
// Modify all attributes
writeGlobalAttributeFile("globalAttributesFile", "*.txt custom2");
writeAttributesFile(".git/info/attributes", "*.txt eol=lf");
writeAttributesFile(".gitattributes", "*.txt custom=info");
writeAttributesFile("level1/.gitattributes", "*.txt -text");
writeAttributesFile("level1/level2/.gitattributes", "*.txt delta");
assertEntry(F, ".gitattributes");
assertEntry(F, "l0.txt", asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET),
asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET));
assertEntry(D, "level1");
assertEntry(F, "level1/.gitattributes");
assertEntry(F, "level1/l1.txt",
asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET, TEXT_UNSET),
asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET, TEXT_SET));
assertEntry(D, "level1/level2");
assertEntry(F, "level1/level2/.gitattributes");
assertEntry(F, "level1/level2/l2.txt",
asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET, TEXT_UNSET, DELTA_SET),
asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET, TEXT_SET, DELTA_UNSET));
endWalk();
}
/**
* Checks that the index is used as fallback when the git attributes file
* are missing in the working tree.
*
* @throws IOException
* @throws NoFilepatternException
* @throws GitAPIException
*/
@Test
public void testIndexOnly() throws IOException, NoFilepatternException,
GitAPIException {
List<File> attrFiles = new ArrayList<File>();
attrFiles.add(writeGlobalAttributeFile("globalAttributesFile",
"*.txt -custom2"));
attrFiles.add(writeAttributesFile(".git/info/attributes",
"*.txt eol=crlf"));
attrFiles
.add(writeAttributesFile(".gitattributes", "*.txt custom=root"));
attrFiles
.add(writeAttributesFile("level1/.gitattributes", "*.txt text"));
attrFiles.add(writeAttributesFile("level1/level2/.gitattributes",
"*.txt -delta"));
writeTrashFile("l0.txt", "");
writeTrashFile("level1/l1.txt", "");
writeTrashFile("level1/level2/l2.txt", "");
git.add().addFilepattern(".").call();
// Modify all attributes
for (File attrFile : attrFiles)
attrFile.delete();
beginWalk();
assertEntry(M, ".gitattributes");
assertEntry(F, "l0.txt", asSet(CUSTOM_ROOT));
assertEntry(D, "level1");
assertEntry(M, "level1/.gitattributes");
assertEntry(F, "level1/l1.txt",
asSet(CUSTOM_ROOT, TEXT_SET));
assertEntry(D, "level1/level2");
assertEntry(M, "level1/level2/.gitattributes");
assertEntry(F, "level1/level2/l2.txt",
asSet(CUSTOM_ROOT, TEXT_SET, DELTA_UNSET));
endWalk();
}
/**
* Check that we search in the working tree for attributes although the file
* we are currently inspecting does not exist anymore in the working tree.
*
* @throws IOException
* @throws NoFilepatternException
* @throws GitAPIException
*/
@Test
public void testIndexOnly2()
throws IOException, NoFilepatternException, GitAPIException {
File l2 = writeTrashFile("level1/level2/l2.txt", "");
writeTrashFile("level1/level2/l1.txt", "");
git.add().addFilepattern(".").call();
writeAttributesFile(".gitattributes", "*.txt custom=root");
assertTrue(l2.delete());
beginWalk();
assertEntry(F, ".gitattributes");
assertEntry(D, "level1");
assertEntry(D, "level1/level2");
assertEntry(F, "level1/level2/l1.txt", asSet(CUSTOM_ROOT));
assertEntry(M, "level1/level2/l2.txt", asSet(CUSTOM_ROOT));
endWalk();
}
/**
* Basic test for git attributes.
* <p>
* In this use case files are present in both the working tree and the index
* </p>
*
* @throws IOException
* @throws NoFilepatternException
* @throws GitAPIException
*/
@Test
public void testRules() throws IOException, NoFilepatternException,
GitAPIException {
writeAttributesFile(".git/info/attributes", "windows* eol=crlf");
writeAttributesFile(".gitattributes", "*.txt eol=lf");
writeTrashFile("windows.file", "");
writeTrashFile("windows.txt", "");
writeTrashFile("readme.txt", "");
writeAttributesFile("src/config/.gitattributes", "*.txt -delta");
writeTrashFile("src/config/readme.txt", "");
writeTrashFile("src/config/windows.file", "");
writeTrashFile("src/config/windows.txt", "");
beginWalk();
git.add().addFilepattern(".").call();
assertEntry(F, ".gitattributes");
assertEntry(F, "readme.txt", asSet(EOL_LF));
assertEntry(D, "src");
assertEntry(D, "src/config");
assertEntry(F, "src/config/.gitattributes");
assertEntry(F, "src/config/readme.txt", asSet(DELTA_UNSET, EOL_LF));
assertEntry(F, "src/config/windows.file", asSet(EOL_CRLF));
assertEntry(F, "src/config/windows.txt", asSet(DELTA_UNSET, EOL_CRLF));
assertEntry(F, "windows.file", asSet(EOL_CRLF));
assertEntry(F, "windows.txt", asSet(EOL_CRLF));
endWalk();
}
/**
* Checks that if there is no .gitattributes file in the repository
* everything still work fine.
*
* @throws IOException
*/
@Test
public void testNoAttributes() throws IOException {
writeTrashFile("l0.txt", "");
writeTrashFile("level1/l1.txt", "");
writeTrashFile("level1/level2/l2.txt", "");
beginWalk();
assertEntry(F, "l0.txt");
assertEntry(D, "level1");
assertEntry(F, "level1/l1.txt");
assertEntry(D, "level1/level2");
assertEntry(F, "level1/level2/l2.txt");
endWalk();
}
/**
* Checks that an empty .gitattribute file does not return incorrect value.
*
* @throws IOException
*/
@Test
public void testEmptyGitAttributeFile() throws IOException {
writeAttributesFile(".git/info/attributes", "");
writeTrashFile("l0.txt", "");
writeAttributesFile(".gitattributes", "");
writeTrashFile("level1/l1.txt", "");
writeTrashFile("level1/level2/l2.txt", "");
beginWalk();
assertEntry(F, ".gitattributes");
assertEntry(F, "l0.txt");
assertEntry(D, "level1");
assertEntry(F, "level1/l1.txt");
assertEntry(D, "level1/level2");
assertEntry(F, "level1/level2/l2.txt");
endWalk();
}
@Test
public void testNoMatchingAttributes() throws IOException {
writeAttributesFile(".git/info/attributes", "*.java delta");
writeAttributesFile(".gitattributes", "*.java -delta");
writeAttributesFile("levelA/.gitattributes", "*.java eol=lf");
writeAttributesFile("levelB/.gitattributes", "*.txt eol=lf");
writeTrashFile("levelA/lA.txt", "");
beginWalk();
assertEntry(F, ".gitattributes");
assertEntry(D, "levelA");
assertEntry(F, "levelA/.gitattributes");
assertEntry(F, "levelA/lA.txt");
assertEntry(D, "levelB");
assertEntry(F, "levelB/.gitattributes");
endWalk();
}
/**
* Checks that $GIT_DIR/info/attributes file has the highest precedence.
*
* @throws IOException
*/
@Test
public void testPrecedenceInfo() throws IOException {
writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
writeAttributesFile(".git/info/attributes", "*.txt custom=info");
writeAttributesFile(".gitattributes", "*.txt custom=root");
writeAttributesFile("level1/.gitattributes", "*.txt custom=parent");
writeAttributesFile("level1/level2/.gitattributes",
"*.txt custom=current");
writeTrashFile("level1/level2/file.txt", "");
beginWalk();
assertEntry(F, ".gitattributes");
assertEntry(D, "level1");
assertEntry(F, "level1/.gitattributes");
assertEntry(D, "level1/level2");
assertEntry(F, "level1/level2/.gitattributes");
assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_INFO));
endWalk();
}
/**
* Checks that a subfolder ".gitattributes" file has precedence over its
* parent.
*
* @throws IOException
*/
@Test
public void testPrecedenceCurrent() throws IOException {
writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
writeAttributesFile(".gitattributes", "*.txt custom=root");
writeAttributesFile("level1/.gitattributes", "*.txt custom=parent");
writeAttributesFile("level1/level2/.gitattributes",
"*.txt custom=current");
writeTrashFile("level1/level2/file.txt", "");
beginWalk();
assertEntry(F, ".gitattributes");
assertEntry(D, "level1");
assertEntry(F, "level1/.gitattributes");
assertEntry(D, "level1/level2");
assertEntry(F, "level1/level2/.gitattributes");
assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_CURRENT));
endWalk();
}
/**
* Checks that the parent ".gitattributes" file is used as fallback.
*
* @throws IOException
*/
@Test
public void testPrecedenceParent() throws IOException {
writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
writeAttributesFile(".gitattributes", "*.txt custom=root");
writeAttributesFile("level1/.gitattributes", "*.txt custom=parent");
writeTrashFile("level1/level2/file.txt", "");
beginWalk();
assertEntry(F, ".gitattributes");
assertEntry(D, "level1");
assertEntry(F, "level1/.gitattributes");
assertEntry(D, "level1/level2");
assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_PARENT));
endWalk();
}
/**
* Checks that the grand parent ".gitattributes" file is used as fallback.
*
* @throws IOException
*/
@Test
public void testPrecedenceRoot() throws IOException {
writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
writeAttributesFile(".gitattributes", "*.txt custom=root");
writeTrashFile("level1/level2/file.txt", "");
beginWalk();
assertEntry(F, ".gitattributes");
assertEntry(D, "level1");
assertEntry(D, "level1/level2");
assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_ROOT));
endWalk();
}
/**
* Checks that the global attribute file is used as fallback.
*
* @throws IOException
*/
@Test
public void testPrecedenceGlobal() throws IOException {
writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
writeTrashFile("level1/level2/file.txt", "");
beginWalk();
assertEntry(D, "level1");
assertEntry(D, "level1/level2");
assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_GLOBAL));
endWalk();
}
/**
* Checks the precedence on a hierarchy with multiple attributes.
* <p>
* In this test all file are present in both the working tree and the index.
* </p>
*
* @throws IOException
* @throws GitAPIException
* @throws NoFilepatternException
*/
@Test
public void testHierarchyBothIterator() throws IOException,
NoFilepatternException, GitAPIException {
writeAttributesFile(".git/info/attributes", "*.global eol=crlf");
writeAttributesFile(".gitattributes", "*.local eol=lf");
writeAttributesFile("level1/.gitattributes", "*.local text");
writeAttributesFile("level1/level2/.gitattributes", "*.local -text");
writeTrashFile("l0.global", "");
writeTrashFile("l0.local", "");
writeTrashFile("level1/l1.global", "");
writeTrashFile("level1/l1.local", "");
writeTrashFile("level1/level2/l2.global", "");
writeTrashFile("level1/level2/l2.local", "");
beginWalk();
git.add().addFilepattern(".").call();
assertEntry(F, ".gitattributes");
assertEntry(F, "l0.global", asSet(EOL_CRLF));
assertEntry(F, "l0.local", asSet(EOL_LF));
assertEntry(D, "level1");
assertEntry(F, "level1/.gitattributes");
assertEntry(F, "level1/l1.global", asSet(EOL_CRLF));
assertEntry(F, "level1/l1.local", asSet(EOL_LF, TEXT_SET));
assertEntry(D, "level1/level2");
assertEntry(F, "level1/level2/.gitattributes");
assertEntry(F, "level1/level2/l2.global", asSet(EOL_CRLF));
assertEntry(F, "level1/level2/l2.local", asSet(EOL_LF, TEXT_UNSET));
endWalk();
}
/**
* Checks the precedence on a hierarchy with multiple attributes.
* <p>
* In this test all file are present only in the working tree.
* </p>
*
* @throws IOException
* @throws GitAPIException
* @throws NoFilepatternException
*/
@Test
public void testHierarchyWorktreeOnly()
throws IOException, NoFilepatternException, GitAPIException {
writeAttributesFile(".git/info/attributes", "*.global eol=crlf");
writeAttributesFile(".gitattributes", "*.local eol=lf");
writeAttributesFile("level1/.gitattributes", "*.local text");
writeAttributesFile("level1/level2/.gitattributes", "*.local -text");
writeTrashFile("l0.global", "");
writeTrashFile("l0.local", "");
writeTrashFile("level1/l1.global", "");
writeTrashFile("level1/l1.local", "");
writeTrashFile("level1/level2/l2.global", "");
writeTrashFile("level1/level2/l2.local", "");
beginWalk();
assertEntry(F, ".gitattributes");
assertEntry(F, "l0.global", asSet(EOL_CRLF));
assertEntry(F, "l0.local", asSet(EOL_LF));
assertEntry(D, "level1");
assertEntry(F, "level1/.gitattributes");
assertEntry(F, "level1/l1.global", asSet(EOL_CRLF));
assertEntry(F, "level1/l1.local", asSet(EOL_LF, TEXT_SET));
assertEntry(D, "level1/level2");
assertEntry(F, "level1/level2/.gitattributes");
assertEntry(F, "level1/level2/l2.global", asSet(EOL_CRLF));
assertEntry(F, "level1/level2/l2.local", asSet(EOL_LF, TEXT_UNSET));
endWalk();
}
/**
* Checks that the list of attributes is an aggregation of all the
* attributes from the attributes files hierarchy.
*
* @throws IOException
*/
@Test
public void testAggregation() throws IOException {
writeGlobalAttributeFile("globalAttributesFile", "*.txt -custom2");
writeAttributesFile(".git/info/attributes", "*.txt eol=crlf");
writeAttributesFile(".gitattributes", "*.txt custom=root");
writeAttributesFile("level1/.gitattributes", "*.txt text");
writeAttributesFile("level1/level2/.gitattributes", "*.txt -delta");
writeTrashFile("l0.txt", "");
writeTrashFile("level1/l1.txt", "");
writeTrashFile("level1/level2/l2.txt", "");
beginWalk();
assertEntry(F, ".gitattributes");
assertEntry(F, "l0.txt", asSet(EOL_CRLF, CUSTOM_ROOT, CUSTOM2_UNSET));
assertEntry(D, "level1");
assertEntry(F, "level1/.gitattributes");
assertEntry(F, "level1/l1.txt",
asSet(EOL_CRLF, CUSTOM_ROOT, TEXT_SET, CUSTOM2_UNSET));
assertEntry(D, "level1/level2");
assertEntry(F, "level1/level2/.gitattributes");
assertEntry(
F,
"level1/level2/l2.txt",
asSet(EOL_CRLF, CUSTOM_ROOT, TEXT_SET, DELTA_UNSET,
CUSTOM2_UNSET));
endWalk();
}
/**
* Checks that the last entry in .gitattributes is used if 2 lines match the
* same attribute
*
* @throws IOException
*/
@Test
public void testOverriding() throws IOException {
writeAttributesFile(".git/info/attributes",//
//
"*.txt custom=current",//
"*.txt custom=parent",//
"*.txt custom=root",//
"*.txt custom=info",
//
"*.txt delta",//
"*.txt -delta",
//
"*.txt eol=lf",//
"*.txt eol=crlf",
//
"*.txt text",//
"*.txt -text");
writeTrashFile("l0.txt", "");
beginWalk();
assertEntry(F, "l0.txt",
asSet(TEXT_UNSET, EOL_CRLF, DELTA_UNSET, CUSTOM_INFO));
endWalk();
}
/**
* Checks that the last value of an attribute is used if in the same line an
* attribute is defined several time.
*
* @throws IOException
*/
@Test
public void testOverriding2() throws IOException {
writeAttributesFile(".git/info/attributes",
"*.txt custom=current custom=parent custom=root custom=info",//
"*.txt delta -delta",//
"*.txt eol=lf eol=crlf",//
"*.txt text -text");
writeTrashFile("l0.txt", "");
beginWalk();
assertEntry(F, "l0.txt",
asSet(TEXT_UNSET, EOL_CRLF, DELTA_UNSET, CUSTOM_INFO));
endWalk();
}
@Test
public void testRulesInherited() throws Exception {
writeAttributesFile(".gitattributes", "**/*.txt eol=lf");
writeTrashFile("src/config/readme.txt", "");
writeTrashFile("src/config/windows.file", "");
beginWalk();
assertEntry(F, ".gitattributes");
assertEntry(D, "src");
assertEntry(D, "src/config");
assertEntry(F, "src/config/readme.txt", asSet(EOL_LF));
assertEntry(F, "src/config/windows.file",
Collections.<Attribute> emptySet());
endWalk();
}
private void beginWalk() throws NoWorkTreeException, IOException {
walk = new TreeWalk(db);
walk.addTree(new FileTreeIterator(db));
walk.addTree(new DirCacheIterator(db.readDirCache()));
ci_walk = new TreeWalk(db);
ci_walk.setOperationType(OperationType.CHECKIN_OP);
ci_walk.addTree(new FileTreeIterator(db));
ci_walk.addTree(new DirCacheIterator(db.readDirCache()));
}
/**
* Assert an entry in which checkin and checkout attributes are expected to
* be the same.
*
* @param type
* @param pathName
* @param forBothOperaiton
* @throws IOException
*/
private void assertEntry(FileMode type, String pathName,
Set<Attribute> forBothOperaiton) throws IOException {
assertEntry(type, pathName, forBothOperaiton, forBothOperaiton);
}
/**
* Assert an entry with no attribute expected.
*
* @param type
* @param pathName
* @throws IOException
*/
private void assertEntry(FileMode type, String pathName) throws IOException {
assertEntry(type, pathName, Collections.<Attribute> emptySet(),
Collections.<Attribute> emptySet());
}
/**
* Assert that an entry;
* <ul>
* <li>Has the correct type</li>
* <li>Exist in the tree walk</li>
* <li>Has the expected attributes on a checkin operation</li>
* <li>Has the expected attributes on a checkout operation</li>
* </ul>
*
* @param type
* @param pathName
* @param checkinAttributes
* @param checkoutAttributes
* @throws IOException
*/
private void assertEntry(FileMode type, String pathName,
Set<Attribute> checkinAttributes, Set<Attribute> checkoutAttributes)
throws IOException {
assertTrue("walk has entry", walk.next());
assertTrue("walk has entry", ci_walk.next());
assertEquals(pathName, walk.getPathString());
assertEquals(type, walk.getFileMode(0));
assertEquals(checkinAttributes,
asSet(ci_walk.getAttributes().values()));
assertEquals(checkoutAttributes, asSet(walk.getAttributes().values()));
if (D.equals(type)) {
walk.enterSubtree();
ci_walk.enterSubtree();
}
}
private static Set<Attribute> asSet(Collection<Attribute> attributes) {
Set<Attribute> ret = new HashSet<Attribute>();
for (Attribute a : attributes) {
ret.add(a);
}
return (ret);
}
private File writeAttributesFile(String name, String... rules)
throws IOException {
StringBuilder data = new StringBuilder();
for (String line : rules)
data.append(line + "\n");
return writeTrashFile(name, data.toString());
}
/**
* Creates an attributes file and set its location in the git configuration.
*
* @param fileName
* @param attributes
* @return The attribute file
* @throws IOException
* @see Repository#getConfig()
*/
private File writeGlobalAttributeFile(String fileName, String... attributes)
throws IOException {
customAttributeFile = File.createTempFile("tmp_", fileName, null);
customAttributeFile.deleteOnExit();
StringBuilder attributesFileContent = new StringBuilder();
for (String attr : attributes) {
attributesFileContent.append(attr).append("\n");
}
JGitTestUtil.write(customAttributeFile,
attributesFileContent.toString());
db.getConfig().setString("core", null, "attributesfile",
customAttributeFile.getAbsolutePath());
return customAttributeFile;
}
static Set<Attribute> asSet(Attribute... attrs) {
HashSet<Attribute> result = new HashSet<Attribute>();
for (Attribute attr : attrs)
result.add(attr);
return result;
}
private void endWalk() throws IOException {
assertFalse("Not all files tested", walk.next());
assertFalse("Not all files tested", ci_walk.next());
}
}

14
org.eclipse.jgit/.settings/.api_filters

@ -14,6 +14,20 @@
</message_arguments> </message_arguments>
</filter> </filter>
</resource> </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"> <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"> <filter comment="PushCertificate wasn't really usable in 4.0" id="338722907">
<message_arguments> <message_arguments>

3
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.lib.Repository;
import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
import org.eclipse.jgit.treewalk.WorkingTreeIterator; import org.eclipse.jgit.treewalk.WorkingTreeIterator;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup; import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
@ -139,6 +140,7 @@ public class AddCommand extends GitCommand<DirCache> {
try (ObjectInserter inserter = repo.newObjectInserter(); try (ObjectInserter inserter = repo.newObjectInserter();
final TreeWalk tw = new TreeWalk(repo)) { final TreeWalk tw = new TreeWalk(repo)) {
tw.setOperationType(OperationType.CHECKIN_OP);
dc = repo.lockDirCache(); dc = repo.lockDirCache();
DirCacheIterator c; DirCacheIterator c;
@ -146,6 +148,7 @@ public class AddCommand extends GitCommand<DirCache> {
tw.addTree(new DirCacheBuildIterator(builder)); tw.addTree(new DirCacheBuildIterator(builder));
if (workingTreeIterator == null) if (workingTreeIterator == null)
workingTreeIterator = new FileTreeIterator(repo); workingTreeIterator = new FileTreeIterator(repo);
workingTreeIterator.setDirCacheIterator(tw, 0);
tw.addTree(workingTreeIterator); tw.addTree(workingTreeIterator);
tw.setRecursive(true); tw.setRecursive(true);
if (!addAll) if (!addAll)

2
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.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
import org.eclipse.jgit.util.ChangeIdUtil; import org.eclipse.jgit.util.ChangeIdUtil;
/** /**
@ -328,6 +329,7 @@ public class CommitCommand extends GitCommand<RevCommit> {
boolean emptyCommit = true; boolean emptyCommit = true;
try (TreeWalk treeWalk = new TreeWalk(repo)) { try (TreeWalk treeWalk = new TreeWalk(repo)) {
treeWalk.setOperationType(OperationType.CHECKIN_OP);
int dcIdx = treeWalk int dcIdx = treeWalk
.addTree(new DirCacheBuildIterator(existingBuilder)); .addTree(new DirCacheBuildIterator(existingBuilder));
int fIdx = treeWalk.addTree(new FileTreeIterator(repo)); int fIdx = treeWalk.addTree(new FileTreeIterator(repo));

81
org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java

@ -0,0 +1,81 @@
/*
* Copyright (C) 2014, Arthur Daussy <arthur.daussy@obeo.fr>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.attributes;
import java.io.IOException;
import org.eclipse.jgit.lib.CoreConfig;
/**
* An interface used to retrieve the global and info {@link AttributesNode}s.
*
* @since 4.2
*
*/
public interface AttributesNodeProvider {
/**
* Retrieve the {@link AttributesNode} that holds the information located
* in $GIT_DIR/info/attributes file.
*
* @return the {@link AttributesNode} that holds the information located in
* $GIT_DIR/info/attributes file.
* @throws IOException
* if an error is raised while parsing the attributes file
*/
public AttributesNode getInfoAttributesNode() throws IOException;
/**
* Retrieve the {@link AttributesNode} that holds the information located
* in the global gitattributes file.
*
* @return the {@link AttributesNode} that holds the information located in
* the global gitattributes file.
* @throws IOException
* IOException if an error is raised while parsing the
* attributes file
* @see CoreConfig#getAttributesFile()
*/
public AttributesNode getGlobalAttributesNode() throws IOException;
}

57
org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java

@ -0,0 +1,57 @@
/*
* Copyright (C) 2015, Christian Halstrick <christian.halstrick@sap.com>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.attributes;
import java.util.Map;
/**
* Interface for classes which provide git attributes
*
* @since 4.2
*/
public interface AttributesProvider {
/**
* @return the currently active attributes by attribute key
*/
public Map<String, Attribute> getAttributes();
}

2
org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java vendored

@ -76,6 +76,7 @@ import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup; import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.IO;
@ -963,6 +964,7 @@ public class DirCache {
private void updateSmudgedEntries() throws IOException { private void updateSmudgedEntries() throws IOException {
List<String> paths = new ArrayList<String>(128); List<String> paths = new ArrayList<String>(128);
try (TreeWalk walk = new TreeWalk(repository)) { try (TreeWalk walk = new TreeWalk(repository)) {
walk.setOperationType(OperationType.CHECKIN_OP);
for (int i = 0; i < entryCnt; i++) for (int i = 0; i < entryCnt; i++)
if (sortedEntries[i].isSmudged()) if (sortedEntries[i].isSmudged())
paths.add(sortedEntries[i].getPathString()); paths.add(sortedEntries[i].getPathString());

37
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java

@ -44,8 +44,13 @@
package org.eclipse.jgit.internal.storage.dfs; package org.eclipse.jgit.internal.storage.dfs;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat; 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.internal.JGitText;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate;
@ -126,4 +131,36 @@ public abstract class DfsRepository extends Repository {
public ReflogReader getReflogReader(String refName) throws IOException { public ReflogReader getReflogReader(String refName) throws IOException {
throw new UnsupportedOperationException(); 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
}
}
}
} }

63
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 static org.eclipse.jgit.lib.RefDatabase.ALL;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; 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.errors.ConfigInvalidException;
import org.eclipse.jgit.events.ConfigChangedEvent; import org.eclipse.jgit.events.ConfigChangedEvent;
import org.eclipse.jgit.events.ConfigChangedListener; import org.eclipse.jgit.events.ConfigChangedListener;
@ -479,4 +483,63 @@ public class FileRepository extends Repository {
return new ReflogReaderImpl(this, ref.getName()); return new ReflogReaderImpl(this, ref.getName());
return null; 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();
}
}
}
}
} }

87
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java

@ -0,0 +1,87 @@
/*
* Copyright (C) 2014, Arthur Daussy <arthur.daussy@obeo.fr>
* Copyright (C) 2015, Christian Halstrick <christian.halstrick@sap.com>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.internal.storage.file;
import java.io.File;
import java.io.IOException;
import org.eclipse.jgit.attributes.AttributesNode;
import org.eclipse.jgit.lib.CoreConfig;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.FS;
/** Attribute node loaded from global system-wide file. */
public class GlobalAttributesNode extends AttributesNode {
final Repository repository;
/**
* @param repository
*/
public GlobalAttributesNode(Repository repository) {
this.repository = repository;
}
/**
* @return the attributes node
* @throws IOException
*/
public AttributesNode load() throws IOException {
AttributesNode r = new AttributesNode();
FS fs = repository.getFS();
String path = repository.getConfig().get(CoreConfig.KEY)
.getAttributesFile();
if (path != null) {
File attributesFile;
if (path.startsWith("~/")) { //$NON-NLS-1$
attributesFile = fs.resolve(fs.userHome(),
path.substring(2));
} else {
attributesFile = fs.resolve(null, path);
}
FileRepository.AttributesNodeProviderImpl.loadRulesFromFile(r, attributesFile);
}
return r.getRules().isEmpty() ? null : r;
}
}

80
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java

@ -0,0 +1,80 @@
/*
* Copyright (C) 2014, Arthur Daussy <arthur.daussy@obeo.fr>
* Copyright (C) 2015, Christian Halstrick <christian.halstrick@sap.com>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.internal.storage.file;
import java.io.File;
import java.io.IOException;
import org.eclipse.jgit.attributes.AttributesNode;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.FS;
/** Attribute node loaded from the $GIT_DIR/info/attributes file. */
public class InfoAttributesNode extends AttributesNode {
final Repository repository;
/**
* @param repository
*/
public InfoAttributesNode(Repository repository) {
this.repository = repository;
}
/**
* @return the attributes node
* @throws IOException
*/
public AttributesNode load() throws IOException {
AttributesNode r = new AttributesNode();
FS fs = repository.getFS();
File attributes = fs.resolve(repository.getDirectory(),
"info/attributes"); //$NON-NLS-1$
FileRepository.AttributesNodeProviderImpl.loadRulesFromFile(r, attributes);
return r.getRules().isEmpty() ? null : r;
}
}

2
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.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.WorkingTreeIterator; 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.AndTreeFilter;
import org.eclipse.jgit.treewalk.filter.IndexDiffFilter; import org.eclipse.jgit.treewalk.filter.IndexDiffFilter;
import org.eclipse.jgit.treewalk.filter.SkipWorkTreeFilter; import org.eclipse.jgit.treewalk.filter.SkipWorkTreeFilter;
@ -403,6 +404,7 @@ public class IndexDiff {
dirCache = repository.readDirCache(); dirCache = repository.readDirCache();
try (TreeWalk treeWalk = new TreeWalk(repository)) { try (TreeWalk treeWalk = new TreeWalk(repository)) {
treeWalk.setOperationType(OperationType.CHECKIN_OP);
treeWalk.setRecursive(true); treeWalk.setRecursive(true);
// add the trees (tree, dirchache, workdir) // add the trees (tree, dirchache, workdir)
if (tree != null) if (tree != null)

11
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.Set;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jgit.attributes.AttributesNodeProvider;
import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.errors.AmbiguousObjectException; import org.eclipse.jgit.errors.AmbiguousObjectException;
import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.CorruptObjectException;
@ -209,6 +210,16 @@ public abstract class Repository implements AutoCloseable {
*/ */
public abstract StoredConfig getConfig(); 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 * @return the used file system abstraction
*/ */

2
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. * the repository the walker will obtain data from.
*/ */
public NameConflictTreeWalk(final Repository repo) { public NameConflictTreeWalk(final Repository repo) {
this(repo.newObjectReader()); super(repo);
} }
/** /**

270
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java

@ -45,7 +45,17 @@
package org.eclipse.jgit.treewalk; package org.eclipse.jgit.treewalk;
import java.io.IOException; 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.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.MissingObjectException;
@ -82,9 +92,38 @@ import org.eclipse.jgit.util.RawParseUtils;
* Multiple simultaneous TreeWalk instances per {@link Repository} are * Multiple simultaneous TreeWalk instances per {@link Repository} are
* permitted, even from concurrent threads. * permitted, even from concurrent threads.
*/ */
public class TreeWalk implements AutoCloseable { public class TreeWalk implements AutoCloseable, AttributesProvider {
private static final AbstractTreeIterator[] NO_TREES = {}; 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. * Open a tree walk and filter to exactly one path.
* <p> * <p>
@ -213,8 +252,13 @@ public class TreeWalk implements AutoCloseable {
private boolean postChildren; private boolean postChildren;
private AttributesNodeProvider attributesNodeProvider;
AbstractTreeIterator currentHead; AbstractTreeIterator currentHead;
/** Cached attribute for the current entry */
private Map<String, Attribute> attrs = null;
/** /**
* Create a new tree walker for a given repository. * Create a new tree walker for a given repository.
* *
@ -225,6 +269,7 @@ public class TreeWalk implements AutoCloseable {
*/ */
public TreeWalk(final Repository repo) { public TreeWalk(final Repository repo) {
this(repo.newObjectReader(), true); this(repo.newObjectReader(), true);
attributesNodeProvider = repo.createAttributesNodeProvider();
} }
/** /**
@ -356,8 +401,29 @@ public class TreeWalk implements AutoCloseable {
postOrderTraversal = b; 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. */ /** Reset this walker so new tree iterators can be added to it. */
public void reset() { public void reset() {
attrs = null;
trees = NO_TREES; trees = NO_TREES;
advance = false; advance = false;
depth = 0; depth = 0;
@ -401,6 +467,7 @@ public class TreeWalk implements AutoCloseable {
advance = false; advance = false;
depth = 0; depth = 0;
attrs = null;
} }
/** /**
@ -450,6 +517,7 @@ public class TreeWalk implements AutoCloseable {
trees = r; trees = r;
advance = false; advance = false;
depth = 0; depth = 0;
attrs = null;
} }
/** /**
@ -546,6 +614,7 @@ public class TreeWalk implements AutoCloseable {
public boolean next() throws MissingObjectException, public boolean next() throws MissingObjectException,
IncorrectObjectTypeException, CorruptObjectException, IOException { IncorrectObjectTypeException, CorruptObjectException, IOException {
try { try {
attrs = null;
if (advance) { if (advance) {
advance = false; advance = false;
postChildren = false; postChildren = false;
@ -915,6 +984,7 @@ public class TreeWalk implements AutoCloseable {
*/ */
public void enterSubtree() throws MissingObjectException, public void enterSubtree() throws MissingObjectException,
IncorrectObjectTypeException, CorruptObjectException, IOException { IncorrectObjectTypeException, CorruptObjectException, IOException {
attrs = null;
final AbstractTreeIterator ch = currentHead; final AbstractTreeIterator ch = currentHead;
final AbstractTreeIterator[] tmp = new AbstractTreeIterator[trees.length]; final AbstractTreeIterator[] tmp = new AbstractTreeIterator[trees.length];
for (int i = 0; i < trees.length; i++) { 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) { static String pathOf(final byte[] buf, int pos, int end) {
return RawParseUtils.decode(Constants.CHARSET, buf, pos, 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;
}
} }

88
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.FastIgnoreRule;
import org.eclipse.jgit.ignore.IgnoreNode; import org.eclipse.jgit.ignore.IgnoreNode;
import org.eclipse.jgit.internal.JGitText; 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.Constants;
import org.eclipse.jgit.lib.CoreConfig; import org.eclipse.jgit.lib.CoreConfig;
import org.eclipse.jgit.lib.CoreConfig.CheckStat; 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 * Holds the {@link AttributesNode} that is stored in
* $GIT_DIR/info/attributes file. * $GIT_DIR/info/attributes file.
*/ */
private AttributesNode infoAttributeNode; private AttributesNode infoAttributesNode;
/** /**
* Holds the {@link AttributesNode} that is stored in global attribute file. * Holds the {@link AttributesNode} that is stored in global attribute file.
* *
* @see CoreConfig#getAttributesFile() * @see CoreConfig#getAttributesFile()
*/ */
private AttributesNode globalAttributeNode; private AttributesNode globalAttributesNode;
/** /**
* Create a new iterator with no parent. * Create a new iterator with no parent.
@ -202,8 +204,8 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
protected WorkingTreeIterator(final WorkingTreeIterator p) { protected WorkingTreeIterator(final WorkingTreeIterator p) {
super(p); super(p);
state = p.state; state = p.state;
infoAttributeNode = p.infoAttributeNode; infoAttributesNode = p.infoAttributesNode;
globalAttributeNode = p.globalAttributeNode; globalAttributesNode = p.globalAttributesNode;
} }
/** /**
@ -224,9 +226,9 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
entry = null; entry = null;
ignoreNode = new RootIgnoreNode(entry, repo); 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 * @since 3.7
*/ */
public AttributesNode getInfoAttributesNode() throws IOException { public AttributesNode getInfoAttributesNode() throws IOException {
if (infoAttributeNode instanceof InfoAttributesNode) if (infoAttributesNode instanceof InfoAttributesNode)
infoAttributeNode = ((InfoAttributesNode) infoAttributeNode).load(); infoAttributesNode = ((InfoAttributesNode) infoAttributesNode).load();
return infoAttributeNode; return infoAttributesNode;
} }
/** /**
@ -696,10 +698,10 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
* @since 3.7 * @since 3.7
*/ */
public AttributesNode getGlobalAttributesNode() throws IOException { public AttributesNode getGlobalAttributesNode() throws IOException {
if (globalAttributeNode instanceof GlobalAttributesNode) if (globalAttributesNode instanceof GlobalAttributesNode)
globalAttributeNode = ((GlobalAttributesNode) globalAttributeNode) globalAttributesNode = ((GlobalAttributesNode) globalAttributesNode)
.load(); .load();
return globalAttributeNode; return globalAttributesNode;
} }
private static final Comparator<Entry> ENTRY_CMP = new Comparator<Entry>() { 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 { private static final class IteratorState {
/** Options used to process the working tree. */ /** Options used to process the working tree. */

Loading…
Cancel
Save