]> source.dussan.org Git - jgit.git/commitdiff
Implement DIR_NO_GITLINKS 82/69882/1
authorPreben Ingvaldsen <preben@puppetlabs.com>
Tue, 1 Mar 2016 19:37:54 +0000 (11:37 -0800)
committerChristian Halstrick <christian.halstrick@sap.com>
Tue, 5 Apr 2016 08:43:40 +0000 (10:43 +0200)
Implement the DIR_NO_GITLINKS setting with the same functionality
it provides in cGit.

Bug: 436200
Change-Id: I8304e42df2d7e8d7925f515805e075a92ff6ce28
Signed-off-by: Preben Ingvaldsen <preben@puppetlabs.com>
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java

index 4fefdfddab6dbf59c0166b83986f4cc62243b4d3..126ca5cdd1119039cb1a745633c34550410a9ccf 100644 (file)
@@ -53,6 +53,7 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.Set;
 
 import org.eclipse.jgit.api.errors.FilterFailedException;
 import org.eclipse.jgit.api.errors.GitAPIException;
@@ -62,14 +63,11 @@ import org.eclipse.jgit.dircache.DirCacheBuilder;
 import org.eclipse.jgit.dircache.DirCacheEntry;
 import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.junit.RepositoryTestCase;
-import org.eclipse.jgit.lib.ConfigConstants;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.FileMode;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.lib.*;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
 import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.WorkingTreeOptions;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FileUtils;
 import org.junit.Test;
@@ -1002,6 +1000,91 @@ public class AddCommandTest extends RepositoryTestCase {
                assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0));
        }
 
+       @Test
+       public void testAddGitlink() throws Exception {
+               createNestedRepo("git-link-dir");
+               try (Git git = new Git(db)) {
+                       git.add().addFilepattern("git-link-dir").call();
+
+                       assertEquals(
+                                       "[git-link-dir, mode:160000]",
+                                       indexState(0));
+                       Set<String> untrackedFiles = git.status().call().getUntracked();
+                       assert (untrackedFiles.isEmpty());
+               }
+
+       }
+
+       @Test
+       public void testAddSubrepoWithDirNoGitlinks() throws Exception {
+               createNestedRepo("nested-repo");
+
+               // Set DIR_NO_GITLINKS
+               StoredConfig config = db.getConfig();
+               config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+                               ConfigConstants.CONFIG_KEY_DIRNOGITLINKS, true);
+               config.save();
+
+               assert (db.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks());
+
+               try (Git git = new Git(db)) {
+                       git.add().addFilepattern("nested-repo").call();
+
+                       assertEquals(
+                                       "[nested-repo/README1.md, mode:100644]" +
+                                                       "[nested-repo/README2.md, mode:100644]",
+                                       indexState(0));
+               }
+
+               // Turn off DIR_NO_GITLINKS, ensure nested-repo is still treated as
+               // a normal directory
+               // Set DIR_NO_GITLINKS
+               config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+                               ConfigConstants.CONFIG_KEY_DIRNOGITLINKS, false);
+               config.save();
+
+               writeTrashFile("nested-repo", "README3.md", "content");
+
+               try (Git git = new Git(db)) {
+                       git.add().addFilepattern("nested-repo").call();
+
+                       assertEquals(
+                                       "[nested-repo/README1.md, mode:100644]" +
+                                                       "[nested-repo/README2.md, mode:100644]" +
+                                                       "[nested-repo/README3.md, mode:100644]",
+                                       indexState(0));
+               }
+       }
+
+       @Test
+       public void testAddGitlinkDoesNotChange() throws Exception {
+               createNestedRepo("nested-repo");
+
+               try (Git git = new Git(db)) {
+                       git.add().addFilepattern("nested-repo").call();
+
+                       assertEquals(
+                                       "[nested-repo, mode:160000]",
+                                       indexState(0));
+               }
+
+               // Set DIR_NO_GITLINKS
+               StoredConfig config = db.getConfig();
+               config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+                               ConfigConstants.CONFIG_KEY_DIRNOGITLINKS, true);
+               config.save();
+
+               assert (db.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks());
+
+               try (Git git = new Git(db)) {
+                       git.add().addFilepattern("nested-repo").call();
+
+                       assertEquals(
+                                       "[nested-repo, mode:160000]",
+                                       indexState(0));
+               }
+       }
+
        private static DirCacheEntry addEntryToBuilder(String path, File file,
                        ObjectInserter newObjectInserter, DirCacheBuilder builder, int stage)
                        throws IOException {
@@ -1029,4 +1112,25 @@ public class AddCommandTest extends RepositoryTestCase {
                        throw new IOException("could not commit");
        }
 
+       private void createNestedRepo(String path) throws IOException {
+               File gitLinkDir = new File(db.getWorkTree(), path);
+               FileUtils.mkdir(gitLinkDir);
+
+               FileRepositoryBuilder nestedBuilder = new FileRepositoryBuilder();
+               nestedBuilder.setWorkTree(gitLinkDir);
+
+               Repository nestedRepo = nestedBuilder.build();
+               nestedRepo.create();
+
+               writeTrashFile(path, "README1.md", "content");
+               writeTrashFile(path, "README2.md", "content");
+
+               // Commit these changes in the subrepo
+               try (Git git = new Git(nestedRepo)) {
+                       git.add().addFilepattern(".").call();
+                       git.commit().setMessage("subrepo commit").call();
+               } catch (GitAPIException e) {
+                       throw new RuntimeException(e);
+               }
+       }
 }
