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.

RecursiveMergerTest.java 31KB

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