diff options
51 files changed, 976 insertions, 332 deletions
@@ -145,13 +145,13 @@ maven_jar( BYTE_BUDDY_VERSION = "1.9.0" maven_jar( - name = "byte_buddy", + name = "bytebuddy", artifact = "net.bytebuddy:byte-buddy:" + BYTE_BUDDY_VERSION, sha1 = "8cb0d5baae526c9df46ae17693bbba302640538b", ) maven_jar( - name = "byte_buddy_agent", + name = "bytebuddy-agent", artifact = "net.bytebuddy:byte-buddy-agent:" + BYTE_BUDDY_VERSION, sha1 = "37b5703b4a6290be3fffc63ae9c6bcaaee0ff856", ) @@ -189,9 +189,13 @@ java_library( testonly = 1, visibility = ["//visibility:public"], exports = [ + "@bytebuddy//jar", + "@bytebuddy-agent//jar", "@hamcrest-core//jar", "@hamcrest-library//jar", "@junit//jar", + "@mockito//jar", + "@objenesis//jar" ], ) @@ -200,8 +204,8 @@ java_library( testonly = 1, visibility = ["//visibility:public"], exports = [ - "@byte_buddy//jar", - "@byte_buddy_agent//jar", + "@bytebuddy//jar", + "@bytebuddy-agent//jar", "@mockito//jar", "@objenesis//jar", ], diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java index 54c1c03b50..3aac85233f 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java @@ -56,6 +56,7 @@ import static org.junit.Assert.fail; import java.io.IOException; import java.io.PrintWriter; +import java.net.URI; import java.net.URISyntaxException; import java.text.MessageFormat; import java.util.Arrays; @@ -107,7 +108,6 @@ import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.transport.AbstractAdvertiseRefsHook; import org.eclipse.jgit.transport.AdvertiseRefsHook; import org.eclipse.jgit.transport.CredentialItem; @@ -124,7 +124,6 @@ import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.eclipse.jgit.transport.http.HttpConnectionFactory; import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory; import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory; -import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.HttpSupport; import org.eclipse.jgit.util.SystemReader; import org.hamcrest.Matchers; @@ -340,6 +339,23 @@ public class SmartClientSmartServerTest extends HttpTestCase { // empty } + private String local(String url, boolean toLocal) { + if (!toLocal) { + return url; + } + try { + URI u = new URI(url); + String fragment = u.getRawFragment(); + if (fragment != null) { + return u.getRawPath() + '#' + fragment; + } else { + return u.getRawPath(); + } + } catch (URISyntaxException e) { + return url; + } + } + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) @@ -352,6 +368,11 @@ public class SmartClientSmartServerTest extends HttpTestCase { .append(httpServletRequest.getQueryString()); } String urlString = fullUrl.toString(); + boolean localRedirect = false; + if (urlString.contains("/local")) { + urlString = urlString.replace("/local", ""); + localRedirect = true; + } if (urlString.contains("/loop/")) { urlString = urlString.replace("/loop/", "/loop/x/"); if (urlString.contains("/loop/x/x/x/x/x/x/x/x/")) { @@ -362,7 +383,7 @@ public class SmartClientSmartServerTest extends HttpTestCase { httpServletResponse.setStatus( HttpServletResponse.SC_MOVED_TEMPORARILY); httpServletResponse.setHeader(HttpSupport.HDR_LOCATION, - urlString); + local(urlString, localRedirect)); return; } int responseCode = HttpServletResponse.SC_MOVED_PERMANENTLY; @@ -392,9 +413,10 @@ public class SmartClientSmartServerTest extends HttpTestCase { targetContext = matcher.group(1); } urlString = urlString.replace("/redirect", targetContext); + } httpServletResponse.setHeader(HttpSupport.HDR_LOCATION, - urlString); + local(urlString, localRedirect)); } @Override @@ -561,7 +583,15 @@ public class SmartClientSmartServerTest extends HttpTestCase { private void initialClone_Redirect(int nofRedirects, int code) throws Exception { + initialClone_Redirect(nofRedirects, code, false); + } + + private void initialClone_Redirect(int nofRedirects, int code, + boolean localRedirect) throws Exception { URIish cloneFrom = redirectURI; + if (localRedirect) { + cloneFrom = extendPath(cloneFrom, "/local"); + } if (code != 301 || nofRedirects > 1) { cloneFrom = extendPath(cloneFrom, "/response/" + nofRedirects + "/" + code); @@ -615,6 +645,11 @@ public class SmartClientSmartServerTest extends HttpTestCase { } @Test + public void testInitialClone_Redirect301Local() throws Exception { + initialClone_Redirect(1, 301, true); + } + + @Test public void testInitialClone_Redirect302Small() throws Exception { initialClone_Redirect(1, 302); } @@ -636,8 +671,8 @@ public class SmartClientSmartServerTest extends HttpTestCase { @Test public void testInitialClone_RedirectMax() throws Exception { - FileBasedConfig userConfig = SystemReader.getInstance() - .openUserConfig(null, FS.DETECTED); + StoredConfig userConfig = SystemReader.getInstance() + .getUserConfig(); userConfig.setInt("http", null, "maxRedirects", 4); userConfig.save(); initialClone_Redirect(4, 302); @@ -645,8 +680,8 @@ public class SmartClientSmartServerTest extends HttpTestCase { @Test public void testInitialClone_RedirectTooOften() throws Exception { - FileBasedConfig userConfig = SystemReader.getInstance() - .openUserConfig(null, FS.DETECTED); + StoredConfig userConfig = SystemReader.getInstance() + .getUserConfig(); userConfig.setInt("http", null, "maxRedirects", 3); userConfig.save(); @@ -684,8 +719,8 @@ public class SmartClientSmartServerTest extends HttpTestCase { @Test public void testInitialClone_RedirectOnPostAllowed() throws Exception { - FileBasedConfig userConfig = SystemReader.getInstance() - .openUserConfig(null, FS.DETECTED); + StoredConfig userConfig = SystemReader.getInstance() + .getUserConfig(); userConfig.setString("http", null, "followRedirects", "true"); userConfig.save(); @@ -745,8 +780,8 @@ public class SmartClientSmartServerTest extends HttpTestCase { @Test public void testInitialClone_RedirectForbidden() throws Exception { - FileBasedConfig userConfig = SystemReader.getInstance() - .openUserConfig(null, FS.DETECTED); + StoredConfig userConfig = SystemReader.getInstance() + .getUserConfig(); userConfig.setString("http", null, "followRedirects", "false"); userConfig.save(); 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 98e1f8240a..5c2cd6ac65 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 @@ -128,21 +128,25 @@ public abstract class LocalDiskRepositoryTestCase { if (!tmp.delete() || !tmp.mkdir()) throw new IOException("Cannot create " + tmp); - // measure timer resolution before the test to avoid time critical tests - // are affected by time needed for measurement + mockSystemReader = new MockSystemReader(); + SystemReader.setInstance(mockSystemReader); + + // 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 + // the same one here FS.getFileStoreAttributes(tmp.toPath().getParent()); - mockSystemReader = new MockSystemReader(); - mockSystemReader.userGitConfig = new FileBasedConfig(new File(tmp, - "usergitconfig"), FS.DETECTED); + FileBasedConfig userConfig = new FileBasedConfig( + 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 // in the middle of writing or deleting files, which would disrupt this. - mockSystemReader.userGitConfig.setBoolean(ConfigConstants.CONFIG_GC_SECTION, + userConfig.setBoolean(ConfigConstants.CONFIG_GC_SECTION, null, ConfigConstants.CONFIG_KEY_AUTODETACH, false); - mockSystemReader.userGitConfig.save(); + userConfig.save(); + mockSystemReader.setUserGitConfig(userConfig); ceilTestDirectories(getCeilings()); - SystemReader.setInstance(mockSystemReader); author = new PersonIdent("J. Author", "jauthor@example.com"); committer = new PersonIdent("J. Committer", "jcommitter@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 89f2530a31..13c2932282 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 @@ -60,6 +60,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.SystemReader; @@ -81,20 +82,56 @@ public class MockSystemReader extends SystemReader { } @Override + public void save() throws IOException { + // Do nothing + } + + @Override public boolean isOutdated() { return false; } + + @Override + public String toString() { + return "MockConfig"; + } } long now = 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009 final Map<String, String> values = new HashMap<>(); - FileBasedConfig userGitConfig; + private FileBasedConfig userGitConfig; FileBasedConfig systemGitConfig; /** + * Set the user-level git config + * + * @param userGitConfig + * set another user-level git config + * @return the old user-level git config + */ + public FileBasedConfig setUserGitConfig(FileBasedConfig userGitConfig) { + FileBasedConfig old = this.userGitConfig; + this.userGitConfig = userGitConfig; + return old; + } + + /** + * Set the system-level git config + * + * @param systemGitConfig + * the new system-level git config + * @return the old system-level config + */ + public FileBasedConfig setSystemGitConfig(FileBasedConfig systemGitConfig) { + FileBasedConfig old = this.systemGitConfig; + this.systemGitConfig = systemGitConfig; + return old; + } + + /** * Constructor for <code>MockSystemReader</code> */ public MockSystemReader() { @@ -156,6 +193,18 @@ public class MockSystemReader extends SystemReader { return systemGitConfig; } + @Override + public StoredConfig getUserConfig() + throws IOException, ConfigInvalidException { + return userGitConfig; + } + + @Override + public StoredConfig getSystemConfig() + throws IOException, ConfigInvalidException { + return systemGitConfig; + } + /** {@inheritDoc} */ @Override public String getHostname() { @@ -278,4 +327,10 @@ public class MockSystemReader extends SystemReader { e.printStackTrace(); } } + + @Override + public String toString() { + return "MockSystemReader"; + } + } diff --git a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java index ec44da4cac..e55c4c6431 100644 --- a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java +++ b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java @@ -74,6 +74,7 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jgit.junit.MockSystemReader; import org.eclipse.jgit.junit.http.AppServer; import org.eclipse.jgit.lfs.errors.LfsException; import org.eclipse.jgit.lfs.lib.AnyLongObjectId; @@ -85,6 +86,7 @@ import org.eclipse.jgit.lfs.test.LongObjectIdTestUtils; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.SystemReader; import org.junit.After; import org.junit.Before; @@ -119,6 +121,7 @@ public abstract class LfsServerTest { @Before public void setup() throws Exception { + SystemReader.setInstance(new MockSystemReader()); tmp = Files.createTempDirectory("jgit_test_"); // measure timer resolution before the test to avoid time critical tests diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/InstallBuiltinLfsCommand.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/InstallBuiltinLfsCommand.java index 028b19b2ab..b7b0535ea5 100644 --- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/InstallBuiltinLfsCommand.java +++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/InstallBuiltinLfsCommand.java @@ -43,14 +43,12 @@ package org.eclipse.jgit.lfs; import java.io.IOException; -import java.text.MessageFormat; +import org.eclipse.jgit.api.errors.InvalidConfigurationException; import org.eclipse.jgit.errors.ConfigInvalidException; -import org.eclipse.jgit.lfs.internal.LfsText; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; -import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.LfsFactory.LfsInstallCommand; import org.eclipse.jgit.util.SystemReader; @@ -70,12 +68,28 @@ public class InstallBuiltinLfsCommand implements LfsInstallCommand { private Repository repository; - /** {@inheritDoc} */ + /** + * {@inheritDoc} + * + * @throws IOException + * if an I/O error occurs while accessing a git config or + * executing {@code git lfs install} in an external process + * @throws InvalidConfigurationException + * if a git configuration is invalid + * @throws InterruptedException + * if the current thread is interrupted while waiting for the + * {@code git lfs install} executed in an external process + */ @Override - public Void call() throws Exception { + public Void call() throws IOException, InvalidConfigurationException, + InterruptedException { StoredConfig cfg = null; if (repository == null) { - cfg = loadUserConfig(); + try { + cfg = SystemReader.getInstance().getUserConfig(); + } catch (ConfigInvalidException e) { + throw new InvalidConfigurationException(e.getMessage(), e); + } } else { cfg = repository.getConfig(); } @@ -116,19 +130,4 @@ public class InstallBuiltinLfsCommand implements LfsInstallCommand { return this; } - private StoredConfig loadUserConfig() throws IOException { - FileBasedConfig c = SystemReader.getInstance().openUserConfig(null, - FS.DETECTED); - try { - c.load(); - } catch (ConfigInvalidException e1) { - throw new IOException(MessageFormat - .format(LfsText.get().userConfigInvalid, c.getFile() - .getAbsolutePath(), e1), - e1); - } - - return c; - } - } diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target index f5eca33acf..5a9714f2ea 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <?pde?> <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl --> -<target name="jgit-4.10" sequenceNumber="1560816180"> +<target name="jgit-4.10" sequenceNumber="1566252827"> <locations> <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit"> <unit id="org.eclipse.jetty.client" version="9.4.14.v20181114"/> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target index bdbc75a9bf..c00aebd4f5 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <?pde?> <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl --> -<target name="jgit-4.11" sequenceNumber="1560816197"> +<target name="jgit-4.11" sequenceNumber="1566252823"> <locations> <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit"> <unit id="org.eclipse.jetty.client" version="9.4.14.v20181114"/> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12-staging.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12-staging.target index e98c89e4d7..a79aeb0169 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12-staging.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12-staging.target @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <?pde?> <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl --> -<target name="jgit-4.12-staging" sequenceNumber="1560816219"> +<target name="jgit-4.12-staging" sequenceNumber="1566252818"> <locations> <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit"> <unit id="org.eclipse.jetty.client" version="9.4.14.v20181114"/> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target index 308f9f9f80..082f5d4b4a 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <?pde?> <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl --> -<target name="jgit-4.6" sequenceNumber="1560816227"> +<target name="jgit-4.6" sequenceNumber="1566252830"> <locations> <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit"> <unit id="org.eclipse.jetty.client" version="9.4.14.v20181114"/> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target index eeae219049..aff5ce6a26 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <?pde?> <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl --> -<target name="jgit-4.7" sequenceNumber="1560816219"> +<target name="jgit-4.7" sequenceNumber="1566252816"> <locations> <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit"> <unit id="org.eclipse.jetty.client" version="9.4.14.v20181114"/> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target index 4ada70f913..4995bae26d 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <?pde?> <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl --> -<target name="jgit-4.8" sequenceNumber="1560816217"> +<target name="jgit-4.8" sequenceNumber="1566252807"> <locations> <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit"> <unit id="org.eclipse.jetty.client" version="9.4.14.v20181114"/> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target index 97c9de3695..debd6164af 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <?pde?> <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl --> -<target name="jgit-4.9" sequenceNumber="1560816222"> +<target name="jgit-4.9" sequenceNumber="1566252778"> <locations> <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit"> <unit id="org.eclipse.jetty.client" version="9.4.14.v20181114"/> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20190602212107-2019-06.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20190602212107-2019-06.tpd index e2f975ebdc..db674493b5 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20190602212107-2019-06.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20190602212107-2019-06.tpd @@ -62,3 +62,4 @@ location "https://download.eclipse.org/tools/orbit/downloads/drops/R201906022121 org.apache.sshd.sftp [2.2.0.v20190425-2127,2.2.0.v20190425-2127] org.apache.sshd.sftp.source [2.2.0.v20190425-2127,2.2.0.v20190425-2127] } + 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 0ce645139d..1ce86d15ed 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 @@ -43,14 +43,14 @@ package org.eclipse.jgit.pgm; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; +import java.util.HashMap; +import java.util.Map; 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; @@ -65,6 +65,7 @@ public class ConfigTest extends CLIRepositoryTestCase { } } + @SuppressWarnings("boxing") @Test public void testListConfig() throws Exception { boolean isWindows = SystemReader.getInstance().getProperty("os.name") @@ -73,19 +74,31 @@ public class ConfigTest extends CLIRepositoryTestCase { .equals("Mac OS X"); String[] output = execute("git config --list"); - List<String> expect = new ArrayList<>(); - expect.add("gc.autoDetach=false"); - expect.add("core.filemode=" + !isWindows); - expect.add("core.logallrefupdates=true"); - if (isMac) - expect.add("core.precomposeunicode=true"); - expect.add("core.repositoryformatversion=0"); - if (!FS.DETECTED.supportsSymlinks()) - expect.add("core.symlinks=false"); - expect.add(""); // ends with LF (last line empty) - assertEquals("expected default configuration", - Arrays.asList(expect.toArray()).toString(), - Arrays.asList(output).toString()); + + Map<String, String> options = parseOptions(output); + + assertEquals(!isWindows, Boolean.valueOf(options.get("core.filemode"))); + assertTrue((Boolean.valueOf(options.get("core.logallrefupdates")))); + if (isMac) { + assertTrue( + (Boolean.valueOf(options.get("core.precomposeunicode")))); + } + assertEquals(Integer.valueOf(0), + Integer.valueOf(options.get("core.repositoryformatversion"))); + } + + private Map<String, String> parseOptions(String[] output) { + Map<String, String> options = new HashMap<>(); + Arrays.stream(output).forEachOrdered(s -> { + int p = s.indexOf('='); + if (p == -1) { + return; + } + String key = s.substring(0, p); + String value = s.substring(p + 1); + options.put(key, value); + }); + return options; } } diff --git a/org.eclipse.jgit.test/BUILD b/org.eclipse.jgit.test/BUILD index fc8c53c185..f90a28030d 100644 --- a/org.eclipse.jgit.test/BUILD +++ b/org.eclipse.jgit.test/BUILD @@ -51,6 +51,7 @@ java_library( deps = [ "//lib:jsch", "//lib:junit", + "//lib:slf4j-simple", "//lib:mockito", "//org.eclipse.jgit:jgit", "//org.eclipse.jgit.junit:junit", diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF index 53b79b0cb7..e36f5e419a 100644 --- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF @@ -10,7 +10,7 @@ Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", com.jcraft.jsch;version="[0.1.54,0.2.0)", - net.bytebuddy.dynamic.loading;version="[1.7.0,2.0.0)", + net.bytebuddy.dynamic.loading;version="[1.9.0,2.0.0)", org.apache.commons.compress.archivers;version="[1.15.0,2.0)", org.apache.commons.compress.archivers.tar;version="[1.15.0,2.0)", org.apache.commons.compress.archivers.zip;version="[1.15.0,2.0)", @@ -74,10 +74,10 @@ Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", org.junit.rules;version="[4.12,5.0.0)", org.junit.runner;version="[4.12,5.0.0)", org.junit.runners;version="[4.12,5.0.0)", - org.mockito;version="[2.13.0,3.0.0)", - org.mockito.invocation;version="[2.13.0,3.0.0)", - org.mockito.junit;version="[2.13.0,3.0.0)", - org.mockito.stubbing;version="[2.13.0,3.0.0)", + org.mockito;version="[2.23.0,3.0.0)", + org.mockito.invocation;version="[2.23.0,3.0.0)", + org.mockito.junit;version="[2.23.0,3.0.0)", + org.mockito.stubbing;version="[2.23.0,3.0.0)", org.objenesis;version="[2.6.0,3.0.0)", org.slf4j;version="[1.7.0,2.0.0)", org.tukaani.xz;version="[1.6.0,2.0)" diff --git a/org.eclipse.jgit.test/tst-rsrc/log4j.properties b/org.eclipse.jgit.test/tst-rsrc/log4j.properties index ee1ac35158..856a731ab9 100644 --- a/org.eclipse.jgit.test/tst-rsrc/log4j.properties +++ b/org.eclipse.jgit.test/tst-rsrc/log4j.properties @@ -7,8 +7,8 @@ log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.out log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n -#log4j.appender.fileLogger.bufferedIO = true -#log4j.appender.fileLogger.bufferSize = 4096 +log4j.appender.fileLogger.bufferedIO = true +log4j.appender.fileLogger.bufferSize = 4096 #log4j.logger.org.eclipse.jgit.util.FS = DEBUG #log4j.logger.org.eclipse.jgit.internal.storage.file.FileSnapshot = DEBUG diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/A3.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/A3.patch new file mode 100644 index 0000000000..ac63d91137 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/A3.patch @@ -0,0 +1,3 @@ +diff --git a/A3 b/A3 +new file mode 100644 +index 0000000..de98044
\ No newline at end of file diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/A3_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/A3_PostImage new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/A3_PostImage diff --git a/org.eclipse.jgit.test/tst-rsrc/simplelogger.properties b/org.eclipse.jgit.test/tst-rsrc/simplelogger.properties new file mode 100644 index 0000000000..011b2f8bb0 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/simplelogger.properties @@ -0,0 +1,9 @@ +org.slf4j.simpleLogger.logFile = System.err +org.slf4j.simpleLogger.cacheOutputStream = true +org.slf4j.simpleLogger.defaultLogLevel = info +org.slf4j.simpleLogger.showDateTime = true +org.slf4j.simpleLogger.dateTimeFormat = HH:mm:ss.SSSXXX +org.slf4j.simpleLogger.showThreadName = true + +#org.slf4j.simpleLogger.log.org.eclipse.jgit.util.FS = debug +#org.slf4j.simpleLogger.log.org.eclipse.jgit.internal.storage.file.FileSnapshot = debug diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java index 1dd329a9e7..714a54c90c 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java @@ -111,6 +111,16 @@ public class ApplyCommandTest extends RepositoryTestCase { } @Test + public void testAddA3() throws Exception { + ApplyResult result = init("A3", false, true); + assertEquals(1, result.getUpdatedFiles().size()); + assertEquals(new File(db.getWorkTree(), "A3"), + result.getUpdatedFiles().get(0)); + checkFile(new File(db.getWorkTree(), "A3"), + b.getString(0, b.size(), false)); + } + + @Test public void testAddA1Sub() throws Exception { ApplyResult result = init("A1_sub", false, false); assertEquals(1, result.getUpdatedFiles().size()); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java index 74d13883e1..2270a6a8be 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java @@ -68,9 +68,9 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.submodule.SubmoduleStatus; import org.eclipse.jgit.submodule.SubmoduleStatusType; import org.eclipse.jgit.submodule.SubmoduleWalk; @@ -680,8 +680,8 @@ public class CloneCommandTest extends RepositoryTestCase { ConfigConstants.CONFIG_BRANCH_SECTION, "test", ConfigConstants.CONFIG_KEY_REBASE, null)); - FileBasedConfig userConfig = SystemReader.getInstance().openUserConfig( - null, git.getRepository().getFS()); + StoredConfig userConfig = SystemReader.getInstance() + .getUserConfig(); userConfig.setString(ConfigConstants.CONFIG_BRANCH_SECTION, null, ConfigConstants.CONFIG_KEY_AUTOSETUPREBASE, ConfigConstants.CONFIG_KEY_ALWAYS); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitOnlyTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitOnlyTest.java index 83181eea23..2a2a6ba1bc 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitOnlyTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitOnlyTest.java @@ -1295,9 +1295,15 @@ public class CommitOnlyTest extends RepositoryTestCase { try { final Repository repo = git.getRepository(); final ObjectId headId = repo.resolve(Constants.HEAD + "^{commit}"); + if (headId == null) { + return ""; + } try (RevWalk rw = new RevWalk(repo)) { final TreeWalk tw = TreeWalk.forPath(repo, path, rw.parseTree(headId)); + if (tw == null) { + return ""; + } return new String(tw.getObjectReader().open(tw.getObjectId(0)) .getBytes(), UTF_8); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java index 6fa35d64b0..35f4fcba9b 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java @@ -62,6 +62,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.concurrent.TimeUnit; +import org.eclipse.jgit.junit.MockSystemReader; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FS.FileStoreAttributes; import org.eclipse.jgit.util.FileUtils; @@ -84,6 +85,7 @@ public class FileSnapshotTest { @Before public void setUp() throws Exception { + SystemReader.setInstance(new MockSystemReader()); trash = Files.createTempDirectory("tmp_"); // measure timer resolution before the test to avoid time critical tests // are affected by time needed for measurement diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java index a142166983..0e33fa6e76 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java @@ -46,13 +46,16 @@ package org.eclipse.jgit.internal.storage.reftable; import static org.eclipse.jgit.lib.Constants.HEAD; import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; import static org.eclipse.jgit.lib.Constants.R_HEADS; +import static org.eclipse.jgit.lib.MoreAsserts.assertThrows; import static org.eclipse.jgit.lib.Ref.Storage.NEW; import static org.eclipse.jgit.lib.Ref.Storage.PACKED; +import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -415,6 +418,54 @@ public class ReftableTest { } @Test + public void invalidRefWriteOrder() throws IOException { + Ref master = ref(MASTER, 1); + Ref next = ref(NEXT, 2); + ReftableWriter writer = new ReftableWriter() + .setMinUpdateIndex(1) + .setMaxUpdateIndex(1) + .begin(new ByteArrayOutputStream()); + + writer.writeRef(next); + IllegalArgumentException e = assertThrows( + IllegalArgumentException.class, + () -> writer.writeRef(master)); + assertThat(e.getMessage(), containsString("records must be increasing")); + } + + @Test + public void invalidReflogWriteOrderUpdateIndex() throws IOException { + ReftableWriter writer = new ReftableWriter() + .setMinUpdateIndex(1) + .setMaxUpdateIndex(2) + .begin(new ByteArrayOutputStream()); + PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60); + String msg = "test"; + + writer.writeLog(MASTER, 1, who, ObjectId.zeroId(), id(1), msg); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> writer.writeLog( + MASTER, 2, who, ObjectId.zeroId(), id(2), msg)); + assertThat(e.getMessage(), containsString("records must be increasing")); + } + + @Test + public void invalidReflogWriteOrderName() throws IOException { + ReftableWriter writer = new ReftableWriter() + .setMinUpdateIndex(1) + .setMaxUpdateIndex(1) + .begin(new ByteArrayOutputStream()); + PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60); + String msg = "test"; + + writer.writeLog(NEXT, 1, who, ObjectId.zeroId(), id(1), msg); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> writer.writeLog( + MASTER, 1, who, ObjectId.zeroId(), id(2), msg)); + assertThat(e.getMessage(), containsString("records must be increasing")); + } + + @Test public void withReflog() throws IOException { Ref master = ref(MASTER, 1); Ref next = ref(NEXT, 2); @@ -472,6 +523,74 @@ public class ReftableTest { } @Test + public void reflogSeek() throws IOException { + PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60); + String msg = "test"; + String msgNext = "test next"; + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + ReftableWriter writer = new ReftableWriter() + .setMinUpdateIndex(1) + .setMaxUpdateIndex(1) + .begin(buffer); + + writer.writeLog(MASTER, 1, who, ObjectId.zeroId(), id(1), msg); + writer.writeLog(NEXT, 1, who, ObjectId.zeroId(), id(2), msgNext); + + writer.finish(); + byte[] table = buffer.toByteArray(); + + ReftableReader t = read(table); + try (LogCursor c = t.seekLog(MASTER, Long.MAX_VALUE)) { + assertTrue(c.next()); + assertEquals(c.getReflogEntry().getComment(), msg); + } + try (LogCursor c = t.seekLog(MASTER, 0)) { + assertFalse(c.next()); + } + try (LogCursor c = t.seekLog(MASTER, 1)) { + assertTrue(c.next()); + assertEquals(c.getUpdateIndex(), 1); + assertEquals(c.getReflogEntry().getComment(), msg); + } + try (LogCursor c = t.seekLog(NEXT, Long.MAX_VALUE)) { + assertTrue(c.next()); + assertEquals(c.getReflogEntry().getComment(), msgNext); + } + try (LogCursor c = t.seekLog(NEXT, 0)) { + assertFalse(c.next()); + } + try (LogCursor c = t.seekLog(NEXT, 1)) { + assertTrue(c.next()); + assertEquals(c.getUpdateIndex(), 1); + assertEquals(c.getReflogEntry().getComment(), msgNext); + } + } + + @Test + public void reflogSeekPrefix() throws IOException { + PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60); + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + ReftableWriter writer = new ReftableWriter() + .setMinUpdateIndex(1) + .setMaxUpdateIndex(1) + .begin(buffer); + + writer.writeLog("branchname", 1, who, ObjectId.zeroId(), id(1), "branchname"); + + writer.finish(); + byte[] table = buffer.toByteArray(); + + ReftableReader t = read(table); + try (LogCursor c = t.seekLog("branch", Long.MAX_VALUE)) { + // We find a reflog block, but the iteration won't confuse branchname + // and branch. + assertFalse(c.next()); + } + } + + @Test public void onlyReflog() throws IOException { PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60); String msg = "test"; @@ -505,6 +624,8 @@ public class ReftableTest { assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId()); assertEquals(id(1), lc.getReflogEntry().getNewId()); assertEquals(who, lc.getReflogEntry().getWho()); + // compare string too, to catch tz differences. + assertEquals(who.toExternalString(), lc.getReflogEntry().getWho().toExternalString()); assertEquals(msg, lc.getReflogEntry().getComment()); assertTrue(lc.next()); @@ -532,7 +653,7 @@ public class ReftableTest { List<Ref> refs = new ArrayList<>(); for (int i = 1; i <= 5670; i++) { - Ref ref = ref(String.format("refs/heads/%03d", i), i); + Ref ref = ref(String.format("refs/heads/%04d", i), i); refs.add(ref); writer.writeRef(ref); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java index d5a07e02fa..cafec04f7c 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java @@ -80,6 +80,7 @@ public class LocalDiskRefTreeDatabaseTest extends LocalDiskRepositoryTestCase { @Override @Before public void setUp() throws Exception { + super.setUp(); FileRepository init = createWorkRepository(); FileBasedConfig cfg = init.getConfig(); cfg.setInt("core", null, "repositoryformatversion", 1); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/FileBasedConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/FileBasedConfigTest.java index 43648206f0..5100d258d7 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/FileBasedConfigTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/FileBasedConfigTest.java @@ -56,9 +56,11 @@ import java.nio.file.StandardOpenOption; import java.util.StringTokenizer; import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.junit.MockSystemReader; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.SystemReader; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -90,6 +92,7 @@ public class FileBasedConfigTest { @Before public void setUp() throws Exception { + SystemReader.setInstance(new MockSystemReader()); trash = Files.createTempDirectory("tmp_"); FS.getFileStoreAttributes(trash.getParent()); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java index 99d435100b..89a2fc44c7 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java @@ -67,6 +67,7 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import org.eclipse.jgit.errors.CommandFailedException; +import org.eclipse.jgit.junit.MockSystemReader; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.RepositoryCache; import org.junit.After; @@ -79,6 +80,7 @@ public class FSTest { @Before public void setUp() throws Exception { + SystemReader.setInstance(new MockSystemReader()); trash = File.createTempFile("tmp_", ""); trash.delete(); assertTrue("mkdir " + trash, trash.mkdir()); 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 new file mode 100644 index 0000000000..87349a25a4 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FS_POSIXTest.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2019, Vishal Devgire <vishaldevgire@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.util; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.eclipse.jgit.junit.MockSystemReader; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class FS_POSIXTest { + private SystemReader originalSystemReaderInstance; + + private FileBasedConfig systemConfig; + + private FileBasedConfig userConfig; + + private Path tmp; + + @Before + public void setUp() throws Exception { + tmp = Files.createTempDirectory("jgit_test_"); + MockSystemReader mockSystemReader = new MockSystemReader(); + SystemReader.setInstance(mockSystemReader); + + // 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 + // the same one here + FS.getFileStoreAttributes(tmp.getParent()); + systemConfig = new FileBasedConfig( + new File(tmp.toFile(), "systemgitconfig"), FS.DETECTED); + userConfig = new FileBasedConfig(systemConfig, + new File(tmp.toFile(), "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 in the middle of writing or deleting files, + // which would disrupt this. + userConfig.setBoolean(ConfigConstants.CONFIG_GC_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTODETACH, false); + userConfig.save(); + mockSystemReader.setSystemGitConfig(systemConfig); + mockSystemReader.setUserGitConfig(userConfig); + + originalSystemReaderInstance = SystemReader.getInstance(); + SystemReader.setInstance(mockSystemReader); + } + + @After + public void tearDown() throws IOException { + SystemReader.setInstance(originalSystemReaderInstance); + FileUtils.delete(tmp.toFile(), FileUtils.RECURSIVE | FileUtils.RETRY); + } + + @Test + public void supportsAtomicCreateNewFile_shouldReturnSupportedAsDefault() { + assertTrue(new FS_POSIX().supportsAtomicCreateNewFile()); + } + + @Test + public void supportsAtomicCreateNewFile_shouldReturnTrueIfFlagIsSetInUserConfig() { + setAtomicCreateCreationFlag(userConfig, "true"); + assertTrue(new FS_POSIX().supportsAtomicCreateNewFile()); + } + + @Test + public void supportsAtomicCreateNewFile_shouldReturnTrueIfFlagIsSetInSystemConfig() { + setAtomicCreateCreationFlag(systemConfig, "true"); + assertTrue(new FS_POSIX().supportsAtomicCreateNewFile()); + } + + @Test + public void supportsAtomicCreateNewFile_shouldReturnFalseIfFlagUnsetInUserConfig() { + setAtomicCreateCreationFlag(userConfig, "false"); + assertFalse(new FS_POSIX().supportsAtomicCreateNewFile()); + } + + @Test + public void supportsAtomicCreateNewFile_shouldReturnFalseIfFlagUnsetInSystemConfig() { + setAtomicCreateCreationFlag(systemConfig, "false"); + assertFalse(new FS_POSIX().supportsAtomicCreateNewFile()); + } + + private void setAtomicCreateCreationFlag(FileBasedConfig config, + String value) { + config.setString(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_SUPPORTSATOMICFILECREATION, value); + } +} diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters index 3a5dd11f5d..17054cd88c 100644 --- a/org.eclipse.jgit/.settings/.api_filters +++ b/org.eclipse.jgit/.settings/.api_filters @@ -49,14 +49,6 @@ </message_arguments> </filter> </resource> - <resource path="src/org/eclipse/jgit/storage/file/FileBasedConfig.java" type="org.eclipse.jgit.storage.file.FileBasedConfig"> - <filter id="1142947843"> - <message_arguments> - <message_argument value="5.1.9"/> - <message_argument value="load(boolean)"/> - </message_arguments> - </filter> - </resource> <resource path="src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java" type="org.eclipse.jgit.treewalk.WorkingTreeIterator"> <filter id="1142947843"> <message_arguments> @@ -158,4 +150,30 @@ </message_arguments> </filter> </resource> + <resource path="src/org/eclipse/jgit/util/SystemReader.java" type="org.eclipse.jgit.util.SystemReader"> + <filter id="336695337"> + <message_arguments> + <message_argument value="org.eclipse.jgit.util.SystemReader"/> + <message_argument value="getSystemConfig()"/> + </message_arguments> + </filter> + <filter id="336695337"> + <message_arguments> + <message_argument value="org.eclipse.jgit.util.SystemReader"/> + <message_argument value="getUserConfig()"/> + </message_arguments> + </filter> + <filter id="1142947843"> + <message_arguments> + <message_argument value="5.1.9"/> + <message_argument value="getSystemConfig()"/> + </message_arguments> + </filter> + <filter id="1142947843"> + <message_arguments> + <message_argument value="5.1.9"/> + <message_argument value="getUserConfig()"/> + </message_arguments> + </filter> + </resource> </component> 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 e26163e24a..2a2ee8ddc7 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -17,6 +17,7 @@ applyingCommit=Applying {0} archiveFormatAlreadyAbsent=Archive format already absent: {0} archiveFormatAlreadyRegistered=Archive format already registered with different implementation: {0} argumentIsNotAValidCommentString=Invalid comment: {0} +assumeAtomicCreateNewFile=Reading option "core.supportsAtomicFileCreation" failed, fallback to default assuming atomic file creation is supported atLeastOnePathIsRequired=At least one path is required. atLeastOnePatternIsRequired=At least one pattern is required. atLeastTwoFiltersNeeded=At least two filters needed. @@ -311,6 +312,7 @@ expectedPktLineWithService=expected pkt-line with ''# service=-'', got ''{0}'' expectedReceivedContentType=expected Content-Type {0}; received Content-Type {1} expectedReportForRefNotReceived={0}: expected report for ref {1} not received failedAtomicFileCreation=Atomic file creation failed, number of hard links to file {0} was not 2 but {1} +failedCreateLockFile=Creating lock file {} failed failedToDetermineFilterDefinition=An exception occurred while determining filter definitions failedUpdatingRefs=failed updating refs failureDueToOneOfTheFollowing=Failure due to one of the following: @@ -446,6 +448,7 @@ localRefIsMissingObjects=Local ref {0} is missing object(s). localRepository=local repository lockCountMustBeGreaterOrEqual1=lockCount must be >= 1 lockError=lock error: {0} +lockFailedRetry=locking {0} failed after {1} retries lockOnNotClosed=Lock on {0} not closed. lockOnNotHeld=Lock on {0} not held. malformedpersonIdentString=Malformed PersonIdent string (no < was found): {0} @@ -580,6 +583,7 @@ pushNotPermitted=push not permitted pushOptionsNotSupported=Push options not supported; received {0} rawLogMessageDoesNotParseAsLogEntry=Raw log message does not parse as log entry readConfigFailed=Reading config file ''{0}'' failed +readFileStoreAttributesFailed=Reading FileStore attributes from user config failed readerIsRequired=Reader is required readingObjectsFromLocalRepositoryFailed=reading objects from local repository failed: {0} readLastModifiedFailed=Reading lastModified of {0} failed @@ -598,6 +602,7 @@ refAlreadyExists=already exists refAlreadyExists1=Ref {0} already exists reflogEntryNotFound=Entry {0} not found in reflog for ''{1}'' refNotResolved=Ref {0} cannot be resolved +refTableRecordsMustIncrease=records must be increasing: last {0}, this {1} refUpdateReturnCodeWas=RefUpdate return code was: {0} remoteConfigHasNoURIAssociated=Remote config "{0}" has no URIs associated remoteDoesNotHaveSpec=Remote does not have {0} available for fetch. @@ -638,6 +643,7 @@ s3ActionDeletion=Deletion s3ActionReading=Reading s3ActionWriting=Writing searchForReachableBranches=Finding reachable branches +saveFileStoreAttributesFailed=Saving measured FileStore attributes to user config failed searchForReuse=Finding sources searchForSizes=Getting sizes secondsAgo={0} seconds ago @@ -797,7 +803,7 @@ uriNotConfigured=Submodule URI not configured uriNotFound={0} not found uriNotFoundWithMessage={0} not found: {1} URINotSupported=URI not supported: {0} -userConfigFileInvalid=User config file {0} invalid {1} +userConfigInvalid=Git config in the user's home directory {0} is invalid {1} validatingGitModules=Validating .gitmodules files walkFailure=Walk failure. wantNoSpaceWithCapabilities=No space between oid and first capability in first want line diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java index c6f3c671a9..38e5444523 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java @@ -279,9 +279,13 @@ public class ApplyCommand extends GitCommand<ApplyResult> { } private boolean isNoNewlineAtEndOfFile(FileHeader fh) { - HunkHeader lastHunk = fh.getHunks().get(fh.getHunks().size() - 1); + List<? extends HunkHeader> hunks = fh.getHunks(); + if (hunks == null || hunks.isEmpty()) { + return false; + } + HunkHeader lastHunk = hunks.get(hunks.size() - 1); RawText lhrt = new RawText(lastHunk.getBuffer()); - return lhrt.getString(lhrt.size() - 1).equals( - "\\ No newline at end of file"); //$NON-NLS-1$ + return lhrt.getString(lhrt.size() - 1) + .equals("\\ No newline at end of file"); //$NON-NLS-1$ } } 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 320036c52a..649f77724e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -78,6 +78,7 @@ public class JGitText extends TranslationBundle { /***/ public String archiveFormatAlreadyAbsent; /***/ public String archiveFormatAlreadyRegistered; /***/ public String argumentIsNotAValidCommentString; + /***/ public String assumeAtomicCreateNewFile; /***/ public String atLeastOnePathIsRequired; /***/ public String atLeastOnePatternIsRequired; /***/ public String atLeastTwoFiltersNeeded; @@ -372,6 +373,7 @@ public class JGitText extends TranslationBundle { /***/ public String expectedReceivedContentType; /***/ public String expectedReportForRefNotReceived; /***/ public String failedAtomicFileCreation; + /***/ public String failedCreateLockFile; /***/ public String failedToDetermineFilterDefinition; /***/ public String failedUpdatingRefs; /***/ public String failureDueToOneOfTheFollowing; @@ -507,6 +509,7 @@ public class JGitText extends TranslationBundle { /***/ public String localRepository; /***/ public String lockCountMustBeGreaterOrEqual1; /***/ public String lockError; + /***/ public String lockFailedRetry; /***/ public String lockOnNotClosed; /***/ public String lockOnNotHeld; /***/ public String malformedpersonIdentString; @@ -641,6 +644,7 @@ public class JGitText extends TranslationBundle { /***/ public String pushOptionsNotSupported; /***/ public String rawLogMessageDoesNotParseAsLogEntry; /***/ public String readConfigFailed; + /***/ public String readFileStoreAttributesFailed; /***/ public String readerIsRequired; /***/ public String readingObjectsFromLocalRepositoryFailed; /***/ public String readLastModifiedFailed; @@ -659,6 +663,7 @@ public class JGitText extends TranslationBundle { /***/ public String refAlreadyExists1; /***/ public String reflogEntryNotFound; /***/ public String refNotResolved; + /***/ public String refTableRecordsMustIncrease; /***/ public String refUpdateReturnCodeWas; /***/ public String remoteConfigHasNoURIAssociated; /***/ public String remoteDoesNotHaveSpec; @@ -698,6 +703,7 @@ public class JGitText extends TranslationBundle { /***/ public String s3ActionDeletion; /***/ public String s3ActionReading; /***/ public String s3ActionWriting; + /***/ public String saveFileStoreAttributesFailed; /***/ public String searchForReachableBranches; /***/ public String searchForReuse; /***/ public String searchForSizes; @@ -857,7 +863,7 @@ public class JGitText extends TranslationBundle { /***/ public String uriNotFound; /***/ public String uriNotFoundWithMessage; /***/ public String URINotSupported; - /***/ public String userConfigFileInvalid; + /***/ public String userConfigInvalid; /***/ public String validatingGitModules; /***/ public String walkFailure; /***/ public String wantNoSpaceWithCapabilities; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java index 90772970ae..4f5f8a613e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java @@ -79,15 +79,17 @@ import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.ReflogReader; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.storage.pack.PackConfig; -import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.StringUtils; import org.eclipse.jgit.util.SystemReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Represents a Git repository. A repository holds all objects and refs used for @@ -114,10 +116,10 @@ import org.eclipse.jgit.util.SystemReader; * This implementation only handles a subtly undocumented subset of git features. */ public class FileRepository extends Repository { + private static final Logger LOG = LoggerFactory + .getLogger(FileRepository.class); private static final String UNNAMED = "Unnamed repository; edit this file to name it for gitweb."; //$NON-NLS-1$ - private final FileBasedConfig systemConfig; - private final FileBasedConfig userConfig; private final FileBasedConfig repoConfig; private final RefDatabase refs; private final ObjectDirectory objectDatabase; @@ -176,32 +178,16 @@ public class FileRepository extends Repository { */ public FileRepository(BaseRepositoryBuilder options) throws IOException { super(options); - - if (StringUtils.isEmptyOrNull(SystemReader.getInstance().getenv( - Constants.GIT_CONFIG_NOSYSTEM_KEY))) - systemConfig = SystemReader.getInstance().openSystemConfig(null, - getFS()); - else - systemConfig = new FileBasedConfig(null, FS.DETECTED) { - @Override - public void load() { - // empty, do not load - } - - @Override - public boolean isOutdated() { - // regular class would bomb here - return false; - } - }; - userConfig = SystemReader.getInstance().openUserConfig(systemConfig, - getFS()); + StoredConfig userConfig = null; + try { + userConfig = SystemReader.getInstance().getUserConfig(); + } catch (ConfigInvalidException e) { + LOG.error(e.getMessage(), e); + throw new IOException(e.getMessage(), e); + } repoConfig = new FileBasedConfig(userConfig, getFS().resolve( getDirectory(), Constants.CONFIG), getFS()); - - loadSystemConfig(); - loadUserConfig(); loadRepoConfig(); repoConfig.addChangeListener(this::fireEvent); @@ -240,28 +226,6 @@ public class FileRepository extends Repository { } } - private void loadSystemConfig() throws IOException { - try { - systemConfig.load(); - } catch (ConfigInvalidException e) { - throw new IOException(MessageFormat.format(JGitText - .get().systemConfigFileInvalid, systemConfig.getFile() - .getAbsolutePath(), - e), e); - } - } - - private void loadUserConfig() throws IOException { - try { - userConfig.load(); - } catch (ConfigInvalidException e) { - throw new IOException(MessageFormat.format(JGitText - .get().userConfigFileInvalid, userConfig.getFile() - .getAbsolutePath(), - e), e); - } - } - private void loadRepoConfig() throws IOException { try { repoConfig.load(); @@ -402,26 +366,13 @@ public class FileRepository extends Repository { /** {@inheritDoc} */ @Override public FileBasedConfig getConfig() { - if (systemConfig.isOutdated()) { - try { - loadSystemConfig(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - if (userConfig.isOutdated()) { - try { - loadUserConfig(); - } catch (IOException e) { - throw new RuntimeException(e); + try { + SystemReader.getInstance().getUserConfig(); + if (repoConfig.isOutdated()) { + loadRepoConfig(); } - } - if (repoConfig.isOutdated()) { - try { - loadRepoConfig(); - } catch (IOException e) { - throw new RuntimeException(e); - } + } catch (IOException | ConfigInvalidException e) { + throw new RuntimeException(e); } return repoConfig; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java index fa8732d14a..6af41256d6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java @@ -161,7 +161,12 @@ public class LockFile { */ public boolean lock() throws IOException { FileUtils.mkdirs(lck.getParentFile(), true); - token = FS.DETECTED.createNewFileAtomic(lck); + try { + token = FS.DETECTED.createNewFileAtomic(lck); + } catch (IOException e) { + LOG.error(JGitText.get().failedCreateLockFile, lck, e); + throw e; + } if (token.isCreated()) { haveLck = true; try { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java index 44529bfff2..b66751b94c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java @@ -84,7 +84,11 @@ import org.eclipse.jgit.util.LongList; import org.eclipse.jgit.util.NB; import org.eclipse.jgit.util.RawParseUtils; -/** Reads a single block for {@link ReftableReader}. */ +/** + * Reads a single block for {@link ReftableReader}. Instances are tied to a + * specific block in the file so are not reused for other blocks. Instances hold + * an offset into the block. + */ class BlockReader { private byte blockType; private long endPosition; @@ -141,6 +145,8 @@ class BlockReader { return RawParseUtils.decode(UTF_8, nameBuf, 0, len); } + // Matches the key against a name or a prefix. For reflogs, only the + // refname is matched, and the updateIndex suffix is ignored. boolean match(byte[] match, boolean matchIsPrefix) { int len = nameLen; if (blockType == LOG_BLOCK_TYPE) { @@ -551,7 +557,9 @@ class BlockReader { } private short readInt16() { - return (short) NB.decodeUInt16(buf, ptr += 2); + short result =(short) NB.decodeUInt16(buf, ptr); + ptr += 2; + return result; } private int readVarint32() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java index bf3a9aeca0..4f0ff2d1d1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java @@ -255,6 +255,32 @@ public class ReftableReader extends Reftable { block.seekKey(key); return block; } + if (blockType == LOG_BLOCK_TYPE) { + // No index. Log blocks are irregularly sized, so we can't do binary + // search between blocks. Scan over blocks instead. + BlockReader block = readBlock(startPos, endPos); + + for (;;) { + if (block == null || block.type() != LOG_BLOCK_TYPE) { + return null; + } + + int result = block.seekKey(key); + if (result <= 0) { + // == 0 : we found the key. + // < 0 : the key is before this block. Either the ref name is there + // but only at a newer updateIndex, or it is absent. We leave it to + // logcursor to distinguish between both cases. + return block; + } + + long pos = block.endPosition(); + if (pos >= endPos) { + return null; + } + block = readBlock(pos, endPos); + } + } return binarySearch(blockType, key, startPos, endPos); } @@ -529,12 +555,20 @@ public class ReftableReader extends Reftable { private class LogCursorImpl extends LogCursor { private final long scanEnd; private final byte[] match; - + private String refName; private long updateIndex; private ReflogEntry entry; BlockReader block; + /** + * Scans logs from this table until scanEnd position. + * + * @param scanEnd + * end of the log data in the reftable. + * @param match + * if non-null, limits the scan to precisely that refname. + */ LogCursorImpl(long scanEnd, byte[] match) { this.scanEnd = scanEnd; this.match = match; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableWriter.java index f8b9ffd176..6459c2763d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableWriter.java @@ -44,6 +44,7 @@ package org.eclipse.jgit.internal.storage.reftable; import static java.lang.Math.log; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.jgit.internal.storage.reftable.BlockWriter.padBetweenBlocks; import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_FOOTER_LEN; import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN; @@ -59,6 +60,7 @@ import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; import java.io.IOException; import java.io.OutputStream; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -69,6 +71,7 @@ import java.util.Set; import java.util.zip.CRC32; import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.reftable.BlockWriter.DeleteLogEntry; import org.eclipse.jgit.internal.storage.reftable.BlockWriter.Entry; import org.eclipse.jgit.internal.storage.reftable.BlockWriter.IndexEntry; @@ -108,6 +111,8 @@ public class ReftableWriter { private ReftableOutputStream out; private ObjectIdSubclassMap<RefList> obj2ref; + private BlockWriter.Entry lastRef; + private BlockWriter.Entry lastLog; private BlockWriter cur; private Section refs; private Section objs; @@ -120,6 +125,8 @@ public class ReftableWriter { */ public ReftableWriter() { this(new ReftableConfig()); + lastRef = null; + lastLog = null; } /** @@ -269,10 +276,22 @@ public class ReftableWriter { throw new IllegalArgumentException(); } long d = updateIndex - minUpdateIndex; - long blockPos = refs.write(new RefEntry(ref, d)); + RefEntry entry = new RefEntry(ref, d); + if (lastRef != null && Entry.compare(lastRef, entry) >= 0) { + throwIllegalEntry(lastRef, entry); + } + lastRef = entry; + + long blockPos = refs.write(entry); indexRef(ref, blockPos); } + private void throwIllegalEntry(Entry last, Entry now) { + throw new IllegalArgumentException(MessageFormat.format( + JGitText.get().refTableRecordsMustIncrease, + new String(last.key, UTF_8), new String(now.key, UTF_8))); + } + private void indexRef(Ref ref, long blockPos) { if (indexObjects && !ref.isSymbolic()) { indexId(ref.getObjectId(), blockPos); @@ -322,7 +341,12 @@ public class ReftableWriter { throws IOException { String msg = message != null ? message : ""; //$NON-NLS-1$ beginLog(); - logs.write(new LogEntry(ref, updateIndex, who, oldId, newId, msg)); + LogEntry entry = new LogEntry(ref, updateIndex, who, oldId, newId, msg); + if (lastLog != null && Entry.compare(lastLog, entry) >= 0) { + throwIllegalEntry(lastLog, entry); + } + lastLog = entry; + logs.write(entry); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java index 9df593375d..74c712c46c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java @@ -135,6 +135,9 @@ public class ObjectIdOwnerMap<V extends ObjectIdOwnerMap.Entry> */ @SuppressWarnings("unchecked") public V get(AnyObjectId toFind) { + if (toFind == null) { + return null; + } int h = toFind.w1; V obj = directory[h & mask][h >>> SEGMENT_SHIFT]; for (; obj != null; obj = (V) obj.next) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java index c0fcd4161f..ebbb3a4844 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java @@ -137,11 +137,13 @@ public class RebaseTodoFile { if (skip != -1) { // try to parse the line as non-comment line = parseLine(buf, skip, lineEnd); - // successfully parsed as non-comment line - // mark this line as a comment explicitly - line.setAction(Action.COMMENT); - // use the read line as comment string - line.setComment(commentString); + if (line != null) { + // successfully parsed as non-comment line + // mark this line as a comment explicitly + line.setAction(Action.COMMENT); + // use the read line as comment string + line.setComment(commentString); + } } } catch (Exception e) { // parsing as non-comment line failed diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java index e3e9d41de3..11db7c5998 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java @@ -50,6 +50,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URISyntaxException; +import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; @@ -72,6 +73,8 @@ import org.bouncycastle.gpg.keybox.UserID; import org.bouncycastle.gpg.keybox.jcajce.JcaKeyBoxBuilder; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; @@ -115,6 +118,9 @@ class BouncyCastleGpgKeyLocator { private static final Path USER_SECRET_KEY_DIR = GPG_DIRECTORY .resolve("private-keys-v1.d"); //$NON-NLS-1$ + private static final Path USER_PGP_PUBRING_FILE = GPG_DIRECTORY + .resolve("pubring.gpg"); //$NON-NLS-1$ + private static final Path USER_PGP_LEGACY_SECRING_FILE = GPG_DIRECTORY .resolve("secring.gpg"); //$NON-NLS-1$ @@ -250,8 +256,13 @@ class BouncyCastleGpgKeyLocator { } /** - * Use pubring.kbx when available, if not fallback to secring.gpg or secret - * key path provided to parse and return secret key + * If there is a private key directory containing keys, use pubring.kbx or + * pubring.gpg to find the public key; then try to find the secret key in + * the directory. + * <p> + * If there is no private key directory (or it doesn't contain any keys), + * try to find the key in secring.gpg directly. + * </p> * * @return the secret key * @throws IOException @@ -259,51 +270,97 @@ class BouncyCastleGpgKeyLocator { * @throws NoSuchAlgorithmException * @throws NoSuchProviderException * @throws PGPException - * in case of issues finding a key + * in case of issues finding a key, including no key found * @throws CanceledException * @throws URISyntaxException * @throws UnsupportedCredentialItem */ + @NonNull public BouncyCastleGpgKey findSecretKey() throws IOException, NoSuchAlgorithmException, NoSuchProviderException, PGPException, CanceledException, UnsupportedCredentialItem, URISyntaxException { BouncyCastleGpgKey key; - if (exists(USER_KEYBOX_PATH)) { - try { - key = loadKeyFromKeybox(USER_KEYBOX_PATH); - if (key != null) { - return key; + PGPPublicKey publicKey = null; + if (hasKeyFiles(USER_SECRET_KEY_DIR)) { + // Use pubring.kbx or pubring.gpg to find the public key, then try + // the key files in the directory. If the public key was found in + // pubring.gpg also try secring.gpg to find the secret key. + if (exists(USER_KEYBOX_PATH)) { + try { + publicKey = findPublicKeyInKeyBox(USER_KEYBOX_PATH); + if (publicKey != null) { + key = findSecretKeyForKeyBoxPublicKey(publicKey, + USER_KEYBOX_PATH); + if (key != null) { + return key; + } + throw new PGPException(MessageFormat.format( + JGitText.get().gpgNoSecretKeyForPublicKey, + Long.toHexString(publicKey.getKeyID()))); + } + throw new PGPException(MessageFormat.format( + JGitText.get().gpgNoPublicKeyFound, signingKey)); + } catch (NoOpenPgpKeyException e) { + // There are no OpenPGP keys in the keybox at all: try the + // pubring.gpg, if it exists. + if (log.isDebugEnabled()) { + log.debug("{} does not contain any OpenPGP keys", //$NON-NLS-1$ + USER_KEYBOX_PATH); + } + } + } + if (exists(USER_PGP_PUBRING_FILE)) { + publicKey = findPublicKeyInPubring(USER_PGP_PUBRING_FILE); + if (publicKey != null) { + // GPG < 2.1 may have both; the agent using the directory + // and gpg using secring.gpg. GPG >= 2.1 delegates all + // secret key handling to the agent and doesn't use + // secring.gpg at all, even if it exists. Which means for us + // we have to try both since we don't know which GPG version + // the user has. + key = findSecretKeyForKeyBoxPublicKey(publicKey, + USER_PGP_PUBRING_FILE); + if (key != null) { + return key; + } } + } + if (publicKey == null) { throw new PGPException(MessageFormat.format( JGitText.get().gpgNoPublicKeyFound, signingKey)); - } catch (NoOpenPgpKeyException e) { - // Ignore and try the secring.gpg, if it exists. - if (log.isDebugEnabled()) { - log.debug("{} does not contain any OpenPGP keys", //$NON-NLS-1$ - USER_KEYBOX_PATH); - } } + // We found a public key, but didn't find the secret key in the + // private key directory. Go try the secring.gpg. } + boolean hasSecring = false; if (exists(USER_PGP_LEGACY_SECRING_FILE)) { + hasSecring = true; key = loadKeyFromSecring(USER_PGP_LEGACY_SECRING_FILE); if (key != null) { return key; } + } + if (publicKey != null) { + throw new PGPException(MessageFormat.format( + JGitText.get().gpgNoSecretKeyForPublicKey, + Long.toHexString(publicKey.getKeyID()))); + } else if (hasSecring) { + // publicKey == null: user has _only_ pubring.gpg/secring.gpg. throw new PGPException(MessageFormat.format( JGitText.get().gpgNoKeyInLegacySecring, signingKey)); + } else { + throw new PGPException(JGitText.get().gpgNoKeyring); } - throw new PGPException(JGitText.get().gpgNoKeyring); } - private BouncyCastleGpgKey loadKeyFromKeybox(Path keybox) - throws NoOpenPgpKeyException, NoSuchAlgorithmException, - NoSuchProviderException, IOException, CanceledException, - UnsupportedCredentialItem, PGPException, URISyntaxException { - PGPPublicKey publicKey = findPublicKeyInKeyBox(keybox); - if (publicKey != null) { - return findSecretKeyForKeyBoxPublicKey(publicKey, keybox); + private boolean hasKeyFiles(Path dir) { + try (DirectoryStream<Path> contents = Files.newDirectoryStream(dir, + "*.key")) { //$NON-NLS-1$ + return contents.iterator().hasNext(); + } catch (IOException e) { + // Not a directory, or something else + return false; } - return null; } private BouncyCastleGpgKey loadKeyFromSecring(Path secring) @@ -353,9 +410,7 @@ class BouncyCastleGpgKeyLocator { } passphrasePrompt.clear(); - throw new PGPException(MessageFormat.format( - JGitText.get().gpgNoSecretKeyForPublicKey, - Long.toHexString(publicKey.getKeyID()))); + return null; } catch (RuntimeException e) { passphrasePrompt.clear(); throw e; @@ -417,6 +472,51 @@ class BouncyCastleGpgKeyLocator { return null; } + /** + * Return the first public key matching the key id ({@link #signingKey}. + * + * @param pubringFile + * + * @return the PGP public key, or {@code null} if none found + * @throws IOException + * on I/O related errors + * @throws PGPException + * on BouncyCastle errors + */ + private PGPPublicKey findPublicKeyInPubring(Path pubringFile) + throws IOException, PGPException { + try (InputStream in = newInputStream(pubringFile)) { + PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection( + new BufferedInputStream(in), + new JcaKeyFingerprintCalculator()); + + String keyId = signingKey.toLowerCase(Locale.ROOT); + Iterator<PGPPublicKeyRing> keyrings = pgpPub.getKeyRings(); + while (keyrings.hasNext()) { + PGPPublicKeyRing keyRing = keyrings.next(); + Iterator<PGPPublicKey> keys = keyRing.getPublicKeys(); + while (keys.hasNext()) { + PGPPublicKey key = keys.next(); + // try key id + String fingerprint = Hex.toHexString(key.getFingerprint()) + .toLowerCase(Locale.ROOT); + if (fingerprint.endsWith(keyId)) { + return key; + } + // try user id + Iterator<String> userIDs = key.getUserIDs(); + while (userIDs.hasNext()) { + String userId = userIDs.next(); + if (containsSigningKey(userId)) { + return key; + } + } + } + } + } + return null; + } + private PGPPublicKey getFirstPublicKey(KeyBlob keyBlob) throws IOException { return ((PublicKeyRingBlob) keyBlob).getPGPPublicKeyRing() .getPublicKey(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java index 10b00b77a0..5bb8153a58 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java @@ -148,38 +148,14 @@ public class FileBasedConfig extends StoredConfig { */ @Override public void load() throws IOException, ConfigInvalidException { - load(true); - } - - /** - * Load the configuration as a Git text style configuration file. - * <p> - * If the file does not exist, this configuration is cleared, and thus - * behaves the same as though the file exists, but is empty. - * - * @param useFileSnapshotWithConfig - * if {@code true} use the FileSnapshot with config, otherwise - * use it without config - * @throws IOException - * if IO failed - * @throws ConfigInvalidException - * if config is invalid - * @since 5.1.9 - */ - public void load(boolean useFileSnapshotWithConfig) - throws IOException, ConfigInvalidException { final int maxRetries = 5; int retryDelayMillis = 20; int retries = 0; while (true) { final FileSnapshot oldSnapshot = snapshot; final FileSnapshot newSnapshot; - if (useFileSnapshotWithConfig) { - newSnapshot = FileSnapshot.save(getFile()); - } else { - // don't use config in this snapshot to avoid endless recursion - newSnapshot = FileSnapshot.saveNoConfig(getFile()); - } + // don't use config in this snapshot to avoid endless recursion + newSnapshot = FileSnapshot.saveNoConfig(getFile()); try { final byte[] in = IO.readFully(getFile()); final ObjectId newHash = hash(in); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java index 36ff2836ae..be7111d904 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java @@ -53,8 +53,7 @@ import java.util.function.Supplier; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Config; -import org.eclipse.jgit.storage.file.FileBasedConfig; -import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.util.StringUtils; import org.eclipse.jgit.util.SystemReader; import org.slf4j.Logger; @@ -274,14 +273,12 @@ public class HttpConfig { * to get the configuration values for */ public HttpConfig(URIish uri) { - FileBasedConfig userConfig = SystemReader.getInstance() - .openUserConfig(null, FS.DETECTED); + StoredConfig userConfig = null; try { - userConfig.load(); + userConfig = SystemReader.getInstance().getUserConfig(); } catch (IOException | ConfigInvalidException e) { // Log it and then work with default values. - LOG.error(MessageFormat.format(JGitText.get().userConfigFileInvalid, - userConfig.getFile().getAbsolutePath(), e)); + LOG.error(e.getMessage(), e); init(new Config(), uri); return; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java index d5b06c1f6d..3029327c52 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java @@ -77,6 +77,7 @@ import java.net.HttpCookie; import java.net.MalformedURLException; import java.net.Proxy; import java.net.ProxySelector; +import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.InvalidPathException; @@ -121,11 +122,9 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.lib.SymbolicRef; -import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.transport.HttpAuthMethod.Type; import org.eclipse.jgit.transport.HttpConfig.HttpRedirectMode; import org.eclipse.jgit.transport.http.HttpConnection; -import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.HttpSupport; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; @@ -593,7 +592,8 @@ public class TransportHttp extends HttpTransport implements WalkTransport, JGitText.get().redirectsOff, Integer.valueOf(status))); } - URIish newUri = redirect(conn.getHeaderField(HDR_LOCATION), + URIish newUri = redirect(u, + conn.getHeaderField(HDR_LOCATION), Constants.INFO_REFS, redirects++); setURI(newUri); u = getServiceURL(service); @@ -792,19 +792,18 @@ public class TransportHttp extends HttpTransport implements WalkTransport, } private void updateSslVerifyUser(boolean value) { - FileBasedConfig userConfig = SystemReader.getInstance() - .openUserConfig(null, FS.DETECTED); + StoredConfig userConfig = null; try { - userConfig.load(); + userConfig = SystemReader.getInstance().getUserConfig(); updateSslVerify(userConfig, value); } catch (IOException | ConfigInvalidException e) { // Log it, but otherwise ignore here. - LOG.error(MessageFormat.format(JGitText.get().userConfigFileInvalid, - userConfig.getFile().getAbsolutePath(), e)); + LOG.error(e.getMessage(), e); } } - private URIish redirect(String location, String checkFor, int redirects) + private URIish redirect(URL currentUrl, String location, String checkFor, + int redirects) throws TransportException { if (location == null || location.isEmpty()) { throw new TransportException(uri, @@ -818,13 +817,16 @@ public class TransportHttp extends HttpTransport implements WalkTransport, location)); } try { - if (!isValidRedirect(baseUrl, location, checkFor)) { + URI redirectTo = new URI(location); + redirectTo = currentUrl.toURI().resolve(redirectTo); + String redirected = redirectTo.toASCIIString(); + if (!isValidRedirect(baseUrl, redirected, checkFor)) { throw new TransportException(uri, MessageFormat.format(JGitText.get().redirectBlocked, - baseUrl, location)); + baseUrl, redirected)); } - location = location.substring(0, location.indexOf(checkFor)); - URIish result = new URIish(location); + redirected = redirected.substring(0, redirected.indexOf(checkFor)); + URIish result = new URIish(redirected); if (LOG.isInfoEnabled()) { LOG.info(MessageFormat.format(JGitText.get().redirectHttp, uri.setPass(null), @@ -1454,7 +1456,8 @@ public class TransportHttp extends HttpTransport implements WalkTransport, // Let openResponse() issue an error return; } - currentUri = redirect(conn.getHeaderField(HDR_LOCATION), + currentUri = redirect(conn.getURL(), + conn.getHeaderField(HDR_LOCATION), '/' + serviceName, redirects++); try { baseUrl = toURL(currentUri); 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 421bf81494..90305013f5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java @@ -100,7 +100,7 @@ import org.eclipse.jgit.internal.storage.file.FileSnapshot; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry; import org.eclipse.jgit.treewalk.FileTreeIterator.FileModeStrategy; import org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry; @@ -510,18 +510,12 @@ public abstract class FS { private static Optional<FileStoreAttributes> readFromConfig( FileStore s) { - FileBasedConfig userConfig = SystemReader.getInstance() - .openUserConfig(null, FS.DETECTED); + StoredConfig userConfig; try { - userConfig.load(false); - } catch (IOException e) { - LOG.error(MessageFormat.format(JGitText.get().readConfigFailed, - userConfig.getFile().getAbsolutePath()), e); - } catch (ConfigInvalidException e) { - LOG.error(MessageFormat.format( - JGitText.get().repositoryConfigFileInvalid, - userConfig.getFile().getAbsolutePath(), - e.getMessage())); + userConfig = SystemReader.getInstance().getUserConfig(); + } catch (IOException | ConfigInvalidException e) { + LOG.error(JGitText.get().readFileStoreAttributesFailed, e); + return Optional.empty(); } String key = getConfigKey(s); Duration resolution = Duration.ofNanos(userConfig.getTimeUnit( @@ -544,8 +538,13 @@ public abstract class FS { private static void saveToConfig(FileStore s, FileStoreAttributes c) { - FileBasedConfig userConfig = SystemReader.getInstance() - .openUserConfig(null, FS.DETECTED); + StoredConfig userConfig; + try { + userConfig = SystemReader.getInstance().getUserConfig(); + } catch (IOException | ConfigInvalidException e) { + LOG.error(JGitText.get().saveFileStoreAttributesFailed, e); + return; + } long resolution = c.getFsTimestampResolution().toNanos(); TimeUnit resolutionUnit = getUnit(resolution); long resolutionValue = resolutionUnit.convert(resolution, @@ -562,7 +561,7 @@ public abstract class FS { String key = getConfigKey(s); while (!succeeded && retries < max_retries) { try { - userConfig.load(false); + userConfig.load(); userConfig.setString( ConfigConstants.CONFIG_FILESYSTEM_SECTION, key, ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION, @@ -580,22 +579,30 @@ public abstract class FS { } catch (LockFailedException e) { // race with another thread, wait a bit and try again try { - LOG.warn(MessageFormat.format(JGitText.get().cannotLock, - userConfig.getFile().getAbsolutePath())); retries++; - Thread.sleep(20); + if (retries < max_retries) { + Thread.sleep(100); + LOG.debug("locking {} failed, retries {}/{}", //$NON-NLS-1$ + userConfig, Integer.valueOf(retries), + Integer.valueOf(max_retries)); + } else { + LOG.warn(MessageFormat.format( + JGitText.get().lockFailedRetry, userConfig, + Integer.valueOf(retries))); + } } catch (InterruptedException e1) { - Thread.interrupted(); + Thread.currentThread().interrupt(); + break; } } catch (IOException e) { LOG.error(MessageFormat.format( - JGitText.get().cannotSaveConfig, - userConfig.getFile().getAbsolutePath()), e); + JGitText.get().cannotSaveConfig, userConfig), e); + break; } catch (ConfigInvalidException e) { LOG.error(MessageFormat.format( JGitText.get().repositoryConfigFileInvalid, - userConfig.getFile().getAbsolutePath(), - e.getMessage())); + userConfig, e.getMessage())); + break; } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java index 6ec50c24e0..a485389a9a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java @@ -42,15 +42,20 @@ */ package org.eclipse.jgit.util; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_SUPPORTSATOMICFILECREATION; + import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.nio.charset.Charset; +import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileStore; import java.nio.file.FileSystemException; import java.nio.file.Files; +import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.PosixFilePermission; @@ -69,10 +74,9 @@ import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.CommandFailedException; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.lib.StoredConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -89,7 +93,7 @@ public class FS_POSIX extends FS { private static final Map<FileStore, Boolean> CAN_HARD_LINK = new ConcurrentHashMap<>(); - private volatile AtomicFileCreation supportsAtomicCreateNewFile = AtomicFileCreation.UNDEFINED; + private volatile AtomicFileCreation supportsAtomicFileCreation = AtomicFileCreation.UNDEFINED; private enum AtomicFileCreation { SUPPORTED, NOT_SUPPORTED, UNDEFINED @@ -114,37 +118,6 @@ public class FS_POSIX extends FS { } } - private void determineAtomicFileCreationSupport() { - // @TODO: enhance SystemReader to support this without copying code - AtomicFileCreation ret = getAtomicFileCreationSupportOption( - SystemReader.getInstance().openUserConfig(null, this)); - if (ret == AtomicFileCreation.UNDEFINED - && StringUtils.isEmptyOrNull(SystemReader.getInstance() - .getenv(Constants.GIT_CONFIG_NOSYSTEM_KEY))) { - ret = getAtomicFileCreationSupportOption( - SystemReader.getInstance().openSystemConfig(null, this)); - } - supportsAtomicCreateNewFile = ret; - } - - private AtomicFileCreation getAtomicFileCreationSupportOption( - FileBasedConfig config) { - try { - config.load(); - String value = config.getString(ConfigConstants.CONFIG_CORE_SECTION, - null, - ConfigConstants.CONFIG_KEY_SUPPORTSATOMICFILECREATION); - if (value == null) { - return AtomicFileCreation.UNDEFINED; - } - return StringUtils.toBoolean(value) - ? AtomicFileCreation.SUPPORTED - : AtomicFileCreation.NOT_SUPPORTED; - } catch (IOException | ConfigInvalidException e) { - return AtomicFileCreation.SUPPORTED; - } - } - /** {@inheritDoc} */ @Override public FS newInstance() { @@ -359,10 +332,24 @@ public class FS_POSIX extends FS { /** {@inheritDoc} */ @Override public boolean supportsAtomicCreateNewFile() { - if (supportsAtomicCreateNewFile == AtomicFileCreation.UNDEFINED) { - determineAtomicFileCreationSupport(); + if (supportsAtomicFileCreation == AtomicFileCreation.UNDEFINED) { + try { + StoredConfig config = SystemReader.getInstance().getUserConfig(); + String value = config.getString(CONFIG_CORE_SECTION, null, + CONFIG_KEY_SUPPORTSATOMICFILECREATION); + if (value != null) { + supportsAtomicFileCreation = StringUtils.toBoolean(value) + ? AtomicFileCreation.SUPPORTED + : AtomicFileCreation.NOT_SUPPORTED; + } else { + supportsAtomicFileCreation = AtomicFileCreation.SUPPORTED; + } + } catch (IOException | ConfigInvalidException e) { + LOG.warn(JGitText.get().assumeAtomicCreateNewFile, e); + supportsAtomicFileCreation = AtomicFileCreation.SUPPORTED; + } } - return supportsAtomicCreateNewFile == AtomicFileCreation.SUPPORTED; + return supportsAtomicFileCreation == AtomicFileCreation.SUPPORTED; } @Override @@ -438,7 +425,7 @@ public class FS_POSIX extends FS { * An implementation of the File#createNewFile() semantics which can create * a unique file atomically also on NFS. If the config option * {@code core.supportsAtomicCreateNewFile = true} (which is the default) - * then simply File#createNewFile() is called. + * then simply Files#createFile() is called. * * But if {@code core.supportsAtomicCreateNewFile = false} then after * successful creation of the lock file a hard link to that lock file is @@ -459,14 +446,17 @@ public class FS_POSIX extends FS { */ @Override public LockToken createNewFileAtomic(File file) throws IOException { - if (!file.createNewFile()) { + Path path; + try { + path = file.toPath(); + Files.createFile(path); + } catch (FileAlreadyExistsException | InvalidPathException e) { return token(false, null); } if (supportsAtomicCreateNewFile()) { return token(true, null); } Path link = null; - Path path = file.toPath(); FileStore store = null; try { store = Files.getFileStore(path); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java index 4a773b05bd..4d791e470a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java @@ -56,6 +56,7 @@ import java.nio.file.CopyOption; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.LinkOption; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; @@ -697,9 +698,14 @@ public class FileUtils { try { return Files.getLastModifiedTime(path, LinkOption.NOFOLLOW_LINKS) .toInstant(); + } catch (NoSuchFileException e) { + LOG.debug( + "Cannot read lastModifiedInstant since path {} does not exist", //$NON-NLS-1$ + path); + return Instant.EPOCH; } catch (IOException e) { LOG.error(MessageFormat - .format(JGitText.get().readLastModifiedFailed, path)); + .format(JGitText.get().readLastModifiedFailed, path), e); return Instant.ofEpochMilli(path.toFile().lastModified()); } } 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 d04f087d58..8431196cb5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java @@ -47,6 +47,7 @@ package org.eclipse.jgit.util; import java.io.File; +import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.security.AccessController; @@ -56,12 +57,17 @@ import java.text.SimpleDateFormat; import java.util.Locale; import java.util.TimeZone; +import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectChecker; +import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.util.time.MonotonicClock; import org.eclipse.jgit.util.time.MonotonicSystemClock; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Interface to read values from the system. @@ -72,6 +78,10 @@ import org.eclipse.jgit.util.time.MonotonicSystemClock; * </p> */ public abstract class SystemReader { + + private final static Logger LOG = LoggerFactory + .getLogger(SystemReader.class); + private static final SystemReader DEFAULT; private static Boolean isMacOS; @@ -87,6 +97,10 @@ public abstract class SystemReader { private static class Default extends SystemReader { private volatile String hostname; + private volatile FileBasedConfig systemConfig; + + private volatile FileBasedConfig userConfig; + @Override public String getenv(String variable) { return System.getenv(variable); @@ -99,28 +113,69 @@ public abstract class SystemReader { @Override public FileBasedConfig openSystemConfig(Config parent, FS fs) { - File configFile = fs.getGitSystemConfig(); - if (configFile == null) { - return new FileBasedConfig(parent, null, fs) { - @Override - public void load() { - // empty, do not load - } - - @Override - public boolean isOutdated() { - // regular class would bomb here - return false; - } - }; + if (systemConfig == null) { + systemConfig = createSystemConfig(parent, fs); } - return new FileBasedConfig(parent, configFile, fs); + return systemConfig; + } + + protected FileBasedConfig createSystemConfig(Config parent, FS fs) { + if (StringUtils.isEmptyOrNull(getenv(Constants.GIT_CONFIG_NOSYSTEM_KEY))) { + File configFile = fs.getGitSystemConfig(); + if (configFile != null) { + return new FileBasedConfig(parent, configFile, fs); + } + } + return new FileBasedConfig(null, fs) { + @Override + public void load() { + // empty, do not load + } + + @Override + public boolean isOutdated() { + // regular class would bomb here + return false; + } + }; } @Override public FileBasedConfig openUserConfig(Config parent, FS fs) { - final File home = fs.userHome(); - return new FileBasedConfig(parent, new File(home, ".gitconfig"), fs); //$NON-NLS-1$ + if (userConfig == null) { + File home = fs.userHome(); + userConfig = new FileBasedConfig(parent, + new File(home, ".gitconfig"), fs); //$NON-NLS-1$ + } + return userConfig; + } + + @Override + public StoredConfig getSystemConfig() + throws IOException, ConfigInvalidException { + if (systemConfig == null) { + systemConfig = createSystemConfig(null, FS.DETECTED); + } + if (systemConfig.isOutdated()) { + LOG.debug("loading system config {}", systemConfig); //$NON-NLS-1$ + systemConfig.load(); + } + return systemConfig; + } + + @Override + public StoredConfig getUserConfig() + throws IOException, ConfigInvalidException { + if (userConfig == null) { + userConfig = openUserConfig(getSystemConfig(), FS.DETECTED); + } else { + getSystemConfig(); + } + if (userConfig.isOutdated()) { + LOG.debug("loading user config {}", userConfig); //$NON-NLS-1$ + userConfig.load(); + } + return userConfig; } @Override @@ -149,19 +204,19 @@ public abstract class SystemReader { } } - private static SystemReader INSTANCE = DEFAULT; + private static volatile SystemReader INSTANCE = DEFAULT; /** - * Get time since epoch, with up to millisecond resolution. + * Get the current SystemReader instance * - * @return time since epoch, with up to millisecond resolution. + * @return the current SystemReader instance. */ public static SystemReader getInstance() { return INSTANCE; } /** - * Set the new instance to use when accessing properties. + * Set a new SystemReader instance to use when accessing properties. * * @param newReader * the new instance to use when accessing properties, or null for @@ -225,7 +280,10 @@ public abstract class SystemReader { public abstract String getProperty(String key); /** - * Open the git configuration found in the user home + * Open the git configuration found in the user home. Use + * {@link #getUserConfig()} to get the current git configuration in the user + * home since it manages automatic reloading when the gitconfig file was + * modified and avoids unnecessary reloads. * * @param parent * a config with values not found directly in the returned config @@ -237,7 +295,10 @@ public abstract class SystemReader { public abstract FileBasedConfig openUserConfig(Config parent, FS fs); /** - * Open the gitconfig configuration found in the system-wide "etc" directory + * Open the gitconfig configuration found in the system-wide "etc" + * directory. Use {@link #getSystemConfig()} to get the current system-wide + * git configuration since it manages automatic reloading when the gitconfig + * file was modified and avoids unnecessary reloads. * * @param parent * a config with values not found directly in the returned @@ -251,6 +312,38 @@ public abstract class SystemReader { public abstract FileBasedConfig openSystemConfig(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 + * reloads the system config if the system config file was modified. If the + * configuration file wasn't modified returns the cached configuration. + * + * @return the git configuration found in the user home + * @throws ConfigInvalidException + * if configuration is invalid + * @throws IOException + * if something went wrong when reading files + * @since 5.1.9 + */ + public abstract StoredConfig getUserConfig() + throws IOException, ConfigInvalidException; + + /** + * Get the gitconfig configuration found in the system-wide "etc" directory. + * The configuration will be reloaded automatically if the configuration + * file was modified otherwise returns the cached system level config. + * + * @return the gitconfig configuration found in the system-wide "etc" + * directory + * @throws ConfigInvalidException + * if configuration is invalid + * @throws IOException + * if something went wrong when reading files + * @since 5.1.9 + */ + public abstract StoredConfig getSystemConfig() + throws IOException, ConfigInvalidException; + + /** * Get the current system time * * @return the current system time @@ -200,7 +200,7 @@ <slf4j-version>1.7.2</slf4j-version> <log4j-version>1.2.15</log4j-version> <maven-javadoc-plugin-version>3.1.1</maven-javadoc-plugin-version> - <tycho-extras-version>1.3.0</tycho-extras-version> + <tycho-extras-version>1.4.0</tycho-extras-version> <gson-version>2.8.2</gson-version> <bouncycastle-version>1.61</bouncycastle-version> <spotbugs-maven-plugin-version>3.1.12.2</spotbugs-maven-plugin-version> |