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

Keep line endings for text files committed with CR/LF on text=auto Git never converts line endings if the version in the repository is a text file with CR/LF and text=auto. See [1]: "When the file has been committed with CRLF, no conversion is done." Because the sentence just before is about converting line endings on check-in, I had understood that in commit 60cf85a [2] to mean that no conversion on check-in was to be done. However, as bug 565048 and a code inspection of the C git code showed it really means no conversion is done on check-in *or check-out*. If the text attribute is not set but core.autocrlf = true, this is the same as text=auto eol=crlf. C git does not convert on check-out even on text=auto eol=lf if the index version is a text file with CR/LF. For check-in, one has to look at the intended target, which is done in WorkingTreeIterator since commit 60cf85a. For check-out, it can be done by looking at the source and can thus be done in the AutoLFOutputStream. Additionally, provide a constructor for AutoLFInputStream to do the same; for cases where the equivalent of a check-out is done via an input stream obtained from a blob. (EGit does that in its GitBlobStorage for the Eclipse compare framework; it's more efficient than using a TemporaryBuffer and DirCacheCheckout.getContent(), and it avoids the need for a temporary file.) Adapt existing tests, and add new checkout and merge tests to verify the resulting files have the correct line endings. EGit's GitBlobStorage will need to call the new version of EolStreamTypeUtil.wrapInputStream(). [1] https://git-scm.com/docs/gitattributes#Documentation/gitattributes.txt-Settostringvalueauto [2] https://git.eclipse.org/r/c/jgit/jgit/+/127324 Bug: 565048 Change-Id: If1282ef43e2abd00263541bd10a01fe1f5c619fc Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
3 years ago
Keep line endings for text files committed with CR/LF on text=auto Git never converts line endings if the version in the repository is a text file with CR/LF and text=auto. See [1]: "When the file has been committed with CRLF, no conversion is done." Because the sentence just before is about converting line endings on check-in, I had understood that in commit 60cf85a [2] to mean that no conversion on check-in was to be done. However, as bug 565048 and a code inspection of the C git code showed it really means no conversion is done on check-in *or check-out*. If the text attribute is not set but core.autocrlf = true, this is the same as text=auto eol=crlf. C git does not convert on check-out even on text=auto eol=lf if the index version is a text file with CR/LF. For check-in, one has to look at the intended target, which is done in WorkingTreeIterator since commit 60cf85a. For check-out, it can be done by looking at the source and can thus be done in the AutoLFOutputStream. Additionally, provide a constructor for AutoLFInputStream to do the same; for cases where the equivalent of a check-out is done via an input stream obtained from a blob. (EGit does that in its GitBlobStorage for the Eclipse compare framework; it's more efficient than using a TemporaryBuffer and DirCacheCheckout.getContent(), and it avoids the need for a temporary file.) Adapt existing tests, and add new checkout and merge tests to verify the resulting files have the correct line endings. EGit's GitBlobStorage will need to call the new version of EolStreamTypeUtil.wrapInputStream(). [1] https://git-scm.com/docs/gitattributes#Documentation/gitattributes.txt-Settostringvalueauto [2] https://git.eclipse.org/r/c/jgit/jgit/+/127324 Bug: 565048 Change-Id: If1282ef43e2abd00263541bd10a01fe1f5c619fc Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
3 years ago
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. }