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.

MergerTest.java 46KB

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