Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

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