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