Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

ResolveMergerTest.java 38KB

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