/* * Copyright (C) 2010, Chris Aniszczyk * Copyright (C) 2011, Matthias Sohn and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import static java.time.Instant.EPOCH; import static org.eclipse.jgit.lib.Constants.MASTER; import static org.eclipse.jgit.lib.Constants.R_HEADS; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.attribute.FileTime; import java.time.Instant; import org.eclipse.jgit.api.CheckoutResult.Status; import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode; import org.eclipse.jgit.api.errors.CheckoutConflictException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.InvalidRemoteException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.RefAlreadyExistsException; import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.api.errors.TransportException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.time.TimeUtil; import org.eclipse.jgit.lfs.BuiltinLFS; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Sets; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.SystemReader; import org.junit.Before; import org.junit.Test; public class CheckoutCommandTest extends RepositoryTestCase { private Git git; RevCommit initialCommit; RevCommit secondCommit; @Override @Before public void setUp() throws Exception { BuiltinLFS.register(); super.setUp(); git = new Git(db); // commit something writeTrashFile("Test.txt", "Hello world"); git.add().addFilepattern("Test.txt").call(); initialCommit = git.commit().setMessage("Initial commit").call(); // create a test branch and switch to it git.branchCreate().setName("test").call(); RefUpdate rup = db.updateRef(Constants.HEAD); rup.link("refs/heads/test"); // commit something on the test branch writeTrashFile("Test.txt", "Some change"); git.add().addFilepattern("Test.txt").call(); secondCommit = git.commit().setMessage("Second commit").call(); } @Test public void testSimpleCheckout() throws Exception { git.checkout().setName("test").call(); } @Test public void testCheckout() throws Exception { git.checkout().setName("test").call(); assertEquals("[Test.txt, mode:100644, content:Some change]", indexState(CONTENT)); Ref result = git.checkout().setName("master").call(); assertEquals("[Test.txt, mode:100644, content:Hello world]", indexState(CONTENT)); assertEquals("refs/heads/master", result.getName()); assertEquals("refs/heads/master", git.getRepository().getFullBranch()); } @Test public void testCheckoutForced() throws Exception { writeTrashFile("Test.txt", "Garbage"); try { git.checkout().setName("master").call().getObjectId(); fail("Expected CheckoutConflictException didn't occur"); } catch (CheckoutConflictException e) { // Expected } assertEquals(initialCommit.getId(), git.checkout().setName("master") .setForced(true).call().getObjectId()); } @Test public void testCheckoutForced_deleteFileAndRestore() throws Exception { File testFile = new File(db.getWorkTree(), "Test.txt"); assertTrue(testFile.exists()); assertEquals("test", git.getRepository().getBranch()); FileUtils.delete(testFile); assertFalse(testFile.exists()); // Switch from "test" to "master". assertEquals(initialCommit.getId(), git.checkout().setName("master") .setForced(true).call().getObjectId()); assertTrue(testFile.exists()); assertEquals("master", git.getRepository().getBranch()); FileUtils.delete(testFile); assertFalse(testFile.exists()); // Stay in current branch. assertEquals(initialCommit.getId(), git.checkout().setName("master") .setForced(true).call().getObjectId()); assertTrue(testFile.exists()); } @Test public void testCheckoutForcedNoChangeNotInIndex() throws Exception { git.checkout().setCreateBranch(true).setName("test2").call(); File f = writeTrashFile("NewFile.txt", "New file"); git.add().addFilepattern("NewFile.txt").call(); git.commit().setMessage("New file created").call(); git.checkout().setName("test").call(); assertFalse("NewFile.txt should not exist", f.exists()); writeTrashFile("NewFile.txt", "New file"); git.add().addFilepattern("NewFile.txt").call(); git.commit().setMessage("New file created again with same content") .call(); // Now remove the file from the index only. So it exists in both // commits, and in the working tree, but not in the index. git.rm().addFilepattern("NewFile.txt").setCached(true).call(); assertTrue("NewFile.txt should exist", f.isFile()); git.checkout().setForced(true).setName("test2").call(); assertTrue("NewFile.txt should exist", f.isFile()); assertEquals(Constants.R_HEADS + "test2", git.getRepository() .exactRef(Constants.HEAD).getTarget().getName()); assertTrue("Force checkout should have undone git rm --cached", git.status().call().isClean()); } @Test public void testCheckoutNoChangeNotInIndex() throws Exception { git.checkout().setCreateBranch(true).setName("test2").call(); File f = writeTrashFile("NewFile.txt", "New file"); git.add().addFilepattern("NewFile.txt").call(); git.commit().setMessage("New file created").call(); git.checkout().setName("test").call(); assertFalse("NewFile.txt should not exist", f.exists()); writeTrashFile("NewFile.txt", "New file"); git.add().addFilepattern("NewFile.txt").call(); git.commit().setMessage("New file created again with same content") .call(); // Now remove the file from the index only. So it exists in both // commits, and in the working tree, but not in the index. git.rm().addFilepattern("NewFile.txt").setCached(true).call(); assertTrue("NewFile.txt should exist", f.isFile()); git.checkout().setName("test2").call(); assertTrue("NewFile.txt should exist", f.isFile()); assertEquals(Constants.R_HEADS + "test2", git.getRepository() .exactRef(Constants.HEAD).getTarget().getName()); org.eclipse.jgit.api.Status status = git.status().call(); assertEquals("[NewFile.txt]", status.getRemoved().toString()); assertEquals("[NewFile.txt]", status.getUntracked().toString()); } @Test public void testCreateBranchOnCheckout() throws Exception { git.checkout().setCreateBranch(true).setName("test2").call(); assertNotNull(db.exactRef("refs/heads/test2")); } @Test public void testCheckoutToNonExistingBranch() throws GitAPIException { try { git.checkout().setName("badbranch").call(); fail("Should have failed"); } catch (RefNotFoundException e) { // except to hit here } } @Test public void testCheckoutWithConflict() throws Exception { CheckoutCommand co = git.checkout(); try { writeTrashFile("Test.txt", "Another change"); assertEquals(Status.NOT_TRIED, co.getResult().getStatus()); co.setName("master").call(); fail("Should have failed"); } catch (Exception e) { assertEquals(Status.CONFLICTS, co.getResult().getStatus()); assertTrue(co.getResult().getConflictList().contains("Test.txt")); } git.checkout().setName("master").setForced(true).call(); assertThat(read("Test.txt"), is("Hello world")); } @Test public void testCheckoutWithNonDeletedFiles() throws Exception { File testFile = writeTrashFile("temp", ""); try (FileInputStream fis = new FileInputStream(testFile)) { FileUtils.delete(testFile); return; } catch (IOException e) { // the test makes only sense if deletion of // a file with open stream fails } FileUtils.delete(testFile); CheckoutCommand co = git.checkout(); // delete Test.txt in branch test testFile = new File(db.getWorkTree(), "Test.txt"); assertTrue(testFile.exists()); FileUtils.delete(testFile); assertFalse(testFile.exists()); git.add().addFilepattern("Test.txt"); git.commit().setMessage("Delete Test.txt").setAll(true).call(); git.checkout().setName("master").call(); assertTrue(testFile.exists()); // lock the file so it can't be deleted (in Windows, that is) try (FileInputStream fis = new FileInputStream(testFile)) { assertEquals(Status.NOT_TRIED, co.getResult().getStatus()); co.setName("test").call(); assertTrue(testFile.exists()); assertEquals(Status.NONDELETED, co.getResult().getStatus()); assertTrue(co.getResult().getUndeletedList().contains("Test.txt")); } } @Test public void testCheckoutCommit() throws Exception { Ref result = git.checkout().setName(initialCommit.name()).call(); assertEquals("[Test.txt, mode:100644, content:Hello world]", indexState(CONTENT)); assertNull(result); assertEquals(initialCommit.name(), git.getRepository().getFullBranch()); } @Test public void testCheckoutLightweightTag() throws Exception { git.tag().setAnnotated(false).setName("test-tag") .setObjectId(initialCommit).call(); Ref result = git.checkout().setName("test-tag").call(); assertNull(result); assertEquals(initialCommit.getId(), db.resolve(Constants.HEAD)); assertHeadDetached(); } @Test public void testCheckoutAnnotatedTag() throws Exception { git.tag().setAnnotated(true).setName("test-tag") .setObjectId(initialCommit).call(); Ref result = git.checkout().setName("test-tag").call(); assertNull(result); assertEquals(initialCommit.getId(), db.resolve(Constants.HEAD)); assertHeadDetached(); } @Test public void testCheckoutRemoteTrackingWithUpstream() throws Exception { Repository db2 = createRepositoryWithRemote(); addRepoToClose(db2); Git.wrap(db2).checkout().setCreateBranch(true).setName("test") .setStartPoint("origin/test") .setUpstreamMode(SetupUpstreamMode.TRACK).call(); assertEquals("refs/heads/test", db2.exactRef(Constants.HEAD).getTarget().getName()); StoredConfig config = db2.getConfig(); assertEquals("origin", config.getString( ConfigConstants.CONFIG_BRANCH_SECTION, "test", ConfigConstants.CONFIG_KEY_REMOTE)); assertEquals("refs/heads/test", config.getString( ConfigConstants.CONFIG_BRANCH_SECTION, "test", ConfigConstants.CONFIG_KEY_MERGE)); } @Test public void testCheckoutRemoteTrackingWithoutLocalBranch() throws Exception { Repository db2 = createRepositoryWithRemote(); addRepoToClose(db2); // checkout remote tracking branch in second repository // (no local branches exist yet in second repository) Git.wrap(db2).checkout().setName("remotes/origin/test").call(); assertEquals("[Test.txt, mode:100644, content:Some change]", indexState(db2, CONTENT)); } @Test public void testCheckoutOfFileWithInexistentParentDir() throws Exception { File a = writeTrashFile("dir/a.txt", "A"); writeTrashFile("dir/b.txt", "A"); git.add().addFilepattern("dir/a.txt").addFilepattern("dir/b.txt") .call(); git.commit().setMessage("Added dir").call(); File dir = new File(db.getWorkTree(), "dir"); FileUtils.delete(dir, FileUtils.RECURSIVE); git.checkout().addPath("dir/a.txt").call(); assertTrue(a.exists()); } @Test public void testCheckoutOfDirectoryShouldBeRecursive() throws Exception { File a = writeTrashFile("dir/a.txt", "A"); File b = writeTrashFile("dir/sub/b.txt", "B"); git.add().addFilepattern("dir").call(); git.commit().setMessage("Added dir").call(); write(a, "modified"); write(b, "modified"); git.checkout().addPath("dir").call(); assertThat(read(a), is("A")); assertThat(read(b), is("B")); } @Test public void testCheckoutAllPaths() throws Exception { File a = writeTrashFile("dir/a.txt", "A"); File b = writeTrashFile("dir/sub/b.txt", "B"); git.add().addFilepattern("dir").call(); git.commit().setMessage("Added dir").call(); write(a, "modified"); write(b, "modified"); git.checkout().setAllPaths(true).call(); assertThat(read(a), is("A")); assertThat(read(b), is("B")); } @Test public void testCheckoutWithStartPoint() throws Exception { File a = writeTrashFile("a.txt", "A"); git.add().addFilepattern("a.txt").call(); RevCommit first = git.commit().setMessage("Added a").call(); write(a, "other"); git.commit().setAll(true).setMessage("Other").call(); git.checkout().setCreateBranch(true).setName("a") .setStartPoint(first.getId().getName()).call(); assertThat(read(a), is("A")); } @Test public void testCheckoutWithStartPointOnlyCertainFiles() throws Exception { File a = writeTrashFile("a.txt", "A"); File b = writeTrashFile("b.txt", "B"); git.add().addFilepattern("a.txt").addFilepattern("b.txt").call(); RevCommit first = git.commit().setMessage("First").call(); write(a, "other"); write(b, "other"); git.commit().setAll(true).setMessage("Other").call(); git.checkout().setCreateBranch(true).setName("a") .setStartPoint(first.getId().getName()).addPath("a.txt").call(); assertThat(read(a), is("A")); assertThat(read(b), is("other")); } @Test public void testDetachedHeadOnCheckout() throws JGitInternalException, IOException, GitAPIException { CheckoutCommand co = git.checkout(); co.setName("master").call(); String commitId = db.exactRef(R_HEADS + MASTER).getObjectId().name(); co = git.checkout(); co.setName(commitId).call(); assertHeadDetached(); } @Test public void testUpdateSmudgedEntries() throws Exception { git.branchCreate().setName("test2").call(); RefUpdate rup = db.updateRef(Constants.HEAD); rup.link("refs/heads/test2"); File file = new File(db.getWorkTree(), "Test.txt"); long size = file.length(); Instant mTime = TimeUtil.setLastModifiedWithOffset(file.toPath(), -5000L); DirCache cache = DirCache.lock(db.getIndexFile(), db.getFS()); DirCacheEntry entry = cache.getEntry("Test.txt"); assertNotNull(entry); entry.setLength(0); entry.setLastModified(EPOCH); cache.write(); assertTrue(cache.commit()); cache = DirCache.read(db.getIndexFile(), db.getFS()); entry = cache.getEntry("Test.txt"); assertNotNull(entry); assertEquals(0, entry.getLength()); assertEquals(EPOCH, entry.getLastModifiedInstant()); Files.setLastModifiedTime(db.getIndexFile().toPath(), FileTime.from(FS.DETECTED .lastModifiedInstant(db.getIndexFile()) .minusMillis(5000L))); assertNotNull(git.checkout().setName("test").call()); cache = DirCache.read(db.getIndexFile(), db.getFS()); entry = cache.getEntry("Test.txt"); assertNotNull(entry); assertEquals(size, entry.getLength()); assertEquals(mTime, entry.getLastModifiedInstant()); } @Test public void testCheckoutOrphanBranch() throws Exception { CheckoutCommand co = newOrphanBranchCommand(); assertCheckoutRef(co.call()); File HEAD = new File(trash, ".git/HEAD"); String headRef = read(HEAD); assertEquals("ref: refs/heads/orphanbranch\n", headRef); assertEquals(2, trash.list().length); File heads = new File(trash, ".git/refs/heads"); assertEquals(2, heads.listFiles().length); this.assertNoHead(); this.assertRepositoryCondition(1); assertEquals(CheckoutResult.NOT_TRIED_RESULT, co.getResult()); } private Repository createRepositoryWithRemote() throws IOException, URISyntaxException, MalformedURLException, GitAPIException, InvalidRemoteException, TransportException { // create second repository Repository db2 = createWorkRepository(); try (Git git2 = new Git(db2)) { // setup the second repository to fetch from the first repository final StoredConfig config = db2.getConfig(); RemoteConfig remoteConfig = new RemoteConfig(config, "origin"); URIish uri = new URIish(db.getDirectory().toURI().toURL()); remoteConfig.addURI(uri); remoteConfig.update(config); config.save(); // fetch from first repository git2.fetch().setRemote("origin") .setRefSpecs("+refs/heads/*:refs/remotes/origin/*").call(); return db2; } } private CheckoutCommand newOrphanBranchCommand() { return git.checkout().setOrphan(true) .setName("orphanbranch"); } private static void assertCheckoutRef(Ref ref) { assertNotNull(ref); assertEquals("refs/heads/orphanbranch", ref.getTarget().getName()); } private void assertNoHead() throws IOException { assertNull(db.resolve("HEAD")); } private void assertHeadDetached() throws IOException { Ref head = db.exactRef(Constants.HEAD); assertFalse(head.isSymbolic()); assertSame(head, head.getTarget()); } private void assertRepositoryCondition(int files) throws GitAPIException { org.eclipse.jgit.api.Status status = this.git.status().call(); assertFalse(status.isClean()); assertEquals(files, status.getAdded().size()); } @Test public void testCreateOrphanBranchWithStartCommit() throws Exception { CheckoutCommand co = newOrphanBranchCommand(); Ref ref = co.setStartPoint(initialCommit).call(); assertCheckoutRef(ref); assertEquals(2, trash.list().length); this.assertNoHead(); this.assertRepositoryCondition(1); } @Test public void testCreateOrphanBranchWithStartPoint() throws Exception { CheckoutCommand co = newOrphanBranchCommand(); Ref ref = co.setStartPoint("HEAD^").call(); assertCheckoutRef(ref); assertEquals(2, trash.list().length); this.assertNoHead(); this.assertRepositoryCondition(1); } @Test public void testInvalidRefName() throws Exception { try { git.checkout().setOrphan(true).setName("../invalidname").call(); fail("Should have failed"); } catch (InvalidRefNameException e) { // except to hit here } } @Test public void testNullRefName() throws Exception { try { git.checkout().setOrphan(true).setName(null).call(); fail("Should have failed"); } catch (InvalidRefNameException e) { // except to hit here } } @Test public void testAlreadyExists() throws Exception { this.git.checkout().setCreateBranch(true).setName("orphanbranch") .call(); this.git.checkout().setName("master").call(); try { newOrphanBranchCommand().call(); fail("Should have failed"); } catch (RefAlreadyExistsException e) { // except to hit here } } // TODO: write a faster test which depends less on characteristics of // underlying filesystem/OS. @Test public void testCheckoutAutoCrlfTrue() throws Exception { int nrOfAutoCrlfTestFiles = 200; FileBasedConfig c = db.getConfig(); c.setString("core", null, "autocrlf", "true"); c.save(); AddCommand add = git.add(); for (int i = 100; i < 100 + nrOfAutoCrlfTestFiles; i++) { writeTrashFile("Test_" + i + ".txt", "Hello " + i + " world\nX\nYU\nJK\n"); add.addFilepattern("Test_" + i + ".txt"); } fsTick(null); add.call(); RevCommit c1 = git.commit().setMessage("add some lines").call(); add = git.add(); for (int i = 100; i < 100 + nrOfAutoCrlfTestFiles; i++) { writeTrashFile("Test_" + i + ".txt", "Hello " + i + " world\nX\nY\n"); add.addFilepattern("Test_" + i + ".txt"); } fsTick(null); add.call(); git.commit().setMessage("add more").call(); git.checkout().setName(c1.getName()).call(); boolean foundUnsmudged = false; DirCache dc = db.readDirCache(); for (int i = 100; i < 100 + nrOfAutoCrlfTestFiles; i++) { DirCacheEntry entry = dc.getEntry( "Test_" + i + ".txt"); if (!entry.isSmudged()) { foundUnsmudged = true; assertEquals("unexpected file length in git index", 28, entry.getLength()); } } org.junit.Assume.assumeTrue(foundUnsmudged); } @Test public void testSmudgeFilter_modifyExisting() throws IOException, GitAPIException { File script = writeTempFile("sed s/o/e/g"); StoredConfig config = git.getRepository().getConfig(); config.setString("filter", "lfs", "smudge", "sh " + slashify(script.getPath())); config.save(); writeTrashFile(".gitattributes", "*.txt filter=lfs"); git.add().addFilepattern(".gitattributes").call(); git.commit().setMessage("add filter").call(); writeTrashFile("src/a.tmp", "x"); // Caution: we need a trailing '\n' since sed on mac always appends // linefeeds if missing writeTrashFile("src/a.txt", "x\n"); git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt") .call(); RevCommit content1 = git.commit().setMessage("add content").call(); writeTrashFile("src/a.tmp", "foo"); writeTrashFile("src/a.txt", "foo\n"); git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt") .call(); RevCommit content2 = git.commit().setMessage("changed content").call(); git.checkout().setName(content1.getName()).call(); git.checkout().setName(content2.getName()).call(); assertEquals( "[.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]", indexState(CONTENT)); assertEquals(Sets.of("src/a.txt"), git.status().call().getModified()); assertEquals("foo", read("src/a.tmp")); assertEquals("fee\n", read("src/a.txt")); } @Test public void testSmudgeFilter_createNew() throws IOException, GitAPIException { File script = writeTempFile("sed s/o/e/g"); StoredConfig config = git.getRepository().getConfig(); config.setString("filter", "lfs", "smudge", "sh " + slashify(script.getPath())); config.save(); writeTrashFile("foo", "foo"); git.add().addFilepattern("foo").call(); RevCommit initial = git.commit().setMessage("initial").call(); writeTrashFile(".gitattributes", "*.txt filter=lfs"); git.add().addFilepattern(".gitattributes").call(); git.commit().setMessage("add filter").call(); writeTrashFile("src/a.tmp", "foo"); // Caution: we need a trailing '\n' since sed on mac always appends // linefeeds if missing writeTrashFile("src/a.txt", "foo\n"); git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt") .call(); RevCommit content = git.commit().setMessage("added content").call(); git.checkout().setName(initial.getName()).call(); git.checkout().setName(content.getName()).call(); assertEquals( "[.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]", indexState(CONTENT)); assertEquals("foo", read("src/a.tmp")); assertEquals("fee\n", read("src/a.txt")); } @Test public void testSmudgeFilter_deleteFileAndRestoreFromCommit() throws IOException, GitAPIException { File script = writeTempFile("sed s/o/e/g"); StoredConfig config = git.getRepository().getConfig(); config.setString("filter", "lfs", "smudge", "sh " + slashify(script.getPath())); config.save(); writeTrashFile("foo", "foo"); git.add().addFilepattern("foo").call(); git.commit().setMessage("initial").call(); writeTrashFile(".gitattributes", "*.txt filter=lfs"); git.add().addFilepattern(".gitattributes").call(); git.commit().setMessage("add filter").call(); writeTrashFile("src/a.tmp", "foo"); // Caution: we need a trailing '\n' since sed on mac always appends // linefeeds if missing writeTrashFile("src/a.txt", "foo\n"); git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt") .call(); RevCommit content = git.commit().setMessage("added content").call(); deleteTrashFile("src/a.txt"); git.checkout().setStartPoint(content.getName()).addPath("src/a.txt") .call(); assertEquals( "[.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]", indexState(CONTENT)); assertEquals("foo", read("src/a.tmp")); assertEquals("fee\n", read("src/a.txt")); } @Test public void testSmudgeFilter_deleteFileAndRestoreFromIndex() throws IOException, GitAPIException { File script = writeTempFile("sed s/o/e/g"); StoredConfig config = git.getRepository().getConfig(); config.setString("filter", "lfs", "smudge", "sh " + slashify(script.getPath())); config.save(); writeTrashFile("foo", "foo"); git.add().addFilepattern("foo").call(); git.commit().setMessage("initial").call(); writeTrashFile(".gitattributes", "*.txt filter=lfs"); git.add().addFilepattern(".gitattributes").call(); git.commit().setMessage("add filter").call(); writeTrashFile("src/a.tmp", "foo"); // Caution: we need a trailing '\n' since sed on mac always appends // linefeeds if missing writeTrashFile("src/a.txt", "foo\n"); git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt") .call(); git.commit().setMessage("added content").call(); deleteTrashFile("src/a.txt"); git.checkout().addPath("src/a.txt").call(); assertEquals( "[.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]", indexState(CONTENT)); assertEquals("foo", read("src/a.tmp")); assertEquals("fee\n", read("src/a.txt")); } @Test public void testSmudgeFilter_deleteFileAndCreateBranchAndRestoreFromCommit() throws IOException, GitAPIException { File script = writeTempFile("sed s/o/e/g"); StoredConfig config = git.getRepository().getConfig(); config.setString("filter", "lfs", "smudge", "sh " + slashify(script.getPath())); config.save(); writeTrashFile("foo", "foo"); git.add().addFilepattern("foo").call(); git.commit().setMessage("initial").call(); writeTrashFile(".gitattributes", "*.txt filter=lfs"); git.add().addFilepattern(".gitattributes").call(); git.commit().setMessage("add filter").call(); writeTrashFile("src/a.tmp", "foo"); // Caution: we need a trailing '\n' since sed on mac always appends // linefeeds if missing writeTrashFile("src/a.txt", "foo\n"); git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt") .call(); RevCommit content = git.commit().setMessage("added content").call(); deleteTrashFile("src/a.txt"); git.checkout().setName("newBranch").setCreateBranch(true) .setStartPoint(content).addPath("src/a.txt").call(); assertEquals( "[.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]", indexState(CONTENT)); assertEquals("foo", read("src/a.tmp")); assertEquals("fee\n", read("src/a.txt")); } @Test public void testSmudgeAndClean() throws Exception { File clean_filter = writeTempFile("sed s/V1/@version/g"); File smudge_filter = writeTempFile("sed s/@version/V1/g"); try (Git git2 = new Git(db)) { StoredConfig config = git.getRepository().getConfig(); config.setString("filter", "lfs", "smudge", "sh " + slashify(smudge_filter.getPath())); config.setString("filter", "lfs", "clean", "sh " + slashify(clean_filter.getPath())); config.setBoolean("filter", "lfs", "useJGitBuiltin", true); config.save(); writeTrashFile(".gitattributes", "filterTest.txt filter=lfs"); git2.add().addFilepattern(".gitattributes").call(); git2.commit().setMessage("add attributes").call(); fsTick(writeTrashFile("filterTest.txt", "hello world, V1\n")); git2.add().addFilepattern("filterTest.txt").call(); RevCommit one = git2.commit().setMessage("add filterText.txt").call(); assertEquals( "[.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]", indexState(CONTENT)); fsTick(writeTrashFile("filterTest.txt", "bon giorno world, V1\n")); git2.add().addFilepattern("filterTest.txt").call(); RevCommit two = git2.commit().setMessage("modified filterTest.txt").call(); assertTrue(git2.status().call().isClean()); assertEquals( "[.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]", indexState(CONTENT)); git2.checkout().setName(one.getName()).call(); assertTrue(git2.status().call().isClean()); assertEquals( "[.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]", indexState(CONTENT)); assertEquals("hello world, V1\n", read("filterTest.txt")); git2.checkout().setName(two.getName()).call(); assertTrue(git2.status().call().isClean()); assertEquals( "[.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]", indexState(CONTENT)); assertEquals("bon giorno world, V1\n", read("filterTest.txt")); } } @Test public void testNonDeletableFilesOnWindows() throws GitAPIException, IOException { // Only on windows a FileInputStream blocks us from deleting a file org.junit.Assume.assumeTrue(SystemReader.getInstance().isWindows()); writeTrashFile("toBeModified.txt", "a"); writeTrashFile("toBeDeleted.txt", "a"); git.add().addFilepattern(".").call(); RevCommit addFiles = git.commit().setMessage("add more files").call(); git.rm().setCached(false).addFilepattern("Test.txt") .addFilepattern("toBeDeleted.txt").call(); writeTrashFile("toBeModified.txt", "b"); writeTrashFile("toBeCreated.txt", "a"); git.add().addFilepattern(".").call(); RevCommit crudCommit = git.commit().setMessage("delete, modify, add") .call(); git.checkout().setName(addFiles.getName()).call(); try ( FileInputStream fis=new FileInputStream(new File(db.getWorkTree(), "Test.txt")) ) { CheckoutCommand coCommand = git.checkout(); coCommand.setName(crudCommit.getName()).call(); CheckoutResult result = coCommand.getResult(); assertEquals(Status.NONDELETED, result.getStatus()); assertEquals("[toBeDeleted.txt]", result.getRemovedList().toString()); assertEquals("[toBeCreated.txt, toBeModified.txt]", result.getModifiedList().toString()); assertEquals("[Test.txt]", result.getUndeletedList().toString()); assertTrue(result.getConflictList().isEmpty()); } } private File writeTempFile(String body) throws IOException { File f = File.createTempFile("CheckoutCommandTest_", ""); JGitTestUtil.write(f, body); return f; } }