]> source.dussan.org Git - jgit.git/commitdiff
Default for global (user) git ignore file 60/202560/2
authorThomas Wolf <twolf@apache.org>
Thu, 15 Jun 2023 20:08:06 +0000 (22:08 +0200)
committerThomas Wolf <twolf@apache.org>
Mon, 19 Jun 2023 06:19:29 +0000 (08:19 +0200)
C git has a default for git config core.excludesfile: "Its default
value is $XDG_CONFIG_HOME/git/ignore. If $XDG_CONFIG_HOME is either
not set or empty, $HOME/.config/git/ignore is used instead." [1]

Implement this in the WorkingTreeIterator$RootIgnoreNode.

To make this testable, mock the "user.home" directory for all JGit
tests, otherwise tests might pick up a real user's git ignore file.
Also ensure that JGit code always reads "user.home" via the
SystemReader.

Add tests for both locations.

[1] https://git-scm.com/docs/gitignore#_description

Bug: 436127
Change-Id: Ie510259320286c3c13a6464a37da1bd9ca1e373a
Signed-off-by: Thomas Wolf <twolf@apache.org>
org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestHarness.java
org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/CommitTemplateConfigTest.java
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java

index 6fa30d7e1ec326c1ee63b032b8cdb6449658ca21..b183b2260365cd48eeef009e6aa654d06694bffa 100644 (file)
@@ -89,8 +89,6 @@ public abstract class SshTestHarness extends RepositoryTestCase {
 
        protected File knownHosts;
 
-       private File homeDir;
-
        @Override
        public void setUp() throws Exception {
                super.setUp();
@@ -99,13 +97,8 @@ public abstract class SshTestHarness extends RepositoryTestCase {
                        git.add().addFilepattern("file.txt").call();
                        git.commit().setMessage("Initial commit").call();
                }
-               mockSystemReader.setProperty("user.home",
-                               getTemporaryDirectory().getAbsolutePath());
-               mockSystemReader.setProperty("HOME",
-                               getTemporaryDirectory().getAbsolutePath());
-               homeDir = FS.DETECTED.userHome();
-               FS.DETECTED.setUserHome(getTemporaryDirectory().getAbsoluteFile());
-               sshDir = new File(getTemporaryDirectory(), ".ssh");
+               // The home directory is mocked here
+               sshDir = new File(FS.DETECTED.userHome(), ".ssh");
                assertTrue(sshDir.mkdir());
                File serverDir = new File(getTemporaryDirectory(), "srv");
                assertTrue(serverDir.mkdir());
@@ -236,7 +229,6 @@ public abstract class SshTestHarness extends RepositoryTestCase {
                        server.stop();
                        server = null;
                }
-               FS.DETECTED.setUserHome(homeDir);
                SshSessionFactory.setInstance(null);
                factory = null;
        }
index 0945327ab357c05597bdf3dbc95450890e16ab36..f816158b1008c670b780816ddd5cd50a52ec7111 100644 (file)
@@ -85,6 +85,8 @@ public abstract class LocalDiskRepositoryTestCase {
        private final Set<Repository> toClose = new HashSet<>();
        private File tmp;
 
+       private File homeDir;
+
        /**
         * The current test name.
         *
@@ -119,6 +121,14 @@ public abstract class LocalDiskRepositoryTestCase {
                mockSystemReader = new MockSystemReader();
                SystemReader.setInstance(mockSystemReader);
 
+               // Mock the home directory. We don't want to pick up the real user's git
+               // config, or global git ignore.
+               // XDG_CONFIG_HOME isn't set in the MockSystemReader.
+               mockSystemReader.setProperty("user.home", tmp.getAbsolutePath());
+               mockSystemReader.setProperty("HOME", tmp.getAbsolutePath());
+               homeDir = FS.DETECTED.userHome();
+               FS.DETECTED.setUserHome(tmp.getAbsoluteFile());
+
                // Measure timer resolution before the test to avoid time critical tests
                // are affected by time needed for measurement.
                // The MockSystemReader must be configured first since we need to use
@@ -195,21 +205,25 @@ public abstract class LocalDiskRepositoryTestCase {
        @After
        public void tearDown() throws Exception {
                RepositoryCache.clear();
-               for (Repository r : toClose)
+               for (Repository r : toClose) {
                        r.close();
+               }
                toClose.clear();
 
                // Since memory mapping is controlled by the GC we need to
                // tell it this is a good time to clean up and unlock
                // memory mapped files.
                //
-               if (useMMAP)
+               if (useMMAP) {
                        System.gc();
-               if (tmp != null)
+               }
+               FS.DETECTED.setUserHome(homeDir);
+               if (tmp != null) {
                        recursiveDelete(tmp, false, true);
-               if (tmp != null && !tmp.exists())
+               }
+               if (tmp != null && !tmp.exists()) {
                        CleanupThread.removed(tmp);
-
+               }
                SystemReader.setInstance(null);
        }
 
index 05a953e081376765a2d03ea3f492e872135e6c23..ab08c997969bd12698cc11e0069e0f1e8ca47d13 100644 (file)
@@ -20,14 +20,18 @@ import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
 
+import org.eclipse.jgit.junit.MockSystemReader;
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.treewalk.FileTreeIterator;
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.treewalk.WorkingTreeIterator;
+import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FileUtils;
 import org.eclipse.jgit.util.SystemReader;
 import org.junit.After;
@@ -697,6 +701,110 @@ public class IgnoreNodeTest extends RepositoryTestCase {
                endWalk();
        }
 
+       @Test
+       public void testUserGitIgnoreFound() throws IOException {
+               File homeDir = FS.DETECTED.userHome();
+               Path userIgnore = homeDir.toPath().resolve(".config").resolve("git")
+                               .resolve("ignore");
+               Files.createDirectories(userIgnore.getParent());
+               Files.writeString(userIgnore, "x");
+               try {
+                       writeTrashFile(".foo", "");
+                       writeTrashFile("a/x/file", "");
+                       writeTrashFile("b/x", "");
+                       writeTrashFile("x/file", "");
+
+                       beginWalk();
+                       assertEntry(F, tracked, ".foo");
+                       assertEntry(D, tracked, "a");
+                       assertEntry(D, ignored, "a/x");
+                       assertEntry(F, ignored, "a/x/file");
+                       assertEntry(D, tracked, "b");
+                       assertEntry(F, ignored, "b/x");
+                       assertEntry(D, ignored, "x");
+                       assertEntry(F, ignored, "x/file");
+                       endWalk();
+               } finally {
+                       Files.deleteIfExists(userIgnore);
+               }
+       }
+
+       @Test
+       public void testXdgIgnoreFound() throws IOException {
+               File tmp = getTemporaryDirectory();
+               Path xdg = tmp.toPath().resolve("xdg");
+               Path userIgnore = xdg.resolve("git").resolve("ignore");
+               Files.createDirectories(userIgnore.getParent());
+               Files.writeString(userIgnore, "x");
+               SystemReader system = SystemReader.getInstance();
+               assertTrue(system instanceof MockSystemReader);
+               ((MockSystemReader) system).setProperty("XDG_CONFIG_HOME",
+                               xdg.toAbsolutePath().toString());
+               // Also create the one in the home directory -- it should not be active
+               File homeDir = FS.DETECTED.userHome();
+               Path userIgnore2 = homeDir.toPath().resolve(".config").resolve("git")
+                               .resolve("ignore");
+               Files.createDirectories(userIgnore2.getParent());
+               Files.writeString(userIgnore2, "a");
+               try {
+                       writeTrashFile(".foo", "");
+                       writeTrashFile("a/x/file", "");
+                       writeTrashFile("b/x", "");
+                       writeTrashFile("x/file", "");
+
+                       beginWalk();
+                       assertEntry(F, tracked, ".foo");
+                       assertEntry(D, tracked, "a");
+                       assertEntry(D, ignored, "a/x");
+                       assertEntry(F, ignored, "a/x/file");
+                       assertEntry(D, tracked, "b");
+                       assertEntry(F, ignored, "b/x");
+                       assertEntry(D, ignored, "x");
+                       assertEntry(F, ignored, "x/file");
+                       endWalk();
+               } finally {
+                       ((MockSystemReader) system).setProperty("XDG_CONFIG_HOME", null);
+                       Files.deleteIfExists(userIgnore2);
+               }
+       }
+
+       @Test
+       public void testXdgWrong() throws IOException {
+               File tmp = getTemporaryDirectory();
+               Path xdg = tmp.toPath().resolve("xdg");
+               SystemReader system = SystemReader.getInstance();
+               assertTrue(system instanceof MockSystemReader);
+               // Valid value, but the directory doesn't exist
+               ((MockSystemReader) system).setProperty("XDG_CONFIG_HOME",
+                               xdg.toAbsolutePath().toString());
+               // Also create the one in the home directory -- it should not be active
+               File homeDir = FS.DETECTED.userHome();
+               Path userIgnore2 = homeDir.toPath().resolve(".config").resolve("git")
+                               .resolve("ignore");
+               Files.createDirectories(userIgnore2.getParent());
+               Files.writeString(userIgnore2, "x");
+               try {
+                       writeTrashFile(".foo", "");
+                       writeTrashFile("a/x/file", "");
+                       writeTrashFile("b/x", "");
+                       writeTrashFile("x/file", "");
+
+                       beginWalk();
+                       assertEntry(F, tracked, ".foo");
+                       assertEntry(D, tracked, "a");
+                       assertEntry(D, tracked, "a/x");
+                       assertEntry(F, tracked, "a/x/file");
+                       assertEntry(D, tracked, "b");
+                       assertEntry(F, tracked, "b/x");
+                       assertEntry(D, tracked, "x");
+                       assertEntry(F, tracked, "x/file");
+                       endWalk();
+               } finally {
+                       ((MockSystemReader) system).setProperty("XDG_CONFIG_HOME", null);
+                       Files.deleteIfExists(userIgnore2);
+               }
+       }
+
        @Test
        public void testToString() throws Exception {
                assertEquals(Arrays.asList("").toString(), new IgnoreNode().toString());
index 42bafb60cacecc0096bade2b030ac84d7dda2350..3ccd0ef0214ef7a5793573f3be6215d7a58106cc 100644 (file)
@@ -17,7 +17,9 @@ import java.io.IOException;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.junit.JGitTestUtil;
+import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
 import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
+import org.eclipse.jgit.util.FS;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -27,7 +29,7 @@ import org.junit.rules.TemporaryFolder;
  * test using bazel which doesn't allow tests to create files in the home
  * directory
  */
-public class CommitTemplateConfigTest {
+public class CommitTemplateConfigTest extends LocalDiskRepositoryTestCase {
 
        @Rule
        public TemporaryFolder tmp = new TemporaryFolder();
@@ -42,9 +44,11 @@ public class CommitTemplateConfigTest {
                String templateContent = "content of the template";
                JGitTestUtil.write(tempFile, templateContent);
                // proper evaluation of the ~/ directory
-               String homeDir = System.getProperty("user.home");
+               File homeDir = FS.DETECTED.userHome();
                File tempFileInHomeDirectory = File.createTempFile("fileInHomeFolder",
-                               ".tmp", new File(homeDir));
+                               ".tmp", homeDir);
+               // The home directory should be a mocked temporary directory, but
+               // still...
                tempFileInHomeDirectory.deleteOnExit();
                JGitTestUtil.write(tempFileInHomeDirectory, templateContent);
                String expectedTemplatePath = "~/" + tempFileInHomeDirectory.getName();
index aa544606af85d58665cb5c8ef62e622bf4e9e51c..98d08466126b4e37c284677eb2d901f4c1a7702b 100644 (file)
@@ -25,6 +25,7 @@ import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 import java.nio.charset.CharacterCodingException;
 import java.nio.charset.CharsetEncoder;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.text.MessageFormat;
 import java.time.Instant;
@@ -68,6 +69,7 @@ import org.eclipse.jgit.util.Holder;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.Paths;
 import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.SystemReader;
 import org.eclipse.jgit.util.TemporaryBuffer;
 import org.eclipse.jgit.util.TemporaryBuffer.LocalFile;
 import org.eclipse.jgit.util.io.EolStreamTypeUtil;
@@ -1329,7 +1331,11 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
                                        ConfigConstants.CONFIG_CORE_SECTION, null,
                                        ConfigConstants.CONFIG_KEY_EXCLUDESFILE, fs, null, null);
                        if (path != null) {
-                               loadRulesFromFile(coreExclude, path.toFile());
+                               if (Files.exists(path)) {
+                                       loadRulesFromFile(coreExclude, path.toFile());
+                               }
+                       } else {
+                               loadRulesFromDefaultFile(coreExclude, fs);
                        }
                        if (coreExclude.getRules().isEmpty()) {
                                coreExclude = parent;
@@ -1339,7 +1345,9 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
                                        coreExclude);
                        File exclude = fs.resolve(repository.getDirectory(),
                                        Constants.INFO_EXCLUDE);
-                       loadRulesFromFile(infoExclude, exclude);
+                       if (fs.exists(exclude)) {
+                               loadRulesFromFile(infoExclude, exclude);
+                       }
                        if (infoExclude.getRules().isEmpty()) {
                                infoExclude = null;
                        }
@@ -1361,9 +1369,19 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
 
                private static void loadRulesFromFile(IgnoreNode r, File exclude)
                                throws FileNotFoundException, IOException {
-                       if (FS.DETECTED.exists(exclude)) {
-                               try (FileInputStream in = new FileInputStream(exclude)) {
-                                       r.parse(exclude.getAbsolutePath(), in);
+                       try (FileInputStream in = new FileInputStream(exclude)) {
+                               r.parse(exclude.getAbsolutePath(), in);
+                       }
+               }
+
+               private static void loadRulesFromDefaultFile(IgnoreNode r,
+                               FS fileSystem) throws FileNotFoundException, IOException {
+                       Path cfg = SystemReader.getInstance()
+                                       .getXdgConfigDirectory(fileSystem);
+                       if (cfg != null) {
+                               Path cfgPath = cfg.resolve("git").resolve("ignore"); //$NON-NLS-1$ //$NON-NLS-2$
+                               if (Files.exists(cfgPath)) {
+                                       loadRulesFromFile(r, cfgPath.toFile());
                                }
                        }
                }
index f504c66eb3528284fa56b310056d31613460f013..158365a299579fbe02532033c6cf90dfad4b2d26 100644 (file)
@@ -31,8 +31,6 @@ import java.nio.file.Path;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.nio.file.attribute.FileTime;
 import java.security.AccessControlException;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
 import java.text.MessageFormat;
 import java.time.Duration;
 import java.time.Instant;
@@ -1294,11 +1292,10 @@ public abstract class FS {
        }
 
        private File defaultUserHomeImpl() {
-               String home = AccessController.doPrivileged(
-                               (PrivilegedAction<String>) () -> System.getProperty("user.home") //$NON-NLS-1$
-               );
-               if (home == null || home.length() == 0)
+               String home = SystemReader.getInstance().getProperty("user.home"); //$NON-NLS-1$
+               if (StringUtils.isEmptyOrNull(home)) {
                        return null;
+               }
                return new File(home).getAbsoluteFile();
        }
 
index a8a77904a2245968d76f286e0bccc4ad3f96947f..991de51df73d6f22a6f54f5d72ac6178b07ba688 100644 (file)
@@ -128,24 +128,9 @@ public abstract class SystemReader {
                                        fs);
                }
 
-               private Path getXDGConfigHome(FS fs) {
-                       String configHomePath = getenv(Constants.XDG_CONFIG_HOME);
-                       if (StringUtils.isEmptyOrNull(configHomePath)) {
-                               configHomePath = new File(fs.userHome(), ".config") //$NON-NLS-1$
-                                               .getAbsolutePath();
-                       }
-                       try {
-                               return Paths.get(configHomePath);
-                       } catch (InvalidPathException e) {
-                               LOG.error(JGitText.get().logXDGConfigHomeInvalid,
-                                               configHomePath, e);
-                       }
-                       return null;
-               }
-
                @Override
                public FileBasedConfig openJGitConfig(Config parent, FS fs) {
-                       Path xdgPath = getXDGConfigHome(fs);
+                       Path xdgPath = getXdgConfigDirectory(fs);
                        if (xdgPath != null) {
                                Path configPath = xdgPath.resolve("jgit") //$NON-NLS-1$
                                                .resolve(Constants.CONFIG);
@@ -390,6 +375,36 @@ public abstract class SystemReader {
                return c;
        }
 
+       /**
+        * Gets the directory denoted by environment variable XDG_CONFIG_HOME. If
+        * the variable is not set or empty, return a path for
+        * {@code $HOME/.config}.
+        *
+        * @param fileSystem
+        *            {@link FS} to get the user's home directory
+        * @return a {@link Path} denoting the directory, which may exist or not, or
+        *         {@code null} if the environment variable is not set and there is
+        *         no home directory, or the path is invalid.
+        * @since 6.7
+        */
+       public Path getXdgConfigDirectory(FS fileSystem) {
+               String configHomePath = getenv(Constants.XDG_CONFIG_HOME);
+               if (StringUtils.isEmptyOrNull(configHomePath)) {
+                       File home = fileSystem.userHome();
+                       if (home == null) {
+                               return null;
+                       }
+                       configHomePath = new File(home, ".config").getAbsolutePath(); //$NON-NLS-1$
+               }
+               try {
+                       return Paths.get(configHomePath);
+               } catch (InvalidPathException e) {
+                       LOG.error(JGitText.get().logXDGConfigHomeInvalid, configHomePath,
+                                       e);
+               }
+               return null;
+       }
+
        /**
         * Update config and its parents if they seem modified
         *