index f80e117cf07a885f06553f6d4b2cbc3c8a945a8d..bf1d0e6d234c778431a3b1d70b64b90bab9074c5 100644 (file)
@@ -64,7 +64,12 @@ import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.junit.RepositoryTestCase;
-import org.eclipse.jgit.lib.*;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
 import org.eclipse.jgit.treewalk.WorkingTreeIterator.MetadataDiff;
@@ -279,6 +284,37 @@ public class FileTreeIteratorTest extends RepositoryTestCase {
                }
        }
 
+       @Test
+       public void testTreewalkEnterSubtree() throws Exception {
+               try (Git git = new Git(db)) {
+                       writeTrashFile("b/c", "b/c");
+                       writeTrashFile("z/.git", "gitdir: /tmp/somewhere");
+                       git.add().addFilepattern(".").call();
+                       git.rm().addFilepattern("a,").addFilepattern("a,b")
+                                       .addFilepattern("a0b").call();
+                       assertEquals("[a/b, mode:100644][b/c, mode:100644][z, mode:160000]",
+                                       indexState(0));
+                       FileUtils.delete(new File(db.getWorkTree(), "b"),
+                                       FileUtils.RECURSIVE);
+
+                       TreeWalk tw = new TreeWalk(db);
+                       tw.addTree(new DirCacheIterator(db.readDirCache()));
+                       tw.addTree(new FileTreeIterator(db));
+                       assertTrue(tw.next());
+                       assertEquals("a", tw.getPathString());
+                       tw.enterSubtree();
+                       tw.next();
+                       assertEquals("a/b", tw.getPathString());
+                       tw.next();
+                       assertEquals("b", tw.getPathString());
+                       tw.enterSubtree();
+                       tw.next();
+                       assertEquals("b/c", tw.getPathString());
+                       assertNotNull(tw.getTree(0, AbstractTreeIterator.class));
+                       assertNotNull(tw.getTree(EmptyTreeIterator.class));
+               }
+       }
+
        @Test
        public void testIsModifiedSymlinkAsFile() throws Exception {
                writeTrashFile("symlink", "content");
@@ -345,7 +381,7 @@ public class FileTreeIteratorTest extends RepositoryTestCase {
                        DirCache cache = db.lockDirCache();
                        DirCacheEditor editor = cache.editor();
                        editor.add(new PathEdit(path) {
-       
+
                                public void apply(DirCacheEntry ent) {
                                        ent.setFileMode(FileMode.GITLINK);
                                        ent.setObjectId(id);
@@ -362,7 +398,7 @@ public class FileTreeIteratorTest extends RepositoryTestCase {
                        walk.addTree(indexIter);
                        walk.addTree(workTreeIter);
                        walk.setFilter(PathFilter.create(path));
-       
+
                        assertTrue(walk.next());
                        assertTrue(indexIter.idEqual(workTreeIter));
                }
index 3b94f16f1aa8f57dbcc1092789f54d679f721a8f..1f37833a414e433e10196af4974541777fe3a50c 100644 (file)
@@ -45,6 +45,7 @@ package org.eclipse.jgit.api;
 
 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 import static org.eclipse.jgit.lib.FileMode.GITLINK;
+import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK;
 import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
 
 import java.io.IOException;
@@ -201,7 +202,10 @@ public class AddCommand extends GitCommand<DirCache> {
                                        continue;
                                }
 
-                               if (f.getEntryRawMode() == TYPE_TREE) {
+                               if ((f.getEntryRawMode() == TYPE_TREE
+                                               && f.getIndexFileMode(c) != FileMode.GITLINK) ||
+                                               (f.getEntryRawMode() == TYPE_GITLINK
+                                                               && f.getIndexFileMode(c) == FileMode.TREE)) {
                                        // Index entry exists and is symlink, gitlink or file,
                                        // otherwise the tree would have been entered above.
                                        // Replace the index entry by diving into tree of files.
index 054d1930170ce6f813cc45de37315020d670e84e..abc8c269ff4da7f5e8da3c5c066d706c84fcd8ca 100644 (file)
@@ -235,6 +235,12 @@ public class ConfigConstants {
         */
        public static final String CONFIG_KEY_HIDEDOTFILES = "hidedotfiles";
 
+       /**
+        * The "dirnogitlinks" key
+        * @since 4.3
+        */
+       public static final String CONFIG_KEY_DIRNOGITLINKS = "dirNoGitLinks";
+
        /** The "precomposeunicode" key */
        public static final String CONFIG_KEY_PRECOMPOSEUNICODE = "precomposeunicode";
 
index dc835e4f36cc6fa56f79febef7d7506d5dd4978c..07fc829db4d6bf3a6306e3cd2968f88d9eeb6ca0 100644 (file)
@@ -729,4 +729,13 @@ public abstract class AbstractTreeIterator {
        public String toString() {
                return getClass().getSimpleName() + "[" + getEntryPathString() + "]"; //$NON-NLS-1$
        }
+
+       /**
+        * @return whether or not this Iterator is iterating through the Work Tree
+        *
+        * @since 4.3
+        */
+       public boolean isWorkTree() {
+               return false;
+       }
 }
index eb4f1a87d070c425929e393d9e4334e8342240eb..db81e1af9b01698b25a66473b7f421c5bdbfd19c 100644 (file)
@@ -94,7 +94,10 @@ public class FileTreeIterator extends WorkingTreeIterator {
         *            the repository whose working tree will be scanned.
         */
        public FileTreeIterator(Repository repo) {
-               this(repo, DefaultFileModeStrategy.INSTANCE);
+               this(repo,
+                               repo.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks() ?
+                                               NoGitlinksStrategy.INSTANCE :
+                                               DefaultFileModeStrategy.INSTANCE);
        }
 
        /**
@@ -291,6 +294,35 @@ public class FileTreeIterator extends WorkingTreeIterator {
                }
        }
 
+       /**
+        * A FileModeStrategy that implements native git's DIR_NO_GITLINKS
+        * behavior. This is the same as the default FileModeStrategy, except
+        * all directories will be treated as directories regardless of whether
+        * or not they contain a .git directory or file.
+        *
+        * @since 4.3
+        */
+       static public class NoGitlinksStrategy implements FileModeStrategy {
+
+               /**
+                * a singleton instance of the default FileModeStrategy
+                */
+               public final static NoGitlinksStrategy INSTANCE = new NoGitlinksStrategy();
+
+               @Override
+               public FileMode getMode(File f, FS.Attributes attributes) {
+                       if (attributes.isSymbolicLink()) {
+                               return FileMode.SYMLINK;
+                       } else if (attributes.isDirectory()) {
+                               return FileMode.TREE;
+                       } else if (attributes.isExecutable()) {
+                               return FileMode.EXECUTABLE_FILE;
+                       } else {
+                               return FileMode.REGULAR_FILE;
+                       }
+               }
+       }
+
 
        /**
         * Wrapper for a standard Java IO file
index aecbac11ea4d5e985bda28a2700bc21a7fbde782..501d6767350db364ffcc21797bca8ed1d73a1f0a 100644 (file)
@@ -1187,7 +1187,12 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
                for (int i = 0; i < trees.length; i++) {
                        final AbstractTreeIterator t = trees[i];
                        final AbstractTreeIterator n;
-                       if (t.matches == ch && !t.eof() && FileMode.TREE.equals(t.mode))
+                       // If we find a GITLINK when attempting to enter a subtree, then the
+                       // GITLINK must exist as a TREE in the index, meaning the working tree
+                       // entry should be treated as a TREE
+                       if (t.matches == ch && !t.eof() &&
+                                       (FileMode.TREE.equals(t.mode)
+                                                       || (FileMode.GITLINK.equals(t.mode) && t.isWorkTree())))
                                n = t.createSubtreeIterator(reader, idBuffer);
                        else
                                n = t.createEmptyTreeIterator();
index ca8f9aa3732bb75a1d90cc8e2bad0bca9e8ebbfa..39176c621eaceb6c8e6f909cba967cf1ee385170 100644 (file)
@@ -264,7 +264,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
                        // the cached index information for the path.
                        //
                        DirCacheIterator i = state.walk.getTree(state.dirCacheTree,
-                                       DirCacheIterator.class);
+                                                       DirCacheIterator.class);
                        if (i != null) {
                                DirCacheEntry ent = i.getDirCacheEntry();
                                if (ent != null && compareMetadata(ent) == MetadataDiff.EQUAL) {
@@ -289,6 +289,11 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
                return zeroid;
        }
 
+       @Override
+       public boolean isWorkTree() {
+               return true;
+       }
+
        /**
         * Get submodule id for given entry.
         *
@@ -916,17 +921,31 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
         */
        public FileMode getIndexFileMode(final DirCacheIterator indexIter) {
                final FileMode wtMode = getEntryFileMode();
-               if (indexIter == null)
-                       return wtMode;
-               if (getOptions().isFileMode())
+               if (indexIter == null) {
                        return wtMode;
+               }
                final FileMode iMode = indexIter.getEntryFileMode();
-               if (FileMode.REGULAR_FILE == wtMode
-                               && FileMode.EXECUTABLE_FILE == iMode)
+               if (getOptions().isFileMode() && iMode != FileMode.GITLINK && iMode != FileMode.TREE) {
+                       return wtMode;
+               }
+               if (!getOptions().isFileMode()) {
+                       if (FileMode.REGULAR_FILE == wtMode
+                                       && FileMode.EXECUTABLE_FILE == iMode) {
+                               return iMode;
+                       }
+                       if (FileMode.EXECUTABLE_FILE == wtMode
+                                       && FileMode.REGULAR_FILE == iMode) {
+                               return iMode;
+                       }
+               }
+               if (FileMode.GITLINK == iMode
+                               && FileMode.TREE == wtMode) {
                        return iMode;
-               if (FileMode.EXECUTABLE_FILE == wtMode
-                               && FileMode.REGULAR_FILE == iMode)
+               }
+               if (FileMode.TREE == iMode
+                               && FileMode.GITLINK == wtMode) {
                        return iMode;
+               }
                return wtMode;
        }
 
index a8990b1e951392d1ac8f60809c5ede4009bc82e1..dea07c1973bd6602762dd929ae2e1c67093d2f45 100644 (file)
@@ -73,6 +73,8 @@ public class WorkingTreeOptions {
 
        private final HideDotFiles hideDotFiles;
 
+       private final boolean dirNoGitLinks;
+
        private WorkingTreeOptions(final Config rc) {
                fileMode = rc.getBoolean(ConfigConstants.CONFIG_CORE_SECTION,
                                ConfigConstants.CONFIG_KEY_FILEMODE, true);
@@ -87,6 +89,9 @@ public class WorkingTreeOptions {
                hideDotFiles = rc.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
                                ConfigConstants.CONFIG_KEY_HIDEDOTFILES,
                                HideDotFiles.DOTGITONLY);
+               dirNoGitLinks = rc.getBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+                               ConfigConstants.CONFIG_KEY_DIRNOGITLINKS,
+                               false);
        }
 
        /** @return true if the execute bit on working files should be trusted. */
@@ -131,4 +136,12 @@ public class WorkingTreeOptions {
        public HideDotFiles getHideDotFiles() {
                return hideDotFiles;
        }
+
+       /**
+        * @return whether or not we treat nested repos as directories.
+        *                 If true, folders containing .git entries will not be
+        *                 treated as gitlinks.
+        * @since 4.3
+        */
+       public boolean isDirNoGitLinks() { return dirNoGitLinks; }
 }