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