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.

AddCommandTest.java 42KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309
  1. /*
  2. * Copyright (C) 2010, Stefan Lay <stefan.lay@sap.com>
  3. * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
  4. * and other copyright owners as documented in the project's IP log.
  5. *
  6. * This program and the accompanying materials are made available
  7. * under the terms of the Eclipse Distribution License v1.0 which
  8. * accompanies this distribution, is reproduced below, and is
  9. * available at http://www.eclipse.org/org/documents/edl-v10.php
  10. *
  11. * All rights reserved.
  12. *
  13. * Redistribution and use in source and binary forms, with or
  14. * without modification, are permitted provided that the following
  15. * conditions are met:
  16. *
  17. * - Redistributions of source code must retain the above copyright
  18. * notice, this list of conditions and the following disclaimer.
  19. *
  20. * - Redistributions in binary form must reproduce the above
  21. * copyright notice, this list of conditions and the following
  22. * disclaimer in the documentation and/or other materials provided
  23. * with the distribution.
  24. *
  25. * - Neither the name of the Eclipse Foundation, Inc. nor the
  26. * names of its contributors may be used to endorse or promote
  27. * products derived from this software without specific prior
  28. * written permission.
  29. *
  30. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  31. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  32. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  33. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  34. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  35. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  36. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  37. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  38. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  39. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  40. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  41. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  42. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  43. */
  44. package org.eclipse.jgit.api;
  45. import static java.nio.charset.StandardCharsets.UTF_8;
  46. import static org.eclipse.jgit.util.FileUtils.RECURSIVE;
  47. import static org.junit.Assert.assertEquals;
  48. import static org.junit.Assert.assertTrue;
  49. import static org.junit.Assert.fail;
  50. import java.io.File;
  51. import java.io.FileInputStream;
  52. import java.io.IOException;
  53. import java.io.PrintWriter;
  54. import java.util.Set;
  55. import org.eclipse.jgit.api.errors.FilterFailedException;
  56. import org.eclipse.jgit.api.errors.GitAPIException;
  57. import org.eclipse.jgit.api.errors.NoFilepatternException;
  58. import org.eclipse.jgit.attributes.FilterCommandRegistry;
  59. import org.eclipse.jgit.dircache.DirCache;
  60. import org.eclipse.jgit.dircache.DirCacheBuilder;
  61. import org.eclipse.jgit.dircache.DirCacheEntry;
  62. import org.eclipse.jgit.junit.JGitTestUtil;
  63. import org.eclipse.jgit.junit.RepositoryTestCase;
  64. import org.eclipse.jgit.lfs.BuiltinLFS;
  65. import org.eclipse.jgit.lib.ConfigConstants;
  66. import org.eclipse.jgit.lib.Constants;
  67. import org.eclipse.jgit.lib.FileMode;
  68. import org.eclipse.jgit.lib.ObjectId;
  69. import org.eclipse.jgit.lib.ObjectInserter;
  70. import org.eclipse.jgit.lib.Repository;
  71. import org.eclipse.jgit.lib.StoredConfig;
  72. import org.eclipse.jgit.revwalk.RevCommit;
  73. import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
  74. import org.eclipse.jgit.treewalk.TreeWalk;
  75. import org.eclipse.jgit.treewalk.WorkingTreeOptions;
  76. import org.eclipse.jgit.util.FS;
  77. import org.eclipse.jgit.util.FileUtils;
  78. import org.junit.Test;
  79. import org.junit.experimental.theories.DataPoints;
  80. import org.junit.experimental.theories.Theories;
  81. import org.junit.experimental.theories.Theory;
  82. import org.junit.runner.RunWith;
  83. @RunWith(Theories.class)
  84. public class AddCommandTest extends RepositoryTestCase {
  85. @DataPoints
  86. public static boolean[] sleepBeforeAddOptions = { true, false };
  87. @Override
  88. public void setUp() throws Exception {
  89. BuiltinLFS.register();
  90. super.setUp();
  91. }
  92. @Test
  93. public void testAddNothing() throws GitAPIException {
  94. try (Git git = new Git(db)) {
  95. git.add().call();
  96. fail("Expected IllegalArgumentException");
  97. } catch (NoFilepatternException e) {
  98. // expected
  99. }
  100. }
  101. @Test
  102. public void testAddNonExistingSingleFile() throws GitAPIException {
  103. try (Git git = new Git(db)) {
  104. DirCache dc = git.add().addFilepattern("a.txt").call();
  105. assertEquals(0, dc.getEntryCount());
  106. }
  107. }
  108. @Test
  109. public void testAddExistingSingleFile() throws IOException, GitAPIException {
  110. File file = new File(db.getWorkTree(), "a.txt");
  111. FileUtils.createNewFile(file);
  112. try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
  113. writer.print("content");
  114. }
  115. try (Git git = new Git(db)) {
  116. git.add().addFilepattern("a.txt").call();
  117. assertEquals(
  118. "[a.txt, mode:100644, content:content]",
  119. indexState(CONTENT));
  120. }
  121. }
  122. @Test
  123. public void testCleanFilter() throws IOException, GitAPIException {
  124. writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
  125. writeTrashFile("src/a.tmp", "foo");
  126. // Caution: we need a trailing '\n' since sed on mac always appends
  127. // linefeeds if missing
  128. writeTrashFile("src/a.txt", "foo\n");
  129. File script = writeTempFile("sed s/o/e/g");
  130. try (Git git = new Git(db)) {
  131. StoredConfig config = git.getRepository().getConfig();
  132. config.setString("filter", "tstFilter", "clean",
  133. "sh " + slashify(script.getPath()));
  134. config.save();
  135. git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp")
  136. .call();
  137. assertEquals(
  138. "[src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:fee\n]",
  139. indexState(CONTENT));
  140. }
  141. }
  142. @Theory
  143. public void testBuiltinFilters(boolean sleepBeforeAdd)
  144. throws IOException,
  145. GitAPIException, InterruptedException {
  146. writeTrashFile(".gitattributes", "*.txt filter=lfs");
  147. writeTrashFile("src/a.tmp", "foo");
  148. // Caution: we need a trailing '\n' since sed on mac always appends
  149. // linefeeds if missing
  150. File script = writeTempFile("sed s/o/e/g");
  151. File f = writeTrashFile("src/a.txt", "foo\n");
  152. try (Git git = new Git(db)) {
  153. if (!sleepBeforeAdd) {
  154. fsTick(f);
  155. }
  156. git.add().addFilepattern(".gitattributes").call();
  157. StoredConfig config = git.getRepository().getConfig();
  158. config.setString("filter", "lfs", "clean",
  159. "sh " + slashify(script.getPath()));
  160. config.setString("filter", "lfs", "smudge",
  161. "sh " + slashify(script.getPath()));
  162. config.setBoolean("filter", "lfs", "useJGitBuiltin", true);
  163. config.save();
  164. if (!sleepBeforeAdd) {
  165. fsTick(f);
  166. }
  167. git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp")
  168. .addFilepattern(".gitattributes").call();
  169. assertEquals(
  170. "[.gitattributes, mode:100644, content:*.txt filter=lfs][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c\nsize 4\n]",
  171. indexState(CONTENT));
  172. RevCommit c1 = git.commit().setMessage("c1").call();
  173. assertTrue(git.status().call().isClean());
  174. f = writeTrashFile("src/a.txt", "foobar\n");
  175. if (!sleepBeforeAdd) {
  176. fsTick(f);
  177. }
  178. git.add().addFilepattern("src/a.txt").call();
  179. git.commit().setMessage("c2").call();
  180. assertTrue(git.status().call().isClean());
  181. assertEquals(
  182. "[.gitattributes, mode:100644, content:*.txt filter=lfs][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f\nsize 7\n]",
  183. indexState(CONTENT));
  184. assertEquals("foobar\n", read("src/a.txt"));
  185. git.checkout().setName(c1.getName()).call();
  186. assertEquals(
  187. "[.gitattributes, mode:100644, content:*.txt filter=lfs][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c\nsize 4\n]",
  188. indexState(CONTENT));
  189. assertEquals(
  190. "foo\n", read("src/a.txt"));
  191. }
  192. }
  193. @Theory
  194. public void testBuiltinCleanFilter(boolean sleepBeforeAdd)
  195. throws IOException, GitAPIException, InterruptedException {
  196. writeTrashFile(".gitattributes", "*.txt filter=lfs");
  197. writeTrashFile("src/a.tmp", "foo");
  198. // Caution: we need a trailing '\n' since sed on mac always appends
  199. // linefeeds if missing
  200. File script = writeTempFile("sed s/o/e/g");
  201. File f = writeTrashFile("src/a.txt", "foo\n");
  202. // unregister the smudge filter. Only clean filter should be builtin
  203. FilterCommandRegistry.unregister(
  204. org.eclipse.jgit.lib.Constants.BUILTIN_FILTER_PREFIX
  205. + "lfs/smudge");
  206. try (Git git = new Git(db)) {
  207. if (!sleepBeforeAdd) {
  208. fsTick(f);
  209. }
  210. git.add().addFilepattern(".gitattributes").call();
  211. StoredConfig config = git.getRepository().getConfig();
  212. config.setString("filter", "lfs", "clean",
  213. "sh " + slashify(script.getPath()));
  214. config.setString("filter", "lfs", "smudge",
  215. "sh " + slashify(script.getPath()));
  216. config.setBoolean("filter", "lfs", "useJGitBuiltin", true);
  217. config.save();
  218. if (!sleepBeforeAdd) {
  219. fsTick(f);
  220. }
  221. git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp")
  222. .addFilepattern(".gitattributes").call();
  223. assertEquals(
  224. "[.gitattributes, mode:100644, content:*.txt filter=lfs][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c\nsize 4\n]",
  225. indexState(CONTENT));
  226. RevCommit c1 = git.commit().setMessage("c1").call();
  227. assertTrue(git.status().call().isClean());
  228. f = writeTrashFile("src/a.txt", "foobar\n");
  229. if (!sleepBeforeAdd) {
  230. fsTick(f);
  231. }
  232. git.add().addFilepattern("src/a.txt").call();
  233. git.commit().setMessage("c2").call();
  234. assertTrue(git.status().call().isClean());
  235. assertEquals(
  236. "[.gitattributes, mode:100644, content:*.txt filter=lfs][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f\nsize 7\n]",
  237. indexState(CONTENT));
  238. assertEquals("foobar\n", read("src/a.txt"));
  239. git.checkout().setName(c1.getName()).call();
  240. assertEquals(
  241. "[.gitattributes, mode:100644, content:*.txt filter=lfs][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c\nsize 4\n]",
  242. indexState(CONTENT));
  243. // due to lfs clean filter but dummy smudge filter we expect strange
  244. // content. The smudge filter converts from real content to pointer
  245. // file content (starting with "version ") but the smudge filter
  246. // replaces 'o' by 'e' which results in a text starting with
  247. // "versien "
  248. assertEquals(
  249. "versien https://git-lfs.github.cem/spec/v1\neid sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c\nsize 4\n",
  250. read("src/a.txt"));
  251. }
  252. }
  253. @Test
  254. public void testAttributesWithTreeWalkFilter()
  255. throws IOException, GitAPIException {
  256. writeTrashFile(".gitattributes", "*.txt filter=lfs");
  257. writeTrashFile("src/a.tmp", "foo");
  258. writeTrashFile("src/a.txt", "foo\n");
  259. File script = writeTempFile("sed s/o/e/g");
  260. try (Git git = new Git(db)) {
  261. StoredConfig config = git.getRepository().getConfig();
  262. config.setString("filter", "lfs", "clean",
  263. "sh " + slashify(script.getPath()));
  264. config.save();
  265. git.add().addFilepattern(".gitattributes").call();
  266. git.commit().setMessage("attr").call();
  267. git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp")
  268. .addFilepattern(".gitattributes").call();
  269. git.commit().setMessage("c1").call();
  270. assertTrue(git.status().call().isClean());
  271. }
  272. }
  273. @Test
  274. public void testAttributesConflictingMatch() throws Exception {
  275. writeTrashFile(".gitattributes", "foo/** crlf=input\n*.jar binary");
  276. writeTrashFile("foo/bar.jar", "\r\n");
  277. // We end up with attributes [binary -diff -merge -text crlf=input].
  278. // crlf should have no effect when -text is present.
  279. try (Git git = new Git(db)) {
  280. git.add().addFilepattern(".").call();
  281. assertEquals(
  282. "[.gitattributes, mode:100644, content:foo/** crlf=input\n*.jar binary]"
  283. + "[foo/bar.jar, mode:100644, content:\r\n]",
  284. indexState(CONTENT));
  285. }
  286. }
  287. @Test
  288. public void testCleanFilterEnvironment()
  289. throws IOException, GitAPIException {
  290. writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
  291. writeTrashFile("src/a.txt", "foo");
  292. File script = writeTempFile("echo $GIT_DIR; echo 1 >xyz");
  293. try (Git git = new Git(db)) {
  294. StoredConfig config = git.getRepository().getConfig();
  295. config.setString("filter", "tstFilter", "clean",
  296. "sh " + slashify(script.getPath()));
  297. config.save();
  298. git.add().addFilepattern("src/a.txt").call();
  299. String gitDir = db.getDirectory().getAbsolutePath();
  300. assertEquals("[src/a.txt, mode:100644, content:" + gitDir
  301. + "\n]", indexState(CONTENT));
  302. assertTrue(new File(db.getWorkTree(), "xyz").exists());
  303. }
  304. }
  305. @Test
  306. public void testMultipleCleanFilter() throws IOException, GitAPIException {
  307. writeTrashFile(".gitattributes",
  308. "*.txt filter=tstFilter\n*.tmp filter=tstFilter2");
  309. // Caution: we need a trailing '\n' since sed on mac always appends
  310. // linefeeds if missing
  311. writeTrashFile("src/a.tmp", "foo\n");
  312. writeTrashFile("src/a.txt", "foo\n");
  313. File script = writeTempFile("sed s/o/e/g");
  314. File script2 = writeTempFile("sed s/f/x/g");
  315. try (Git git = new Git(db)) {
  316. StoredConfig config = git.getRepository().getConfig();
  317. config.setString("filter", "tstFilter", "clean",
  318. "sh " + slashify(script.getPath()));
  319. config.setString("filter", "tstFilter2", "clean",
  320. "sh " + slashify(script2.getPath()));
  321. config.save();
  322. git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp")
  323. .call();
  324. assertEquals(
  325. "[src/a.tmp, mode:100644, content:xoo\n][src/a.txt, mode:100644, content:fee\n]",
  326. indexState(CONTENT));
  327. // TODO: multiple clean filters for one file???
  328. }
  329. }
  330. /**
  331. * The path of an added file name contains ';' and afterwards malicious
  332. * commands. Make sure when calling filter commands to properly escape the
  333. * filenames
  334. *
  335. * @throws IOException
  336. * @throws GitAPIException
  337. */
  338. @Test
  339. public void testCommandInjection() throws IOException, GitAPIException {
  340. // Caution: we need a trailing '\n' since sed on mac always appends
  341. // linefeeds if missing
  342. writeTrashFile("; echo virus", "foo\n");
  343. File script = writeTempFile("sed s/o/e/g");
  344. try (Git git = new Git(db)) {
  345. StoredConfig config = git.getRepository().getConfig();
  346. config.setString("filter", "tstFilter", "clean",
  347. "sh " + slashify(script.getPath()) + " %f");
  348. writeTrashFile(".gitattributes", "* filter=tstFilter");
  349. git.add().addFilepattern("; echo virus").call();
  350. // Without proper escaping the content would be "feovirus". The sed
  351. // command and the "echo virus" would contribute to the content
  352. assertEquals("[; echo virus, mode:100644, content:fee\n]",
  353. indexState(CONTENT));
  354. }
  355. }
  356. @Test
  357. public void testBadCleanFilter() throws IOException, GitAPIException {
  358. writeTrashFile("a.txt", "foo");
  359. File script = writeTempFile("sedfoo s/o/e/g");
  360. try (Git git = new Git(db)) {
  361. StoredConfig config = git.getRepository().getConfig();
  362. config.setString("filter", "tstFilter", "clean",
  363. "sh " + script.getPath());
  364. config.save();
  365. writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
  366. try {
  367. git.add().addFilepattern("a.txt").call();
  368. fail("Didn't received the expected exception");
  369. } catch (FilterFailedException e) {
  370. assertEquals(127, e.getReturnCode());
  371. }
  372. }
  373. }
  374. @Test
  375. public void testBadCleanFilter2() throws IOException, GitAPIException {
  376. writeTrashFile("a.txt", "foo");
  377. File script = writeTempFile("sed s/o/e/g");
  378. try (Git git = new Git(db)) {
  379. StoredConfig config = git.getRepository().getConfig();
  380. config.setString("filter", "tstFilter", "clean",
  381. "shfoo " + script.getPath());
  382. config.save();
  383. writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
  384. try {
  385. git.add().addFilepattern("a.txt").call();
  386. fail("Didn't received the expected exception");
  387. } catch (FilterFailedException e) {
  388. assertEquals(127, e.getReturnCode());
  389. }
  390. }
  391. }
  392. @Test
  393. public void testCleanFilterReturning12() throws IOException,
  394. GitAPIException {
  395. writeTrashFile("a.txt", "foo");
  396. File script = writeTempFile("exit 12");
  397. try (Git git = new Git(db)) {
  398. StoredConfig config = git.getRepository().getConfig();
  399. config.setString("filter", "tstFilter", "clean",
  400. "sh " + slashify(script.getPath()));
  401. config.save();
  402. writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
  403. try {
  404. git.add().addFilepattern("a.txt").call();
  405. fail("Didn't received the expected exception");
  406. } catch (FilterFailedException e) {
  407. assertEquals(12, e.getReturnCode());
  408. }
  409. }
  410. }
  411. @Test
  412. public void testNotApplicableFilter() throws IOException, GitAPIException {
  413. writeTrashFile("a.txt", "foo");
  414. File script = writeTempFile("sed s/o/e/g");
  415. try (Git git = new Git(db)) {
  416. StoredConfig config = git.getRepository().getConfig();
  417. config.setString("filter", "tstFilter", "something",
  418. "sh " + script.getPath());
  419. config.save();
  420. writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
  421. git.add().addFilepattern("a.txt").call();
  422. assertEquals("[a.txt, mode:100644, content:foo]",
  423. indexState(CONTENT));
  424. }
  425. }
  426. private File writeTempFile(String body) throws IOException {
  427. File f = File.createTempFile("AddCommandTest_", "");
  428. JGitTestUtil.write(f, body);
  429. return f;
  430. }
  431. @Test
  432. public void testAddExistingSingleSmallFileWithNewLine() throws IOException,
  433. GitAPIException {
  434. File file = new File(db.getWorkTree(), "a.txt");
  435. FileUtils.createNewFile(file);
  436. try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
  437. writer.print("row1\r\nrow2");
  438. }
  439. try (Git git = new Git(db)) {
  440. db.getConfig().setString("core", null, "autocrlf", "false");
  441. git.add().addFilepattern("a.txt").call();
  442. assertEquals("[a.txt, mode:100644, content:row1\r\nrow2]",
  443. indexState(CONTENT));
  444. db.getConfig().setString("core", null, "autocrlf", "true");
  445. git.add().addFilepattern("a.txt").call();
  446. assertEquals("[a.txt, mode:100644, content:row1\nrow2]",
  447. indexState(CONTENT));
  448. db.getConfig().setString("core", null, "autocrlf", "input");
  449. git.add().addFilepattern("a.txt").call();
  450. assertEquals("[a.txt, mode:100644, content:row1\nrow2]",
  451. indexState(CONTENT));
  452. }
  453. }
  454. @Test
  455. public void testAddExistingSingleMediumSizeFileWithNewLine()
  456. throws IOException, GitAPIException {
  457. File file = new File(db.getWorkTree(), "a.txt");
  458. FileUtils.createNewFile(file);
  459. StringBuilder data = new StringBuilder();
  460. for (int i = 0; i < 1000; ++i) {
  461. data.append("row1\r\nrow2");
  462. }
  463. String crData = data.toString();
  464. try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
  465. writer.print(crData);
  466. }
  467. String lfData = data.toString().replaceAll("\r", "");
  468. try (Git git = new Git(db)) {
  469. db.getConfig().setString("core", null, "autocrlf", "false");
  470. git.add().addFilepattern("a.txt").call();
  471. assertEquals("[a.txt, mode:100644, content:" + data + "]",
  472. indexState(CONTENT));
  473. db.getConfig().setString("core", null, "autocrlf", "true");
  474. git.add().addFilepattern("a.txt").call();
  475. assertEquals("[a.txt, mode:100644, content:" + lfData + "]",
  476. indexState(CONTENT));
  477. db.getConfig().setString("core", null, "autocrlf", "input");
  478. git.add().addFilepattern("a.txt").call();
  479. assertEquals("[a.txt, mode:100644, content:" + lfData + "]",
  480. indexState(CONTENT));
  481. }
  482. }
  483. @Test
  484. public void testAddExistingSingleBinaryFile() throws IOException,
  485. GitAPIException {
  486. File file = new File(db.getWorkTree(), "a.txt");
  487. FileUtils.createNewFile(file);
  488. try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
  489. writer.print("row1\r\nrow2\u0000");
  490. }
  491. try (Git git = new Git(db)) {
  492. db.getConfig().setString("core", null, "autocrlf", "false");
  493. git.add().addFilepattern("a.txt").call();
  494. assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]",
  495. indexState(CONTENT));
  496. db.getConfig().setString("core", null, "autocrlf", "true");
  497. git.add().addFilepattern("a.txt").call();
  498. assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]",
  499. indexState(CONTENT));
  500. db.getConfig().setString("core", null, "autocrlf", "input");
  501. git.add().addFilepattern("a.txt").call();
  502. assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]",
  503. indexState(CONTENT));
  504. }
  505. }
  506. @Test
  507. public void testAddExistingSingleFileInSubDir() throws IOException,
  508. GitAPIException {
  509. FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
  510. File file = new File(db.getWorkTree(), "sub/a.txt");
  511. FileUtils.createNewFile(file);
  512. try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
  513. writer.print("content");
  514. }
  515. try (Git git = new Git(db)) {
  516. git.add().addFilepattern("sub/a.txt").call();
  517. assertEquals(
  518. "[sub/a.txt, mode:100644, content:content]",
  519. indexState(CONTENT));
  520. }
  521. }
  522. @Test
  523. public void testAddExistingSingleFileTwice() throws IOException,
  524. GitAPIException {
  525. File file = new File(db.getWorkTree(), "a.txt");
  526. FileUtils.createNewFile(file);
  527. try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
  528. writer.print("content");
  529. }
  530. try (Git git = new Git(db)) {
  531. DirCache dc = git.add().addFilepattern("a.txt").call();
  532. dc.getEntry(0).getObjectId();
  533. try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
  534. writer.print("other content");
  535. }
  536. dc = git.add().addFilepattern("a.txt").call();
  537. assertEquals(
  538. "[a.txt, mode:100644, content:other content]",
  539. indexState(CONTENT));
  540. }
  541. }
  542. @Test
  543. public void testAddExistingSingleFileTwiceWithCommit() throws Exception {
  544. File file = new File(db.getWorkTree(), "a.txt");
  545. FileUtils.createNewFile(file);
  546. try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
  547. writer.print("content");
  548. }
  549. try (Git git = new Git(db)) {
  550. DirCache dc = git.add().addFilepattern("a.txt").call();
  551. dc.getEntry(0).getObjectId();
  552. git.commit().setMessage("commit a.txt").call();
  553. try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
  554. writer.print("other content");
  555. }
  556. dc = git.add().addFilepattern("a.txt").call();
  557. assertEquals(
  558. "[a.txt, mode:100644, content:other content]",
  559. indexState(CONTENT));
  560. }
  561. }
  562. @Test
  563. public void testAddRemovedFile() throws Exception {
  564. File file = new File(db.getWorkTree(), "a.txt");
  565. FileUtils.createNewFile(file);
  566. try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
  567. writer.print("content");
  568. }
  569. try (Git git = new Git(db)) {
  570. DirCache dc = git.add().addFilepattern("a.txt").call();
  571. dc.getEntry(0).getObjectId();
  572. FileUtils.delete(file);
  573. // is supposed to do nothing
  574. dc = git.add().addFilepattern("a.txt").call();
  575. assertEquals(
  576. "[a.txt, mode:100644, content:content]",
  577. indexState(CONTENT));
  578. }
  579. }
  580. @Test
  581. public void testAddRemovedCommittedFile() throws Exception {
  582. File file = new File(db.getWorkTree(), "a.txt");
  583. FileUtils.createNewFile(file);
  584. try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
  585. writer.print("content");
  586. }
  587. try (Git git = new Git(db)) {
  588. DirCache dc = git.add().addFilepattern("a.txt").call();
  589. git.commit().setMessage("commit a.txt").call();
  590. dc.getEntry(0).getObjectId();
  591. FileUtils.delete(file);
  592. // is supposed to do nothing
  593. dc = git.add().addFilepattern("a.txt").call();
  594. assertEquals(
  595. "[a.txt, mode:100644, content:content]",
  596. indexState(CONTENT));
  597. }
  598. }
  599. @Test
  600. public void testAddWithConflicts() throws Exception {
  601. // prepare conflict
  602. File file = new File(db.getWorkTree(), "a.txt");
  603. FileUtils.createNewFile(file);
  604. try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
  605. writer.print("content");
  606. }
  607. File file2 = new File(db.getWorkTree(), "b.txt");
  608. FileUtils.createNewFile(file2);
  609. try (PrintWriter writer = new PrintWriter(file2, UTF_8.name())) {
  610. writer.print("content b");
  611. }
  612. ObjectInserter newObjectInserter = db.newObjectInserter();
  613. DirCache dc = db.lockDirCache();
  614. DirCacheBuilder builder = dc.builder();
  615. addEntryToBuilder("b.txt", file2, newObjectInserter, builder, 0);
  616. addEntryToBuilder("a.txt", file, newObjectInserter, builder, 1);
  617. try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
  618. writer.print("other content");
  619. }
  620. addEntryToBuilder("a.txt", file, newObjectInserter, builder, 3);
  621. try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
  622. writer.print("our content");
  623. }
  624. addEntryToBuilder("a.txt", file, newObjectInserter, builder, 2)
  625. .getObjectId();
  626. builder.commit();
  627. assertEquals(
  628. "[a.txt, mode:100644, stage:1, content:content]" +
  629. "[a.txt, mode:100644, stage:2, content:our content]" +
  630. "[a.txt, mode:100644, stage:3, content:other content]" +
  631. "[b.txt, mode:100644, content:content b]",
  632. indexState(CONTENT));
  633. // now the test begins
  634. try (Git git = new Git(db)) {
  635. dc = git.add().addFilepattern("a.txt").call();
  636. assertEquals(
  637. "[a.txt, mode:100644, content:our content]" +
  638. "[b.txt, mode:100644, content:content b]",
  639. indexState(CONTENT));
  640. }
  641. }
  642. @Test
  643. public void testAddTwoFiles() throws Exception {
  644. File file = new File(db.getWorkTree(), "a.txt");
  645. FileUtils.createNewFile(file);
  646. try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
  647. writer.print("content");
  648. }
  649. File file2 = new File(db.getWorkTree(), "b.txt");
  650. FileUtils.createNewFile(file2);
  651. try (PrintWriter writer = new PrintWriter(file2, UTF_8.name())) {
  652. writer.print("content b");
  653. }
  654. try (Git git = new Git(db)) {
  655. git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
  656. assertEquals(
  657. "[a.txt, mode:100644, content:content]" +
  658. "[b.txt, mode:100644, content:content b]",
  659. indexState(CONTENT));
  660. }
  661. }
  662. @Test
  663. public void testAddFolder() throws Exception {
  664. FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
  665. File file = new File(db.getWorkTree(), "sub/a.txt");
  666. FileUtils.createNewFile(file);
  667. try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
  668. writer.print("content");
  669. }
  670. File file2 = new File(db.getWorkTree(), "sub/b.txt");
  671. FileUtils.createNewFile(file2);
  672. try (PrintWriter writer = new PrintWriter(file2, UTF_8.name())) {
  673. writer.print("content b");
  674. }
  675. try (Git git = new Git(db)) {
  676. git.add().addFilepattern("sub").call();
  677. assertEquals(
  678. "[sub/a.txt, mode:100644, content:content]" +
  679. "[sub/b.txt, mode:100644, content:content b]",
  680. indexState(CONTENT));
  681. }
  682. }
  683. @Test
  684. public void testAddIgnoredFile() throws Exception {
  685. FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
  686. File file = new File(db.getWorkTree(), "sub/a.txt");
  687. FileUtils.createNewFile(file);
  688. try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
  689. writer.print("content");
  690. }
  691. File ignoreFile = new File(db.getWorkTree(), ".gitignore");
  692. FileUtils.createNewFile(ignoreFile);
  693. try (PrintWriter writer = new PrintWriter(ignoreFile, UTF_8.name())) {
  694. writer.print("sub/b.txt");
  695. }
  696. File file2 = new File(db.getWorkTree(), "sub/b.txt");
  697. FileUtils.createNewFile(file2);
  698. try (PrintWriter writer = new PrintWriter(file2, UTF_8.name())) {
  699. writer.print("content b");
  700. }
  701. try (Git git = new Git(db)) {
  702. git.add().addFilepattern("sub").call();
  703. assertEquals(
  704. "[sub/a.txt, mode:100644, content:content]",
  705. indexState(CONTENT));
  706. }
  707. }
  708. @Test
  709. public void testAddWholeRepo() throws Exception {
  710. FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
  711. File file = new File(db.getWorkTree(), "sub/a.txt");
  712. FileUtils.createNewFile(file);
  713. try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
  714. writer.print("content");
  715. }
  716. File file2 = new File(db.getWorkTree(), "sub/b.txt");
  717. FileUtils.createNewFile(file2);
  718. try (PrintWriter writer = new PrintWriter(file2, UTF_8.name())) {
  719. writer.print("content b");
  720. }
  721. try (Git git = new Git(db)) {
  722. git.add().addFilepattern(".").call();
  723. assertEquals(
  724. "[sub/a.txt, mode:100644, content:content]" +
  725. "[sub/b.txt, mode:100644, content:content b]",
  726. indexState(CONTENT));
  727. }
  728. }
  729. // the same three cases as in testAddWithParameterUpdate
  730. // file a exists in workdir and in index -> added
  731. // file b exists not in workdir but in index -> unchanged
  732. // file c exists in workdir but not in index -> added
  733. @Test
  734. public void testAddWithoutParameterUpdate() throws Exception {
  735. FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
  736. File file = new File(db.getWorkTree(), "sub/a.txt");
  737. FileUtils.createNewFile(file);
  738. try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
  739. writer.print("content");
  740. }
  741. File file2 = new File(db.getWorkTree(), "sub/b.txt");
  742. FileUtils.createNewFile(file2);
  743. try (PrintWriter writer = new PrintWriter(file2, UTF_8.name())) {
  744. writer.print("content b");
  745. }
  746. try (Git git = new Git(db)) {
  747. git.add().addFilepattern("sub").call();
  748. assertEquals(
  749. "[sub/a.txt, mode:100644, content:content]" +
  750. "[sub/b.txt, mode:100644, content:content b]",
  751. indexState(CONTENT));
  752. git.commit().setMessage("commit").call();
  753. // new unstaged file sub/c.txt
  754. File file3 = new File(db.getWorkTree(), "sub/c.txt");
  755. FileUtils.createNewFile(file3);
  756. try (PrintWriter writer = new PrintWriter(file3, UTF_8.name())) {
  757. writer.print("content c");
  758. }
  759. // file sub/a.txt is modified
  760. try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
  761. writer.print("modified content");
  762. }
  763. // file sub/b.txt is deleted
  764. FileUtils.delete(file2);
  765. git.add().addFilepattern("sub").call();
  766. // change in sub/a.txt is staged
  767. // deletion of sub/b.txt is not staged
  768. // sub/c.txt is staged
  769. assertEquals(
  770. "[sub/a.txt, mode:100644, content:modified content]" +
  771. "[sub/b.txt, mode:100644, content:content b]" +
  772. "[sub/c.txt, mode:100644, content:content c]",
  773. indexState(CONTENT));
  774. }
  775. }
  776. // file a exists in workdir and in index -> added
  777. // file b exists not in workdir but in index -> deleted
  778. // file c exists in workdir but not in index -> unchanged
  779. @Test
  780. public void testAddWithParameterUpdate() throws Exception {
  781. FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
  782. File file = new File(db.getWorkTree(), "sub/a.txt");
  783. FileUtils.createNewFile(file);
  784. try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
  785. writer.print("content");
  786. }
  787. File file2 = new File(db.getWorkTree(), "sub/b.txt");
  788. FileUtils.createNewFile(file2);
  789. try (PrintWriter writer = new PrintWriter(file2, UTF_8.name())) {
  790. writer.print("content b");
  791. }
  792. try (Git git = new Git(db)) {
  793. git.add().addFilepattern("sub").call();
  794. assertEquals(
  795. "[sub/a.txt, mode:100644, content:content]" +
  796. "[sub/b.txt, mode:100644, content:content b]",
  797. indexState(CONTENT));
  798. git.commit().setMessage("commit").call();
  799. // new unstaged file sub/c.txt
  800. File file3 = new File(db.getWorkTree(), "sub/c.txt");
  801. FileUtils.createNewFile(file3);
  802. try (PrintWriter writer = new PrintWriter(file3, UTF_8.name())) {
  803. writer.print("content c");
  804. }
  805. // file sub/a.txt is modified
  806. try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
  807. writer.print("modified content");
  808. }
  809. FileUtils.delete(file2);
  810. // change in sub/a.txt is staged
  811. // deletion of sub/b.txt is staged
  812. // sub/c.txt is not staged
  813. git.add().addFilepattern("sub").setUpdate(true).call();
  814. // change in sub/a.txt is staged
  815. assertEquals(
  816. "[sub/a.txt, mode:100644, content:modified content]",
  817. indexState(CONTENT));
  818. }
  819. }
  820. @Test
  821. public void testAssumeUnchanged() throws Exception {
  822. try (Git git = new Git(db)) {
  823. String path = "a.txt";
  824. writeTrashFile(path, "content");
  825. git.add().addFilepattern(path).call();
  826. String path2 = "b.txt";
  827. writeTrashFile(path2, "content");
  828. git.add().addFilepattern(path2).call();
  829. git.commit().setMessage("commit").call();
  830. assertEquals("[a.txt, mode:100644, content:"
  831. + "content, assume-unchanged:false]"
  832. + "[b.txt, mode:100644, content:content, "
  833. + "assume-unchanged:false]", indexState(CONTENT
  834. | ASSUME_UNCHANGED));
  835. assumeUnchanged(path2);
  836. assertEquals("[a.txt, mode:100644, content:content, "
  837. + "assume-unchanged:false][b.txt, mode:100644, "
  838. + "content:content, assume-unchanged:true]", indexState(CONTENT
  839. | ASSUME_UNCHANGED));
  840. writeTrashFile(path, "more content");
  841. writeTrashFile(path2, "more content");
  842. git.add().addFilepattern(".").call();
  843. assertEquals("[a.txt, mode:100644, content:more content,"
  844. + " assume-unchanged:false][b.txt, mode:100644,"
  845. + " content:content, assume-unchanged:true]",
  846. indexState(CONTENT
  847. | ASSUME_UNCHANGED));
  848. }
  849. }
  850. @Test
  851. public void testReplaceFileWithDirectory()
  852. throws IOException, NoFilepatternException, GitAPIException {
  853. try (Git git = new Git(db)) {
  854. writeTrashFile("df", "before replacement");
  855. git.add().addFilepattern("df").call();
  856. assertEquals("[df, mode:100644, content:before replacement]",
  857. indexState(CONTENT));
  858. FileUtils.delete(new File(db.getWorkTree(), "df"));
  859. writeTrashFile("df/f", "after replacement");
  860. git.add().addFilepattern("df").call();
  861. assertEquals("[df/f, mode:100644, content:after replacement]",
  862. indexState(CONTENT));
  863. }
  864. }
  865. @Test
  866. public void testReplaceDirectoryWithFile()
  867. throws IOException, NoFilepatternException, GitAPIException {
  868. try (Git git = new Git(db)) {
  869. writeTrashFile("df/f", "before replacement");
  870. git.add().addFilepattern("df").call();
  871. assertEquals("[df/f, mode:100644, content:before replacement]",
  872. indexState(CONTENT));
  873. FileUtils.delete(new File(db.getWorkTree(), "df"), RECURSIVE);
  874. writeTrashFile("df", "after replacement");
  875. git.add().addFilepattern("df").call();
  876. assertEquals("[df, mode:100644, content:after replacement]",
  877. indexState(CONTENT));
  878. }
  879. }
  880. @Test
  881. public void testReplaceFileByPartOfDirectory()
  882. throws IOException, NoFilepatternException, GitAPIException {
  883. try (Git git = new Git(db)) {
  884. writeTrashFile("src/main", "df", "before replacement");
  885. writeTrashFile("src/main", "z", "z");
  886. writeTrashFile("z", "z2");
  887. git.add().addFilepattern("src/main/df")
  888. .addFilepattern("src/main/z")
  889. .addFilepattern("z")
  890. .call();
  891. assertEquals(
  892. "[src/main/df, mode:100644, content:before replacement]" +
  893. "[src/main/z, mode:100644, content:z]" +
  894. "[z, mode:100644, content:z2]",
  895. indexState(CONTENT));
  896. FileUtils.delete(new File(db.getWorkTree(), "src/main/df"));
  897. writeTrashFile("src/main/df", "a", "after replacement");
  898. writeTrashFile("src/main/df", "b", "unrelated file");
  899. git.add().addFilepattern("src/main/df/a").call();
  900. assertEquals(
  901. "[src/main/df/a, mode:100644, content:after replacement]" +
  902. "[src/main/z, mode:100644, content:z]" +
  903. "[z, mode:100644, content:z2]",
  904. indexState(CONTENT));
  905. }
  906. }
  907. @Test
  908. public void testReplaceDirectoryConflictsWithFile()
  909. throws IOException, NoFilepatternException, GitAPIException {
  910. DirCache dc = db.lockDirCache();
  911. try (ObjectInserter oi = db.newObjectInserter()) {
  912. DirCacheBuilder builder = dc.builder();
  913. File f = writeTrashFile("a", "df", "content");
  914. addEntryToBuilder("a", f, oi, builder, 1);
  915. f = writeTrashFile("a", "df", "other content");
  916. addEntryToBuilder("a/df", f, oi, builder, 3);
  917. f = writeTrashFile("a", "df", "our content");
  918. addEntryToBuilder("a/df", f, oi, builder, 2);
  919. f = writeTrashFile("z", "z");
  920. addEntryToBuilder("z", f, oi, builder, 0);
  921. builder.commit();
  922. }
  923. assertEquals(
  924. "[a, mode:100644, stage:1, content:content]" +
  925. "[a/df, mode:100644, stage:2, content:our content]" +
  926. "[a/df, mode:100644, stage:3, content:other content]" +
  927. "[z, mode:100644, content:z]",
  928. indexState(CONTENT));
  929. try (Git git = new Git(db)) {
  930. FileUtils.delete(new File(db.getWorkTree(), "a"), RECURSIVE);
  931. writeTrashFile("a", "merged");
  932. git.add().addFilepattern("a").call();
  933. assertEquals("[a, mode:100644, content:merged]" +
  934. "[z, mode:100644, content:z]",
  935. indexState(CONTENT));
  936. }
  937. }
  938. @Test
  939. public void testExecutableRetention() throws Exception {
  940. StoredConfig config = db.getConfig();
  941. config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
  942. ConfigConstants.CONFIG_KEY_FILEMODE, true);
  943. config.save();
  944. FS executableFs = new FS() {
  945. @Override
  946. public boolean supportsExecute() {
  947. return true;
  948. }
  949. @Override
  950. public boolean setExecute(File f, boolean canExec) {
  951. return true;
  952. }
  953. @Override
  954. public ProcessBuilder runInShell(String cmd, String[] args) {
  955. return null;
  956. }
  957. @Override
  958. public boolean retryFailedLockFileCommit() {
  959. return false;
  960. }
  961. @Override
  962. public FS newInstance() {
  963. return this;
  964. }
  965. @Override
  966. protected File discoverGitExe() {
  967. return null;
  968. }
  969. @Override
  970. public boolean canExecute(File f) {
  971. try {
  972. return read(f).startsWith("binary:");
  973. } catch (IOException e) {
  974. return false;
  975. }
  976. }
  977. @Override
  978. public boolean isCaseSensitive() {
  979. return false;
  980. }
  981. };
  982. Git git = Git.open(db.getDirectory(), executableFs);
  983. String path = "a.txt";
  984. String path2 = "a.sh";
  985. writeTrashFile(path, "content");
  986. writeTrashFile(path2, "binary: content");
  987. git.add().addFilepattern(path).addFilepattern(path2).call();
  988. RevCommit commit1 = git.commit().setMessage("commit").call();
  989. try (TreeWalk walk = new TreeWalk(db)) {
  990. walk.addTree(commit1.getTree());
  991. walk.next();
  992. assertEquals(path2, walk.getPathString());
  993. assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0));
  994. walk.next();
  995. assertEquals(path, walk.getPathString());
  996. assertEquals(FileMode.REGULAR_FILE, walk.getFileMode(0));
  997. }
  998. config = db.getConfig();
  999. config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
  1000. ConfigConstants.CONFIG_KEY_FILEMODE, false);
  1001. config.save();
  1002. Git git2 = Git.open(db.getDirectory(), executableFs);
  1003. writeTrashFile(path2, "content2");
  1004. writeTrashFile(path, "binary: content2");
  1005. git2.add().addFilepattern(path).addFilepattern(path2).call();
  1006. RevCommit commit2 = git2.commit().setMessage("commit2").call();
  1007. try (TreeWalk walk = new TreeWalk(db)) {
  1008. walk.addTree(commit2.getTree());
  1009. walk.next();
  1010. assertEquals(path2, walk.getPathString());
  1011. assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0));
  1012. walk.next();
  1013. assertEquals(path, walk.getPathString());
  1014. assertEquals(FileMode.REGULAR_FILE, walk.getFileMode(0));
  1015. }
  1016. }
  1017. @Test
  1018. public void testAddGitlink() throws Exception {
  1019. createNestedRepo("git-link-dir");
  1020. try (Git git = new Git(db)) {
  1021. git.add().addFilepattern("git-link-dir").call();
  1022. assertEquals(
  1023. "[git-link-dir, mode:160000]",
  1024. indexState(0));
  1025. Set<String> untrackedFiles = git.status().call().getUntracked();
  1026. assert (untrackedFiles.isEmpty());
  1027. }
  1028. }
  1029. @Test
  1030. public void testAddSubrepoWithDirNoGitlinks() throws Exception {
  1031. createNestedRepo("nested-repo");
  1032. // Set DIR_NO_GITLINKS
  1033. StoredConfig config = db.getConfig();
  1034. config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
  1035. ConfigConstants.CONFIG_KEY_DIRNOGITLINKS, true);
  1036. config.save();
  1037. assert (db.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks());
  1038. try (Git git = new Git(db)) {
  1039. git.add().addFilepattern("nested-repo").call();
  1040. assertEquals(
  1041. "[nested-repo/README1.md, mode:100644]" +
  1042. "[nested-repo/README2.md, mode:100644]",
  1043. indexState(0));
  1044. }
  1045. // Turn off DIR_NO_GITLINKS, ensure nested-repo is still treated as
  1046. // a normal directory
  1047. // Set DIR_NO_GITLINKS
  1048. config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
  1049. ConfigConstants.CONFIG_KEY_DIRNOGITLINKS, false);
  1050. config.save();
  1051. writeTrashFile("nested-repo", "README3.md", "content");
  1052. try (Git git = new Git(db)) {
  1053. git.add().addFilepattern("nested-repo").call();
  1054. assertEquals(
  1055. "[nested-repo/README1.md, mode:100644]" +
  1056. "[nested-repo/README2.md, mode:100644]" +
  1057. "[nested-repo/README3.md, mode:100644]",
  1058. indexState(0));
  1059. }
  1060. }
  1061. @Test
  1062. public void testAddGitlinkDoesNotChange() throws Exception {
  1063. createNestedRepo("nested-repo");
  1064. try (Git git = new Git(db)) {
  1065. git.add().addFilepattern("nested-repo").call();
  1066. assertEquals(
  1067. "[nested-repo, mode:160000]",
  1068. indexState(0));
  1069. }
  1070. // Set DIR_NO_GITLINKS
  1071. StoredConfig config = db.getConfig();
  1072. config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
  1073. ConfigConstants.CONFIG_KEY_DIRNOGITLINKS, true);
  1074. config.save();
  1075. assertTrue(
  1076. db.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks());
  1077. try (Git git = new Git(db)) {
  1078. git.add().addFilepattern("nested-repo").call();
  1079. // with gitlinks ignored, we treat this as a normal directory
  1080. assertEquals(
  1081. "[nested-repo/README1.md, mode:100644][nested-repo/README2.md, mode:100644]",
  1082. indexState(0));
  1083. }
  1084. }
  1085. private static DirCacheEntry addEntryToBuilder(String path, File file,
  1086. ObjectInserter newObjectInserter, DirCacheBuilder builder, int stage)
  1087. throws IOException {
  1088. ObjectId id;
  1089. try (FileInputStream inputStream = new FileInputStream(file)) {
  1090. id = newObjectInserter.insert(
  1091. Constants.OBJ_BLOB, file.length(), inputStream);
  1092. }
  1093. DirCacheEntry entry = new DirCacheEntry(path, stage);
  1094. entry.setObjectId(id);
  1095. entry.setFileMode(FileMode.REGULAR_FILE);
  1096. entry.setLastModified(FS.DETECTED.lastModifiedInstant(file));
  1097. entry.setLength((int) file.length());
  1098. builder.add(entry);
  1099. return entry;
  1100. }
  1101. private void assumeUnchanged(String path) throws IOException {
  1102. final DirCache dirc = db.lockDirCache();
  1103. final DirCacheEntry ent = dirc.getEntry(path);
  1104. if (ent != null)
  1105. ent.setAssumeValid(true);
  1106. dirc.write();
  1107. if (!dirc.commit())
  1108. throw new IOException("could not commit");
  1109. }
  1110. private void createNestedRepo(String path) throws IOException {
  1111. File gitLinkDir = new File(db.getWorkTree(), path);
  1112. FileUtils.mkdir(gitLinkDir);
  1113. FileRepositoryBuilder nestedBuilder = new FileRepositoryBuilder();
  1114. nestedBuilder.setWorkTree(gitLinkDir);
  1115. Repository nestedRepo = nestedBuilder.build();
  1116. nestedRepo.create();
  1117. writeTrashFile(path, "README1.md", "content");
  1118. writeTrashFile(path, "README2.md", "content");
  1119. // Commit these changes in the subrepo
  1120. try (Git git = new Git(nestedRepo)) {
  1121. git.add().addFilepattern(".").call();
  1122. git.commit().setMessage("subrepo commit").call();
  1123. } catch (GitAPIException e) {
  1124. throw new RuntimeException(e);
  1125. }
  1126. }
  1127. }