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 public PullResult call() throws Exception {
350 StoredConfig config = dbTarget.getConfig();
351 config.setString("pull", null, "rebase", "preserve");
352 config.save();
353 return target.pull().call();
354 }
355 };
356 doTestPullWithRebase(setup, TestPullMode.REBASE_PREASERVE);
357 }
358
359 @Test
360
361 public void testPullWithRebasePreserveConfig2() throws Exception {
362 Callable<PullResult> setup = new Callable<PullResult>() {
363 public PullResult call() throws Exception {
364 StoredConfig config = dbTarget.getConfig();
365 config.setString("pull", null, "rebase", "false");
366 config.setString("branch", "master", "rebase", "preserve");
367 config.save();
368 return target.pull().call();
369 }
370 };
371 doTestPullWithRebase(setup, TestPullMode.REBASE_PREASERVE);
372 }
373
374 @Test
375
376 public void testPullWithRebasePreserveConfig3() throws Exception {
377 Callable<PullResult> setup = new Callable<PullResult>() {
378 public PullResult call() throws Exception {
379 StoredConfig config = dbTarget.getConfig();
380 config.setString("branch", "master", "rebase", "preserve");
381 config.save();
382 return target.pull().call();
383 }
384 };
385 doTestPullWithRebase(setup, TestPullMode.REBASE_PREASERVE);
386 }
387
388 @Test
389
390 public void testPullWithRebaseConfig1() throws Exception {
391 Callable<PullResult> setup = new Callable<PullResult>() {
392 public PullResult call() throws Exception {
393 StoredConfig config = dbTarget.getConfig();
394 config.setString("pull", null, "rebase", "true");
395 config.save();
396 return target.pull().call();
397 }
398 };
399 doTestPullWithRebase(setup, TestPullMode.REBASE);
400 }
401
402 @Test
403
404 public void testPullWithRebaseConfig2() throws Exception {
405 Callable<PullResult> setup = new Callable<PullResult>() {
406 public PullResult call() throws Exception {
407 StoredConfig config = dbTarget.getConfig();
408 config.setString("pull", null, "rebase", "preserve");
409 config.setString("branch", "master", "rebase", "true");
410 config.save();
411 return target.pull().call();
412 }
413 };
414 doTestPullWithRebase(setup, TestPullMode.REBASE);
415 }
416
417 @Test
418
419 public void testPullWithRebaseConfig3() throws Exception {
420 Callable<PullResult> setup = new Callable<PullResult>() {
421 public PullResult call() throws Exception {
422 StoredConfig config = dbTarget.getConfig();
423 config.setString("branch", "master", "rebase", "true");
424 config.save();
425 return target.pull().call();
426 }
427 };
428 doTestPullWithRebase(setup, TestPullMode.REBASE);
429 }
430
431 @Test
432
433 public void testPullWithoutConfig() throws Exception {
434 Callable<PullResult> setup = new Callable<PullResult>() {
435 public PullResult call() throws Exception {
436 return target.pull().call();
437 }
438 };
439 doTestPullWithRebase(setup, TestPullMode.MERGE);
440 }
441
442 @Test
443
444 public void testPullWithMergeConfig() throws Exception {
445 Callable<PullResult> setup = new Callable<PullResult>() {
446 public PullResult call() throws Exception {
447 StoredConfig config = dbTarget.getConfig();
448 config.setString("pull", null, "rebase", "true");
449 config.setString("branch", "master", "rebase", "false");
450 config.save();
451 return target.pull().call();
452 }
453 };
454 doTestPullWithRebase(setup, TestPullMode.MERGE);
455 }
456
457 @Test
458
459 public void testPullWithMergeConfig2() throws Exception {
460 Callable<PullResult> setup = new Callable<PullResult>() {
461 public PullResult call() throws Exception {
462 StoredConfig config = dbTarget.getConfig();
463 config.setString("pull", null, "rebase", "false");
464 config.save();
465 return target.pull().call();
466 }
467 };
468 doTestPullWithRebase(setup, TestPullMode.MERGE);
469 }
470
471 private void doTestPullWithRebase(Callable<PullResult> pullSetup,
472 TestPullMode expectedPullMode) throws Exception {
473
474 writeToFile(sourceFile, "content");
475 source.add().addFilepattern(sourceFile.getName()).call();
476 RevCommit sourceCommit = source.commit().setMessage("source commit")
477 .call();
478
479
480 File loxalFile = new File(dbTarget.getWorkTree(), "local.txt");
481 writeToFile(loxalFile, "initial\n");
482 target.add().addFilepattern("local.txt").call();
483 RevCommit t1 = target.commit().setMessage("target commit 1").call();
484
485 target.checkout().setCreateBranch(true).setName("side").call();
486
487 String newContent = "initial\n" + "and more\n";
488 writeToFile(loxalFile, newContent);
489 target.add().addFilepattern("local.txt").call();
490 RevCommit t2 = target.commit().setMessage("target commit 2").call();
491
492 target.checkout().setName("master").call();
493
494 MergeResult mergeResult = target.merge()
495 .setFastForward(MergeCommand.FastForwardMode.NO_FF).include(t2)
496 .call();
497 assertEquals(MergeStatus.MERGED, mergeResult.getMergeStatus());
498 assertFileContentsEqual(loxalFile, newContent);
499 ObjectId merge = mergeResult.getNewHead();
500
501
502 PullResult res = pullSetup.call();
503 assertNotNull(res.getFetchResult());
504
505 if (expectedPullMode == TestPullMode.MERGE) {
506 assertEquals(MergeStatus.MERGED, res.getMergeResult()
507 .getMergeStatus());
508 assertNull(res.getRebaseResult());
509 } else {
510 assertNull(res.getMergeResult());
511 assertEquals(RebaseResult.OK_RESULT, res.getRebaseResult());
512 }
513 assertFileContentsEqual(sourceFile, "content");
514
515 try (RevWalk rw = new RevWalk(dbTarget)) {
516 rw.sort(RevSort.TOPO);
517 rw.markStart(rw.parseCommit(dbTarget.resolve("refs/heads/master")));
518
519 RevCommit next;
520 if (expectedPullMode == TestPullMode.MERGE) {
521 next = rw.next();
522 assertEquals(2, next.getParentCount());
523 assertEquals(merge, next.getParent(0));
524 assertEquals(sourceCommit, next.getParent(1));
525
526 } else {
527 if (expectedPullMode == TestPullMode.REBASE_PREASERVE) {
528 next = rw.next();
529 assertEquals(2, next.getParentCount());
530 }
531 next = rw.next();
532 assertEquals(t2.getShortMessage(), next.getShortMessage());
533 next = rw.next();
534 assertEquals(t1.getShortMessage(), next.getShortMessage());
535 next = rw.next();
536 assertEquals(sourceCommit, next);
537 next = rw.next();
538 assertEquals("Initial commit for source",
539 next.getShortMessage());
540 next = rw.next();
541 assertNull(next);
542 }
543 }
544 }
545
546 @Override
547 @Before
548 public void setUp() throws Exception {
549 super.setUp();
550 dbTarget = createWorkRepository();
551 source = new Git(db);
552 target = new Git(dbTarget);
553
554
555 sourceFile = new File(db.getWorkTree(), "SomeFile.txt");
556 writeToFile(sourceFile, "Hello world");
557
558 source.add().addFilepattern("SomeFile.txt").call();
559 source.commit().setMessage("Initial commit for source").call();
560
561
562 StoredConfig targetConfig = dbTarget.getConfig();
563 targetConfig.setString("branch", "master", "remote", "origin");
564 targetConfig
565 .setString("branch", "master", "merge", "refs/heads/master");
566 RemoteConfig config = new RemoteConfig(targetConfig, "origin");
567
568 config
569 .addURI(new URIish(source.getRepository().getWorkTree()
570 .getAbsolutePath()));
571 config.addFetchRefSpec(new RefSpec(
572 "+refs/heads/*:refs/remotes/origin/*"));
573 config.update(targetConfig);
574 targetConfig.save();
575
576 targetFile = new File(dbTarget.getWorkTree(), "SomeFile.txt");
577
578 target.pull().call();
579 assertFileContentsEqual(targetFile, "Hello world");
580 }
581
582 private static void writeToFile(File actFile, String string)
583 throws IOException {
584 FileOutputStream fos = null;
585 try {
586 fos = new FileOutputStream(actFile);
587 fos.write(string.getBytes("UTF-8"));
588 fos.close();
589 } finally {
590 if (fos != null)
591 fos.close();
592 }
593 }
594
595 private static void assertFileContentsEqual(File actFile, String string)
596 throws IOException {
597 ByteArrayOutputStream bos = new ByteArrayOutputStream();
598 FileInputStream fis = null;
599 byte[] buffer = new byte[100];
600 try {
601 fis = new FileInputStream(actFile);
602 int read = fis.read(buffer);
603 while (read > 0) {
604 bos.write(buffer, 0, read);
605 read = fis.read(buffer);
606 }
607 String content = new String(bos.toByteArray(), "UTF-8");
608 assertEquals(string, content);
609 } finally {
610 if (fis != null)
611 fis.close();
612 }
613 }
614 }