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.

CherryPickCommandTest.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. /*
  2. * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com> and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.api;
  11. import static org.junit.Assert.assertEquals;
  12. import static org.junit.Assert.assertFalse;
  13. import static org.junit.Assert.assertNotNull;
  14. import static org.junit.Assert.assertTrue;
  15. import static org.junit.Assert.fail;
  16. import java.io.File;
  17. import java.io.IOException;
  18. import java.util.Iterator;
  19. import org.eclipse.jgit.api.CherryPickResult.CherryPickStatus;
  20. import org.eclipse.jgit.api.ResetCommand.ResetType;
  21. import org.eclipse.jgit.api.errors.GitAPIException;
  22. import org.eclipse.jgit.api.errors.JGitInternalException;
  23. import org.eclipse.jgit.api.errors.MultipleParentsNotAllowedException;
  24. import org.eclipse.jgit.dircache.DirCache;
  25. import org.eclipse.jgit.events.ChangeRecorder;
  26. import org.eclipse.jgit.events.ListenerHandle;
  27. import org.eclipse.jgit.junit.RepositoryTestCase;
  28. import org.eclipse.jgit.lib.ConfigConstants;
  29. import org.eclipse.jgit.lib.Constants;
  30. import org.eclipse.jgit.lib.FileMode;
  31. import org.eclipse.jgit.lib.ObjectId;
  32. import org.eclipse.jgit.lib.ReflogReader;
  33. import org.eclipse.jgit.lib.RepositoryState;
  34. import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
  35. import org.eclipse.jgit.revwalk.RevCommit;
  36. import org.junit.Test;
  37. /**
  38. * Test cherry-pick command
  39. */
  40. public class CherryPickCommandTest extends RepositoryTestCase {
  41. @Test
  42. public void testCherryPick() throws IOException, JGitInternalException,
  43. GitAPIException {
  44. doTestCherryPick(false);
  45. }
  46. @Test
  47. public void testCherryPickNoCommit() throws IOException,
  48. JGitInternalException, GitAPIException {
  49. doTestCherryPick(true);
  50. }
  51. private void doTestCherryPick(boolean noCommit) throws IOException,
  52. JGitInternalException,
  53. GitAPIException {
  54. try (Git git = new Git(db)) {
  55. writeTrashFile("a", "first line\nsec. line\nthird line\n");
  56. git.add().addFilepattern("a").call();
  57. RevCommit firstCommit = git.commit().setMessage("create a").call();
  58. writeTrashFile("b", "content\n");
  59. git.add().addFilepattern("b").call();
  60. git.commit().setMessage("create b").call();
  61. writeTrashFile("a", "first line\nsec. line\nthird line\nfourth line\n");
  62. git.add().addFilepattern("a").call();
  63. git.commit().setMessage("enlarged a").call();
  64. writeTrashFile("a",
  65. "first line\nsecond line\nthird line\nfourth line\n");
  66. git.add().addFilepattern("a").call();
  67. RevCommit fixingA = git.commit().setMessage("fixed a").call();
  68. git.branchCreate().setName("side").setStartPoint(firstCommit).call();
  69. checkoutBranch("refs/heads/side");
  70. writeTrashFile("a", "first line\nsec. line\nthird line\nfeature++\n");
  71. git.add().addFilepattern("a").call();
  72. git.commit().setMessage("enhanced a").call();
  73. CherryPickResult pickResult = git.cherryPick().include(fixingA)
  74. .setNoCommit(noCommit).call();
  75. assertEquals(CherryPickStatus.OK, pickResult.getStatus());
  76. assertFalse(new File(db.getWorkTree(), "b").exists());
  77. checkFile(new File(db.getWorkTree(), "a"),
  78. "first line\nsecond line\nthird line\nfeature++\n");
  79. Iterator<RevCommit> history = git.log().call().iterator();
  80. if (!noCommit)
  81. assertEquals("fixed a", history.next().getFullMessage());
  82. assertEquals("enhanced a", history.next().getFullMessage());
  83. assertEquals("create a", history.next().getFullMessage());
  84. assertFalse(history.hasNext());
  85. }
  86. }
  87. @Test
  88. public void testSequentialCherryPick() throws IOException, JGitInternalException,
  89. GitAPIException {
  90. try (Git git = new Git(db)) {
  91. writeTrashFile("a", "first line\nsec. line\nthird line\n");
  92. git.add().addFilepattern("a").call();
  93. RevCommit firstCommit = git.commit().setMessage("create a").call();
  94. writeTrashFile("a", "first line\nsec. line\nthird line\nfourth line\n");
  95. git.add().addFilepattern("a").call();
  96. RevCommit enlargingA = git.commit().setMessage("enlarged a").call();
  97. writeTrashFile("a",
  98. "first line\nsecond line\nthird line\nfourth line\n");
  99. git.add().addFilepattern("a").call();
  100. RevCommit fixingA = git.commit().setMessage("fixed a").call();
  101. git.branchCreate().setName("side").setStartPoint(firstCommit).call();
  102. checkoutBranch("refs/heads/side");
  103. writeTrashFile("b", "nothing to do with a");
  104. git.add().addFilepattern("b").call();
  105. git.commit().setMessage("create b").call();
  106. CherryPickResult result = git.cherryPick().include(enlargingA).include(fixingA).call();
  107. assertEquals(CherryPickResult.CherryPickStatus.OK, result.getStatus());
  108. Iterator<RevCommit> history = git.log().call().iterator();
  109. assertEquals("fixed a", history.next().getFullMessage());
  110. assertEquals("enlarged a", history.next().getFullMessage());
  111. assertEquals("create b", history.next().getFullMessage());
  112. assertEquals("create a", history.next().getFullMessage());
  113. assertFalse(history.hasNext());
  114. }
  115. }
  116. @Test
  117. public void testCherryPickDirtyIndex() throws Exception {
  118. try (Git git = new Git(db)) {
  119. RevCommit sideCommit = prepareCherryPick(git);
  120. // modify and add file a
  121. writeTrashFile("a", "a(modified)");
  122. git.add().addFilepattern("a").call();
  123. // do not commit
  124. doCherryPickAndCheckResult(git, sideCommit,
  125. MergeFailureReason.DIRTY_INDEX);
  126. }
  127. }
  128. @Test
  129. public void testCherryPickDirtyWorktree() throws Exception {
  130. try (Git git = new Git(db)) {
  131. RevCommit sideCommit = prepareCherryPick(git);
  132. // modify file a
  133. writeTrashFile("a", "a(modified)");
  134. // do not add and commit
  135. doCherryPickAndCheckResult(git, sideCommit,
  136. MergeFailureReason.DIRTY_WORKTREE);
  137. }
  138. }
  139. @Test
  140. public void testCherryPickConflictResolution() throws Exception {
  141. try (Git git = new Git(db)) {
  142. RevCommit sideCommit = prepareCherryPick(git);
  143. CherryPickResult result = git.cherryPick().include(sideCommit.getId())
  144. .call();
  145. assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
  146. assertTrue(new File(db.getDirectory(), Constants.MERGE_MSG).exists());
  147. assertEquals("side\n\nConflicts:\n\ta\n", db.readMergeCommitMsg());
  148. assertTrue(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
  149. .exists());
  150. assertEquals(sideCommit.getId(), db.readCherryPickHead());
  151. assertEquals(RepositoryState.CHERRY_PICKING, db.getRepositoryState());
  152. // Resolve
  153. writeTrashFile("a", "a");
  154. git.add().addFilepattern("a").call();
  155. assertEquals(RepositoryState.CHERRY_PICKING_RESOLVED,
  156. db.getRepositoryState());
  157. git.commit().setOnly("a").setMessage("resolve").call();
  158. assertEquals(RepositoryState.SAFE, db.getRepositoryState());
  159. }
  160. }
  161. @Test
  162. public void testCherryPickConflictResolutionNoCOmmit() throws Exception {
  163. Git git = new Git(db);
  164. RevCommit sideCommit = prepareCherryPick(git);
  165. CherryPickResult result = git.cherryPick().include(sideCommit.getId())
  166. .setNoCommit(true).call();
  167. assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
  168. assertTrue(db.readDirCache().hasUnmergedPaths());
  169. String expected = "<<<<<<< master\na(master)\n=======\na(side)\n>>>>>>> 527460a side\n";
  170. assertEquals(expected, read("a"));
  171. assertTrue(new File(db.getDirectory(), Constants.MERGE_MSG).exists());
  172. assertEquals("side\n\nConflicts:\n\ta\n", db.readMergeCommitMsg());
  173. assertFalse(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
  174. .exists());
  175. assertEquals(RepositoryState.SAFE, db.getRepositoryState());
  176. // Resolve
  177. writeTrashFile("a", "a");
  178. git.add().addFilepattern("a").call();
  179. assertEquals(RepositoryState.SAFE, db.getRepositoryState());
  180. git.commit().setOnly("a").setMessage("resolve").call();
  181. assertEquals(RepositoryState.SAFE, db.getRepositoryState());
  182. }
  183. @Test
  184. public void testCherryPickConflictReset() throws Exception {
  185. try (Git git = new Git(db)) {
  186. RevCommit sideCommit = prepareCherryPick(git);
  187. CherryPickResult result = git.cherryPick().include(sideCommit.getId())
  188. .call();
  189. assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
  190. assertEquals(RepositoryState.CHERRY_PICKING, db.getRepositoryState());
  191. assertTrue(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
  192. .exists());
  193. git.reset().setMode(ResetType.MIXED).setRef("HEAD").call();
  194. assertEquals(RepositoryState.SAFE, db.getRepositoryState());
  195. assertFalse(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
  196. .exists());
  197. }
  198. }
  199. @Test
  200. public void testCherryPickOverExecutableChangeOnNonExectuableFileSystem()
  201. throws Exception {
  202. try (Git git = new Git(db)) {
  203. File file = writeTrashFile("test.txt", "a");
  204. assertNotNull(git.add().addFilepattern("test.txt").call());
  205. assertNotNull(git.commit().setMessage("commit1").call());
  206. assertNotNull(git.checkout().setCreateBranch(true).setName("a").call());
  207. writeTrashFile("test.txt", "b");
  208. assertNotNull(git.add().addFilepattern("test.txt").call());
  209. RevCommit commit2 = git.commit().setMessage("commit2").call();
  210. assertNotNull(commit2);
  211. assertNotNull(git.checkout().setName(Constants.MASTER).call());
  212. DirCache cache = db.lockDirCache();
  213. cache.getEntry("test.txt").setFileMode(FileMode.EXECUTABLE_FILE);
  214. cache.write();
  215. assertTrue(cache.commit());
  216. cache.unlock();
  217. assertNotNull(git.commit().setMessage("commit3").call());
  218. db.getFS().setExecute(file, false);
  219. git.getRepository()
  220. .getConfig()
  221. .setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
  222. ConfigConstants.CONFIG_KEY_FILEMODE, false);
  223. CherryPickResult result = git.cherryPick().include(commit2).call();
  224. assertNotNull(result);
  225. assertEquals(CherryPickStatus.OK, result.getStatus());
  226. }
  227. }
  228. @Test
  229. public void testCherryPickConflictMarkers() throws Exception {
  230. try (Git git = new Git(db)) {
  231. RevCommit sideCommit = prepareCherryPick(git);
  232. CherryPickResult result = git.cherryPick().include(sideCommit.getId())
  233. .call();
  234. assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
  235. String expected = "<<<<<<< master\na(master)\n=======\na(side)\n>>>>>>> 527460a side\n";
  236. checkFile(new File(db.getWorkTree(), "a"), expected);
  237. }
  238. }
  239. @Test
  240. public void testCherryPickConflictFiresModifiedEvent() throws Exception {
  241. ListenerHandle listener = null;
  242. try (Git git = new Git(db)) {
  243. RevCommit sideCommit = prepareCherryPick(git);
  244. ChangeRecorder recorder = new ChangeRecorder();
  245. listener = db.getListenerList()
  246. .addWorkingTreeModifiedListener(recorder);
  247. CherryPickResult result = git.cherryPick()
  248. .include(sideCommit.getId()).call();
  249. assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
  250. recorder.assertEvent(new String[] { "a" }, ChangeRecorder.EMPTY);
  251. } finally {
  252. if (listener != null) {
  253. listener.remove();
  254. }
  255. }
  256. }
  257. @Test
  258. public void testCherryPickNewFileFiresModifiedEvent() throws Exception {
  259. ListenerHandle listener = null;
  260. try (Git git = new Git(db)) {
  261. writeTrashFile("test.txt", "a");
  262. git.add().addFilepattern("test.txt").call();
  263. git.commit().setMessage("commit1").call();
  264. git.checkout().setCreateBranch(true).setName("a").call();
  265. writeTrashFile("side.txt", "side");
  266. git.add().addFilepattern("side.txt").call();
  267. RevCommit side = git.commit().setMessage("side").call();
  268. assertNotNull(side);
  269. assertNotNull(git.checkout().setName(Constants.MASTER).call());
  270. writeTrashFile("test.txt", "b");
  271. assertNotNull(git.add().addFilepattern("test.txt").call());
  272. assertNotNull(git.commit().setMessage("commit2").call());
  273. ChangeRecorder recorder = new ChangeRecorder();
  274. listener = db.getListenerList()
  275. .addWorkingTreeModifiedListener(recorder);
  276. CherryPickResult result = git.cherryPick()
  277. .include(side.getId()).call();
  278. assertEquals(CherryPickStatus.OK, result.getStatus());
  279. recorder.assertEvent(new String[] { "side.txt" },
  280. ChangeRecorder.EMPTY);
  281. } finally {
  282. if (listener != null) {
  283. listener.remove();
  284. }
  285. }
  286. }
  287. @Test
  288. public void testCherryPickOurCommitName() throws Exception {
  289. try (Git git = new Git(db)) {
  290. RevCommit sideCommit = prepareCherryPick(git);
  291. CherryPickResult result = git.cherryPick().include(sideCommit.getId())
  292. .setOurCommitName("custom name").call();
  293. assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
  294. String expected = "<<<<<<< custom name\na(master)\n=======\na(side)\n>>>>>>> 527460a side\n";
  295. checkFile(new File(db.getWorkTree(), "a"), expected);
  296. }
  297. }
  298. private RevCommit prepareCherryPick(Git git) throws Exception {
  299. // create, add and commit file a
  300. writeTrashFile("a", "a");
  301. git.add().addFilepattern("a").call();
  302. RevCommit firstMasterCommit = git.commit().setMessage("first master")
  303. .call();
  304. // create and checkout side branch
  305. createBranch(firstMasterCommit, "refs/heads/side");
  306. checkoutBranch("refs/heads/side");
  307. // modify, add and commit file a
  308. writeTrashFile("a", "a(side)");
  309. git.add().addFilepattern("a").call();
  310. RevCommit sideCommit = git.commit().setMessage("side").call();
  311. // checkout master branch
  312. checkoutBranch("refs/heads/master");
  313. // modify, add and commit file a
  314. writeTrashFile("a", "a(master)");
  315. git.add().addFilepattern("a").call();
  316. git.commit().setMessage("second master").call();
  317. return sideCommit;
  318. }
  319. private void doCherryPickAndCheckResult(final Git git,
  320. final RevCommit sideCommit, final MergeFailureReason reason)
  321. throws Exception {
  322. // get current index state
  323. String indexState = indexState(CONTENT);
  324. // cherry-pick
  325. CherryPickResult result = git.cherryPick().include(sideCommit.getId())
  326. .call();
  327. assertEquals(CherryPickStatus.FAILED, result.getStatus());
  328. // staged file a causes DIRTY_INDEX
  329. assertEquals(1, result.getFailingPaths().size());
  330. assertEquals(reason, result.getFailingPaths().get("a"));
  331. assertEquals("a(modified)", read(new File(db.getWorkTree(), "a")));
  332. // index shall be unchanged
  333. assertEquals(indexState, indexState(CONTENT));
  334. assertEquals(RepositoryState.SAFE, db.getRepositoryState());
  335. if (reason == null) {
  336. ReflogReader reader = db.getReflogReader(Constants.HEAD);
  337. assertTrue(reader.getLastEntry().getComment()
  338. .startsWith("cherry-pick: "));
  339. reader = db.getReflogReader(db.getBranch());
  340. assertTrue(reader.getLastEntry().getComment()
  341. .startsWith("cherry-pick: "));
  342. }
  343. }
  344. /**
  345. * Cherry-picking merge commit M onto T
  346. * <pre>
  347. * M
  348. * |\
  349. * C D
  350. * |/
  351. * T B
  352. * | /
  353. * A
  354. * </pre>
  355. * @throws Exception
  356. */
  357. @Test
  358. public void testCherryPickMerge() throws Exception {
  359. try (Git git = new Git(db)) {
  360. commitFile("file", "1\n2\n3\n", "master");
  361. commitFile("file", "1\n2\n3\n", "side");
  362. checkoutBranch("refs/heads/side");
  363. RevCommit commitD = commitFile("file", "1\n2\n3\n4\n5\n", "side2");
  364. commitFile("file", "a\n2\n3\n", "side");
  365. MergeResult mergeResult = git.merge().include(commitD).call();
  366. ObjectId commitM = mergeResult.getNewHead();
  367. checkoutBranch("refs/heads/master");
  368. RevCommit commitT = commitFile("another", "t", "master");
  369. try {
  370. git.cherryPick().include(commitM).call();
  371. fail("merges should not be cherry-picked by default");
  372. } catch (MultipleParentsNotAllowedException e) {
  373. // expected
  374. }
  375. try {
  376. git.cherryPick().include(commitM).setMainlineParentNumber(3).call();
  377. fail("specifying a non-existent parent should fail");
  378. } catch (JGitInternalException e) {
  379. // expected
  380. assertTrue(e.getMessage().endsWith(
  381. "does not have a parent number 3."));
  382. }
  383. CherryPickResult result = git.cherryPick().include(commitM)
  384. .setMainlineParentNumber(1).call();
  385. assertEquals(CherryPickStatus.OK, result.getStatus());
  386. checkFile(new File(db.getWorkTree(), "file"), "1\n2\n3\n4\n5\n");
  387. git.reset().setMode(ResetType.HARD).setRef(commitT.getName()).call();
  388. CherryPickResult result2 = git.cherryPick().include(commitM)
  389. .setMainlineParentNumber(2).call();
  390. assertEquals(CherryPickStatus.OK, result2.getStatus());
  391. checkFile(new File(db.getWorkTree(), "file"), "a\n2\n3\n");
  392. }
  393. }
  394. }