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

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