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