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 31KB

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