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