/* * Copyright (C) 2012, IBM Corporation * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available * under the terms of the Eclipse Distribution License v1.0 which * accompanies this distribution, is reproduced below, and is * available at http://www.eclipse.org/org/documents/edl-v10.php * * All rights reserved. * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * - Neither the name of the Eclipse Foundation, Inc. nor the * names of its contributors may be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.eclipse.jgit.pgm; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.List; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.CheckoutConflictException; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.lib.CLIRepositoryTestCase; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; import org.junit.Assume; import org.junit.Test; public class CheckoutTest extends CLIRepositoryTestCase { @Test public void testCheckoutSelf() throws Exception { try (Git git = new Git(db)) { git.commit().setMessage("initial commit").call(); assertStringArrayEquals("Already on 'master'", execute("git checkout master")); } } @Test public void testCheckoutBranch() throws Exception { try (Git git = new Git(db)) { git.commit().setMessage("initial commit").call(); git.branchCreate().setName("side").call(); assertStringArrayEquals("Switched to branch 'side'", execute("git checkout side")); } } @Test public void testCheckoutNewBranch() throws Exception { try (Git git = new Git(db)) { git.commit().setMessage("initial commit").call(); assertStringArrayEquals("Switched to a new branch 'side'", execute("git checkout -b side")); } } @Test public void testCheckoutNonExistingBranch() throws Exception { assertStringArrayEquals( "error: pathspec 'side' did not match any file(s) known to git.", execute("git checkout side")); } @Test public void testCheckoutNewBranchThatAlreadyExists() throws Exception { try (Git git = new Git(db)) { git.commit().setMessage("initial commit").call(); assertStringArrayEquals( "fatal: A branch named 'master' already exists.", executeUnchecked("git checkout -b master")); } } @Test public void testCheckoutNewBranchOnBranchToBeBorn() throws Exception { assertStringArrayEquals("fatal: You are on a branch yet to be born", executeUnchecked("git checkout -b side")); } @Test public void testCheckoutUnresolvedHead() throws Exception { assertStringArrayEquals( "error: pathspec 'HEAD' did not match any file(s) known to git.", execute("git checkout HEAD")); } @Test public void testCheckoutHead() throws Exception { try (Git git = new Git(db)) { git.commit().setMessage("initial commit").call(); assertStringArrayEquals("", execute("git checkout HEAD")); } } @Test public void testCheckoutExistingBranchWithConflict() throws Exception { try (Git git = new Git(db)) { writeTrashFile("a", "Hello world a"); git.add().addFilepattern(".").call(); git.commit().setMessage("commit file a").call(); git.branchCreate().setName("branch_1").call(); git.rm().addFilepattern("a").call(); FileUtils.mkdirs(new File(db.getWorkTree(), "a")); writeTrashFile("a/b", "Hello world b"); git.add().addFilepattern("a/b").call(); git.commit().setMessage("commit folder a").call(); git.rm().addFilepattern("a").call(); writeTrashFile("a", "New Hello world a"); git.add().addFilepattern(".").call(); String[] execute = execute("git checkout branch_1"); assertEquals( "error: Your local changes to the following files would be overwritten by checkout:", execute[0]); assertEquals("\ta", execute[1]); } } /** * Steps: * <ol> * <li>Add file 'a' and 'b' * <li>Commit * <li>Create branch '1' * <li>modify file 'a' * <li>Commit * <li>Delete file 'a' in the working tree * <li>Checkout branch '1' * </ol> * <p> * The working tree should contain 'a' with FileMode.REGULAR_FILE after the * checkout. * * @throws Exception */ @Test public void testCheckoutWithMissingWorkingTreeFile() throws Exception { try (Git git = new Git(db)) { File fileA = writeTrashFile("a", "Hello world a"); writeTrashFile("b", "Hello world b"); git.add().addFilepattern(".").call(); git.commit().setMessage("add files a & b").call(); Ref branch_1 = git.branchCreate().setName("branch_1").call(); writeTrashFile("a", "b"); git.add().addFilepattern("a").call(); git.commit().setMessage("modify file a").call(); FileEntry entry = new FileTreeIterator.FileEntry(new File( db.getWorkTree(), "a"), db.getFS()); assertEquals(FileMode.REGULAR_FILE, entry.getMode()); FileUtils.delete(fileA); git.checkout().setName(branch_1.getName()).call(); entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"), db.getFS()); assertEquals(FileMode.REGULAR_FILE, entry.getMode()); assertEquals("Hello world a", read(fileA)); } } @Test public void testCheckoutOrphan() throws Exception { try (Git git = new Git(db)) { git.commit().setMessage("initial commit").call(); assertStringArrayEquals("Switched to a new branch 'new_branch'", execute("git checkout --orphan new_branch")); assertEquals("refs/heads/new_branch", db.exactRef("HEAD").getTarget().getName()); RevCommit commit = git.commit().setMessage("orphan commit").call(); assertEquals(0, commit.getParentCount()); } } /** * Steps: * <ol> * <li>Add file 'b' * <li>Commit * <li>Create branch '1' * <li>Add folder 'a' * <li>Commit * <li>Replace folder 'a' by file 'a' in the working tree * <li>Checkout branch '1' * </ol> * <p> * The checkout has to delete folder but the workingtree contains a dirty * file at this path. The checkout should fail like in native git. * * @throws Exception */ @Test public void fileModeTestMissingThenFolderWithFileInWorkingTree() throws Exception { try (Git git = new Git(db)) { writeTrashFile("b", "Hello world b"); git.add().addFilepattern(".").call(); git.commit().setMessage("add file b").call(); Ref branch_1 = git.branchCreate().setName("branch_1").call(); File folderA = new File(db.getWorkTree(), "a"); FileUtils.mkdirs(folderA); writeTrashFile("a/c", "Hello world c"); git.add().addFilepattern(".").call(); git.commit().setMessage("add folder a").call(); FileEntry entry = new FileTreeIterator.FileEntry(new File( db.getWorkTree(), "a"), db.getFS()); assertEquals(FileMode.TREE, entry.getMode()); FileUtils.delete(folderA, FileUtils.RECURSIVE); writeTrashFile("a", "b"); entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"), db.getFS()); assertEquals(FileMode.REGULAR_FILE, entry.getMode()); try { git.checkout().setName(branch_1.getName()).call(); fail("Don't get the expected conflict"); } catch (CheckoutConflictException e) { assertEquals("[a]", e.getConflictingPaths().toString()); entry = new FileTreeIterator.FileEntry( new File(db.getWorkTree(), "a"), db.getFS()); assertEquals(FileMode.REGULAR_FILE, entry.getMode()); } } } /** * Steps: * <ol> * <li>Add file 'a' * <li>Commit * <li>Create branch '1' * <li>Replace file 'a' by folder 'a' * <li>Commit * <li>Delete folder 'a' in the working tree * <li>Checkout branch '1' * </ol> * <p> * The working tree should contain 'a' with FileMode.REGULAR_FILE after the * checkout. * * @throws Exception */ @Test public void fileModeTestFolderWithMissingInWorkingTree() throws Exception { try (Git git = new Git(db)) { writeTrashFile("b", "Hello world b"); writeTrashFile("a", "b"); git.add().addFilepattern(".").call(); git.commit().setMessage("add file b & file a").call(); Ref branch_1 = git.branchCreate().setName("branch_1").call(); git.rm().addFilepattern("a").call(); File folderA = new File(db.getWorkTree(), "a"); FileUtils.mkdirs(folderA); writeTrashFile("a/c", "Hello world c"); git.add().addFilepattern(".").call(); git.commit().setMessage("add folder a").call(); FileEntry entry = new FileTreeIterator.FileEntry(new File( db.getWorkTree(), "a"), db.getFS()); assertEquals(FileMode.TREE, entry.getMode()); FileUtils.delete(folderA, FileUtils.RECURSIVE); git.checkout().setName(branch_1.getName()).call(); entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"), db.getFS()); assertEquals(FileMode.REGULAR_FILE, entry.getMode()); } } /** * Steps: * <ol> * <li>Add file 'a' * <li>Commit * <li>Create branch '1' * <li>Delete file 'a' * <li>Commit * <li>Add folder 'a' in the working tree * <li>Checkout branch '1' * </ol> * <p> * The checkout command should raise an error. The conflicting paths are 'a' * and 'a/c'. * * @throws Exception */ @Test public void fileModeTestMissingWithFolderInWorkingTree() throws Exception { try (Git git = new Git(db)) { writeTrashFile("b", "Hello world b"); writeTrashFile("a", "b"); git.add().addFilepattern(".").call(); git.commit().setMessage("add file b & file a").call(); Ref branch_1 = git.branchCreate().setName("branch_1").call(); git.rm().addFilepattern("a").call(); git.commit().setMessage("delete file a").call(); FileUtils.mkdirs(new File(db.getWorkTree(), "a")); writeTrashFile("a/c", "Hello world c"); FileEntry entry = new FileTreeIterator.FileEntry(new File( db.getWorkTree(), "a"), db.getFS()); assertEquals(FileMode.TREE, entry.getMode()); CheckoutConflictException exception = null; try { git.checkout().setName(branch_1.getName()).call(); } catch (CheckoutConflictException e) { exception = e; } assertNotNull(exception); assertEquals(2, exception.getConflictingPaths().size()); assertEquals("a", exception.getConflictingPaths().get(0)); assertEquals("a/c", exception.getConflictingPaths().get(1)); } } /** * Steps: * <ol> * <li>Add folder 'a' * <li>Commit * <li>Create branch '1' * <li>Delete folder 'a' * <li>Commit * <li>Add file 'a' in the working tree * <li>Checkout branch '1' * </ol> * <p> * The checkout command should raise an error. The conflicting path is 'a'. * * @throws Exception */ @Test public void fileModeTestFolderThenMissingWithFileInWorkingTree() throws Exception { try (Git git = new Git(db)) { FileUtils.mkdirs(new File(db.getWorkTree(), "a")); writeTrashFile("a/c", "Hello world c"); writeTrashFile("b", "Hello world b"); git.add().addFilepattern(".").call(); RevCommit commit1 = git.commit().setMessage("add folder a & file b") .call(); Ref branch_1 = git.branchCreate().setName("branch_1").call(); git.rm().addFilepattern("a").call(); RevCommit commit2 = git.commit().setMessage("delete folder a").call(); TreeWalk tw = new TreeWalk(db); tw.addTree(commit1.getTree()); tw.addTree(commit2.getTree()); List<DiffEntry> scan = DiffEntry.scan(tw); assertEquals(1, scan.size()); assertEquals(FileMode.MISSING, scan.get(0).getNewMode()); assertEquals(FileMode.TREE, scan.get(0).getOldMode()); writeTrashFile("a", "b"); FileEntry entry = new FileTreeIterator.FileEntry(new File( db.getWorkTree(), "a"), db.getFS()); assertEquals(FileMode.REGULAR_FILE, entry.getMode()); CheckoutConflictException exception = null; try { git.checkout().setName(branch_1.getName()).call(); } catch (CheckoutConflictException e) { exception = e; } assertNotNull(exception); assertEquals(1, exception.getConflictingPaths().size()); assertEquals("a", exception.getConflictingPaths().get(0)); } } /** * Steps: * <ol> * <li>Add folder 'a' * <li>Commit * <li>Create branch '1' * <li>Replace folder 'a'by file 'a' * <li>Commit * <li>Delete file 'a' in the working tree * <li>Checkout branch '1' * </ol> * <p> * The working tree should contain 'a' with FileMode.TREE after the * checkout. * * @throws Exception */ @Test public void fileModeTestFolderThenFileWithMissingInWorkingTree() throws Exception { try (Git git = new Git(db)) { FileUtils.mkdirs(new File(db.getWorkTree(), "a")); writeTrashFile("a/c", "Hello world c"); writeTrashFile("b", "Hello world b"); git.add().addFilepattern(".").call(); git.commit().setMessage("add folder a & file b").call(); Ref branch_1 = git.branchCreate().setName("branch_1").call(); git.rm().addFilepattern("a").call(); File fileA = new File(db.getWorkTree(), "a"); writeTrashFile("a", "b"); git.add().addFilepattern("a").call(); git.commit().setMessage("add file a").call(); FileEntry entry = new FileTreeIterator.FileEntry(new File( db.getWorkTree(), "a"), db.getFS()); assertEquals(FileMode.REGULAR_FILE, entry.getMode()); FileUtils.delete(fileA); git.checkout().setName(branch_1.getName()).call(); entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"), db.getFS()); assertEquals(FileMode.TREE, entry.getMode()); } } /** * Steps: * <ol> * <li>Add file 'a' * <li>Commit * <li>Create branch '1' * <li>Modify file 'a' * <li>Commit * <li>Delete file 'a' & replace by folder 'a' in the working tree & index * <li>Checkout branch '1' * </ol> * <p> * The checkout command should raise an error. The conflicting path is 'a'. * * @throws Exception */ @Test public void fileModeTestFileThenFileWithFolderInIndex() throws Exception { try (Git git = new Git(db)) { writeTrashFile("a", "Hello world a"); writeTrashFile("b", "Hello world b"); git.add().addFilepattern(".").call(); git.commit().setMessage("add files a & b").call(); Ref branch_1 = git.branchCreate().setName("branch_1").call(); writeTrashFile("a", "b"); git.add().addFilepattern("a").call(); git.commit().setMessage("add file a").call(); FileEntry entry = new FileTreeIterator.FileEntry(new File( db.getWorkTree(), "a"), db.getFS()); assertEquals(FileMode.REGULAR_FILE, entry.getMode()); git.rm().addFilepattern("a").call(); FileUtils.mkdirs(new File(db.getWorkTree(), "a")); writeTrashFile("a/c", "Hello world c"); git.add().addFilepattern(".").call(); entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"), db.getFS()); assertEquals(FileMode.TREE, entry.getMode()); CheckoutConflictException exception = null; try { git.checkout().setName(branch_1.getName()).call(); } catch (CheckoutConflictException e) { exception = e; } assertNotNull(exception); assertEquals(1, exception.getConflictingPaths().size()); assertEquals("a", exception.getConflictingPaths().get(0)); } } /** * Steps: * <ol> * <li>Add file 'a' * <li>Commit * <li>Create branch '1' * <li>Modify file 'a' * <li>Commit * <li>Delete file 'a' & replace by folder 'a' in the working tree & index * <li>Checkout branch '1' * </ol> * <p> * The checkout command should raise an error. The conflicting paths are 'a' * and 'a/c'. * * @throws Exception */ @Test public void fileModeTestFileWithFolderInIndex() throws Exception { try (Git git = new Git(db)) { writeTrashFile("b", "Hello world b"); writeTrashFile("a", "b"); git.add().addFilepattern(".").call(); git.commit().setMessage("add file b & file a").call(); Ref branch_1 = git.branchCreate().setName("branch_1").call(); git.rm().addFilepattern("a").call(); writeTrashFile("a", "Hello world a"); git.add().addFilepattern("a").call(); git.commit().setMessage("add file a").call(); FileEntry entry = new FileTreeIterator.FileEntry(new File( db.getWorkTree(), "a"), db.getFS()); assertEquals(FileMode.REGULAR_FILE, entry.getMode()); git.rm().addFilepattern("a").call(); FileUtils.mkdirs(new File(db.getWorkTree(), "a")); writeTrashFile("a/c", "Hello world c"); git.add().addFilepattern(".").call(); entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"), db.getFS()); assertEquals(FileMode.TREE, entry.getMode()); CheckoutConflictException exception = null; try { git.checkout().setName(branch_1.getName()).call(); } catch (CheckoutConflictException e) { exception = e; } assertNotNull(exception); assertEquals(1, exception.getConflictingPaths().size()); assertEquals("a", exception.getConflictingPaths().get(0)); // TODO: ideally we'd like to get two paths from this exception // assertEquals(2, exception.getConflictingPaths().size()); // assertEquals("a", exception.getConflictingPaths().get(0)); // assertEquals("a/c", exception.getConflictingPaths().get(1)); } } @Test public void testCheckoutPath() throws Exception { try (Git git = new Git(db)) { writeTrashFile("a", "Hello world a"); git.add().addFilepattern(".").call(); git.commit().setMessage("commit file a").call(); git.branchCreate().setName("branch_1").call(); git.checkout().setName("branch_1").call(); File b = writeTrashFile("b", "Hello world b"); git.add().addFilepattern("b").call(); git.commit().setMessage("commit file b").call(); File a = writeTrashFile("a", "New Hello world a"); git.add().addFilepattern(".").call(); git.commit().setMessage("modified a").call(); assertArrayEquals(new String[] { "" }, execute("git checkout HEAD~2 -- a")); assertEquals("Hello world a", read(a)); assertArrayEquals(new String[] { "* branch_1", " master", "" }, execute("git branch")); assertEquals("Hello world b", read(b)); } } @Test public void testCheckoutAllPaths() throws Exception { try (Git git = new Git(db)) { writeTrashFile("a", "Hello world a"); git.add().addFilepattern(".").call(); git.commit().setMessage("commit file a").call(); git.branchCreate().setName("branch_1").call(); git.checkout().setName("branch_1").call(); File b = writeTrashFile("b", "Hello world b"); git.add().addFilepattern("b").call(); git.commit().setMessage("commit file b").call(); File a = writeTrashFile("a", "New Hello world a"); git.add().addFilepattern(".").call(); git.commit().setMessage("modified a").call(); assertArrayEquals(new String[] { "" }, execute("git checkout HEAD~2 -- .")); assertEquals("Hello world a", read(a)); assertArrayEquals(new String[] { "* branch_1", " master", "" }, execute("git branch")); assertEquals("Hello world b", read(b)); } } @Test public void testCheckoutSingleFile() throws Exception { try (Git git = new Git(db)) { File a = writeTrashFile("a", "file a"); git.add().addFilepattern(".").call(); git.commit().setMessage("commit file a").call(); writeTrashFile("a", "b"); assertEquals("b", read(a)); assertEquals("[]", Arrays.toString(execute("git checkout -- a"))); assertEquals("file a", read(a)); } } @Test public void testCheckoutLink() throws Exception { Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); try (Git git = new Git(db)) { Path path = writeLink("a", "link_a"); assertTrue(Files.isSymbolicLink(path)); git.add().addFilepattern(".").call(); git.commit().setMessage("commit link a").call(); deleteTrashFile("a"); writeTrashFile("a", "Hello world a"); assertFalse(Files.isSymbolicLink(path)); assertEquals("[]", Arrays.toString(execute("git checkout -- a"))); assertEquals("link_a", FileUtils.readSymLink(path.toFile())); assertTrue(Files.isSymbolicLink(path)); } } }