diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java index 5c2cd6ac6..eca8179ec 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java @@ -125,8 +125,9 @@ public abstract class LocalDiskRepositoryTestCase { public void setUp() throws Exception { tmp = File.createTempFile("jgit_test_", "_tmp"); CleanupThread.deleteOnShutdown(tmp); - if (!tmp.delete() || !tmp.mkdir()) + if (!tmp.delete() || !tmp.mkdir()) { throw new IOException("Cannot create " + tmp); + } mockSystemReader = new MockSystemReader(); SystemReader.setInstance(mockSystemReader); @@ -137,7 +138,11 @@ public abstract class LocalDiskRepositoryTestCase { // the same one here FS.getFileStoreAttributes(tmp.toPath().getParent()); - FileBasedConfig userConfig = new FileBasedConfig( + FileBasedConfig jgitConfig = new FileBasedConfig( + new File(tmp, "jgitconfig"), FS.DETECTED); + FileBasedConfig systemConfig = new FileBasedConfig(jgitConfig, + new File(tmp, "systemgitconfig"), FS.DETECTED); + FileBasedConfig userConfig = new FileBasedConfig(systemConfig, new File(tmp, "usergitconfig"), FS.DETECTED); // We have to set autoDetach to false for tests, because tests expect to be able // to clean up by recursively removing the repository, and background GC might be @@ -145,7 +150,10 @@ public abstract class LocalDiskRepositoryTestCase { userConfig.setBoolean(ConfigConstants.CONFIG_GC_SECTION, null, ConfigConstants.CONFIG_KEY_AUTODETACH, false); userConfig.save(); + mockSystemReader.setJGitConfig(jgitConfig); + mockSystemReader.setSystemGitConfig(systemConfig); mockSystemReader.setUserGitConfig(userConfig); + ceilTestDirectories(getCeilings()); author = new PersonIdent("J. Author", "jauthor@example.com"); diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java index 13c293228..630c8a479 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java @@ -103,6 +103,8 @@ public class MockSystemReader extends SystemReader { private FileBasedConfig userGitConfig; + private FileBasedConfig jgitConfig; + FileBasedConfig systemGitConfig; /** @@ -118,6 +120,16 @@ public class MockSystemReader extends SystemReader { return old; } + /** + * Set the jgit config stored at $XDG_CONFIG_HOME/jgit/config + * + * @param jgitConfig + * set the jgit configuration + */ + public void setJGitConfig(FileBasedConfig jgitConfig) { + this.jgitConfig = jgitConfig; + } + /** * Set the system-level git config * @@ -142,6 +154,7 @@ public class MockSystemReader extends SystemReader { init(Constants.GIT_COMMITTER_EMAIL_KEY); setProperty(Constants.OS_USER_DIR, "."); userGitConfig = new MockConfig(null, null); + jgitConfig = new MockConfig(null, null); systemGitConfig = new MockConfig(null, null); setCurrentPlatform(); } @@ -199,6 +212,11 @@ public class MockSystemReader extends SystemReader { return userGitConfig; } + @Override + public FileBasedConfig getJGitConfig() { + return jgitConfig; + } + @Override public StoredConfig getSystemConfig() throws IOException, ConfigInvalidException { @@ -333,4 +351,9 @@ public class MockSystemReader extends SystemReader { return "MockSystemReader"; } + @Override + public FileBasedConfig openJGitConfig(Config parent, FS fs) { + return jgitConfig; + } + } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FS_POSIXTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FS_POSIXTest.java index 87349a25a..d7a7d8677 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FS_POSIXTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FS_POSIXTest.java @@ -59,7 +59,7 @@ import org.junit.Before; import org.junit.Test; public class FS_POSIXTest { - private SystemReader originalSystemReaderInstance; + private FileBasedConfig jgitConfig; private FileBasedConfig systemConfig; @@ -70,6 +70,7 @@ public class FS_POSIXTest { @Before public void setUp() throws Exception { tmp = Files.createTempDirectory("jgit_test_"); + MockSystemReader mockSystemReader = new MockSystemReader(); SystemReader.setInstance(mockSystemReader); @@ -78,7 +79,10 @@ public class FS_POSIXTest { // The MockSystemReader must be configured first since we need to use // the same one here FS.getFileStoreAttributes(tmp.getParent()); - systemConfig = new FileBasedConfig( + + jgitConfig = new FileBasedConfig(new File(tmp.toFile(), "jgitconfig"), + FS.DETECTED); + systemConfig = new FileBasedConfig(jgitConfig, new File(tmp.toFile(), "systemgitconfig"), FS.DETECTED); userConfig = new FileBasedConfig(systemConfig, new File(tmp.toFile(), "usergitconfig"), FS.DETECTED); @@ -89,16 +93,14 @@ public class FS_POSIXTest { userConfig.setBoolean(ConfigConstants.CONFIG_GC_SECTION, null, ConfigConstants.CONFIG_KEY_AUTODETACH, false); userConfig.save(); + mockSystemReader.setJGitConfig(jgitConfig); mockSystemReader.setSystemGitConfig(systemConfig); mockSystemReader.setUserGitConfig(userConfig); - - originalSystemReaderInstance = SystemReader.getInstance(); - SystemReader.setInstance(mockSystemReader); } @After public void tearDown() throws IOException { - SystemReader.setInstance(originalSystemReaderInstance); + SystemReader.setInstance(null); FileUtils.delete(tmp.toFile(), FileUtils.RECURSIVE | FileUtils.RETRY); } diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index 7391826c0..52eaa1fc2 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -201,8 +201,10 @@ countingObjects=Counting objects corruptPack=Pack file {0} is corrupt, removing it from pack list createBranchFailedUnknownReason=Create branch failed for unknown reason createBranchUnexpectedResult=Create branch returned unexpected result {0} +createJGitConfigFailed=Creating JGit config directory {} failed createNewFileFailed=Could not create new file {0} createRequiresZeroOldId=Create requires old ID to be zero +createXDGConfigHomeFailed=Creating XDG_CONFIG_HOME directory {} failed credentialPassword=Password credentialPassphrase=Passphrase credentialUsername=Username diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java index 2357e8ee9..43c2cc34d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -262,8 +262,10 @@ public class JGitText extends TranslationBundle { /***/ public String countingObjects; /***/ public String createBranchFailedUnknownReason; /***/ public String createBranchUnexpectedResult; + /***/ public String createJGitConfigFailed; /***/ public String createNewFileFailed; /***/ public String createRequiresZeroOldId; + /***/ public String createXDGConfigHomeFailed; /***/ public String credentialPassword; /***/ public String credentialPassphrase; /***/ public String credentialUsername; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java index 938fa2394..2ef365399 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java @@ -128,10 +128,22 @@ public class Config { state = new AtomicReference<>(newState()); } + /** + * Retrieves this config's base config. + * + * @return the base configuration of this config. + * + * @since 5.5.2 + */ + public Config getBaseConfig() { + return baseConfig; + } + /** * Check if a given string is the "missing" value. * - * @param value string to be checked. + * @param value + * string to be checked. * @return true if the given string is the "missing" value. * @since 5.4 */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java index a084c8287..9274fc677 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java @@ -342,6 +342,15 @@ public final class Constants { */ public static final String GIT_CONFIG_NOSYSTEM_KEY = "GIT_CONFIG_NOSYSTEM"; + /** + * The key of the XDG_CONFIG_HOME directory defined in the XDG base + * directory specification, see + * {@link "https://wiki.archlinux.org/index.php/XDG_Base_Directory"} + * + * @since 5.5.2 + */ + public static final String XDG_CONFIG_HOME = "XDG_CONFIG_HOME"; + /** * The environment variable that limits how close to the root of the file * systems JGit will traverse when looking for a repository root. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java index d4fa17e25..40c76cdf9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java @@ -1361,7 +1361,7 @@ public abstract class BaseReceivePack { } static ReceiveCommand parseCommand(String line) throws PackProtocolException { - if (line == null || line.length() < 83) { + if (line == null || line.length() < 83) { throw new PackProtocolException( JGitText.get().errorInvalidProtocolWantedOldNewRef); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java index 2035ada80..b6c88503b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java @@ -246,8 +246,9 @@ public abstract class FS { background.set(async); } - private static final String javaVersionPrefix = System - .getProperty("java.vendor") + '|' //$NON-NLS-1$ + private static final String javaVersionPrefix = SystemReader + .getInstance().getHostname() + '|' + + System.getProperty("java.vendor") + '|' //$NON-NLS-1$ + System.getProperty("java.version") + '|'; //$NON-NLS-1$ private static final Duration FALLBACK_MIN_RACY_INTERVAL = Duration @@ -547,9 +548,9 @@ public abstract class FS { private static void saveToConfig(FileStore s, FileStoreAttributes c) { - StoredConfig userConfig; + StoredConfig jgitConfig; try { - userConfig = SystemReader.getInstance().getUserConfig(); + jgitConfig = SystemReader.getInstance().getJGitConfig(); } catch (IOException | ConfigInvalidException e) { LOG.error(JGitText.get().saveFileStoreAttributesFailed, e); return; @@ -570,20 +571,19 @@ public abstract class FS { String key = getConfigKey(s); while (!succeeded && retries < max_retries) { try { - userConfig.load(); - userConfig.setString( + jgitConfig.setString( ConfigConstants.CONFIG_FILESYSTEM_SECTION, key, ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION, String.format("%d %s", //$NON-NLS-1$ Long.valueOf(resolutionValue), resolutionUnit.name().toLowerCase())); - userConfig.setString( + jgitConfig.setString( ConfigConstants.CONFIG_FILESYSTEM_SECTION, key, ConfigConstants.CONFIG_KEY_MIN_RACY_THRESHOLD, String.format("%d %s", //$NON-NLS-1$ Long.valueOf(minRacyThresholdValue), minRacyThresholdUnit.name().toLowerCase())); - userConfig.save(); + jgitConfig.save(); succeeded = true; } catch (LockFailedException e) { // race with another thread, wait a bit and try again @@ -592,11 +592,11 @@ public abstract class FS { if (retries < max_retries) { Thread.sleep(100); LOG.debug("locking {} failed, retries {}/{}", //$NON-NLS-1$ - userConfig, Integer.valueOf(retries), + jgitConfig, Integer.valueOf(retries), Integer.valueOf(max_retries)); } else { LOG.warn(MessageFormat.format( - JGitText.get().lockFailedRetry, userConfig, + JGitText.get().lockFailedRetry, jgitConfig, Integer.valueOf(retries))); } } catch (InterruptedException e1) { @@ -605,12 +605,7 @@ public abstract class FS { } } catch (IOException e) { LOG.error(MessageFormat.format( - JGitText.get().cannotSaveConfig, userConfig), e); - break; - } catch (ConfigInvalidException e) { - LOG.error(MessageFormat.format( - JGitText.get().repositoryConfigFileInvalid, - userConfig, e.getMessage())); + JGitText.get().cannotSaveConfig, jgitConfig), e); break; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java index b80ffb51b..59184ffda 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java @@ -50,6 +50,10 @@ import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.security.AccessController; import java.security.PrivilegedAction; import java.text.DateFormat; @@ -60,6 +64,7 @@ import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectChecker; @@ -137,6 +142,42 @@ 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 { + Path xdgHomePath = Paths.get(configHomePath); + Files.createDirectories(xdgHomePath); + return xdgHomePath; + } catch (IOException | InvalidPathException e) { + LOG.error(JGitText.get().createXDGConfigHomeFailed, + configHomePath, e); + } + return null; + } + + @Override + public FileBasedConfig openJGitConfig(Config parent, FS fs) { + Path xdgPath = getXDGConfigHome(fs); + if (xdgPath != null) { + Path configPath = null; + try { + configPath = xdgPath.resolve("jgit"); //$NON-NLS-1$ + Files.createDirectories(configPath); + configPath = configPath.resolve(Constants.CONFIG); + return new FileBasedConfig(parent, configPath.toFile(), fs); + } catch (IOException e) { + LOG.error(JGitText.get().createJGitConfigFailed, configPath, + e); + } + } + return new FileBasedConfig(parent, + new File(fs.userHome(), ".jgitconfig"), fs); //$NON-NLS-1$ + } + @Override public String getHostname() { if (hostname == null) { @@ -198,6 +239,8 @@ public abstract class SystemReader { private AtomicReference userConfig = new AtomicReference<>(); + private AtomicReference jgitConfig = new AtomicReference<>(); + private void init() { // Creating ObjectChecker must be deferred. Unit tests change // behavior of is{Windows,MacOS} in constructor of subclass. @@ -274,6 +317,22 @@ public abstract class SystemReader { */ public abstract FileBasedConfig openSystemConfig(Config parent, FS fs); + /** + * Open the jgit configuration located at $XDG_CONFIG_HOME/jgit/config. Use + * {@link #getJGitConfig()} to get the current jgit configuration in the + * user home since it manages automatic reloading when the jgit config file + * was modified and avoids unnecessary reloads. + * + * @param parent + * a config with values not found directly in the returned config + * @param fs + * the file system abstraction which will be necessary to perform + * certain file system operations. + * @return the jgit configuration located at $XDG_CONFIG_HOME/jgit/config + * @since 5.5.2 + */ + public abstract FileBasedConfig openJGitConfig(Config parent, FS fs); + /** * Get the git configuration found in the user home. The configuration will * be reloaded automatically if the configuration file was modified. Also @@ -288,20 +347,41 @@ public abstract class SystemReader { * @since 5.1.9 */ public StoredConfig getUserConfig() - throws IOException, ConfigInvalidException { + throws ConfigInvalidException, IOException { FileBasedConfig c = userConfig.get(); if (c == null) { userConfig.compareAndSet(null, openUserConfig(getSystemConfig(), FS.DETECTED)); c = userConfig.get(); - } else { - // Ensure the parent is up to date - getSystemConfig(); } - if (c.isOutdated()) { - LOG.debug("loading user config {}", userConfig); //$NON-NLS-1$ - c.load(); + // on the very first call this will check a second time if the system + // config is outdated + updateAll(c); + return c; + } + + /** + * Get the jgit configuration located at $XDG_CONFIG_HOME/jgit/config. The + * configuration will be reloaded automatically if the configuration file + * was modified. If the configuration file wasn't modified returns the + * cached configuration. + * + * @return the jgit configuration located at $XDG_CONFIG_HOME/jgit/config + * @throws ConfigInvalidException + * if configuration is invalid + * @throws IOException + * if something went wrong when reading files + * @since 5.5.2 + */ + public StoredConfig getJGitConfig() + throws ConfigInvalidException, IOException { + FileBasedConfig c = jgitConfig.get(); + if (c == null) { + jgitConfig.compareAndSet(null, + openJGitConfig(null, FS.DETECTED)); + c = jgitConfig.get(); } + updateAll(c); return c; } @@ -319,20 +399,45 @@ public abstract class SystemReader { * @since 5.1.9 */ public StoredConfig getSystemConfig() - throws IOException, ConfigInvalidException { + throws ConfigInvalidException, IOException { FileBasedConfig c = systemConfig.get(); if (c == null) { systemConfig.compareAndSet(null, - openSystemConfig(null, FS.DETECTED)); + openSystemConfig(getJGitConfig(), FS.DETECTED)); c = systemConfig.get(); } - if (c.isOutdated()) { - LOG.debug("loading system config {}", systemConfig); //$NON-NLS-1$ - c.load(); - } + updateAll(c); return c; } + /** + * Update config and its parents if they seem modified + * + * @param config + * configuration to reload if outdated + * @throws ConfigInvalidException + * if configuration is invalid + * @throws IOException + * if something went wrong when reading files + */ + private void updateAll(Config config) + throws ConfigInvalidException, IOException { + if (config == null) { + return; + } + updateAll(config.getBaseConfig()); + if (config instanceof FileBasedConfig) { + FileBasedConfig cfg = (FileBasedConfig) config; + if (!cfg.getFile().exists()) { + return; + } + if (cfg.isOutdated()) { + LOG.debug("loading config {}", cfg); //$NON-NLS-1$ + cfg.load(); + } + } + } + /** * Get the current system time *