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