View Javadoc
1   /*
2    * Copyright (C) 2011, 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 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  
57  import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
58  import org.eclipse.jgit.api.MergeResult.MergeStatus;
59  import org.eclipse.jgit.api.RebaseResult.Status;
60  import org.eclipse.jgit.junit.RepositoryTestCase;
61  import org.eclipse.jgit.lib.ConfigConstants;
62  import org.eclipse.jgit.lib.Constants;
63  import org.eclipse.jgit.lib.ObjectId;
64  import org.eclipse.jgit.lib.Ref;
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.merge.MergeStrategy;
69  import org.eclipse.jgit.revwalk.RevCommit;
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 PullCommandWithRebaseTest extends RepositoryTestCase {
78  	/** Second Test repository */
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  		// nothing to update since we don't have different data yet
93  		assertTrue(res.getFetchResult().getTrackingRefUpdates().isEmpty());
94  		assertEquals(Status.UP_TO_DATE, res.getRebaseResult().getStatus());
95  
96  		assertFileContentsEqual(targetFile, "Hello world");
97  
98  		// change the source file
99  		writeToFile(sourceFile, "Another change");
100 		source.add().addFilepattern("SomeFile.txt").call();
101 		source.commit().setMessage("Some change in remote").call();
102 
103 		res = target.pull().call();
104 
105 		assertFalse(res.getFetchResult().getTrackingRefUpdates().isEmpty());
106 		assertEquals(Status.FAST_FORWARD, res.getRebaseResult().getStatus());
107 		assertFileContentsEqual(targetFile, "Another change");
108 		assertEquals(RepositoryState.SAFE, target.getRepository()
109 				.getRepositoryState());
110 
111 		res = target.pull().call();
112 		assertEquals(Status.UP_TO_DATE, res.getRebaseResult().getStatus());
113 	}
114 
115 	@Test
116 	public void testPullFastForwardWithBranchInSource() throws Exception {
117 		PullResult res = target.pull().call();
118 		// nothing to update since we don't have different data yet
119 		assertTrue(res.getFetchResult().getTrackingRefUpdates().isEmpty());
120 		assertEquals(Status.UP_TO_DATE, res.getRebaseResult().getStatus());
121 
122 		assertFileContentsEqual(targetFile, "Hello world");
123 
124 		// change the source file
125 		writeToFile(sourceFile, "Another change\n\n\n\nFoo");
126 		source.add().addFilepattern("SomeFile.txt").call();
127 		RevCommit initialCommit = source.commit()
128 				.setMessage("Some change in remote").call();
129 
130 		// modify the source file in a branch
131 		createBranch(initialCommit, "refs/heads/side");
132 		checkoutBranch("refs/heads/side");
133 		writeToFile(sourceFile, "Another change\n\n\n\nBoo");
134 		source.add().addFilepattern("SomeFile.txt").call();
135 		RevCommit sideCommit = source.commit()
136 				.setMessage("Some change in remote").call();
137 
138 		// modify the source file on master
139 		checkoutBranch("refs/heads/master");
140 		writeToFile(sourceFile, "More change\n\n\n\nFoo");
141 		source.add().addFilepattern("SomeFile.txt").call();
142 		source.commit().setMessage("Some change in remote").call();
143 
144 		// merge side into master
145 		MergeResult result = source.merge().include(sideCommit.getId())
146 				.setStrategy(MergeStrategy.RESOLVE).call();
147 		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
148 
149 	}
150 
151 	@Test
152 	public void testPullFastForwardDetachedHead() throws Exception {
153 		Repository repository = source.getRepository();
154 		writeToFile(sourceFile, "2nd commit");
155 		source.add().addFilepattern("SomeFile.txt").call();
156 		source.commit().setMessage("2nd commit").call();
157 
158 		try (RevWalk revWalk = new RevWalk(repository)) {
159 			// git checkout HEAD^
160 			String initialBranch = repository.getBranch();
161 			Ref initialRef = repository.findRef(Constants.HEAD);
162 			RevCommit initialCommit = revWalk
163 					.parseCommit(initialRef.getObjectId());
164 			assertEquals("this test need linear history", 1,
165 					initialCommit.getParentCount());
166 			source.checkout().setName(initialCommit.getParent(0).getName())
167 					.call();
168 			assertFalse("expected detached HEAD",
169 					repository.getFullBranch().startsWith(Constants.R_HEADS));
170 
171 			// change and commit another file
172 			File otherFile = new File(sourceFile.getParentFile(),
173 					System.currentTimeMillis() + ".tst");
174 			writeToFile(otherFile, "other 2nd commit");
175 			source.add().addFilepattern(otherFile.getName()).call();
176 			RevCommit newCommit = source.commit().setMessage("other 2nd commit")
177 					.call();
178 
179 			// git pull --rebase initialBranch
180 			source.pull().setRebase(true).setRemote(".")
181 					.setRemoteBranchName(initialBranch)
182 					.call();
183 
184 			assertEquals(RepositoryState.SAFE,
185 					source.getRepository().getRepositoryState());
186 			Ref head = source.getRepository().findRef(Constants.HEAD);
187 			RevCommit headCommit = revWalk.parseCommit(head.getObjectId());
188 
189 			// HEAD^ == initialCommit, no merge commit
190 			assertEquals(1, headCommit.getParentCount());
191 			assertEquals(initialCommit, headCommit.getParent(0));
192 
193 			// both contributions for both commits are available
194 			assertFileContentsEqual(sourceFile, "2nd commit");
195 			assertFileContentsEqual(otherFile, "other 2nd commit");
196 			// HEAD has same message as rebased commit
197 			assertEquals(newCommit.getShortMessage(),
198 					headCommit.getShortMessage());
199 		}
200 	}
201 
202 	@Test
203 	public void testPullConflict() throws Exception {
204 		PullResult res = target.pull().call();
205 		// nothing to update since we don't have different data yet
206 		assertTrue(res.getFetchResult().getTrackingRefUpdates().isEmpty());
207 		assertEquals(Status.UP_TO_DATE, res.getRebaseResult().getStatus());
208 
209 		assertFileContentsEqual(targetFile, "Hello world");
210 
211 		// change the source file
212 		writeToFile(sourceFile, "Source change");
213 		source.add().addFilepattern("SomeFile.txt").call();
214 		source.commit().setMessage("Source change in remote").call();
215 
216 		// change the target file
217 		writeToFile(targetFile, "Target change");
218 		target.add().addFilepattern("SomeFile.txt").call();
219 		target.commit().setMessage("Target change in local").call();
220 
221 		res = target.pull().call();
222 
223 		String remoteUri = target
224 				.getRepository()
225 				.getConfig()
226 				.getString(ConfigConstants.CONFIG_REMOTE_SECTION, "origin",
227 						ConfigConstants.CONFIG_KEY_URL);
228 
229 		assertFalse(res.getFetchResult().getTrackingRefUpdates().isEmpty());
230 		assertEquals(Status.STOPPED, res.getRebaseResult().getStatus());
231 		String result = "<<<<<<< Upstream, based on branch 'master' of "
232 				+ remoteUri
233 				+ "\nSource change\n=======\nTarget change\n>>>>>>> 42453fd Target change in local\n";
234 		assertFileContentsEqual(targetFile, result);
235 		assertEquals(RepositoryState.REBASING_MERGE, target
236 				.getRepository().getRepositoryState());
237 	}
238 
239 	@Test
240 	public void testPullLocalConflict() throws Exception {
241 		target.branchCreate().setName("basedOnMaster").setStartPoint(
242 				"refs/heads/master").setUpstreamMode(SetupUpstreamMode.NOTRACK)
243 				.call();
244 		StoredConfig config = target.getRepository().getConfig();
245 		config.setString("branch", "basedOnMaster", "remote", ".");
246 		config.setString("branch", "basedOnMaster", "merge",
247 				"refs/heads/master");
248 		config.setBoolean("branch", "basedOnMaster", "rebase", true);
249 		config.save();
250 		target.getRepository().updateRef(Constants.HEAD).link(
251 				"refs/heads/basedOnMaster");
252 		PullResult res = target.pull().call();
253 		// nothing to update since we don't have different data yet
254 		assertNull(res.getFetchResult());
255 		assertEquals(Status.UP_TO_DATE, res.getRebaseResult().getStatus());
256 
257 		assertFileContentsEqual(targetFile, "Hello world");
258 
259 		// change the file in master
260 		target.getRepository().updateRef(Constants.HEAD).link(
261 				"refs/heads/master");
262 		writeToFile(targetFile, "Master change");
263 		target.add().addFilepattern("SomeFile.txt").call();
264 		target.commit().setMessage("Source change in master").call();
265 
266 		// change the file in slave
267 		target.getRepository().updateRef(Constants.HEAD).link(
268 				"refs/heads/basedOnMaster");
269 		writeToFile(targetFile, "Slave change");
270 		target.add().addFilepattern("SomeFile.txt").call();
271 		target.commit().setMessage("Source change in based on master").call();
272 
273 		res = target.pull().call();
274 
275 		assertNull(res.getFetchResult());
276 		assertEquals(Status.STOPPED, res.getRebaseResult().getStatus());
277 		String result = "<<<<<<< Upstream, based on branch 'master' of local repository\n"
278 				+ "Master change\n=======\nSlave change\n>>>>>>> 4049c9e Source change in based on master\n";
279 		assertFileContentsEqual(targetFile, result);
280 		assertEquals(RepositoryState.REBASING_MERGE, target
281 				.getRepository().getRepositoryState());
282 	}
283 
284     @Test
285 	public void testPullFastForwardWithLocalCommitAndRebaseFlagSet() throws Exception {
286 		final String SOURCE_COMMIT_MESSAGE = "Source commit message for rebase flag test";
287 		final String TARGET_COMMIT_MESSAGE = "Target commit message for rebase flag test";
288 
289 		assertFalse(SOURCE_COMMIT_MESSAGE.equals(TARGET_COMMIT_MESSAGE));
290 
291 		final String SOURCE_FILE_CONTENTS = "Source change";
292 		final String NEW_FILE_CONTENTS = "New file from target";
293 
294 		// make sure the config for target says we should pull with merge
295 		// we will override this later with the setRebase method
296 		StoredConfig targetConfig = dbTarget.getConfig();
297 		targetConfig.setBoolean("branch", "master", "rebase", false);
298 		targetConfig.save();
299 
300 		// create commit in source
301 		writeToFile(sourceFile, SOURCE_FILE_CONTENTS);
302 		source.add().addFilepattern(sourceFile.getName()).call();
303 		source.commit().setMessage(SOURCE_COMMIT_MESSAGE).call();
304 
305 		// create commit in target, not conflicting with the new commit in source
306 		File newFile = new File(dbTarget.getWorkTree().getPath() + "/newFile.txt");
307 		writeToFile(newFile, NEW_FILE_CONTENTS);
308 		target.add().addFilepattern(newFile.getName()).call();
309 		target.commit().setMessage(TARGET_COMMIT_MESSAGE).call();
310 
311 		// verify that rebase is set to false in the config
312 		assertFalse(targetConfig.getBoolean("branch", "master", "rebase", true));
313 
314 		// pull with rebase - local commit in target should be on top
315 		PullResult pullResult = target.pull().setRebase(true).call();
316 
317 		// make sure pull is considered successful
318 		assertTrue(pullResult.isSuccessful());
319 
320 		// verify rebase result is ok
321 		RebaseResult rebaseResult = pullResult.getRebaseResult();
322 		assertNotNull(rebaseResult);
323 		assertNull(rebaseResult.getFailingPaths());
324 		assertEquals(Status.OK, rebaseResult.getStatus());
325 
326 		// Get the HEAD and HEAD~1 commits
327 		Repository targetRepo = target.getRepository();
328 		try (RevWalk revWalk = new RevWalk(targetRepo)) {
329 			ObjectId headId = targetRepo.resolve(Constants.HEAD);
330 			RevCommit root = revWalk.parseCommit(headId);
331 			revWalk.markStart(root);
332 			// HEAD
333 			RevCommit head = revWalk.next();
334 			// HEAD~1
335 			RevCommit beforeHead = revWalk.next();
336 
337 			// verify the commit message on the HEAD commit
338 			assertEquals(TARGET_COMMIT_MESSAGE, head.getFullMessage());
339 			// verify the commit just before HEAD
340 			assertEquals(SOURCE_COMMIT_MESSAGE, beforeHead.getFullMessage());
341 
342 			// verify file states
343 			assertFileContentsEqual(sourceFile, SOURCE_FILE_CONTENTS);
344 			assertFileContentsEqual(newFile, NEW_FILE_CONTENTS);
345 			// verify repository state
346 			assertEquals(RepositoryState.SAFE, target
347 				.getRepository().getRepositoryState());
348 		}
349 	}
350 
351 	@Override
352 	@Before
353 	public void setUp() throws Exception {
354 		super.setUp();
355 		dbTarget = createWorkRepository();
356 		source = new Git(db);
357 		target = new Git(dbTarget);
358 
359 		// put some file in the source repo
360 		sourceFile = new File(db.getWorkTree(), "SomeFile.txt");
361 		writeToFile(sourceFile, "Hello world");
362 		// and commit it
363 		source.add().addFilepattern("SomeFile.txt").call();
364 		source.commit().setMessage("Initial commit for source").call();
365 
366 		// configure the target repo to connect to the source via "origin"
367 		StoredConfig targetConfig = dbTarget.getConfig();
368 		targetConfig.setString("branch", "master", "remote", "origin");
369 		targetConfig
370 				.setString("branch", "master", "merge", "refs/heads/master");
371 		RemoteConfig config = new RemoteConfig(targetConfig, "origin");
372 
373 		config
374 				.addURI(new URIish(source.getRepository().getWorkTree()
375 						.getAbsolutePath()));
376 		config.addFetchRefSpec(new RefSpec(
377 				"+refs/heads/*:refs/remotes/origin/*"));
378 		config.update(targetConfig);
379 		targetConfig.save();
380 
381 		targetFile = new File(dbTarget.getWorkTree(), "SomeFile.txt");
382 		// make sure we have the same content
383 		target.pull().call();
384 		target.checkout().setStartPoint("refs/remotes/origin/master").setName(
385 				"master").call();
386 
387 		targetConfig
388 				.setString("branch", "master", "merge", "refs/heads/master");
389 		targetConfig.setBoolean("branch", "master", "rebase", true);
390 		targetConfig.save();
391 
392 		assertFileContentsEqual(targetFile, "Hello world");
393 	}
394 
395 	private static void writeToFile(File actFile, String string)
396 			throws IOException {
397 		FileOutputStream fos = null;
398 		try {
399 			fos = new FileOutputStream(actFile);
400 			fos.write(string.getBytes("UTF-8"));
401 			fos.close();
402 		} finally {
403 			if (fos != null)
404 				fos.close();
405 		}
406 	}
407 
408 	private static void assertFileContentsEqual(File actFile, String string)
409 			throws IOException {
410 		ByteArrayOutputStream bos = new ByteArrayOutputStream();
411 		FileInputStream fis = null;
412 		byte[] buffer = new byte[100];
413 		try {
414 			fis = new FileInputStream(actFile);
415 			int read = fis.read(buffer);
416 			while (read > 0) {
417 				bos.write(buffer, 0, read);
418 				read = fis.read(buffer);
419 			}
420 			String content = new String(bos.toByteArray(), "UTF-8");
421 			assertEquals(string, content);
422 		} finally {
423 			if (fis != null)
424 				fis.close();
425 		}
426 	}
427 }