diff options
8 files changed, 729 insertions, 69 deletions
diff --git a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF index 5869d76153..b467d2dab6 100644 --- a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF @@ -8,6 +8,8 @@ Bundle-Localization: plugin Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: J2SE-1.5 Import-Package: org.eclipse.jgit.api;version="[3.4.0,3.5.0)", + org.eclipse.jgit.api.errors;version="[3.4.0,3.5.0)", + org.eclipse.jgit.diff;version="[3.4.0,3.5.0)", org.eclipse.jgit.dircache;version="[3.4.0,3.5.0)", org.eclipse.jgit.junit;version="[3.4.0,3.5.0)", org.eclipse.jgit.lib;version="[3.4.0,3.5.0)", diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java index 2c1f59f250..90284736cf 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java @@ -43,15 +43,21 @@ package org.eclipse.jgit.pgm; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertNotNull; import java.io.File; +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.FileUtils; import org.junit.Assert; import org.junit.Test; @@ -150,6 +156,7 @@ public class CheckoutTest extends CLIRepositoryTestCase { * <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. * @@ -181,6 +188,359 @@ public class CheckoutTest extends CLIRepositoryTestCase { assertEquals("Hello world a", read(fileA)); } + /** + * 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 working tree should contain 'a' with FileMode.REGULAR_FILE after the + * checkout. + * + * @throws Exception + */ + @Test + public void fileModeTestMissingThenFolderWithFileInWorkingTree() + throws Exception { + 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()); + + 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>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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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)); + } + static private void assertEquals(Object expected, Object actual) { Assert.assertEquals(expected, actual); } diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ConfigTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ConfigTest.java index 3c62e85502..aefdff185c 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ConfigTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ConfigTest.java @@ -42,15 +42,15 @@ */ package org.eclipse.jgit.pgm; -import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.lib.CLIRepositoryTestCase; +import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.SystemReader; import org.junit.Before; import org.junit.Test; @@ -77,28 +77,12 @@ public class ConfigTest extends CLIRepositoryTestCase { if (isMac) expect.add("core.precomposeunicode=true"); expect.add("core.repositoryformatversion=0"); - if (SystemReader.getInstance().isWindows() && osVersion() < 6 - || javaVersion() < 1.7) { + if (!FS.DETECTED.supportsSymlinks()) expect.add("core.symlinks=false"); - } expect.add(""); // ends with LF (last line empty) - assertArrayEquals("expected default configuration", expect.toArray(), - output); + assertEquals("expected default configuration", + Arrays.asList(expect.toArray()).toString(), + Arrays.asList(output).toString()); } - private static float javaVersion() { - String versionString = System.getProperty("java.version"); - Matcher matcher = Pattern.compile("(\\d+\\.\\d+).*").matcher( - versionString); - matcher.matches(); - return Float.parseFloat(matcher.group(1)); - } - - private static float osVersion() { - String versionString = System.getProperty("os.version"); - Matcher matcher = Pattern.compile("(\\d+\\.\\d+).*").matcher( - versionString); - matcher.matches(); - return Float.parseFloat(matcher.group(1)); - } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java new file mode 100644 index 0000000000..d85fb54720 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2014, Shaul Zorea <shaulzorea@gmail.com> + * 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.api; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.revwalk.RevCommit; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.*; + +public class ArchiveCommandTest extends RepositoryTestCase { + + private static final String UNEXPECTED_ARCHIVE_SIZE = "Unexpected archive size"; + private static final String UNEXPECTED_FILE_CONTENTS = "Unexpected file contents"; + private static final String UNEXPECTED_TREE_CONTENTS = "Unexpected tree contents"; + + private MockFormat format = null; + + @Before + public void setup() { + format = new MockFormat(); + ArchiveCommand.registerFormat(format.SUFFIXES.get(0), format); + } + + @After + public void tearDown() { + ArchiveCommand.unregisterFormat(format.SUFFIXES.get(0)); + } + + @Test + public void archiveHeadAllFiles() throws IOException, GitAPIException { + Git git = new Git(db); + writeTrashFile("file_1.txt", "content_1_1"); + git.add().addFilepattern("file_1.txt").call(); + git.commit().setMessage("create file").call(); + + writeTrashFile("file_1.txt", "content_1_2"); + writeTrashFile("file_2.txt", "content_2_2"); + git.add().addFilepattern(".").call(); + git.commit().setMessage("updated file").call(); + + git.archive().setOutputStream(new MockOutputStream()) + .setFormat(format.SUFFIXES.get(0)) + .setTree(git.getRepository().resolve("HEAD")).call(); + + assertEquals(UNEXPECTED_ARCHIVE_SIZE, 2, format.size()); + assertEquals(UNEXPECTED_FILE_CONTENTS, "content_1_2", format.getByPath("file_1.txt")); + assertEquals(UNEXPECTED_FILE_CONTENTS, "content_2_2", format.getByPath("file_2.txt")); + } + + @Test + public void archiveHeadSpecificPath() throws IOException, GitAPIException { + Git git = new Git(db); + writeTrashFile("file_1.txt", "content_1_1"); + git.add().addFilepattern("file_1.txt").call(); + git.commit().setMessage("create file").call(); + + writeTrashFile("file_1.txt", "content_1_2"); + String expectedFilePath = "some_directory/file_2.txt"; + writeTrashFile(expectedFilePath, "content_2_2"); + git.add().addFilepattern(".").call(); + git.commit().setMessage("updated file").call(); + + git.archive().setOutputStream(new MockOutputStream()) + .setFormat(format.SUFFIXES.get(0)) + .setTree(git.getRepository().resolve("HEAD")) + .setPaths(expectedFilePath).call(); + + assertEquals(UNEXPECTED_ARCHIVE_SIZE, 2, format.size()); + assertEquals(UNEXPECTED_FILE_CONTENTS, "content_2_2", format.getByPath(expectedFilePath)); + assertNull(UNEXPECTED_TREE_CONTENTS, format.getByPath("some_directory")); + } + + @Test + public void archiveByIdSpecificFile() throws IOException, GitAPIException { + Git git = new Git(db); + writeTrashFile("file_1.txt", "content_1_1"); + git.add().addFilepattern("file_1.txt").call(); + RevCommit first = git.commit().setMessage("create file").call(); + + writeTrashFile("file_1.txt", "content_1_2"); + String expectedFilePath = "some_directory/file_2.txt"; + writeTrashFile(expectedFilePath, "content_2_2"); + git.add().addFilepattern(".").call(); + git.commit().setMessage("updated file").call(); + + git.archive().setOutputStream(new MockOutputStream()) + .setFormat(format.SUFFIXES.get(0)).setTree(first) + .setPaths("file_1.txt").call(); + + assertEquals(UNEXPECTED_ARCHIVE_SIZE, 1, format.size()); + assertEquals(UNEXPECTED_FILE_CONTENTS, "content_1_1", format.getByPath("file_1.txt")); + } + + @Test + public void archiveByDirectoryPath() throws GitAPIException, IOException { + Git git = new Git(db); + writeTrashFile("file_0.txt", "content_0_1"); + git.add().addFilepattern("file_0.txt").call(); + git.commit().setMessage("commit_1").call(); + + writeTrashFile("file_0.txt", "content_0_2"); + String expectedFilePath1 = "some_directory/file_1.txt"; + writeTrashFile(expectedFilePath1, "content_1_2"); + String expectedFilePath2 = "some_directory/file_2.txt"; + writeTrashFile(expectedFilePath2, "content_2_2"); + String expectedFilePath3 = "some_directory/nested_directory/file_3.txt"; + writeTrashFile(expectedFilePath3, "content_3_2"); + git.add().addFilepattern(".").call(); + git.commit().setMessage("commit_2").call(); + git.archive().setOutputStream(new MockOutputStream()) + .setFormat(format.SUFFIXES.get(0)) + .setTree(git.getRepository().resolve("HEAD")) + .setPaths("some_directory/").call(); + + assertEquals(UNEXPECTED_ARCHIVE_SIZE, 5, format.size()); + assertEquals(UNEXPECTED_FILE_CONTENTS, "content_1_2", format.getByPath(expectedFilePath1)); + assertEquals(UNEXPECTED_FILE_CONTENTS, "content_2_2", format.getByPath(expectedFilePath2)); + assertEquals(UNEXPECTED_FILE_CONTENTS, "content_3_2", format.getByPath(expectedFilePath3)); + assertNull(UNEXPECTED_TREE_CONTENTS, format.getByPath("some_directory")); + assertNull(UNEXPECTED_TREE_CONTENTS, format.getByPath("some_directory/nested_directory")); + } + + private class MockFormat implements ArchiveCommand.Format<MockOutputStream> { + + private Map<String, String> entries = new HashMap<String, String>(); + + private int size() { + return entries.size(); + } + + private String getByPath(String path) { + return entries.get(path); + } + + private final List<String> SUFFIXES = Collections + .unmodifiableList(Arrays.asList(".mck")); + + public MockOutputStream createArchiveOutputStream(OutputStream s) + throws IOException { + return new MockOutputStream(); + } + + public void putEntry(MockOutputStream out, String path, FileMode mode, ObjectLoader loader) { + String content = mode != FileMode.TREE ? new String(loader.getBytes()) : null; + entries.put(path, content); + } + + public Iterable<String> suffixes() { + return SUFFIXES; + } + } + + private class MockOutputStream extends OutputStream { + + @Override + public void write(int b) throws IOException { + // Do nothing. for testing purposes. + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java index 98ec706bec..afbad6ab2c 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java @@ -619,7 +619,7 @@ public class DirCacheCheckoutTest extends RepositoryTestCase { @Test public void testDirectoryFileConflicts_9() throws Exception { // 9 - doit(mk("DF"), mkmap("DF", "QP"), mk("DF/DF")); + doit(mkmap("DF", "QP"), mkmap("DF", "QP"), mkmap("DF/DF", "DF/DF")); assertRemoved("DF/DF"); assertUpdated("DF"); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java index 70ab73015f..88bc2aee7d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java @@ -46,6 +46,9 @@ import java.io.Closeable; import java.io.IOException; import java.io.OutputStream; import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -60,6 +63,7 @@ import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.PathFilterGroup; /** * Create an archive of files from a named tree. @@ -324,6 +328,7 @@ public class ArchiveCommand extends GitCommand<OutputStream> { private ObjectId tree; private String prefix; private String format; + private List<String> paths = new ArrayList<String>(); /** Filename suffix, for automatically choosing a format. */ private String suffix; @@ -347,6 +352,9 @@ public class ArchiveCommand extends GitCommand<OutputStream> { final RevWalk rw = new RevWalk(walk.getObjectReader()); walk.reset(rw.parseTree(tree)); + if (!paths.isEmpty()) + walk.setFilter(PathFilterGroup.createFromStrings(paths)); + while (walk.next()) { final String name = pfx + walk.getPathString(); FileMode mode = walk.getFileMode(0); @@ -462,4 +470,21 @@ public class ArchiveCommand extends GitCommand<OutputStream> { this.format = fmt; return this; } + + /** + * Set an optional parameter path. without an optional path parameter, all + * files and subdirectories of the current working directory are included in + * the archive. If one or more paths are specified, only these are included. + * @param paths + * file names (e.g <code>file1.c</code>) or directory names (e.g. + * <code>dir</code> to add <code>dir/file1</code> and + * <code>dir/file2</code>) can also be given to add all files in + * the directory, recursively. Fileglobs (e.g. *.c) are not yet + * supported. + * @return this + */ + public ArchiveCommand setPaths(String... paths) { + this.paths = Arrays.asList(paths); + return this; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java index fdf8c052fe..80dda8eb83 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java @@ -562,7 +562,7 @@ public class DirCacheCheckout { * 6b D F F N N N N Conflict * 7 F D F Y Y N N Update * 8 F D F N Y N N Conflict - * 9 F D F Y N N N Update + * 9 F D F N N N Conflict * 10 F D D N N Y Keep * 11 F D D N N N Conflict * 12 F F D Y N Y N Update @@ -610,7 +610,7 @@ public class DirCacheCheckout { // switch processes all relevant cases. switch (ffMask) { case 0xDDF: // 1 2 - if (isModified(name)) { + if (f != null && isModifiedSubtree_IndexWorkingtree(name)) { conflict(name, dce, h, m); // 1 } else { update(name, mId, mMode); // 2 @@ -647,32 +647,29 @@ public class DirCacheCheckout { break; case 0xFDF: // 7 8 9 if (equalIdAndMode(hId, hMode, mId, mMode)) { - if (isModified(name)) + if (isModifiedSubtree_IndexWorkingtree(name)) conflict(name, dce, h, m); // 8 else update(name, mId, mMode); // 7 - } else if (!isModified(name)) - update(name, mId, mMode); // 9 - else - // To be confirmed - this case is not in the table. - conflict(name, dce, h, m); + } else + conflict(name, dce, h, m); // 9 break; case 0xFD0: // keep without a rule keep(dce); break; case 0xFFD: // 12 13 14 if (equalIdAndMode(hId, hMode, iId, iMode)) - if (f == null - || f.isModified(dce, true, + if (f != null + && f.isModified(dce, true, this.walk.getObjectReader())) - conflict(name, dce, h, m); + conflict(name, dce, h, m); // 13 else - remove(name); + remove(name); // 12 else - conflict(name, dce, h, m); + conflict(name, dce, h, m); // 14 break; case 0x0DF: // 16 17 - if (!isModified(name)) + if (!isModifiedSubtree_IndexWorkingtree(name)) update(name, mId, mMode); else conflict(name, dce, h, m); @@ -684,12 +681,14 @@ public class DirCacheCheckout { } // if we have no file at all then there is nothing to do - if ((ffMask & 0x222) == 0) + if ((ffMask & 0x222) == 0 + && (f == null || FileMode.TREE.equals(f.getEntryFileMode()))) return; if ((ffMask == 0x00F) && f != null && FileMode.TREE.equals(f.getEntryFileMode())) { // File/Directory conflict case #20 conflict(name, null, h, m); + return; } if (i == null) { @@ -768,7 +767,9 @@ public class DirCacheCheckout { * </pre> */ - if (m == null || equalIdAndMode(mId, mMode, iId, iMode)) { + if (m == null + || !isModified_IndexTree(name, iId, iMode, mId, mMode, + mergeCommitTree)) { // Merge contains nothing or the same as Index // Nothing in Head // Something in Index @@ -824,7 +825,7 @@ public class DirCacheCheckout { * clean I==H I==M H M Result * ----------------------------------------------------- * 10 yes yes N/A exists nothing remove path from index - * 11 no yes N/A exists nothing fail + * 11 no yes N/A exists nothing keep file * 12 yes no N/A exists nothing fail * 13 no no N/A exists nothing fail * </pre> @@ -841,23 +842,31 @@ public class DirCacheCheckout { // Something different from a submodule in Index // Nothing in Merge // Something in Head - if (equalIdAndMode(hId, hMode, iId, iMode)) { + if (!isModified_IndexTree(name, iId, iMode, hId, hMode, + headCommitTree)) { // Index contains the same as Head // Something different from a submodule in Index // Nothing in Merge // Something in Head - if (f == null - || f.isModified(dce, true, - this.walk.getObjectReader())) + if (f != null + && f.isModified(dce, true, + this.walk.getObjectReader())) { // file is dirty // Index contains the same as Head // Something different from a submodule in Index // Nothing in Merge // Something in Head - // -> file is dirty but is should be removed. That's - // a conflict - conflict(name, dce, h, m); - else + + if (!FileMode.TREE.equals(f.getEntryFileMode()) + && FileMode.TREE.equals(iMode)) + // The workingtree contains a file and the index semantically contains a folder. + // Git considers the workingtree file as untracked. Just keep the untracked file. + return; + else + // -> file is dirty and tracked but is should be + // removed. That's a conflict + conflict(name, dce, h, m); + } else // file doesn't exist or is clean // Index contains the same as Head // Something different from a submodule in Index @@ -880,8 +889,10 @@ public class DirCacheCheckout { // Something in Head // Something in Index if (!equalIdAndMode(hId, hMode, mId, mMode) - && !equalIdAndMode(hId, hMode, iId, iMode) - && !equalIdAndMode(mId, mMode, iId, iMode)) + && isModified_IndexTree(name, iId, iMode, hId, hMode, + headCommitTree) + && isModified_IndexTree(name, iId, iMode, mId, mMode, + mergeCommitTree)) // All three contents in Head, Merge, Index differ from each // other // -> All contents differ. Report a conflict. @@ -893,8 +904,10 @@ public class DirCacheCheckout { // Something in Head // Something in Index - if (equalIdAndMode(hId, hMode, iId, iMode) - && !equalIdAndMode(mId, mMode, iId, iMode)) { + if (!isModified_IndexTree(name, iId, iMode, hId, hMode, + headCommitTree) + && isModified_IndexTree(name, iId, iMode, mId, mMode, + mergeCommitTree)) { // Head contains the same as Index. Merge differs // Something in Merge @@ -1036,25 +1049,88 @@ public class DirCacheCheckout { } } - private boolean isModified(String path) throws CorruptObjectException, IOException { + /** + * Checks whether the subtree starting at a given path differs between Index and + * workingtree. + * + * @param path + * @return true if the subtrees differ + * @throws CorruptObjectException + * @throws IOException + */ + private boolean isModifiedSubtree_IndexWorkingtree(String path) + throws CorruptObjectException, IOException { + NameConflictTreeWalk tw = new NameConflictTreeWalk(repo); + try { + tw.addTree(new DirCacheIterator(dc)); + tw.addTree(new FileTreeIterator(repo)); + tw.setRecursive(true); + tw.setFilter(PathFilter.create(path)); + DirCacheIterator dcIt; + WorkingTreeIterator wtIt; + while (tw.next()) { + dcIt = tw.getTree(0, DirCacheIterator.class); + wtIt = tw.getTree(1, WorkingTreeIterator.class); + if (dcIt == null || wtIt == null) + return true; + if (wtIt.isModified(dcIt.getDirCacheEntry(), true, + this.walk.getObjectReader())) { + return true; + } + } + return false; + } finally { + tw.release(); + } + } + + private boolean isModified_IndexTree(String path, ObjectId iId, + FileMode iMode, ObjectId tId, FileMode tMode, ObjectId rootTree) + throws CorruptObjectException, IOException { + if (iMode != tMode) + return true; + if (FileMode.TREE.equals(iMode) + && (iId == null || ObjectId.zeroId().equals(iId))) + return isModifiedSubtree_IndexTree(path, rootTree); + else + return !equalIdAndMode(iId, iMode, tId, tMode); + } + + /** + * Checks whether the subtree starting at a given path differs between Index and + * some tree. + * + * @param path + * @param tree + * the tree to compare + * @return true if the subtrees differ + * @throws CorruptObjectException + * @throws IOException + */ + private boolean isModifiedSubtree_IndexTree(String path, ObjectId tree) + throws CorruptObjectException, IOException { NameConflictTreeWalk tw = new NameConflictTreeWalk(repo); - tw.addTree(new DirCacheIterator(dc)); - tw.addTree(new FileTreeIterator(repo)); - tw.setRecursive(true); - tw.setFilter(PathFilter.create(path)); - DirCacheIterator dcIt; - WorkingTreeIterator wtIt; - while(tw.next()) { - dcIt = tw.getTree(0, DirCacheIterator.class); - wtIt = tw.getTree(1, WorkingTreeIterator.class); - if (dcIt == null || wtIt == null) - return true; - if (wtIt.isModified(dcIt.getDirCacheEntry(), true, - this.walk.getObjectReader())) { - return true; + try { + tw.addTree(new DirCacheIterator(dc)); + tw.addTree(tree); + tw.setRecursive(true); + tw.setFilter(PathFilter.create(path)); + while (tw.next()) { + AbstractTreeIterator dcIt = tw.getTree(0, + DirCacheIterator.class); + AbstractTreeIterator treeIt = tw.getTree(1, + AbstractTreeIterator.class); + if (dcIt == null || treeIt == null) + return true; + if (dcIt.getEntryRawMode() != treeIt.getEntryRawMode()) + return true; + if (!dcIt.getEntryObjectId().equals(treeIt.getEntryObjectId())) + return true; } + return false; + } finally { + tw.release(); } - return false; } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java index 964869bb87..9eb4285557 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java @@ -832,6 +832,8 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { */ public boolean isModified(DirCacheEntry entry, boolean forceContentCheck, ObjectReader reader) throws IOException { + if (entry == null) + return !FileMode.MISSING.equals(getEntryFileMode()); MetadataDiff diff = compareMetadata(entry); switch (diff) { case DIFFER_BY_TIMESTAMP: |