1
2
3
4
5
6
7
8
9
10 package org.eclipse.jgit.api;
11
12 import static java.nio.charset.StandardCharsets.UTF_8;
13 import static org.junit.Assert.assertEquals;
14 import static org.junit.Assert.assertFalse;
15 import static org.junit.Assert.assertNotNull;
16 import static org.junit.Assert.assertNull;
17 import static org.junit.Assert.assertTrue;
18
19 import java.io.ByteArrayOutputStream;
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.util.concurrent.Callable;
25
26 import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
27 import org.eclipse.jgit.api.MergeResult.MergeStatus;
28 import org.eclipse.jgit.api.errors.NoHeadException;
29 import org.eclipse.jgit.junit.JGitTestUtil;
30 import org.eclipse.jgit.junit.RepositoryTestCase;
31 import org.eclipse.jgit.lib.Constants;
32 import org.eclipse.jgit.lib.ObjectId;
33 import org.eclipse.jgit.lib.RefUpdate;
34 import org.eclipse.jgit.lib.Repository;
35 import org.eclipse.jgit.lib.RepositoryState;
36 import org.eclipse.jgit.lib.StoredConfig;
37 import org.eclipse.jgit.merge.ContentMergeStrategy;
38 import org.eclipse.jgit.merge.MergeStrategy;
39 import org.eclipse.jgit.revwalk.RevCommit;
40 import org.eclipse.jgit.revwalk.RevSort;
41 import org.eclipse.jgit.revwalk.RevWalk;
42 import org.eclipse.jgit.transport.RefSpec;
43 import org.eclipse.jgit.transport.RemoteConfig;
44 import org.eclipse.jgit.transport.URIish;
45 import org.junit.Before;
46 import org.junit.Test;
47
48 public class PullCommandTest extends RepositoryTestCase {
49
50 protected Repository dbTarget;
51
52 private Git source;
53
54 private Git target;
55
56 private File sourceFile;
57
58 private File targetFile;
59
60 @Test
61 public void testPullFastForward() throws Exception {
62 PullResult res = target.pull().call();
63
64 assertTrue(res.getFetchResult().getTrackingRefUpdates().isEmpty());
65 assertTrue(res.getMergeResult().getMergeStatus().equals(
66 MergeStatus.ALREADY_UP_TO_DATE));
67
68 assertFileContentsEqual(targetFile, "Hello world");
69
70
71 writeToFile(sourceFile, "Another change");
72 source.add().addFilepattern("SomeFile.txt").call();
73 source.commit().setMessage("Some change in remote").call();
74
75 res = target.pull().call();
76
77 assertFalse(res.getFetchResult().getTrackingRefUpdates().isEmpty());
78 assertEquals(res.getMergeResult().getMergeStatus(),
79 MergeStatus.FAST_FORWARD);
80 assertFileContentsEqual(targetFile, "Another change");
81 assertEquals(RepositoryState.SAFE, target.getRepository()
82 .getRepositoryState());
83
84 res = target.pull().call();
85 assertEquals(res.getMergeResult().getMergeStatus(),
86 MergeStatus.ALREADY_UP_TO_DATE);
87 }
88
89 @Test
90 public void testPullMerge() throws Exception {
91 PullResult res = target.pull().call();
92
93 assertTrue(res.getFetchResult().getTrackingRefUpdates().isEmpty());
94 assertTrue(res.getMergeResult().getMergeStatus()
95 .equals(MergeStatus.ALREADY_UP_TO_DATE));
96
97 writeToFile(sourceFile, "Source change");
98 source.add().addFilepattern("SomeFile.txt");
99 RevCommit sourceCommit = source.commit()
100 .setMessage("Source change in remote").call();
101
102 File targetFile2 = new File(dbTarget.getWorkTree(), "OtherFile.txt");
103 writeToFile(targetFile2, "Unconflicting change");
104 target.add().addFilepattern("OtherFile.txt").call();
105 RevCommit targetCommit = target.commit()
106 .setMessage("Unconflicting change in local").call();
107
108 res = target.pull().call();
109
110 MergeResult mergeResult = res.getMergeResult();
111 ObjectId[] mergedCommits = mergeResult.getMergedCommits();
112 assertEquals(targetCommit.getId(), mergedCommits[0]);
113 assertEquals(sourceCommit.getId(), mergedCommits[1]);
114 try (RevWalk rw = new RevWalk(dbTarget)) {
115 RevCommit mergeCommit = rw.parseCommit(mergeResult.getNewHead());
116 String message = "Merge branch 'master' of "
117 + db.getWorkTree().getAbsolutePath();
118 assertEquals(message, mergeCommit.getShortMessage());
119 }
120 }
121
122 @Test
123 public void testPullConflict() throws Exception {
124 PullResult res = target.pull().call();
125
126 assertTrue(res.getFetchResult().getTrackingRefUpdates().isEmpty());
127 assertTrue(res.getMergeResult().getMergeStatus().equals(
128 MergeStatus.ALREADY_UP_TO_DATE));
129
130 assertFileContentsEqual(targetFile, "Hello world");
131
132
133 writeToFile(sourceFile, "Source change");
134 source.add().addFilepattern("SomeFile.txt").call();
135 source.commit().setMessage("Source change in remote").call();
136
137
138 writeToFile(targetFile, "Target change");
139 target.add().addFilepattern("SomeFile.txt").call();
140 target.commit().setMessage("Target change in local").call();
141
142 res = target.pull().call();
143
144 String sourceChangeString = "Source change\n>>>>>>> branch 'master' of "
145 + target.getRepository().getConfig().getString("remote",
146 "origin", "url");
147
148 assertFalse(res.getFetchResult().getTrackingRefUpdates().isEmpty());
149 assertEquals(res.getMergeResult().getMergeStatus(),
150 MergeStatus.CONFLICTING);
151 String result = "<<<<<<< HEAD\nTarget change\n=======\n"
152 + sourceChangeString + "\n";
153 assertFileContentsEqual(targetFile, result);
154 assertEquals(RepositoryState.MERGING, target.getRepository()
155 .getRepositoryState());
156 }
157
158 @Test
159 public void testPullConflictTheirs() throws Exception {
160 PullResult res = target.pull().call();
161
162 assertTrue(res.getFetchResult().getTrackingRefUpdates().isEmpty());
163 assertTrue(res.getMergeResult().getMergeStatus()
164 .equals(MergeStatus.ALREADY_UP_TO_DATE));
165
166 assertFileContentsEqual(targetFile, "Hello world");
167
168
169 writeToFile(sourceFile, "Source change");
170 source.add().addFilepattern("SomeFile.txt").call();
171 source.commit().setMessage("Source change in remote").call();
172
173
174 writeToFile(targetFile, "Target change");
175 target.add().addFilepattern("SomeFile.txt").call();
176 target.commit().setMessage("Target change in local").call();
177
178 res = target.pull().setStrategy(MergeStrategy.THEIRS).call();
179
180 assertTrue(res.isSuccessful());
181 assertFileContentsEqual(targetFile, "Source change");
182 assertEquals(RepositoryState.SAFE,
183 target.getRepository().getRepositoryState());
184 assertTrue(target.status().call().isClean());
185 }
186
187 @Test
188 public void testPullConflictXtheirs() throws Exception {
189 PullResult res = target.pull().call();
190
191 assertTrue(res.getFetchResult().getTrackingRefUpdates().isEmpty());
192 assertTrue(res.getMergeResult().getMergeStatus()
193 .equals(MergeStatus.ALREADY_UP_TO_DATE));
194
195 assertFileContentsEqual(targetFile, "Hello world");
196
197
198 writeToFile(sourceFile, "a\nHello\nb\n");
199 source.add().addFilepattern("SomeFile.txt").call();
200 source.commit().setMessage("Multi-line change in remote").call();
201
202
203 res = target.pull().call();
204 assertTrue(res.isSuccessful());
205 assertFileContentsEqual(targetFile, "a\nHello\nb\n");
206
207
208 writeToFile(sourceFile, "a\nSource change\nb\n");
209 source.add().addFilepattern("SomeFile.txt").call();
210 source.commit().setMessage("Source change in remote").call();
211
212
213 writeToFile(targetFile, "a\nTarget change\nb\nc\n");
214 target.add().addFilepattern("SomeFile.txt").call();
215 target.commit().setMessage("Target change in local").call();
216
217 res = target.pull().setContentMergeStrategy(ContentMergeStrategy.THEIRS)
218 .call();
219
220 assertTrue(res.isSuccessful());
221 assertFileContentsEqual(targetFile, "a\nSource change\nb\nc\n");
222 assertEquals(RepositoryState.SAFE,
223 target.getRepository().getRepositoryState());
224 assertTrue(target.status().call().isClean());
225 }
226
227 @Test
228 public void testPullWithUntrackedStash() throws Exception {
229 target.pull().call();
230
231
232 writeToFile(sourceFile, "Source change");
233 source.add().addFilepattern("SomeFile.txt").call();
234 source.commit().setMessage("Source change in remote").call();
235
236
237 writeToFile(new File(dbTarget.getWorkTree(), "untracked.txt"),
238 "untracked");
239 RevCommit stash = target.stashCreate().setIndexMessage("message here")
240 .setIncludeUntracked(true).call();
241 assertNotNull(stash);
242 assertTrue(target.status().call().isClean());
243
244
245 assertTrue(target.pull().call().isSuccessful());
246 assertEquals("[SomeFile.txt, mode:100644, content:Source change]",
247 indexState(dbTarget, CONTENT));
248 assertFalse(JGitTestUtil.check(dbTarget, "untracked.txt"));
249 assertEquals("Source change",
250 JGitTestUtil.read(dbTarget, "SomeFile.txt"));
251
252
253 target.stashApply().setStashRef(stash.getName()).call();
254 assertEquals("[SomeFile.txt, mode:100644, content:Source change]",
255 indexState(dbTarget, CONTENT));
256 assertEquals("untracked", JGitTestUtil.read(dbTarget, "untracked.txt"));
257 assertEquals("Source change",
258 JGitTestUtil.read(dbTarget, "SomeFile.txt"));
259 }
260
261 @Test
262 public void testPullLocalConflict() throws Exception {
263 target.branchCreate().setName("basedOnMaster").setStartPoint(
264 "refs/heads/master").setUpstreamMode(SetupUpstreamMode.TRACK)
265 .call();
266 target.getRepository().updateRef(Constants.HEAD).link(
267 "refs/heads/basedOnMaster");
268 PullResult res = target.pull().call();
269
270 assertNull(res.getFetchResult());
271 assertTrue(res.getMergeResult().getMergeStatus().equals(
272 MergeStatus.ALREADY_UP_TO_DATE));
273
274 assertFileContentsEqual(targetFile, "Hello world");
275
276
277 target.getRepository().updateRef(Constants.HEAD).link(
278 "refs/heads/master");
279 writeToFile(targetFile, "Master change");
280 target.add().addFilepattern("SomeFile.txt").call();
281 target.commit().setMessage("Source change in master").call();
282
283
284 target.getRepository().updateRef(Constants.HEAD).link(
285 "refs/heads/basedOnMaster");
286 writeToFile(targetFile, "Slave change");
287 target.add().addFilepattern("SomeFile.txt").call();
288 target.commit().setMessage("Source change in based on master").call();
289
290 res = target.pull().call();
291
292 String sourceChangeString = "Master change\n>>>>>>> branch 'master' of local repository";
293
294 assertNull(res.getFetchResult());
295 assertEquals(res.getMergeResult().getMergeStatus(),
296 MergeStatus.CONFLICTING);
297 String result = "<<<<<<< HEAD\nSlave change\n=======\n"
298 + sourceChangeString + "\n";
299 assertFileContentsEqual(targetFile, result);
300 assertEquals(RepositoryState.MERGING, target.getRepository()
301 .getRepositoryState());
302 }
303
304 @Test(expected = NoHeadException.class)
305 public void testPullEmptyRepository() throws Exception {
306 Repository empty = createWorkRepository();
307 RefUpdate delete = empty.updateRef(Constants.HEAD, true);
308 delete.setForceUpdate(true);
309 delete.delete();
310 Git.wrap(empty).pull().call();
311 }
312
313 @Test
314 public void testPullMergeProgrammaticConfiguration() throws Exception {
315
316 source.checkout().setCreateBranch(true).setName("other").call();
317 sourceFile = new File(db.getWorkTree(), "file2.txt");
318 writeToFile(sourceFile, "content");
319 source.add().addFilepattern("file2.txt").call();
320 RevCommit sourceCommit = source.commit()
321 .setMessage("source commit on branch other").call();
322
323 File targetFile2 = new File(dbTarget.getWorkTree(), "OtherFile.txt");
324 writeToFile(targetFile2, "Unconflicting change");
325 target.add().addFilepattern("OtherFile.txt").call();
326 RevCommit targetCommit = target.commit()
327 .setMessage("Unconflicting change in local").call();
328
329 PullResult res = target.pull().setRemote("origin")
330 .setRemoteBranchName("other")
331 .setRebase(false).call();
332
333 MergeResult mergeResult = res.getMergeResult();
334 ObjectId[] mergedCommits = mergeResult.getMergedCommits();
335 assertEquals(targetCommit.getId(), mergedCommits[0]);
336 assertEquals(sourceCommit.getId(), mergedCommits[1]);
337 try (RevWalk rw = new RevWalk(dbTarget)) {
338 RevCommit mergeCommit = rw.parseCommit(mergeResult.getNewHead());
339 String message = "Merge branch 'other' of "
340 + db.getWorkTree().getAbsolutePath();
341 assertEquals(message, mergeCommit.getShortMessage());
342 }
343 }
344
345 @Test
346 public void testPullMergeProgrammaticConfigurationImpliedTargetBranch()
347 throws Exception {
348
349 source.checkout().setCreateBranch(true).setName("other").call();
350 sourceFile = new File(db.getWorkTree(), "file2.txt");
351 writeToFile(sourceFile, "content");
352 source.add().addFilepattern("file2.txt").call();
353 RevCommit sourceCommit = source.commit()
354 .setMessage("source commit on branch other").call();
355
356 target.checkout().setCreateBranch(true).setName("other").call();
357 File targetFile2 = new File(dbTarget.getWorkTree(), "OtherFile.txt");
358 writeToFile(targetFile2, "Unconflicting change");
359 target.add().addFilepattern("OtherFile.txt").call();
360 RevCommit targetCommit = target.commit()
361 .setMessage("Unconflicting change in local").call();
362
363
364
365 PullResult res = target.pull().setRemote("origin").setRebase(false)
366 .call();
367
368 MergeResult mergeResult = res.getMergeResult();
369 ObjectId[] mergedCommits = mergeResult.getMergedCommits();
370 assertEquals(targetCommit.getId(), mergedCommits[0]);
371 assertEquals(sourceCommit.getId(), mergedCommits[1]);
372 try (RevWalk rw = new RevWalk(dbTarget)) {
373 RevCommit mergeCommit = rw.parseCommit(mergeResult.getNewHead());
374 String message = "Merge branch 'other' of "
375 + db.getWorkTree().getAbsolutePath() + " into other";
376 assertEquals(message, mergeCommit.getShortMessage());
377 }
378 }
379
380 private enum TestPullMode {
381 MERGE, REBASE, REBASE_PREASERVE
382 }
383
384 @Test
385
386 public void testPullWithRebasePreserve1Config() throws Exception {
387 Callable<PullResult> setup = () -> {
388 StoredConfig config = dbTarget.getConfig();
389 config.setString("pull", null, "rebase", "preserve");
390 config.save();
391 return target.pull().call();
392 };
393 doTestPullWithRebase(setup, TestPullMode.REBASE_PREASERVE);
394 }
395
396 @Test
397
398 public void testPullWithRebasePreserveConfig2() throws Exception {
399 Callable<PullResult> setup = () -> {
400 StoredConfig config = dbTarget.getConfig();
401 config.setString("pull", null, "rebase", "false");
402 config.setString("branch", "master", "rebase", "preserve");
403 config.save();
404 return target.pull().call();
405 };
406 doTestPullWithRebase(setup, TestPullMode.REBASE_PREASERVE);
407 }
408
409 @Test
410
411 public void testPullWithRebasePreserveConfig3() throws Exception {
412 Callable<PullResult> setup = () -> {
413 StoredConfig config = dbTarget.getConfig();
414 config.setString("branch", "master", "rebase", "preserve");
415 config.save();
416 return target.pull().call();
417 };
418 doTestPullWithRebase(setup, TestPullMode.REBASE_PREASERVE);
419 }
420
421 @Test
422
423 public void testPullWithRebaseConfig1() throws Exception {
424 Callable<PullResult> setup = () -> {
425 StoredConfig config = dbTarget.getConfig();
426 config.setString("pull", null, "rebase", "true");
427 config.save();
428 return target.pull().call();
429 };
430 doTestPullWithRebase(setup, TestPullMode.REBASE);
431 }
432
433 @Test
434
435 public void testPullWithRebaseConfig2() throws Exception {
436 Callable<PullResult> setup = () -> {
437 StoredConfig config = dbTarget.getConfig();
438 config.setString("pull", null, "rebase", "preserve");
439 config.setString("branch", "master", "rebase", "true");
440 config.save();
441 return target.pull().call();
442 };
443 doTestPullWithRebase(setup, TestPullMode.REBASE);
444 }
445
446 @Test
447
448 public void testPullWithRebaseConfig3() throws Exception {
449 Callable<PullResult> setup = () -> {
450 StoredConfig config = dbTarget.getConfig();
451 config.setString("branch", "master", "rebase", "true");
452 config.save();
453 return target.pull().call();
454 };
455 doTestPullWithRebase(setup, TestPullMode.REBASE);
456 }
457
458 @Test
459
460 public void testPullWithoutConfig() throws Exception {
461 Callable<PullResult> setup = target.pull()::call;
462 doTestPullWithRebase(setup, TestPullMode.MERGE);
463 }
464
465 @Test
466
467 public void testPullWithMergeConfig() throws Exception {
468 Callable<PullResult> setup = () -> {
469 StoredConfig config = dbTarget.getConfig();
470 config.setString("pull", null, "rebase", "true");
471 config.setString("branch", "master", "rebase", "false");
472 config.save();
473 return target.pull().call();
474 };
475 doTestPullWithRebase(setup, TestPullMode.MERGE);
476 }
477
478 @Test
479
480 public void testPullWithMergeConfig2() throws Exception {
481 Callable<PullResult> setup = () -> {
482 StoredConfig config = dbTarget.getConfig();
483 config.setString("pull", null, "rebase", "false");
484 config.save();
485 return target.pull().call();
486 };
487 doTestPullWithRebase(setup, TestPullMode.MERGE);
488 }
489
490 private void doTestPullWithRebase(Callable<PullResult> pullSetup,
491 TestPullMode expectedPullMode) throws Exception {
492
493 writeToFile(sourceFile, "content");
494 source.add().addFilepattern(sourceFile.getName()).call();
495 RevCommit sourceCommit = source.commit().setMessage("source commit")
496 .call();
497
498
499 File loxalFile = new File(dbTarget.getWorkTree(), "local.txt");
500 writeToFile(loxalFile, "initial\n");
501 target.add().addFilepattern("local.txt").call();
502 RevCommit t1 = target.commit().setMessage("target commit 1").call();
503
504 target.checkout().setCreateBranch(true).setName("side").call();
505
506 String newContent = "initial\n" + "and more\n";
507 writeToFile(loxalFile, newContent);
508 target.add().addFilepattern("local.txt").call();
509 RevCommit t2 = target.commit().setMessage("target commit 2").call();
510
511 target.checkout().setName("master").call();
512
513 MergeResult mergeResult = target.merge()
514 .setFastForward(MergeCommand.FastForwardMode.NO_FF).include(t2)
515 .call();
516 assertEquals(MergeStatus.MERGED, mergeResult.getMergeStatus());
517 assertFileContentsEqual(loxalFile, newContent);
518 ObjectId merge = mergeResult.getNewHead();
519
520
521 PullResult res = pullSetup.call();
522 assertNotNull(res.getFetchResult());
523
524 if (expectedPullMode == TestPullMode.MERGE) {
525 assertEquals(MergeStatus.MERGED, res.getMergeResult()
526 .getMergeStatus());
527 assertNull(res.getRebaseResult());
528 } else {
529 assertNull(res.getMergeResult());
530 assertEquals(RebaseResult.OK_RESULT, res.getRebaseResult());
531 }
532 assertFileContentsEqual(sourceFile, "content");
533
534 try (RevWalk rw = new RevWalk(dbTarget)) {
535 rw.sort(RevSort.TOPO);
536 rw.markStart(rw.parseCommit(dbTarget.resolve("refs/heads/master")));
537
538 RevCommit next;
539 if (expectedPullMode == TestPullMode.MERGE) {
540 next = rw.next();
541 assertEquals(2, next.getParentCount());
542 assertEquals(merge, next.getParent(0));
543 assertEquals(sourceCommit, next.getParent(1));
544
545 } else {
546 if (expectedPullMode == TestPullMode.REBASE_PREASERVE) {
547 next = rw.next();
548 assertEquals(2, next.getParentCount());
549 }
550 next = rw.next();
551 assertEquals(t2.getShortMessage(), next.getShortMessage());
552 next = rw.next();
553 assertEquals(t1.getShortMessage(), next.getShortMessage());
554 next = rw.next();
555 assertEquals(sourceCommit, next);
556 next = rw.next();
557 assertEquals("Initial commit for source",
558 next.getShortMessage());
559 next = rw.next();
560 assertNull(next);
561 }
562 }
563 }
564
565 @Override
566 @Before
567 public void setUp() throws Exception {
568 super.setUp();
569 dbTarget = createWorkRepository();
570 addRepoToClose(dbTarget);
571 source = new Git(db);
572 target = new Git(dbTarget);
573
574
575 sourceFile = new File(db.getWorkTree(), "SomeFile.txt");
576 writeToFile(sourceFile, "Hello world");
577
578 source.add().addFilepattern("SomeFile.txt").call();
579 source.commit().setMessage("Initial commit for source").call();
580
581
582 StoredConfig targetConfig = dbTarget.getConfig();
583 targetConfig.setString("branch", "master", "remote", "origin");
584 targetConfig
585 .setString("branch", "master", "merge", "refs/heads/master");
586 RemoteConfig config = new RemoteConfig(targetConfig, "origin");
587
588 config
589 .addURI(new URIish(source.getRepository().getWorkTree()
590 .getAbsolutePath()));
591 config.addFetchRefSpec(new RefSpec(
592 "+refs/heads/*:refs/remotes/origin/*"));
593 config.update(targetConfig);
594 targetConfig.save();
595
596 targetFile = new File(dbTarget.getWorkTree(), "SomeFile.txt");
597
598 target.pull().call();
599 assertFileContentsEqual(targetFile, "Hello world");
600 }
601
602 private static void writeToFile(File actFile, String string)
603 throws IOException {
604 try (FileOutputStream fos = new FileOutputStream(actFile)) {
605 fos.write(string.getBytes(UTF_8));
606 }
607 }
608
609 private static void assertFileContentsEqual(File actFile, String string)
610 throws IOException {
611 ByteArrayOutputStream bos = new ByteArrayOutputStream();
612 byte[] buffer = new byte[100];
613 try (FileInputStream fis = new FileInputStream(actFile)) {
614 int read = fis.read(buffer);
615 while (read > 0) {
616 bos.write(buffer, 0, read);
617 read = fis.read(buffer);
618 }
619 String content = new String(bos.toByteArray(), UTF_8);
620 assertEquals(string, content);
621 }
622 }
623 }