You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CGitAttributesTest.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. /*
  2. * Copyright (C) 2017 Thomas Wolf <thomas.wolf@paranor.ch> and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.attributes;
  11. import static java.nio.charset.StandardCharsets.UTF_8;
  12. import static org.junit.Assert.assertArrayEquals;
  13. import static org.junit.Assert.assertEquals;
  14. import java.io.BufferedInputStream;
  15. import java.io.BufferedReader;
  16. import java.io.ByteArrayInputStream;
  17. import java.io.File;
  18. import java.io.IOException;
  19. import java.io.InputStreamReader;
  20. import java.util.Iterator;
  21. import java.util.LinkedHashMap;
  22. import java.util.Map;
  23. import java.util.Set;
  24. import org.eclipse.jgit.junit.RepositoryTestCase;
  25. import org.eclipse.jgit.lib.StoredConfig;
  26. import org.eclipse.jgit.treewalk.FileTreeIterator;
  27. import org.eclipse.jgit.treewalk.TreeWalk;
  28. import org.eclipse.jgit.treewalk.filter.NotIgnoredFilter;
  29. import org.eclipse.jgit.util.FS;
  30. import org.eclipse.jgit.util.FS.ExecutionResult;
  31. import org.eclipse.jgit.util.RawParseUtils;
  32. import org.eclipse.jgit.util.TemporaryBuffer;
  33. import org.junit.Before;
  34. import org.junit.Test;
  35. /**
  36. * Tests that verify that the attributes of files in a repository are the same
  37. * in JGit and in C-git.
  38. */
  39. public class CGitAttributesTest extends RepositoryTestCase {
  40. @Before
  41. public void initRepo() throws IOException {
  42. // Because we run C-git, we must ensure that global or user exclude
  43. // files cannot influence the tests. So we set core.excludesFile to an
  44. // empty file inside the repository.
  45. StoredConfig config = db.getConfig();
  46. File fakeUserGitignore = writeTrashFile(".fake_user_gitignore", "");
  47. config.setString("core", null, "excludesFile",
  48. fakeUserGitignore.getAbsolutePath());
  49. // Disable case-insensitivity -- JGit doesn't handle that yet.
  50. config.setBoolean("core", null, "ignoreCase", false);
  51. // And try to switch off the global attributes file, too.
  52. config.setString("core", null, "attributesFile",
  53. fakeUserGitignore.getAbsolutePath());
  54. config.save();
  55. }
  56. private void createFiles(String... paths) throws IOException {
  57. for (String path : paths) {
  58. writeTrashFile(path, "x");
  59. }
  60. }
  61. private String toString(TemporaryBuffer b) throws IOException {
  62. return RawParseUtils.decode(b.toByteArray());
  63. }
  64. private Attribute fromString(String key, String value) {
  65. if ("set".equals(value)) {
  66. return new Attribute(key, Attribute.State.SET);
  67. }
  68. if ("unset".equals(value)) {
  69. return new Attribute(key, Attribute.State.UNSET);
  70. }
  71. if ("unspecified".equals(value)) {
  72. return new Attribute(key, Attribute.State.UNSPECIFIED);
  73. }
  74. return new Attribute(key, value);
  75. }
  76. private LinkedHashMap<String, Attributes> cgitAttributes(
  77. Set<String> allFiles) throws Exception {
  78. FS fs = db.getFS();
  79. StringBuilder input = new StringBuilder();
  80. for (String filename : allFiles) {
  81. input.append(filename).append('\n');
  82. }
  83. ProcessBuilder builder = fs.runInShell("git",
  84. new String[] { "check-attr", "--stdin", "--all" });
  85. builder.directory(db.getWorkTree());
  86. builder.environment().put("HOME", fs.userHome().getAbsolutePath());
  87. ExecutionResult result = fs.execute(builder, new ByteArrayInputStream(
  88. input.toString().getBytes(UTF_8)));
  89. String errorOut = toString(result.getStderr());
  90. assertEquals("External git failed", "exit 0\n",
  91. "exit " + result.getRc() + '\n' + errorOut);
  92. LinkedHashMap<String, Attributes> map = new LinkedHashMap<>();
  93. try (BufferedReader r = new BufferedReader(new InputStreamReader(
  94. new BufferedInputStream(result.getStdout().openInputStream()),
  95. UTF_8))) {
  96. r.lines().forEach(line -> {
  97. // Parse the line and add to result map
  98. int start = 0;
  99. int i = line.indexOf(':');
  100. String path = line.substring(0, i).trim();
  101. start = i + 1;
  102. i = line.indexOf(':', start);
  103. String key = line.substring(start, i).trim();
  104. String value = line.substring(i + 1).trim();
  105. Attribute attr = fromString(key, value);
  106. Attributes attrs = map.get(path);
  107. if (attrs == null) {
  108. attrs = new Attributes(attr);
  109. map.put(path, attrs);
  110. } else {
  111. attrs.put(attr);
  112. }
  113. });
  114. }
  115. return map;
  116. }
  117. private LinkedHashMap<String, Attributes> jgitAttributes()
  118. throws IOException {
  119. // Do a tree walk and return a list of all files and directories with
  120. // their attributes
  121. LinkedHashMap<String, Attributes> result = new LinkedHashMap<>();
  122. try (TreeWalk walk = new TreeWalk(db)) {
  123. walk.addTree(new FileTreeIterator(db));
  124. walk.setFilter(new NotIgnoredFilter(0));
  125. while (walk.next()) {
  126. String path = walk.getPathString();
  127. if (walk.isSubtree() && !path.endsWith("/")) {
  128. // git check-attr expects directory paths to end with a
  129. // slash
  130. path += '/';
  131. }
  132. Attributes attrs = walk.getAttributes();
  133. if (attrs != null && !attrs.isEmpty()) {
  134. result.put(path, attrs);
  135. } else {
  136. result.put(path, null);
  137. }
  138. if (walk.isSubtree()) {
  139. walk.enterSubtree();
  140. }
  141. }
  142. }
  143. return result;
  144. }
  145. private void assertSameAsCGit() throws Exception {
  146. LinkedHashMap<String, Attributes> jgit = jgitAttributes();
  147. LinkedHashMap<String, Attributes> cgit = cgitAttributes(jgit.keySet());
  148. // remove all without attributes
  149. Iterator<Map.Entry<String, Attributes>> iterator = jgit.entrySet()
  150. .iterator();
  151. while (iterator.hasNext()) {
  152. Map.Entry<String, Attributes> entry = iterator.next();
  153. if (entry.getValue() == null) {
  154. iterator.remove();
  155. }
  156. }
  157. assertArrayEquals("JGit attributes differ from C git",
  158. cgit.entrySet().toArray(), jgit.entrySet().toArray());
  159. }
  160. @Test
  161. public void testBug508568() throws Exception {
  162. createFiles("foo.xml/bar.jar", "sub/foo.xml/bar.jar");
  163. writeTrashFile(".gitattributes", "*.xml xml\n" + "*.jar jar\n");
  164. assertSameAsCGit();
  165. }
  166. @Test
  167. public void testRelativePath() throws Exception {
  168. createFiles("sub/foo.txt");
  169. writeTrashFile("sub/.gitattributes", "sub/** sub\n" + "*.txt txt\n");
  170. assertSameAsCGit();
  171. }
  172. @Test
  173. public void testRelativePaths() throws Exception {
  174. createFiles("sub/foo.txt", "sub/sub/bar", "foo/sub/a.txt",
  175. "foo/sub/bar/a.tmp");
  176. writeTrashFile(".gitattributes", "sub/** sub\n" + "*.txt txt\n");
  177. assertSameAsCGit();
  178. }
  179. @Test
  180. public void testNestedMatchNot() throws Exception {
  181. createFiles("foo.xml/bar.jar", "foo.xml/bar.xml", "sub/b.jar",
  182. "sub/b.xml");
  183. writeTrashFile("sub/.gitattributes", "*.xml xml\n" + "*.jar jar\n");
  184. assertSameAsCGit();
  185. }
  186. @Test
  187. public void testNestedMatch() throws Exception {
  188. // This is an interesting test. At the time of this writing, the
  189. // gitignore documentation says: "In other words, foo/ will match a
  190. // directory foo AND PATHS UNDERNEATH IT, but will not match a regular
  191. // file or a symbolic link foo". (Emphasis added.) And gitattributes is
  192. // supposed to follow the same rules. But the documentation appears to
  193. // lie: C-git will *not* apply the attribute "xml" to *any* files in
  194. // any subfolder "foo" here. It will only apply the "jar" attribute
  195. // to the three *.jar files.
  196. //
  197. // The point is probably that ignores are handled top-down, and once a
  198. // directory "foo" is matched (here: on paths "foo" and "sub/foo" by
  199. // pattern "foo/"), the directory is excluded and the gitignore
  200. // documentation also says: "It is not possible to re-include a file if
  201. // a parent directory of that file is excluded." So once the pattern
  202. // "foo/" has matched, it appears as if everything beneath would also be
  203. // matched.
  204. //
  205. // But not so for gitattributes! The foo/ rule only matches the
  206. // directory itself, but not anything beneath.
  207. createFiles("foo/bar.jar", "foo/bar.xml", "sub/b.jar", "sub/b.xml",
  208. "sub/foo/b.jar");
  209. writeTrashFile(".gitattributes",
  210. "foo/ xml\n" + "sub/foo/ sub\n" + "*.jar jar\n");
  211. assertSameAsCGit();
  212. }
  213. @Test
  214. public void testNestedMatchWithWildcard() throws Exception {
  215. // See above.
  216. createFiles("foo/bar.jar", "foo/bar.xml", "sub/b.jar", "sub/b.xml",
  217. "sub/foo/b.jar");
  218. writeTrashFile(".gitattributes",
  219. "**/foo/ xml\n" + "*/foo/ sub\n" + "*.jar jar\n");
  220. assertSameAsCGit();
  221. }
  222. @Test
  223. public void testNestedMatchRecursive() throws Exception {
  224. createFiles("foo/bar.jar", "foo/bar.xml", "sub/b.jar", "sub/b.xml",
  225. "sub/foo/b.jar");
  226. writeTrashFile(".gitattributes", "foo/** xml\n" + "*.jar jar\n");
  227. assertSameAsCGit();
  228. }
  229. @Test
  230. public void testStarMatchOnSlashNot() throws Exception {
  231. createFiles("sub/a.txt", "foo/sext", "foo/s.txt");
  232. writeTrashFile(".gitattributes", "s*xt bar");
  233. assertSameAsCGit();
  234. }
  235. @Test
  236. public void testPrefixMatchNot() throws Exception {
  237. createFiles("src/new/foo.txt");
  238. writeTrashFile(".gitattributes", "src/new bar\n");
  239. assertSameAsCGit();
  240. }
  241. @Test
  242. public void testComplexPathMatchNot() throws Exception {
  243. createFiles("src/new/foo.txt", "src/ndw");
  244. writeTrashFile(".gitattributes", "s[p-s]c/n[de]w bar\n");
  245. assertSameAsCGit();
  246. }
  247. @Test
  248. public void testStarPathMatchNot() throws Exception {
  249. createFiles("src/new/foo.txt", "src/ndw");
  250. writeTrashFile(".gitattributes", "src/* bar\n");
  251. assertSameAsCGit();
  252. }
  253. @Test
  254. public void testDirectoryMatchSubSimple() throws Exception {
  255. createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");
  256. writeTrashFile(".gitattributes", "src/new/ bar\n");
  257. assertSameAsCGit();
  258. }
  259. @Test
  260. public void testDirectoryMatchSubRecursive() throws Exception {
  261. createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");
  262. writeTrashFile(".gitattributes", "**/src/new/ bar\n");
  263. assertSameAsCGit();
  264. }
  265. @Test
  266. public void testDirectoryMatchSubRecursiveBacktrack() throws Exception {
  267. createFiles("src/new/foo.txt", "src/src/new/foo.txt");
  268. writeTrashFile(".gitattributes", "**/src/new/ bar\n");
  269. assertSameAsCGit();
  270. }
  271. @Test
  272. public void testDirectoryMatchSubRecursiveBacktrack2() throws Exception {
  273. createFiles("src/new/foo.txt", "src/src/new/foo.txt");
  274. writeTrashFile(".gitattributes", "**/**/src/new/ bar\n");
  275. assertSameAsCGit();
  276. }
  277. @Test
  278. public void testDirectoryMatchSubRecursiveBacktrack3() throws Exception {
  279. createFiles("src/new/src/new/foo.txt",
  280. "foo/src/new/bar/src/new/foo.txt");
  281. writeTrashFile(".gitattributes", "**/src/new/ bar\n");
  282. assertSameAsCGit();
  283. }
  284. @Test
  285. public void testDirectoryMatchSubRecursiveBacktrack4() throws Exception {
  286. createFiles("src/src/src/new/foo.txt",
  287. "foo/src/src/bar/src/new/foo.txt");
  288. writeTrashFile(".gitattributes", "**/src/ bar\n");
  289. assertSameAsCGit();
  290. }
  291. @Test
  292. public void testDirectoryMatchSubRecursiveBacktrack5() throws Exception {
  293. createFiles("x/a/a/b/foo.txt", "x/y/z/b/a/b/foo.txt",
  294. "x/y/a/a/a/a/b/foo.txt", "x/y/a/a/a/a/b/a/b/foo.txt");
  295. writeTrashFile(".gitattributes", "**/*/a/b bar\n");
  296. assertSameAsCGit();
  297. }
  298. @Test
  299. public void testDirectoryMatchSubRecursiveBacktrack6() throws Exception {
  300. createFiles("x/a/a/b/foo.txt", "x/y/a/b/a/b/foo.txt");
  301. writeTrashFile(".gitattributes", "**/*/**/a/b bar\n");
  302. assertSameAsCGit();
  303. }
  304. @Test
  305. public void testDirectoryWildmatchDoesNotMatchFiles1() throws Exception {
  306. createFiles("a", "dir/b", "dir/sub/c");
  307. writeTrashFile(".gitattributes", "**/ bar\n");
  308. assertSameAsCGit();
  309. }
  310. @Test
  311. public void testDirectoryWildmatchDoesNotMatchFiles2() throws Exception {
  312. createFiles("a", "dir/b", "dir/sub/c");
  313. writeTrashFile(".gitattributes", "**/**/ bar\n");
  314. assertSameAsCGit();
  315. }
  316. @Test
  317. public void testDirectoryWildmatchDoesNotMatchFiles3() throws Exception {
  318. createFiles("a", "x/b", "sub/x/c", "sub/x/d/e");
  319. writeTrashFile(".gitattributes", "x/**/ bar\n");
  320. assertSameAsCGit();
  321. }
  322. @Test
  323. public void testDirectoryWildmatchDoesNotMatchFiles4() throws Exception {
  324. createFiles("a", "dir/x", "dir/sub1/x", "dir/sub2/x/y");
  325. writeTrashFile(".gitattributes", "x/**/ bar\n");
  326. assertSameAsCGit();
  327. }
  328. @Test
  329. public void testDirectoryMatchSubComplex() throws Exception {
  330. createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");
  331. writeTrashFile(".gitattributes", "s[rs]c/n*/ bar\n");
  332. assertSameAsCGit();
  333. }
  334. @Test
  335. public void testDirectoryMatch() throws Exception {
  336. createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");
  337. writeTrashFile(".gitattributes", "new/ bar\n");
  338. assertSameAsCGit();
  339. }
  340. @Test
  341. public void testBracketsInGroup() throws Exception {
  342. createFiles("[", "]", "[]", "][", "[[]", "[]]", "[[]]");
  343. writeTrashFile(".gitattributes", "[[]] bar1\n" + "[\\[]] bar2\n"
  344. + "[[\\]] bar3\n" + "[\\[\\]] bar4\n");
  345. assertSameAsCGit();
  346. }
  347. }