View Javadoc
1   /*
2    * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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  	/** Second Test repository */
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  		// nothing to update since we don't have different data yet
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 		// change the source file
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 		// nothing to update since we don't have different data yet
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 		// nothing to update since we don't have different data yet
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 		// change the source file
164 		writeToFile(sourceFile, "Source change");
165 		source.add().addFilepattern("SomeFile.txt").call();
166 		source.commit().setMessage("Source change in remote").call();
167 
168 		// change the target file
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 		// change the source file
194 		writeToFile(sourceFile, "Source change");
195 		source.add().addFilepattern("SomeFile.txt").call();
196 		source.commit().setMessage("Source change in remote").call();
197 
198 		// write untracked file
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 		// pull from source
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 		// apply the stash
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 		// nothing to update since we don't have different data yet
232 		assertNull(res.getFetchResult());
233 		assertTrue(res.getMergeResult().getMergeStatus().equals(
234 				MergeStatus.ALREADY_UP_TO_DATE));
235 
236 		assertFileContentsEqual(targetFile, "Hello world");
237 
238 		// change the file in master
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 		// change the file in slave
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 		// create another commit on another branch in source
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 		// create another commit on another branch in source
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 		// the source branch "other" matching the target branch should be
326 		// implied
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 	/** global rebase config should be respected */
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 	/** the branch-local config should win over the global config */
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 	/** the branch-local config should be respected */
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 	/** global rebase config should be respected */
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 	/** the branch-local config should win over the global config */
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 	/** the branch-local config should be respected */
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 	/** without config it should merge */
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 	/** the branch local config should win over the global config */
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 	/** the branch local config should win over the global config */
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 		// simple upstream change
484 		writeToFile(sourceFile, "content");
485 		source.add().addFilepattern(sourceFile.getName()).call();
486 		RevCommit sourceCommit = source.commit().setMessage("source commit")
487 				.call();
488 
489 		// create a merge commit in target
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 		// pull
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 				// since both parents are known do no further checks here
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 		// put some file in the source repo
565 		sourceFile = new File(db.getWorkTree(), "SomeFile.txt");
566 		writeToFile(sourceFile, "Hello world");
567 		// and commit it
568 		source.add().addFilepattern("SomeFile.txt").call();
569 		source.commit().setMessage("Initial commit for source").call();
570 
571 		// configure the target repo to connect to the source via "origin"
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 		// make sure we have the same content
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 }