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