View Javadoc
1   /*
2    * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com>
3    * Copyright (C) 2011, Matthias Sohn <matthias.sohn@sap.com>
4    * and other copyright owners as documented in the project's IP log.
5    *
6    * This program and the accompanying materials are made available
7    * under the terms of the Eclipse Distribution License v1.0 which
8    * accompanies this distribution, is reproduced below, and is
9    * available at http://www.eclipse.org/org/documents/edl-v10.php
10   *
11   * All rights reserved.
12   *
13   * Redistribution and use in source and binary forms, with or
14   * without modification, are permitted provided that the following
15   * conditions are met:
16   *
17   * - Redistributions of source code must retain the above copyright
18   *   notice, this list of conditions and the following disclaimer.
19   *
20   * - Redistributions in binary form must reproduce the above
21   *   copyright notice, this list of conditions and the following
22   *   disclaimer in the documentation and/or other materials provided
23   *   with the distribution.
24   *
25   * - Neither the name of the Eclipse Foundation, Inc. nor the
26   *   names of its contributors may be used to endorse or promote
27   *   products derived from this software without specific prior
28   *   written permission.
29   *
30   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
31   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
32   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
33   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
35   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
37   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
38   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
39   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43   */
44  package org.eclipse.jgit.api;
45  
46  import static org.eclipse.jgit.lib.Constants.MASTER;
47  import static org.eclipse.jgit.lib.Constants.R_HEADS;
48  import static org.hamcrest.CoreMatchers.is;
49  import static org.hamcrest.MatcherAssert.assertThat;
50  import static org.junit.Assert.assertEquals;
51  import static org.junit.Assert.assertFalse;
52  import static org.junit.Assert.assertNotNull;
53  import static org.junit.Assert.assertNull;
54  import static org.junit.Assert.assertSame;
55  import static org.junit.Assert.assertTrue;
56  import static org.junit.Assert.fail;
57  
58  import java.io.File;
59  import java.io.FileInputStream;
60  import java.io.IOException;
61  import java.net.MalformedURLException;
62  import java.net.URISyntaxException;
63  
64  import org.eclipse.jgit.api.CheckoutResult.Status;
65  import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
66  import org.eclipse.jgit.api.errors.GitAPIException;
67  import org.eclipse.jgit.api.errors.InvalidRefNameException;
68  import org.eclipse.jgit.api.errors.InvalidRemoteException;
69  import org.eclipse.jgit.api.errors.JGitInternalException;
70  import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
71  import org.eclipse.jgit.api.errors.RefNotFoundException;
72  import org.eclipse.jgit.api.errors.TransportException;
73  import org.eclipse.jgit.dircache.DirCache;
74  import org.eclipse.jgit.dircache.DirCacheEntry;
75  import org.eclipse.jgit.junit.JGitTestUtil;
76  import org.eclipse.jgit.junit.RepositoryTestCase;
77  import org.eclipse.jgit.lfs.BuiltinLFS;
78  import org.eclipse.jgit.lib.ConfigConstants;
79  import org.eclipse.jgit.lib.Constants;
80  import org.eclipse.jgit.lib.Ref;
81  import org.eclipse.jgit.lib.RefUpdate;
82  import org.eclipse.jgit.lib.Repository;
83  import org.eclipse.jgit.lib.Sets;
84  import org.eclipse.jgit.lib.StoredConfig;
85  import org.eclipse.jgit.revwalk.RevCommit;
86  import org.eclipse.jgit.storage.file.FileBasedConfig;
87  import org.eclipse.jgit.transport.RemoteConfig;
88  import org.eclipse.jgit.transport.URIish;
89  import org.eclipse.jgit.util.FileUtils;
90  import org.eclipse.jgit.util.SystemReader;
91  import org.junit.Before;
92  import org.junit.Test;
93  
94  public class CheckoutCommandTest extends RepositoryTestCase {
95  	private Git git;
96  
97  	RevCommit initialCommit;
98  
99  	RevCommit secondCommit;
100 
101 	@Override
102 	@Before
103 	public void setUp() throws Exception {
104 		BuiltinLFS.register();
105 		super.setUp();
106 		git = new Git(db);
107 		// commit something
108 		writeTrashFile("Test.txt", "Hello world");
109 		git.add().addFilepattern("Test.txt").call();
110 		initialCommit = git.commit().setMessage("Initial commit").call();
111 
112 		// create a master branch and switch to it
113 		git.branchCreate().setName("test").call();
114 		RefUpdate rup = db.updateRef(Constants.HEAD);
115 		rup.link("refs/heads/test");
116 
117 		// commit something on the test branch
118 		writeTrashFile("Test.txt", "Some change");
119 		git.add().addFilepattern("Test.txt").call();
120 		secondCommit = git.commit().setMessage("Second commit").call();
121 	}
122 
123 	@Test
124 	public void testSimpleCheckout() throws Exception {
125 		git.checkout().setName("test").call();
126 	}
127 
128 	@Test
129 	public void testCheckout() throws Exception {
130 		git.checkout().setName("test").call();
131 		assertEquals("[Test.txt, mode:100644, content:Some change]",
132 				indexState(CONTENT));
133 		Ref result = git.checkout().setName("master").call();
134 		assertEquals("[Test.txt, mode:100644, content:Hello world]",
135 				indexState(CONTENT));
136 		assertEquals("refs/heads/master", result.getName());
137 		assertEquals("refs/heads/master", git.getRepository().getFullBranch());
138 	}
139 
140 	@Test
141 	public void testCreateBranchOnCheckout() throws Exception {
142 		git.checkout().setCreateBranch(true).setName("test2").call();
143 		assertNotNull(db.exactRef("refs/heads/test2"));
144 	}
145 
146 	@Test
147 	public void testCheckoutToNonExistingBranch() throws GitAPIException {
148 		try {
149 			git.checkout().setName("badbranch").call();
150 			fail("Should have failed");
151 		} catch (RefNotFoundException e) {
152 			// except to hit here
153 		}
154 	}
155 
156 	@Test
157 	public void testCheckoutWithConflict() {
158 		CheckoutCommand co = git.checkout();
159 		try {
160 			writeTrashFile("Test.txt", "Another change");
161 			assertEquals(Status.NOT_TRIED, co.getResult().getStatus());
162 			co.setName("master").call();
163 			fail("Should have failed");
164 		} catch (Exception e) {
165 			assertEquals(Status.CONFLICTS, co.getResult().getStatus());
166 			assertTrue(co.getResult().getConflictList().contains("Test.txt"));
167 		}
168 	}
169 
170 	@Test
171 	public void testCheckoutWithNonDeletedFiles() throws Exception {
172 		File testFile = writeTrashFile("temp", "");
173 		try (FileInputStream fis = new FileInputStream(testFile)) {
174 			FileUtils.delete(testFile);
175 			return;
176 		} catch (IOException e) {
177 			// the test makes only sense if deletion of
178 			// a file with open stream fails
179 		}
180 		FileUtils.delete(testFile);
181 		CheckoutCommand co = git.checkout();
182 		// delete Test.txt in branch test
183 		testFile = new File(db.getWorkTree(), "Test.txt");
184 		assertTrue(testFile.exists());
185 		FileUtils.delete(testFile);
186 		assertFalse(testFile.exists());
187 		git.add().addFilepattern("Test.txt");
188 		git.commit().setMessage("Delete Test.txt").setAll(true).call();
189 		git.checkout().setName("master").call();
190 		assertTrue(testFile.exists());
191 		// lock the file so it can't be deleted (in Windows, that is)
192 		try (FileInputStream fis = new FileInputStream(testFile)) {
193 			assertEquals(Status.NOT_TRIED, co.getResult().getStatus());
194 			co.setName("test").call();
195 			assertTrue(testFile.exists());
196 			assertEquals(Status.NONDELETED, co.getResult().getStatus());
197 			assertTrue(co.getResult().getUndeletedList().contains("Test.txt"));
198 		}
199 	}
200 
201 	@Test
202 	public void testCheckoutCommit() throws Exception {
203 		Ref result = git.checkout().setName(initialCommit.name()).call();
204 		assertEquals("[Test.txt, mode:100644, content:Hello world]",
205 				indexState(CONTENT));
206 		assertNull(result);
207 		assertEquals(initialCommit.name(), git.getRepository().getFullBranch());
208 	}
209 
210 	@Test
211 	public void testCheckoutLightweightTag() throws Exception {
212 		git.tag().setAnnotated(false).setName("test-tag")
213 				.setObjectId(initialCommit).call();
214 		Ref result = git.checkout().setName("test-tag").call();
215 
216 		assertNull(result);
217 		assertEquals(initialCommit.getId(), db.resolve(Constants.HEAD));
218 		assertHeadDetached();
219 	}
220 
221 	@Test
222 	public void testCheckoutAnnotatedTag() throws Exception {
223 		git.tag().setAnnotated(true).setName("test-tag")
224 				.setObjectId(initialCommit).call();
225 		Ref result = git.checkout().setName("test-tag").call();
226 
227 		assertNull(result);
228 		assertEquals(initialCommit.getId(), db.resolve(Constants.HEAD));
229 		assertHeadDetached();
230 	}
231 
232 	@Test
233 	public void testCheckoutRemoteTrackingWithUpstream() throws Exception {
234 		Repository db2 = createRepositoryWithRemote();
235 
236 		Git.wrap(db2).checkout().setCreateBranch(true).setName("test")
237 				.setStartPoint("origin/test")
238 				.setUpstreamMode(SetupUpstreamMode.TRACK).call();
239 
240 		assertEquals("refs/heads/test",
241 				db2.exactRef(Constants.HEAD).getTarget().getName());
242 		StoredConfig config = db2.getConfig();
243 		assertEquals("origin", config.getString(
244 				ConfigConstants.CONFIG_BRANCH_SECTION, "test",
245 				ConfigConstants.CONFIG_KEY_REMOTE));
246 		assertEquals("refs/heads/test", config.getString(
247 				ConfigConstants.CONFIG_BRANCH_SECTION, "test",
248 				ConfigConstants.CONFIG_KEY_MERGE));
249 	}
250 
251 	@Test
252 	public void testCheckoutRemoteTrackingWithoutLocalBranch() throws Exception {
253 		Repository db2 = createRepositoryWithRemote();
254 
255 		// checkout remote tracking branch in second repository
256 		// (no local branches exist yet in second repository)
257 		Git.wrap(db2).checkout().setName("remotes/origin/test").call();
258 		assertEquals("[Test.txt, mode:100644, content:Some change]",
259 				indexState(db2, CONTENT));
260 	}
261 
262 
263 
264 	@Test
265 	public void testCheckoutOfFileWithInexistentParentDir() throws Exception {
266 		File a = writeTrashFile("dir/a.txt", "A");
267 		writeTrashFile("dir/b.txt", "A");
268 		git.add().addFilepattern("dir/a.txt").addFilepattern("dir/b.txt")
269 				.call();
270 		git.commit().setMessage("Added dir").call();
271 
272 		File dir = new File(db.getWorkTree(), "dir");
273 		FileUtils.delete(dir, FileUtils.RECURSIVE);
274 
275 		git.checkout().addPath("dir/a.txt").call();
276 		assertTrue(a.exists());
277 	}
278 
279 	@Test
280 	public void testCheckoutOfDirectoryShouldBeRecursive() throws Exception {
281 		File a = writeTrashFile("dir/a.txt", "A");
282 		File b = writeTrashFile("dir/sub/b.txt", "B");
283 		git.add().addFilepattern("dir").call();
284 		git.commit().setMessage("Added dir").call();
285 
286 		write(a, "modified");
287 		write(b, "modified");
288 		git.checkout().addPath("dir").call();
289 
290 		assertThat(read(a), is("A"));
291 		assertThat(read(b), is("B"));
292 	}
293 
294 	@Test
295 	public void testCheckoutAllPaths() throws Exception {
296 		File a = writeTrashFile("dir/a.txt", "A");
297 		File b = writeTrashFile("dir/sub/b.txt", "B");
298 		git.add().addFilepattern("dir").call();
299 		git.commit().setMessage("Added dir").call();
300 
301 		write(a, "modified");
302 		write(b, "modified");
303 		git.checkout().setAllPaths(true).call();
304 
305 		assertThat(read(a), is("A"));
306 		assertThat(read(b), is("B"));
307 	}
308 
309 	@Test
310 	public void testCheckoutWithStartPoint() throws Exception {
311 		File a = writeTrashFile("a.txt", "A");
312 		git.add().addFilepattern("a.txt").call();
313 		RevCommit first = git.commit().setMessage("Added a").call();
314 
315 		write(a, "other");
316 		git.commit().setAll(true).setMessage("Other").call();
317 
318 		git.checkout().setCreateBranch(true).setName("a")
319 				.setStartPoint(first.getId().getName()).call();
320 
321 		assertThat(read(a), is("A"));
322 	}
323 
324 	@Test
325 	public void testCheckoutWithStartPointOnlyCertainFiles() throws Exception {
326 		File a = writeTrashFile("a.txt", "A");
327 		File b = writeTrashFile("b.txt", "B");
328 		git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
329 		RevCommit first = git.commit().setMessage("First").call();
330 
331 		write(a, "other");
332 		write(b, "other");
333 		git.commit().setAll(true).setMessage("Other").call();
334 
335 		git.checkout().setCreateBranch(true).setName("a")
336 				.setStartPoint(first.getId().getName()).addPath("a.txt").call();
337 
338 		assertThat(read(a), is("A"));
339 		assertThat(read(b), is("other"));
340 	}
341 
342 	@Test
343 	public void testDetachedHeadOnCheckout() throws JGitInternalException,
344 			IOException, GitAPIException {
345 		CheckoutCommand co = git.checkout();
346 		co.setName("master").call();
347 
348 		String commitId = db.exactRef(R_HEADS + MASTER).getObjectId().name();
349 		co = git.checkout();
350 		co.setName(commitId).call();
351 
352 		assertHeadDetached();
353 	}
354 
355 	@Test
356 	public void testUpdateSmudgedEntries() throws Exception {
357 		git.branchCreate().setName("test2").call();
358 		RefUpdate rup = db.updateRef(Constants.HEAD);
359 		rup.link("refs/heads/test2");
360 
361 		File file = new File(db.getWorkTree(), "Test.txt");
362 		long size = file.length();
363 		long mTime = file.lastModified() - 5000L;
364 		assertTrue(file.setLastModified(mTime));
365 
366 		DirCache cache = DirCache.lock(db.getIndexFile(), db.getFS());
367 		DirCacheEntry entry = cache.getEntry("Test.txt");
368 		assertNotNull(entry);
369 		entry.setLength(0);
370 		entry.setLastModified(0);
371 		cache.write();
372 		assertTrue(cache.commit());
373 
374 		cache = DirCache.read(db.getIndexFile(), db.getFS());
375 		entry = cache.getEntry("Test.txt");
376 		assertNotNull(entry);
377 		assertEquals(0, entry.getLength());
378 		assertEquals(0, entry.getLastModified());
379 
380 		db.getIndexFile().setLastModified(
381 				db.getIndexFile().lastModified() - 5000);
382 
383 		assertNotNull(git.checkout().setName("test").call());
384 
385 		cache = DirCache.read(db.getIndexFile(), db.getFS());
386 		entry = cache.getEntry("Test.txt");
387 		assertNotNull(entry);
388 		assertEquals(size, entry.getLength());
389 		assertEquals(mTime, entry.getLastModified());
390 	}
391 
392 	@Test
393 	public void testCheckoutOrphanBranch() throws Exception {
394 		CheckoutCommand co = newOrphanBranchCommand();
395 		assertCheckoutRef(co.call());
396 
397 		File HEAD = new File(trash, ".git/HEAD");
398 		String headRef = read(HEAD);
399 		assertEquals("ref: refs/heads/orphanbranch\n", headRef);
400 		assertEquals(2, trash.list().length);
401 
402 		File heads = new File(trash, ".git/refs/heads");
403 		assertEquals(2, heads.listFiles().length);
404 
405 		this.assertNoHead();
406 		this.assertRepositoryCondition(1);
407 		assertEquals(CheckoutResult.NOT_TRIED_RESULT, co.getResult());
408 	}
409 
410 	private Repository createRepositoryWithRemote() throws IOException,
411 			URISyntaxException, MalformedURLException, GitAPIException,
412 			InvalidRemoteException, TransportException {
413 		// create second repository
414 		Repository db2 = createWorkRepository();
415 		try (Git git2 = new Git(db2)) {
416 			// setup the second repository to fetch from the first repository
417 			final StoredConfig config = db2.getConfig();
418 			RemoteConfig remoteConfig = new RemoteConfig(config, "origin");
419 			URIish uri = new URIish(db.getDirectory().toURI().toURL());
420 			remoteConfig.addURI(uri);
421 			remoteConfig.update(config);
422 			config.save();
423 
424 			// fetch from first repository
425 			git2.fetch().setRemote("origin")
426 					.setRefSpecs("+refs/heads/*:refs/remotes/origin/*").call();
427 			return db2;
428 		}
429 	}
430 
431 	private CheckoutCommand newOrphanBranchCommand() {
432 		return git.checkout().setOrphan(true)
433 				.setName("orphanbranch");
434 	}
435 
436 	private static void assertCheckoutRef(Ref ref) {
437 		assertNotNull(ref);
438 		assertEquals("refs/heads/orphanbranch", ref.getTarget().getName());
439 	}
440 
441 	private void assertNoHead() throws IOException {
442 		assertNull(db.resolve("HEAD"));
443 	}
444 
445 	private void assertHeadDetached() throws IOException {
446 		Ref head = db.exactRef(Constants.HEAD);
447 		assertFalse(head.isSymbolic());
448 		assertSame(head, head.getTarget());
449 	}
450 
451 	private void assertRepositoryCondition(int files) throws GitAPIException {
452 		org.eclipse.jgit.api.Status status = this.git.status().call();
453 		assertFalse(status.isClean());
454 		assertEquals(files, status.getAdded().size());
455 	}
456 
457 	@Test
458 	public void testCreateOrphanBranchWithStartCommit() throws Exception {
459 		CheckoutCommand co = newOrphanBranchCommand();
460 		Ref ref = co.setStartPoint(initialCommit).call();
461 		assertCheckoutRef(ref);
462 		assertEquals(2, trash.list().length);
463 		this.assertNoHead();
464 		this.assertRepositoryCondition(1);
465 	}
466 
467 	@Test
468 	public void testCreateOrphanBranchWithStartPoint() throws Exception {
469 		CheckoutCommand co = newOrphanBranchCommand();
470 		Ref ref = co.setStartPoint("HEAD^").call();
471 		assertCheckoutRef(ref);
472 
473 		assertEquals(2, trash.list().length);
474 		this.assertNoHead();
475 		this.assertRepositoryCondition(1);
476 	}
477 
478 	@Test
479 	public void testInvalidRefName() throws Exception {
480 		try {
481 			git.checkout().setOrphan(true).setName("../invalidname").call();
482 			fail("Should have failed");
483 		} catch (InvalidRefNameException e) {
484 			// except to hit here
485 		}
486 	}
487 
488 	@Test
489 	public void testNullRefName() throws Exception {
490 		try {
491 			git.checkout().setOrphan(true).setName(null).call();
492 			fail("Should have failed");
493 		} catch (InvalidRefNameException e) {
494 			// except to hit here
495 		}
496 	}
497 
498 	@Test
499 	public void testAlreadyExists() throws Exception {
500 		this.git.checkout().setCreateBranch(true).setName("orphanbranch")
501 				.call();
502 		this.git.checkout().setName("master").call();
503 
504 		try {
505 			newOrphanBranchCommand().call();
506 			fail("Should have failed");
507 		} catch (RefAlreadyExistsException e) {
508 			// except to hit here
509 		}
510 	}
511 
512 	// TODO: write a faster test which depends less on characteristics of
513 	// underlying filesystem/OS.
514 	@Test
515 	public void testCheckoutAutoCrlfTrue() throws Exception {
516 		int nrOfAutoCrlfTestFiles = 200;
517 
518 		FileBasedConfig c = db.getConfig();
519 		c.setString("core", null, "autocrlf", "true");
520 		c.save();
521 
522 		AddCommand add = git.add();
523 		for (int i = 100; i < 100 + nrOfAutoCrlfTestFiles; i++) {
524 			writeTrashFile("Test_" + i + ".txt", "Hello " + i
525 					+ " world\nX\nYU\nJK\n");
526 			add.addFilepattern("Test_" + i + ".txt");
527 		}
528 		fsTick(null);
529 		add.call();
530 		RevCommit c1 = git.commit().setMessage("add some lines").call();
531 
532 		add = git.add();
533 		for (int i = 100; i < 100 + nrOfAutoCrlfTestFiles; i++) {
534 			writeTrashFile("Test_" + i + ".txt", "Hello " + i
535 					+ " world\nX\nY\n");
536 			add.addFilepattern("Test_" + i + ".txt");
537 		}
538 		fsTick(null);
539 		add.call();
540 		git.commit().setMessage("add more").call();
541 
542 		git.checkout().setName(c1.getName()).call();
543 
544 		boolean foundUnsmudged = false;
545 		DirCache dc = db.readDirCache();
546 		for (int i = 100; i < 100 + nrOfAutoCrlfTestFiles; i++) {
547 			DirCacheEntry entry = dc.getEntry(
548 					"Test_" + i + ".txt");
549 			if (!entry.isSmudged()) {
550 				foundUnsmudged = true;
551 				assertEquals("unexpected file length in git index", 28,
552 						entry.getLength());
553 			}
554 		}
555 		org.junit.Assume.assumeTrue(foundUnsmudged);
556 	}
557 
558 	@Test
559 	public void testSmudgeFilter_modifyExisting() throws IOException, GitAPIException {
560 		File script = writeTempFile("sed s/o/e/g");
561 		StoredConfig config = git.getRepository().getConfig();
562 		config.setString("filter", "lfs", "smudge",
563 				"sh " + slashify(script.getPath()));
564 		config.save();
565 
566 		writeTrashFile(".gitattributes", "*.txt filter=lfs");
567 		git.add().addFilepattern(".gitattributes").call();
568 		git.commit().setMessage("add filter").call();
569 
570 		writeTrashFile("src/a.tmp", "x");
571 		// Caution: we need a trailing '\n' since sed on mac always appends
572 		// linefeeds if missing
573 		writeTrashFile("src/a.txt", "x\n");
574 		git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt")
575 				.call();
576 		RevCommit content1 = git.commit().setMessage("add content").call();
577 
578 		writeTrashFile("src/a.tmp", "foo");
579 		writeTrashFile("src/a.txt", "foo\n");
580 		git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt")
581 				.call();
582 		RevCommit content2 = git.commit().setMessage("changed content").call();
583 
584 		git.checkout().setName(content1.getName()).call();
585 		git.checkout().setName(content2.getName()).call();
586 
587 		assertEquals(
588 				"[.gitattributes, mode:100644, content:*.txt filter=lfs][Test.txt, mode:100644, content:Some change][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:foo\n]",
589 				indexState(CONTENT));
590 		assertEquals(Sets.of("src/a.txt"), git.status().call().getModified());
591 		assertEquals("foo", read("src/a.tmp"));
592 		assertEquals("fee\n", read("src/a.txt"));
593 	}
594 
595 	@Test
596 	public void testSmudgeFilter_createNew()
597 			throws IOException, GitAPIException {
598 		File script = writeTempFile("sed s/o/e/g");
599 		StoredConfig config = git.getRepository().getConfig();
600 		config.setString("filter", "lfs", "smudge",
601 				"sh " + slashify(script.getPath()));
602 		config.save();
603 
604 		writeTrashFile("foo", "foo");
605 		git.add().addFilepattern("foo").call();
606 		RevCommit initial = git.commit().setMessage("initial").call();
607 
608 		writeTrashFile(".gitattributes", "*.txt filter=lfs");
609 		git.add().addFilepattern(".gitattributes").call();
610 		git.commit().setMessage("add filter").call();
611 
612 		writeTrashFile("src/a.tmp", "foo");
613 		// Caution: we need a trailing '\n' since sed on mac always appends
614 		// linefeeds if missing
615 		writeTrashFile("src/a.txt", "foo\n");
616 		git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt")
617 				.call();
618 		RevCommit content = git.commit().setMessage("added content").call();
619 
620 		git.checkout().setName(initial.getName()).call();
621 		git.checkout().setName(content.getName()).call();
622 
623 		assertEquals(
624 				"[.gitattributes, mode:100644, content:*.txt filter=lfs][Test.txt, mode:100644, content:Some change][foo, mode:100644, content:foo][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:foo\n]",
625 				indexState(CONTENT));
626 		assertEquals("foo", read("src/a.tmp"));
627 		assertEquals("fee\n", read("src/a.txt"));
628 	}
629 
630 	@Test
631 	public void testSmudgeFilter_deleteFileAndRestoreFromCommit()
632 			throws IOException, GitAPIException {
633 		File script = writeTempFile("sed s/o/e/g");
634 		StoredConfig config = git.getRepository().getConfig();
635 		config.setString("filter", "lfs", "smudge",
636 				"sh " + slashify(script.getPath()));
637 		config.save();
638 
639 		writeTrashFile("foo", "foo");
640 		git.add().addFilepattern("foo").call();
641 		git.commit().setMessage("initial").call();
642 
643 		writeTrashFile(".gitattributes", "*.txt filter=lfs");
644 		git.add().addFilepattern(".gitattributes").call();
645 		git.commit().setMessage("add filter").call();
646 
647 		writeTrashFile("src/a.tmp", "foo");
648 		// Caution: we need a trailing '\n' since sed on mac always appends
649 		// linefeeds if missing
650 		writeTrashFile("src/a.txt", "foo\n");
651 		git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt")
652 				.call();
653 		RevCommit content = git.commit().setMessage("added content").call();
654 
655 		deleteTrashFile("src/a.txt");
656 		git.checkout().setStartPoint(content.getName()).addPath("src/a.txt")
657 				.call();
658 
659 		assertEquals(
660 				"[.gitattributes, mode:100644, content:*.txt filter=lfs][Test.txt, mode:100644, content:Some change][foo, mode:100644, content:foo][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:foo\n]",
661 				indexState(CONTENT));
662 		assertEquals("foo", read("src/a.tmp"));
663 		assertEquals("fee\n", read("src/a.txt"));
664 	}
665 
666 	@Test
667 	public void testSmudgeFilter_deleteFileAndRestoreFromIndex()
668 			throws IOException, GitAPIException {
669 		File script = writeTempFile("sed s/o/e/g");
670 		StoredConfig config = git.getRepository().getConfig();
671 		config.setString("filter", "lfs", "smudge",
672 				"sh " + slashify(script.getPath()));
673 		config.save();
674 
675 		writeTrashFile("foo", "foo");
676 		git.add().addFilepattern("foo").call();
677 		git.commit().setMessage("initial").call();
678 
679 		writeTrashFile(".gitattributes", "*.txt filter=lfs");
680 		git.add().addFilepattern(".gitattributes").call();
681 		git.commit().setMessage("add filter").call();
682 
683 		writeTrashFile("src/a.tmp", "foo");
684 		// Caution: we need a trailing '\n' since sed on mac always appends
685 		// linefeeds if missing
686 		writeTrashFile("src/a.txt", "foo\n");
687 		git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt")
688 				.call();
689 		git.commit().setMessage("added content").call();
690 
691 		deleteTrashFile("src/a.txt");
692 		git.checkout().addPath("src/a.txt").call();
693 
694 		assertEquals(
695 				"[.gitattributes, mode:100644, content:*.txt filter=lfs][Test.txt, mode:100644, content:Some change][foo, mode:100644, content:foo][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:foo\n]",
696 				indexState(CONTENT));
697 		assertEquals("foo", read("src/a.tmp"));
698 		assertEquals("fee\n", read("src/a.txt"));
699 	}
700 
701 	@Test
702 	public void testSmudgeFilter_deleteFileAndCreateBranchAndRestoreFromCommit()
703 			throws IOException, GitAPIException {
704 		File script = writeTempFile("sed s/o/e/g");
705 		StoredConfig config = git.getRepository().getConfig();
706 		config.setString("filter", "lfs", "smudge",
707 				"sh " + slashify(script.getPath()));
708 		config.save();
709 
710 		writeTrashFile("foo", "foo");
711 		git.add().addFilepattern("foo").call();
712 		git.commit().setMessage("initial").call();
713 
714 		writeTrashFile(".gitattributes", "*.txt filter=lfs");
715 		git.add().addFilepattern(".gitattributes").call();
716 		git.commit().setMessage("add filter").call();
717 
718 		writeTrashFile("src/a.tmp", "foo");
719 		// Caution: we need a trailing '\n' since sed on mac always appends
720 		// linefeeds if missing
721 		writeTrashFile("src/a.txt", "foo\n");
722 		git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt")
723 				.call();
724 		RevCommit content = git.commit().setMessage("added content").call();
725 
726 		deleteTrashFile("src/a.txt");
727 		git.checkout().setName("newBranch").setCreateBranch(true)
728 				.setStartPoint(content).addPath("src/a.txt").call();
729 
730 		assertEquals(
731 				"[.gitattributes, mode:100644, content:*.txt filter=lfs][Test.txt, mode:100644, content:Some change][foo, mode:100644, content:foo][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:foo\n]",
732 				indexState(CONTENT));
733 		assertEquals("foo", read("src/a.tmp"));
734 		assertEquals("fee\n", read("src/a.txt"));
735 	}
736 
737 	@Test
738 	public void testSmudgeAndClean() throws Exception {
739 		File clean_filter = writeTempFile("sed s/V1/@version/g");
740 		File smudge_filter = writeTempFile("sed s/@version/V1/g");
741 
742 		try (Git git2 = new Git(db)) {
743 			StoredConfig config = git.getRepository().getConfig();
744 			config.setString("filter", "lfs", "smudge",
745 					"sh " + slashify(smudge_filter.getPath()));
746 			config.setString("filter", "lfs", "clean",
747 					"sh " + slashify(clean_filter.getPath()));
748 			config.setBoolean("filter", "lfs", "useJGitBuiltin", true);
749 			config.save();
750 			writeTrashFile(".gitattributes", "filterTest.txt filter=lfs");
751 			git2.add().addFilepattern(".gitattributes").call();
752 			git2.commit().setMessage("add attributes").call();
753 
754 			fsTick(writeTrashFile("filterTest.txt", "hello world, V1\n"));
755 			git2.add().addFilepattern("filterTest.txt").call();
756 			RevCommit one = git2.commit().setMessage("add filterText.txt").call();
757 			assertEquals(
758 					"[.gitattributes, mode:100644, content:filterTest.txt filter=lfs][Test.txt, mode:100644, content:Some change][filterTest.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:7bd5d32e5c494354aa4c2473a1306d0ce7b52cc3bffeb342c03cd517ef8cf8da\nsize 16\n]",
759 					indexState(CONTENT));
760 
761 			fsTick(writeTrashFile("filterTest.txt", "bon giorno world, V1\n"));
762 			git2.add().addFilepattern("filterTest.txt").call();
763 			RevCommit two = git2.commit().setMessage("modified filterTest.txt").call();
764 
765 			assertTrue(git2.status().call().isClean());
766 			assertEquals(
767 					"[.gitattributes, mode:100644, content:filterTest.txt filter=lfs][Test.txt, mode:100644, content:Some change][filterTest.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:087148cccf53b0049c56475c1595113c9da4b638997c3489af8ac7108d51ef13\nsize 21\n]",
768 					indexState(CONTENT));
769 
770 			git2.checkout().setName(one.getName()).call();
771 			assertTrue(git2.status().call().isClean());
772 			assertEquals(
773 					"[.gitattributes, mode:100644, content:filterTest.txt filter=lfs][Test.txt, mode:100644, content:Some change][filterTest.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:7bd5d32e5c494354aa4c2473a1306d0ce7b52cc3bffeb342c03cd517ef8cf8da\nsize 16\n]",
774 					indexState(CONTENT));
775 			assertEquals("hello world, V1\n", read("filterTest.txt"));
776 
777 			git2.checkout().setName(two.getName()).call();
778 			assertTrue(git2.status().call().isClean());
779 			assertEquals(
780 					"[.gitattributes, mode:100644, content:filterTest.txt filter=lfs][Test.txt, mode:100644, content:Some change][filterTest.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:087148cccf53b0049c56475c1595113c9da4b638997c3489af8ac7108d51ef13\nsize 21\n]",
781 					indexState(CONTENT));
782 			assertEquals("bon giorno world, V1\n", read("filterTest.txt"));
783 		}
784 	}
785 
786 	@Test
787 	public void testNonDeletableFilesOnWindows()
788 			throws GitAPIException, IOException {
789 		// Only on windows a FileInputStream blocks us from deleting a file
790 		org.junit.Assume.assumeTrue(SystemReader.getInstance().isWindows());
791 		writeTrashFile("toBeModified.txt", "a");
792 		writeTrashFile("toBeDeleted.txt", "a");
793 		git.add().addFilepattern(".").call();
794 		RevCommit addFiles = git.commit().setMessage("add more files").call();
795 
796 		git.rm().setCached(false).addFilepattern("Test.txt")
797 				.addFilepattern("toBeDeleted.txt").call();
798 		writeTrashFile("toBeModified.txt", "b");
799 		writeTrashFile("toBeCreated.txt", "a");
800 		git.add().addFilepattern(".").call();
801 		RevCommit crudCommit = git.commit().setMessage("delete, modify, add")
802 				.call();
803 		git.checkout().setName(addFiles.getName()).call();
804 		try ( FileInputStream fis=new FileInputStream(new File(db.getWorkTree(), "Test.txt")) ) {
805 			CheckoutCommand coCommand = git.checkout();
806 			coCommand.setName(crudCommit.getName()).call();
807 			CheckoutResult result = coCommand.getResult();
808 			assertEquals(Status.NONDELETED, result.getStatus());
809 			assertEquals("[Test.txt, toBeDeleted.txt]",
810 					result.getRemovedList().toString());
811 			assertEquals("[toBeCreated.txt, toBeModified.txt]",
812 					result.getModifiedList().toString());
813 			assertEquals("[Test.txt]", result.getUndeletedList().toString());
814 			assertTrue(result.getConflictList().isEmpty());
815 		}
816 	}
817 
818 	private File writeTempFile(String body) throws IOException {
819 		File f = File.createTempFile("CheckoutCommandTest_", "");
820 		JGitTestUtil.write(f, body);
821 		return f;
822 	}
823 }