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.

TreeWalkAttributeTest.java 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839
  1. /*
  2. * Copyright (C) 2014, Obeo. 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 org.junit.Assert.assertEquals;
  12. import static org.junit.Assert.assertFalse;
  13. import static org.junit.Assert.assertTrue;
  14. import java.io.File;
  15. import java.io.IOException;
  16. import java.util.ArrayList;
  17. import java.util.Arrays;
  18. import java.util.Collection;
  19. import java.util.Collections;
  20. import java.util.HashSet;
  21. import java.util.List;
  22. import java.util.Set;
  23. import org.eclipse.jgit.api.Git;
  24. import org.eclipse.jgit.api.errors.GitAPIException;
  25. import org.eclipse.jgit.api.errors.NoFilepatternException;
  26. import org.eclipse.jgit.attributes.Attribute.State;
  27. import org.eclipse.jgit.dircache.DirCacheIterator;
  28. import org.eclipse.jgit.errors.NoWorkTreeException;
  29. import org.eclipse.jgit.junit.JGitTestUtil;
  30. import org.eclipse.jgit.junit.RepositoryTestCase;
  31. import org.eclipse.jgit.lib.FileMode;
  32. import org.eclipse.jgit.lib.Repository;
  33. import org.eclipse.jgit.treewalk.FileTreeIterator;
  34. import org.eclipse.jgit.treewalk.TreeWalk;
  35. import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
  36. import org.junit.After;
  37. import org.junit.Before;
  38. import org.junit.Test;
  39. /**
  40. * Tests the attributes are correctly computed in a {@link TreeWalk}.
  41. *
  42. * @see TreeWalk#getAttributes()
  43. */
  44. public class TreeWalkAttributeTest extends RepositoryTestCase {
  45. private static final FileMode M = FileMode.MISSING;
  46. private static final FileMode D = FileMode.TREE;
  47. private static final FileMode F = FileMode.REGULAR_FILE;
  48. private static Attribute EOL_CRLF = new Attribute("eol", "crlf");
  49. private static Attribute EOL_LF = new Attribute("eol", "lf");
  50. private static Attribute TEXT_SET = new Attribute("text", State.SET);
  51. private static Attribute TEXT_UNSET = new Attribute("text", State.UNSET);
  52. private static Attribute DELTA_UNSET = new Attribute("delta", State.UNSET);
  53. private static Attribute DELTA_SET = new Attribute("delta", State.SET);
  54. private static Attribute CUSTOM_GLOBAL = new Attribute("custom", "global");
  55. private static Attribute CUSTOM_INFO = new Attribute("custom", "info");
  56. private static Attribute CUSTOM_ROOT = new Attribute("custom", "root");
  57. private static Attribute CUSTOM_PARENT = new Attribute("custom", "parent");
  58. private static Attribute CUSTOM_CURRENT = new Attribute("custom", "current");
  59. private static Attribute CUSTOM2_UNSET = new Attribute("custom2",
  60. State.UNSET);
  61. private static Attribute CUSTOM2_SET = new Attribute("custom2", State.SET);
  62. private TreeWalk walk;
  63. private TreeWalk ci_walk;
  64. private Git git;
  65. private File customAttributeFile;
  66. @Override
  67. @Before
  68. public void setUp() throws Exception {
  69. super.setUp();
  70. git = new Git(db);
  71. }
  72. @Override
  73. @After
  74. public void tearDown() throws Exception {
  75. if (walk != null) {
  76. walk.close();
  77. }
  78. if (ci_walk != null) {
  79. ci_walk.close();
  80. }
  81. super.tearDown();
  82. if (customAttributeFile != null)
  83. customAttributeFile.delete();
  84. }
  85. /**
  86. * Checks that the attributes are computed correctly depending on the
  87. * operation type.
  88. * <p>
  89. * In this test we changed the content of the attribute files in the working
  90. * tree compared to the one in the index.
  91. * </p>
  92. *
  93. * @throws IOException
  94. * @throws NoFilepatternException
  95. * @throws GitAPIException
  96. */
  97. @Test
  98. public void testCheckinCheckoutDifferences() throws IOException,
  99. NoFilepatternException, GitAPIException {
  100. writeGlobalAttributeFile("globalAttributesFile", "*.txt -custom2");
  101. writeAttributesFile(".git/info/attributes", "*.txt eol=crlf");
  102. writeAttributesFile(".gitattributes", "*.txt custom=root");
  103. writeAttributesFile("level1/.gitattributes", "*.txt text");
  104. writeAttributesFile("level1/level2/.gitattributes", "*.txt -delta");
  105. writeTrashFile("l0.txt", "");
  106. writeTrashFile("level1/l1.txt", "");
  107. writeTrashFile("level1/level2/l2.txt", "");
  108. git.add().addFilepattern(".").call();
  109. beginWalk();
  110. // Modify all attributes
  111. writeGlobalAttributeFile("globalAttributesFile", "*.txt custom2");
  112. writeAttributesFile(".git/info/attributes", "*.txt eol=lf");
  113. writeAttributesFile(".gitattributes", "*.txt custom=info");
  114. writeAttributesFile("level1/.gitattributes", "*.txt -text");
  115. writeAttributesFile("level1/level2/.gitattributes", "*.txt delta");
  116. assertEntry(F, ".gitattributes");
  117. assertEntry(F, "l0.txt", asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET),
  118. asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET));
  119. assertEntry(D, "level1");
  120. assertEntry(F, "level1/.gitattributes");
  121. assertEntry(F, "level1/l1.txt",
  122. asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET, TEXT_UNSET),
  123. asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET, TEXT_SET));
  124. assertEntry(D, "level1/level2");
  125. assertEntry(F, "level1/level2/.gitattributes");
  126. assertEntry(F, "level1/level2/l2.txt",
  127. asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET, TEXT_UNSET, DELTA_SET),
  128. asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET, TEXT_SET, DELTA_UNSET));
  129. endWalk();
  130. }
  131. /**
  132. * Checks that the index is used as fallback when the git attributes file
  133. * are missing in the working tree.
  134. *
  135. * @throws IOException
  136. * @throws NoFilepatternException
  137. * @throws GitAPIException
  138. */
  139. @Test
  140. public void testIndexOnly() throws IOException, NoFilepatternException,
  141. GitAPIException {
  142. List<File> attrFiles = new ArrayList<>();
  143. attrFiles.add(writeGlobalAttributeFile("globalAttributesFile",
  144. "*.txt -custom2"));
  145. attrFiles.add(writeAttributesFile(".git/info/attributes",
  146. "*.txt eol=crlf"));
  147. attrFiles
  148. .add(writeAttributesFile(".gitattributes", "*.txt custom=root"));
  149. attrFiles
  150. .add(writeAttributesFile("level1/.gitattributes", "*.txt text"));
  151. attrFiles.add(writeAttributesFile("level1/level2/.gitattributes",
  152. "*.txt -delta"));
  153. writeTrashFile("l0.txt", "");
  154. writeTrashFile("level1/l1.txt", "");
  155. writeTrashFile("level1/level2/l2.txt", "");
  156. git.add().addFilepattern(".").call();
  157. // Modify all attributes
  158. for (File attrFile : attrFiles)
  159. attrFile.delete();
  160. beginWalk();
  161. assertEntry(M, ".gitattributes");
  162. assertEntry(F, "l0.txt", asSet(CUSTOM_ROOT));
  163. assertEntry(D, "level1");
  164. assertEntry(M, "level1/.gitattributes");
  165. assertEntry(F, "level1/l1.txt",
  166. asSet(CUSTOM_ROOT, TEXT_SET));
  167. assertEntry(D, "level1/level2");
  168. assertEntry(M, "level1/level2/.gitattributes");
  169. assertEntry(F, "level1/level2/l2.txt",
  170. asSet(CUSTOM_ROOT, TEXT_SET, DELTA_UNSET));
  171. endWalk();
  172. }
  173. /**
  174. * Check that we search in the working tree for attributes although the file
  175. * we are currently inspecting does not exist anymore in the working tree.
  176. *
  177. * @throws IOException
  178. * @throws NoFilepatternException
  179. * @throws GitAPIException
  180. */
  181. @Test
  182. public void testIndexOnly2()
  183. throws IOException, NoFilepatternException, GitAPIException {
  184. File l2 = writeTrashFile("level1/level2/l2.txt", "");
  185. writeTrashFile("level1/level2/l1.txt", "");
  186. git.add().addFilepattern(".").call();
  187. writeAttributesFile(".gitattributes", "*.txt custom=root");
  188. assertTrue(l2.delete());
  189. beginWalk();
  190. assertEntry(F, ".gitattributes");
  191. assertEntry(D, "level1");
  192. assertEntry(D, "level1/level2");
  193. assertEntry(F, "level1/level2/l1.txt", asSet(CUSTOM_ROOT));
  194. assertEntry(M, "level1/level2/l2.txt", asSet(CUSTOM_ROOT));
  195. endWalk();
  196. }
  197. /**
  198. * Basic test for git attributes.
  199. * <p>
  200. * In this use case files are present in both the working tree and the index
  201. * </p>
  202. *
  203. * @throws IOException
  204. * @throws NoFilepatternException
  205. * @throws GitAPIException
  206. */
  207. @Test
  208. public void testRules() throws IOException, NoFilepatternException,
  209. GitAPIException {
  210. writeAttributesFile(".git/info/attributes", "windows* eol=crlf");
  211. writeAttributesFile(".gitattributes", "*.txt eol=lf");
  212. writeTrashFile("windows.file", "");
  213. writeTrashFile("windows.txt", "");
  214. writeTrashFile("readme.txt", "");
  215. writeAttributesFile("src/config/.gitattributes", "*.txt -delta");
  216. writeTrashFile("src/config/readme.txt", "");
  217. writeTrashFile("src/config/windows.file", "");
  218. writeTrashFile("src/config/windows.txt", "");
  219. beginWalk();
  220. git.add().addFilepattern(".").call();
  221. assertEntry(F, ".gitattributes");
  222. assertEntry(F, "readme.txt", asSet(EOL_LF));
  223. assertEntry(D, "src");
  224. assertEntry(D, "src/config");
  225. assertEntry(F, "src/config/.gitattributes");
  226. assertEntry(F, "src/config/readme.txt", asSet(DELTA_UNSET, EOL_LF));
  227. assertEntry(F, "src/config/windows.file", asSet(EOL_CRLF));
  228. assertEntry(F, "src/config/windows.txt", asSet(DELTA_UNSET, EOL_CRLF));
  229. assertEntry(F, "windows.file", asSet(EOL_CRLF));
  230. assertEntry(F, "windows.txt", asSet(EOL_CRLF));
  231. endWalk();
  232. }
  233. /**
  234. * Checks that if there is no .gitattributes file in the repository
  235. * everything still work fine.
  236. *
  237. * @throws IOException
  238. */
  239. @Test
  240. public void testNoAttributes() throws IOException {
  241. writeTrashFile("l0.txt", "");
  242. writeTrashFile("level1/l1.txt", "");
  243. writeTrashFile("level1/level2/l2.txt", "");
  244. beginWalk();
  245. assertEntry(F, "l0.txt");
  246. assertEntry(D, "level1");
  247. assertEntry(F, "level1/l1.txt");
  248. assertEntry(D, "level1/level2");
  249. assertEntry(F, "level1/level2/l2.txt");
  250. endWalk();
  251. }
  252. /**
  253. * Checks that an empty .gitattribute file does not return incorrect value.
  254. *
  255. * @throws IOException
  256. */
  257. @Test
  258. public void testEmptyGitAttributeFile() throws IOException {
  259. writeAttributesFile(".git/info/attributes", "");
  260. writeTrashFile("l0.txt", "");
  261. writeAttributesFile(".gitattributes", "");
  262. writeTrashFile("level1/l1.txt", "");
  263. writeTrashFile("level1/level2/l2.txt", "");
  264. beginWalk();
  265. assertEntry(F, ".gitattributes");
  266. assertEntry(F, "l0.txt");
  267. assertEntry(D, "level1");
  268. assertEntry(F, "level1/l1.txt");
  269. assertEntry(D, "level1/level2");
  270. assertEntry(F, "level1/level2/l2.txt");
  271. endWalk();
  272. }
  273. @Test
  274. public void testNoMatchingAttributes() throws IOException {
  275. writeAttributesFile(".git/info/attributes", "*.java delta");
  276. writeAttributesFile(".gitattributes", "*.java -delta");
  277. writeAttributesFile("levelA/.gitattributes", "*.java eol=lf");
  278. writeAttributesFile("levelB/.gitattributes", "*.txt eol=lf");
  279. writeTrashFile("levelA/lA.txt", "");
  280. beginWalk();
  281. assertEntry(F, ".gitattributes");
  282. assertEntry(D, "levelA");
  283. assertEntry(F, "levelA/.gitattributes");
  284. assertEntry(F, "levelA/lA.txt");
  285. assertEntry(D, "levelB");
  286. assertEntry(F, "levelB/.gitattributes");
  287. endWalk();
  288. }
  289. /**
  290. * Checks that $GIT_DIR/info/attributes file has the highest precedence.
  291. *
  292. * @throws IOException
  293. */
  294. @Test
  295. public void testPrecedenceInfo() throws IOException {
  296. writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
  297. writeAttributesFile(".git/info/attributes", "*.txt custom=info");
  298. writeAttributesFile(".gitattributes", "*.txt custom=root");
  299. writeAttributesFile("level1/.gitattributes", "*.txt custom=parent");
  300. writeAttributesFile("level1/level2/.gitattributes",
  301. "*.txt custom=current");
  302. writeTrashFile("level1/level2/file.txt", "");
  303. beginWalk();
  304. assertEntry(F, ".gitattributes");
  305. assertEntry(D, "level1");
  306. assertEntry(F, "level1/.gitattributes");
  307. assertEntry(D, "level1/level2");
  308. assertEntry(F, "level1/level2/.gitattributes");
  309. assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_INFO));
  310. endWalk();
  311. }
  312. /**
  313. * Checks that a subfolder ".gitattributes" file has precedence over its
  314. * parent.
  315. *
  316. * @throws IOException
  317. */
  318. @Test
  319. public void testPrecedenceCurrent() throws IOException {
  320. writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
  321. writeAttributesFile(".gitattributes", "*.txt custom=root");
  322. writeAttributesFile("level1/.gitattributes", "*.txt custom=parent");
  323. writeAttributesFile("level1/level2/.gitattributes",
  324. "*.txt custom=current");
  325. writeTrashFile("level1/level2/file.txt", "");
  326. beginWalk();
  327. assertEntry(F, ".gitattributes");
  328. assertEntry(D, "level1");
  329. assertEntry(F, "level1/.gitattributes");
  330. assertEntry(D, "level1/level2");
  331. assertEntry(F, "level1/level2/.gitattributes");
  332. assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_CURRENT));
  333. endWalk();
  334. }
  335. /**
  336. * Checks that the parent ".gitattributes" file is used as fallback.
  337. *
  338. * @throws IOException
  339. */
  340. @Test
  341. public void testPrecedenceParent() throws IOException {
  342. writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
  343. writeAttributesFile(".gitattributes", "*.txt custom=root");
  344. writeAttributesFile("level1/.gitattributes", "*.txt custom=parent");
  345. writeTrashFile("level1/level2/file.txt", "");
  346. beginWalk();
  347. assertEntry(F, ".gitattributes");
  348. assertEntry(D, "level1");
  349. assertEntry(F, "level1/.gitattributes");
  350. assertEntry(D, "level1/level2");
  351. assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_PARENT));
  352. endWalk();
  353. }
  354. /**
  355. * Checks that the grand parent ".gitattributes" file is used as fallback.
  356. *
  357. * @throws IOException
  358. */
  359. @Test
  360. public void testPrecedenceRoot() throws IOException {
  361. writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
  362. writeAttributesFile(".gitattributes", "*.txt custom=root");
  363. writeTrashFile("level1/level2/file.txt", "");
  364. beginWalk();
  365. assertEntry(F, ".gitattributes");
  366. assertEntry(D, "level1");
  367. assertEntry(D, "level1/level2");
  368. assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_ROOT));
  369. endWalk();
  370. }
  371. /**
  372. * Checks that the global attribute file is used as fallback.
  373. *
  374. * @throws IOException
  375. */
  376. @Test
  377. public void testPrecedenceGlobal() throws IOException {
  378. writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
  379. writeTrashFile("level1/level2/file.txt", "");
  380. beginWalk();
  381. assertEntry(D, "level1");
  382. assertEntry(D, "level1/level2");
  383. assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_GLOBAL));
  384. endWalk();
  385. }
  386. /**
  387. * Checks the precedence on a hierarchy with multiple attributes.
  388. * <p>
  389. * In this test all file are present in both the working tree and the index.
  390. * </p>
  391. *
  392. * @throws IOException
  393. * @throws GitAPIException
  394. * @throws NoFilepatternException
  395. */
  396. @Test
  397. public void testHierarchyBothIterator() throws IOException,
  398. NoFilepatternException, GitAPIException {
  399. writeAttributesFile(".git/info/attributes", "*.global eol=crlf");
  400. writeAttributesFile(".gitattributes", "*.local eol=lf");
  401. writeAttributesFile("level1/.gitattributes", "*.local text");
  402. writeAttributesFile("level1/level2/.gitattributes", "*.local -text");
  403. writeTrashFile("l0.global", "");
  404. writeTrashFile("l0.local", "");
  405. writeTrashFile("level1/l1.global", "");
  406. writeTrashFile("level1/l1.local", "");
  407. writeTrashFile("level1/level2/l2.global", "");
  408. writeTrashFile("level1/level2/l2.local", "");
  409. beginWalk();
  410. git.add().addFilepattern(".").call();
  411. assertEntry(F, ".gitattributes");
  412. assertEntry(F, "l0.global", asSet(EOL_CRLF));
  413. assertEntry(F, "l0.local", asSet(EOL_LF));
  414. assertEntry(D, "level1");
  415. assertEntry(F, "level1/.gitattributes");
  416. assertEntry(F, "level1/l1.global", asSet(EOL_CRLF));
  417. assertEntry(F, "level1/l1.local", asSet(EOL_LF, TEXT_SET));
  418. assertEntry(D, "level1/level2");
  419. assertEntry(F, "level1/level2/.gitattributes");
  420. assertEntry(F, "level1/level2/l2.global", asSet(EOL_CRLF));
  421. assertEntry(F, "level1/level2/l2.local", asSet(EOL_LF, TEXT_UNSET));
  422. endWalk();
  423. }
  424. /**
  425. * Checks the precedence on a hierarchy with multiple attributes.
  426. * <p>
  427. * In this test all file are present only in the working tree.
  428. * </p>
  429. *
  430. * @throws IOException
  431. * @throws GitAPIException
  432. * @throws NoFilepatternException
  433. */
  434. @Test
  435. public void testHierarchyWorktreeOnly()
  436. throws IOException, NoFilepatternException, GitAPIException {
  437. writeAttributesFile(".git/info/attributes", "*.global eol=crlf");
  438. writeAttributesFile(".gitattributes", "*.local eol=lf");
  439. writeAttributesFile("level1/.gitattributes", "*.local text");
  440. writeAttributesFile("level1/level2/.gitattributes", "*.local -text");
  441. writeTrashFile("l0.global", "");
  442. writeTrashFile("l0.local", "");
  443. writeTrashFile("level1/l1.global", "");
  444. writeTrashFile("level1/l1.local", "");
  445. writeTrashFile("level1/level2/l2.global", "");
  446. writeTrashFile("level1/level2/l2.local", "");
  447. beginWalk();
  448. assertEntry(F, ".gitattributes");
  449. assertEntry(F, "l0.global", asSet(EOL_CRLF));
  450. assertEntry(F, "l0.local", asSet(EOL_LF));
  451. assertEntry(D, "level1");
  452. assertEntry(F, "level1/.gitattributes");
  453. assertEntry(F, "level1/l1.global", asSet(EOL_CRLF));
  454. assertEntry(F, "level1/l1.local", asSet(EOL_LF, TEXT_SET));
  455. assertEntry(D, "level1/level2");
  456. assertEntry(F, "level1/level2/.gitattributes");
  457. assertEntry(F, "level1/level2/l2.global", asSet(EOL_CRLF));
  458. assertEntry(F, "level1/level2/l2.local", asSet(EOL_LF, TEXT_UNSET));
  459. endWalk();
  460. }
  461. /**
  462. * Checks that the list of attributes is an aggregation of all the
  463. * attributes from the attributes files hierarchy.
  464. *
  465. * @throws IOException
  466. */
  467. @Test
  468. public void testAggregation() throws IOException {
  469. writeGlobalAttributeFile("globalAttributesFile", "*.txt -custom2");
  470. writeAttributesFile(".git/info/attributes", "*.txt eol=crlf");
  471. writeAttributesFile(".gitattributes", "*.txt custom=root");
  472. writeAttributesFile("level1/.gitattributes", "*.txt text");
  473. writeAttributesFile("level1/level2/.gitattributes", "*.txt -delta");
  474. writeTrashFile("l0.txt", "");
  475. writeTrashFile("level1/l1.txt", "");
  476. writeTrashFile("level1/level2/l2.txt", "");
  477. beginWalk();
  478. assertEntry(F, ".gitattributes");
  479. assertEntry(F, "l0.txt", asSet(EOL_CRLF, CUSTOM_ROOT, CUSTOM2_UNSET));
  480. assertEntry(D, "level1");
  481. assertEntry(F, "level1/.gitattributes");
  482. assertEntry(F, "level1/l1.txt",
  483. asSet(EOL_CRLF, CUSTOM_ROOT, TEXT_SET, CUSTOM2_UNSET));
  484. assertEntry(D, "level1/level2");
  485. assertEntry(F, "level1/level2/.gitattributes");
  486. assertEntry(
  487. F,
  488. "level1/level2/l2.txt",
  489. asSet(EOL_CRLF, CUSTOM_ROOT, TEXT_SET, DELTA_UNSET,
  490. CUSTOM2_UNSET));
  491. endWalk();
  492. }
  493. /**
  494. * Checks that the last entry in .gitattributes is used if 2 lines match the
  495. * same attribute
  496. *
  497. * @throws IOException
  498. */
  499. @Test
  500. public void testOverriding() throws IOException {
  501. writeAttributesFile(".git/info/attributes",//
  502. //
  503. "*.txt custom=current",//
  504. "*.txt custom=parent",//
  505. "*.txt custom=root",//
  506. "*.txt custom=info",
  507. //
  508. "*.txt delta",//
  509. "*.txt -delta",
  510. //
  511. "*.txt eol=lf",//
  512. "*.txt eol=crlf",
  513. //
  514. "*.txt text",//
  515. "*.txt -text");
  516. writeTrashFile("l0.txt", "");
  517. beginWalk();
  518. assertEntry(F, "l0.txt",
  519. asSet(TEXT_UNSET, EOL_CRLF, DELTA_UNSET, CUSTOM_INFO));
  520. endWalk();
  521. }
  522. /**
  523. * Checks that the last value of an attribute is used if in the same line an
  524. * attribute is defined several time.
  525. *
  526. * @throws IOException
  527. */
  528. @Test
  529. public void testOverriding2() throws IOException {
  530. writeAttributesFile(".git/info/attributes",
  531. "*.txt custom=current custom=parent custom=root custom=info",//
  532. "*.txt delta -delta",//
  533. "*.txt eol=lf eol=crlf",//
  534. "*.txt text -text");
  535. writeTrashFile("l0.txt", "");
  536. beginWalk();
  537. assertEntry(F, "l0.txt",
  538. asSet(TEXT_UNSET, EOL_CRLF, DELTA_UNSET, CUSTOM_INFO));
  539. endWalk();
  540. }
  541. @Test
  542. public void testRulesInherited() throws Exception {
  543. writeAttributesFile(".gitattributes", "**/*.txt eol=lf");
  544. writeTrashFile("src/config/readme.txt", "");
  545. writeTrashFile("src/config/windows.file", "");
  546. beginWalk();
  547. assertEntry(F, ".gitattributes");
  548. assertEntry(D, "src");
  549. assertEntry(D, "src/config");
  550. assertEntry(F, "src/config/readme.txt", asSet(EOL_LF));
  551. assertEntry(F, "src/config/windows.file",
  552. Collections.<Attribute> emptySet());
  553. endWalk();
  554. }
  555. private void beginWalk() throws NoWorkTreeException, IOException {
  556. walk = new TreeWalk(db);
  557. walk.addTree(new FileTreeIterator(db));
  558. walk.addTree(new DirCacheIterator(db.readDirCache()));
  559. ci_walk = new TreeWalk(db);
  560. ci_walk.setOperationType(OperationType.CHECKIN_OP);
  561. ci_walk.addTree(new FileTreeIterator(db));
  562. ci_walk.addTree(new DirCacheIterator(db.readDirCache()));
  563. }
  564. /**
  565. * Assert an entry in which checkin and checkout attributes are expected to
  566. * be the same.
  567. *
  568. * @param type
  569. * @param pathName
  570. * @param forBothOperaiton
  571. * @throws IOException
  572. */
  573. private void assertEntry(FileMode type, String pathName,
  574. Set<Attribute> forBothOperaiton) throws IOException {
  575. assertEntry(type, pathName, forBothOperaiton, forBothOperaiton);
  576. }
  577. /**
  578. * Assert an entry with no attribute expected.
  579. *
  580. * @param type
  581. * @param pathName
  582. * @throws IOException
  583. */
  584. private void assertEntry(FileMode type, String pathName) throws IOException {
  585. assertEntry(type, pathName, Collections.<Attribute> emptySet(),
  586. Collections.<Attribute> emptySet());
  587. }
  588. /**
  589. * Assert that an entry;
  590. * <ul>
  591. * <li>Has the correct type</li>
  592. * <li>Exist in the tree walk</li>
  593. * <li>Has the expected attributes on a checkin operation</li>
  594. * <li>Has the expected attributes on a checkout operation</li>
  595. * </ul>
  596. *
  597. * @param type
  598. * @param pathName
  599. * @param checkinAttributes
  600. * @param checkoutAttributes
  601. * @throws IOException
  602. */
  603. private void assertEntry(FileMode type, String pathName,
  604. Set<Attribute> checkinAttributes, Set<Attribute> checkoutAttributes)
  605. throws IOException {
  606. assertTrue("walk has entry", walk.next());
  607. assertTrue("walk has entry", ci_walk.next());
  608. assertEquals(pathName, walk.getPathString());
  609. assertEquals(type, walk.getFileMode(0));
  610. assertEquals(checkinAttributes,
  611. asSet(ci_walk.getAttributes().getAll()));
  612. assertEquals(checkoutAttributes,
  613. asSet(walk.getAttributes().getAll()));
  614. if (D.equals(type)) {
  615. walk.enterSubtree();
  616. ci_walk.enterSubtree();
  617. }
  618. }
  619. private static Set<Attribute> asSet(Collection<Attribute> attributes) {
  620. Set<Attribute> ret = new HashSet<>();
  621. for (Attribute a : attributes) {
  622. ret.add(a);
  623. }
  624. return (ret);
  625. }
  626. private File writeAttributesFile(String name, String... rules)
  627. throws IOException {
  628. StringBuilder data = new StringBuilder();
  629. for (String line : rules)
  630. data.append(line + "\n");
  631. return writeTrashFile(name, data.toString());
  632. }
  633. /**
  634. * Creates an attributes file and set its location in the git configuration.
  635. *
  636. * @param fileName
  637. * @param attributes
  638. * @return The attribute file
  639. * @throws IOException
  640. * @see Repository#getConfig()
  641. */
  642. private File writeGlobalAttributeFile(String fileName, String... attributes)
  643. throws IOException {
  644. customAttributeFile = File.createTempFile("tmp_", fileName, null);
  645. customAttributeFile.deleteOnExit();
  646. StringBuilder attributesFileContent = new StringBuilder();
  647. for (String attr : attributes) {
  648. attributesFileContent.append(attr).append("\n");
  649. }
  650. JGitTestUtil.write(customAttributeFile,
  651. attributesFileContent.toString());
  652. db.getConfig().setString("core", null, "attributesfile",
  653. customAttributeFile.getAbsolutePath());
  654. return customAttributeFile;
  655. }
  656. static Set<Attribute> asSet(Attribute... attrs) {
  657. HashSet<Attribute> result = new HashSet<>();
  658. result.addAll(Arrays.asList(attrs));
  659. return result;
  660. }
  661. private void endWalk() throws IOException {
  662. assertFalse("Not all files tested", walk.next());
  663. assertFalse("Not all files tested", ci_walk.next());
  664. }
  665. }