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 24KB

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