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