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.

DirCacheCheckoutTest.java 65KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149
  1. /*
  2. * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
  3. * Copyright (C) 2008-2011, Shawn O. Pearce <spearce@spearce.org>
  4. * Copyright (C) 2008-2011, Robin Rosenberg <robin.rosenberg@dewire.com>
  5. * Copyright (C) 2010, 2020 Christian Halstrick <christian.halstrick@sap.com> and others
  6. *
  7. * This program and the accompanying materials are made available under the
  8. * terms of the Eclipse Distribution License v. 1.0 which is available at
  9. * https://www.eclipse.org/org/documents/edl-v10.php.
  10. *
  11. * SPDX-License-Identifier: BSD-3-Clause
  12. */
  13. package org.eclipse.jgit.lib;
  14. import static java.nio.charset.StandardCharsets.UTF_8;
  15. import static org.junit.Assert.assertArrayEquals;
  16. import static org.junit.Assert.assertEquals;
  17. import static org.junit.Assert.assertFalse;
  18. import static org.junit.Assert.assertNotNull;
  19. import static org.junit.Assert.assertTrue;
  20. import static org.junit.Assert.fail;
  21. import java.io.File;
  22. import java.io.FileInputStream;
  23. import java.io.IOException;
  24. import java.util.Arrays;
  25. import java.util.HashMap;
  26. import java.util.List;
  27. import java.util.Map;
  28. import org.eclipse.jgit.api.CheckoutCommand;
  29. import org.eclipse.jgit.api.CheckoutResult;
  30. import org.eclipse.jgit.api.Git;
  31. import org.eclipse.jgit.api.MergeResult.MergeStatus;
  32. import org.eclipse.jgit.api.ResetCommand.ResetType;
  33. import org.eclipse.jgit.api.Status;
  34. import org.eclipse.jgit.api.errors.GitAPIException;
  35. import org.eclipse.jgit.api.errors.NoFilepatternException;
  36. import org.eclipse.jgit.dircache.DirCache;
  37. import org.eclipse.jgit.dircache.DirCacheCheckout;
  38. import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
  39. import org.eclipse.jgit.dircache.DirCacheEditor;
  40. import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
  41. import org.eclipse.jgit.dircache.DirCacheEntry;
  42. import org.eclipse.jgit.errors.CheckoutConflictException;
  43. import org.eclipse.jgit.errors.CorruptObjectException;
  44. import org.eclipse.jgit.errors.NoWorkTreeException;
  45. import org.eclipse.jgit.events.ChangeRecorder;
  46. import org.eclipse.jgit.events.ListenerHandle;
  47. import org.eclipse.jgit.junit.RepositoryTestCase;
  48. import org.eclipse.jgit.junit.TestRepository;
  49. import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
  50. import org.eclipse.jgit.revwalk.RevCommit;
  51. import org.eclipse.jgit.treewalk.AbstractTreeIterator;
  52. import org.eclipse.jgit.treewalk.FileTreeIterator;
  53. import org.eclipse.jgit.treewalk.TreeWalk;
  54. import org.eclipse.jgit.treewalk.WorkingTreeIterator;
  55. import org.eclipse.jgit.util.FS;
  56. import org.eclipse.jgit.util.FileUtils;
  57. import org.eclipse.jgit.util.StringUtils;
  58. import org.junit.Assume;
  59. import org.junit.Test;
  60. public class DirCacheCheckoutTest extends RepositoryTestCase {
  61. private DirCacheCheckout dco;
  62. protected ObjectId theHead;
  63. protected ObjectId theMerge;
  64. private DirCache dirCache;
  65. private void prescanTwoTrees(ObjectId head, ObjectId merge)
  66. throws IllegalStateException, IOException {
  67. DirCache dc = db.lockDirCache();
  68. try {
  69. dco = new DirCacheCheckout(db, head, dc, merge);
  70. dco.preScanTwoTrees();
  71. } finally {
  72. dc.unlock();
  73. }
  74. }
  75. private void checkout() throws IOException {
  76. DirCache dc = db.lockDirCache();
  77. try {
  78. dco = new DirCacheCheckout(db, theHead, dc, theMerge);
  79. dco.checkout();
  80. } finally {
  81. dc.unlock();
  82. }
  83. }
  84. private List<String> getRemoved() {
  85. return dco.getRemoved();
  86. }
  87. private Map<String, CheckoutMetadata> getUpdated() {
  88. return dco.getUpdated();
  89. }
  90. private List<String> getConflicts() {
  91. return dco.getConflicts();
  92. }
  93. private static HashMap<String, String> mk(String a) {
  94. return mkmap(a, a);
  95. }
  96. private static HashMap<String, String> mkmap(String... args) {
  97. if ((args.length % 2) > 0)
  98. throw new IllegalArgumentException("needs to be pairs");
  99. HashMap<String, String> map = new HashMap<>();
  100. for (int i = 0; i < args.length; i += 2) {
  101. map.put(args[i], args[i + 1]);
  102. }
  103. return map;
  104. }
  105. @Test
  106. public void testResetHard() throws IOException, NoFilepatternException,
  107. GitAPIException {
  108. ChangeRecorder recorder = new ChangeRecorder();
  109. ListenerHandle handle = null;
  110. try (Git git = new Git(db)) {
  111. handle = db.getListenerList()
  112. .addWorkingTreeModifiedListener(recorder);
  113. writeTrashFile("f", "f()");
  114. writeTrashFile("D/g", "g()");
  115. git.add().addFilepattern(".").call();
  116. git.commit().setMessage("inital").call();
  117. assertIndex(mkmap("f", "f()", "D/g", "g()"));
  118. recorder.assertNoEvent();
  119. git.branchCreate().setName("topic").call();
  120. recorder.assertNoEvent();
  121. writeTrashFile("f", "f()\nmaster");
  122. writeTrashFile("D/g", "g()\ng2()");
  123. writeTrashFile("E/h", "h()");
  124. git.add().addFilepattern(".").call();
  125. RevCommit master = git.commit().setMessage("master-1").call();
  126. assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()"));
  127. recorder.assertNoEvent();
  128. checkoutBranch("refs/heads/topic");
  129. assertIndex(mkmap("f", "f()", "D/g", "g()"));
  130. recorder.assertEvent(new String[] { "f", "D/g" },
  131. new String[] { "E/h" });
  132. writeTrashFile("f", "f()\nside");
  133. assertTrue(new File(db.getWorkTree(), "D/g").delete());
  134. writeTrashFile("G/i", "i()");
  135. git.add().addFilepattern(".").call();
  136. git.add().addFilepattern(".").setUpdate(true).call();
  137. RevCommit topic = git.commit().setMessage("topic-1").call();
  138. assertIndex(mkmap("f", "f()\nside", "G/i", "i()"));
  139. recorder.assertNoEvent();
  140. writeTrashFile("untracked", "untracked");
  141. resetHard(master);
  142. assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()"));
  143. recorder.assertEvent(new String[] { "f", "D/g", "E/h" },
  144. new String[] { "G", "G/i" });
  145. resetHard(topic);
  146. assertIndex(mkmap("f", "f()\nside", "G/i", "i()"));
  147. assertWorkDir(mkmap("f", "f()\nside", "G/i", "i()", "untracked",
  148. "untracked"));
  149. recorder.assertEvent(new String[] { "f", "G/i" },
  150. new String[] { "D", "D/g", "E", "E/h" });
  151. assertEquals(MergeStatus.CONFLICTING, git.merge().include(master)
  152. .call().getMergeStatus());
  153. assertEquals(
  154. "[D/g, mode:100644, stage:1][D/g, mode:100644, stage:3][E/h, mode:100644][G/i, mode:100644][f, mode:100644, stage:1][f, mode:100644, stage:2][f, mode:100644, stage:3]",
  155. indexState(0));
  156. recorder.assertEvent(new String[] { "f", "D/g", "E/h" },
  157. ChangeRecorder.EMPTY);
  158. resetHard(master);
  159. assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()"));
  160. assertWorkDir(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h",
  161. "h()", "untracked", "untracked"));
  162. recorder.assertEvent(new String[] { "f", "D/g" },
  163. new String[] { "G", "G/i" });
  164. } finally {
  165. if (handle != null) {
  166. handle.remove();
  167. }
  168. }
  169. }
  170. /**
  171. * Reset hard from unclean condition.
  172. * <p>
  173. * WorkDir: Empty <br>
  174. * Index: f/g <br>
  175. * Merge: x
  176. *
  177. * @throws Exception
  178. */
  179. @Test
  180. public void testResetHardFromIndexEntryWithoutFileToTreeWithoutFile()
  181. throws Exception {
  182. ChangeRecorder recorder = new ChangeRecorder();
  183. ListenerHandle handle = null;
  184. try (Git git = new Git(db)) {
  185. handle = db.getListenerList()
  186. .addWorkingTreeModifiedListener(recorder);
  187. writeTrashFile("x", "x");
  188. git.add().addFilepattern("x").call();
  189. RevCommit id1 = git.commit().setMessage("c1").call();
  190. writeTrashFile("f/g", "f/g");
  191. git.rm().addFilepattern("x").call();
  192. recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { "x" });
  193. git.add().addFilepattern("f/g").call();
  194. git.commit().setMessage("c2").call();
  195. deleteTrashFile("f/g");
  196. deleteTrashFile("f");
  197. // The actual test
  198. git.reset().setMode(ResetType.HARD).setRef(id1.getName()).call();
  199. assertIndex(mkmap("x", "x"));
  200. recorder.assertEvent(new String[] { "x" }, ChangeRecorder.EMPTY);
  201. } finally {
  202. if (handle != null) {
  203. handle.remove();
  204. }
  205. }
  206. }
  207. /**
  208. * Test first checkout in a repo
  209. *
  210. * @throws Exception
  211. */
  212. @Test
  213. public void testInitialCheckout() throws Exception {
  214. ChangeRecorder recorder = new ChangeRecorder();
  215. ListenerHandle handle = null;
  216. try (Git git = new Git(db);
  217. TestRepository<Repository> db_t = new TestRepository<>(db)) {
  218. handle = db.getListenerList()
  219. .addWorkingTreeModifiedListener(recorder);
  220. BranchBuilder master = db_t.branch("master");
  221. master.commit().add("f", "1").message("m0").create();
  222. assertFalse(new File(db.getWorkTree(), "f").exists());
  223. git.checkout().setName("master").call();
  224. assertTrue(new File(db.getWorkTree(), "f").exists());
  225. recorder.assertEvent(new String[] { "f" }, ChangeRecorder.EMPTY);
  226. } finally {
  227. if (handle != null) {
  228. handle.remove();
  229. }
  230. }
  231. }
  232. private void checkoutLineEndings(String inIndex, String expected,
  233. String attributes) throws Exception {
  234. try (Git git = new Git(db);
  235. TestRepository<Repository> db_t = new TestRepository<>(db)) {
  236. BranchBuilder master = db_t.branch("master");
  237. master.commit().add("f", inIndex).message("m0").create();
  238. if (!StringUtils.isEmptyOrNull(attributes)) {
  239. master.commit().add(".gitattributes", attributes)
  240. .message("attributes").create();
  241. }
  242. File f = new File(db.getWorkTree(), "f");
  243. assertFalse(f.exists());
  244. git.checkout().setName("master").call();
  245. assertTrue(f.exists());
  246. checkFile(f, expected);
  247. }
  248. }
  249. @Test
  250. public void testCheckoutWithCRLF() throws Exception {
  251. checkoutLineEndings("first line\r\nsecond line\r\n",
  252. "first line\r\nsecond line\r\n", null);
  253. }
  254. @Test
  255. public void testCheckoutWithCRLFAuto() throws Exception {
  256. checkoutLineEndings("first line\r\nsecond line\r\n",
  257. "first line\r\nsecond line\r\n", "f text=auto");
  258. }
  259. @Test
  260. public void testCheckoutWithCRLFAutoEolLf() throws Exception {
  261. checkoutLineEndings("first line\r\nsecond line\r\n",
  262. "first line\r\nsecond line\r\n", "f text=auto eol=lf");
  263. }
  264. @Test
  265. public void testCheckoutWithCRLFAutoEolNative() throws Exception {
  266. checkoutLineEndings("first line\r\nsecond line\r\n",
  267. "first line\r\nsecond line\r\n", "f text=auto eol=native");
  268. }
  269. @Test
  270. public void testCheckoutWithCRLFAutoEolCrLf() throws Exception {
  271. checkoutLineEndings("first line\r\nsecond line\r\n",
  272. "first line\r\nsecond line\r\n", "f text=auto eol=crlf");
  273. }
  274. @Test
  275. public void testCheckoutWithLF() throws Exception {
  276. checkoutLineEndings("first line\nsecond line\n",
  277. "first line\nsecond line\n", null);
  278. }
  279. @Test
  280. public void testCheckoutWithLFAuto() throws Exception {
  281. checkoutLineEndings("first line\nsecond line\n",
  282. "first line\nsecond line\n", "f text=auto");
  283. }
  284. @Test
  285. public void testCheckoutWithLFAutoEolLf() throws Exception {
  286. checkoutLineEndings("first line\nsecond line\n",
  287. "first line\nsecond line\n", "f text=auto eol=lf");
  288. }
  289. @Test
  290. public void testCheckoutWithLFAutoEolNative() throws Exception {
  291. checkoutLineEndings(
  292. "first line\nsecond line\n", "first line\nsecond line\n"
  293. .replaceAll("\n", System.lineSeparator()),
  294. "f text=auto eol=native");
  295. }
  296. @Test
  297. public void testCheckoutWithLFAutoEolCrLf() throws Exception {
  298. checkoutLineEndings("first line\nsecond line\n",
  299. "first line\r\nsecond line\r\n", "f text=auto eol=crlf");
  300. }
  301. private DirCacheCheckout resetHard(RevCommit commit)
  302. throws NoWorkTreeException,
  303. CorruptObjectException, IOException {
  304. DirCacheCheckout dc;
  305. dc = new DirCacheCheckout(db, null, db.lockDirCache(),
  306. commit.getTree());
  307. dc.setFailOnConflict(true);
  308. assertTrue(dc.checkout());
  309. return dc;
  310. }
  311. private void assertIndex(HashMap<String, String> i)
  312. throws CorruptObjectException, IOException {
  313. String expectedValue;
  314. String path;
  315. DirCache read = DirCache.read(db.getIndexFile(), db.getFS());
  316. assertEquals("Index has not the right size.", i.size(),
  317. read.getEntryCount());
  318. for (int j = 0; j < read.getEntryCount(); j++) {
  319. path = read.getEntry(j).getPathString();
  320. expectedValue = i.get(path);
  321. assertNotNull("found unexpected entry for path " + path
  322. + " in index", expectedValue);
  323. assertTrue("unexpected content for path " + path
  324. + " in index. Expected: <" + expectedValue + ">",
  325. Arrays.equals(db.open(read.getEntry(j).getObjectId())
  326. .getCachedBytes(), i.get(path).getBytes(UTF_8)));
  327. }
  328. }
  329. @Test
  330. public void testRules1thru3_NoIndexEntry() throws IOException {
  331. ObjectId head = buildTree(mk("foo"));
  332. ObjectId merge = db.newObjectInserter().insert(Constants.OBJ_TREE,
  333. new byte[0]);
  334. prescanTwoTrees(head, merge);
  335. assertTrue(getRemoved().contains("foo"));
  336. prescanTwoTrees(merge, head);
  337. assertTrue(getUpdated().containsKey("foo"));
  338. merge = buildTree(mkmap("foo", "a"));
  339. prescanTwoTrees(head, merge);
  340. assertConflict("foo");
  341. }
  342. void setupCase(HashMap<String, String> headEntries, HashMap<String, String> mergeEntries, HashMap<String, String> indexEntries) throws IOException {
  343. theHead = buildTree(headEntries);
  344. theMerge = buildTree(mergeEntries);
  345. buildIndex(indexEntries);
  346. }
  347. private void buildIndex(HashMap<String, String> indexEntries) throws IOException {
  348. dirCache = new DirCache(db.getIndexFile(), db.getFS());
  349. if (indexEntries != null) {
  350. assertTrue(dirCache.lock());
  351. DirCacheEditor editor = dirCache.editor();
  352. for (java.util.Map.Entry<String,String> e : indexEntries.entrySet()) {
  353. writeTrashFile(e.getKey(), e.getValue());
  354. ObjectId id;
  355. try (ObjectInserter inserter = db.newObjectInserter()) {
  356. id = inserter.insert(Constants.OBJ_BLOB,
  357. Constants.encode(e.getValue()));
  358. }
  359. editor.add(new DirCacheEditor.DeletePath(e.getKey()));
  360. editor.add(new DirCacheEditor.PathEdit(e.getKey()) {
  361. @Override
  362. public void apply(DirCacheEntry ent) {
  363. ent.setFileMode(FileMode.REGULAR_FILE);
  364. ent.setObjectId(id);
  365. ent.setUpdateNeeded(false);
  366. }
  367. });
  368. }
  369. assertTrue(editor.commit());
  370. }
  371. }
  372. static final class AddEdit extends PathEdit {
  373. private final ObjectId data;
  374. private final long length;
  375. public AddEdit(String entryPath, ObjectId data, long length) {
  376. super(entryPath);
  377. this.data = data;
  378. this.length = length;
  379. }
  380. @Override
  381. public void apply(DirCacheEntry ent) {
  382. ent.setFileMode(FileMode.REGULAR_FILE);
  383. ent.setLength(length);
  384. ent.setObjectId(data);
  385. }
  386. }
  387. private ObjectId buildTree(HashMap<String, String> headEntries)
  388. throws IOException {
  389. DirCache lockDirCache = DirCache.newInCore();
  390. // assertTrue(lockDirCache.lock());
  391. DirCacheEditor editor = lockDirCache.editor();
  392. if (headEntries != null) {
  393. for (java.util.Map.Entry<String, String> e : headEntries.entrySet()) {
  394. AddEdit addEdit = new AddEdit(e.getKey(),
  395. genSha1(e.getValue()), e.getValue().length());
  396. editor.add(addEdit);
  397. }
  398. }
  399. editor.finish();
  400. return lockDirCache.writeTree(db.newObjectInserter());
  401. }
  402. ObjectId genSha1(String data) {
  403. try (ObjectInserter w = db.newObjectInserter()) {
  404. ObjectId id = w.insert(Constants.OBJ_BLOB, data.getBytes(UTF_8));
  405. w.flush();
  406. return id;
  407. } catch (IOException e) {
  408. fail(e.toString());
  409. }
  410. return null;
  411. }
  412. protected void go() throws IllegalStateException, IOException {
  413. prescanTwoTrees(theHead, theMerge);
  414. }
  415. @Test
  416. public void testRules4thru13_IndexEntryNotInHead() throws IOException {
  417. // rules 4 and 5
  418. HashMap<String, String> idxMap;
  419. idxMap = new HashMap<>();
  420. idxMap.put("foo", "foo");
  421. setupCase(null, null, idxMap);
  422. go();
  423. assertTrue(getUpdated().isEmpty());
  424. assertTrue(getRemoved().isEmpty());
  425. assertTrue(getConflicts().isEmpty());
  426. // rules 6 and 7
  427. idxMap = new HashMap<>();
  428. idxMap.put("foo", "foo");
  429. setupCase(null, idxMap, idxMap);
  430. go();
  431. assertAllEmpty();
  432. // rules 8 and 9
  433. HashMap<String, String> mergeMap;
  434. mergeMap = new HashMap<>();
  435. mergeMap.put("foo", "merge");
  436. setupCase(null, mergeMap, idxMap);
  437. go();
  438. assertTrue(getUpdated().isEmpty());
  439. assertTrue(getRemoved().isEmpty());
  440. assertTrue(getConflicts().contains("foo"));
  441. // rule 10
  442. HashMap<String, String> headMap = new HashMap<>();
  443. headMap.put("foo", "foo");
  444. setupCase(headMap, null, idxMap);
  445. go();
  446. assertTrue(getRemoved().contains("foo"));
  447. assertTrue(getUpdated().isEmpty());
  448. assertTrue(getConflicts().isEmpty());
  449. // rule 11
  450. setupCase(headMap, null, idxMap);
  451. assertTrue(new File(trash, "foo").delete());
  452. writeTrashFile("foo", "bar");
  453. db.readDirCache().getEntry(0).setUpdateNeeded(true);
  454. go();
  455. assertTrue(getRemoved().isEmpty());
  456. assertTrue(getUpdated().isEmpty());
  457. assertTrue(getConflicts().contains("foo"));
  458. // rule 12 & 13
  459. headMap.put("foo", "head");
  460. setupCase(headMap, null, idxMap);
  461. go();
  462. assertTrue(getRemoved().isEmpty());
  463. assertTrue(getUpdated().isEmpty());
  464. assertTrue(getConflicts().contains("foo"));
  465. // rules 14 & 15
  466. setupCase(headMap, headMap, idxMap);
  467. go();
  468. assertAllEmpty();
  469. // rules 16 & 17
  470. setupCase(headMap, mergeMap, idxMap); go();
  471. assertTrue(getConflicts().contains("foo"));
  472. // rules 18 & 19
  473. setupCase(headMap, idxMap, idxMap); go();
  474. assertAllEmpty();
  475. // rule 20
  476. setupCase(idxMap, mergeMap, idxMap); go();
  477. assertTrue(getUpdated().containsKey("foo"));
  478. // rules 21
  479. setupCase(idxMap, mergeMap, idxMap);
  480. assertTrue(new File(trash, "foo").delete());
  481. writeTrashFile("foo", "bar");
  482. db.readDirCache().getEntry(0).setUpdateNeeded(true);
  483. go();
  484. assertTrue(getConflicts().contains("foo"));
  485. }
  486. private void assertAllEmpty() {
  487. assertTrue(getRemoved().isEmpty());
  488. assertTrue(getUpdated().isEmpty());
  489. assertTrue(getConflicts().isEmpty());
  490. }
  491. /*-
  492. * Directory/File Conflict cases:
  493. * It's entirely possible that in practice a number of these may be equivalent
  494. * to the cases described in git-read-tree.txt. As long as it does the right thing,
  495. * that's all I care about. These are basically reverse-engineered from
  496. * what git currently does. If there are tests for these in git, it's kind of
  497. * hard to track them all down...
  498. *
  499. * H I M Clean H==M H==I I==M Result
  500. * ------------------------------------------------------------------
  501. *1 D D F Y N Y N Update
  502. *2 D D F N N Y N Conflict
  503. *3 D F D Y N N Keep
  504. *4 D F D N N N Conflict
  505. *5 D F F Y N N Y Keep
  506. *5b D F F Y N N N Conflict
  507. *6 D F F N N N Y Keep
  508. *6b D F F N N N N Conflict
  509. *7 F D F Y Y N N Update
  510. *8 F D F N Y N N Conflict
  511. *9 F D F Y N N N Update
  512. *10 F D D N N Y Keep
  513. *11 F D D N N N Conflict
  514. *12 F F D Y N Y N Update
  515. *13 F F D N N Y N Conflict
  516. *14 F F D N N N Conflict
  517. *15 0 F D N N N Conflict
  518. *16 0 D F Y N N N Update
  519. *17 0 D F N N N Conflict
  520. *18 F 0 D Update
  521. *19 D 0 F Update
  522. */
  523. @Test
  524. public void testDirectoryFileSimple() throws IOException {
  525. ObjectId treeDF = buildTree(mkmap("DF", "DF"));
  526. ObjectId treeDFDF = buildTree(mkmap("DF/DF", "DF/DF"));
  527. buildIndex(mkmap("DF", "DF"));
  528. prescanTwoTrees(treeDF, treeDFDF);
  529. assertTrue(getRemoved().contains("DF"));
  530. assertTrue(getUpdated().containsKey("DF/DF"));
  531. recursiveDelete(new File(trash, "DF"));
  532. buildIndex(mkmap("DF/DF", "DF/DF"));
  533. prescanTwoTrees(treeDFDF, treeDF);
  534. assertTrue(getRemoved().contains("DF/DF"));
  535. assertTrue(getUpdated().containsKey("DF"));
  536. }
  537. @Test
  538. public void testDirectoryFileConflicts_1() throws Exception {
  539. // 1
  540. doit(mk("DF/DF"), mk("DF"), mk("DF/DF"));
  541. assertNoConflicts();
  542. assertUpdated("DF");
  543. assertRemoved("DF/DF");
  544. }
  545. @Test
  546. public void testDirectoryFileConflicts_2() throws Exception {
  547. // 2
  548. setupCase(mk("DF/DF"), mk("DF"), mk("DF/DF"));
  549. writeTrashFile("DF/DF", "different");
  550. go();
  551. assertConflict("DF/DF");
  552. }
  553. @Test
  554. public void testDirectoryFileConflicts_3() throws Exception {
  555. // 3
  556. doit(mk("DF/DF"), mk("DF/DF"), mk("DF"));
  557. assertNoConflicts();
  558. }
  559. @Test
  560. public void testDirectoryFileConflicts_4() throws Exception {
  561. // 4 (basically same as 3, just with H and M different)
  562. doit(mk("DF/DF"), mkmap("DF/DF", "foo"), mk("DF"));
  563. assertConflict("DF/DF");
  564. }
  565. @Test
  566. public void testDirectoryFileConflicts_5() throws Exception {
  567. // 5
  568. doit(mk("DF/DF"), mk("DF"), mk("DF"));
  569. assertRemoved("DF/DF");
  570. assertEquals(0, dco.getConflicts().size());
  571. assertEquals(0, dco.getUpdated().size());
  572. }
  573. @Test
  574. public void testDirectoryFileConflicts_5b() throws Exception {
  575. // 5
  576. doit(mk("DF/DF"), mkmap("DF", "different"), mk("DF"));
  577. assertRemoved("DF/DF");
  578. assertConflict("DF");
  579. assertEquals(0, dco.getUpdated().size());
  580. }
  581. @Test
  582. public void testDirectoryFileConflicts_6() throws Exception {
  583. // 6
  584. setupCase(mk("DF/DF"), mk("DF"), mk("DF"));
  585. writeTrashFile("DF", "different");
  586. go();
  587. assertRemoved("DF/DF");
  588. assertEquals(0, dco.getConflicts().size());
  589. assertEquals(0, dco.getUpdated().size());
  590. }
  591. @Test
  592. public void testDirectoryFileConflicts_6b() throws Exception {
  593. // 6
  594. setupCase(mk("DF/DF"), mk("DF"), mkmap("DF", "different"));
  595. writeTrashFile("DF", "again different");
  596. go();
  597. assertRemoved("DF/DF");
  598. assertConflict("DF");
  599. assertEquals(0, dco.getUpdated().size());
  600. }
  601. @Test
  602. public void testDirectoryFileConflicts_7() throws Exception {
  603. // 7
  604. doit(mk("DF"), mk("DF"), mk("DF/DF"));
  605. assertUpdated("DF");
  606. assertRemoved("DF/DF");
  607. cleanUpDF();
  608. setupCase(mk("DF/DF"), mk("DF/DF"), mk("DF/DF/DF/DF/DF"));
  609. go();
  610. assertRemoved("DF/DF/DF/DF/DF");
  611. assertUpdated("DF/DF");
  612. cleanUpDF();
  613. setupCase(mk("DF/DF"), mk("DF/DF"), mk("DF/DF/DF/DF/DF"));
  614. writeTrashFile("DF/DF/DF/DF/DF", "diff");
  615. go();
  616. assertConflict("DF/DF/DF/DF/DF");
  617. // assertUpdated("DF/DF");
  618. // Why do we expect an update on DF/DF. H==M,
  619. // H&M are files and index contains a dir, index
  620. // is dirty: that case is not in the table but
  621. // we cannot update DF/DF to a file, this would
  622. // require that we delete DF/DF/DF/DF/DF in workdir
  623. // throwing away unsaved contents.
  624. // This test would fail in DirCacheCheckoutTests.
  625. }
  626. @Test
  627. public void testDirectoryFileConflicts_8() throws Exception {
  628. // 8
  629. setupCase(mk("DF"), mk("DF"), mk("DF/DF"));
  630. recursiveDelete(new File(db.getWorkTree(), "DF"));
  631. writeTrashFile("DF", "xy");
  632. go();
  633. assertConflict("DF/DF");
  634. }
  635. @Test
  636. public void testDirectoryFileConflicts_9() throws Exception {
  637. // 9
  638. doit(mkmap("DF", "QP"), mkmap("DF", "QP"), mkmap("DF/DF", "DF/DF"));
  639. assertRemoved("DF/DF");
  640. assertUpdated("DF");
  641. }
  642. @Test
  643. public void testDirectoryFileConflicts_10() throws Exception {
  644. // 10
  645. cleanUpDF();
  646. doit(mk("DF"), mk("DF/DF"), mk("DF/DF"));
  647. assertNoConflicts();
  648. }
  649. @Test
  650. public void testDirectoryFileConflicts_11() throws Exception {
  651. // 11
  652. doit(mk("DF"), mk("DF/DF"), mkmap("DF/DF", "asdf"));
  653. assertConflict("DF/DF");
  654. }
  655. @Test
  656. public void testDirectoryFileConflicts_12() throws Exception {
  657. // 12
  658. cleanUpDF();
  659. doit(mk("DF"), mk("DF/DF"), mk("DF"));
  660. assertRemoved("DF");
  661. assertUpdated("DF/DF");
  662. }
  663. @Test
  664. public void testDirectoryFileConflicts_13() throws Exception {
  665. // 13
  666. cleanUpDF();
  667. setupCase(mk("DF"), mk("DF/DF"), mk("DF"));
  668. writeTrashFile("DF", "asdfsdf");
  669. go();
  670. assertConflict("DF");
  671. assertUpdated("DF/DF");
  672. }
  673. @Test
  674. public void testDirectoryFileConflicts_14() throws Exception {
  675. // 14
  676. cleanUpDF();
  677. doit(mk("DF"), mk("DF/DF"), mkmap("DF", "Foo"));
  678. assertConflict("DF");
  679. assertUpdated("DF/DF");
  680. }
  681. @Test
  682. public void testDirectoryFileConflicts_15() throws Exception {
  683. // 15
  684. doit(mkmap(), mk("DF/DF"), mk("DF"));
  685. // This test would fail in DirCacheCheckoutTests. I think this test is wrong,
  686. // it should check for conflicts according to rule 15
  687. // assertRemoved("DF");
  688. assertUpdated("DF/DF");
  689. }
  690. @Test
  691. public void testDirectoryFileConflicts_15b() throws Exception {
  692. // 15, take 2, just to check multi-leveled
  693. doit(mkmap(), mk("DF/DF/DF/DF"), mk("DF"));
  694. // I think this test is wrong, it should
  695. // check for conflicts according to rule 15
  696. // This test would fail in DirCacheCheckouts
  697. // assertRemoved("DF");
  698. assertUpdated("DF/DF/DF/DF");
  699. }
  700. @Test
  701. public void testDirectoryFileConflicts_16() throws Exception {
  702. // 16
  703. cleanUpDF();
  704. doit(mkmap(), mk("DF"), mk("DF/DF/DF"));
  705. assertRemoved("DF/DF/DF");
  706. assertUpdated("DF");
  707. }
  708. @Test
  709. public void testDirectoryFileConflicts_17() throws Exception {
  710. // 17
  711. cleanUpDF();
  712. setupCase(mkmap(), mk("DF"), mk("DF/DF/DF"));
  713. writeTrashFile("DF/DF/DF", "asdf");
  714. go();
  715. assertConflict("DF/DF/DF");
  716. // Why do we expect an update on DF. If we really update
  717. // DF and update also the working tree we would have to
  718. // overwrite a dirty file in the work-tree DF/DF/DF
  719. // This test would fail in DirCacheCheckout
  720. // assertUpdated("DF");
  721. }
  722. @Test
  723. public void testDirectoryFileConflicts_18() throws Exception {
  724. // 18
  725. cleanUpDF();
  726. doit(mk("DF/DF"), mk("DF/DF/DF/DF"), null);
  727. assertRemoved("DF/DF");
  728. assertUpdated("DF/DF/DF/DF");
  729. }
  730. @Test
  731. public void testDirectoryFileConflicts_19() throws Exception {
  732. // 19
  733. cleanUpDF();
  734. doit(mk("DF/DF/DF/DF"), mk("DF/DF/DF"), null);
  735. assertRemoved("DF/DF/DF/DF");
  736. assertUpdated("DF/DF/DF");
  737. }
  738. protected void cleanUpDF() throws Exception {
  739. tearDown();
  740. setUp();
  741. recursiveDelete(new File(trash, "DF"));
  742. }
  743. protected void assertConflict(String s) {
  744. assertTrue(getConflicts().contains(s));
  745. }
  746. protected void assertUpdated(String s) {
  747. assertTrue(getUpdated().containsKey(s));
  748. }
  749. protected void assertRemoved(String s) {
  750. assertTrue(getRemoved().contains(s));
  751. }
  752. protected void assertNoConflicts() {
  753. assertTrue(getConflicts().isEmpty());
  754. }
  755. protected void doit(HashMap<String, String> h, HashMap<String, String> m, HashMap<String, String> i)
  756. throws IOException {
  757. setupCase(h, m, i);
  758. go();
  759. }
  760. @Test
  761. public void testUntrackedConflicts() throws IOException {
  762. setupCase(null, mk("foo"), null);
  763. writeTrashFile("foo", "foo");
  764. go();
  765. // test that we don't overwrite untracked files when there is a HEAD
  766. recursiveDelete(new File(trash, "foo"));
  767. setupCase(mk("other"), mkmap("other", "other", "foo", "foo"),
  768. mk("other"));
  769. writeTrashFile("foo", "bar");
  770. try {
  771. checkout();
  772. fail("didn't get the expected exception");
  773. } catch (CheckoutConflictException e) {
  774. assertConflict("foo");
  775. assertEquals("foo", e.getConflictingFiles()[0]);
  776. assertWorkDir(mkmap("foo", "bar", "other", "other"));
  777. assertIndex(mk("other"));
  778. }
  779. // test that we don't overwrite untracked files when there is no HEAD
  780. recursiveDelete(new File(trash, "other"));
  781. recursiveDelete(new File(trash, "foo"));
  782. setupCase(null, mk("foo"), null);
  783. writeTrashFile("foo", "bar");
  784. try {
  785. checkout();
  786. fail("didn't get the expected exception");
  787. } catch (CheckoutConflictException e) {
  788. assertConflict("foo");
  789. assertWorkDir(mkmap("foo", "bar"));
  790. assertIndex(mkmap("other", "other"));
  791. }
  792. // TODO: Why should we expect conflicts here?
  793. // H and M are empty and according to rule #5 of
  794. // the carry-over rules a dirty index is no reason
  795. // for a conflict. (I also feel it should be a
  796. // conflict because we are going to overwrite
  797. // unsaved content in the working tree
  798. // This test would fail in DirCacheCheckoutTest
  799. // assertConflict("foo");
  800. recursiveDelete(new File(trash, "foo"));
  801. recursiveDelete(new File(trash, "other"));
  802. setupCase(null, mk("foo"), null);
  803. writeTrashFile("foo/bar/baz", "");
  804. writeTrashFile("foo/blahblah", "");
  805. go();
  806. assertConflict("foo");
  807. assertConflict("foo/bar/baz");
  808. assertConflict("foo/blahblah");
  809. recursiveDelete(new File(trash, "foo"));
  810. setupCase(mkmap("foo/bar", "", "foo/baz", ""),
  811. mk("foo"), mkmap("foo/bar", "", "foo/baz", ""));
  812. assertTrue(new File(trash, "foo/bar").exists());
  813. go();
  814. assertNoConflicts();
  815. }
  816. @Test
  817. public void testCloseNameConflictsX0() throws IOException {
  818. setupCase(mkmap("a/a", "a/a-c"), mkmap("a/a","a/a", "b.b/b.b","b.b/b.bs"), mkmap("a/a", "a/a-c") );
  819. checkout();
  820. assertIndex(mkmap("a/a", "a/a", "b.b/b.b", "b.b/b.bs"));
  821. assertWorkDir(mkmap("a/a", "a/a", "b.b/b.b", "b.b/b.bs"));
  822. go();
  823. assertIndex(mkmap("a/a", "a/a", "b.b/b.b", "b.b/b.bs"));
  824. assertWorkDir(mkmap("a/a", "a/a", "b.b/b.b", "b.b/b.bs"));
  825. assertNoConflicts();
  826. }
  827. @Test
  828. public void testCloseNameConflicts1() throws IOException {
  829. setupCase(mkmap("a/a", "a/a-c"), mkmap("a/a","a/a", "a.a/a.a","a.a/a.a"), mkmap("a/a", "a/a-c") );
  830. checkout();
  831. assertIndex(mkmap("a/a", "a/a", "a.a/a.a", "a.a/a.a"));
  832. assertWorkDir(mkmap("a/a", "a/a", "a.a/a.a", "a.a/a.a"));
  833. go();
  834. assertIndex(mkmap("a/a", "a/a", "a.a/a.a", "a.a/a.a"));
  835. assertWorkDir(mkmap("a/a", "a/a", "a.a/a.a", "a.a/a.a"));
  836. assertNoConflicts();
  837. }
  838. @Test
  839. public void testCheckoutHierarchy() throws IOException {
  840. setupCase(
  841. mkmap("a", "a", "b/c", "b/c", "d", "d", "e/f", "e/f", "e/g",
  842. "e/g"),
  843. mkmap("a", "a2", "b/c", "b/c", "d", "d", "e/f", "e/f", "e/g",
  844. "e/g2"),
  845. mkmap("a", "a", "b/c", "b/c", "d", "d", "e/f", "e/f", "e/g",
  846. "e/g3"));
  847. try {
  848. checkout();
  849. fail("did not throw CheckoutConflictException");
  850. } catch (CheckoutConflictException e) {
  851. assertWorkDir(mkmap("a", "a", "b/c", "b/c", "d", "d", "e/f",
  852. "e/f", "e/g", "e/g3"));
  853. assertConflict("e/g");
  854. assertEquals("e/g", e.getConflictingFiles()[0]);
  855. }
  856. }
  857. @Test
  858. public void testCheckoutOutChanges() throws IOException {
  859. setupCase(mk("foo"), mk("foo/bar"), mk("foo"));
  860. checkout();
  861. assertIndex(mk("foo/bar"));
  862. assertWorkDir(mk("foo/bar"));
  863. assertFalse(new File(trash, "foo").isFile());
  864. assertTrue(new File(trash, "foo/bar").isFile());
  865. recursiveDelete(new File(trash, "foo"));
  866. assertWorkDir(mkmap());
  867. setupCase(mk("foo/bar"), mk("foo"), mk("foo/bar"));
  868. checkout();
  869. assertIndex(mk("foo"));
  870. assertWorkDir(mk("foo"));
  871. assertFalse(new File(trash, "foo/bar").isFile());
  872. assertTrue(new File(trash, "foo").isFile());
  873. setupCase(mk("foo"), mkmap("foo", "qux"), mkmap("foo", "bar"));
  874. assertIndex(mkmap("foo", "bar"));
  875. assertWorkDir(mkmap("foo", "bar"));
  876. try {
  877. checkout();
  878. fail("did not throw exception");
  879. } catch (CheckoutConflictException e) {
  880. assertIndex(mkmap("foo", "bar"));
  881. assertWorkDir(mkmap("foo", "bar"));
  882. }
  883. }
  884. @Test
  885. public void testCheckoutChangeLinkToEmptyDir() throws Exception {
  886. Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
  887. String fname = "was_file";
  888. ChangeRecorder recorder = new ChangeRecorder();
  889. ListenerHandle handle = null;
  890. try (Git git = new Git(db)) {
  891. handle = db.getListenerList()
  892. .addWorkingTreeModifiedListener(recorder);
  893. // Add a file
  894. writeTrashFile(fname, "a");
  895. git.add().addFilepattern(fname).call();
  896. // Add a link to file
  897. String linkName = "link";
  898. File link = writeLink(linkName, fname).toFile();
  899. git.add().addFilepattern(linkName).call();
  900. git.commit().setMessage("Added file and link").call();
  901. assertWorkDir(mkmap(linkName, "a", fname, "a"));
  902. // replace link with empty directory
  903. FileUtils.delete(link);
  904. FileUtils.mkdir(link);
  905. assertTrue("Link must be a directory now", link.isDirectory());
  906. // modify file
  907. writeTrashFile(fname, "b");
  908. assertWorkDir(mkmap(fname, "b", linkName, "/"));
  909. recorder.assertNoEvent();
  910. // revert both paths to HEAD state
  911. git.checkout().setStartPoint(Constants.HEAD).addPath(fname)
  912. .addPath(linkName).call();
  913. assertWorkDir(mkmap(fname, "a", linkName, "a"));
  914. recorder.assertEvent(new String[] { fname, linkName },
  915. ChangeRecorder.EMPTY);
  916. Status st = git.status().call();
  917. assertTrue(st.isClean());
  918. } finally {
  919. if (handle != null) {
  920. handle.remove();
  921. }
  922. }
  923. }
  924. @Test
  925. public void testCheckoutChangeLinkToEmptyDirs() throws Exception {
  926. Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
  927. String fname = "was_file";
  928. ChangeRecorder recorder = new ChangeRecorder();
  929. ListenerHandle handle = null;
  930. try (Git git = new Git(db)) {
  931. handle = db.getListenerList()
  932. .addWorkingTreeModifiedListener(recorder);
  933. // Add a file
  934. writeTrashFile(fname, "a");
  935. git.add().addFilepattern(fname).call();
  936. // Add a link to file
  937. String linkName = "link";
  938. File link = writeLink(linkName, fname).toFile();
  939. git.add().addFilepattern(linkName).call();
  940. git.commit().setMessage("Added file and link").call();
  941. assertWorkDir(mkmap(linkName, "a", fname, "a"));
  942. // replace link with directory containing only directories, no files
  943. FileUtils.delete(link);
  944. FileUtils.mkdirs(new File(link, "dummyDir"));
  945. assertTrue("Link must be a directory now", link.isDirectory());
  946. assertFalse("Must not delete non empty directory", link.delete());
  947. // modify file
  948. writeTrashFile(fname, "b");
  949. assertWorkDir(mkmap(fname, "b", linkName + "/dummyDir", "/"));
  950. recorder.assertNoEvent();
  951. // revert both paths to HEAD state
  952. git.checkout().setStartPoint(Constants.HEAD).addPath(fname)
  953. .addPath(linkName).call();
  954. assertWorkDir(mkmap(fname, "a", linkName, "a"));
  955. recorder.assertEvent(new String[] { fname, linkName },
  956. ChangeRecorder.EMPTY);
  957. Status st = git.status().call();
  958. assertTrue(st.isClean());
  959. } finally {
  960. if (handle != null) {
  961. handle.remove();
  962. }
  963. }
  964. }
  965. @Test
  966. public void testCheckoutChangeLinkToNonEmptyDirs() throws Exception {
  967. Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
  968. String fname = "file";
  969. ChangeRecorder recorder = new ChangeRecorder();
  970. ListenerHandle handle = null;
  971. try (Git git = new Git(db)) {
  972. handle = db.getListenerList()
  973. .addWorkingTreeModifiedListener(recorder);
  974. // Add a file
  975. writeTrashFile(fname, "a");
  976. git.add().addFilepattern(fname).call();
  977. // Add a link to file
  978. String linkName = "link";
  979. File link = writeLink(linkName, fname).toFile();
  980. git.add().addFilepattern(linkName).call();
  981. git.commit().setMessage("Added file and link").call();
  982. assertWorkDir(mkmap(linkName, "a", fname, "a"));
  983. // replace link with directory containing only directories, no files
  984. FileUtils.delete(link);
  985. // create but do not add a file in the new directory to the index
  986. writeTrashFile(linkName + "/dir1", "file1", "c");
  987. // create but do not add a file in the new directory to the index
  988. writeTrashFile(linkName + "/dir2", "file2", "d");
  989. assertTrue("File must be a directory now", link.isDirectory());
  990. assertFalse("Must not delete non empty directory", link.delete());
  991. // 2 extra files are created
  992. assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
  993. linkName + "/dir2/file2", "d"));
  994. recorder.assertNoEvent();
  995. // revert path to HEAD state
  996. git.checkout().setStartPoint(Constants.HEAD).addPath(linkName)
  997. .call();
  998. // expect only the one added to the index
  999. assertWorkDir(mkmap(linkName, "a", fname, "a"));
  1000. recorder.assertEvent(new String[] { linkName },
  1001. ChangeRecorder.EMPTY);
  1002. Status st = git.status().call();
  1003. assertTrue(st.isClean());
  1004. } finally {
  1005. if (handle != null) {
  1006. handle.remove();
  1007. }
  1008. }
  1009. }
  1010. @Test
  1011. public void testCheckoutChangeLinkToNonEmptyDirsAndNewIndexEntry()
  1012. throws Exception {
  1013. Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
  1014. String fname = "file";
  1015. ChangeRecorder recorder = new ChangeRecorder();
  1016. ListenerHandle handle = null;
  1017. try (Git git = new Git(db)) {
  1018. handle = db.getListenerList()
  1019. .addWorkingTreeModifiedListener(recorder);
  1020. // Add a file
  1021. writeTrashFile(fname, "a");
  1022. git.add().addFilepattern(fname).call();
  1023. // Add a link to file
  1024. String linkName = "link";
  1025. File link = writeLink(linkName, fname).toFile();
  1026. git.add().addFilepattern(linkName).call();
  1027. git.commit().setMessage("Added file and link").call();
  1028. assertWorkDir(mkmap(linkName, "a", fname, "a"));
  1029. // replace link with directory containing only directories, no files
  1030. FileUtils.delete(link);
  1031. // create and add a file in the new directory to the index
  1032. writeTrashFile(linkName + "/dir1", "file1", "c");
  1033. git.add().addFilepattern(linkName + "/dir1/file1").call();
  1034. // create but do not add a file in the new directory to the index
  1035. writeTrashFile(linkName + "/dir2", "file2", "d");
  1036. assertTrue("File must be a directory now", link.isDirectory());
  1037. assertFalse("Must not delete non empty directory", link.delete());
  1038. // 2 extra files are created
  1039. assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
  1040. linkName + "/dir2/file2", "d"));
  1041. recorder.assertNoEvent();
  1042. // revert path to HEAD state
  1043. git.checkout().setStartPoint(Constants.HEAD).addPath(linkName)
  1044. .call();
  1045. // original file and link
  1046. assertWorkDir(mkmap(linkName, "a", fname, "a"));
  1047. recorder.assertEvent(new String[] { linkName },
  1048. ChangeRecorder.EMPTY);
  1049. Status st = git.status().call();
  1050. assertTrue(st.isClean());
  1051. } finally {
  1052. if (handle != null) {
  1053. handle.remove();
  1054. }
  1055. }
  1056. }
  1057. @Test
  1058. public void testCheckoutChangeFileToEmptyDir() throws Exception {
  1059. String fname = "was_file";
  1060. ChangeRecorder recorder = new ChangeRecorder();
  1061. ListenerHandle handle = null;
  1062. try (Git git = new Git(db)) {
  1063. handle = db.getListenerList()
  1064. .addWorkingTreeModifiedListener(recorder);
  1065. // Add a file
  1066. File file = writeTrashFile(fname, "a");
  1067. git.add().addFilepattern(fname).call();
  1068. git.commit().setMessage("Added file").call();
  1069. // replace file with empty directory
  1070. FileUtils.delete(file);
  1071. FileUtils.mkdir(file);
  1072. assertTrue("File must be a directory now", file.isDirectory());
  1073. assertWorkDir(mkmap(fname, "/"));
  1074. recorder.assertNoEvent();
  1075. // revert path to HEAD state
  1076. git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
  1077. assertWorkDir(mkmap(fname, "a"));
  1078. recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
  1079. Status st = git.status().call();
  1080. assertTrue(st.isClean());
  1081. } finally {
  1082. if (handle != null) {
  1083. handle.remove();
  1084. }
  1085. }
  1086. }
  1087. @Test
  1088. public void testCheckoutChangeFileToEmptyDirs() throws Exception {
  1089. String fname = "was_file";
  1090. ChangeRecorder recorder = new ChangeRecorder();
  1091. ListenerHandle handle = null;
  1092. try (Git git = new Git(db)) {
  1093. handle = db.getListenerList()
  1094. .addWorkingTreeModifiedListener(recorder);
  1095. // Add a file
  1096. File file = writeTrashFile(fname, "a");
  1097. git.add().addFilepattern(fname).call();
  1098. git.commit().setMessage("Added file").call();
  1099. // replace file with directory containing only directories, no files
  1100. FileUtils.delete(file);
  1101. FileUtils.mkdirs(new File(file, "dummyDir"));
  1102. assertTrue("File must be a directory now", file.isDirectory());
  1103. assertFalse("Must not delete non empty directory", file.delete());
  1104. assertWorkDir(mkmap(fname + "/dummyDir", "/"));
  1105. recorder.assertNoEvent();
  1106. // revert path to HEAD state
  1107. git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
  1108. assertWorkDir(mkmap(fname, "a"));
  1109. recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
  1110. Status st = git.status().call();
  1111. assertTrue(st.isClean());
  1112. } finally {
  1113. if (handle != null) {
  1114. handle.remove();
  1115. }
  1116. }
  1117. }
  1118. @Test
  1119. public void testCheckoutChangeFileToNonEmptyDirs() throws Exception {
  1120. String fname = "was_file";
  1121. ChangeRecorder recorder = new ChangeRecorder();
  1122. ListenerHandle handle = null;
  1123. try (Git git = new Git(db)) {
  1124. handle = db.getListenerList()
  1125. .addWorkingTreeModifiedListener(recorder);
  1126. // Add a file
  1127. File file = writeTrashFile(fname, "a");
  1128. git.add().addFilepattern(fname).call();
  1129. git.commit().setMessage("Added file").call();
  1130. assertWorkDir(mkmap(fname, "a"));
  1131. // replace file with directory containing only directories, no files
  1132. FileUtils.delete(file);
  1133. // create but do not add a file in the new directory to the index
  1134. writeTrashFile(fname + "/dir1", "file1", "c");
  1135. // create but do not add a file in the new directory to the index
  1136. writeTrashFile(fname + "/dir2", "file2", "d");
  1137. assertTrue("File must be a directory now", file.isDirectory());
  1138. assertFalse("Must not delete non empty directory", file.delete());
  1139. // 2 extra files are created
  1140. assertWorkDir(mkmap(fname + "/dir1/file1", "c",
  1141. fname + "/dir2/file2", "d"));
  1142. recorder.assertNoEvent();
  1143. // revert path to HEAD state
  1144. git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
  1145. // expect only the one added to the index
  1146. assertWorkDir(mkmap(fname, "a"));
  1147. recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
  1148. Status st = git.status().call();
  1149. assertTrue(st.isClean());
  1150. } finally {
  1151. if (handle != null) {
  1152. handle.remove();
  1153. }
  1154. }
  1155. }
  1156. @Test
  1157. public void testCheckoutChangeFileToNonEmptyDirsAndNewIndexEntry()
  1158. throws Exception {
  1159. String fname = "was_file";
  1160. ChangeRecorder recorder = new ChangeRecorder();
  1161. ListenerHandle handle = null;
  1162. try (Git git = new Git(db)) {
  1163. handle = db.getListenerList()
  1164. .addWorkingTreeModifiedListener(recorder);
  1165. // Add a file
  1166. File file = writeTrashFile(fname, "a");
  1167. git.add().addFilepattern(fname).call();
  1168. git.commit().setMessage("Added file").call();
  1169. assertWorkDir(mkmap(fname, "a"));
  1170. // replace file with directory containing only directories, no files
  1171. FileUtils.delete(file);
  1172. // create and add a file in the new directory to the index
  1173. writeTrashFile(fname + "/dir", "file1", "c");
  1174. git.add().addFilepattern(fname + "/dir/file1").call();
  1175. // create but do not add a file in the new directory to the index
  1176. writeTrashFile(fname + "/dir", "file2", "d");
  1177. assertTrue("File must be a directory now", file.isDirectory());
  1178. assertFalse("Must not delete non empty directory", file.delete());
  1179. // 2 extra files are created
  1180. assertWorkDir(mkmap(fname + "/dir/file1", "c", fname + "/dir/file2",
  1181. "d"));
  1182. recorder.assertNoEvent();
  1183. // revert path to HEAD state
  1184. git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
  1185. assertWorkDir(mkmap(fname, "a"));
  1186. recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
  1187. Status st = git.status().call();
  1188. assertTrue(st.isClean());
  1189. } finally {
  1190. if (handle != null) {
  1191. handle.remove();
  1192. }
  1193. }
  1194. }
  1195. @Test
  1196. public void testCheckoutOutChangesAutoCRLFfalse() throws IOException {
  1197. setupCase(mk("foo"), mkmap("foo/bar", "foo\nbar"), mk("foo"));
  1198. checkout();
  1199. assertIndex(mkmap("foo/bar", "foo\nbar"));
  1200. assertWorkDir(mkmap("foo/bar", "foo\nbar"));
  1201. }
  1202. @Test
  1203. public void testCheckoutOutChangesAutoCRLFInput() throws IOException {
  1204. setupCase(mk("foo"), mkmap("foo/bar", "foo\nbar"), mk("foo"));
  1205. db.getConfig().setString("core", null, "autocrlf", "input");
  1206. checkout();
  1207. assertIndex(mkmap("foo/bar", "foo\nbar"));
  1208. assertWorkDir(mkmap("foo/bar", "foo\nbar"));
  1209. }
  1210. @Test
  1211. public void testCheckoutOutChangesAutoCRLFtrue() throws IOException {
  1212. setupCase(mk("foo"), mkmap("foo/bar", "foo\nbar"), mk("foo"));
  1213. db.getConfig().setString("core", null, "autocrlf", "true");
  1214. checkout();
  1215. assertIndex(mkmap("foo/bar", "foo\nbar"));
  1216. assertWorkDir(mkmap("foo/bar", "foo\r\nbar"));
  1217. }
  1218. @Test
  1219. public void testCheckoutOutChangesAutoCRLFtrueBinary() throws IOException {
  1220. setupCase(mk("foo"), mkmap("foo/bar", "foo\nb\u0000ar"), mk("foo"));
  1221. db.getConfig().setString("core", null, "autocrlf", "true");
  1222. checkout();
  1223. assertIndex(mkmap("foo/bar", "foo\nb\u0000ar"));
  1224. assertWorkDir(mkmap("foo/bar", "foo\nb\u0000ar"));
  1225. }
  1226. @Test
  1227. public void testCheckoutUncachedChanges() throws IOException {
  1228. setupCase(mk("foo"), mk("foo"), mk("foo"));
  1229. writeTrashFile("foo", "otherData");
  1230. checkout();
  1231. assertIndex(mk("foo"));
  1232. assertWorkDir(mkmap("foo", "otherData"));
  1233. assertTrue(new File(trash, "foo").isFile());
  1234. }
  1235. @Test
  1236. public void testDontOverwriteDirtyFile() throws IOException {
  1237. setupCase(mk("foo"), mk("other"), mk("foo"));
  1238. writeTrashFile("foo", "different");
  1239. try {
  1240. checkout();
  1241. fail("Didn't got the expected conflict");
  1242. } catch (CheckoutConflictException e) {
  1243. assertIndex(mk("foo"));
  1244. assertWorkDir(mkmap("foo", "different"));
  1245. assertEquals(Arrays.asList("foo"), getConflicts());
  1246. assertTrue(new File(trash, "foo").isFile());
  1247. }
  1248. }
  1249. @Test
  1250. public void testDontOverwriteEmptyFolder() throws IOException {
  1251. setupCase(mk("foo"), mk("foo"), mk("foo"));
  1252. FileUtils.mkdir(new File(db.getWorkTree(), "d"));
  1253. checkout();
  1254. assertWorkDir(mkmap("foo", "foo", "d", "/"));
  1255. }
  1256. @Test
  1257. public void testOverwriteUntrackedIgnoredFile() throws IOException,
  1258. GitAPIException {
  1259. String fname="file.txt";
  1260. ChangeRecorder recorder = new ChangeRecorder();
  1261. ListenerHandle handle = null;
  1262. try (Git git = new Git(db)) {
  1263. handle = db.getListenerList()
  1264. .addWorkingTreeModifiedListener(recorder);
  1265. // Add a file
  1266. writeTrashFile(fname, "a");
  1267. git.add().addFilepattern(fname).call();
  1268. git.commit().setMessage("create file").call();
  1269. // Create branch
  1270. git.branchCreate().setName("side").call();
  1271. // Modify file
  1272. writeTrashFile(fname, "b");
  1273. git.add().addFilepattern(fname).call();
  1274. git.commit().setMessage("modify file").call();
  1275. recorder.assertNoEvent();
  1276. // Switch branches
  1277. git.checkout().setName("side").call();
  1278. recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
  1279. git.rm().addFilepattern(fname).call();
  1280. recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { fname });
  1281. writeTrashFile(".gitignore", fname);
  1282. git.add().addFilepattern(".gitignore").call();
  1283. git.commit().setMessage("delete and ignore file").call();
  1284. writeTrashFile(fname, "Something different");
  1285. recorder.assertNoEvent();
  1286. git.checkout().setName("master").call();
  1287. assertWorkDir(mkmap(fname, "b"));
  1288. recorder.assertEvent(new String[] { fname },
  1289. new String[] { ".gitignore" });
  1290. assertTrue(git.status().call().isClean());
  1291. } finally {
  1292. if (handle != null) {
  1293. handle.remove();
  1294. }
  1295. }
  1296. }
  1297. @Test
  1298. public void testOverwriteUntrackedFileModeChange()
  1299. throws IOException, GitAPIException {
  1300. String fname = "file.txt";
  1301. ChangeRecorder recorder = new ChangeRecorder();
  1302. ListenerHandle handle = null;
  1303. try (Git git = new Git(db)) {
  1304. handle = db.getListenerList()
  1305. .addWorkingTreeModifiedListener(recorder);
  1306. // Add a file
  1307. File file = writeTrashFile(fname, "a");
  1308. git.add().addFilepattern(fname).call();
  1309. git.commit().setMessage("create file").call();
  1310. assertWorkDir(mkmap(fname, "a"));
  1311. // Create branch
  1312. git.branchCreate().setName("side").call();
  1313. // Switch branches
  1314. git.checkout().setName("side").call();
  1315. recorder.assertNoEvent();
  1316. // replace file with directory containing files
  1317. FileUtils.delete(file);
  1318. // create and add a file in the new directory to the index
  1319. writeTrashFile(fname + "/dir1", "file1", "c");
  1320. git.add().addFilepattern(fname + "/dir1/file1").call();
  1321. // create but do not add a file in the new directory to the index
  1322. writeTrashFile(fname + "/dir2", "file2", "d");
  1323. assertTrue("File must be a directory now", file.isDirectory());
  1324. assertFalse("Must not delete non empty directory", file.delete());
  1325. // 2 extra files are created
  1326. assertWorkDir(mkmap(fname + "/dir1/file1", "c",
  1327. fname + "/dir2/file2", "d"));
  1328. try {
  1329. git.checkout().setName("master").call();
  1330. fail("did not throw exception");
  1331. } catch (Exception e) {
  1332. // 2 extra files are still there
  1333. assertWorkDir(mkmap(fname + "/dir1/file1", "c",
  1334. fname + "/dir2/file2", "d"));
  1335. }
  1336. recorder.assertNoEvent();
  1337. } finally {
  1338. if (handle != null) {
  1339. handle.remove();
  1340. }
  1341. }
  1342. }
  1343. @Test
  1344. public void testOverwriteUntrackedLinkModeChange()
  1345. throws Exception {
  1346. Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
  1347. String fname = "file.txt";
  1348. ChangeRecorder recorder = new ChangeRecorder();
  1349. ListenerHandle handle = null;
  1350. try (Git git = new Git(db)) {
  1351. handle = db.getListenerList()
  1352. .addWorkingTreeModifiedListener(recorder);
  1353. // Add a file
  1354. writeTrashFile(fname, "a");
  1355. git.add().addFilepattern(fname).call();
  1356. // Add a link to file
  1357. String linkName = "link";
  1358. File link = writeLink(linkName, fname).toFile();
  1359. git.add().addFilepattern(linkName).call();
  1360. git.commit().setMessage("Added file and link").call();
  1361. assertWorkDir(mkmap(linkName, "a", fname, "a"));
  1362. // Create branch
  1363. git.branchCreate().setName("side").call();
  1364. // Switch branches
  1365. git.checkout().setName("side").call();
  1366. recorder.assertNoEvent();
  1367. // replace link with directory containing files
  1368. FileUtils.delete(link);
  1369. // create and add a file in the new directory to the index
  1370. writeTrashFile(linkName + "/dir1", "file1", "c");
  1371. git.add().addFilepattern(linkName + "/dir1/file1").call();
  1372. // create but do not add a file in the new directory to the index
  1373. writeTrashFile(linkName + "/dir2", "file2", "d");
  1374. assertTrue("Link must be a directory now", link.isDirectory());
  1375. assertFalse("Must not delete non empty directory", link.delete());
  1376. // 2 extra files are created
  1377. assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
  1378. linkName + "/dir2/file2", "d"));
  1379. try {
  1380. git.checkout().setName("master").call();
  1381. fail("did not throw exception");
  1382. } catch (Exception e) {
  1383. // 2 extra files are still there
  1384. assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
  1385. linkName + "/dir2/file2", "d"));
  1386. }
  1387. recorder.assertNoEvent();
  1388. } finally {
  1389. if (handle != null) {
  1390. handle.remove();
  1391. }
  1392. }
  1393. }
  1394. @Test
  1395. public void testFileModeChangeWithNoContentChangeUpdate() throws Exception {
  1396. if (!FS.DETECTED.supportsExecute())
  1397. return;
  1398. ChangeRecorder recorder = new ChangeRecorder();
  1399. ListenerHandle handle = null;
  1400. try (Git git = new Git(db)) {
  1401. handle = db.getListenerList()
  1402. .addWorkingTreeModifiedListener(recorder);
  1403. // Add non-executable file
  1404. File file = writeTrashFile("file.txt", "a");
  1405. git.add().addFilepattern("file.txt").call();
  1406. git.commit().setMessage("commit1").call();
  1407. assertFalse(db.getFS().canExecute(file));
  1408. // Create branch
  1409. git.branchCreate().setName("b1").call();
  1410. // Make file executable
  1411. db.getFS().setExecute(file, true);
  1412. git.add().addFilepattern("file.txt").call();
  1413. git.commit().setMessage("commit2").call();
  1414. recorder.assertNoEvent();
  1415. // Verify executable and working directory is clean
  1416. Status status = git.status().call();
  1417. assertTrue(status.getModified().isEmpty());
  1418. assertTrue(status.getChanged().isEmpty());
  1419. assertTrue(db.getFS().canExecute(file));
  1420. // Switch branches
  1421. git.checkout().setName("b1").call();
  1422. // Verify not executable and working directory is clean
  1423. status = git.status().call();
  1424. assertTrue(status.getModified().isEmpty());
  1425. assertTrue(status.getChanged().isEmpty());
  1426. assertFalse(db.getFS().canExecute(file));
  1427. recorder.assertEvent(new String[] { "file.txt" },
  1428. ChangeRecorder.EMPTY);
  1429. } finally {
  1430. if (handle != null) {
  1431. handle.remove();
  1432. }
  1433. }
  1434. }
  1435. @Test
  1436. public void testFileModeChangeAndContentChangeConflict() throws Exception {
  1437. if (!FS.DETECTED.supportsExecute())
  1438. return;
  1439. ChangeRecorder recorder = new ChangeRecorder();
  1440. ListenerHandle handle = null;
  1441. try (Git git = new Git(db)) {
  1442. handle = db.getListenerList()
  1443. .addWorkingTreeModifiedListener(recorder);
  1444. // Add non-executable file
  1445. File file = writeTrashFile("file.txt", "a");
  1446. git.add().addFilepattern("file.txt").call();
  1447. git.commit().setMessage("commit1").call();
  1448. assertFalse(db.getFS().canExecute(file));
  1449. // Create branch
  1450. git.branchCreate().setName("b1").call();
  1451. // Make file executable
  1452. db.getFS().setExecute(file, true);
  1453. git.add().addFilepattern("file.txt").call();
  1454. git.commit().setMessage("commit2").call();
  1455. // Verify executable and working directory is clean
  1456. Status status = git.status().call();
  1457. assertTrue(status.getModified().isEmpty());
  1458. assertTrue(status.getChanged().isEmpty());
  1459. assertTrue(db.getFS().canExecute(file));
  1460. writeTrashFile("file.txt", "b");
  1461. // Switch branches
  1462. CheckoutCommand checkout = git.checkout().setName("b1");
  1463. try {
  1464. checkout.call();
  1465. fail("Checkout exception not thrown");
  1466. } catch (org.eclipse.jgit.api.errors.CheckoutConflictException e) {
  1467. CheckoutResult result = checkout.getResult();
  1468. assertNotNull(result);
  1469. assertNotNull(result.getConflictList());
  1470. assertEquals(1, result.getConflictList().size());
  1471. assertTrue(result.getConflictList().contains("file.txt"));
  1472. }
  1473. recorder.assertNoEvent();
  1474. } finally {
  1475. if (handle != null) {
  1476. handle.remove();
  1477. }
  1478. }
  1479. }
  1480. @Test
  1481. public void testDirtyFileModeEqualHeadMerge()
  1482. throws Exception {
  1483. if (!FS.DETECTED.supportsExecute())
  1484. return;
  1485. ChangeRecorder recorder = new ChangeRecorder();
  1486. ListenerHandle handle = null;
  1487. try (Git git = new Git(db)) {
  1488. handle = db.getListenerList()
  1489. .addWorkingTreeModifiedListener(recorder);
  1490. // Add non-executable file
  1491. File file = writeTrashFile("file.txt", "a");
  1492. git.add().addFilepattern("file.txt").call();
  1493. git.commit().setMessage("commit1").call();
  1494. assertFalse(db.getFS().canExecute(file));
  1495. // Create branch
  1496. git.branchCreate().setName("b1").call();
  1497. // Create second commit and don't touch file
  1498. writeTrashFile("file2.txt", "");
  1499. git.add().addFilepattern("file2.txt").call();
  1500. git.commit().setMessage("commit2").call();
  1501. // stage a mode change
  1502. writeTrashFile("file.txt", "a");
  1503. db.getFS().setExecute(file, true);
  1504. git.add().addFilepattern("file.txt").call();
  1505. // dirty the file
  1506. writeTrashFile("file.txt", "b");
  1507. assertEquals(
  1508. "[file.txt, mode:100755, content:a][file2.txt, mode:100644, content:]",
  1509. indexState(CONTENT));
  1510. assertWorkDir(mkmap("file.txt", "b", "file2.txt", ""));
  1511. recorder.assertNoEvent();
  1512. // Switch branches and check that the dirty file survived in
  1513. // worktree and index
  1514. git.checkout().setName("b1").call();
  1515. assertEquals("[file.txt, mode:100755, content:a]",
  1516. indexState(CONTENT));
  1517. assertWorkDir(mkmap("file.txt", "b"));
  1518. recorder.assertEvent(ChangeRecorder.EMPTY,
  1519. new String[] { "file2.txt" });
  1520. } finally {
  1521. if (handle != null) {
  1522. handle.remove();
  1523. }
  1524. }
  1525. }
  1526. @Test
  1527. public void testDirtyFileModeEqualIndexMerge()
  1528. throws Exception {
  1529. if (!FS.DETECTED.supportsExecute())
  1530. return;
  1531. ChangeRecorder recorder = new ChangeRecorder();
  1532. ListenerHandle handle = null;
  1533. try (Git git = new Git(db)) {
  1534. handle = db.getListenerList()
  1535. .addWorkingTreeModifiedListener(recorder);
  1536. // Add non-executable file
  1537. File file = writeTrashFile("file.txt", "a");
  1538. git.add().addFilepattern("file.txt").call();
  1539. git.commit().setMessage("commit1").call();
  1540. assertFalse(db.getFS().canExecute(file));
  1541. // Create branch
  1542. git.branchCreate().setName("b1").call();
  1543. // Create second commit with executable file
  1544. file = writeTrashFile("file.txt", "b");
  1545. db.getFS().setExecute(file, true);
  1546. git.add().addFilepattern("file.txt").call();
  1547. git.commit().setMessage("commit2").call();
  1548. // stage the same content as in the branch we want to switch to
  1549. writeTrashFile("file.txt", "a");
  1550. db.getFS().setExecute(file, false);
  1551. git.add().addFilepattern("file.txt").call();
  1552. // dirty the file
  1553. writeTrashFile("file.txt", "c");
  1554. db.getFS().setExecute(file, true);
  1555. assertEquals("[file.txt, mode:100644, content:a]",
  1556. indexState(CONTENT));
  1557. assertWorkDir(mkmap("file.txt", "c"));
  1558. recorder.assertNoEvent();
  1559. // Switch branches and check that the dirty file survived in
  1560. // worktree
  1561. // and index
  1562. git.checkout().setName("b1").call();
  1563. assertEquals("[file.txt, mode:100644, content:a]",
  1564. indexState(CONTENT));
  1565. assertWorkDir(mkmap("file.txt", "c"));
  1566. recorder.assertNoEvent();
  1567. } finally {
  1568. if (handle != null) {
  1569. handle.remove();
  1570. }
  1571. }
  1572. }
  1573. @Test
  1574. public void testFileModeChangeAndContentChangeNoConflict() throws Exception {
  1575. if (!FS.DETECTED.supportsExecute())
  1576. return;
  1577. ChangeRecorder recorder = new ChangeRecorder();
  1578. ListenerHandle handle = null;
  1579. try (Git git = new Git(db)) {
  1580. handle = db.getListenerList()
  1581. .addWorkingTreeModifiedListener(recorder);
  1582. // Add first file
  1583. File file1 = writeTrashFile("file1.txt", "a");
  1584. git.add().addFilepattern("file1.txt").call();
  1585. git.commit().setMessage("commit1").call();
  1586. assertFalse(db.getFS().canExecute(file1));
  1587. // Add second file
  1588. File file2 = writeTrashFile("file2.txt", "b");
  1589. git.add().addFilepattern("file2.txt").call();
  1590. git.commit().setMessage("commit2").call();
  1591. assertFalse(db.getFS().canExecute(file2));
  1592. recorder.assertNoEvent();
  1593. // Create branch from first commit
  1594. assertNotNull(git.checkout().setCreateBranch(true).setName("b1")
  1595. .setStartPoint(Constants.HEAD + "~1").call());
  1596. recorder.assertEvent(ChangeRecorder.EMPTY,
  1597. new String[] { "file2.txt" });
  1598. // Change content and file mode in working directory and index
  1599. file1 = writeTrashFile("file1.txt", "c");
  1600. db.getFS().setExecute(file1, true);
  1601. git.add().addFilepattern("file1.txt").call();
  1602. // Switch back to 'master'
  1603. assertNotNull(git.checkout().setName(Constants.MASTER).call());
  1604. recorder.assertEvent(new String[] { "file2.txt" },
  1605. ChangeRecorder.EMPTY);
  1606. } finally {
  1607. if (handle != null) {
  1608. handle.remove();
  1609. }
  1610. }
  1611. }
  1612. @Test(expected = CheckoutConflictException.class)
  1613. public void testFolderFileConflict() throws Exception {
  1614. RevCommit headCommit = commitFile("f/a", "initial content", "master");
  1615. RevCommit checkoutCommit = commitFile("f/a", "side content", "side");
  1616. FileUtils.delete(new File(db.getWorkTree(), "f"), FileUtils.RECURSIVE);
  1617. writeTrashFile("f", "file instead of folder");
  1618. new DirCacheCheckout(db, headCommit.getTree(), db.lockDirCache(),
  1619. checkoutCommit.getTree()).checkout();
  1620. }
  1621. @Test
  1622. public void testMultipleContentConflicts() throws Exception {
  1623. commitFile("a", "initial content", "master");
  1624. RevCommit headCommit = commitFile("b", "initial content", "master");
  1625. commitFile("a", "side content", "side");
  1626. RevCommit checkoutCommit = commitFile("b", "side content", "side");
  1627. writeTrashFile("a", "changed content");
  1628. writeTrashFile("b", "changed content");
  1629. try {
  1630. new DirCacheCheckout(db, headCommit.getTree(), db.lockDirCache(),
  1631. checkoutCommit.getTree()).checkout();
  1632. fail();
  1633. } catch (CheckoutConflictException expected) {
  1634. assertEquals(2, expected.getConflictingFiles().length);
  1635. assertTrue(Arrays.asList(expected.getConflictingFiles())
  1636. .contains("a"));
  1637. assertTrue(Arrays.asList(expected.getConflictingFiles())
  1638. .contains("b"));
  1639. assertEquals("changed content", read("a"));
  1640. assertEquals("changed content", read("b"));
  1641. }
  1642. }
  1643. @Test
  1644. public void testFolderFileAndContentConflicts() throws Exception {
  1645. RevCommit headCommit = commitFile("f/a", "initial content", "master");
  1646. commitFile("b", "side content", "side");
  1647. RevCommit checkoutCommit = commitFile("f/a", "side content", "side");
  1648. FileUtils.delete(new File(db.getWorkTree(), "f"), FileUtils.RECURSIVE);
  1649. writeTrashFile("f", "file instead of a folder");
  1650. writeTrashFile("b", "changed content");
  1651. try {
  1652. new DirCacheCheckout(db, headCommit.getTree(), db.lockDirCache(),
  1653. checkoutCommit.getTree()).checkout();
  1654. fail();
  1655. } catch (CheckoutConflictException expected) {
  1656. assertEquals(2, expected.getConflictingFiles().length);
  1657. assertTrue(Arrays.asList(expected.getConflictingFiles())
  1658. .contains("b"));
  1659. assertTrue(Arrays.asList(expected.getConflictingFiles())
  1660. .contains("f"));
  1661. assertEquals("file instead of a folder", read("f"));
  1662. assertEquals("changed content", read("b"));
  1663. }
  1664. }
  1665. @Test
  1666. public void testLongFilename() throws Exception {
  1667. char[] bytes = new char[253];
  1668. Arrays.fill(bytes, 'f');
  1669. String longFileName = new String(bytes);
  1670. // 1
  1671. doit(mkmap(longFileName, "a"), mkmap(longFileName, "b"),
  1672. mkmap(longFileName, "a"));
  1673. writeTrashFile(longFileName, "a");
  1674. checkout();
  1675. assertNoConflicts();
  1676. assertUpdated(longFileName);
  1677. }
  1678. @Test
  1679. public void testIgnoredDirectory() throws Exception {
  1680. writeTrashFile(".gitignore", "src/ignored");
  1681. writeTrashFile("src/ignored/sub/foo.txt", "1");
  1682. try (Git git = new Git(db)) {
  1683. git.add().addFilepattern(".").call();
  1684. RevCommit commit = git.commit().setMessage("adding .gitignore")
  1685. .call();
  1686. writeTrashFile("foo.txt", "2");
  1687. writeTrashFile("zzz.txt", "3");
  1688. git.add().addFilepattern("foo.txt").call();
  1689. git.commit().setMessage("add file").call();
  1690. assertEquals("Should not have entered ignored directory", 1,
  1691. resetHardAndCount(commit));
  1692. }
  1693. }
  1694. @Test
  1695. public void testIgnoredDirectoryWithTrackedContent() throws Exception {
  1696. writeTrashFile("src/ignored/sub/foo.txt", "1");
  1697. try (Git git = new Git(db)) {
  1698. git.add().addFilepattern(".").call();
  1699. git.commit().setMessage("adding foo.txt").call();
  1700. writeTrashFile(".gitignore", "src/ignored");
  1701. writeTrashFile("src/ignored/sub/foo.txt", "2");
  1702. writeTrashFile("src/ignored/other/bar.txt", "3");
  1703. git.add().addFilepattern(".").call();
  1704. RevCommit commit = git.commit().setMessage("adding .gitignore")
  1705. .call();
  1706. writeTrashFile("foo.txt", "2");
  1707. writeTrashFile("zzz.txt", "3");
  1708. git.add().addFilepattern("foo.txt").call();
  1709. git.commit().setMessage("add file").call();
  1710. File file = writeTrashFile("src/ignored/sub/foo.txt", "3");
  1711. assertEquals("Should have entered ignored directory", 3,
  1712. resetHardAndCount(commit));
  1713. checkFile(file, "2");
  1714. }
  1715. }
  1716. @Test
  1717. public void testResetWithChangeInGitignore() throws Exception {
  1718. writeTrashFile(".gitignore", "src/ignored");
  1719. writeTrashFile("src/ignored/sub/foo.txt", "1");
  1720. try (Git git = new Git(db)) {
  1721. git.add().addFilepattern(".").call();
  1722. RevCommit initial = git.commit().setMessage("initial").call();
  1723. writeTrashFile("src/newignored/foo.txt", "2");
  1724. writeTrashFile("src/.gitignore", "newignored");
  1725. git.add().addFilepattern(".").call();
  1726. RevCommit commit = git.commit().setMessage("newignored").call();
  1727. assertEquals("Should not have entered src/newignored directory", 1,
  1728. resetHardAndCount(initial));
  1729. assertEquals("Should have entered src/newignored directory", 2,
  1730. resetHardAndCount(commit));
  1731. deleteTrashFile("src/.gitignore");
  1732. git.rm().addFilepattern("src/.gitignore").call();
  1733. RevCommit top = git.commit().setMessage("Unignore newignore")
  1734. .call();
  1735. assertEquals("Should have entered src/newignored directory", 2,
  1736. resetHardAndCount(initial));
  1737. assertEquals("Should have entered src/newignored directory", 2,
  1738. resetHardAndCount(commit));
  1739. assertEquals("Should not have entered src/newignored directory", 1,
  1740. resetHardAndCount(top));
  1741. }
  1742. }
  1743. @Test
  1744. public void testCheckoutWithEmptyIndexDoesntOverwrite() throws Exception {
  1745. try (Git git = new Git(db);
  1746. TestRepository<Repository> db_t = new TestRepository<>(db)) {
  1747. // prepare the commits
  1748. BranchBuilder master = db_t.branch("master");
  1749. RevCommit mergeCommit = master.commit()
  1750. .add("p/x", "headContent")
  1751. .message("m0").create();
  1752. master.commit().add("p/x", "headContent").message("m1").create();
  1753. git.checkout().setName("master").call();
  1754. // empty index and write unsaved data in 'p'
  1755. git.rm().addFilepattern("p").call();
  1756. writeTrashFile("p", "important data");
  1757. git.checkout().setName(mergeCommit.getName()).call();
  1758. assertEquals("", indexState(CONTENT));
  1759. assertEquals("important data", read("p"));
  1760. }
  1761. }
  1762. private static class TestFileTreeIterator extends FileTreeIterator {
  1763. // For assertions only
  1764. private final int[] count;
  1765. public TestFileTreeIterator(Repository repo, int[] count) {
  1766. super(repo);
  1767. this.count = count;
  1768. }
  1769. protected TestFileTreeIterator(final WorkingTreeIterator p,
  1770. final File root, FS fs, FileModeStrategy fileModeStrategy,
  1771. int[] count) {
  1772. super(p, root, fs, fileModeStrategy);
  1773. this.count = count;
  1774. }
  1775. @Override
  1776. protected AbstractTreeIterator enterSubtree() {
  1777. count[0] += 1;
  1778. return new TestFileTreeIterator(this,
  1779. ((FileEntry) current()).getFile(), fs, fileModeStrategy,
  1780. count);
  1781. }
  1782. }
  1783. private int resetHardAndCount(RevCommit commit) throws Exception {
  1784. int[] callCount = { 0 };
  1785. DirCache cache = db.lockDirCache();
  1786. FileTreeIterator workingTreeIterator = new TestFileTreeIterator(db,
  1787. callCount);
  1788. try {
  1789. DirCacheCheckout checkout = new DirCacheCheckout(db, null, cache,
  1790. commit.getTree().getId(), workingTreeIterator);
  1791. checkout.setFailOnConflict(false);
  1792. checkout.checkout();
  1793. } finally {
  1794. cache.unlock();
  1795. }
  1796. return callCount[0];
  1797. }
  1798. public void assertWorkDir(Map<String, String> i)
  1799. throws CorruptObjectException,
  1800. IOException {
  1801. try (TreeWalk walk = new TreeWalk(db)) {
  1802. walk.setRecursive(false);
  1803. walk.addTree(new FileTreeIterator(db));
  1804. String expectedValue;
  1805. String path;
  1806. int nrFiles = 0;
  1807. FileTreeIterator ft;
  1808. while (walk.next()) {
  1809. ft = walk.getTree(0, FileTreeIterator.class);
  1810. path = ft.getEntryPathString();
  1811. expectedValue = i.get(path);
  1812. File file = new File(db.getWorkTree(), path);
  1813. assertTrue(file.exists());
  1814. if (file.isFile()) {
  1815. assertNotNull("found unexpected file for path " + path
  1816. + " in workdir", expectedValue);
  1817. try (FileInputStream is = new FileInputStream(file)) {
  1818. byte[] buffer = new byte[(int) file.length()];
  1819. int offset = 0;
  1820. int numRead = 0;
  1821. while (offset < buffer.length
  1822. && (numRead = is.read(buffer, offset,
  1823. buffer.length - offset)) >= 0) {
  1824. offset += numRead;
  1825. }
  1826. assertArrayEquals(
  1827. "unexpected content for path " + path
  1828. + " in workDir. ",
  1829. buffer, i.get(path).getBytes(UTF_8));
  1830. }
  1831. nrFiles++;
  1832. } else if (file.isDirectory()) {
  1833. String[] files = file.list();
  1834. if (files != null && files.length == 0) {
  1835. assertEquals("found unexpected empty folder for path "
  1836. + path + " in workDir. ", "/", i.get(path));
  1837. nrFiles++;
  1838. }
  1839. }
  1840. if (walk.isSubtree()) {
  1841. walk.enterSubtree();
  1842. }
  1843. }
  1844. assertEquals("WorkDir has not the right size.", i.size(), nrFiles);
  1845. }
  1846. }
  1847. }