* stable-5.5: BaseReceivePack: Fix the format Prepend hostname to subsection used to store file timestamp resolution Store filesystem timestamp resolution in extra jgit config SystemReader: extract updating config and its parents if outdated Change-Id: Iecfddce8081303af29badcdcd3d72a0da50c964f Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>tags/v5.6.0.201911271000-m3
@@ -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"); |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
@@ -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 |
@@ -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; |
@@ -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 | |||
*/ |
@@ -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. |
@@ -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); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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<FileBasedConfig> userConfig = new AtomicReference<>(); | |||
private AtomicReference<FileBasedConfig> 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 | |||
* |