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