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

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