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.

ResolveMergerTest.java 35KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009
  1. /*
  2. * Copyright (C) 2012, Robin Stocker <robin@nibor.org>
  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.eclipse.jgit.lib.Constants.OBJ_BLOB;
  46. import static org.junit.Assert.assertEquals;
  47. import static org.junit.Assert.assertFalse;
  48. import static org.junit.Assert.assertTrue;
  49. import java.io.ByteArrayOutputStream;
  50. import java.io.File;
  51. import java.io.FileInputStream;
  52. import java.io.IOException;
  53. import java.util.Arrays;
  54. import org.eclipse.jgit.api.Git;
  55. import org.eclipse.jgit.api.MergeResult;
  56. import org.eclipse.jgit.api.MergeResult.MergeStatus;
  57. import org.eclipse.jgit.api.errors.CheckoutConflictException;
  58. import org.eclipse.jgit.api.errors.GitAPIException;
  59. import org.eclipse.jgit.api.errors.JGitInternalException;
  60. import org.eclipse.jgit.dircache.DirCache;
  61. import org.eclipse.jgit.errors.NoMergeBaseException;
  62. import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
  63. import org.eclipse.jgit.junit.RepositoryTestCase;
  64. import org.eclipse.jgit.junit.TestRepository;
  65. import org.eclipse.jgit.lib.ObjectId;
  66. import org.eclipse.jgit.lib.ObjectInserter;
  67. import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
  68. import org.eclipse.jgit.revwalk.RevCommit;
  69. import org.eclipse.jgit.revwalk.RevObject;
  70. import org.eclipse.jgit.revwalk.RevTree;
  71. import org.eclipse.jgit.revwalk.RevWalk;
  72. import org.eclipse.jgit.treewalk.FileTreeIterator;
  73. import org.eclipse.jgit.util.FS;
  74. import org.eclipse.jgit.util.FileUtils;
  75. import org.junit.Assert;
  76. import org.junit.experimental.theories.DataPoint;
  77. import org.junit.experimental.theories.Theories;
  78. import org.junit.experimental.theories.Theory;
  79. import org.junit.runner.RunWith;
  80. @RunWith(Theories.class)
  81. public class ResolveMergerTest extends RepositoryTestCase {
  82. @DataPoint
  83. public static MergeStrategy resolve = MergeStrategy.RESOLVE;
  84. @DataPoint
  85. public static MergeStrategy recursive = MergeStrategy.RECURSIVE;
  86. @Theory
  87. public void failingDeleteOfDirectoryWithUntrackedContent(
  88. MergeStrategy strategy) throws Exception {
  89. File folder1 = new File(db.getWorkTree(), "folder1");
  90. FileUtils.mkdir(folder1);
  91. File file = new File(folder1, "file1.txt");
  92. write(file, "folder1--file1.txt");
  93. file = new File(folder1, "file2.txt");
  94. write(file, "folder1--file2.txt");
  95. try (Git git = new Git(db)) {
  96. git.add().addFilepattern(folder1.getName()).call();
  97. RevCommit base = git.commit().setMessage("adding folder").call();
  98. recursiveDelete(folder1);
  99. git.rm().addFilepattern("folder1/file1.txt")
  100. .addFilepattern("folder1/file2.txt").call();
  101. RevCommit other = git.commit()
  102. .setMessage("removing folders on 'other'").call();
  103. git.checkout().setName(base.name()).call();
  104. file = new File(db.getWorkTree(), "unrelated.txt");
  105. write(file, "unrelated");
  106. git.add().addFilepattern("unrelated.txt").call();
  107. RevCommit head = git.commit().setMessage("Adding another file").call();
  108. // Untracked file to cause failing path for delete() of folder1
  109. // but that's ok.
  110. file = new File(folder1, "file3.txt");
  111. write(file, "folder1--file3.txt");
  112. ResolveMerger merger = (ResolveMerger) strategy.newMerger(db, false);
  113. merger.setCommitNames(new String[] { "BASE", "HEAD", "other" });
  114. merger.setWorkingTreeIterator(new FileTreeIterator(db));
  115. boolean ok = merger.merge(head.getId(), other.getId());
  116. assertTrue(ok);
  117. assertTrue(file.exists());
  118. }
  119. }
  120. /**
  121. * Merging two conflicting subtrees when the index does not contain any file
  122. * in that subtree should lead to a conflicting state.
  123. *
  124. * @param strategy
  125. * @throws Exception
  126. */
  127. @Theory
  128. public void checkMergeConflictingTreesWithoutIndex(MergeStrategy strategy)
  129. throws Exception {
  130. Git git = Git.wrap(db);
  131. writeTrashFile("d/1", "orig");
  132. git.add().addFilepattern("d/1").call();
  133. RevCommit first = git.commit().setMessage("added d/1").call();
  134. writeTrashFile("d/1", "master");
  135. RevCommit masterCommit = git.commit().setAll(true)
  136. .setMessage("modified d/1 on master").call();
  137. git.checkout().setCreateBranch(true).setStartPoint(first)
  138. .setName("side").call();
  139. writeTrashFile("d/1", "side");
  140. git.commit().setAll(true).setMessage("modified d/1 on side").call();
  141. git.rm().addFilepattern("d/1").call();
  142. git.rm().addFilepattern("d").call();
  143. MergeResult mergeRes = git.merge().setStrategy(strategy)
  144. .include(masterCommit).call();
  145. assertEquals(MergeStatus.CONFLICTING, mergeRes.getMergeStatus());
  146. assertEquals(
  147. "[d/1, mode:100644, stage:1, content:orig][d/1, mode:100644, stage:2, content:side][d/1, mode:100644, stage:3, content:master]",
  148. indexState(CONTENT));
  149. }
  150. /**
  151. * Merging two different but mergeable subtrees when the index does not
  152. * contain any file in that subtree should lead to a merged state.
  153. *
  154. * @param strategy
  155. * @throws Exception
  156. */
  157. @Theory
  158. public void checkMergeMergeableTreesWithoutIndex(MergeStrategy strategy)
  159. throws Exception {
  160. Git git = Git.wrap(db);
  161. writeTrashFile("d/1", "1\n2\n3");
  162. git.add().addFilepattern("d/1").call();
  163. RevCommit first = git.commit().setMessage("added d/1").call();
  164. writeTrashFile("d/1", "1master\n2\n3");
  165. RevCommit masterCommit = git.commit().setAll(true)
  166. .setMessage("modified d/1 on master").call();
  167. git.checkout().setCreateBranch(true).setStartPoint(first)
  168. .setName("side").call();
  169. writeTrashFile("d/1", "1\n2\n3side");
  170. git.commit().setAll(true).setMessage("modified d/1 on side").call();
  171. git.rm().addFilepattern("d/1").call();
  172. git.rm().addFilepattern("d").call();
  173. MergeResult mergeRes = git.merge().setStrategy(strategy)
  174. .include(masterCommit).call();
  175. assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
  176. assertEquals("[d/1, mode:100644, content:1master\n2\n3side]",
  177. indexState(CONTENT));
  178. }
  179. /**
  180. * An existing directory without tracked content should not prevent merging
  181. * a tree where that directory exists.
  182. *
  183. * @param strategy
  184. * @throws Exception
  185. */
  186. @Theory
  187. public void checkUntrackedFolderIsNotAConflict(
  188. MergeStrategy strategy) throws Exception {
  189. Git git = Git.wrap(db);
  190. writeTrashFile("d/1", "1");
  191. git.add().addFilepattern("d/1").call();
  192. RevCommit first = git.commit().setMessage("added d/1").call();
  193. writeTrashFile("e/1", "4");
  194. git.add().addFilepattern("e/1").call();
  195. RevCommit masterCommit = git.commit().setMessage("added e/1").call();
  196. git.checkout().setCreateBranch(true).setStartPoint(first)
  197. .setName("side").call();
  198. writeTrashFile("f/1", "5");
  199. git.add().addFilepattern("f/1").call();
  200. git.commit().setAll(true).setMessage("added f/1")
  201. .call();
  202. // Untracked directory e shall not conflict with merged e/1
  203. writeTrashFile("e/2", "d two");
  204. MergeResult mergeRes = git.merge().setStrategy(strategy)
  205. .include(masterCommit).call();
  206. assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
  207. assertEquals(
  208. "[d/1, mode:100644, content:1][e/1, mode:100644, content:4][f/1, mode:100644, content:5]",
  209. indexState(CONTENT));
  210. }
  211. /**
  212. * A tracked file is replaced by a folder in THEIRS.
  213. *
  214. * @param strategy
  215. * @throws Exception
  216. */
  217. @Theory
  218. public void checkFileReplacedByFolderInTheirs(MergeStrategy strategy)
  219. throws Exception {
  220. Git git = Git.wrap(db);
  221. writeTrashFile("sub", "file");
  222. git.add().addFilepattern("sub").call();
  223. RevCommit first = git.commit().setMessage("initial").call();
  224. git.checkout().setCreateBranch(true).setStartPoint(first)
  225. .setName("side").call();
  226. git.rm().addFilepattern("sub").call();
  227. writeTrashFile("sub/file", "subfile");
  228. git.add().addFilepattern("sub/file").call();
  229. RevCommit masterCommit = git.commit().setMessage("file -> folder")
  230. .call();
  231. git.checkout().setName("master").call();
  232. writeTrashFile("noop", "other");
  233. git.add().addFilepattern("noop").call();
  234. git.commit().setAll(true).setMessage("noop").call();
  235. MergeResult mergeRes = git.merge().setStrategy(strategy)
  236. .include(masterCommit).call();
  237. assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
  238. assertEquals(
  239. "[noop, mode:100644, content:other][sub/file, mode:100644, content:subfile]",
  240. indexState(CONTENT));
  241. }
  242. /**
  243. * A tracked file is replaced by a folder in OURS.
  244. *
  245. * @param strategy
  246. * @throws Exception
  247. */
  248. @Theory
  249. public void checkFileReplacedByFolderInOurs(MergeStrategy strategy)
  250. throws Exception {
  251. Git git = Git.wrap(db);
  252. writeTrashFile("sub", "file");
  253. git.add().addFilepattern("sub").call();
  254. RevCommit first = git.commit().setMessage("initial").call();
  255. git.checkout().setCreateBranch(true).setStartPoint(first)
  256. .setName("side").call();
  257. writeTrashFile("noop", "other");
  258. git.add().addFilepattern("noop").call();
  259. RevCommit sideCommit = git.commit().setAll(true).setMessage("noop")
  260. .call();
  261. git.checkout().setName("master").call();
  262. git.rm().addFilepattern("sub").call();
  263. writeTrashFile("sub/file", "subfile");
  264. git.add().addFilepattern("sub/file").call();
  265. git.commit().setMessage("file -> folder")
  266. .call();
  267. MergeResult mergeRes = git.merge().setStrategy(strategy)
  268. .include(sideCommit).call();
  269. assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
  270. assertEquals(
  271. "[noop, mode:100644, content:other][sub/file, mode:100644, content:subfile]",
  272. indexState(CONTENT));
  273. }
  274. /**
  275. * An existing directory without tracked content should not prevent merging
  276. * a file with that name.
  277. *
  278. * @param strategy
  279. * @throws Exception
  280. */
  281. @Theory
  282. public void checkUntrackedEmpytFolderIsNotAConflictWithFile(
  283. MergeStrategy strategy)
  284. throws Exception {
  285. Git git = Git.wrap(db);
  286. writeTrashFile("d/1", "1");
  287. git.add().addFilepattern("d/1").call();
  288. RevCommit first = git.commit().setMessage("added d/1").call();
  289. writeTrashFile("e", "4");
  290. git.add().addFilepattern("e").call();
  291. RevCommit masterCommit = git.commit().setMessage("added e").call();
  292. git.checkout().setCreateBranch(true).setStartPoint(first)
  293. .setName("side").call();
  294. writeTrashFile("f/1", "5");
  295. git.add().addFilepattern("f/1").call();
  296. git.commit().setAll(true).setMessage("added f/1").call();
  297. // Untracked empty directory hierarcy e/1 shall not conflict with merged
  298. // e/1
  299. FileUtils.mkdirs(new File(trash, "e/1"), true);
  300. MergeResult mergeRes = git.merge().setStrategy(strategy)
  301. .include(masterCommit).call();
  302. assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
  303. assertEquals(
  304. "[d/1, mode:100644, content:1][e, mode:100644, content:4][f/1, mode:100644, content:5]",
  305. indexState(CONTENT));
  306. }
  307. @Theory
  308. public void mergeWithCrlfInWT(MergeStrategy strategy) throws IOException,
  309. GitAPIException {
  310. Git git = Git.wrap(db);
  311. db.getConfig().setString("core", null, "autocrlf", "false");
  312. db.getConfig().save();
  313. writeTrashFile("crlf.txt", "some\r\ndata\r\n");
  314. git.add().addFilepattern("crlf.txt").call();
  315. git.commit().setMessage("base").call();
  316. git.branchCreate().setName("brancha").call();
  317. writeTrashFile("crlf.txt", "some\r\nmore\r\ndata\r\n");
  318. git.add().addFilepattern("crlf.txt").call();
  319. git.commit().setMessage("on master").call();
  320. git.checkout().setName("brancha").call();
  321. writeTrashFile("crlf.txt", "some\r\ndata\r\ntoo\r\n");
  322. git.add().addFilepattern("crlf.txt").call();
  323. git.commit().setMessage("on brancha").call();
  324. db.getConfig().setString("core", null, "autocrlf", "input");
  325. db.getConfig().save();
  326. MergeResult mergeResult = git.merge().setStrategy(strategy)
  327. .include(db.resolve("master"))
  328. .call();
  329. assertEquals(MergeResult.MergeStatus.MERGED,
  330. mergeResult.getMergeStatus());
  331. }
  332. /**
  333. * Merging two equal subtrees when the index does not contain any file in
  334. * that subtree should lead to a merged state.
  335. *
  336. * @param strategy
  337. * @throws Exception
  338. */
  339. @Theory
  340. public void checkMergeEqualTreesWithoutIndex(MergeStrategy strategy)
  341. throws Exception {
  342. Git git = Git.wrap(db);
  343. writeTrashFile("d/1", "orig");
  344. git.add().addFilepattern("d/1").call();
  345. RevCommit first = git.commit().setMessage("added d/1").call();
  346. writeTrashFile("d/1", "modified");
  347. RevCommit masterCommit = git.commit().setAll(true)
  348. .setMessage("modified d/1 on master").call();
  349. git.checkout().setCreateBranch(true).setStartPoint(first)
  350. .setName("side").call();
  351. writeTrashFile("d/1", "modified");
  352. git.commit().setAll(true).setMessage("modified d/1 on side").call();
  353. git.rm().addFilepattern("d/1").call();
  354. git.rm().addFilepattern("d").call();
  355. MergeResult mergeRes = git.merge().setStrategy(strategy)
  356. .include(masterCommit).call();
  357. assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
  358. assertEquals("[d/1, mode:100644, content:modified]",
  359. indexState(CONTENT));
  360. }
  361. /**
  362. * Merging two equal subtrees with an incore merger should lead to a merged
  363. * state.
  364. *
  365. * @param strategy
  366. * @throws Exception
  367. */
  368. @Theory
  369. public void checkMergeEqualTreesInCore(MergeStrategy strategy)
  370. throws Exception {
  371. Git git = Git.wrap(db);
  372. writeTrashFile("d/1", "orig");
  373. git.add().addFilepattern("d/1").call();
  374. RevCommit first = git.commit().setMessage("added d/1").call();
  375. writeTrashFile("d/1", "modified");
  376. RevCommit masterCommit = git.commit().setAll(true)
  377. .setMessage("modified d/1 on master").call();
  378. git.checkout().setCreateBranch(true).setStartPoint(first)
  379. .setName("side").call();
  380. writeTrashFile("d/1", "modified");
  381. RevCommit sideCommit = git.commit().setAll(true)
  382. .setMessage("modified d/1 on side").call();
  383. git.rm().addFilepattern("d/1").call();
  384. git.rm().addFilepattern("d").call();
  385. ThreeWayMerger resolveMerger = (ThreeWayMerger) strategy.newMerger(db,
  386. true);
  387. boolean noProblems = resolveMerger.merge(masterCommit, sideCommit);
  388. assertTrue(noProblems);
  389. }
  390. /**
  391. * Merging two equal subtrees with an incore merger should lead to a merged
  392. * state, without using a Repository (the 'Gerrit' use case).
  393. *
  394. * @param strategy
  395. * @throws Exception
  396. */
  397. @Theory
  398. public void checkMergeEqualTreesInCore_noRepo(MergeStrategy strategy)
  399. throws Exception {
  400. Git git = Git.wrap(db);
  401. writeTrashFile("d/1", "orig");
  402. git.add().addFilepattern("d/1").call();
  403. RevCommit first = git.commit().setMessage("added d/1").call();
  404. writeTrashFile("d/1", "modified");
  405. RevCommit masterCommit = git.commit().setAll(true)
  406. .setMessage("modified d/1 on master").call();
  407. git.checkout().setCreateBranch(true).setStartPoint(first)
  408. .setName("side").call();
  409. writeTrashFile("d/1", "modified");
  410. RevCommit sideCommit = git.commit().setAll(true)
  411. .setMessage("modified d/1 on side").call();
  412. git.rm().addFilepattern("d/1").call();
  413. git.rm().addFilepattern("d").call();
  414. try (ObjectInserter ins = db.newObjectInserter()) {
  415. ThreeWayMerger resolveMerger =
  416. (ThreeWayMerger) strategy.newMerger(ins, db.getConfig());
  417. boolean noProblems = resolveMerger.merge(masterCommit, sideCommit);
  418. assertTrue(noProblems);
  419. }
  420. }
  421. /**
  422. * Merging two equal subtrees when the index and HEAD does not contain any
  423. * file in that subtree should lead to a merged state.
  424. *
  425. * @param strategy
  426. * @throws Exception
  427. */
  428. @Theory
  429. public void checkMergeEqualNewTrees(MergeStrategy strategy)
  430. throws Exception {
  431. Git git = Git.wrap(db);
  432. writeTrashFile("2", "orig");
  433. git.add().addFilepattern("2").call();
  434. RevCommit first = git.commit().setMessage("added 2").call();
  435. writeTrashFile("d/1", "orig");
  436. git.add().addFilepattern("d/1").call();
  437. RevCommit masterCommit = git.commit().setAll(true)
  438. .setMessage("added d/1 on master").call();
  439. git.checkout().setCreateBranch(true).setStartPoint(first)
  440. .setName("side").call();
  441. writeTrashFile("d/1", "orig");
  442. git.add().addFilepattern("d/1").call();
  443. git.commit().setAll(true).setMessage("added d/1 on side").call();
  444. git.rm().addFilepattern("d/1").call();
  445. git.rm().addFilepattern("d").call();
  446. MergeResult mergeRes = git.merge().setStrategy(strategy)
  447. .include(masterCommit).call();
  448. assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
  449. assertEquals(
  450. "[2, mode:100644, content:orig][d/1, mode:100644, content:orig]",
  451. indexState(CONTENT));
  452. }
  453. /**
  454. * Merging two conflicting subtrees when the index and HEAD does not contain
  455. * any file in that subtree should lead to a conflicting state.
  456. *
  457. * @param strategy
  458. * @throws Exception
  459. */
  460. @Theory
  461. public void checkMergeConflictingNewTrees(MergeStrategy strategy)
  462. throws Exception {
  463. Git git = Git.wrap(db);
  464. writeTrashFile("2", "orig");
  465. git.add().addFilepattern("2").call();
  466. RevCommit first = git.commit().setMessage("added 2").call();
  467. writeTrashFile("d/1", "master");
  468. git.add().addFilepattern("d/1").call();
  469. RevCommit masterCommit = git.commit().setAll(true)
  470. .setMessage("added d/1 on master").call();
  471. git.checkout().setCreateBranch(true).setStartPoint(first)
  472. .setName("side").call();
  473. writeTrashFile("d/1", "side");
  474. git.add().addFilepattern("d/1").call();
  475. git.commit().setAll(true).setMessage("added d/1 on side").call();
  476. git.rm().addFilepattern("d/1").call();
  477. git.rm().addFilepattern("d").call();
  478. MergeResult mergeRes = git.merge().setStrategy(strategy)
  479. .include(masterCommit).call();
  480. assertEquals(MergeStatus.CONFLICTING, mergeRes.getMergeStatus());
  481. assertEquals(
  482. "[2, mode:100644, content:orig][d/1, mode:100644, stage:2, content:side][d/1, mode:100644, stage:3, content:master]",
  483. indexState(CONTENT));
  484. }
  485. /**
  486. * Merging two conflicting files when the index contains a tree for that
  487. * path should lead to a failed state.
  488. *
  489. * @param strategy
  490. * @throws Exception
  491. */
  492. @Theory
  493. public void checkMergeConflictingFilesWithTreeInIndex(MergeStrategy strategy)
  494. throws Exception {
  495. Git git = Git.wrap(db);
  496. writeTrashFile("0", "orig");
  497. git.add().addFilepattern("0").call();
  498. RevCommit first = git.commit().setMessage("added 0").call();
  499. writeTrashFile("0", "master");
  500. RevCommit masterCommit = git.commit().setAll(true)
  501. .setMessage("modified 0 on master").call();
  502. git.checkout().setCreateBranch(true).setStartPoint(first)
  503. .setName("side").call();
  504. writeTrashFile("0", "side");
  505. git.commit().setAll(true).setMessage("modified 0 on side").call();
  506. git.rm().addFilepattern("0").call();
  507. writeTrashFile("0/0", "side");
  508. git.add().addFilepattern("0/0").call();
  509. MergeResult mergeRes = git.merge().setStrategy(strategy)
  510. .include(masterCommit).call();
  511. assertEquals(MergeStatus.FAILED, mergeRes.getMergeStatus());
  512. }
  513. /**
  514. * Merging two equal files when the index contains a tree for that path
  515. * should lead to a failed state.
  516. *
  517. * @param strategy
  518. * @throws Exception
  519. */
  520. @Theory
  521. public void checkMergeMergeableFilesWithTreeInIndex(MergeStrategy strategy)
  522. throws Exception {
  523. Git git = Git.wrap(db);
  524. writeTrashFile("0", "orig");
  525. writeTrashFile("1", "1\n2\n3");
  526. git.add().addFilepattern("0").addFilepattern("1").call();
  527. RevCommit first = git.commit().setMessage("added 0, 1").call();
  528. writeTrashFile("1", "1master\n2\n3");
  529. RevCommit masterCommit = git.commit().setAll(true)
  530. .setMessage("modified 1 on master").call();
  531. git.checkout().setCreateBranch(true).setStartPoint(first)
  532. .setName("side").call();
  533. writeTrashFile("1", "1\n2\n3side");
  534. git.commit().setAll(true).setMessage("modified 1 on side").call();
  535. git.rm().addFilepattern("0").call();
  536. writeTrashFile("0/0", "modified");
  537. git.add().addFilepattern("0/0").call();
  538. try {
  539. git.merge().setStrategy(strategy).include(masterCommit).call();
  540. Assert.fail("Didn't get the expected exception");
  541. } catch (CheckoutConflictException e) {
  542. assertEquals(1, e.getConflictingPaths().size());
  543. assertEquals("0/0", e.getConflictingPaths().get(0));
  544. }
  545. }
  546. @Theory
  547. public void checkContentMergeNoConflict(MergeStrategy strategy)
  548. throws Exception {
  549. Git git = Git.wrap(db);
  550. writeTrashFile("file", "1\n2\n3");
  551. git.add().addFilepattern("file").call();
  552. RevCommit first = git.commit().setMessage("added file").call();
  553. writeTrashFile("file", "1master\n2\n3");
  554. git.commit().setAll(true).setMessage("modified file on master").call();
  555. git.checkout().setCreateBranch(true).setStartPoint(first)
  556. .setName("side").call();
  557. writeTrashFile("file", "1\n2\n3side");
  558. RevCommit sideCommit = git.commit().setAll(true)
  559. .setMessage("modified file on side").call();
  560. git.checkout().setName("master").call();
  561. MergeResult result =
  562. git.merge().setStrategy(strategy).include(sideCommit).call();
  563. assertEquals(MergeStatus.MERGED, result.getMergeStatus());
  564. String expected = "1master\n2\n3side";
  565. assertEquals(expected, read("file"));
  566. }
  567. @Theory
  568. public void checkContentMergeNoConflict_noRepo(MergeStrategy strategy)
  569. throws Exception {
  570. Git git = Git.wrap(db);
  571. writeTrashFile("file", "1\n2\n3");
  572. git.add().addFilepattern("file").call();
  573. RevCommit first = git.commit().setMessage("added file").call();
  574. writeTrashFile("file", "1master\n2\n3");
  575. RevCommit masterCommit = git.commit().setAll(true)
  576. .setMessage("modified file on master").call();
  577. git.checkout().setCreateBranch(true).setStartPoint(first)
  578. .setName("side").call();
  579. writeTrashFile("file", "1\n2\n3side");
  580. RevCommit sideCommit = git.commit().setAll(true)
  581. .setMessage("modified file on side").call();
  582. try (ObjectInserter ins = db.newObjectInserter()) {
  583. ResolveMerger merger =
  584. (ResolveMerger) strategy.newMerger(ins, db.getConfig());
  585. boolean noProblems = merger.merge(masterCommit, sideCommit);
  586. assertTrue(noProblems);
  587. assertEquals("1master\n2\n3side",
  588. readBlob(merger.getResultTreeId(), "file"));
  589. }
  590. }
  591. @Theory
  592. public void checkContentMergeConflict(MergeStrategy strategy)
  593. throws Exception {
  594. Git git = Git.wrap(db);
  595. writeTrashFile("file", "1\n2\n3");
  596. git.add().addFilepattern("file").call();
  597. RevCommit first = git.commit().setMessage("added file").call();
  598. writeTrashFile("file", "1master\n2\n3");
  599. git.commit().setAll(true).setMessage("modified file on master").call();
  600. git.checkout().setCreateBranch(true).setStartPoint(first)
  601. .setName("side").call();
  602. writeTrashFile("file", "1side\n2\n3");
  603. RevCommit sideCommit = git.commit().setAll(true)
  604. .setMessage("modified file on side").call();
  605. git.checkout().setName("master").call();
  606. MergeResult result =
  607. git.merge().setStrategy(strategy).include(sideCommit).call();
  608. assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
  609. String expected = "<<<<<<< HEAD\n"
  610. + "1master\n"
  611. + "=======\n"
  612. + "1side\n"
  613. + ">>>>>>> " + sideCommit.name() + "\n"
  614. + "2\n"
  615. + "3";
  616. assertEquals(expected, read("file"));
  617. }
  618. @Theory
  619. public void checkContentMergeConflict_noTree(MergeStrategy strategy)
  620. throws Exception {
  621. Git git = Git.wrap(db);
  622. writeTrashFile("file", "1\n2\n3");
  623. git.add().addFilepattern("file").call();
  624. RevCommit first = git.commit().setMessage("added file").call();
  625. writeTrashFile("file", "1master\n2\n3");
  626. RevCommit masterCommit = git.commit().setAll(true)
  627. .setMessage("modified file on master").call();
  628. git.checkout().setCreateBranch(true).setStartPoint(first)
  629. .setName("side").call();
  630. writeTrashFile("file", "1side\n2\n3");
  631. RevCommit sideCommit = git.commit().setAll(true)
  632. .setMessage("modified file on side").call();
  633. try (ObjectInserter ins = db.newObjectInserter()) {
  634. ResolveMerger merger =
  635. (ResolveMerger) strategy.newMerger(ins, db.getConfig());
  636. boolean noProblems = merger.merge(masterCommit, sideCommit);
  637. assertFalse(noProblems);
  638. assertEquals(Arrays.asList("file"), merger.getUnmergedPaths());
  639. MergeFormatter fmt = new MergeFormatter();
  640. merger.getMergeResults().get("file");
  641. try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
  642. fmt.formatMerge(out, merger.getMergeResults().get("file"),
  643. "BASE", "OURS", "THEIRS", UTF_8.name());
  644. String expected = "<<<<<<< OURS\n"
  645. + "1master\n"
  646. + "=======\n"
  647. + "1side\n"
  648. + ">>>>>>> THEIRS\n"
  649. + "2\n"
  650. + "3";
  651. assertEquals(expected, new String(out.toByteArray(), UTF_8));
  652. }
  653. }
  654. }
  655. /**
  656. * Merging after criss-cross merges. In this case we merge together two
  657. * commits which have two equally good common ancestors
  658. *
  659. * @param strategy
  660. * @throws Exception
  661. */
  662. @Theory
  663. public void checkMergeCrissCross(MergeStrategy strategy) throws Exception {
  664. Git git = Git.wrap(db);
  665. writeTrashFile("1", "1\n2\n3");
  666. git.add().addFilepattern("1").call();
  667. RevCommit first = git.commit().setMessage("added 1").call();
  668. writeTrashFile("1", "1master\n2\n3");
  669. RevCommit masterCommit = git.commit().setAll(true)
  670. .setMessage("modified 1 on master").call();
  671. writeTrashFile("1", "1master2\n2\n3");
  672. git.commit().setAll(true)
  673. .setMessage("modified 1 on master again").call();
  674. git.checkout().setCreateBranch(true).setStartPoint(first)
  675. .setName("side").call();
  676. writeTrashFile("1", "1\n2\na\nb\nc\n3side");
  677. RevCommit sideCommit = git.commit().setAll(true)
  678. .setMessage("modified 1 on side").call();
  679. writeTrashFile("1", "1\n2\n3side2");
  680. git.commit().setAll(true)
  681. .setMessage("modified 1 on side again").call();
  682. MergeResult result = git.merge().setStrategy(strategy)
  683. .include(masterCommit).call();
  684. assertEquals(MergeStatus.MERGED, result.getMergeStatus());
  685. result.getNewHead();
  686. git.checkout().setName("master").call();
  687. result = git.merge().setStrategy(strategy).include(sideCommit).call();
  688. assertEquals(MergeStatus.MERGED, result.getMergeStatus());
  689. // we have two branches which are criss-cross merged. Try to merge the
  690. // tips. This should succeed with RecursiveMerge and fail with
  691. // ResolveMerge
  692. try {
  693. MergeResult mergeResult = git.merge().setStrategy(strategy)
  694. .include(git.getRepository().exactRef("refs/heads/side"))
  695. .call();
  696. assertEquals(MergeStrategy.RECURSIVE, strategy);
  697. assertEquals(MergeResult.MergeStatus.MERGED,
  698. mergeResult.getMergeStatus());
  699. assertEquals("1master2\n2\n3side2", read("1"));
  700. } catch (JGitInternalException e) {
  701. assertEquals(MergeStrategy.RESOLVE, strategy);
  702. assertTrue(e.getCause() instanceof NoMergeBaseException);
  703. assertEquals(((NoMergeBaseException) e.getCause()).getReason(),
  704. MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
  705. }
  706. }
  707. @Theory
  708. public void checkLockedFilesToBeDeleted(MergeStrategy strategy)
  709. throws Exception {
  710. Git git = Git.wrap(db);
  711. writeTrashFile("a.txt", "orig");
  712. writeTrashFile("b.txt", "orig");
  713. git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
  714. RevCommit first = git.commit().setMessage("added a.txt, b.txt").call();
  715. // modify and delete files on the master branch
  716. writeTrashFile("a.txt", "master");
  717. git.rm().addFilepattern("b.txt").call();
  718. RevCommit masterCommit = git.commit()
  719. .setMessage("modified a.txt, deleted b.txt").setAll(true)
  720. .call();
  721. // switch back to a side branch
  722. git.checkout().setCreateBranch(true).setStartPoint(first)
  723. .setName("side").call();
  724. writeTrashFile("c.txt", "side");
  725. git.add().addFilepattern("c.txt").call();
  726. git.commit().setMessage("added c.txt").call();
  727. // Get a handle to the the file so on windows it can't be deleted.
  728. FileInputStream fis = new FileInputStream(new File(db.getWorkTree(),
  729. "b.txt"));
  730. MergeResult mergeRes = git.merge().setStrategy(strategy)
  731. .include(masterCommit).call();
  732. if (mergeRes.getMergeStatus().equals(MergeStatus.FAILED)) {
  733. // probably windows
  734. assertEquals(1, mergeRes.getFailingPaths().size());
  735. assertEquals(MergeFailureReason.COULD_NOT_DELETE, mergeRes
  736. .getFailingPaths().get("b.txt"));
  737. }
  738. assertEquals("[a.txt, mode:100644, content:master]"
  739. + "[c.txt, mode:100644, content:side]", indexState(CONTENT));
  740. fis.close();
  741. }
  742. @Theory
  743. public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
  744. File f;
  745. long lastTs4, lastTsIndex;
  746. Git git = Git.wrap(db);
  747. File indexFile = db.getIndexFile();
  748. // Create initial content and remember when the last file was written.
  749. f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
  750. lastTs4 = FS.DETECTED.lastModified(f);
  751. // add all files, commit and check this doesn't update any working tree
  752. // files and that the index is in a new file system timer tick. Make
  753. // sure to wait long enough before adding so the index doesn't contain
  754. // racily clean entries
  755. fsTick(f);
  756. git.add().addFilepattern(".").call();
  757. RevCommit firstCommit = git.commit().setMessage("initial commit")
  758. .call();
  759. checkConsistentLastModified("0", "1", "2", "3", "4");
  760. checkModificationTimeStampOrder("1", "2", "3", "4", "<.git/index");
  761. assertEquals("Commit should not touch working tree file 4", lastTs4,
  762. FS.DETECTED.lastModified(new File(db.getWorkTree(), "4")));
  763. lastTsIndex = FS.DETECTED.lastModified(indexFile);
  764. // Do modifications on the master branch. Then add and commit. This
  765. // should touch only "0", "2 and "3"
  766. fsTick(indexFile);
  767. f = writeTrashFiles(false, "master", null, "1master\n2\n3", "master",
  768. null);
  769. fsTick(f);
  770. git.add().addFilepattern(".").call();
  771. RevCommit masterCommit = git.commit().setMessage("master commit")
  772. .call();
  773. checkConsistentLastModified("0", "1", "2", "3", "4");
  774. checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
  775. + lastTsIndex, "<0", "2", "3", "<.git/index");
  776. lastTsIndex = FS.DETECTED.lastModified(indexFile);
  777. // Checkout a side branch. This should touch only "0", "2 and "3"
  778. fsTick(indexFile);
  779. git.checkout().setCreateBranch(true).setStartPoint(firstCommit)
  780. .setName("side").call();
  781. checkConsistentLastModified("0", "1", "2", "3", "4");
  782. checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
  783. + lastTsIndex, "<0", "2", "3", ".git/index");
  784. lastTsIndex = FS.DETECTED.lastModified(indexFile);
  785. // This checkout may have populated worktree and index so fast that we
  786. // may have smudged entries now. Check that we have the right content
  787. // and then rewrite the index to get rid of smudged state
  788. assertEquals("[0, mode:100644, content:orig]" //
  789. + "[1, mode:100644, content:orig]" //
  790. + "[2, mode:100644, content:1\n2\n3]" //
  791. + "[3, mode:100644, content:orig]" //
  792. + "[4, mode:100644, content:orig]", //
  793. indexState(CONTENT));
  794. fsTick(indexFile);
  795. f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
  796. lastTs4 = FS.DETECTED.lastModified(f);
  797. fsTick(f);
  798. git.add().addFilepattern(".").call();
  799. checkConsistentLastModified("0", "1", "2", "3", "4");
  800. checkModificationTimeStampOrder("*" + lastTsIndex, "<0", "1", "2", "3",
  801. "4", "<.git/index");
  802. lastTsIndex = FS.DETECTED.lastModified(indexFile);
  803. // Do modifications on the side branch. Touch only "1", "2 and "3"
  804. fsTick(indexFile);
  805. f = writeTrashFiles(false, null, "side", "1\n2\n3side", "side", null);
  806. fsTick(f);
  807. git.add().addFilepattern(".").call();
  808. git.commit().setMessage("side commit").call();
  809. checkConsistentLastModified("0", "1", "2", "3", "4");
  810. checkModificationTimeStampOrder("0", "4", "*" + lastTs4, "<*"
  811. + lastTsIndex, "<1", "2", "3", "<.git/index");
  812. lastTsIndex = FS.DETECTED.lastModified(indexFile);
  813. // merge master and side. Should only touch "0," "2" and "3"
  814. fsTick(indexFile);
  815. git.merge().setStrategy(strategy).include(masterCommit).call();
  816. checkConsistentLastModified("0", "1", "2", "4");
  817. checkModificationTimeStampOrder("4", "*" + lastTs4, "<1", "<*"
  818. + lastTsIndex, "<0", "2", "3", ".git/index");
  819. assertEquals(
  820. "[0, mode:100644, content:master]" //
  821. + "[1, mode:100644, content:side]" //
  822. + "[2, mode:100644, content:1master\n2\n3side]" //
  823. + "[3, mode:100644, stage:1, content:orig][3, mode:100644, stage:2, content:side][3, mode:100644, stage:3, content:master]" //
  824. + "[4, mode:100644, content:orig]", //
  825. indexState(CONTENT));
  826. }
  827. // Assert that every specified index entry has the same last modification
  828. // timestamp as the associated file
  829. private void checkConsistentLastModified(String... pathes)
  830. throws IOException {
  831. DirCache dc = db.readDirCache();
  832. File workTree = db.getWorkTree();
  833. for (String path : pathes)
  834. assertEquals(
  835. "IndexEntry with path "
  836. + path
  837. + " has lastmodified with is different from the worktree file",
  838. FS.DETECTED.lastModified(new File(workTree, path)), dc.getEntry(path)
  839. .getLastModified());
  840. }
  841. // Assert that modification timestamps of working tree files are as
  842. // expected. You may specify n files. It is asserted that every file
  843. // i+1 is not older than file i. If a path of file i+1 is prefixed with "<"
  844. // then this file must be younger then file i. A path "*<modtime>"
  845. // represents a file with a modification time of <modtime>
  846. // E.g. ("a", "b", "<c", "f/a.txt") means: a<=b<c<=f/a.txt
  847. private void checkModificationTimeStampOrder(String... pathes)
  848. throws IOException {
  849. long lastMod = Long.MIN_VALUE;
  850. for (String p : pathes) {
  851. boolean strong = p.startsWith("<");
  852. boolean fixed = p.charAt(strong ? 1 : 0) == '*';
  853. p = p.substring((strong ? 1 : 0) + (fixed ? 1 : 0));
  854. long curMod = fixed ? Long.valueOf(p).longValue()
  855. : FS.DETECTED.lastModified(new File(db.getWorkTree(), p));
  856. if (strong)
  857. assertTrue("path " + p + " is not younger than predecesssor",
  858. curMod > lastMod);
  859. else
  860. assertTrue("path " + p + " is older than predecesssor",
  861. curMod >= lastMod);
  862. }
  863. }
  864. private String readBlob(ObjectId treeish, String path) throws Exception {
  865. TestRepository<?> tr = new TestRepository<>(db);
  866. RevWalk rw = tr.getRevWalk();
  867. RevTree tree = rw.parseTree(treeish);
  868. RevObject obj = tr.get(tree, path);
  869. if (obj == null) {
  870. return null;
  871. }
  872. return new String(rw.getObjectReader().open(obj, OBJ_BLOB).getBytes(), UTF_8);
  873. }
  874. }