| /* | |
| * Copyright (C) 2011, 2020 Kevin Sawicki <kevin@github.com> 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 org.junit.Assert.assertEquals; | |
| import static org.junit.Assert.assertFalse; | |
| import static org.junit.Assert.assertTrue; | |
| import java.io.File; | |
| import java.io.IOException; | |
| import java.nio.file.Path; | |
| import org.eclipse.jgit.api.CheckoutCommand.Stage; | |
| import org.eclipse.jgit.api.errors.JGitInternalException; | |
| import org.eclipse.jgit.dircache.DirCache; | |
| import org.eclipse.jgit.dircache.DirCacheEntry; | |
| import org.eclipse.jgit.errors.NoWorkTreeException; | |
| import org.eclipse.jgit.junit.RepositoryTestCase; | |
| import org.eclipse.jgit.lib.ConfigConstants; | |
| import org.eclipse.jgit.lib.Constants; | |
| import org.eclipse.jgit.lib.ObjectReader; | |
| import org.eclipse.jgit.lib.RepositoryState; | |
| import org.eclipse.jgit.lib.StoredConfig; | |
| import org.eclipse.jgit.revwalk.RevCommit; | |
| import org.eclipse.jgit.util.FS; | |
| import org.eclipse.jgit.util.FileUtils; | |
| import org.junit.Assume; | |
| import org.junit.Before; | |
| import org.junit.Test; | |
| /** | |
| * Unit tests of path-based uses of {@link CheckoutCommand} | |
| */ | |
| public class PathCheckoutCommandTest extends RepositoryTestCase { | |
| private static final String FILE1 = "f/Test.txt"; | |
| private static final String FILE2 = "Test2.txt"; | |
| private static final String FILE3 = "Test3.txt"; | |
| private static final String LINK = "link"; | |
| Git git; | |
| RevCommit initialCommit; | |
| RevCommit secondCommit; | |
| @Override | |
| @Before | |
| public void setUp() throws Exception { | |
| super.setUp(); | |
| git = new Git(db); | |
| writeTrashFile(FILE1, "1"); | |
| writeTrashFile(FILE2, "a"); | |
| git.add().addFilepattern(FILE1).addFilepattern(FILE2).call(); | |
| initialCommit = git.commit().setMessage("Initial commit").call(); | |
| writeTrashFile(FILE1, "2"); | |
| writeTrashFile(FILE2, "b"); | |
| git.add().addFilepattern(FILE1).addFilepattern(FILE2).call(); | |
| secondCommit = git.commit().setMessage("Second commit").call(); | |
| writeTrashFile(FILE1, "3"); | |
| writeTrashFile(FILE2, "c"); | |
| git.add().addFilepattern(FILE1).addFilepattern(FILE2).call(); | |
| git.commit().setMessage("Third commit").call(); | |
| } | |
| @Test | |
| public void testUpdateSymLink() throws Exception { | |
| Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); | |
| Path path = writeLink(LINK, FILE1); | |
| git.add().addFilepattern(LINK).call(); | |
| git.commit().setMessage("Added link").call(); | |
| assertEquals("3", read(path.toFile())); | |
| writeLink(LINK, FILE2); | |
| assertEquals("c", read(path.toFile())); | |
| CheckoutCommand co = git.checkout(); | |
| co.addPath(LINK).call(); | |
| assertEquals("3", read(path.toFile())); | |
| } | |
| @Test | |
| public void testUpdateBrokenSymLinkToDirectory() throws Exception { | |
| Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); | |
| Path path = writeLink(LINK, "f"); | |
| git.add().addFilepattern(LINK).call(); | |
| git.commit().setMessage("Added link").call(); | |
| assertEquals("f", FileUtils.readSymLink(path.toFile())); | |
| assertTrue(path.toFile().exists()); | |
| writeLink(LINK, "link_to_nowhere"); | |
| assertFalse(path.toFile().exists()); | |
| assertEquals("link_to_nowhere", FileUtils.readSymLink(path.toFile())); | |
| CheckoutCommand co = git.checkout(); | |
| co.addPath(LINK).call(); | |
| assertEquals("f", FileUtils.readSymLink(path.toFile())); | |
| } | |
| @Test | |
| public void testUpdateBrokenSymLink() throws Exception { | |
| Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); | |
| Path path = writeLink(LINK, FILE1); | |
| git.add().addFilepattern(LINK).call(); | |
| git.commit().setMessage("Added link").call(); | |
| assertEquals("3", read(path.toFile())); | |
| assertEquals(FILE1, FileUtils.readSymLink(path.toFile())); | |
| writeLink(LINK, "link_to_nowhere"); | |
| assertFalse(path.toFile().exists()); | |
| assertEquals("link_to_nowhere", FileUtils.readSymLink(path.toFile())); | |
| CheckoutCommand co = git.checkout(); | |
| co.addPath(LINK).call(); | |
| assertEquals("3", read(path.toFile())); | |
| } | |
| @Test | |
| public void testUpdateWorkingDirectory() throws Exception { | |
| CheckoutCommand co = git.checkout(); | |
| File written = writeTrashFile(FILE1, ""); | |
| assertEquals("", read(written)); | |
| co.addPath(FILE1).call(); | |
| assertEquals("3", read(written)); | |
| assertEquals("c", read(new File(db.getWorkTree(), FILE2))); | |
| } | |
| @Test | |
| public void testCheckoutFirst() throws Exception { | |
| CheckoutCommand co = git.checkout(); | |
| File written = writeTrashFile(FILE1, ""); | |
| co.setStartPoint(initialCommit).addPath(FILE1).call(); | |
| assertEquals("1", read(written)); | |
| assertEquals("c", read(new File(db.getWorkTree(), FILE2))); | |
| } | |
| @Test | |
| public void testCheckoutSecond() throws Exception { | |
| CheckoutCommand co = git.checkout(); | |
| File written = writeTrashFile(FILE1, ""); | |
| co.setStartPoint("HEAD~1").addPath(FILE1).call(); | |
| assertEquals("2", read(written)); | |
| assertEquals("c", read(new File(db.getWorkTree(), FILE2))); | |
| } | |
| @Test | |
| public void testCheckoutMultiple() throws Exception { | |
| CheckoutCommand co = git.checkout(); | |
| File test = writeTrashFile(FILE1, ""); | |
| File test2 = writeTrashFile(FILE2, ""); | |
| co.setStartPoint("HEAD~2").addPath(FILE1).addPath(FILE2).call(); | |
| assertEquals("1", read(test)); | |
| assertEquals("a", read(test2)); | |
| } | |
| @Test | |
| public void testUpdateWorkingDirectoryFromIndex() throws Exception { | |
| CheckoutCommand co = git.checkout(); | |
| writeTrashFile(FILE1, "3a"); | |
| git.add().addFilepattern(FILE1).call(); | |
| File written = writeTrashFile(FILE1, ""); | |
| assertEquals("", read(written)); | |
| co.addPath(FILE1).call(); | |
| assertEquals("3a", read(written)); | |
| assertEquals("c", read(new File(db.getWorkTree(), FILE2))); | |
| } | |
| @Test | |
| public void testUpdateWorkingDirectoryFromHeadWithIndexChange() | |
| throws Exception { | |
| CheckoutCommand co = git.checkout(); | |
| writeTrashFile(FILE1, "3a"); | |
| git.add().addFilepattern(FILE1).call(); | |
| File written = writeTrashFile(FILE1, ""); | |
| assertEquals("", read(written)); | |
| co.addPath(FILE1).setStartPoint("HEAD").call(); | |
| assertEquals("3", read(written)); | |
| assertEquals("c", read(new File(db.getWorkTree(), FILE2))); | |
| } | |
| @Test | |
| public void testUpdateWorkingDirectoryFromIndex2() throws Exception { | |
| CheckoutCommand co = git.checkout(); | |
| fsTick(git.getRepository().getIndexFile()); | |
| File written1 = writeTrashFile(FILE1, "3(modified)"); | |
| File written2 = writeTrashFile(FILE2, "a(modified)"); | |
| fsTick(written2); | |
| // make sure that we get unsmudged entries for FILE1 and FILE2 | |
| writeTrashFile(FILE3, "foo"); | |
| git.add().addFilepattern(FILE3).call(); | |
| fsTick(git.getRepository().getIndexFile()); | |
| git.add().addFilepattern(FILE1).addFilepattern(FILE2).call(); | |
| fsTick(git.getRepository().getIndexFile()); | |
| writeTrashFile(FILE1, "3(modified again)"); | |
| writeTrashFile(FILE2, "a(modified again)"); | |
| fsTick(written2); | |
| co.addPath(FILE1).setStartPoint(secondCommit).call(); | |
| assertEquals("2", read(written1)); | |
| assertEquals("a(modified again)", read(written2)); | |
| validateIndex(git); | |
| } | |
| public static void validateIndex(Git git) throws NoWorkTreeException, | |
| IOException { | |
| DirCache dc = git.getRepository().lockDirCache(); | |
| try (ObjectReader r = git.getRepository().getObjectDatabase() | |
| .newReader()) { | |
| for (int i = 0; i < dc.getEntryCount(); ++i) { | |
| DirCacheEntry entry = dc.getEntry(i); | |
| if (entry.getLength() > 0) | |
| assertEquals(entry.getLength(), r.getObjectSize( | |
| entry.getObjectId(), ObjectReader.OBJ_ANY)); | |
| } | |
| } finally { | |
| dc.unlock(); | |
| } | |
| } | |
| @Test | |
| public void testCheckoutMixedNewlines() throws Exception { | |
| // "git config core.autocrlf true" | |
| StoredConfig config = git.getRepository().getConfig(); | |
| config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, | |
| ConfigConstants.CONFIG_KEY_AUTOCRLF, true); | |
| config.save(); | |
| // edit <FILE1> | |
| File written = writeTrashFile(FILE1, "4\r\n4"); | |
| assertEquals("4\r\n4", read(written)); | |
| // "git add <FILE1>" | |
| git.add().addFilepattern(FILE1).call(); | |
| // "git commit -m 'CRLF'" | |
| git.commit().setMessage("CRLF").call(); | |
| // edit <FILE1> | |
| written = writeTrashFile(FILE1, "4\n4"); | |
| assertEquals("4\n4", read(written)); | |
| // "git add <FILE1>" | |
| git.add().addFilepattern(FILE1).call(); | |
| // "git checkout -- <FILE1> | |
| git.checkout().addPath(FILE1).call(); | |
| // "git status" => clean | |
| Status status = git.status().call(); | |
| assertEquals(0, status.getAdded().size()); | |
| assertEquals(0, status.getChanged().size()); | |
| assertEquals(0, status.getConflicting().size()); | |
| assertEquals(0, status.getMissing().size()); | |
| assertEquals(0, status.getModified().size()); | |
| assertEquals(0, status.getRemoved().size()); | |
| assertEquals(0, status.getUntracked().size()); | |
| } | |
| @Test | |
| public void testCheckoutRepository() throws Exception { | |
| CheckoutCommand co = git.checkout(); | |
| File test = writeTrashFile(FILE1, ""); | |
| File test2 = writeTrashFile(FILE2, ""); | |
| co.setStartPoint("HEAD~2").setAllPaths(true).call(); | |
| assertEquals("1", read(test)); | |
| assertEquals("a", read(test2)); | |
| } | |
| @Test(expected = JGitInternalException.class) | |
| public void testCheckoutOfConflictingFileShouldThrow() | |
| throws Exception { | |
| setupConflictingState(); | |
| git.checkout().addPath(FILE1).call(); | |
| } | |
| @Test | |
| public void testCheckoutOurs() throws Exception { | |
| setupConflictingState(); | |
| git.checkout().setStage(Stage.OURS).addPath(FILE1).call(); | |
| assertEquals("3", read(FILE1)); | |
| assertStageOneToThree(FILE1); | |
| } | |
| @Test | |
| public void testCheckoutTheirs() throws Exception { | |
| setupConflictingState(); | |
| git.checkout().setStage(Stage.THEIRS).addPath(FILE1).call(); | |
| assertEquals("Conflicting", read(FILE1)); | |
| assertStageOneToThree(FILE1); | |
| } | |
| @Test | |
| public void testCheckoutFileWithConflict() throws Exception { | |
| setupConflictingState(); | |
| assertEquals('[' + FILE1 + ']', | |
| git.status().call().getConflicting().toString()); | |
| git.checkout().setStartPoint(Constants.HEAD).addPath(FILE1).call(); | |
| assertEquals("3", read(FILE1)); | |
| assertTrue(git.status().call().isClean()); | |
| } | |
| @Test | |
| public void testCheckoutOursWhenNoBase() throws Exception { | |
| String file = "added.txt"; | |
| git.checkout().setCreateBranch(true).setName("side") | |
| .setStartPoint(initialCommit).call(); | |
| writeTrashFile(file, "Added on side"); | |
| git.add().addFilepattern(file).call(); | |
| RevCommit side = git.commit().setMessage("Commit on side").call(); | |
| git.checkout().setName("master").call(); | |
| writeTrashFile(file, "Added on master"); | |
| git.add().addFilepattern(file).call(); | |
| git.commit().setMessage("Commit on master").call(); | |
| git.merge().include(side).call(); | |
| assertEquals(RepositoryState.MERGING, db.getRepositoryState()); | |
| DirCache cache = DirCache.read(db.getIndexFile(), db.getFS()); | |
| assertEquals("Expected add/add file to not have base stage", | |
| DirCacheEntry.STAGE_2, cache.getEntry(file).getStage()); | |
| assertTrue(read(file).startsWith("<<<<<<< HEAD")); | |
| git.checkout().setStage(Stage.OURS).addPath(file).call(); | |
| assertEquals("Added on master", read(file)); | |
| cache = DirCache.read(db.getIndexFile(), db.getFS()); | |
| assertEquals("Expected conflict stages to still exist after checkout", | |
| DirCacheEntry.STAGE_2, cache.getEntry(file).getStage()); | |
| } | |
| @Test(expected = IllegalStateException.class) | |
| public void testStageNotPossibleWithBranch() throws Exception { | |
| git.checkout().setStage(Stage.OURS).setStartPoint("master").call(); | |
| } | |
| private void setupConflictingState() throws Exception { | |
| git.checkout().setCreateBranch(true).setName("conflict") | |
| .setStartPoint(initialCommit).call(); | |
| writeTrashFile(FILE1, "Conflicting"); | |
| RevCommit conflict = git.commit().setAll(true) | |
| .setMessage("Conflicting change").call(); | |
| git.checkout().setName("master").call(); | |
| git.merge().include(conflict).call(); | |
| assertEquals(RepositoryState.MERGING, db.getRepositoryState()); | |
| assertStageOneToThree(FILE1); | |
| } | |
| private void assertStageOneToThree(String name) throws Exception { | |
| DirCache cache = DirCache.read(db.getIndexFile(), db.getFS()); | |
| int i = cache.findEntry(name); | |
| DirCacheEntry stage1 = cache.getEntry(i); | |
| DirCacheEntry stage2 = cache.getEntry(i + 1); | |
| DirCacheEntry stage3 = cache.getEntry(i + 2); | |
| assertEquals(DirCacheEntry.STAGE_1, stage1.getStage()); | |
| assertEquals(DirCacheEntry.STAGE_2, stage2.getStage()); | |
| assertEquals(DirCacheEntry.STAGE_3, stage3.getStage()); | |
| } | |
| } |