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.

MergerTest.java 60KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740
  1. /*
  2. * Copyright (C) 2012, 2020 Robin Stocker <robin@nibor.org> and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.merge;
  11. import static java.nio.charset.StandardCharsets.UTF_8;
  12. import static java.time.Instant.EPOCH;
  13. import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
  14. import static org.junit.Assert.assertEquals;
  15. import static org.junit.Assert.assertFalse;
  16. import static org.junit.Assert.assertNotNull;
  17. import static org.junit.Assert.assertNull;
  18. import static org.junit.Assert.assertTrue;
  19. import java.io.ByteArrayOutputStream;
  20. import java.io.File;
  21. import java.io.FileInputStream;
  22. import java.io.IOException;
  23. import java.time.Instant;
  24. import java.util.Arrays;
  25. import java.util.Map;
  26. import org.eclipse.jgit.api.Git;
  27. import org.eclipse.jgit.api.MergeResult;
  28. import org.eclipse.jgit.api.MergeResult.MergeStatus;
  29. import org.eclipse.jgit.api.RebaseResult;
  30. import org.eclipse.jgit.api.errors.CheckoutConflictException;
  31. import org.eclipse.jgit.api.errors.GitAPIException;
  32. import org.eclipse.jgit.api.errors.JGitInternalException;
  33. import org.eclipse.jgit.dircache.DirCache;
  34. import org.eclipse.jgit.dircache.DirCacheEditor;
  35. import org.eclipse.jgit.dircache.DirCacheEntry;
  36. import org.eclipse.jgit.errors.ConfigInvalidException;
  37. import org.eclipse.jgit.errors.NoMergeBaseException;
  38. import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
  39. import org.eclipse.jgit.junit.RepositoryTestCase;
  40. import org.eclipse.jgit.junit.TestRepository;
  41. import org.eclipse.jgit.lib.AnyObjectId;
  42. import org.eclipse.jgit.lib.ConfigConstants;
  43. import org.eclipse.jgit.lib.Constants;
  44. import org.eclipse.jgit.lib.FileMode;
  45. import org.eclipse.jgit.lib.ObjectId;
  46. import org.eclipse.jgit.lib.ObjectInserter;
  47. import org.eclipse.jgit.lib.ObjectLoader;
  48. import org.eclipse.jgit.lib.ObjectReader;
  49. import org.eclipse.jgit.lib.ObjectStream;
  50. import org.eclipse.jgit.lib.StoredConfig;
  51. import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
  52. import org.eclipse.jgit.revwalk.RevCommit;
  53. import org.eclipse.jgit.revwalk.RevObject;
  54. import org.eclipse.jgit.revwalk.RevTree;
  55. import org.eclipse.jgit.revwalk.RevWalk;
  56. import org.eclipse.jgit.storage.file.FileBasedConfig;
  57. import org.eclipse.jgit.treewalk.FileTreeIterator;
  58. import org.eclipse.jgit.util.FS;
  59. import org.eclipse.jgit.util.FileUtils;
  60. import org.junit.Assert;
  61. import org.junit.experimental.theories.DataPoints;
  62. import org.junit.experimental.theories.Theories;
  63. import org.junit.experimental.theories.Theory;
  64. import org.junit.runner.RunWith;
  65. @RunWith(Theories.class)
  66. public class MergerTest extends RepositoryTestCase {
  67. @DataPoints
  68. public static MergeStrategy[] strategiesUnderTest = new MergeStrategy[] {
  69. MergeStrategy.RECURSIVE, MergeStrategy.RESOLVE };
  70. @Theory
  71. public void failingDeleteOfDirectoryWithUntrackedContent(
  72. MergeStrategy strategy) throws Exception {
  73. File folder1 = new File(db.getWorkTree(), "folder1");
  74. FileUtils.mkdir(folder1);
  75. File file = new File(folder1, "file1.txt");
  76. write(file, "folder1--file1.txt");
  77. file = new File(folder1, "file2.txt");
  78. write(file, "folder1--file2.txt");
  79. try (Git git = new Git(db)) {
  80. git.add().addFilepattern(folder1.getName()).call();
  81. RevCommit base = git.commit().setMessage("adding folder").call();
  82. recursiveDelete(folder1);
  83. git.rm().addFilepattern("folder1/file1.txt")
  84. .addFilepattern("folder1/file2.txt").call();
  85. RevCommit other = git.commit()
  86. .setMessage("removing folders on 'other'").call();
  87. git.checkout().setName(base.name()).call();
  88. file = new File(db.getWorkTree(), "unrelated.txt");
  89. write(file, "unrelated");
  90. git.add().addFilepattern("unrelated.txt").call();
  91. RevCommit head = git.commit().setMessage("Adding another file").call();
  92. // Untracked file to cause failing path for delete() of folder1
  93. // but that's ok.
  94. file = new File(folder1, "file3.txt");
  95. write(file, "folder1--file3.txt");
  96. ResolveMerger merger = (ResolveMerger) strategy.newMerger(db, false);
  97. merger.setCommitNames(new String[] { "BASE", "HEAD", "other" });
  98. merger.setWorkingTreeIterator(new FileTreeIterator(db));
  99. boolean ok = merger.merge(head.getId(), other.getId());
  100. assertTrue(ok);
  101. assertTrue(file.exists());
  102. }
  103. }
  104. /**
  105. * Merging two conflicting subtrees when the index does not contain any file
  106. * in that subtree should lead to a conflicting state.
  107. *
  108. * @param strategy
  109. * @throws Exception
  110. */
  111. @Theory
  112. public void checkMergeConflictingTreesWithoutIndex(MergeStrategy strategy)
  113. throws Exception {
  114. Git git = Git.wrap(db);
  115. writeTrashFile("d/1", "orig");
  116. git.add().addFilepattern("d/1").call();
  117. RevCommit first = git.commit().setMessage("added d/1").call();
  118. writeTrashFile("d/1", "master");
  119. RevCommit masterCommit = git.commit().setAll(true)
  120. .setMessage("modified d/1 on master").call();
  121. git.checkout().setCreateBranch(true).setStartPoint(first)
  122. .setName("side").call();
  123. writeTrashFile("d/1", "side");
  124. git.commit().setAll(true).setMessage("modified d/1 on side").call();
  125. git.rm().addFilepattern("d/1").call();
  126. git.rm().addFilepattern("d").call();
  127. MergeResult mergeRes = git.merge().setStrategy(strategy)
  128. .include(masterCommit).call();
  129. assertEquals(MergeStatus.CONFLICTING, mergeRes.getMergeStatus());
  130. assertEquals(
  131. "[d/1, mode:100644, stage:1, content:orig][d/1, mode:100644, stage:2, content:side][d/1, mode:100644, stage:3, content:master]",
  132. indexState(CONTENT));
  133. }
  134. /**
  135. * Merging two different but mergeable subtrees when the index does not
  136. * contain any file in that subtree should lead to a merged state.
  137. *
  138. * @param strategy
  139. * @throws Exception
  140. */
  141. @Theory
  142. public void checkMergeMergeableTreesWithoutIndex(MergeStrategy strategy)
  143. throws Exception {
  144. Git git = Git.wrap(db);
  145. writeTrashFile("d/1", "1\n2\n3");
  146. git.add().addFilepattern("d/1").call();
  147. RevCommit first = git.commit().setMessage("added d/1").call();
  148. writeTrashFile("d/1", "1master\n2\n3");
  149. RevCommit masterCommit = git.commit().setAll(true)
  150. .setMessage("modified d/1 on master").call();
  151. git.checkout().setCreateBranch(true).setStartPoint(first)
  152. .setName("side").call();
  153. writeTrashFile("d/1", "1\n2\n3side");
  154. git.commit().setAll(true).setMessage("modified d/1 on side").call();
  155. git.rm().addFilepattern("d/1").call();
  156. git.rm().addFilepattern("d").call();
  157. MergeResult mergeRes = git.merge().setStrategy(strategy)
  158. .include(masterCommit).call();
  159. assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
  160. assertEquals("[d/1, mode:100644, content:1master\n2\n3side]",
  161. indexState(CONTENT));
  162. }
  163. /**
  164. * An existing directory without tracked content should not prevent merging
  165. * a tree where that directory exists.
  166. *
  167. * @param strategy
  168. * @throws Exception
  169. */
  170. @Theory
  171. public void checkUntrackedFolderIsNotAConflict(
  172. MergeStrategy strategy) throws Exception {
  173. Git git = Git.wrap(db);
  174. writeTrashFile("d/1", "1");
  175. git.add().addFilepattern("d/1").call();
  176. RevCommit first = git.commit().setMessage("added d/1").call();
  177. writeTrashFile("e/1", "4");
  178. git.add().addFilepattern("e/1").call();
  179. RevCommit masterCommit = git.commit().setMessage("added e/1").call();
  180. git.checkout().setCreateBranch(true).setStartPoint(first)
  181. .setName("side").call();
  182. writeTrashFile("f/1", "5");
  183. git.add().addFilepattern("f/1").call();
  184. git.commit().setAll(true).setMessage("added f/1")
  185. .call();
  186. // Untracked directory e shall not conflict with merged e/1
  187. writeTrashFile("e/2", "d two");
  188. MergeResult mergeRes = git.merge().setStrategy(strategy)
  189. .include(masterCommit).call();
  190. assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
  191. assertEquals(
  192. "[d/1, mode:100644, content:1][e/1, mode:100644, content:4][f/1, mode:100644, content:5]",
  193. indexState(CONTENT));
  194. }
  195. /**
  196. * A tracked file is replaced by a folder in THEIRS.
  197. *
  198. * @param strategy
  199. * @throws Exception
  200. */
  201. @Theory
  202. public void checkFileReplacedByFolderInTheirs(MergeStrategy strategy)
  203. throws Exception {
  204. Git git = Git.wrap(db);
  205. writeTrashFile("sub", "file");
  206. git.add().addFilepattern("sub").call();
  207. RevCommit first = git.commit().setMessage("initial").call();
  208. git.checkout().setCreateBranch(true).setStartPoint(first)
  209. .setName("side").call();
  210. git.rm().addFilepattern("sub").call();
  211. writeTrashFile("sub/file", "subfile");
  212. git.add().addFilepattern("sub/file").call();
  213. RevCommit masterCommit = git.commit().setMessage("file -> folder")
  214. .call();
  215. git.checkout().setName("master").call();
  216. writeTrashFile("noop", "other");
  217. git.add().addFilepattern("noop").call();
  218. git.commit().setAll(true).setMessage("noop").call();
  219. MergeResult mergeRes = git.merge().setStrategy(strategy)
  220. .include(masterCommit).call();
  221. assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
  222. assertEquals(
  223. "[noop, mode:100644, content:other][sub/file, mode:100644, content:subfile]",
  224. indexState(CONTENT));
  225. }
  226. /**
  227. * A tracked file is replaced by a folder in OURS.
  228. *
  229. * @param strategy
  230. * @throws Exception
  231. */
  232. @Theory
  233. public void checkFileReplacedByFolderInOurs(MergeStrategy strategy)
  234. throws Exception {
  235. Git git = Git.wrap(db);
  236. writeTrashFile("sub", "file");
  237. git.add().addFilepattern("sub").call();
  238. RevCommit first = git.commit().setMessage("initial").call();
  239. git.checkout().setCreateBranch(true).setStartPoint(first)
  240. .setName("side").call();
  241. writeTrashFile("noop", "other");
  242. git.add().addFilepattern("noop").call();
  243. RevCommit sideCommit = git.commit().setAll(true).setMessage("noop")
  244. .call();
  245. git.checkout().setName("master").call();
  246. git.rm().addFilepattern("sub").call();
  247. writeTrashFile("sub/file", "subfile");
  248. git.add().addFilepattern("sub/file").call();
  249. git.commit().setMessage("file -> folder")
  250. .call();
  251. MergeResult mergeRes = git.merge().setStrategy(strategy)
  252. .include(sideCommit).call();
  253. assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
  254. assertEquals(
  255. "[noop, mode:100644, content:other][sub/file, mode:100644, content:subfile]",
  256. indexState(CONTENT));
  257. }
  258. /**
  259. * An existing directory without tracked content should not prevent merging
  260. * a file with that name.
  261. *
  262. * @param strategy
  263. * @throws Exception
  264. */
  265. @Theory
  266. public void checkUntrackedEmpytFolderIsNotAConflictWithFile(
  267. MergeStrategy strategy)
  268. throws Exception {
  269. Git git = Git.wrap(db);
  270. writeTrashFile("d/1", "1");
  271. git.add().addFilepattern("d/1").call();
  272. RevCommit first = git.commit().setMessage("added d/1").call();
  273. writeTrashFile("e", "4");
  274. git.add().addFilepattern("e").call();
  275. RevCommit masterCommit = git.commit().setMessage("added e").call();
  276. git.checkout().setCreateBranch(true).setStartPoint(first)
  277. .setName("side").call();
  278. writeTrashFile("f/1", "5");
  279. git.add().addFilepattern("f/1").call();
  280. git.commit().setAll(true).setMessage("added f/1").call();
  281. // Untracked empty directory hierarcy e/1 shall not conflict with merged
  282. // e/1
  283. FileUtils.mkdirs(new File(trash, "e/1"), true);
  284. MergeResult mergeRes = git.merge().setStrategy(strategy)
  285. .include(masterCommit).call();
  286. assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
  287. assertEquals(
  288. "[d/1, mode:100644, content:1][e, mode:100644, content:4][f/1, mode:100644, content:5]",
  289. indexState(CONTENT));
  290. }
  291. @Theory
  292. public void mergeWithCrlfInWT(MergeStrategy strategy) throws IOException,
  293. GitAPIException {
  294. Git git = Git.wrap(db);
  295. db.getConfig().setString("core", null, "autocrlf", "false");
  296. db.getConfig().save();
  297. writeTrashFile("crlf.txt", "some\r\ndata\r\n");
  298. git.add().addFilepattern("crlf.txt").call();
  299. git.commit().setMessage("base").call();
  300. git.branchCreate().setName("brancha").call();
  301. writeTrashFile("crlf.txt", "some\r\nmore\r\ndata\r\n");
  302. git.add().addFilepattern("crlf.txt").call();
  303. git.commit().setMessage("on master").call();
  304. git.checkout().setName("brancha").call();
  305. writeTrashFile("crlf.txt", "some\r\ndata\r\ntoo\r\n");
  306. git.add().addFilepattern("crlf.txt").call();
  307. git.commit().setMessage("on brancha").call();
  308. db.getConfig().setString("core", null, "autocrlf", "input");
  309. db.getConfig().save();
  310. MergeResult mergeResult = git.merge().setStrategy(strategy)
  311. .include(db.resolve("master"))
  312. .call();
  313. assertEquals(MergeResult.MergeStatus.MERGED,
  314. mergeResult.getMergeStatus());
  315. }
  316. @Theory
  317. public void mergeConflictWithCrLfTextAuto(MergeStrategy strategy)
  318. throws IOException, GitAPIException {
  319. Git git = Git.wrap(db);
  320. writeTrashFile("crlf.txt", "a crlf file\r\n");
  321. git.add().addFilepattern("crlf.txt").call();
  322. git.commit().setMessage("base").call();
  323. assertEquals("[crlf.txt, mode:100644, content:a crlf file\r\n]",
  324. indexState(CONTENT));
  325. writeTrashFile(".gitattributes", "crlf.txt text=auto");
  326. git.add().addFilepattern(".gitattributes").call();
  327. git.commit().setMessage("attributes").call();
  328. git.branchCreate().setName("brancha").call();
  329. writeTrashFile("crlf.txt", "a crlf file\r\na second line\r\n");
  330. git.add().addFilepattern("crlf.txt").call();
  331. git.commit().setMessage("on master").call();
  332. assertEquals(
  333. "[.gitattributes, mode:100644, content:crlf.txt text=auto]"
  334. + "[crlf.txt, mode:100644, content:a crlf file\r\na second line\r\n]",
  335. indexState(CONTENT));
  336. git.checkout().setName("brancha").call();
  337. File testFile = writeTrashFile("crlf.txt",
  338. "a crlf file\r\nanother line\r\n");
  339. git.add().addFilepattern("crlf.txt").call();
  340. git.commit().setMessage("on brancha").call();
  341. MergeResult mergeResult = git.merge().setStrategy(strategy)
  342. .include(db.resolve("master")).call();
  343. assertEquals(MergeResult.MergeStatus.CONFLICTING,
  344. mergeResult.getMergeStatus());
  345. checkFile(testFile,
  346. "a crlf file\r\n" //
  347. + "<<<<<<< HEAD\n" //
  348. + "another line\r\n" //
  349. + "=======\n" //
  350. + "a second line\r\n" //
  351. + ">>>>>>> 8e9e704742f1bc8a41eac88aac4aeefd338b7384\n");
  352. }
  353. @Theory
  354. public void mergeWithCrlfAutoCrlfTrue(MergeStrategy strategy)
  355. throws IOException, GitAPIException {
  356. Git git = Git.wrap(db);
  357. db.getConfig().setString("core", null, "autocrlf", "true");
  358. db.getConfig().save();
  359. writeTrashFile("crlf.txt", "a crlf file\r\n");
  360. git.add().addFilepattern("crlf.txt").call();
  361. git.commit().setMessage("base").call();
  362. git.branchCreate().setName("brancha").call();
  363. writeTrashFile("crlf.txt", "a crlf file\r\na second line\r\n");
  364. git.add().addFilepattern("crlf.txt").call();
  365. git.commit().setMessage("on master").call();
  366. git.checkout().setName("brancha").call();
  367. File testFile = writeTrashFile("crlf.txt",
  368. "a first line\r\na crlf file\r\n");
  369. git.add().addFilepattern("crlf.txt").call();
  370. git.commit().setMessage("on brancha").call();
  371. MergeResult mergeResult = git.merge().setStrategy(strategy)
  372. .include(db.resolve("master")).call();
  373. assertEquals(MergeResult.MergeStatus.MERGED,
  374. mergeResult.getMergeStatus());
  375. checkFile(testFile, "a first line\r\na crlf file\r\na second line\r\n");
  376. assertEquals(
  377. "[crlf.txt, mode:100644, content:a first line\na crlf file\na second line\n]",
  378. indexState(CONTENT));
  379. }
  380. @Theory
  381. public void rebaseWithCrlfAutoCrlfTrue(MergeStrategy strategy)
  382. throws IOException, GitAPIException {
  383. Git git = Git.wrap(db);
  384. db.getConfig().setString("core", null, "autocrlf", "true");
  385. db.getConfig().save();
  386. writeTrashFile("crlf.txt", "line 1\r\nline 2\r\nline 3\r\n");
  387. git.add().addFilepattern("crlf.txt").call();
  388. RevCommit first = git.commit().setMessage("base").call();
  389. git.checkout().setCreateBranch(true).setStartPoint(first)
  390. .setName("brancha").call();
  391. File testFile = writeTrashFile("crlf.txt",
  392. "line 1\r\nmodified line\r\nline 3\r\n");
  393. git.add().addFilepattern("crlf.txt").call();
  394. git.commit().setMessage("on brancha").call();
  395. git.checkout().setName("master").call();
  396. File otherFile = writeTrashFile("otherfile.txt", "a line\r\n");
  397. git.add().addFilepattern("otherfile.txt").call();
  398. git.commit().setMessage("on master").call();
  399. git.checkout().setName("brancha").call();
  400. checkFile(testFile, "line 1\r\nmodified line\r\nline 3\r\n");
  401. assertFalse(otherFile.exists());
  402. RebaseResult rebaseResult = git.rebase().setStrategy(strategy)
  403. .setUpstream(db.resolve("master")).call();
  404. assertEquals(RebaseResult.Status.OK, rebaseResult.getStatus());
  405. checkFile(testFile, "line 1\r\nmodified line\r\nline 3\r\n");
  406. checkFile(otherFile, "a line\r\n");
  407. assertEquals(
  408. "[crlf.txt, mode:100644, content:line 1\nmodified line\nline 3\n]"
  409. + "[otherfile.txt, mode:100644, content:a line\n]",
  410. indexState(CONTENT));
  411. }
  412. /**
  413. * Merging two equal subtrees when the index does not contain any file in
  414. * that subtree should lead to a merged state.
  415. *
  416. * @param strategy
  417. * @throws Exception
  418. */
  419. @Theory
  420. public void checkMergeEqualTreesWithoutIndex(MergeStrategy strategy)
  421. throws Exception {
  422. Git git = Git.wrap(db);
  423. writeTrashFile("d/1", "orig");
  424. git.add().addFilepattern("d/1").call();
  425. RevCommit first = git.commit().setMessage("added d/1").call();
  426. writeTrashFile("d/1", "modified");
  427. RevCommit masterCommit = git.commit().setAll(true)
  428. .setMessage("modified d/1 on master").call();
  429. git.checkout().setCreateBranch(true).setStartPoint(first)
  430. .setName("side").call();
  431. writeTrashFile("d/1", "modified");
  432. git.commit().setAll(true).setMessage("modified d/1 on side").call();
  433. git.rm().addFilepattern("d/1").call();
  434. git.rm().addFilepattern("d").call();
  435. MergeResult mergeRes = git.merge().setStrategy(strategy)
  436. .include(masterCommit).call();
  437. assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
  438. assertEquals("[d/1, mode:100644, content:modified]",
  439. indexState(CONTENT));
  440. }
  441. /**
  442. * Merging two equal subtrees with an incore merger should lead to a merged
  443. * state.
  444. *
  445. * @param strategy
  446. * @throws Exception
  447. */
  448. @Theory
  449. public void checkMergeEqualTreesInCore(MergeStrategy strategy)
  450. throws Exception {
  451. Git git = Git.wrap(db);
  452. writeTrashFile("d/1", "orig");
  453. git.add().addFilepattern("d/1").call();
  454. RevCommit first = git.commit().setMessage("added d/1").call();
  455. writeTrashFile("d/1", "modified");
  456. RevCommit masterCommit = git.commit().setAll(true)
  457. .setMessage("modified d/1 on master").call();
  458. git.checkout().setCreateBranch(true).setStartPoint(first)
  459. .setName("side").call();
  460. writeTrashFile("d/1", "modified");
  461. RevCommit sideCommit = git.commit().setAll(true)
  462. .setMessage("modified d/1 on side").call();
  463. git.rm().addFilepattern("d/1").call();
  464. git.rm().addFilepattern("d").call();
  465. ThreeWayMerger resolveMerger = (ThreeWayMerger) strategy.newMerger(db,
  466. true);
  467. boolean noProblems = resolveMerger.merge(masterCommit, sideCommit);
  468. assertTrue(noProblems);
  469. }
  470. /**
  471. * Merging two equal subtrees with an incore merger should lead to a merged
  472. * state, without using a Repository (the 'Gerrit' use case).
  473. *
  474. * @param strategy
  475. * @throws Exception
  476. */
  477. @Theory
  478. public void checkMergeEqualTreesInCore_noRepo(MergeStrategy strategy)
  479. throws Exception {
  480. Git git = Git.wrap(db);
  481. writeTrashFile("d/1", "orig");
  482. git.add().addFilepattern("d/1").call();
  483. RevCommit first = git.commit().setMessage("added d/1").call();
  484. writeTrashFile("d/1", "modified");
  485. RevCommit masterCommit = git.commit().setAll(true)
  486. .setMessage("modified d/1 on master").call();
  487. git.checkout().setCreateBranch(true).setStartPoint(first)
  488. .setName("side").call();
  489. writeTrashFile("d/1", "modified");
  490. RevCommit sideCommit = git.commit().setAll(true)
  491. .setMessage("modified d/1 on side").call();
  492. git.rm().addFilepattern("d/1").call();
  493. git.rm().addFilepattern("d").call();
  494. try (ObjectInserter ins = db.newObjectInserter()) {
  495. ThreeWayMerger resolveMerger =
  496. (ThreeWayMerger) strategy.newMerger(ins, db.getConfig());
  497. boolean noProblems = resolveMerger.merge(masterCommit, sideCommit);
  498. assertTrue(noProblems);
  499. }
  500. }
  501. /**
  502. * Merging two equal subtrees when the index and HEAD does not contain any
  503. * file in that subtree should lead to a merged state.
  504. *
  505. * @param strategy
  506. * @throws Exception
  507. */
  508. @Theory
  509. public void checkMergeEqualNewTrees(MergeStrategy strategy)
  510. throws Exception {
  511. Git git = Git.wrap(db);
  512. writeTrashFile("2", "orig");
  513. git.add().addFilepattern("2").call();
  514. RevCommit first = git.commit().setMessage("added 2").call();
  515. writeTrashFile("d/1", "orig");
  516. git.add().addFilepattern("d/1").call();
  517. RevCommit masterCommit = git.commit().setAll(true)
  518. .setMessage("added d/1 on master").call();
  519. git.checkout().setCreateBranch(true).setStartPoint(first)
  520. .setName("side").call();
  521. writeTrashFile("d/1", "orig");
  522. git.add().addFilepattern("d/1").call();
  523. git.commit().setAll(true).setMessage("added d/1 on side").call();
  524. git.rm().addFilepattern("d/1").call();
  525. git.rm().addFilepattern("d").call();
  526. MergeResult mergeRes = git.merge().setStrategy(strategy)
  527. .include(masterCommit).call();
  528. assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
  529. assertEquals(
  530. "[2, mode:100644, content:orig][d/1, mode:100644, content:orig]",
  531. indexState(CONTENT));
  532. }
  533. /**
  534. * Merging two conflicting subtrees when the index and HEAD does not contain
  535. * any file in that subtree should lead to a conflicting state.
  536. *
  537. * @param strategy
  538. * @throws Exception
  539. */
  540. @Theory
  541. public void checkMergeConflictingNewTrees(MergeStrategy strategy)
  542. throws Exception {
  543. Git git = Git.wrap(db);
  544. writeTrashFile("2", "orig");
  545. git.add().addFilepattern("2").call();
  546. RevCommit first = git.commit().setMessage("added 2").call();
  547. writeTrashFile("d/1", "master");
  548. git.add().addFilepattern("d/1").call();
  549. RevCommit masterCommit = git.commit().setAll(true)
  550. .setMessage("added d/1 on master").call();
  551. git.checkout().setCreateBranch(true).setStartPoint(first)
  552. .setName("side").call();
  553. writeTrashFile("d/1", "side");
  554. git.add().addFilepattern("d/1").call();
  555. git.commit().setAll(true).setMessage("added d/1 on side").call();
  556. git.rm().addFilepattern("d/1").call();
  557. git.rm().addFilepattern("d").call();
  558. MergeResult mergeRes = git.merge().setStrategy(strategy)
  559. .include(masterCommit).call();
  560. assertEquals(MergeStatus.CONFLICTING, mergeRes.getMergeStatus());
  561. assertEquals(
  562. "[2, mode:100644, content:orig][d/1, mode:100644, stage:2, content:side][d/1, mode:100644, stage:3, content:master]",
  563. indexState(CONTENT));
  564. }
  565. /**
  566. * Merging two conflicting files when the index contains a tree for that
  567. * path should lead to a failed state.
  568. *
  569. * @param strategy
  570. * @throws Exception
  571. */
  572. @Theory
  573. public void checkMergeConflictingFilesWithTreeInIndex(MergeStrategy strategy)
  574. throws Exception {
  575. Git git = Git.wrap(db);
  576. writeTrashFile("0", "orig");
  577. git.add().addFilepattern("0").call();
  578. RevCommit first = git.commit().setMessage("added 0").call();
  579. writeTrashFile("0", "master");
  580. RevCommit masterCommit = git.commit().setAll(true)
  581. .setMessage("modified 0 on master").call();
  582. git.checkout().setCreateBranch(true).setStartPoint(first)
  583. .setName("side").call();
  584. writeTrashFile("0", "side");
  585. git.commit().setAll(true).setMessage("modified 0 on side").call();
  586. git.rm().addFilepattern("0").call();
  587. writeTrashFile("0/0", "side");
  588. git.add().addFilepattern("0/0").call();
  589. MergeResult mergeRes = git.merge().setStrategy(strategy)
  590. .include(masterCommit).call();
  591. assertEquals(MergeStatus.FAILED, mergeRes.getMergeStatus());
  592. }
  593. /**
  594. * Merging two equal files when the index contains a tree for that path
  595. * should lead to a failed state.
  596. *
  597. * @param strategy
  598. * @throws Exception
  599. */
  600. @Theory
  601. public void checkMergeMergeableFilesWithTreeInIndex(MergeStrategy strategy)
  602. throws Exception {
  603. Git git = Git.wrap(db);
  604. writeTrashFile("0", "orig");
  605. writeTrashFile("1", "1\n2\n3");
  606. git.add().addFilepattern("0").addFilepattern("1").call();
  607. RevCommit first = git.commit().setMessage("added 0, 1").call();
  608. writeTrashFile("1", "1master\n2\n3");
  609. RevCommit masterCommit = git.commit().setAll(true)
  610. .setMessage("modified 1 on master").call();
  611. git.checkout().setCreateBranch(true).setStartPoint(first)
  612. .setName("side").call();
  613. writeTrashFile("1", "1\n2\n3side");
  614. git.commit().setAll(true).setMessage("modified 1 on side").call();
  615. git.rm().addFilepattern("0").call();
  616. writeTrashFile("0/0", "modified");
  617. git.add().addFilepattern("0/0").call();
  618. try {
  619. git.merge().setStrategy(strategy).include(masterCommit).call();
  620. Assert.fail("Didn't get the expected exception");
  621. } catch (CheckoutConflictException e) {
  622. assertEquals(1, e.getConflictingPaths().size());
  623. assertEquals("0/0", e.getConflictingPaths().get(0));
  624. }
  625. }
  626. @Theory
  627. public void checkContentMergeNoConflict(MergeStrategy strategy)
  628. throws Exception {
  629. Git git = Git.wrap(db);
  630. writeTrashFile("file", "1\n2\n3");
  631. git.add().addFilepattern("file").call();
  632. RevCommit first = git.commit().setMessage("added file").call();
  633. writeTrashFile("file", "1master\n2\n3");
  634. git.commit().setAll(true).setMessage("modified file on master").call();
  635. git.checkout().setCreateBranch(true).setStartPoint(first)
  636. .setName("side").call();
  637. writeTrashFile("file", "1\n2\n3side");
  638. RevCommit sideCommit = git.commit().setAll(true)
  639. .setMessage("modified file on side").call();
  640. git.checkout().setName("master").call();
  641. MergeResult result =
  642. git.merge().setStrategy(strategy).include(sideCommit).call();
  643. assertEquals(MergeStatus.MERGED, result.getMergeStatus());
  644. String expected = "1master\n2\n3side";
  645. assertEquals(expected, read("file"));
  646. }
  647. @Theory
  648. public void checkContentMergeNoConflict_noRepo(MergeStrategy strategy)
  649. throws Exception {
  650. Git git = Git.wrap(db);
  651. writeTrashFile("file", "1\n2\n3");
  652. git.add().addFilepattern("file").call();
  653. RevCommit first = git.commit().setMessage("added file").call();
  654. writeTrashFile("file", "1master\n2\n3");
  655. RevCommit masterCommit = git.commit().setAll(true)
  656. .setMessage("modified file on master").call();
  657. git.checkout().setCreateBranch(true).setStartPoint(first)
  658. .setName("side").call();
  659. writeTrashFile("file", "1\n2\n3side");
  660. RevCommit sideCommit = git.commit().setAll(true)
  661. .setMessage("modified file on side").call();
  662. try (ObjectInserter ins = db.newObjectInserter()) {
  663. ResolveMerger merger =
  664. (ResolveMerger) strategy.newMerger(ins, db.getConfig());
  665. boolean noProblems = merger.merge(masterCommit, sideCommit);
  666. assertTrue(noProblems);
  667. assertEquals("1master\n2\n3side",
  668. readBlob(merger.getResultTreeId(), "file"));
  669. }
  670. }
  671. /**
  672. * Merging a change involving large binary files should short-circuit reads.
  673. *
  674. * @param strategy
  675. * @throws Exception
  676. */
  677. @Theory
  678. public void checkContentMergeLargeBinaries(MergeStrategy strategy) throws Exception {
  679. Git git = Git.wrap(db);
  680. final int LINELEN = 72;
  681. // setup a merge that would work correctly if we disconsider the stray '\0'
  682. // that the file contains near the start.
  683. byte[] binary = new byte[LINELEN * 2000];
  684. for (int i = 0; i < binary.length; i++) {
  685. binary[i] = (byte)((i % LINELEN) == 0 ? '\n' : 'x');
  686. }
  687. binary[50] = '\0';
  688. writeTrashFile("file", new String(binary, UTF_8));
  689. git.add().addFilepattern("file").call();
  690. RevCommit first = git.commit().setMessage("added file").call();
  691. // Generate an edit in a single line.
  692. int idx = LINELEN * 1200 + 1;
  693. byte save = binary[idx];
  694. binary[idx] = '@';
  695. writeTrashFile("file", new String(binary, UTF_8));
  696. binary[idx] = save;
  697. git.add().addFilepattern("file").call();
  698. RevCommit masterCommit = git.commit().setAll(true)
  699. .setMessage("modified file l 1200").call();
  700. git.checkout().setCreateBranch(true).setStartPoint(first).setName("side").call();
  701. binary[LINELEN * 1500 + 1] = '!';
  702. writeTrashFile("file", new String(binary, UTF_8));
  703. git.add().addFilepattern("file").call();
  704. RevCommit sideCommit = git.commit().setAll(true)
  705. .setMessage("modified file l 1500").call();
  706. try (ObjectInserter ins = db.newObjectInserter()) {
  707. // Check that we don't read the large blobs.
  708. ObjectInserter forbidInserter = new ObjectInserter.Filter() {
  709. @Override
  710. protected ObjectInserter delegate() {
  711. return ins;
  712. }
  713. @Override
  714. public ObjectReader newReader() {
  715. return new BigReadForbiddenReader(super.newReader(), 8000);
  716. }
  717. };
  718. ResolveMerger merger =
  719. (ResolveMerger) strategy.newMerger(forbidInserter, db.getConfig());
  720. boolean noProblems = merger.merge(masterCommit, sideCommit);
  721. assertFalse(noProblems);
  722. }
  723. }
  724. /**
  725. * Throws an exception if reading beyond limit.
  726. */
  727. static class BigReadForbiddenStream extends ObjectStream.Filter {
  728. long limit;
  729. BigReadForbiddenStream(ObjectStream orig, long limit) {
  730. super(orig.getType(), orig.getSize(), orig);
  731. this.limit = limit;
  732. }
  733. @Override
  734. public long skip(long n) throws IOException {
  735. limit -= n;
  736. if (limit < 0) {
  737. throw new IllegalStateException();
  738. }
  739. return super.skip(n);
  740. }
  741. @Override
  742. public int read() throws IOException {
  743. int r = super.read();
  744. limit--;
  745. if (limit < 0) {
  746. throw new IllegalStateException();
  747. }
  748. return r;
  749. }
  750. @Override
  751. public int read(byte[] b, int off, int len) throws IOException {
  752. int n = super.read(b, off, len);
  753. limit -= n;
  754. if (limit < 0) {
  755. throw new IllegalStateException();
  756. }
  757. return n;
  758. }
  759. }
  760. static class BigReadForbiddenReader extends ObjectReader.Filter {
  761. ObjectReader delegate;
  762. int limit;
  763. @Override
  764. protected ObjectReader delegate() {
  765. return delegate;
  766. }
  767. BigReadForbiddenReader(ObjectReader delegate, int limit) {
  768. this.delegate = delegate;
  769. this.limit = limit;
  770. }
  771. @Override
  772. public ObjectLoader open(AnyObjectId objectId, int typeHint) throws IOException {
  773. ObjectLoader orig = super.open(objectId, typeHint);
  774. return new ObjectLoader.Filter() {
  775. @Override
  776. protected ObjectLoader delegate() {
  777. return orig;
  778. }
  779. @Override
  780. public ObjectStream openStream() throws IOException {
  781. ObjectStream os = orig.openStream();
  782. return new BigReadForbiddenStream(os, limit);
  783. }
  784. };
  785. }
  786. }
  787. @Theory
  788. public void checkContentMergeConflict(MergeStrategy strategy)
  789. throws Exception {
  790. Git git = Git.wrap(db);
  791. writeTrashFile("file", "1\n2\n3");
  792. git.add().addFilepattern("file").call();
  793. RevCommit first = git.commit().setMessage("added file").call();
  794. writeTrashFile("file", "1master\n2\n3");
  795. git.commit().setAll(true).setMessage("modified file on master").call();
  796. git.checkout().setCreateBranch(true).setStartPoint(first)
  797. .setName("side").call();
  798. writeTrashFile("file", "1side\n2\n3");
  799. RevCommit sideCommit = git.commit().setAll(true)
  800. .setMessage("modified file on side").call();
  801. git.checkout().setName("master").call();
  802. MergeResult result =
  803. git.merge().setStrategy(strategy).include(sideCommit).call();
  804. assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
  805. String expected = "<<<<<<< HEAD\n"
  806. + "1master\n"
  807. + "=======\n"
  808. + "1side\n"
  809. + ">>>>>>> " + sideCommit.name() + "\n"
  810. + "2\n"
  811. + "3";
  812. assertEquals(expected, read("file"));
  813. }
  814. @Theory
  815. public void checkContentMergeConflict_noTree(MergeStrategy strategy)
  816. throws Exception {
  817. Git git = Git.wrap(db);
  818. writeTrashFile("file", "1\n2\n3");
  819. git.add().addFilepattern("file").call();
  820. RevCommit first = git.commit().setMessage("added file").call();
  821. writeTrashFile("file", "1master\n2\n3");
  822. RevCommit masterCommit = git.commit().setAll(true)
  823. .setMessage("modified file on master").call();
  824. git.checkout().setCreateBranch(true).setStartPoint(first)
  825. .setName("side").call();
  826. writeTrashFile("file", "1side\n2\n3");
  827. RevCommit sideCommit = git.commit().setAll(true)
  828. .setMessage("modified file on side").call();
  829. try (ObjectInserter ins = db.newObjectInserter()) {
  830. ResolveMerger merger =
  831. (ResolveMerger) strategy.newMerger(ins, db.getConfig());
  832. boolean noProblems = merger.merge(masterCommit, sideCommit);
  833. assertFalse(noProblems);
  834. assertEquals(Arrays.asList("file"), merger.getUnmergedPaths());
  835. MergeFormatter fmt = new MergeFormatter();
  836. merger.getMergeResults().get("file");
  837. try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
  838. fmt.formatMerge(out, merger.getMergeResults().get("file"),
  839. "BASE", "OURS", "THEIRS", UTF_8);
  840. String expected = "<<<<<<< OURS\n"
  841. + "1master\n"
  842. + "=======\n"
  843. + "1side\n"
  844. + ">>>>>>> THEIRS\n"
  845. + "2\n"
  846. + "3";
  847. assertEquals(expected, new String(out.toByteArray(), UTF_8));
  848. }
  849. }
  850. }
  851. /**
  852. * Merging after criss-cross merges. In this case we merge together two
  853. * commits which have two equally good common ancestors
  854. *
  855. * @param strategy
  856. * @throws Exception
  857. */
  858. @Theory
  859. public void checkMergeCrissCross(MergeStrategy strategy) throws Exception {
  860. Git git = Git.wrap(db);
  861. writeTrashFile("1", "1\n2\n3");
  862. git.add().addFilepattern("1").call();
  863. RevCommit first = git.commit().setMessage("added 1").call();
  864. writeTrashFile("1", "1master\n2\n3");
  865. RevCommit masterCommit = git.commit().setAll(true)
  866. .setMessage("modified 1 on master").call();
  867. writeTrashFile("1", "1master2\n2\n3");
  868. git.commit().setAll(true)
  869. .setMessage("modified 1 on master again").call();
  870. git.checkout().setCreateBranch(true).setStartPoint(first)
  871. .setName("side").call();
  872. writeTrashFile("1", "1\n2\na\nb\nc\n3side");
  873. RevCommit sideCommit = git.commit().setAll(true)
  874. .setMessage("modified 1 on side").call();
  875. writeTrashFile("1", "1\n2\n3side2");
  876. git.commit().setAll(true)
  877. .setMessage("modified 1 on side again").call();
  878. MergeResult result = git.merge().setStrategy(strategy)
  879. .include(masterCommit).call();
  880. assertEquals(MergeStatus.MERGED, result.getMergeStatus());
  881. result.getNewHead();
  882. git.checkout().setName("master").call();
  883. result = git.merge().setStrategy(strategy).include(sideCommit).call();
  884. assertEquals(MergeStatus.MERGED, result.getMergeStatus());
  885. // we have two branches which are criss-cross merged. Try to merge the
  886. // tips. This should succeed with RecursiveMerge and fail with
  887. // ResolveMerge
  888. try {
  889. MergeResult mergeResult = git.merge().setStrategy(strategy)
  890. .include(git.getRepository().exactRef("refs/heads/side"))
  891. .call();
  892. assertEquals(MergeStrategy.RECURSIVE, strategy);
  893. assertEquals(MergeResult.MergeStatus.MERGED,
  894. mergeResult.getMergeStatus());
  895. assertEquals("1master2\n2\n3side2", read("1"));
  896. } catch (JGitInternalException e) {
  897. assertEquals(MergeStrategy.RESOLVE, strategy);
  898. assertTrue(e.getCause() instanceof NoMergeBaseException);
  899. assertEquals(((NoMergeBaseException) e.getCause()).getReason(),
  900. MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
  901. }
  902. }
  903. @Theory
  904. public void checkLockedFilesToBeDeleted(MergeStrategy strategy)
  905. throws Exception {
  906. Git git = Git.wrap(db);
  907. writeTrashFile("a.txt", "orig");
  908. writeTrashFile("b.txt", "orig");
  909. git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
  910. RevCommit first = git.commit().setMessage("added a.txt, b.txt").call();
  911. // modify and delete files on the master branch
  912. writeTrashFile("a.txt", "master");
  913. git.rm().addFilepattern("b.txt").call();
  914. RevCommit masterCommit = git.commit()
  915. .setMessage("modified a.txt, deleted b.txt").setAll(true)
  916. .call();
  917. // switch back to a side branch
  918. git.checkout().setCreateBranch(true).setStartPoint(first)
  919. .setName("side").call();
  920. writeTrashFile("c.txt", "side");
  921. git.add().addFilepattern("c.txt").call();
  922. git.commit().setMessage("added c.txt").call();
  923. // Get a handle to the file so on windows it can't be deleted.
  924. try (FileInputStream fis = new FileInputStream(
  925. new File(db.getWorkTree(), "b.txt"))) {
  926. MergeResult mergeRes = git.merge().setStrategy(strategy)
  927. .include(masterCommit).call();
  928. if (mergeRes.getMergeStatus().equals(MergeStatus.FAILED)) {
  929. // probably windows
  930. assertEquals(1, mergeRes.getFailingPaths().size());
  931. assertEquals(MergeFailureReason.COULD_NOT_DELETE,
  932. mergeRes.getFailingPaths().get("b.txt"));
  933. }
  934. assertEquals(
  935. "[a.txt, mode:100644, content:master]"
  936. + "[c.txt, mode:100644, content:side]",
  937. indexState(CONTENT));
  938. }
  939. }
  940. @Theory
  941. public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
  942. File f;
  943. Instant lastTs4, lastTsIndex;
  944. Git git = Git.wrap(db);
  945. File indexFile = db.getIndexFile();
  946. // Create initial content and remember when the last file was written.
  947. f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
  948. lastTs4 = FS.DETECTED.lastModifiedInstant(f);
  949. // add all files, commit and check this doesn't update any working tree
  950. // files and that the index is in a new file system timer tick. Make
  951. // sure to wait long enough before adding so the index doesn't contain
  952. // racily clean entries
  953. fsTick(f);
  954. git.add().addFilepattern(".").call();
  955. RevCommit firstCommit = git.commit().setMessage("initial commit")
  956. .call();
  957. checkConsistentLastModified("0", "1", "2", "3", "4");
  958. checkModificationTimeStampOrder("1", "2", "3", "4", "<.git/index");
  959. assertEquals("Commit should not touch working tree file 4", lastTs4,
  960. FS.DETECTED
  961. .lastModifiedInstant(new File(db.getWorkTree(), "4")));
  962. lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
  963. // Do modifications on the master branch. Then add and commit. This
  964. // should touch only "0", "2 and "3"
  965. fsTick(indexFile);
  966. f = writeTrashFiles(false, "master", null, "1master\n2\n3", "master",
  967. null);
  968. fsTick(f);
  969. git.add().addFilepattern(".").call();
  970. RevCommit masterCommit = git.commit().setMessage("master commit")
  971. .call();
  972. checkConsistentLastModified("0", "1", "2", "3", "4");
  973. checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
  974. + lastTsIndex, "<0", "2", "3", "<.git/index");
  975. lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
  976. // Checkout a side branch. This should touch only "0", "2 and "3"
  977. fsTick(indexFile);
  978. git.checkout().setCreateBranch(true).setStartPoint(firstCommit)
  979. .setName("side").call();
  980. checkConsistentLastModified("0", "1", "2", "3", "4");
  981. checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
  982. + lastTsIndex, "<0", "2", "3", ".git/index");
  983. lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
  984. // This checkout may have populated worktree and index so fast that we
  985. // may have smudged entries now. Check that we have the right content
  986. // and then rewrite the index to get rid of smudged state
  987. assertEquals("[0, mode:100644, content:orig]" //
  988. + "[1, mode:100644, content:orig]" //
  989. + "[2, mode:100644, content:1\n2\n3]" //
  990. + "[3, mode:100644, content:orig]" //
  991. + "[4, mode:100644, content:orig]", //
  992. indexState(CONTENT));
  993. fsTick(indexFile);
  994. f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
  995. lastTs4 = FS.DETECTED.lastModifiedInstant(f);
  996. fsTick(f);
  997. git.add().addFilepattern(".").call();
  998. checkConsistentLastModified("0", "1", "2", "3", "4");
  999. checkModificationTimeStampOrder("*" + lastTsIndex, "<0", "1", "2", "3",
  1000. "4", "<.git/index");
  1001. lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
  1002. // Do modifications on the side branch. Touch only "1", "2 and "3"
  1003. fsTick(indexFile);
  1004. f = writeTrashFiles(false, null, "side", "1\n2\n3side", "side", null);
  1005. fsTick(f);
  1006. git.add().addFilepattern(".").call();
  1007. git.commit().setMessage("side commit").call();
  1008. checkConsistentLastModified("0", "1", "2", "3", "4");
  1009. checkModificationTimeStampOrder("0", "4", "*" + lastTs4, "<*"
  1010. + lastTsIndex, "<1", "2", "3", "<.git/index");
  1011. lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
  1012. // merge master and side. Should only touch "0," "2" and "3"
  1013. fsTick(indexFile);
  1014. git.merge().setStrategy(strategy).include(masterCommit).call();
  1015. checkConsistentLastModified("0", "1", "2", "4");
  1016. checkModificationTimeStampOrder("4", "*" + lastTs4, "<1", "<*"
  1017. + lastTsIndex, "<0", "2", "3", ".git/index");
  1018. assertEquals(
  1019. "[0, mode:100644, content:master]" //
  1020. + "[1, mode:100644, content:side]" //
  1021. + "[2, mode:100644, content:1master\n2\n3side]" //
  1022. + "[3, mode:100644, stage:1, content:orig][3, mode:100644, stage:2, content:side][3, mode:100644, stage:3, content:master]" //
  1023. + "[4, mode:100644, content:orig]", //
  1024. indexState(CONTENT));
  1025. }
  1026. /**
  1027. * Merging two conflicting submodules when the index does not contain any
  1028. * entry for that submodule.
  1029. *
  1030. * @param strategy
  1031. * @throws Exception
  1032. */
  1033. @Theory
  1034. public void checkMergeConflictingSubmodulesWithoutIndex(
  1035. MergeStrategy strategy) throws Exception {
  1036. Git git = Git.wrap(db);
  1037. writeTrashFile("initial", "initial");
  1038. git.add().addFilepattern("initial").call();
  1039. RevCommit initial = git.commit().setMessage("initial").call();
  1040. writeSubmodule("one", ObjectId
  1041. .fromString("1000000000000000000000000000000000000000"));
  1042. git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
  1043. RevCommit right = git.commit().setMessage("added one").call();
  1044. // a second commit in the submodule
  1045. git.checkout().setStartPoint(initial).setName("left")
  1046. .setCreateBranch(true).call();
  1047. writeSubmodule("one", ObjectId
  1048. .fromString("2000000000000000000000000000000000000000"));
  1049. git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
  1050. git.commit().setMessage("a different one").call();
  1051. MergeResult result = git.merge().setStrategy(strategy).include(right)
  1052. .call();
  1053. assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
  1054. Map<String, int[][]> conflicts = result.getConflicts();
  1055. assertEquals(1, conflicts.size());
  1056. assertNotNull(conflicts.get("one"));
  1057. }
  1058. /**
  1059. * Merging two non-conflicting submodules when the index does not contain
  1060. * any entry for either submodule.
  1061. *
  1062. * @param strategy
  1063. * @throws Exception
  1064. */
  1065. @Theory
  1066. public void checkMergeNonConflictingSubmodulesWithoutIndex(
  1067. MergeStrategy strategy) throws Exception {
  1068. Git git = Git.wrap(db);
  1069. writeTrashFile("initial", "initial");
  1070. git.add().addFilepattern("initial").call();
  1071. writeSubmodule("one", ObjectId
  1072. .fromString("1000000000000000000000000000000000000000"));
  1073. // Our initial commit should include a .gitmodules with a bunch of
  1074. // comment lines, so that
  1075. // we don't have a content merge issue when we add a new submodule at
  1076. // the top and a different
  1077. // one at the bottom. This is sort of a hack, but it should allow
  1078. // add/add submodule merges
  1079. String existing = read(Constants.DOT_GIT_MODULES);
  1080. String context = "\n# context\n# more context\n# yet more context\n";
  1081. write(new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
  1082. existing + context + context + context);
  1083. git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
  1084. RevCommit initial = git.commit().setMessage("initial").call();
  1085. writeSubmodule("two", ObjectId
  1086. .fromString("1000000000000000000000000000000000000000"));
  1087. git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
  1088. RevCommit right = git.commit().setMessage("added two").call();
  1089. git.checkout().setStartPoint(initial).setName("left")
  1090. .setCreateBranch(true).call();
  1091. // we need to manually create the submodule for three for the
  1092. // .gitmodules hackery
  1093. addSubmoduleToIndex("three", ObjectId
  1094. .fromString("1000000000000000000000000000000000000000"));
  1095. new File(db.getWorkTree(), "three").mkdir();
  1096. existing = read(Constants.DOT_GIT_MODULES);
  1097. String three = "[submodule \"three\"]\n\tpath = three\n\turl = "
  1098. + db.getDirectory().toURI() + "\n";
  1099. write(new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
  1100. three + existing);
  1101. git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
  1102. git.commit().setMessage("a different one").call();
  1103. MergeResult result = git.merge().setStrategy(strategy).include(right)
  1104. .call();
  1105. assertNull(result.getCheckoutConflicts());
  1106. assertNull(result.getFailingPaths());
  1107. for (String dir : Arrays.asList("one", "two", "three")) {
  1108. assertTrue(new File(db.getWorkTree(), dir).isDirectory());
  1109. }
  1110. }
  1111. /**
  1112. * Merging two commits with a conflict in the virtual ancestor.
  1113. *
  1114. * Content conflicts while merging the virtual ancestor must be ignored.
  1115. *
  1116. * In the following tree, while merging A and B, the recursive algorithm
  1117. * finds as base commits X and Y and tries to merge them: X deletes file "a"
  1118. * and Y modifies it.
  1119. *
  1120. * Note: we delete "a" in (master) and (second-branch) to make avoid manual
  1121. * merges. The situation is the same without those deletions and fixing
  1122. * manually the merge of (merge-both-sides) on both branches.
  1123. *
  1124. * <pre>
  1125. * A (second-branch) Merge branch 'merge-both-sides' into second-branch
  1126. * |\
  1127. * o | Delete modified a
  1128. * | |
  1129. * | | B (master) Merge branch 'merge-both-sides' (into master)
  1130. * | |/|
  1131. * | X | (merge-both-sides) Delete original a
  1132. * | | |
  1133. * | | o Delete modified a
  1134. * | |/
  1135. * |/|
  1136. * Y | Modify a
  1137. * |/
  1138. * o Initial commit
  1139. * </pre>
  1140. *
  1141. * @param strategy
  1142. * @throws Exception
  1143. */
  1144. @Theory
  1145. public void checkMergeConflictInVirtualAncestor(
  1146. MergeStrategy strategy) throws Exception {
  1147. if (!strategy.equals(MergeStrategy.RECURSIVE)) {
  1148. return;
  1149. }
  1150. Git git = Git.wrap(db);
  1151. // master
  1152. writeTrashFile("a", "aaaaaaaa");
  1153. writeTrashFile("b", "bbbbbbbb");
  1154. git.add().addFilepattern("a").addFilepattern("b").call();
  1155. RevCommit first = git.commit().setMessage("Initial commit").call();
  1156. writeTrashFile("a", "aaaaaaaaaaaaaaa");
  1157. git.add().addFilepattern("a").call();
  1158. RevCommit commitY = git.commit().setMessage("Modify a").call();
  1159. git.rm().addFilepattern("a").call();
  1160. // Do more in this commits, so it is not identical to the deletion in
  1161. // second-branch
  1162. writeTrashFile("c", "cccccccc");
  1163. git.add().addFilepattern("c").call();
  1164. git.commit().setMessage("Delete modified a").call();
  1165. // merge-both-sides: starts before "a" is modified and deletes it
  1166. git.checkout().setCreateBranch(true).setStartPoint(first)
  1167. .setName("merge-both-sides").call();
  1168. git.rm().addFilepattern("a").call();
  1169. RevCommit commitX = git.commit().setMessage("Delete original a").call();
  1170. // second branch
  1171. git.checkout().setCreateBranch(true).setStartPoint(commitY)
  1172. .setName("second-branch").call();
  1173. git.rm().addFilepattern("a").call();
  1174. git.commit().setMessage("Delete modified a").call();
  1175. // Merge merge-both-sides into second-branch
  1176. MergeResult mergeResult = git.merge().include(commitX)
  1177. .setStrategy(strategy)
  1178. .call();
  1179. ObjectId commitB = mergeResult.getNewHead();
  1180. // Merge merge-both-sides into master
  1181. git.checkout().setName("master").call();
  1182. mergeResult = git.merge().include(commitX).setStrategy(strategy)
  1183. .call();
  1184. // Now, merge commit A and B (i.e. "master" and "second-branch").
  1185. // None of them have the file "a", so there is no conflict, BUT while
  1186. // building the virtual ancestor it will find a conflict between Y and X
  1187. git.merge().include(commitB).call();
  1188. }
  1189. /**
  1190. * Merging two commits with a file/dir conflict in the virtual ancestor.
  1191. *
  1192. * <p>
  1193. * Those conflicts should be ignored, otherwise the found base can not be used by the
  1194. * RecursiveMerger.
  1195. * <pre>
  1196. * --------------
  1197. * | \
  1198. * | C1 - C4 --- ? master
  1199. * | / /
  1200. * | I - A1 - C2 - C3 second-branch
  1201. * | \ /
  1202. * \ \ /
  1203. * ----A2-------- branch-to-merge
  1204. * </pre>
  1205. * <p>
  1206. * <p>
  1207. * Path "a" is initially a file in I and A1. It is changed to a directory in A2
  1208. * ("branch-to-merge").
  1209. * <p>
  1210. * A2 is merged into "master" and "second-branch". The dir/file merge conflict is resolved
  1211. * manually, results in C4 and C3.
  1212. * <p>
  1213. * While merging C3 and C4, A1 and A2 are the base commits found by the recursive merge that
  1214. * have the dir/file conflict.
  1215. */
  1216. @Theory
  1217. public void checkFileDirMergeConflictInVirtualAncestor_NoConflictInChildren(
  1218. MergeStrategy strategy)
  1219. throws Exception {
  1220. if (!strategy.equals(MergeStrategy.RECURSIVE)) {
  1221. return;
  1222. }
  1223. Git git = Git.wrap(db);
  1224. // master
  1225. writeTrashFile("a", "initial content");
  1226. git.add().addFilepattern("a").call();
  1227. RevCommit commitI = git.commit().setMessage("Initial commit").call();
  1228. writeTrashFile("a", "content in Ancestor 1");
  1229. git.add().addFilepattern("a").call();
  1230. RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call();
  1231. writeTrashFile("a", "content in Child 1 (commited on master)");
  1232. git.add().addFilepattern("a").call();
  1233. // commit C1M
  1234. git.commit().setMessage("Child 1 on master").call();
  1235. git.checkout().setCreateBranch(true).setStartPoint(commitI).setName("branch-to-merge").call();
  1236. // "a" becomes a directory in A2
  1237. git.rm().addFilepattern("a").call();
  1238. writeTrashFile("a/content", "content in Ancestor 2 (commited on branch-to-merge)");
  1239. git.add().addFilepattern("a/content").call();
  1240. RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call();
  1241. // second branch
  1242. git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call();
  1243. writeTrashFile("a", "content in Child 2 (commited on second-branch)");
  1244. git.add().addFilepattern("a").call();
  1245. // commit C2S
  1246. git.commit().setMessage("Child 2 on second-branch").call();
  1247. // Merge branch-to-merge into second-branch
  1248. MergeResult mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
  1249. assertEquals(mergeResult.getNewHead(), null);
  1250. assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
  1251. // Resolve the conflict manually, merge "a" as a file
  1252. git.rm().addFilepattern("a").call();
  1253. git.rm().addFilepattern("a/content").call();
  1254. writeTrashFile("a", "merge conflict resolution");
  1255. git.add().addFilepattern("a").call();
  1256. RevCommit commitC3S = git.commit().setMessage("Child 3 on second bug - resolve merge conflict")
  1257. .call();
  1258. // Merge branch-to-merge into master
  1259. git.checkout().setName("master").call();
  1260. mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
  1261. assertEquals(mergeResult.getNewHead(), null);
  1262. assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
  1263. // Resolve the conflict manually - merge "a" as a file
  1264. git.rm().addFilepattern("a").call();
  1265. git.rm().addFilepattern("a/content").call();
  1266. writeTrashFile("a", "merge conflict resolution");
  1267. git.add().addFilepattern("a").call();
  1268. // commit C4M
  1269. git.commit().setMessage("Child 4 on master - resolve merge conflict").call();
  1270. // Merge C4M (second-branch) into master (C3S)
  1271. // Conflict in virtual base should be here, but there are no conflicts in
  1272. // children
  1273. mergeResult = git.merge().include(commitC3S).call();
  1274. assertEquals(mergeResult.getMergeStatus(), MergeStatus.MERGED);
  1275. }
  1276. @Theory
  1277. public void checkFileDirMergeConflictInVirtualAncestor_ConflictInChildren_FileDir(MergeStrategy strategy)
  1278. throws Exception {
  1279. if (!strategy.equals(MergeStrategy.RECURSIVE)) {
  1280. return;
  1281. }
  1282. Git git = Git.wrap(db);
  1283. // master
  1284. writeTrashFile("a", "initial content");
  1285. git.add().addFilepattern("a").call();
  1286. RevCommit commitI = git.commit().setMessage("Initial commit").call();
  1287. writeTrashFile("a", "content in Ancestor 1");
  1288. git.add().addFilepattern("a").call();
  1289. RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call();
  1290. writeTrashFile("a", "content in Child 1 (commited on master)");
  1291. git.add().addFilepattern("a").call();
  1292. // commit C1M
  1293. git.commit().setMessage("Child 1 on master").call();
  1294. git.checkout().setCreateBranch(true).setStartPoint(commitI).setName("branch-to-merge").call();
  1295. // "a" becomes a directory in A2
  1296. git.rm().addFilepattern("a").call();
  1297. writeTrashFile("a/content", "content in Ancestor 2 (commited on branch-to-merge)");
  1298. git.add().addFilepattern("a/content").call();
  1299. RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call();
  1300. // second branch
  1301. git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call();
  1302. writeTrashFile("a", "content in Child 2 (commited on second-branch)");
  1303. git.add().addFilepattern("a").call();
  1304. // commit C2S
  1305. git.commit().setMessage("Child 2 on second-branch").call();
  1306. // Merge branch-to-merge into second-branch
  1307. MergeResult mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
  1308. assertEquals(mergeResult.getNewHead(), null);
  1309. assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
  1310. // Resolve the conflict manually - write a file
  1311. git.rm().addFilepattern("a").call();
  1312. git.rm().addFilepattern("a/content").call();
  1313. writeTrashFile("a",
  1314. "content in Child 3 (commited on second-branch) - merge conflict resolution");
  1315. git.add().addFilepattern("a").call();
  1316. RevCommit commitC3S = git.commit().setMessage("Child 3 on second bug - resolve merge conflict")
  1317. .call();
  1318. // Merge branch-to-merge into master
  1319. git.checkout().setName("master").call();
  1320. mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
  1321. assertEquals(mergeResult.getNewHead(), null);
  1322. assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
  1323. // Resolve the conflict manually - write a file
  1324. git.rm().addFilepattern("a").call();
  1325. git.rm().addFilepattern("a/content").call();
  1326. writeTrashFile("a", "content in Child 4 (commited on master) - merge conflict resolution");
  1327. git.add().addFilepattern("a").call();
  1328. // commit C4M
  1329. git.commit().setMessage("Child 4 on master - resolve merge conflict").call();
  1330. // Merge C4M (second-branch) into master (C3S)
  1331. // Conflict in virtual base should be here
  1332. mergeResult = git.merge().include(commitC3S).call();
  1333. assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
  1334. String expected =
  1335. "<<<<<<< HEAD\n" + "content in Child 4 (commited on master) - merge conflict resolution\n"
  1336. + "=======\n"
  1337. + "content in Child 3 (commited on second-branch) - merge conflict resolution\n"
  1338. + ">>>>>>> " + commitC3S.name() + "\n";
  1339. assertEquals(expected, read("a"));
  1340. // Nothing was populated from the ancestors.
  1341. assertEquals(
  1342. "[a, mode:100644, stage:2, content:content in Child 4 (commited on master) - merge conflict resolution][a, mode:100644, stage:3, content:content in Child 3 (commited on second-branch) - merge conflict resolution]",
  1343. indexState(CONTENT));
  1344. }
  1345. /**
  1346. * Same test as above, but "a" is a dir in A1 and a file in A2
  1347. */
  1348. @Theory
  1349. public void checkFileDirMergeConflictInVirtualAncestor_ConflictInChildren_DirFile(MergeStrategy strategy)
  1350. throws Exception {
  1351. if (!strategy.equals(MergeStrategy.RECURSIVE)) {
  1352. return;
  1353. }
  1354. Git git = Git.wrap(db);
  1355. // master
  1356. writeTrashFile("a/content", "initial content");
  1357. git.add().addFilepattern("a/content").call();
  1358. RevCommit commitI = git.commit().setMessage("Initial commit").call();
  1359. writeTrashFile("a/content", "content in Ancestor 1");
  1360. git.add().addFilepattern("a/content").call();
  1361. RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call();
  1362. writeTrashFile("a/content", "content in Child 1 (commited on master)");
  1363. git.add().addFilepattern("a/content").call();
  1364. // commit C1M
  1365. git.commit().setMessage("Child 1 on master").call();
  1366. git.checkout().setCreateBranch(true).setStartPoint(commitI).setName("branch-to-merge").call();
  1367. // "a" becomes a file in A2
  1368. git.rm().addFilepattern("a/content").call();
  1369. writeTrashFile("a", "content in Ancestor 2 (commited on branch-to-merge)");
  1370. git.add().addFilepattern("a").call();
  1371. RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call();
  1372. // second branch
  1373. git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call();
  1374. writeTrashFile("a/content", "content in Child 2 (commited on second-branch)");
  1375. git.add().addFilepattern("a/content").call();
  1376. // commit C2S
  1377. git.commit().setMessage("Child 2 on second-branch").call();
  1378. // Merge branch-to-merge into second-branch
  1379. MergeResult mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
  1380. assertEquals(mergeResult.getNewHead(), null);
  1381. assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
  1382. // Resolve the conflict manually - write a file
  1383. git.rm().addFilepattern("a").call();
  1384. git.rm().addFilepattern("a/content").call();
  1385. deleteTrashFile("a/content");
  1386. deleteTrashFile("a");
  1387. writeTrashFile("a", "content in Child 3 (commited on second-branch) - merge conflict resolution");
  1388. git.add().addFilepattern("a").call();
  1389. RevCommit commitC3S = git.commit().setMessage("Child 3 on second bug - resolve merge conflict").call();
  1390. // Merge branch-to-merge into master
  1391. git.checkout().setName("master").call();
  1392. mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
  1393. assertEquals(mergeResult.getNewHead(), null);
  1394. assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
  1395. // Resolve the conflict manually - write a file
  1396. git.rm().addFilepattern("a").call();
  1397. git.rm().addFilepattern("a/content").call();
  1398. deleteTrashFile("a/content");
  1399. deleteTrashFile("a");
  1400. writeTrashFile("a", "content in Child 4 (commited on master) - merge conflict resolution");
  1401. git.add().addFilepattern("a").call();
  1402. // commit C4M
  1403. git.commit().setMessage("Child 4 on master - resolve merge conflict").call();
  1404. // Merge C4M (second-branch) into master (C3S)
  1405. // Conflict in virtual base should be here
  1406. mergeResult = git.merge().include(commitC3S).call();
  1407. assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
  1408. String expected = "<<<<<<< HEAD\n" + "content in Child 4 (commited on master) - merge conflict resolution\n"
  1409. + "=======\n" + "content in Child 3 (commited on second-branch) - merge conflict resolution\n"
  1410. + ">>>>>>> " + commitC3S.name() + "\n";
  1411. assertEquals(expected, read("a"));
  1412. // Nothing was populated from the ancestors.
  1413. assertEquals(
  1414. "[a, mode:100644, stage:2, content:content in Child 4 (commited on master) - merge conflict resolution][a, mode:100644, stage:3, content:content in Child 3 (commited on second-branch) - merge conflict resolution]",
  1415. indexState(CONTENT));
  1416. }
  1417. private void writeSubmodule(String path, ObjectId commit)
  1418. throws IOException, ConfigInvalidException {
  1419. addSubmoduleToIndex(path, commit);
  1420. new File(db.getWorkTree(), path).mkdir();
  1421. StoredConfig config = db.getConfig();
  1422. config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
  1423. ConfigConstants.CONFIG_KEY_URL,
  1424. db.getDirectory().toURI().toString());
  1425. config.save();
  1426. FileBasedConfig modulesConfig = new FileBasedConfig(
  1427. new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
  1428. db.getFS());
  1429. modulesConfig.load();
  1430. modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
  1431. ConfigConstants.CONFIG_KEY_PATH, path);
  1432. modulesConfig.save();
  1433. }
  1434. private void addSubmoduleToIndex(String path, ObjectId commit)
  1435. throws IOException {
  1436. DirCache cache = db.lockDirCache();
  1437. DirCacheEditor editor = cache.editor();
  1438. editor.add(new DirCacheEditor.PathEdit(path) {
  1439. @Override
  1440. public void apply(DirCacheEntry ent) {
  1441. ent.setFileMode(FileMode.GITLINK);
  1442. ent.setObjectId(commit);
  1443. }
  1444. });
  1445. editor.commit();
  1446. }
  1447. // Assert that every specified index entry has the same last modification
  1448. // timestamp as the associated file
  1449. private void checkConsistentLastModified(String... pathes)
  1450. throws IOException {
  1451. DirCache dc = db.readDirCache();
  1452. File workTree = db.getWorkTree();
  1453. for (String path : pathes)
  1454. assertEquals(
  1455. "IndexEntry with path "
  1456. + path
  1457. + " has lastmodified which is different from the worktree file",
  1458. FS.DETECTED.lastModifiedInstant(new File(workTree, path)),
  1459. dc.getEntry(path)
  1460. .getLastModifiedInstant());
  1461. }
  1462. // Assert that modification timestamps of working tree files are as
  1463. // expected. You may specify n files. It is asserted that every file
  1464. // i+1 is not older than file i. If a path of file i+1 is prefixed with "<"
  1465. // then this file must be younger then file i. A path "*<modtime>"
  1466. // represents a file with a modification time of <modtime>
  1467. // E.g. ("a", "b", "<c", "f/a.txt") means: a<=b<c<=f/a.txt
  1468. private void checkModificationTimeStampOrder(String... pathes) {
  1469. Instant lastMod = EPOCH;
  1470. for (String p : pathes) {
  1471. boolean strong = p.startsWith("<");
  1472. boolean fixed = p.charAt(strong ? 1 : 0) == '*';
  1473. p = p.substring((strong ? 1 : 0) + (fixed ? 1 : 0));
  1474. Instant curMod = fixed ? Instant.parse(p)
  1475. : FS.DETECTED
  1476. .lastModifiedInstant(new File(db.getWorkTree(), p));
  1477. if (strong) {
  1478. assertTrue("path " + p + " is not younger than predecesssor",
  1479. curMod.compareTo(lastMod) > 0);
  1480. } else {
  1481. assertTrue("path " + p + " is older than predecesssor",
  1482. curMod.compareTo(lastMod) >= 0);
  1483. }
  1484. }
  1485. }
  1486. private String readBlob(ObjectId treeish, String path) throws Exception {
  1487. try (TestRepository<?> tr = new TestRepository<>(db);
  1488. RevWalk rw = tr.getRevWalk()) {
  1489. RevTree tree = rw.parseTree(treeish);
  1490. RevObject obj = tr.get(tree, path);
  1491. if (obj == null) {
  1492. return null;
  1493. }
  1494. return new String(
  1495. rw.getObjectReader().open(obj, OBJ_BLOB).getBytes(), UTF_8);
  1496. }
  1497. }
  1498. }