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

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