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