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

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