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\nConflicts:\n\ta\n", db.readMergeCommitMsg());
179 assertTrue(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
180 .exists());
181 assertEquals(sideCommit.getId(), db.readCherryPickHead());
182 assertEquals(RepositoryState.CHERRY_PICKING, db.getRepositoryState());
183
184
185 writeTrashFile("a", "a");
186 git.add().addFilepattern("a").call();
187
188 assertEquals(RepositoryState.CHERRY_PICKING_RESOLVED,
189 db.getRepositoryState());
190
191 git.commit().setOnly("a").setMessage("resolve").call();
192
193 assertEquals(RepositoryState.SAFE, db.getRepositoryState());
194 }
195 }
196
197 @Test
198 public void testCherryPickConflictResolutionNoCommit() throws Exception {
199 Git git = new Git(db);
200 RevCommit sideCommit = prepareCherryPick(git);
201
202 CherryPickResult result = git.cherryPick().include(sideCommit.getId())
203 .setNoCommit(true).call();
204
205 assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
206 assertTrue(db.readDirCache().hasUnmergedPaths());
207 String expected = "<<<<<<< master\na(master)\n=======\na(side)\n>>>>>>> 527460a side\n";
208 assertEquals(expected, read("a"));
209 assertTrue(new File(db.getDirectory(), Constants.MERGE_MSG).exists());
210 assertEquals("side\n\nConflicts:\n\ta\n", db.readMergeCommitMsg());
211 assertFalse(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
212 .exists());
213 assertEquals(RepositoryState.SAFE, db.getRepositoryState());
214
215
216 writeTrashFile("a", "a");
217 git.add().addFilepattern("a").call();
218
219 assertEquals(RepositoryState.SAFE, db.getRepositoryState());
220
221 git.commit().setOnly("a").setMessage("resolve").call();
222
223 assertEquals(RepositoryState.SAFE, db.getRepositoryState());
224 }
225
226 @Test
227 public void testCherryPickConflictReset() throws Exception {
228 try (Git git = new Git(db)) {
229 RevCommit sideCommit = prepareCherryPick(git);
230
231 CherryPickResult result = git.cherryPick().include(sideCommit.getId())
232 .call();
233
234 assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
235 assertEquals(RepositoryState.CHERRY_PICKING, db.getRepositoryState());
236 assertTrue(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
237 .exists());
238
239 git.reset().setMode(ResetType.MIXED).setRef("HEAD").call();
240
241 assertEquals(RepositoryState.SAFE, db.getRepositoryState());
242 assertFalse(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
243 .exists());
244 }
245 }
246
247 @Test
248 public void testCherryPickOverExecutableChangeOnNonExectuableFileSystem()
249 throws Exception {
250 try (Git git = new Git(db)) {
251 File file = writeTrashFile("test.txt", "a");
252 assertNotNull(git.add().addFilepattern("test.txt").call());
253 assertNotNull(git.commit().setMessage("commit1").call());
254
255 assertNotNull(git.checkout().setCreateBranch(true).setName("a").call());
256
257 writeTrashFile("test.txt", "b");
258 assertNotNull(git.add().addFilepattern("test.txt").call());
259 RevCommit commit2 = git.commit().setMessage("commit2").call();
260 assertNotNull(commit2);
261
262 assertNotNull(git.checkout().setName(Constants.MASTER).call());
263
264 DirCache cache = db.lockDirCache();
265 cache.getEntry("test.txt").setFileMode(FileMode.EXECUTABLE_FILE);
266 cache.write();
267 assertTrue(cache.commit());
268 cache.unlock();
269
270 assertNotNull(git.commit().setMessage("commit3").call());
271
272 db.getFS().setExecute(file, false);
273 git.getRepository()
274 .getConfig()
275 .setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
276 ConfigConstants.CONFIG_KEY_FILEMODE, false);
277
278 CherryPickResult result = git.cherryPick().include(commit2).call();
279 assertNotNull(result);
280 assertEquals(CherryPickStatus.OK, result.getStatus());
281 }
282 }
283
284 @Test
285 public void testCherryPickOurs() throws Exception {
286 try (Git git = new Git(db)) {
287 RevCommit sideCommit = prepareCherryPick(git);
288
289 CherryPickResult result = git.cherryPick()
290 .include(sideCommit.getId())
291 .setStrategy(MergeStrategy.OURS)
292 .call();
293 assertEquals(CherryPickStatus.OK, result.getStatus());
294
295 String expected = "a(master)";
296 checkFile(new File(db.getWorkTree(), "a"), expected);
297 }
298 }
299
300 @Test
301 public void testCherryPickTheirs() throws Exception {
302 try (Git git = new Git(db)) {
303 RevCommit sideCommit = prepareCherryPick(git);
304
305 CherryPickResult result = git.cherryPick()
306 .include(sideCommit.getId())
307 .setStrategy(MergeStrategy.THEIRS)
308 .call();
309 assertEquals(CherryPickStatus.OK, result.getStatus());
310
311 String expected = "a(side)";
312 checkFile(new File(db.getWorkTree(), "a"), expected);
313 }
314 }
315
316 @Test
317 public void testCherryPickXours() throws Exception {
318 try (Git git = new Git(db)) {
319 RevCommit sideCommit = prepareCherryPickStrategyOption(git);
320
321 CherryPickResult result = git.cherryPick()
322 .include(sideCommit.getId())
323 .setContentMergeStrategy(ContentMergeStrategy.OURS)
324 .call();
325 assertEquals(CherryPickStatus.OK, result.getStatus());
326
327 String expected = "a\nmaster\nc\nd\n";
328 checkFile(new File(db.getWorkTree(), "a"), expected);
329 }
330 }
331
332 @Test
333 public void testCherryPickXtheirs() throws Exception {
334 try (Git git = new Git(db)) {
335 RevCommit sideCommit = prepareCherryPickStrategyOption(git);
336
337 CherryPickResult result = git.cherryPick()
338 .include(sideCommit.getId())
339 .setContentMergeStrategy(ContentMergeStrategy.THEIRS)
340 .call();
341 assertEquals(CherryPickStatus.OK, result.getStatus());
342
343 String expected = "a\nside\nc\nd\n";
344 checkFile(new File(db.getWorkTree(), "a"), expected);
345 }
346 }
347
348 @Test
349 public void testCherryPickConflictMarkers() throws Exception {
350 try (Git git = new Git(db)) {
351 RevCommit sideCommit = prepareCherryPick(git);
352
353 CherryPickResult result = git.cherryPick().include(sideCommit.getId())
354 .call();
355 assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
356
357 String expected = "<<<<<<< master\na(master)\n=======\na(side)\n>>>>>>> 527460a side\n";
358 checkFile(new File(db.getWorkTree(), "a"), expected);
359 }
360 }
361
362 @Test
363 public void testCherryPickConflictFiresModifiedEvent() throws Exception {
364 ListenerHandle listener = null;
365 try (Git git = new Git(db)) {
366 RevCommit sideCommit = prepareCherryPick(git);
367 ChangeRecorder recorder = new ChangeRecorder();
368 listener = db.getListenerList()
369 .addWorkingTreeModifiedListener(recorder);
370 CherryPickResult result = git.cherryPick()
371 .include(sideCommit.getId()).call();
372 assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
373 recorder.assertEvent(new String[] { "a" }, ChangeRecorder.EMPTY);
374 } finally {
375 if (listener != null) {
376 listener.remove();
377 }
378 }
379 }
380
381 @Test
382 public void testCherryPickNewFileFiresModifiedEvent() throws Exception {
383 ListenerHandle listener = null;
384 try (Git git = new Git(db)) {
385 writeTrashFile("test.txt", "a");
386 git.add().addFilepattern("test.txt").call();
387 git.commit().setMessage("commit1").call();
388 git.checkout().setCreateBranch(true).setName("a").call();
389
390 writeTrashFile("side.txt", "side");
391 git.add().addFilepattern("side.txt").call();
392 RevCommit side = git.commit().setMessage("side").call();
393 assertNotNull(side);
394
395 assertNotNull(git.checkout().setName(Constants.MASTER).call());
396 writeTrashFile("test.txt", "b");
397 assertNotNull(git.add().addFilepattern("test.txt").call());
398 assertNotNull(git.commit().setMessage("commit2").call());
399
400 ChangeRecorder recorder = new ChangeRecorder();
401 listener = db.getListenerList()
402 .addWorkingTreeModifiedListener(recorder);
403 CherryPickResult result = git.cherryPick()
404 .include(side.getId()).call();
405 assertEquals(CherryPickStatus.OK, result.getStatus());
406 recorder.assertEvent(new String[] { "side.txt" },
407 ChangeRecorder.EMPTY);
408 } finally {
409 if (listener != null) {
410 listener.remove();
411 }
412 }
413 }
414
415 @Test
416 public void testCherryPickOurCommitName() throws Exception {
417 try (Git git = new Git(db)) {
418 RevCommit sideCommit = prepareCherryPick(git);
419
420 CherryPickResult result = git.cherryPick().include(sideCommit.getId())
421 .setOurCommitName("custom name").call();
422 assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
423
424 String expected = "<<<<<<< custom name\na(master)\n=======\na(side)\n>>>>>>> 527460a side\n";
425 checkFile(new File(db.getWorkTree(), "a"), expected);
426 }
427 }
428
429 private RevCommit prepareCherryPick(Git git) throws Exception {
430
431 writeTrashFile("a", "a");
432 git.add().addFilepattern("a").call();
433 RevCommit firstMasterCommit = git.commit().setMessage("first master")
434 .call();
435
436
437 createBranch(firstMasterCommit, "refs/heads/side");
438 checkoutBranch("refs/heads/side");
439
440 writeTrashFile("a", "a(side)");
441 git.add().addFilepattern("a").call();
442 RevCommit sideCommit = git.commit().setMessage("side").call();
443
444
445 checkoutBranch("refs/heads/master");
446
447 writeTrashFile("a", "a(master)");
448 git.add().addFilepattern("a").call();
449 git.commit().setMessage("second master").call();
450 return sideCommit;
451 }
452
453 private RevCommit prepareCherryPickStrategyOption(Git git)
454 throws Exception {
455
456 writeTrashFile("a", "a\nb\nc\n");
457 git.add().addFilepattern("a").call();
458 RevCommit firstMasterCommit = git.commit().setMessage("first master")
459 .call();
460
461
462 createBranch(firstMasterCommit, "refs/heads/side");
463 checkoutBranch("refs/heads/side");
464
465 writeTrashFile("a", "a\nside\nc\nd\n");
466 git.add().addFilepattern("a").call();
467 RevCommit sideCommit = git.commit().setMessage("side").call();
468
469
470 checkoutBranch("refs/heads/master");
471
472 writeTrashFile("a", "a\nmaster\nc\n");
473 git.add().addFilepattern("a").call();
474 git.commit().setMessage("second master").call();
475 return sideCommit;
476 }
477
478 private void doCherryPickAndCheckResult(final Git git,
479 final RevCommit sideCommit, final MergeFailureReason reason)
480 throws Exception {
481
482 String indexState = indexState(CONTENT);
483
484
485 CherryPickResult result = git.cherryPick().include(sideCommit.getId())
486 .call();
487 assertEquals(CherryPickStatus.FAILED, result.getStatus());
488
489 assertEquals(1, result.getFailingPaths().size());
490 assertEquals(reason, result.getFailingPaths().get("a"));
491 assertEquals("a(modified)", read(new File(db.getWorkTree(), "a")));
492
493 assertEquals(indexState, indexState(CONTENT));
494 assertEquals(RepositoryState.SAFE, db.getRepositoryState());
495
496 if (reason == null) {
497 ReflogReader reader = db.getReflogReader(Constants.HEAD);
498 assertTrue(reader.getLastEntry().getComment()
499 .startsWith("cherry-pick: "));
500 reader = db.getReflogReader(db.getBranch());
501 assertTrue(reader.getLastEntry().getComment()
502 .startsWith("cherry-pick: "));
503 }
504 }
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519 @Test
520 public void testCherryPickMerge() throws Exception {
521 try (Git git = new Git(db)) {
522 commitFile("file", "1\n2\n3\n", "master");
523 commitFile("file", "1\n2\n3\n", "side");
524 checkoutBranch("refs/heads/side");
525 RevCommit commitD = commitFile("file", "1\n2\n3\n4\n5\n", "side2");
526 commitFile("file", "a\n2\n3\n", "side");
527 MergeResult mergeResult = git.merge().include(commitD).call();
528 ObjectId commitM = mergeResult.getNewHead();
529 checkoutBranch("refs/heads/master");
530 RevCommit commitT = commitFile("another", "t", "master");
531
532 try {
533 git.cherryPick().include(commitM).call();
534 fail("merges should not be cherry-picked by default");
535 } catch (MultipleParentsNotAllowedException e) {
536
537 }
538 try {
539 git.cherryPick().include(commitM).setMainlineParentNumber(3).call();
540 fail("specifying a non-existent parent should fail");
541 } catch (JGitInternalException e) {
542
543 assertTrue(e.getMessage().endsWith(
544 "does not have a parent number 3."));
545 }
546
547 CherryPickResult result = git.cherryPick().include(commitM)
548 .setMainlineParentNumber(1).call();
549 assertEquals(CherryPickStatus.OK, result.getStatus());
550 checkFile(new File(db.getWorkTree(), "file"), "1\n2\n3\n4\n5\n");
551
552 git.reset().setMode(ResetType.HARD).setRef(commitT.getName()).call();
553
554 CherryPickResult result2 = git.cherryPick().include(commitM)
555 .setMainlineParentNumber(2).call();
556 assertEquals(CherryPickStatus.OK, result2.getStatus());
557 checkFile(new File(db.getWorkTree(), "file"), "a\n2\n3\n");
558 }
559 }
560 }