1
2
3
4
5
6
7
8
9
10 package org.eclipse.jgit.api;
11
12 import static org.junit.Assert.assertEquals;
13 import static org.junit.Assert.assertFalse;
14 import static org.junit.Assert.assertNotNull;
15 import static org.junit.Assert.assertTrue;
16 import static org.junit.Assert.fail;
17
18 import java.io.File;
19 import java.io.IOException;
20 import java.util.Iterator;
21
22 import org.eclipse.jgit.api.CherryPickResult.CherryPickStatus;
23 import org.eclipse.jgit.api.ResetCommand.ResetType;
24 import org.eclipse.jgit.api.errors.GitAPIException;
25 import org.eclipse.jgit.api.errors.JGitInternalException;
26 import org.eclipse.jgit.api.errors.MultipleParentsNotAllowedException;
27 import org.eclipse.jgit.dircache.DirCache;
28 import org.eclipse.jgit.events.ChangeRecorder;
29 import org.eclipse.jgit.events.ListenerHandle;
30 import org.eclipse.jgit.junit.RepositoryTestCase;
31 import org.eclipse.jgit.lib.ConfigConstants;
32 import org.eclipse.jgit.lib.Constants;
33 import org.eclipse.jgit.lib.FileMode;
34 import org.eclipse.jgit.lib.ObjectId;
35 import org.eclipse.jgit.lib.ReflogReader;
36 import org.eclipse.jgit.lib.RepositoryState;
37 import org.eclipse.jgit.merge.ContentMergeStrategy;
38 import org.eclipse.jgit.merge.MergeStrategy;
39 import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
40 import org.eclipse.jgit.revwalk.RevCommit;
41 import org.junit.Test;
42
43
44
45
46 public class CherryPickCommandTest extends RepositoryTestCase {
47 @Test
48 public void testCherryPick() throws IOException, JGitInternalException,
49 GitAPIException {
50 doTestCherryPick(false);
51 }
52
53 @Test
54 public void testCherryPickNoCommit() throws IOException,
55 JGitInternalException, GitAPIException {
56 doTestCherryPick(true);
57 }
58
59 private void doTestCherryPick(boolean noCommit) throws IOException,
60 JGitInternalException,
61 GitAPIException {
62 try (Git git = new Git(db)) {
63 writeTrashFile("a", "first line\nsec. line\nthird line\n");
64 git.add().addFilepattern("a").call();
65 RevCommit firstCommit = git.commit().setMessage("create a").call();
66
67 writeTrashFile("b", "content\n");
68 git.add().addFilepattern("b").call();
69 git.commit().setMessage("create b").call();
70
71 writeTrashFile("a", "first line\nsec. line\nthird line\nfourth line\n");
72 git.add().addFilepattern("a").call();
73 git.commit().setMessage("enlarged a").call();
74
75 writeTrashFile("a",
76 "first line\nsecond line\nthird line\nfourth line\n");
77 git.add().addFilepattern("a").call();
78 RevCommit fixingA = git.commit().setMessage("fixed a").call();
79
80 git.branchCreate().setName("side").setStartPoint(firstCommit).call();
81 checkoutBranch("refs/heads/side");
82
83 writeTrashFile("a", "first line\nsec. line\nthird line\nfeature++\n");
84 git.add().addFilepattern("a").call();
85 git.commit().setMessage("enhanced a").call();
86
87 CherryPickResult pickResult = git.cherryPick().include(fixingA)
88 .setNoCommit(noCommit).call();
89
90 assertEquals(CherryPickStatus.OK, pickResult.getStatus());
91 assertFalse(new File(db.getWorkTree(), "b").exists());
92 checkFile(new File(db.getWorkTree(), "a"),
93 "first line\nsecond line\nthird line\nfeature++\n");
94 Iterator<RevCommit> history = git.log().call().iterator();
95 if (!noCommit)
96 assertEquals("fixed a", history.next().getFullMessage());
97 assertEquals("enhanced a", history.next().getFullMessage());
98 assertEquals("create a", history.next().getFullMessage());
99 assertFalse(history.hasNext());
100 }
101 }
102
103 @Test
104 public void testSequentialCherryPick() throws IOException, JGitInternalException,
105 GitAPIException {
106 try (Git git = new Git(db)) {
107 writeTrashFile("a", "first line\nsec. line\nthird line\n");
108 git.add().addFilepattern("a").call();
109 RevCommit firstCommit = git.commit().setMessage("create a").call();
110
111 writeTrashFile("a", "first line\nsec. line\nthird line\nfourth line\n");
112 git.add().addFilepattern("a").call();
113 RevCommit enlargingA = git.commit().setMessage("enlarged a").call();
114
115 writeTrashFile("a",
116 "first line\nsecond line\nthird line\nfourth line\n");
117 git.add().addFilepattern("a").call();
118 RevCommit fixingA = git.commit().setMessage("fixed a").call();
119
120 git.branchCreate().setName("side").setStartPoint(firstCommit).call();
121 checkoutBranch("refs/heads/side");
122
123 writeTrashFile("b", "nothing to do with a");
124 git.add().addFilepattern("b").call();
125 git.commit().setMessage("create b").call();
126
127 CherryPickResult result = git.cherryPick().include(enlargingA).include(fixingA).call();
128 assertEquals(CherryPickResult.CherryPickStatus.OK, result.getStatus());
129
130 Iterator<RevCommit> history = git.log().call().iterator();
131 assertEquals("fixed a", history.next().getFullMessage());
132 assertEquals("enlarged a", history.next().getFullMessage());
133 assertEquals("create b", history.next().getFullMessage());
134 assertEquals("create a", history.next().getFullMessage());
135 assertFalse(history.hasNext());
136 }
137 }
138
139 @Test
140 public void testCherryPickDirtyIndex() throws Exception {
141 try (Git git = new Git(db)) {
142 RevCommit sideCommit = prepareCherryPick(git);
143
144
145 writeTrashFile("a", "a(modified)");
146 git.add().addFilepattern("a").call();
147
148
149 doCherryPickAndCheckResult(git, sideCommit,
150 MergeFailureReason.DIRTY_INDEX);
151 }
152 }
153
154 @Test
155 public void testCherryPickDirtyWorktree() throws Exception {
156 try (Git git = new Git(db)) {
157 RevCommit sideCommit = prepareCherryPick(git);
158
159
160 writeTrashFile("a", "a(modified)");
161
162
163 doCherryPickAndCheckResult(git, sideCommit,
164 MergeFailureReason.DIRTY_WORKTREE);
165 }
166 }
167
168 @Test
169 public void testCherryPickConflictResolution() throws Exception {
170 try (Git git = new Git(db)) {
171 RevCommit sideCommit = prepareCherryPick(git);
172
173 CherryPickResult result = git.cherryPick().include(sideCommit.getId())
174 .call();
175
176 assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
177 assertTrue(new File(db.getDirectory(), Constants.MERGE_MSG).exists());
178 assertEquals("side\n\n# Conflicts:\n#\ta\n",
179 db.readMergeCommitMsg());
180 assertTrue(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
181 .exists());
182 assertEquals(sideCommit.getId(), db.readCherryPickHead());
183 assertEquals(RepositoryState.CHERRY_PICKING, db.getRepositoryState());
184
185
186 writeTrashFile("a", "a");
187 git.add().addFilepattern("a").call();
188
189 assertEquals(RepositoryState.CHERRY_PICKING_RESOLVED,
190 db.getRepositoryState());
191
192 git.commit().setOnly("a").setMessage("resolve").call();
193
194 assertEquals(RepositoryState.SAFE, db.getRepositoryState());
195 }
196 }
197
198 @Test
199 public void testCherryPickConflictResolutionNoCommit() throws Exception {
200 Git git = new Git(db);
201 RevCommit sideCommit = prepareCherryPick(git);
202
203 CherryPickResult result = git.cherryPick().include(sideCommit.getId())
204 .setNoCommit(true).call();
205
206 assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
207 assertTrue(db.readDirCache().hasUnmergedPaths());
208 String expected = "<<<<<<< master\na(master)\n=======\na(side)\n>>>>>>> 527460a side\n";
209 assertEquals(expected, read("a"));
210 assertTrue(new File(db.getDirectory(), Constants.MERGE_MSG).exists());
211 assertEquals("side\n\n# Conflicts:\n#\ta\n", db.readMergeCommitMsg());
212 assertFalse(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
213 .exists());
214 assertEquals(RepositoryState.SAFE, db.getRepositoryState());
215
216
217 writeTrashFile("a", "a");
218 git.add().addFilepattern("a").call();
219
220 assertEquals(RepositoryState.SAFE, db.getRepositoryState());
221
222 git.commit().setOnly("a").setMessage("resolve").call();
223
224 assertEquals(RepositoryState.SAFE, db.getRepositoryState());
225 }
226
227 @Test
228 public void testCherryPickConflictReset() throws Exception {
229 try (Git git = new Git(db)) {
230 RevCommit sideCommit = prepareCherryPick(git);
231
232 CherryPickResult result = git.cherryPick().include(sideCommit.getId())
233 .call();
234
235 assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
236 assertEquals(RepositoryState.CHERRY_PICKING, db.getRepositoryState());
237 assertTrue(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
238 .exists());
239
240 git.reset().setMode(ResetType.MIXED).setRef("HEAD").call();
241
242 assertEquals(RepositoryState.SAFE, db.getRepositoryState());
243 assertFalse(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
244 .exists());
245 }
246 }
247
248 @Test
249 public void testCherryPickOverExecutableChangeOnNonExectuableFileSystem()
250 throws Exception {
251 try (Git git = new Git(db)) {
252 File file = writeTrashFile("test.txt", "a");
253 assertNotNull(git.add().addFilepattern("test.txt").call());
254 assertNotNull(git.commit().setMessage("commit1").call());
255
256 assertNotNull(git.checkout().setCreateBranch(true).setName("a").call());
257
258 writeTrashFile("test.txt", "b");
259 assertNotNull(git.add().addFilepattern("test.txt").call());
260 RevCommit commit2 = git.commit().setMessage("commit2").call();
261 assertNotNull(commit2);
262
263 assertNotNull(git.checkout().setName(Constants.MASTER).call());
264
265 DirCache cache = db.lockDirCache();
266 cache.getEntry("test.txt").setFileMode(FileMode.EXECUTABLE_FILE);
267 cache.write();
268 assertTrue(cache.commit());
269 cache.unlock();
270
271 assertNotNull(git.commit().setMessage("commit3").call());
272
273 db.getFS().setExecute(file, false);
274 git.getRepository()
275 .getConfig()
276 .setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
277 ConfigConstants.CONFIG_KEY_FILEMODE, false);
278
279 CherryPickResult result = git.cherryPick().include(commit2).call();
280 assertNotNull(result);
281 assertEquals(CherryPickStatus.OK, result.getStatus());
282 }
283 }
284
285 @Test
286 public void testCherryPickOurs() throws Exception {
287 try (Git git = new Git(db)) {
288 RevCommit sideCommit = prepareCherryPick(git);
289
290 CherryPickResult result = git.cherryPick()
291 .include(sideCommit.getId())
292 .setStrategy(MergeStrategy.OURS)
293 .call();
294 assertEquals(CherryPickStatus.OK, result.getStatus());
295
296 String expected = "a(master)";
297 checkFile(new File(db.getWorkTree(), "a"), expected);
298 }
299 }
300
301 @Test
302 public void testCherryPickTheirs() throws Exception {
303 try (Git git = new Git(db)) {
304 RevCommit sideCommit = prepareCherryPick(git);
305
306 CherryPickResult result = git.cherryPick()
307 .include(sideCommit.getId())
308 .setStrategy(MergeStrategy.THEIRS)
309 .call();
310 assertEquals(CherryPickStatus.OK, result.getStatus());
311
312 String expected = "a(side)";
313 checkFile(new File(db.getWorkTree(), "a"), expected);
314 }
315 }
316
317 @Test
318 public void testCherryPickXours() throws Exception {
319 try (Git git = new Git(db)) {
320 RevCommit sideCommit = prepareCherryPickStrategyOption(git);
321
322 CherryPickResult result = git.cherryPick()
323 .include(sideCommit.getId())
324 .setContentMergeStrategy(ContentMergeStrategy.OURS)
325 .call();
326 assertEquals(CherryPickStatus.OK, result.getStatus());
327
328 String expected = "a\nmaster\nc\nd\n";
329 checkFile(new File(db.getWorkTree(), "a"), expected);
330 }
331 }
332
333 @Test
334 public void testCherryPickXtheirs() throws Exception {
335 try (Git git = new Git(db)) {
336 RevCommit sideCommit = prepareCherryPickStrategyOption(git);
337
338 CherryPickResult result = git.cherryPick()
339 .include(sideCommit.getId())
340 .setContentMergeStrategy(ContentMergeStrategy.THEIRS)
341 .call();
342 assertEquals(CherryPickStatus.OK, result.getStatus());
343
344 String expected = "a\nside\nc\nd\n";
345 checkFile(new File(db.getWorkTree(), "a"), expected);
346 }
347 }
348
349 @Test
350 public void testCherryPickConflictMarkers() throws Exception {
351 try (Git git = new Git(db)) {
352 RevCommit sideCommit = prepareCherryPick(git);
353
354 CherryPickResult result = git.cherryPick().include(sideCommit.getId())
355 .call();
356 assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
357
358 String expected = "<<<<<<< master\na(master)\n=======\na(side)\n>>>>>>> 527460a side\n";
359 checkFile(new File(db.getWorkTree(), "a"), expected);
360 }
361 }
362
363 @Test
364 public void testCherryPickConflictFiresModifiedEvent() throws Exception {
365 ListenerHandle listener = null;
366 try (Git git = new Git(db)) {
367 RevCommit sideCommit = prepareCherryPick(git);
368 ChangeRecorder recorder = new ChangeRecorder();
369 listener = db.getListenerList()
370 .addWorkingTreeModifiedListener(recorder);
371 CherryPickResult result = git.cherryPick()
372 .include(sideCommit.getId()).call();
373 assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
374 recorder.assertEvent(new String[] { "a" }, ChangeRecorder.EMPTY);
375 } finally {
376 if (listener != null) {
377 listener.remove();
378 }
379 }
380 }
381
382 @Test
383 public void testCherryPickNewFileFiresModifiedEvent() throws Exception {
384 ListenerHandle listener = null;
385 try (Git git = new Git(db)) {
386 writeTrashFile("test.txt", "a");
387 git.add().addFilepattern("test.txt").call();
388 git.commit().setMessage("commit1").call();
389 git.checkout().setCreateBranch(true).setName("a").call();
390
391 writeTrashFile("side.txt", "side");
392 git.add().addFilepattern("side.txt").call();
393 RevCommit side = git.commit().setMessage("side").call();
394 assertNotNull(side);
395
396 assertNotNull(git.checkout().setName(Constants.MASTER).call());
397 writeTrashFile("test.txt", "b");
398 assertNotNull(git.add().addFilepattern("test.txt").call());
399 assertNotNull(git.commit().setMessage("commit2").call());
400
401 ChangeRecorder recorder = new ChangeRecorder();
402 listener = db.getListenerList()
403 .addWorkingTreeModifiedListener(recorder);
404 CherryPickResult result = git.cherryPick()
405 .include(side.getId()).call();
406 assertEquals(CherryPickStatus.OK, result.getStatus());
407 recorder.assertEvent(new String[] { "side.txt" },
408 ChangeRecorder.EMPTY);
409 } finally {
410 if (listener != null) {
411 listener.remove();
412 }
413 }
414 }
415
416 @Test
417 public void testCherryPickOurCommitName() throws Exception {
418 try (Git git = new Git(db)) {
419 RevCommit sideCommit = prepareCherryPick(git);
420
421 CherryPickResult result = git.cherryPick().include(sideCommit.getId())
422 .setOurCommitName("custom name").call();
423 assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
424
425 String expected = "<<<<<<< custom name\na(master)\n=======\na(side)\n>>>>>>> 527460a side\n";
426 checkFile(new File(db.getWorkTree(), "a"), expected);
427 }
428 }
429
430 private RevCommit prepareCherryPick(Git git) throws Exception {
431
432 writeTrashFile("a", "a");
433 git.add().addFilepattern("a").call();
434 RevCommit firstMasterCommit = git.commit().setMessage("first master")
435 .call();
436
437
438 createBranch(firstMasterCommit, "refs/heads/side");
439 checkoutBranch("refs/heads/side");
440
441 writeTrashFile("a", "a(side)");
442 git.add().addFilepattern("a").call();
443 RevCommit sideCommit = git.commit().setMessage("side").call();
444
445
446 checkoutBranch("refs/heads/master");
447
448 writeTrashFile("a", "a(master)");
449 git.add().addFilepattern("a").call();
450 git.commit().setMessage("second master").call();
451 return sideCommit;
452 }
453
454 private RevCommit prepareCherryPickStrategyOption(Git git)
455 throws Exception {
456
457 writeTrashFile("a", "a\nb\nc\n");
458 git.add().addFilepattern("a").call();
459 RevCommit firstMasterCommit = git.commit().setMessage("first master")
460 .call();
461
462
463 createBranch(firstMasterCommit, "refs/heads/side");
464 checkoutBranch("refs/heads/side");
465
466 writeTrashFile("a", "a\nside\nc\nd\n");
467 git.add().addFilepattern("a").call();
468 RevCommit sideCommit = git.commit().setMessage("side").call();
469
470
471 checkoutBranch("refs/heads/master");
472
473 writeTrashFile("a", "a\nmaster\nc\n");
474 git.add().addFilepattern("a").call();
475 git.commit().setMessage("second master").call();
476 return sideCommit;
477 }
478
479 private void doCherryPickAndCheckResult(final Git git,
480 final RevCommit sideCommit, final MergeFailureReason reason)
481 throws Exception {
482
483 String indexState = indexState(CONTENT);
484
485
486 CherryPickResult result = git.cherryPick().include(sideCommit.getId())
487 .call();
488 assertEquals(CherryPickStatus.FAILED, result.getStatus());
489
490 assertEquals(1, result.getFailingPaths().size());
491 assertEquals(reason, result.getFailingPaths().get("a"));
492 assertEquals("a(modified)", read(new File(db.getWorkTree(), "a")));
493
494 assertEquals(indexState, indexState(CONTENT));
495 assertEquals(RepositoryState.SAFE, db.getRepositoryState());
496
497 if (reason == null) {
498 ReflogReader reader = db.getReflogReader(Constants.HEAD);
499 assertTrue(reader.getLastEntry().getComment()
500 .startsWith("cherry-pick: "));
501 reader = db.getReflogReader(db.getBranch());
502 assertTrue(reader.getLastEntry().getComment()
503 .startsWith("cherry-pick: "));
504 }
505 }
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520 @Test
521 public void testCherryPickMerge() throws Exception {
522 try (Git git = new Git(db)) {
523 commitFile("file", "1\n2\n3\n", "master");
524 commitFile("file", "1\n2\n3\n", "side");
525 checkoutBranch("refs/heads/side");
526 RevCommit commitD = commitFile("file", "1\n2\n3\n4\n5\n", "side2");
527 commitFile("file", "a\n2\n3\n", "side");
528 MergeResult mergeResult = git.merge().include(commitD).call();
529 ObjectId commitM = mergeResult.getNewHead();
530 checkoutBranch("refs/heads/master");
531 RevCommit commitT = commitFile("another", "t", "master");
532
533 try {
534 git.cherryPick().include(commitM).call();
535 fail("merges should not be cherry-picked by default");
536 } catch (MultipleParentsNotAllowedException e) {
537
538 }
539 try {
540 git.cherryPick().include(commitM).setMainlineParentNumber(3).call();
541 fail("specifying a non-existent parent should fail");
542 } catch (JGitInternalException e) {
543
544 assertTrue(e.getMessage().endsWith(
545 "does not have a parent number 3."));
546 }
547
548 CherryPickResult result = git.cherryPick().include(commitM)
549 .setMainlineParentNumber(1).call();
550 assertEquals(CherryPickStatus.OK, result.getStatus());
551 checkFile(new File(db.getWorkTree(), "file"), "1\n2\n3\n4\n5\n");
552
553 git.reset().setMode(ResetType.HARD).setRef(commitT.getName()).call();
554
555 CherryPickResult result2 = git.cherryPick().include(commitM)
556 .setMainlineParentNumber(2).call();
557 assertEquals(CherryPickStatus.OK, result2.getStatus());
558 checkFile(new File(db.getWorkTree(), "file"), "a\n2\n3\n");
559 }
560 }
561 }