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