View Javadoc
1   /*
2    * Copyright (C) 2011, 2020 Kevin Sawicki <kevin@github.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 org.junit.Assert.assertEquals;
13  import static org.junit.Assert.assertFalse;
14  import static org.junit.Assert.assertTrue;
15  
16  import java.io.File;
17  import java.io.IOException;
18  import java.nio.file.Path;
19  
20  import org.eclipse.jgit.api.CheckoutCommand.Stage;
21  import org.eclipse.jgit.api.errors.JGitInternalException;
22  import org.eclipse.jgit.dircache.DirCache;
23  import org.eclipse.jgit.dircache.DirCacheEntry;
24  import org.eclipse.jgit.errors.NoWorkTreeException;
25  import org.eclipse.jgit.junit.RepositoryTestCase;
26  import org.eclipse.jgit.lib.ConfigConstants;
27  import org.eclipse.jgit.lib.Constants;
28  import org.eclipse.jgit.lib.ObjectReader;
29  import org.eclipse.jgit.lib.RepositoryState;
30  import org.eclipse.jgit.lib.StoredConfig;
31  import org.eclipse.jgit.revwalk.RevCommit;
32  import org.eclipse.jgit.util.FS;
33  import org.eclipse.jgit.util.FileUtils;
34  import org.junit.Assume;
35  import org.junit.Before;
36  import org.junit.Test;
37  
38  /**
39   * Unit tests of path-based uses of {@link CheckoutCommand}
40   */
41  public class PathCheckoutCommandTest extends RepositoryTestCase {
42  
43  	private static final String FILE1 = "f/Test.txt";
44  
45  	private static final String FILE2 = "Test2.txt";
46  
47  	private static final String FILE3 = "Test3.txt";
48  
49  	private static final String LINK = "link";
50  
51  	Git git;
52  
53  	RevCommit initialCommit;
54  
55  	RevCommit secondCommit;
56  
57  	@Override
58  	@Before
59  	public void setUp() throws Exception {
60  		super.setUp();
61  		git = new Git(db);
62  		writeTrashFile(FILE1, "1");
63  		writeTrashFile(FILE2, "a");
64  		git.add().addFilepattern(FILE1).addFilepattern(FILE2).call();
65  		initialCommit = git.commit().setMessage("Initial commit").call();
66  		writeTrashFile(FILE1, "2");
67  		writeTrashFile(FILE2, "b");
68  		git.add().addFilepattern(FILE1).addFilepattern(FILE2).call();
69  		secondCommit = git.commit().setMessage("Second commit").call();
70  		writeTrashFile(FILE1, "3");
71  		writeTrashFile(FILE2, "c");
72  		git.add().addFilepattern(FILE1).addFilepattern(FILE2).call();
73  		git.commit().setMessage("Third commit").call();
74  	}
75  
76  	@Test
77  	public void testUpdateSymLink() throws Exception {
78  		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
79  
80  		Path path = writeLink(LINK, FILE1);
81  		git.add().addFilepattern(LINK).call();
82  		git.commit().setMessage("Added link").call();
83  		assertEquals("3", read(path.toFile()));
84  
85  		writeLink(LINK, FILE2);
86  		assertEquals("c", read(path.toFile()));
87  
88  		CheckoutCommand co = git.checkout();
89  		co.addPath(LINK).call();
90  
91  		assertEquals("3", read(path.toFile()));
92  	}
93  
94  	@Test
95  	public void testUpdateBrokenSymLinkToDirectory() throws Exception {
96  		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
97  
98  		Path path = writeLink(LINK, "f");
99  		git.add().addFilepattern(LINK).call();
100 		git.commit().setMessage("Added link").call();
101 		assertEquals("f", FileUtils.readSymLink(path.toFile()));
102 		assertTrue(path.toFile().exists());
103 
104 		writeLink(LINK, "link_to_nowhere");
105 		assertFalse(path.toFile().exists());
106 		assertEquals("link_to_nowhere", FileUtils.readSymLink(path.toFile()));
107 
108 		CheckoutCommand co = git.checkout();
109 		co.addPath(LINK).call();
110 
111 		assertEquals("f", FileUtils.readSymLink(path.toFile()));
112 	}
113 
114 	@Test
115 	public void testUpdateBrokenSymLink() throws Exception {
116 		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
117 
118 		Path path = writeLink(LINK, FILE1);
119 		git.add().addFilepattern(LINK).call();
120 		git.commit().setMessage("Added link").call();
121 		assertEquals("3", read(path.toFile()));
122 		assertEquals(FILE1, FileUtils.readSymLink(path.toFile()));
123 
124 		writeLink(LINK, "link_to_nowhere");
125 		assertFalse(path.toFile().exists());
126 		assertEquals("link_to_nowhere", FileUtils.readSymLink(path.toFile()));
127 
128 		CheckoutCommand co = git.checkout();
129 		co.addPath(LINK).call();
130 
131 		assertEquals("3", read(path.toFile()));
132 	}
133 
134 	@Test
135 	public void testUpdateWorkingDirectory() throws Exception {
136 		CheckoutCommand co = git.checkout();
137 		File written = writeTrashFile(FILE1, "");
138 		assertEquals("", read(written));
139 		co.addPath(FILE1).call();
140 		assertEquals("3", read(written));
141 		assertEquals("c", read(new File(db.getWorkTree(), FILE2)));
142 	}
143 
144 	@Test
145 	public void testCheckoutFirst() throws Exception {
146 		CheckoutCommand co = git.checkout();
147 		File written = writeTrashFile(FILE1, "");
148 		co.setStartPoint(initialCommit).addPath(FILE1).call();
149 		assertEquals("1", read(written));
150 		assertEquals("c", read(new File(db.getWorkTree(), FILE2)));
151 	}
152 
153 	@Test
154 	public void testCheckoutSecond() throws Exception {
155 		CheckoutCommand co = git.checkout();
156 		File written = writeTrashFile(FILE1, "");
157 		co.setStartPoint("HEAD~1").addPath(FILE1).call();
158 		assertEquals("2", read(written));
159 		assertEquals("c", read(new File(db.getWorkTree(), FILE2)));
160 	}
161 
162 	@Test
163 	public void testCheckoutMultiple() throws Exception {
164 		CheckoutCommand co = git.checkout();
165 		File test = writeTrashFile(FILE1, "");
166 		File test2 = writeTrashFile(FILE2, "");
167 		co.setStartPoint("HEAD~2").addPath(FILE1).addPath(FILE2).call();
168 		assertEquals("1", read(test));
169 		assertEquals("a", read(test2));
170 	}
171 
172 	@Test
173 	public void testUpdateWorkingDirectoryFromIndex() throws Exception {
174 		CheckoutCommand co = git.checkout();
175 		File written = writeTrashFile(FILE1, "3a");
176 		git.add().addFilepattern(FILE1).call();
177 		written = writeTrashFile(FILE1, "");
178 		assertEquals("", read(written));
179 		co.addPath(FILE1).call();
180 		assertEquals("3a", read(written));
181 		assertEquals("c", read(new File(db.getWorkTree(), FILE2)));
182 	}
183 
184 	@Test
185 	public void testUpdateWorkingDirectoryFromHeadWithIndexChange()
186 			throws Exception {
187 		CheckoutCommand co = git.checkout();
188 		File written = writeTrashFile(FILE1, "3a");
189 		git.add().addFilepattern(FILE1).call();
190 		written = writeTrashFile(FILE1, "");
191 		assertEquals("", read(written));
192 		co.addPath(FILE1).setStartPoint("HEAD").call();
193 		assertEquals("3", read(written));
194 		assertEquals("c", read(new File(db.getWorkTree(), FILE2)));
195 	}
196 
197 	@Test
198 	public void testUpdateWorkingDirectoryFromIndex2() throws Exception {
199 		CheckoutCommand co = git.checkout();
200 		fsTick(git.getRepository().getIndexFile());
201 
202 		File written1 = writeTrashFile(FILE1, "3(modified)");
203 		File written2 = writeTrashFile(FILE2, "a(modified)");
204 		fsTick(written2);
205 
206 		// make sure that we get unsmudged entries for FILE1 and FILE2
207 		writeTrashFile(FILE3, "foo");
208 		git.add().addFilepattern(FILE3).call();
209 		fsTick(git.getRepository().getIndexFile());
210 
211 		git.add().addFilepattern(FILE1).addFilepattern(FILE2).call();
212 		fsTick(git.getRepository().getIndexFile());
213 
214 		writeTrashFile(FILE1, "3(modified again)");
215 		writeTrashFile(FILE2, "a(modified again)");
216 		fsTick(written2);
217 
218 		co.addPath(FILE1).setStartPoint(secondCommit).call();
219 
220 		assertEquals("2", read(written1));
221 		assertEquals("a(modified again)", read(written2));
222 
223 		validateIndex(git);
224 	}
225 
226 	public static void validateIndex(Git git) throws NoWorkTreeException,
227 			IOException {
228 		DirCache dc = git.getRepository().lockDirCache();
229 		try (ObjectReader r = git.getRepository().getObjectDatabase()
230 				.newReader()) {
231 			for (int i = 0; i < dc.getEntryCount(); ++i) {
232 				DirCacheEntry entry = dc.getEntry(i);
233 				if (entry.getLength() > 0)
234 					assertEquals(entry.getLength(), r.getObjectSize(
235 							entry.getObjectId(), ObjectReader.OBJ_ANY));
236 			}
237 		} finally {
238 			dc.unlock();
239 		}
240 	}
241 
242 	@Test
243 	public void testCheckoutMixedNewlines() throws Exception {
244 		// "git config core.autocrlf true"
245 		StoredConfig config = git.getRepository().getConfig();
246 		config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
247 				ConfigConstants.CONFIG_KEY_AUTOCRLF, true);
248 		config.save();
249 		// edit <FILE1>
250 		File written = writeTrashFile(FILE1, "4\r\n4");
251 		assertEquals("4\r\n4", read(written));
252 		// "git add <FILE1>"
253 		git.add().addFilepattern(FILE1).call();
254 		// "git commit -m 'CRLF'"
255 		git.commit().setMessage("CRLF").call();
256 		// edit <FILE1>
257 		written = writeTrashFile(FILE1, "4\n4");
258 		assertEquals("4\n4", read(written));
259 		// "git add <FILE1>"
260 		git.add().addFilepattern(FILE1).call();
261 		// "git checkout -- <FILE1>
262 		git.checkout().addPath(FILE1).call();
263 		// "git status" => clean
264 		Status status = git.status().call();
265 		assertEquals(0, status.getAdded().size());
266 		assertEquals(0, status.getChanged().size());
267 		assertEquals(0, status.getConflicting().size());
268 		assertEquals(0, status.getMissing().size());
269 		assertEquals(0, status.getModified().size());
270 		assertEquals(0, status.getRemoved().size());
271 		assertEquals(0, status.getUntracked().size());
272 	}
273 
274 	@Test
275 	public void testCheckoutRepository() throws Exception {
276 		CheckoutCommand co = git.checkout();
277 		File test = writeTrashFile(FILE1, "");
278 		File test2 = writeTrashFile(FILE2, "");
279 		co.setStartPoint("HEAD~2").setAllPaths(true).call();
280 		assertEquals("1", read(test));
281 		assertEquals("a", read(test2));
282 	}
283 
284 
285 	@Test(expected = JGitInternalException.class)
286 	public void testCheckoutOfConflictingFileShouldThrow()
287 			throws Exception {
288 		setupConflictingState();
289 
290 		git.checkout().addPath(FILE1).call();
291 	}
292 
293 	@Test
294 	public void testCheckoutOurs() throws Exception {
295 		setupConflictingState();
296 
297 		git.checkout().setStage(Stage.OURS).addPath(FILE1).call();
298 
299 		assertEquals("3", read(FILE1));
300 		assertStageOneToThree(FILE1);
301 	}
302 
303 	@Test
304 	public void testCheckoutTheirs() throws Exception {
305 		setupConflictingState();
306 
307 		git.checkout().setStage(Stage.THEIRS).addPath(FILE1).call();
308 
309 		assertEquals("Conflicting", read(FILE1));
310 		assertStageOneToThree(FILE1);
311 	}
312 
313 	@Test
314 	public void testCheckoutFileWithConflict() throws Exception {
315 		setupConflictingState();
316 		assertEquals('[' + FILE1 + ']',
317 				git.status().call().getConflicting().toString());
318 		git.checkout().setStartPoint(Constants.HEAD).addPath(FILE1).call();
319 		assertEquals("3", read(FILE1));
320 		assertTrue(git.status().call().isClean());
321 	}
322 
323 	@Test
324 	public void testCheckoutOursWhenNoBase() throws Exception {
325 		String file = "added.txt";
326 
327 		git.checkout().setCreateBranch(true).setName("side")
328 				.setStartPoint(initialCommit).call();
329 		writeTrashFile(file, "Added on side");
330 		git.add().addFilepattern(file).call();
331 		RevCommit side = git.commit().setMessage("Commit on side").call();
332 
333 		git.checkout().setName("master").call();
334 		writeTrashFile(file, "Added on master");
335 		git.add().addFilepattern(file).call();
336 		git.commit().setMessage("Commit on master").call();
337 
338 		git.merge().include(side).call();
339 		assertEquals(RepositoryState.MERGING, db.getRepositoryState());
340 
341 		DirCache cache = DirCache.read(db.getIndexFile(), db.getFS());
342 		assertEquals("Expected add/add file to not have base stage",
343 				DirCacheEntry.STAGE_2, cache.getEntry(file).getStage());
344 
345 		assertTrue(read(file).startsWith("<<<<<<< HEAD"));
346 
347 		git.checkout().setStage(Stage.OURS).addPath(file).call();
348 
349 		assertEquals("Added on master", read(file));
350 
351 		cache = DirCache.read(db.getIndexFile(), db.getFS());
352 		assertEquals("Expected conflict stages to still exist after checkout",
353 				DirCacheEntry.STAGE_2, cache.getEntry(file).getStage());
354 	}
355 
356 	@Test(expected = IllegalStateException.class)
357 	public void testStageNotPossibleWithBranch() throws Exception {
358 		git.checkout().setStage(Stage.OURS).setStartPoint("master").call();
359 	}
360 
361 	private void setupConflictingState() throws Exception {
362 		git.checkout().setCreateBranch(true).setName("conflict")
363 				.setStartPoint(initialCommit).call();
364 		writeTrashFile(FILE1, "Conflicting");
365 		RevCommit conflict = git.commit().setAll(true)
366 				.setMessage("Conflicting change").call();
367 
368 		git.checkout().setName("master").call();
369 
370 		git.merge().include(conflict).call();
371 		assertEquals(RepositoryState.MERGING, db.getRepositoryState());
372 		assertStageOneToThree(FILE1);
373 	}
374 
375 	private void assertStageOneToThree(String name) throws Exception {
376 		DirCache cache = DirCache.read(db.getIndexFile(), db.getFS());
377 		int i = cache.findEntry(name);
378 		DirCacheEntry stage1 = cache.getEntry(i);
379 		DirCacheEntry stage2 = cache.getEntry(i + 1);
380 		DirCacheEntry stage3 = cache.getEntry(i + 2);
381 
382 		assertEquals(DirCacheEntry.STAGE_1, stage1.getStage());
383 		assertEquals(DirCacheEntry.STAGE_2, stage2.getStage());
384 		assertEquals(DirCacheEntry.STAGE_3, stage3.getStage());
385 	}
386 }