View Javadoc
1   /*
2    * Copyright (C) 2012, IBM Corporation 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.pgm;
11  
12  import static org.junit.Assert.assertArrayEquals;
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.assertTrue;
17  import static org.junit.Assert.fail;
18  
19  import java.io.File;
20  import java.nio.file.Files;
21  import java.nio.file.Path;
22  import java.util.Arrays;
23  import java.util.List;
24  
25  import org.eclipse.jgit.api.Git;
26  import org.eclipse.jgit.api.errors.CheckoutConflictException;
27  import org.eclipse.jgit.diff.DiffEntry;
28  import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
29  import org.eclipse.jgit.lib.CLIRepositoryTestCase;
30  import org.eclipse.jgit.lib.FileMode;
31  import org.eclipse.jgit.lib.Ref;
32  import org.eclipse.jgit.revwalk.RevCommit;
33  import org.eclipse.jgit.treewalk.FileTreeIterator;
34  import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry;
35  import org.eclipse.jgit.treewalk.TreeWalk;
36  import org.eclipse.jgit.util.FS;
37  import org.eclipse.jgit.util.FileUtils;
38  import org.junit.Assume;
39  import org.junit.Test;
40  
41  public class CheckoutTest extends CLIRepositoryTestCase {
42  	/**
43  	 * Executes specified git command (with arguments), captures exception and
44  	 * returns its message as an array of lines. Throws an AssertionError if no
45  	 * exception is thrown.
46  	 *
47  	 * @param command
48  	 *            a valid git command line, e.g. "git branch -h"
49  	 * @return message contained within the exception
50  	 */
51  	private String[] executeExpectingException(String command) {
52  		try {
53  			execute(command);
54  			throw new AssertionError("Expected Die");
55  		} catch (Exception e) {
56  			return e.getMessage().split(System.lineSeparator());
57  		}
58  	}
59  
60  	@Test
61  	public void testCheckoutSelf() throws Exception {
62  		try (Git git = new Git(db)) {
63  			git.commit().setMessage("initial commit").call();
64  
65  			assertStringArrayEquals("Already on 'master'",
66  					execute("git checkout master"));
67  		}
68  	}
69  
70  	@Test
71  	public void testCheckoutBranch() throws Exception {
72  		try (Git git = new Git(db)) {
73  			git.commit().setMessage("initial commit").call();
74  			git.branchCreate().setName("side").call();
75  
76  			assertStringArrayEquals("Switched to branch 'side'",
77  					execute("git checkout side"));
78  		}
79  	}
80  
81  	@Test
82  	public void testCheckoutNewBranch() throws Exception {
83  		try (Git git = new Git(db)) {
84  			git.commit().setMessage("initial commit").call();
85  
86  			assertStringArrayEquals("Switched to a new branch 'side'",
87  					execute("git checkout -b side"));
88  		}
89  	}
90  
91  	@Test
92  	public void testCheckoutNonExistingBranch() throws Exception {
93  		assertStringArrayEquals(
94  				"error: pathspec 'side' did not match any file(s) known to git.",
95  				executeExpectingException("git checkout side"));
96  	}
97  
98  	@Test
99  	public void testCheckoutNewBranchThatAlreadyExists() throws Exception {
100 		try (Git git = new Git(db)) {
101 			git.commit().setMessage("initial commit").call();
102 
103 			assertStringArrayEquals(
104 					"fatal: A branch named 'master' already exists.",
105 				executeUnchecked("git checkout -b master"));
106 		}
107 	}
108 
109 	@Test
110 	public void testCheckoutNewBranchOnBranchToBeBorn() throws Exception {
111 		assertStringArrayEquals("fatal: You are on a branch yet to be born",
112 				executeUnchecked("git checkout -b side"));
113 	}
114 
115 	@Test
116 	public void testCheckoutUnresolvedHead() throws Exception {
117 		assertStringArrayEquals(
118 				"error: pathspec 'HEAD' did not match any file(s) known to git.",
119 				executeExpectingException("git checkout HEAD"));
120 	}
121 
122 	@Test
123 	public void testCheckoutHead() throws Exception {
124 		try (Git git = new Git(db)) {
125 			git.commit().setMessage("initial commit").call();
126 
127 			assertStringArrayEquals("", execute("git checkout HEAD"));
128 		}
129 	}
130 
131 	@Test
132 	public void testCheckoutExistingBranchWithConflict() throws Exception {
133 		try (Git git = new Git(db)) {
134 			writeTrashFile("a", "Hello world a");
135 			git.add().addFilepattern(".").call();
136 			git.commit().setMessage("commit file a").call();
137 			git.branchCreate().setName("branch_1").call();
138 			git.rm().addFilepattern("a").call();
139 			FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
140 			writeTrashFile("a/b", "Hello world b");
141 			git.add().addFilepattern("a/b").call();
142 			git.commit().setMessage("commit folder a").call();
143 			git.rm().addFilepattern("a").call();
144 			writeTrashFile("a", "New Hello world a");
145 			git.add().addFilepattern(".").call();
146 
147 			String[] execute = executeExpectingException(
148 					"git checkout branch_1");
149 			assertEquals(
150 					"error: Your local changes to the following files would be overwritten by checkout:",
151 					execute[0]);
152 			assertEquals("\ta", execute[1]);
153 		}
154 	}
155 
156 	/**
157 	 * Steps:
158 	 * <ol>
159 	 * <li>Add file 'a' and 'b'
160 	 * <li>Commit
161 	 * <li>Create branch '1'
162 	 * <li>modify file 'a'
163 	 * <li>Commit
164 	 * <li>Delete file 'a' in the working tree
165 	 * <li>Checkout branch '1'
166 	 * </ol>
167 	 * <p>
168 	 * The working tree should contain 'a' with FileMode.REGULAR_FILE after the
169 	 * checkout.
170 	 *
171 	 * @throws Exception
172 	 */
173 	@Test
174 	public void testCheckoutWithMissingWorkingTreeFile() throws Exception {
175 		try (Git git = new Git(db)) {
176 			File fileA = writeTrashFile("a", "Hello world a");
177 			writeTrashFile("b", "Hello world b");
178 			git.add().addFilepattern(".").call();
179 			git.commit().setMessage("add files a & b").call();
180 			Ref branch_1 = git.branchCreate().setName("branch_1").call();
181 			writeTrashFile("a", "b");
182 			git.add().addFilepattern("a").call();
183 			git.commit().setMessage("modify file a").call();
184 
185 			FileEntry entry = new FileTreeIterator.FileEntry(new File(
186 					db.getWorkTree(), "a"), db.getFS());
187 			assertEquals(FileMode.REGULAR_FILE, entry.getMode());
188 
189 			FileUtils.delete(fileA);
190 
191 			git.checkout().setName(branch_1.getName()).call();
192 
193 			entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
194 					db.getFS());
195 			assertEquals(FileMode.REGULAR_FILE, entry.getMode());
196 			assertEquals("Hello world a", read(fileA));
197 		}
198 	}
199 
200 	@Test
201 	public void testCheckoutOrphan() throws Exception {
202 		try (Git git = new Git(db)) {
203 			git.commit().setMessage("initial commit").call();
204 
205 			assertStringArrayEquals("Switched to a new branch 'new_branch'",
206 					execute("git checkout --orphan new_branch"));
207 			assertEquals("refs/heads/new_branch",
208 					db.exactRef("HEAD").getTarget().getName());
209 			RevCommit commit = git.commit().setMessage("orphan commit").call();
210 			assertEquals(0, commit.getParentCount());
211 		}
212 	}
213 
214 	/**
215 	 * Steps:
216 	 * <ol>
217 	 * <li>Add file 'b'
218 	 * <li>Commit
219 	 * <li>Create branch '1'
220 	 * <li>Add folder 'a'
221 	 * <li>Commit
222 	 * <li>Replace folder 'a' by file 'a' in the working tree
223 	 * <li>Checkout branch '1'
224 	 * </ol>
225 	 * <p>
226 	 * The checkout has to delete folder but the workingtree contains a dirty
227 	 * file at this path. The checkout should fail like in native git.
228 	 *
229 	 * @throws Exception
230 	 */
231 	@Test
232 	public void fileModeTestMissingThenFolderWithFileInWorkingTree()
233 			throws Exception {
234 		try (Git git = new Git(db)) {
235 			writeTrashFile("b", "Hello world b");
236 			git.add().addFilepattern(".").call();
237 			git.commit().setMessage("add file b").call();
238 			Ref branch_1 = git.branchCreate().setName("branch_1").call();
239 			File folderA = new File(db.getWorkTree(), "a");
240 			FileUtils.mkdirs(folderA);
241 			writeTrashFile("a/c", "Hello world c");
242 			git.add().addFilepattern(".").call();
243 			git.commit().setMessage("add folder a").call();
244 
245 			FileEntry entry = new FileTreeIterator.FileEntry(new File(
246 					db.getWorkTree(), "a"), db.getFS());
247 			assertEquals(FileMode.TREE, entry.getMode());
248 
249 			FileUtils.delete(folderA, FileUtils.RECURSIVE);
250 			writeTrashFile("a", "b");
251 
252 			entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
253 					db.getFS());
254 			assertEquals(FileMode.REGULAR_FILE, entry.getMode());
255 
256 			try {
257 				git.checkout().setName(branch_1.getName()).call();
258 				fail("Don't get the expected conflict");
259 			} catch (CheckoutConflictException e) {
260 				assertEquals("[a]", e.getConflictingPaths().toString());
261 				entry = new FileTreeIterator.FileEntry(
262 						new File(db.getWorkTree(), "a"), db.getFS());
263 				assertEquals(FileMode.REGULAR_FILE, entry.getMode());
264 			}
265 		}
266 	}
267 
268 	/**
269 	 * Steps:
270 	 * <ol>
271 	 * <li>Add file 'a'
272 	 * <li>Commit
273 	 * <li>Create branch '1'
274 	 * <li>Replace file 'a' by folder 'a'
275 	 * <li>Commit
276 	 * <li>Delete folder 'a' in the working tree
277 	 * <li>Checkout branch '1'
278 	 * </ol>
279 	 * <p>
280 	 * The working tree should contain 'a' with FileMode.REGULAR_FILE after the
281 	 * checkout.
282 	 *
283 	 * @throws Exception
284 	 */
285 	@Test
286 	public void fileModeTestFolderWithMissingInWorkingTree() throws Exception {
287 		try (Git git = new Git(db)) {
288 			writeTrashFile("b", "Hello world b");
289 			writeTrashFile("a", "b");
290 			git.add().addFilepattern(".").call();
291 			git.commit().setMessage("add file b & file a").call();
292 			Ref branch_1 = git.branchCreate().setName("branch_1").call();
293 			git.rm().addFilepattern("a").call();
294 			File folderA = new File(db.getWorkTree(), "a");
295 			FileUtils.mkdirs(folderA);
296 			writeTrashFile("a/c", "Hello world c");
297 			git.add().addFilepattern(".").call();
298 			git.commit().setMessage("add folder a").call();
299 
300 			FileEntry entry = new FileTreeIterator.FileEntry(new File(
301 					db.getWorkTree(), "a"), db.getFS());
302 			assertEquals(FileMode.TREE, entry.getMode());
303 
304 			FileUtils.delete(folderA, FileUtils.RECURSIVE);
305 
306 			git.checkout().setName(branch_1.getName()).call();
307 
308 			entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
309 					db.getFS());
310 			assertEquals(FileMode.REGULAR_FILE, entry.getMode());
311 		}
312 	}
313 
314 	/**
315 	 * Steps:
316 	 * <ol>
317 	 * <li>Add file 'a'
318 	 * <li>Commit
319 	 * <li>Create branch '1'
320 	 * <li>Delete file 'a'
321 	 * <li>Commit
322 	 * <li>Add folder 'a' in the working tree
323 	 * <li>Checkout branch '1'
324 	 * </ol>
325 	 * <p>
326 	 * The checkout command should raise an error. The conflicting paths are 'a'
327 	 * and 'a/c'.
328 	 *
329 	 * @throws Exception
330 	 */
331 	@Test
332 	public void fileModeTestMissingWithFolderInWorkingTree() throws Exception {
333 		try (Git git = new Git(db)) {
334 			writeTrashFile("b", "Hello world b");
335 			writeTrashFile("a", "b");
336 			git.add().addFilepattern(".").call();
337 			git.commit().setMessage("add file b & file a").call();
338 			Ref branch_1 = git.branchCreate().setName("branch_1").call();
339 			git.rm().addFilepattern("a").call();
340 			git.commit().setMessage("delete file a").call();
341 
342 			FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
343 			writeTrashFile("a/c", "Hello world c");
344 
345 			FileEntry entry = new FileTreeIterator.FileEntry(new File(
346 					db.getWorkTree(), "a"), db.getFS());
347 			assertEquals(FileMode.TREE, entry.getMode());
348 
349 			CheckoutConflictException exception = null;
350 			try {
351 				git.checkout().setName(branch_1.getName()).call();
352 			} catch (CheckoutConflictException e) {
353 				exception = e;
354 			}
355 			assertNotNull(exception);
356 			assertEquals(2, exception.getConflictingPaths().size());
357 			assertEquals("a", exception.getConflictingPaths().get(0));
358 			assertEquals("a/c", exception.getConflictingPaths().get(1));
359 		}
360 	}
361 
362 	/**
363 	 * Steps:
364 	 * <ol>
365 	 * <li>Add folder 'a'
366 	 * <li>Commit
367 	 * <li>Create branch '1'
368 	 * <li>Delete folder 'a'
369 	 * <li>Commit
370 	 * <li>Add file 'a' in the working tree
371 	 * <li>Checkout branch '1'
372 	 * </ol>
373 	 * <p>
374 	 * The checkout command should raise an error. The conflicting path is 'a'.
375 	 *
376 	 * @throws Exception
377 	 */
378 	@Test
379 	public void fileModeTestFolderThenMissingWithFileInWorkingTree()
380 			throws Exception {
381 		try (Git git = new Git(db)) {
382 			FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
383 			writeTrashFile("a/c", "Hello world c");
384 			writeTrashFile("b", "Hello world b");
385 			git.add().addFilepattern(".").call();
386 			RevCommit commit1 = git.commit().setMessage("add folder a & file b")
387 					.call();
388 			Ref branch_1 = git.branchCreate().setName("branch_1").call();
389 			git.rm().addFilepattern("a").call();
390 			RevCommit commit2 = git.commit().setMessage("delete folder a").call();
391 
392 			TreeWalk tw = new TreeWalk(db);
393 			tw.addTree(commit1.getTree());
394 			tw.addTree(commit2.getTree());
395 			List<DiffEntry> scan = DiffEntry.scan(tw);
396 			assertEquals(1, scan.size());
397 			assertEquals(FileMode.MISSING, scan.get(0).getNewMode());
398 			assertEquals(FileMode.TREE, scan.get(0).getOldMode());
399 
400 			writeTrashFile("a", "b");
401 
402 			FileEntry entry = new FileTreeIterator.FileEntry(new File(
403 					db.getWorkTree(), "a"), db.getFS());
404 			assertEquals(FileMode.REGULAR_FILE, entry.getMode());
405 
406 			CheckoutConflictException exception = null;
407 			try {
408 				git.checkout().setName(branch_1.getName()).call();
409 			} catch (CheckoutConflictException e) {
410 				exception = e;
411 			}
412 			assertNotNull(exception);
413 			assertEquals(1, exception.getConflictingPaths().size());
414 			assertEquals("a", exception.getConflictingPaths().get(0));
415 		}
416 	}
417 
418 	/**
419 	 * Steps:
420 	 * <ol>
421 	 * <li>Add folder 'a'
422 	 * <li>Commit
423 	 * <li>Create branch '1'
424 	 * <li>Replace folder 'a'by file 'a'
425 	 * <li>Commit
426 	 * <li>Delete file 'a' in the working tree
427 	 * <li>Checkout branch '1'
428 	 * </ol>
429 	 * <p>
430 	 * The working tree should contain 'a' with FileMode.TREE after the
431 	 * checkout.
432 	 *
433 	 * @throws Exception
434 	 */
435 	@Test
436 	public void fileModeTestFolderThenFileWithMissingInWorkingTree()
437 			throws Exception {
438 		try (Git git = new Git(db)) {
439 			FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
440 			writeTrashFile("a/c", "Hello world c");
441 			writeTrashFile("b", "Hello world b");
442 			git.add().addFilepattern(".").call();
443 			git.commit().setMessage("add folder a & file b").call();
444 			Ref branch_1 = git.branchCreate().setName("branch_1").call();
445 			git.rm().addFilepattern("a").call();
446 			File fileA = new File(db.getWorkTree(), "a");
447 			writeTrashFile("a", "b");
448 			git.add().addFilepattern("a").call();
449 			git.commit().setMessage("add file a").call();
450 
451 			FileEntry entry = new FileTreeIterator.FileEntry(new File(
452 					db.getWorkTree(), "a"), db.getFS());
453 			assertEquals(FileMode.REGULAR_FILE, entry.getMode());
454 
455 			FileUtils.delete(fileA);
456 
457 			git.checkout().setName(branch_1.getName()).call();
458 
459 			entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
460 					db.getFS());
461 			assertEquals(FileMode.TREE, entry.getMode());
462 		}
463 	}
464 
465 	/**
466 	 * Steps:
467 	 * <ol>
468 	 * <li>Add file 'a'
469 	 * <li>Commit
470 	 * <li>Create branch '1'
471 	 * <li>Modify file 'a'
472 	 * <li>Commit
473 	 * <li>Delete file 'a' and replace by folder 'a' in the working tree and
474 	 * index
475 	 * <li>Checkout branch '1'
476 	 * </ol>
477 	 * <p>
478 	 * The checkout command should raise an error. The conflicting path is 'a'.
479 	 *
480 	 * @throws Exception
481 	 */
482 	@Test
483 	public void fileModeTestFileThenFileWithFolderInIndex() throws Exception {
484 		try (Git git = new Git(db)) {
485 			writeTrashFile("a", "Hello world a");
486 			writeTrashFile("b", "Hello world b");
487 			git.add().addFilepattern(".").call();
488 			git.commit().setMessage("add files a & b").call();
489 			Ref branch_1 = git.branchCreate().setName("branch_1").call();
490 			writeTrashFile("a", "b");
491 			git.add().addFilepattern("a").call();
492 			git.commit().setMessage("add file a").call();
493 
494 			FileEntry entry = new FileTreeIterator.FileEntry(new File(
495 					db.getWorkTree(), "a"), db.getFS());
496 			assertEquals(FileMode.REGULAR_FILE, entry.getMode());
497 
498 			git.rm().addFilepattern("a").call();
499 			FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
500 			writeTrashFile("a/c", "Hello world c");
501 			git.add().addFilepattern(".").call();
502 
503 			entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
504 					db.getFS());
505 			assertEquals(FileMode.TREE, entry.getMode());
506 
507 			CheckoutConflictException exception = null;
508 			try {
509 				git.checkout().setName(branch_1.getName()).call();
510 			} catch (CheckoutConflictException e) {
511 				exception = e;
512 			}
513 			assertNotNull(exception);
514 			assertEquals(1, exception.getConflictingPaths().size());
515 			assertEquals("a", exception.getConflictingPaths().get(0));
516 		}
517 	}
518 
519 	/**
520 	 * Steps:
521 	 * <ol>
522 	 * <li>Add file 'a'
523 	 * <li>Commit
524 	 * <li>Create branch '1'
525 	 * <li>Modify file 'a'
526 	 * <li>Commit
527 	 * <li>Delete file 'a' and replace by folder 'a' in the working tree and
528 	 * index
529 	 * <li>Checkout branch '1'
530 	 * </ol>
531 	 * <p>
532 	 * The checkout command should raise an error. The conflicting paths are 'a'
533 	 * and 'a/c'.
534 	 *
535 	 * @throws Exception
536 	 */
537 	@Test
538 	public void fileModeTestFileWithFolderInIndex() throws Exception {
539 		try (Git git = new Git(db)) {
540 			writeTrashFile("b", "Hello world b");
541 			writeTrashFile("a", "b");
542 			git.add().addFilepattern(".").call();
543 			git.commit().setMessage("add file b & file a").call();
544 			Ref branch_1 = git.branchCreate().setName("branch_1").call();
545 			git.rm().addFilepattern("a").call();
546 			writeTrashFile("a", "Hello world a");
547 			git.add().addFilepattern("a").call();
548 			git.commit().setMessage("add file a").call();
549 
550 			FileEntry entry = new FileTreeIterator.FileEntry(new File(
551 					db.getWorkTree(), "a"), db.getFS());
552 			assertEquals(FileMode.REGULAR_FILE, entry.getMode());
553 
554 			git.rm().addFilepattern("a").call();
555 			FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
556 			writeTrashFile("a/c", "Hello world c");
557 			git.add().addFilepattern(".").call();
558 
559 			entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
560 					db.getFS());
561 			assertEquals(FileMode.TREE, entry.getMode());
562 
563 			CheckoutConflictException exception = null;
564 			try {
565 				git.checkout().setName(branch_1.getName()).call();
566 			} catch (CheckoutConflictException e) {
567 				exception = e;
568 			}
569 			assertNotNull(exception);
570 			assertEquals(1, exception.getConflictingPaths().size());
571 			assertEquals("a", exception.getConflictingPaths().get(0));
572 
573 			// TODO: ideally we'd like to get two paths from this exception
574 			// assertEquals(2, exception.getConflictingPaths().size());
575 			// assertEquals("a", exception.getConflictingPaths().get(0));
576 			// assertEquals("a/c", exception.getConflictingPaths().get(1));
577 		}
578 	}
579 
580 	@Test
581 	public void testCheckoutPath() throws Exception {
582 		try (Git git = new Git(db)) {
583 			writeTrashFile("a", "Hello world a");
584 			git.add().addFilepattern(".").call();
585 			git.commit().setMessage("commit file a").call();
586 			git.branchCreate().setName("branch_1").call();
587 			git.checkout().setName("branch_1").call();
588 			File b = writeTrashFile("b", "Hello world b");
589 			git.add().addFilepattern("b").call();
590 			git.commit().setMessage("commit file b").call();
591 			File a = writeTrashFile("a", "New Hello world a");
592 			git.add().addFilepattern(".").call();
593 			git.commit().setMessage("modified a").call();
594 			assertArrayEquals(new String[] { "" },
595 					execute("git checkout HEAD~2 -- a"));
596 			assertEquals("Hello world a", read(a));
597 			assertArrayEquals(new String[] { "* branch_1", "  master", "" },
598 					execute("git branch"));
599 			assertEquals("Hello world b", read(b));
600 		}
601 	}
602 
603 	@Test
604 	public void testCheckoutAllPaths() throws Exception {
605 		try (Git git = new Git(db)) {
606 			writeTrashFile("a", "Hello world a");
607 			git.add().addFilepattern(".").call();
608 			git.commit().setMessage("commit file a").call();
609 			git.branchCreate().setName("branch_1").call();
610 			git.checkout().setName("branch_1").call();
611 			File b = writeTrashFile("b", "Hello world b");
612 			git.add().addFilepattern("b").call();
613 			git.commit().setMessage("commit file b").call();
614 			File a = writeTrashFile("a", "New Hello world a");
615 			git.add().addFilepattern(".").call();
616 			git.commit().setMessage("modified a").call();
617 			assertArrayEquals(new String[] { "" },
618 					execute("git checkout HEAD~2 -- ."));
619 			assertEquals("Hello world a", read(a));
620 			assertArrayEquals(new String[] { "* branch_1", "  master", "" },
621 					execute("git branch"));
622 			assertEquals("Hello world b", read(b));
623 		}
624 	}
625 
626 	@Test
627 	public void testCheckoutSingleFile() throws Exception {
628 		try (Git git = new Git(db)) {
629 			File a = writeTrashFile("a", "file a");
630 			git.add().addFilepattern(".").call();
631 			git.commit().setMessage("commit file a").call();
632 			writeTrashFile("a", "b");
633 			assertEquals("b", read(a));
634 			assertEquals("[]", Arrays.toString(execute("git checkout -- a")));
635 			assertEquals("file a", read(a));
636 		}
637 	}
638 
639 	@Test
640 	public void testCheckoutLink() throws Exception {
641 		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
642 		try (Git git = new Git(db)) {
643 			Path path = writeLink("a", "link_a");
644 			assertTrue(Files.isSymbolicLink(path));
645 			git.add().addFilepattern(".").call();
646 			git.commit().setMessage("commit link a").call();
647 			deleteTrashFile("a");
648 			writeTrashFile("a", "Hello world a");
649 			assertFalse(Files.isSymbolicLink(path));
650 			assertEquals("[]", Arrays.toString(execute("git checkout -- a")));
651 			assertEquals("link_a", FileUtils.readSymLink(path.toFile()));
652 			assertTrue(Files.isSymbolicLink(path));
653 		}
654 	}
655 
656 	@Test
657 	public void testCheckoutForce_Bug530771() throws Exception {
658 		try (Git git = new Git(db)) {
659 			File f = writeTrashFile("a", "Hello world");
660 			git.add().addFilepattern("a").call();
661 			git.commit().setMessage("create a").call();
662 			writeTrashFile("a", "Goodbye world");
663 			assertEquals("[]",
664 					Arrays.toString(execute("git checkout -f HEAD")));
665 			assertEquals("Hello world", read(f));
666 			assertEquals("[a, mode:100644, content:Hello world]",
667 					indexState(db, LocalDiskRepositoryTestCase.CONTENT));
668 		}
669 	}
670 }