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