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