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.

CrissCrossMergeTest.java 29KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866
  1. /*
  2. * Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com> 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 org.junit.Assert.assertEquals;
  13. import static org.junit.Assert.assertFalse;
  14. import static org.junit.Assert.assertTrue;
  15. import java.io.BufferedReader;
  16. import java.io.File;
  17. import java.io.FileOutputStream;
  18. import java.io.IOException;
  19. import java.io.InputStreamReader;
  20. import org.eclipse.jgit.api.Git;
  21. import org.eclipse.jgit.dircache.DirCache;
  22. import org.eclipse.jgit.dircache.DirCacheEditor;
  23. import org.eclipse.jgit.dircache.DirCacheEntry;
  24. import org.eclipse.jgit.errors.MissingObjectException;
  25. import org.eclipse.jgit.errors.NoMergeBaseException;
  26. import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
  27. import org.eclipse.jgit.internal.storage.file.FileRepository;
  28. import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
  29. import org.eclipse.jgit.junit.RepositoryTestCase;
  30. import org.eclipse.jgit.junit.TestRepository;
  31. import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
  32. import org.eclipse.jgit.lib.AnyObjectId;
  33. import org.eclipse.jgit.lib.Constants;
  34. import org.eclipse.jgit.lib.FileMode;
  35. import org.eclipse.jgit.lib.ObjectId;
  36. import org.eclipse.jgit.lib.ObjectLoader;
  37. import org.eclipse.jgit.lib.ObjectReader;
  38. import org.eclipse.jgit.lib.Repository;
  39. import org.eclipse.jgit.revwalk.RevBlob;
  40. import org.eclipse.jgit.revwalk.RevCommit;
  41. import org.eclipse.jgit.treewalk.FileTreeIterator;
  42. import org.eclipse.jgit.treewalk.TreeWalk;
  43. import org.eclipse.jgit.treewalk.filter.PathFilter;
  44. import org.junit.Before;
  45. import org.junit.experimental.theories.DataPoints;
  46. import org.junit.experimental.theories.Theories;
  47. import org.junit.experimental.theories.Theory;
  48. import org.junit.runner.RunWith;
  49. @RunWith(Theories.class)
  50. public class CrissCrossMergeTest extends RepositoryTestCase {
  51. static int counter = 0;
  52. @DataPoints
  53. public static MergeStrategy[] strategiesUnderTest = new MergeStrategy[] {
  54. MergeStrategy.RECURSIVE, MergeStrategy.RESOLVE };
  55. public enum IndexState {
  56. Bare, Missing, SameAsHead, SameAsOther, SameAsWorkTree, DifferentFromHeadAndOtherAndWorktree
  57. }
  58. @DataPoints
  59. public static IndexState[] indexStates = IndexState.values();
  60. public enum WorktreeState {
  61. Bare, Missing, SameAsHead, DifferentFromHeadAndOther, SameAsOther;
  62. }
  63. @DataPoints
  64. public static WorktreeState[] worktreeStates = WorktreeState.values();
  65. private TestRepository<FileRepository> db_t;
  66. @Override
  67. @Before
  68. public void setUp() throws Exception {
  69. super.setUp();
  70. db_t = new TestRepository<>(db);
  71. }
  72. @Theory
  73. /**
  74. * Merging m2,s2 from the following topology. In master and side different
  75. * files are touched. No need to do a real content merge.
  76. *
  77. * <pre>
  78. * m0--m1--m2
  79. * \ \/
  80. * \ /\
  81. * s1--s2
  82. * </pre>
  83. */
  84. public void crissCrossMerge(MergeStrategy strategy, IndexState indexState,
  85. WorktreeState worktreeState) throws Exception {
  86. if (!validateStates(indexState, worktreeState))
  87. return;
  88. // fill the repo
  89. BranchBuilder master = db_t.branch("master");
  90. RevCommit m0 = master.commit().add("m", ",m0").message("m0").create();
  91. RevCommit m1 = master.commit().add("m", "m1").message("m1").create();
  92. db_t.getRevWalk().parseCommit(m1);
  93. BranchBuilder side = db_t.branch("side");
  94. RevCommit s1 = side.commit().parent(m0).add("s", "s1").message("s1")
  95. .create();
  96. RevCommit s2 = side.commit().parent(m1).add("m", "m1")
  97. .message("s2(merge)").create();
  98. RevCommit m2 = master.commit().parent(s1).add("s", "s1")
  99. .message("m2(merge)").create();
  100. Git git = Git.wrap(db);
  101. git.checkout().setName("master").call();
  102. modifyWorktree(worktreeState, "m", "side");
  103. modifyWorktree(worktreeState, "s", "side");
  104. modifyIndex(indexState, "m", "side");
  105. modifyIndex(indexState, "s", "side");
  106. ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
  107. worktreeState == WorktreeState.Bare);
  108. if (worktreeState != WorktreeState.Bare)
  109. merger.setWorkingTreeIterator(new FileTreeIterator(db));
  110. try {
  111. boolean expectSuccess = true;
  112. if (!(indexState == IndexState.Bare
  113. || indexState == IndexState.Missing
  114. || indexState == IndexState.SameAsHead || indexState == IndexState.SameAsOther))
  115. // index is dirty
  116. expectSuccess = false;
  117. assertEquals(Boolean.valueOf(expectSuccess),
  118. Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
  119. assertEquals(MergeStrategy.RECURSIVE, strategy);
  120. assertEquals("m1",
  121. contentAsString(db, merger.getResultTreeId(), "m"));
  122. assertEquals("s1",
  123. contentAsString(db, merger.getResultTreeId(), "s"));
  124. } catch (NoMergeBaseException e) {
  125. assertEquals(MergeStrategy.RESOLVE, strategy);
  126. assertEquals(e.getReason(),
  127. MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
  128. }
  129. }
  130. @Theory
  131. /**
  132. * Merging m2,s2 from the following topology. m1 and s1 are the two root
  133. * commits of the repo. In master and side different files are touched.
  134. * No need to do a real content merge.
  135. *
  136. * <pre>
  137. * m1--m2
  138. * \/
  139. * /\
  140. * s1--s2
  141. * </pre>
  142. */
  143. public void crissCrossMerge_twoRoots(MergeStrategy strategy,
  144. IndexState indexState, WorktreeState worktreeState)
  145. throws Exception {
  146. if (!validateStates(indexState, worktreeState))
  147. return;
  148. // fill the repo
  149. BranchBuilder master = db_t.branch("master");
  150. BranchBuilder side = db_t.branch("side");
  151. RevCommit m1 = master.commit().add("m", "m1").message("m1").create();
  152. db_t.getRevWalk().parseCommit(m1);
  153. RevCommit s1 = side.commit().add("s", "s1").message("s1").create();
  154. RevCommit s2 = side.commit().parent(m1).add("m", "m1")
  155. .message("s2(merge)").create();
  156. RevCommit m2 = master.commit().parent(s1).add("s", "s1")
  157. .message("m2(merge)").create();
  158. Git git = Git.wrap(db);
  159. git.checkout().setName("master").call();
  160. modifyWorktree(worktreeState, "m", "side");
  161. modifyWorktree(worktreeState, "s", "side");
  162. modifyIndex(indexState, "m", "side");
  163. modifyIndex(indexState, "s", "side");
  164. ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
  165. worktreeState == WorktreeState.Bare);
  166. if (worktreeState != WorktreeState.Bare)
  167. merger.setWorkingTreeIterator(new FileTreeIterator(db));
  168. try {
  169. boolean expectSuccess = true;
  170. if (!(indexState == IndexState.Bare
  171. || indexState == IndexState.Missing
  172. || indexState == IndexState.SameAsHead || indexState == IndexState.SameAsOther))
  173. // index is dirty
  174. expectSuccess = false;
  175. assertEquals(Boolean.valueOf(expectSuccess),
  176. Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
  177. assertEquals(MergeStrategy.RECURSIVE, strategy);
  178. assertEquals("m1",
  179. contentAsString(db, merger.getResultTreeId(), "m"));
  180. assertEquals("s1",
  181. contentAsString(db, merger.getResultTreeId(), "s"));
  182. } catch (NoMergeBaseException e) {
  183. assertEquals(MergeStrategy.RESOLVE, strategy);
  184. assertEquals(e.getReason(),
  185. MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
  186. }
  187. }
  188. @Theory
  189. /**
  190. * Merging m2,s2 from the following topology. The same file is modified
  191. * in both branches. The modifications should be mergeable. m2 and s2
  192. * contain branch specific conflict resolutions. Therefore m2 and s2 don't contain the same content.
  193. *
  194. * <pre>
  195. * m0--m1--m2
  196. * \ \/
  197. * \ /\
  198. * s1--s2
  199. * </pre>
  200. */
  201. public void crissCrossMerge_mergeable(MergeStrategy strategy,
  202. IndexState indexState, WorktreeState worktreeState)
  203. throws Exception {
  204. if (!validateStates(indexState, worktreeState))
  205. return;
  206. BranchBuilder master = db_t.branch("master");
  207. RevCommit m0 = master.commit().add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9\n")
  208. .message("m0").create();
  209. RevCommit m1 = master.commit()
  210. .add("f", "1-master\n2\n3\n4\n5\n6\n7\n8\n9\n").message("m1")
  211. .create();
  212. db_t.getRevWalk().parseCommit(m1);
  213. BranchBuilder side = db_t.branch("side");
  214. RevCommit s1 = side.commit().parent(m0)
  215. .add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9-side\n").message("s1")
  216. .create();
  217. RevCommit s2 = side.commit().parent(m1)
  218. .add("f", "1-master\n2\n3\n4\n5\n6\n7-res(side)\n8\n9-side\n")
  219. .message("s2(merge)").create();
  220. RevCommit m2 = master
  221. .commit()
  222. .parent(s1)
  223. .add("f", "1-master\n2\n3-res(master)\n4\n5\n6\n7\n8\n9-side\n")
  224. .message("m2(merge)").create();
  225. Git git = Git.wrap(db);
  226. git.checkout().setName("master").call();
  227. modifyWorktree(worktreeState, "f", "side");
  228. modifyIndex(indexState, "f", "side");
  229. ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
  230. worktreeState == WorktreeState.Bare);
  231. if (worktreeState != WorktreeState.Bare)
  232. merger.setWorkingTreeIterator(new FileTreeIterator(db));
  233. try {
  234. boolean expectSuccess = true;
  235. if (!(indexState == IndexState.Bare
  236. || indexState == IndexState.Missing || indexState == IndexState.SameAsHead))
  237. // index is dirty
  238. expectSuccess = false;
  239. else if (worktreeState == WorktreeState.DifferentFromHeadAndOther
  240. || worktreeState == WorktreeState.SameAsOther)
  241. expectSuccess = false;
  242. assertEquals(Boolean.valueOf(expectSuccess),
  243. Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
  244. assertEquals(MergeStrategy.RECURSIVE, strategy);
  245. if (!expectSuccess)
  246. // if the merge was not successful skip testing the state of index and workingtree
  247. return;
  248. assertEquals(
  249. "1-master\n2\n3-res(master)\n4\n5\n6\n7-res(side)\n8\n9-side",
  250. contentAsString(db, merger.getResultTreeId(), "f"));
  251. if (indexState != IndexState.Bare)
  252. assertEquals(
  253. "[f, mode:100644, content:1-master\n2\n3-res(master)\n4\n5\n6\n7-res(side)\n8\n9-side\n]",
  254. indexState(LocalDiskRepositoryTestCase.CONTENT));
  255. if (worktreeState != WorktreeState.Bare
  256. && worktreeState != WorktreeState.Missing)
  257. assertEquals(
  258. "1-master\n2\n3-res(master)\n4\n5\n6\n7-res(side)\n8\n9-side\n",
  259. read("f"));
  260. } catch (NoMergeBaseException e) {
  261. assertEquals(MergeStrategy.RESOLVE, strategy);
  262. assertEquals(e.getReason(),
  263. MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
  264. }
  265. }
  266. @Theory
  267. /**
  268. * Merging m2,s2 from the following topology. The same file is modified
  269. * in both branches. The modifications should be mergeable but only if the automerge of m1 and s1
  270. * is choosen as parent. Choosing m0 as parent would not be sufficient (in contrast to the merge in
  271. * crissCrossMerge_mergeable). m2 and s2 contain branch specific conflict resolutions. Therefore m2
  272. * and s2 don't contain the same content.
  273. *
  274. * <pre>
  275. * m0--m1--m2
  276. * \ \/
  277. * \ /\
  278. * s1--s2
  279. * </pre>
  280. */
  281. public void crissCrossMerge_mergeable2(MergeStrategy strategy,
  282. IndexState indexState, WorktreeState worktreeState)
  283. throws Exception {
  284. if (!validateStates(indexState, worktreeState))
  285. return;
  286. BranchBuilder master = db_t.branch("master");
  287. RevCommit m0 = master.commit().add("f", "1\n2\n3\n")
  288. .message("m0")
  289. .create();
  290. RevCommit m1 = master.commit().add("f", "1-master\n2\n3\n")
  291. .message("m1").create();
  292. db_t.getRevWalk().parseCommit(m1);
  293. BranchBuilder side = db_t.branch("side");
  294. RevCommit s1 = side.commit().parent(m0).add("f", "1\n2\n3-side\n")
  295. .message("s1").create();
  296. RevCommit s2 = side.commit().parent(m1)
  297. .add("f", "1-master\n2\n3-side-r\n")
  298. .message("s2(merge)")
  299. .create();
  300. RevCommit m2 = master.commit().parent(s1)
  301. .add("f", "1-master-r\n2\n3-side\n")
  302. .message("m2(merge)")
  303. .create();
  304. Git git = Git.wrap(db);
  305. git.checkout().setName("master").call();
  306. modifyWorktree(worktreeState, "f", "side");
  307. modifyIndex(indexState, "f", "side");
  308. ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
  309. worktreeState == WorktreeState.Bare);
  310. if (worktreeState != WorktreeState.Bare)
  311. merger.setWorkingTreeIterator(new FileTreeIterator(db));
  312. try {
  313. boolean expectSuccess = true;
  314. if (!(indexState == IndexState.Bare
  315. || indexState == IndexState.Missing || indexState == IndexState.SameAsHead))
  316. // index is dirty
  317. expectSuccess = false;
  318. else if (worktreeState == WorktreeState.DifferentFromHeadAndOther
  319. || worktreeState == WorktreeState.SameAsOther)
  320. expectSuccess = false;
  321. assertEquals(Boolean.valueOf(expectSuccess),
  322. Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
  323. assertEquals(MergeStrategy.RECURSIVE, strategy);
  324. if (!expectSuccess)
  325. // if the merge was not successful skip testing the state of
  326. // index and workingtree
  327. return;
  328. assertEquals(
  329. "1-master-r\n2\n3-side-r",
  330. contentAsString(db, merger.getResultTreeId(), "f"));
  331. if (indexState != IndexState.Bare)
  332. assertEquals(
  333. "[f, mode:100644, content:1-master-r\n2\n3-side-r\n]",
  334. indexState(LocalDiskRepositoryTestCase.CONTENT));
  335. if (worktreeState != WorktreeState.Bare
  336. && worktreeState != WorktreeState.Missing)
  337. assertEquals(
  338. "1-master-r\n2\n3-side-r\n",
  339. read("f"));
  340. } catch (NoMergeBaseException e) {
  341. assertEquals(MergeStrategy.RESOLVE, strategy);
  342. assertEquals(e.getReason(),
  343. MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
  344. }
  345. }
  346. @Theory
  347. /**
  348. * Merging m2,s2 from the following topology. m1 and s1 are not mergeable
  349. * without conflicts. The same file is modified in both branches. The
  350. * modifications should be mergeable but only if the merge result of
  351. * merging m1 and s1 is choosen as parent (including the conflict markers).
  352. *
  353. * <pre>
  354. * m0--m1--m2
  355. * \ \/
  356. * \ /\
  357. * s1--s2
  358. * </pre>
  359. */
  360. public void crissCrossMerge_ParentsNotMergeable(MergeStrategy strategy,
  361. IndexState indexState, WorktreeState worktreeState)
  362. throws Exception {
  363. if (!validateStates(indexState, worktreeState))
  364. return;
  365. BranchBuilder master = db_t.branch("master");
  366. RevCommit m0 = master.commit().add("f", "1\n2\n3\n").message("m0")
  367. .create();
  368. RevCommit m1 = master.commit().add("f", "1\nx(master)\n2\n3\n")
  369. .message("m1").create();
  370. db_t.getRevWalk().parseCommit(m1);
  371. BranchBuilder side = db_t.branch("side");
  372. RevCommit s1 = side.commit().parent(m0)
  373. .add("f", "1\nx(side)\n2\n3\ny(side)\n")
  374. .message("s1").create();
  375. RevCommit s2 = side.commit().parent(m1)
  376. .add("f", "1\nx(side)\n2\n3\ny(side-again)\n")
  377. .message("s2(merge)")
  378. .create();
  379. RevCommit m2 = master.commit().parent(s1)
  380. .add("f", "1\nx(side)\n2\n3\ny(side)\n").message("m2(merge)")
  381. .create();
  382. Git git = Git.wrap(db);
  383. git.checkout().setName("master").call();
  384. modifyWorktree(worktreeState, "f", "side");
  385. modifyIndex(indexState, "f", "side");
  386. ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
  387. worktreeState == WorktreeState.Bare);
  388. if (worktreeState != WorktreeState.Bare)
  389. merger.setWorkingTreeIterator(new FileTreeIterator(db));
  390. try {
  391. boolean expectSuccess = true;
  392. if (!(indexState == IndexState.Bare
  393. || indexState == IndexState.Missing || indexState == IndexState.SameAsHead))
  394. // index is dirty
  395. expectSuccess = false;
  396. else if (worktreeState == WorktreeState.DifferentFromHeadAndOther
  397. || worktreeState == WorktreeState.SameAsOther)
  398. expectSuccess = false;
  399. assertEquals("Merge didn't return as expected: strategy:"
  400. + strategy.getName() + ", indexState:" + indexState
  401. + ", worktreeState:" + worktreeState + " . ",
  402. Boolean.valueOf(expectSuccess),
  403. Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
  404. assertEquals(MergeStrategy.RECURSIVE, strategy);
  405. if (!expectSuccess)
  406. // if the merge was not successful skip testing the state of
  407. // index and workingtree
  408. return;
  409. assertEquals("1\nx(side)\n2\n3\ny(side-again)",
  410. contentAsString(db, merger.getResultTreeId(), "f"));
  411. if (indexState != IndexState.Bare)
  412. assertEquals(
  413. "[f, mode:100644, content:1\nx(side)\n2\n3\ny(side-again)\n]",
  414. indexState(LocalDiskRepositoryTestCase.CONTENT));
  415. if (worktreeState != WorktreeState.Bare
  416. && worktreeState != WorktreeState.Missing)
  417. assertEquals("1\nx(side)\n2\n3\ny(side-again)\n", read("f"));
  418. } catch (NoMergeBaseException e) {
  419. assertEquals(MergeStrategy.RESOLVE, strategy);
  420. assertEquals(e.getReason(),
  421. MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
  422. }
  423. }
  424. @Theory
  425. /**
  426. * Merging m2,s2 from the following topology. The same file is modified
  427. * in both branches. The modifications should be mergeable but only if the automerge of m1 and s1
  428. * is choosen as parent. On both branches delete and modify files untouched on the other branch.
  429. * On both branches create new files. Make sure these files are correctly merged and
  430. * exist in the workingtree.
  431. *
  432. * <pre>
  433. * m0--m1--m2
  434. * \ \/
  435. * \ /\
  436. * s1--s2
  437. * </pre>
  438. */
  439. public void crissCrossMerge_checkOtherFiles(MergeStrategy strategy,
  440. IndexState indexState, WorktreeState worktreeState)
  441. throws Exception {
  442. if (!validateStates(indexState, worktreeState))
  443. return;
  444. BranchBuilder master = db_t.branch("master");
  445. RevCommit m0 = master.commit().add("f", "1\n2\n3\n").add("m.m", "0")
  446. .add("m.d", "0").add("s.m", "0").add("s.d", "0").message("m0")
  447. .create();
  448. RevCommit m1 = master.commit().add("f", "1-master\n2\n3\n")
  449. .add("m.c", "0").add("m.m", "1").rm("m.d").message("m1")
  450. .create();
  451. db_t.getRevWalk().parseCommit(m1);
  452. BranchBuilder side = db_t.branch("side");
  453. RevCommit s1 = side.commit().parent(m0).add("f", "1\n2\n3-side\n")
  454. .add("s.c", "0").add("s.m", "1").rm("s.d").message("s1")
  455. .create();
  456. RevCommit s2 = side.commit().parent(m1)
  457. .add("f", "1-master\n2\n3-side-r\n").add("m.m", "1")
  458. .add("m.c", "0").rm("m.d").message("s2(merge)").create();
  459. RevCommit m2 = master.commit().parent(s1)
  460. .add("f", "1-master-r\n2\n3-side\n").add("s.m", "1")
  461. .add("s.c", "0").rm("s.d").message("m2(merge)").create();
  462. Git git = Git.wrap(db);
  463. git.checkout().setName("master").call();
  464. modifyWorktree(worktreeState, "f", "side");
  465. modifyIndex(indexState, "f", "side");
  466. ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
  467. worktreeState == WorktreeState.Bare);
  468. if (worktreeState != WorktreeState.Bare)
  469. merger.setWorkingTreeIterator(new FileTreeIterator(db));
  470. try {
  471. boolean expectSuccess = true;
  472. if (!(indexState == IndexState.Bare
  473. || indexState == IndexState.Missing || indexState == IndexState.SameAsHead))
  474. // index is dirty
  475. expectSuccess = false;
  476. else if (worktreeState == WorktreeState.DifferentFromHeadAndOther
  477. || worktreeState == WorktreeState.SameAsOther)
  478. expectSuccess = false;
  479. assertEquals(Boolean.valueOf(expectSuccess),
  480. Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
  481. assertEquals(MergeStrategy.RECURSIVE, strategy);
  482. if (!expectSuccess)
  483. // if the merge was not successful skip testing the state of
  484. // index and workingtree
  485. return;
  486. assertEquals(
  487. "1-master-r\n2\n3-side-r",
  488. contentAsString(db, merger.getResultTreeId(), "f"));
  489. if (indexState != IndexState.Bare)
  490. assertEquals(
  491. "[f, mode:100644, content:1-master-r\n2\n3-side-r\n][m.c, mode:100644, content:0][m.m, mode:100644, content:1][s.c, mode:100644, content:0][s.m, mode:100644, content:1]",
  492. indexState(LocalDiskRepositoryTestCase.CONTENT));
  493. if (worktreeState != WorktreeState.Bare
  494. && worktreeState != WorktreeState.Missing) {
  495. assertEquals(
  496. "1-master-r\n2\n3-side-r\n",
  497. read("f"));
  498. assertTrue(check("s.c"));
  499. assertFalse(check("s.d"));
  500. assertTrue(check("s.m"));
  501. assertTrue(check("m.c"));
  502. assertFalse(check("m.d"));
  503. assertTrue(check("m.m"));
  504. }
  505. } catch (NoMergeBaseException e) {
  506. assertEquals(MergeStrategy.RESOLVE, strategy);
  507. assertEquals(e.getReason(),
  508. MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
  509. }
  510. }
  511. @Theory
  512. /**
  513. * Merging m2,s2 from the following topology. The same file is modified
  514. * in both branches. The modifications are not automatically
  515. * mergeable. m2 and s2 contain branch specific conflict resolutions.
  516. * Therefore m2 and s2 don't contain the same content.
  517. *
  518. * <pre>
  519. * m0--m1--m2
  520. * \ \/
  521. * \ /\
  522. * s1--s2
  523. * </pre>
  524. */
  525. public void crissCrossMerge_nonmergeable(MergeStrategy strategy,
  526. IndexState indexState, WorktreeState worktreeState)
  527. throws Exception {
  528. if (!validateStates(indexState, worktreeState))
  529. return;
  530. BranchBuilder master = db_t.branch("master");
  531. RevCommit m0 = master.commit().add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9\n")
  532. .message("m0").create();
  533. RevCommit m1 = master.commit()
  534. .add("f", "1-master\n2\n3\n4\n5\n6\n7\n8\n9\n").message("m1")
  535. .create();
  536. db_t.getRevWalk().parseCommit(m1);
  537. BranchBuilder side = db_t.branch("side");
  538. RevCommit s1 = side.commit().parent(m0)
  539. .add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9-side\n").message("s1")
  540. .create();
  541. RevCommit s2 = side.commit().parent(m1)
  542. .add("f", "1-master\n2\n3\n4\n5\n6\n7-res(side)\n8\n9-side\n")
  543. .message("s2(merge)").create();
  544. RevCommit m2 = master.commit().parent(s1)
  545. .add("f", "1-master\n2\n3\n4\n5\n6\n7-conflict\n8\n9-side\n")
  546. .message("m2(merge)").create();
  547. Git git = Git.wrap(db);
  548. git.checkout().setName("master").call();
  549. modifyWorktree(worktreeState, "f", "side");
  550. modifyIndex(indexState, "f", "side");
  551. ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
  552. worktreeState == WorktreeState.Bare);
  553. if (worktreeState != WorktreeState.Bare)
  554. merger.setWorkingTreeIterator(new FileTreeIterator(db));
  555. try {
  556. assertFalse(merger.merge(new RevCommit[] { m2, s2 }));
  557. assertEquals(MergeStrategy.RECURSIVE, strategy);
  558. if (indexState == IndexState.SameAsHead
  559. && worktreeState == WorktreeState.SameAsHead) {
  560. assertEquals(
  561. "[f, mode:100644, stage:1, content:1-master\n2\n3\n4\n5\n6\n7\n8\n9-side\n]"
  562. + "[f, mode:100644, stage:2, content:1-master\n2\n3\n4\n5\n6\n7-conflict\n8\n9-side\n]"
  563. + "[f, mode:100644, stage:3, content:1-master\n2\n3\n4\n5\n6\n7-res(side)\n8\n9-side\n]",
  564. indexState(LocalDiskRepositoryTestCase.CONTENT));
  565. assertEquals(
  566. "1-master\n2\n3\n4\n5\n6\n<<<<<<< OURS\n7-conflict\n=======\n7-res(side)\n>>>>>>> THEIRS\n8\n9-side\n",
  567. read("f"));
  568. }
  569. } catch (NoMergeBaseException e) {
  570. assertEquals(MergeStrategy.RESOLVE, strategy);
  571. assertEquals(e.getReason(),
  572. MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
  573. }
  574. }
  575. @Theory
  576. /**
  577. * Merging m2,s2 which have three common predecessors.The same file is modified
  578. * in all branches. The modifications should be mergeable. m2 and s2
  579. * contain branch specific conflict resolutions. Therefore m2 and s2
  580. * don't contain the same content.
  581. *
  582. * <pre>
  583. * m1-----m2
  584. * / \/ /
  585. * / /\ /
  586. * m0--o1 x
  587. * \ \/ \
  588. * \ /\ \
  589. * s1-----s2
  590. * </pre>
  591. */
  592. public void crissCrossMerge_ThreeCommonPredecessors(MergeStrategy strategy,
  593. IndexState indexState, WorktreeState worktreeState)
  594. throws Exception {
  595. if (!validateStates(indexState, worktreeState))
  596. return;
  597. BranchBuilder master = db_t.branch("master");
  598. RevCommit m0 = master.commit().add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9\n")
  599. .message("m0").create();
  600. RevCommit m1 = master.commit()
  601. .add("f", "1-master\n2\n3\n4\n5\n6\n7\n8\n9\n").message("m1")
  602. .create();
  603. BranchBuilder side = db_t.branch("side");
  604. RevCommit s1 = side.commit().parent(m0)
  605. .add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9-side\n").message("s1")
  606. .create();
  607. BranchBuilder other = db_t.branch("other");
  608. RevCommit o1 = other.commit().parent(m0)
  609. .add("f", "1\n2\n3\n4\n5-other\n6\n7\n8\n9\n").message("o1")
  610. .create();
  611. RevCommit m2 = master
  612. .commit()
  613. .parent(s1)
  614. .parent(o1)
  615. .add("f",
  616. "1-master\n2\n3-res(master)\n4\n5-other\n6\n7\n8\n9-side\n")
  617. .message("m2(merge)").create();
  618. RevCommit s2 = side
  619. .commit()
  620. .parent(m1)
  621. .parent(o1)
  622. .add("f",
  623. "1-master\n2\n3\n4\n5-other\n6\n7-res(side)\n8\n9-side\n")
  624. .message("s2(merge)").create();
  625. Git git = Git.wrap(db);
  626. git.checkout().setName("master").call();
  627. modifyWorktree(worktreeState, "f", "side");
  628. modifyIndex(indexState, "f", "side");
  629. ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
  630. worktreeState == WorktreeState.Bare);
  631. if (worktreeState != WorktreeState.Bare)
  632. merger.setWorkingTreeIterator(new FileTreeIterator(db));
  633. try {
  634. boolean expectSuccess = true;
  635. if (!(indexState == IndexState.Bare
  636. || indexState == IndexState.Missing || indexState == IndexState.SameAsHead))
  637. // index is dirty
  638. expectSuccess = false;
  639. else if (worktreeState == WorktreeState.DifferentFromHeadAndOther
  640. || worktreeState == WorktreeState.SameAsOther)
  641. // workingtree is dirty
  642. expectSuccess = false;
  643. assertEquals(Boolean.valueOf(expectSuccess),
  644. Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
  645. assertEquals(MergeStrategy.RECURSIVE, strategy);
  646. if (!expectSuccess)
  647. // if the merge was not successful skip testing the state of index and workingtree
  648. return;
  649. assertEquals(
  650. "1-master\n2\n3-res(master)\n4\n5-other\n6\n7-res(side)\n8\n9-side",
  651. contentAsString(db, merger.getResultTreeId(), "f"));
  652. if (indexState != IndexState.Bare)
  653. assertEquals(
  654. "[f, mode:100644, content:1-master\n2\n3-res(master)\n4\n5-other\n6\n7-res(side)\n8\n9-side\n]",
  655. indexState(LocalDiskRepositoryTestCase.CONTENT));
  656. if (worktreeState != WorktreeState.Bare
  657. && worktreeState != WorktreeState.Missing)
  658. assertEquals(
  659. "1-master\n2\n3-res(master)\n4\n5-other\n6\n7-res(side)\n8\n9-side\n",
  660. read("f"));
  661. } catch (NoMergeBaseException e) {
  662. assertEquals(MergeStrategy.RESOLVE, strategy);
  663. assertEquals(e.getReason(),
  664. MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
  665. }
  666. }
  667. void modifyIndex(IndexState indexState, String path, String other)
  668. throws Exception {
  669. RevBlob blob;
  670. switch (indexState) {
  671. case Missing:
  672. setIndex(null, path);
  673. break;
  674. case SameAsHead:
  675. setIndex(contentId(Constants.HEAD, path), path);
  676. break;
  677. case SameAsOther:
  678. setIndex(contentId(other, path), path);
  679. break;
  680. case SameAsWorkTree:
  681. blob = db_t.blob(read(path));
  682. setIndex(blob, path);
  683. break;
  684. case DifferentFromHeadAndOtherAndWorktree:
  685. blob = db_t.blob(Integer.toString(counter++));
  686. setIndex(blob, path);
  687. break;
  688. case Bare:
  689. File file = new File(db.getDirectory(), "index");
  690. if (!file.exists())
  691. return;
  692. db.close();
  693. file.delete();
  694. db = new FileRepository(db.getDirectory());
  695. db_t = new TestRepository<>(db);
  696. break;
  697. }
  698. }
  699. private void setIndex(ObjectId id, String path)
  700. throws MissingObjectException, IOException {
  701. DirCache lockedDircache;
  702. DirCacheEditor dcedit;
  703. lockedDircache = db.lockDirCache();
  704. dcedit = lockedDircache.editor();
  705. try {
  706. if (id != null) {
  707. final ObjectLoader contLoader = db.newObjectReader().open(id);
  708. dcedit.add(new DirCacheEditor.PathEdit(path) {
  709. @Override
  710. public void apply(DirCacheEntry ent) {
  711. ent.setFileMode(FileMode.REGULAR_FILE);
  712. ent.setLength(contLoader.getSize());
  713. ent.setObjectId(id);
  714. }
  715. });
  716. } else
  717. dcedit.add(new DirCacheEditor.DeletePath(path));
  718. } finally {
  719. dcedit.commit();
  720. }
  721. }
  722. private ObjectId contentId(String revName, String path) throws Exception {
  723. RevCommit headCommit = db_t.getRevWalk().parseCommit(
  724. db.resolve(revName));
  725. db_t.parseBody(headCommit);
  726. return db_t.get(headCommit.getTree(), path).getId();
  727. }
  728. void modifyWorktree(WorktreeState worktreeState, String path, String other)
  729. throws Exception {
  730. switch (worktreeState) {
  731. case Missing:
  732. new File(db.getWorkTree(), path).delete();
  733. break;
  734. case DifferentFromHeadAndOther:
  735. write(new File(db.getWorkTree(), path),
  736. Integer.toString(counter++));
  737. break;
  738. case SameAsHead:
  739. try (FileOutputStream fos = new FileOutputStream(
  740. new File(db.getWorkTree(), path))) {
  741. db.newObjectReader().open(contentId(Constants.HEAD, path))
  742. .copyTo(fos);
  743. }
  744. break;
  745. case SameAsOther:
  746. try (FileOutputStream fos = new FileOutputStream(
  747. new File(db.getWorkTree(), path))) {
  748. db.newObjectReader().open(contentId(other, path)).copyTo(fos);
  749. }
  750. break;
  751. case Bare:
  752. if (db.isBare())
  753. return;
  754. File workTreeFile = db.getWorkTree();
  755. db.getConfig().setBoolean("core", null, "bare", true);
  756. db.getDirectory().renameTo(new File(workTreeFile, "test.git"));
  757. db = new FileRepository(new File(workTreeFile, "test.git"));
  758. db_t = new TestRepository<>(db);
  759. }
  760. }
  761. private boolean validateStates(IndexState indexState,
  762. WorktreeState worktreeState) {
  763. if (worktreeState == WorktreeState.Bare
  764. && indexState != IndexState.Bare)
  765. return false;
  766. if (worktreeState != WorktreeState.Bare
  767. && indexState == IndexState.Bare)
  768. return false;
  769. if (worktreeState != WorktreeState.DifferentFromHeadAndOther
  770. && indexState == IndexState.SameAsWorkTree)
  771. // would be a duplicate: the combination WorktreeState.X and
  772. // IndexState.X already covered this
  773. return false;
  774. return true;
  775. }
  776. private String contentAsString(Repository r, ObjectId treeId, String path)
  777. throws MissingObjectException, IOException {
  778. AnyObjectId blobId;
  779. try (TreeWalk tw = new TreeWalk(r)) {
  780. tw.addTree(treeId);
  781. tw.setFilter(PathFilter.create(path));
  782. tw.setRecursive(true);
  783. if (!tw.next()) {
  784. return null;
  785. }
  786. blobId = tw.getObjectId(0);
  787. }
  788. StringBuilder result = new StringBuilder();
  789. try (ObjectReader or = r.newObjectReader();
  790. BufferedReader br = new BufferedReader(new InputStreamReader(
  791. or.open(blobId).openStream(), UTF_8))) {
  792. String line;
  793. boolean first = true;
  794. while ((line = br.readLine()) != null) {
  795. if (!first) {
  796. result.append('\n');
  797. }
  798. result.append(line);
  799. first = false;
  800. }
  801. return result.toString();
  802. }
  803. }
  804. }