From b61d817c9c4ca4023644fa0d231e8303140963c2 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 17 Dec 2020 10:06:55 +0100 Subject: [PATCH] Implement mercurial cgi protocol as extension (#1458) * Handle mercurial cgi protocol with an hg extension * Refactor CGI executor to allow command arguments * Unify python scripts and hg extensions * Implement new auto configuration * Use HKEY_LOCAL_MACHINE instead of HKEY_CURRENT_USER registry keys to find mercurial installations on windows --- CHANGELOG.md | 1 + .../scm/web/cgi/AbstractCGIExecutor.java | 16 +- .../java/sonia/scm/web/cgi/CGIExecutor.java | 133 +++--- .../main/java/sonia/scm/web/cgi/EnvList.java | 221 +++------ .../java/sonia/scm/web/cgi/EnvListTest.java | 51 +-- .../scm/api/v2/resources/HgConfigDto.java | 3 - .../HgConfigInstallationsResource.java | 117 ----- .../v2/resources/HgConfigPackageResource.java | 142 ------ .../HgConfigPackagesToDtoMapper.java | 83 ---- .../api/v2/resources/HgConfigResource.java | 26 +- .../api/v2/resources/UpdateHgConfigDto.java | 6 - .../AutoConfigModule.java} | 30 +- .../scm/autoconfig/AutoConfigurator.java | 18 +- .../AutoConfiguratorProvider.java} | 61 ++- .../NoOpAutoConfigurator.java} | 52 +-- .../scm/autoconfig/PosixAutoConfigurator.java | 152 +------ .../autoconfig/WindowsAutoConfigurator.java | 129 ++++++ .../WindowsRegistry.java} | 22 +- .../scm/installer/AbstractHgInstaller.java | 65 --- .../java/sonia/scm/installer/HgInstaller.java | 104 ----- .../java/sonia/scm/installer/HgPackage.java | 253 ----------- .../scm/installer/HgPackageInstaller.java | 267 ----------- .../sonia/scm/installer/HgPackageReader.java | 235 ---------- .../java/sonia/scm/installer/HgPackages.java | 89 ---- .../sonia/scm/installer/MacOSHgInstaller.java | 220 --------- .../sonia/scm/installer/UnixHgInstaller.java | 141 ------ .../scm/installer/WindowsHgInstaller.java | 418 ------------------ .../DefaultHgEnvironmentBuilder.java | 4 - .../java/sonia/scm/repository/HgConfig.java | 78 +--- ...{HgPythonScript.java => HgExtensions.java} | 14 +- .../scm/repository/HgRepositoryHandler.java | 82 ++-- .../java/sonia/scm/repository/HgVerifier.java | 118 +++++ .../scm/repository/HgWindowsPackageFix.java | 330 -------------- .../scm/repository/spi/HgVersionCommand.java | 70 +-- .../spi/SimpleHgWorkingCopyFactory.java | 6 +- .../spi/javahg/HgFileviewExtension.java | 34 +- .../main/java/sonia/scm/web/HgCGIServlet.java | 82 ++-- .../java/sonia/scm/web/HgServletModule.java | 7 - .../src/main/java/sonia/scm/web/HgUtil.java | 25 -- .../src/main/js/HgConfigurationForm.tsx | 8 +- .../main/resources/locales/de/plugins.json | 6 - .../main/resources/locales/en/plugins.json | 6 - .../scm/python/{hgweb.py => cgiserve.py} | 37 +- .../sonia/scm/{hg/ext => python}/fileview.py | 0 .../scm/{hg/ext => python}/fileview_test.py | 0 .../resources/sonia/scm/python/scmversion.py | 41 ++ .../resources/sonia/scm/version/scm-hg-plugin | 1 - ...HgConfigAutoConfigurationResourceTest.java | 6 +- .../HgConfigDtoToHgConfigMapperTest.java | 6 - .../HgConfigInstallationsResourceTest.java | 139 ------ .../HgConfigPackageResourceTest.java | 228 ---------- .../HgConfigPackagesToDtoMapperTest.java | 90 ---- .../v2/resources/HgConfigResourceTest.java | 11 +- .../scm/api/v2/resources/HgConfigTests.java | 35 +- .../scm/autoconfig/AutoConfigModuleTest.java} | 30 +- .../AutoConfiguratorProviderTest.java} | 57 ++- .../autoconfig/PosixAutoConfiguratorTest.java | 142 +++--- .../WindowsAutoConfiguratorTest.java | 143 ++++++ .../DefaultHgEnvironmentBuilderTest.java | 25 +- .../repository/HgRepositoryHandlerTest.java | 17 +- .../java/sonia/scm/repository/HgTestUtil.java | 4 +- .../sonia/scm/repository/HgVerifierTest.java | 163 +++++++ .../repository/HgWindowsPackageFixTest.java | 139 ------ .../repository/spi/HgVersionCommandTest.java | 101 +---- .../sonia/scm/web/cgi/DefaultCGIExecutor.java | 151 +++---- 65 files changed, 1164 insertions(+), 4327 deletions(-) delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsResource.java delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackagesToDtoMapper.java rename scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/{api/v2/resources/HgConfigInstallationsToDtoMapper.java => autoconfig/AutoConfigModule.java} (59%) rename scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/{api/v2/resources/HgConfigPackagesDto.java => autoconfig/AutoConfiguratorProvider.java} (54%) rename scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/{installer/HgInstallerFactory.java => autoconfig/NoOpAutoConfigurator.java} (60%) create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/WindowsAutoConfigurator.java rename scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/{api/v2/resources/HgConfigInstallationsDto.java => autoconfig/WindowsRegistry.java} (74%) delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/AbstractHgInstaller.java delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgInstaller.java delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackage.java delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageInstaller.java delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageReader.java delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackages.java delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/MacOSHgInstaller.java delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/UnixHgInstaller.java delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/WindowsHgInstaller.java rename scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/{HgPythonScript.java => HgExtensions.java} (88%) create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVerifier.java delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgWindowsPackageFix.java rename scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/{hgweb.py => cgiserve.py} (55%) rename scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/{hg/ext => python}/fileview.py (100%) rename scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/{hg/ext => python}/fileview_test.py (100%) create mode 100644 scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmversion.py delete mode 100644 scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/version/scm-hg-plugin delete mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java delete mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java delete mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackagesToDtoMapperTest.java rename scm-plugins/scm-hg-plugin/src/{main/java/sonia/scm/repository/HgVersion.java => test/java/sonia/scm/autoconfig/AutoConfigModuleTest.java} (69%) rename scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/{api/v2/resources/HgConfigInstallationsToDtoMapperTest.java => autoconfig/AutoConfiguratorProviderTest.java} (52%) create mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/autoconfig/WindowsAutoConfiguratorTest.java create mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgVerifierTest.java delete mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgWindowsPackageFixTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 887160e68e..56803d0d44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Send mercurial hook callbacks over separate tcp socket instead of http ([#1416](https://github.com/scm-manager/scm-manager/pull/1416)) +- Implement mercurial cgi protocol as extension ([#1458](https://github.com/scm-manager/scm-manager/pull/1458)) ### Fixed - Language detection of files with interpreter parameters e.g.: `#!/usr/bin/make -f` ([#1450](https://github.com/scm-manager/scm-manager/issues/1450)) diff --git a/scm-core/src/main/java/sonia/scm/web/cgi/AbstractCGIExecutor.java b/scm-core/src/main/java/sonia/scm/web/cgi/AbstractCGIExecutor.java index 31bbfb1ab1..40913669d7 100644 --- a/scm-core/src/main/java/sonia/scm/web/cgi/AbstractCGIExecutor.java +++ b/scm-core/src/main/java/sonia/scm/web/cgi/AbstractCGIExecutor.java @@ -21,12 +21,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.web.cgi; //~--- JDK imports ------------------------------------------------------------ import java.io.File; +import java.util.Collections; +import java.util.List; /** * @@ -233,8 +235,20 @@ public abstract class AbstractCGIExecutor implements CGIExecutor this.workDirectory = workDirectory; } + @Override + public void setArgs(List args) { + this.args = args; + } + + @Override + public List getArgs() { + return args; + } + //~--- fields --------------------------------------------------------------- + protected List args = Collections.emptyList(); + /** Field description */ protected int bufferSize; diff --git a/scm-core/src/main/java/sonia/scm/web/cgi/CGIExecutor.java b/scm-core/src/main/java/sonia/scm/web/cgi/CGIExecutor.java index d81c205dad..7498b3803d 100644 --- a/scm-core/src/main/java/sonia/scm/web/cgi/CGIExecutor.java +++ b/scm-core/src/main/java/sonia/scm/web/cgi/CGIExecutor.java @@ -21,103 +21,112 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.web.cgi; //~--- JDK imports ------------------------------------------------------------ +import javax.servlet.ServletException; import java.io.File; import java.io.IOException; - -import javax.servlet.ServletException; +import java.util.Collections; +import java.util.List; /** * * @author Sebastian Sdorra */ -public interface CGIExecutor -{ +public interface CGIExecutor { /** Field description */ - public static final String ENV_AUTH_TYPE = "AUTH_TYPE"; + String ENV_AUTH_TYPE = "AUTH_TYPE"; /** Field description */ - public static final String ENV_CONTENT_LENGTH = "CONTENT_LENGTH"; + String ENV_CONTENT_LENGTH = "CONTENT_LENGTH"; /** Field description */ - public static final String ENV_CONTENT_TYPE = "CONTENT_TYPE"; + String ENV_CONTENT_TYPE = "CONTENT_TYPE"; /** Field description */ - public static final String ENV_GATEWAY_INTERFACE = "GATEWAY_INTERFACE"; + String ENV_GATEWAY_INTERFACE = "GATEWAY_INTERFACE"; /** Field description */ - public static final String ENV_HTTPS = "HTTPS"; + String ENV_HTTPS = "HTTPS"; /** Field description */ - public static final String ENV_HTTPS_VALUE_OFF = "OFF"; + String ENV_HTTPS_VALUE_OFF = "OFF"; /** Field description */ - public static final String ENV_HTTPS_VALUE_ON = "ON"; + String ENV_HTTPS_VALUE_ON = "ON"; /** Field description */ - public static final String ENV_HTTP_HEADER_PREFIX = "HTTP_"; + String ENV_HTTP_HEADER_PREFIX = "HTTP_"; /** Field description */ - public static final String ENV_PATH_INFO = "PATH_INFO"; + String ENV_PATH_INFO = "PATH_INFO"; /** Field description */ - public static final String ENV_PATH_TRANSLATED = "PATH_TRANSLATED"; + String ENV_PATH_TRANSLATED = "PATH_TRANSLATED"; /** Field description */ - public static final String ENV_QUERY_STRING = "QUERY_STRING"; + String ENV_QUERY_STRING = "QUERY_STRING"; /** Field description */ - public static final String ENV_REMOTE_ADDR = "REMOTE_ADDR"; + String ENV_REMOTE_ADDR = "REMOTE_ADDR"; /** Field description */ - public static final String ENV_REMOTE_HOST = "REMOTE_HOST"; + String ENV_REMOTE_HOST = "REMOTE_HOST"; /** Field description */ - public static final String ENV_REMOTE_USER = "REMOTE_USER"; + String ENV_REMOTE_USER = "REMOTE_USER"; /** Field description */ - public static final String ENV_REQUEST_METHOD = "REQUEST_METHOD"; + String ENV_REQUEST_METHOD = "REQUEST_METHOD"; /** Field description */ - public static final String ENV_SCRIPT_FILENAME = "SCRIPT_FILENAME"; + String ENV_SCRIPT_FILENAME = "SCRIPT_FILENAME"; /** Field description */ - public static final String ENV_SCRIPT_NAME = "SCRIPT_NAME"; + String ENV_SCRIPT_NAME = "SCRIPT_NAME"; /** Field description */ - public static final String ENV_SERVER_NAME = "SERVER_NAME"; + String ENV_SERVER_NAME = "SERVER_NAME"; /** Field description */ - public static final String ENV_SERVER_PORT = "SERVER_PORT"; + String ENV_SERVER_PORT = "SERVER_PORT"; /** Field description */ - public static final String ENV_SERVER_PROTOCOL = "SERVER_PROTOCOL"; + String ENV_SERVER_PROTOCOL = "SERVER_PROTOCOL"; /** Field description */ - public static final String ENV_SERVER_SOFTWARE = "SERVER_SOFTWARE"; + String ENV_SERVER_SOFTWARE = "SERVER_SOFTWARE"; /** Field description */ - public static final String ENV_SYSTEM_ROOT = "SystemRoot"; + String ENV_SYSTEM_ROOT = "SystemRoot"; + + /** + * Content type header of response. + * @since 2.12.0 + */ + String RESPONSE_HEADER_CONTENT_TYPE = "Content-Type"; + + /** + * @deprecated use {@link #RESPONSE_HEADER_CONTENT_TYPE} instead. + */ + @Deprecated + String REPSONSE_HEADER_CONTENT_TYPE = RESPONSE_HEADER_CONTENT_TYPE; /** Field description */ - public static final String REPSONSE_HEADER_CONTENT_TYPE = "Content-Type"; + String RESPONSE_HEADER_CONTENT_LENGTH = "Content-Length"; /** Field description */ - public static final String RESPONSE_HEADER_CONTENT_LENGTH = "Content-Length"; + String RESPONSE_HEADER_HTTP_PREFIX = "HTTP"; /** Field description */ - public static final String RESPONSE_HEADER_HTTP_PREFIX = "HTTP"; + String RESPONSE_HEADER_LOCATION = "Location"; /** Field description */ - public static final String RESPONSE_HEADER_LOCATION = "Location"; - - /** Field description */ - public static final String RESPONSE_HEADER_STATUS = "Status"; + String RESPONSE_HEADER_STATUS = "Status"; //~--- methods -------------------------------------------------------------- @@ -130,7 +139,7 @@ public interface CGIExecutor * @throws IOException * @throws ServletException */ - public void execute(String cmd) throws IOException, ServletException; + void execute(String cmd) throws IOException, ServletException; //~--- get methods ---------------------------------------------------------- @@ -140,7 +149,7 @@ public interface CGIExecutor * * @return */ - public int getBufferSize(); + int getBufferSize(); /** * Method description @@ -148,7 +157,7 @@ public interface CGIExecutor * * @return */ - public EnvList getEnvironment(); + EnvList getEnvironment(); /** * Returns the cgi exception handler. @@ -157,7 +166,7 @@ public interface CGIExecutor * @return cgi exception handler * @since 1.8 */ - public CGIExceptionHandler getExceptionHandler(); + CGIExceptionHandler getExceptionHandler(); /** * Method description @@ -165,7 +174,7 @@ public interface CGIExecutor * * @return */ - public String getInterpreter(); + String getInterpreter(); /** * Returns the status code handler. @@ -174,7 +183,7 @@ public interface CGIExecutor * @return status code handler * @since 1.15 */ - public CGIStatusCodeHandler getStatusCodeHandler(); + CGIStatusCodeHandler getStatusCodeHandler(); /** * Method description @@ -182,7 +191,7 @@ public interface CGIExecutor * * @return */ - public File getWorkDirectory(); + File getWorkDirectory(); /** * Method description @@ -191,7 +200,7 @@ public interface CGIExecutor * @return * @since 1.12 */ - public boolean isContentLengthWorkaround(); + boolean isContentLengthWorkaround(); /** * Method description @@ -199,7 +208,7 @@ public interface CGIExecutor * * @return */ - public boolean isIgnoreExitCode(); + boolean isIgnoreExitCode(); /** * Method description @@ -207,7 +216,17 @@ public interface CGIExecutor * * @return */ - public boolean isPassShellEnvironment(); + boolean isPassShellEnvironment(); + + /** + * Returns command args as list. + * + * @return list of command args + * @since 2.12.0 + */ + default List getArgs() { + return Collections.emptyList(); + } //~--- set methods ---------------------------------------------------------- @@ -217,7 +236,7 @@ public interface CGIExecutor * * @param bufferSize */ - public void setBufferSize(int bufferSize); + void setBufferSize(int bufferSize); /** * Method description @@ -225,7 +244,7 @@ public interface CGIExecutor * * @param contentLengthWorkaround */ - public void setContentLengthWorkaround(boolean contentLengthWorkaround); + void setContentLengthWorkaround(boolean contentLengthWorkaround); /** * Method description @@ -233,7 +252,7 @@ public interface CGIExecutor * * @param environment */ - public void setEnvironment(EnvList environment); + void setEnvironment(EnvList environment); /** * Sets the cgi exception handler. @@ -242,7 +261,7 @@ public interface CGIExecutor * @param exceptionHandler cgi exception handler * @since 1.8 */ - public void setExceptionHandler(CGIExceptionHandler exceptionHandler); + void setExceptionHandler(CGIExceptionHandler exceptionHandler); /** * Method description @@ -250,7 +269,7 @@ public interface CGIExecutor * * @param ignoreExitCode */ - public void setIgnoreExitCode(boolean ignoreExitCode); + void setIgnoreExitCode(boolean ignoreExitCode); /** * Method description @@ -258,7 +277,7 @@ public interface CGIExecutor * * @param interpreter */ - public void setInterpreter(String interpreter); + void setInterpreter(String interpreter); /** * Method description @@ -266,7 +285,7 @@ public interface CGIExecutor * * @param passShellEnvironment */ - public void setPassShellEnvironment(boolean passShellEnvironment); + void setPassShellEnvironment(boolean passShellEnvironment); /** * Sets the status code handler. @@ -275,7 +294,7 @@ public interface CGIExecutor * @param statusCodeHandler the handler to set * @since 1.15 */ - public void setStatusCodeHandler(CGIStatusCodeHandler statusCodeHandler); + void setStatusCodeHandler(CGIStatusCodeHandler statusCodeHandler); /** * Method description @@ -283,5 +302,13 @@ public interface CGIExecutor * * @param workDirectory */ - public void setWorkDirectory(File workDirectory); + void setWorkDirectory(File workDirectory); + + /** + * Set command arguments. + * @param args command arguments + * @since 2.12.0 + */ + default void setArgs(List args) { + } } diff --git a/scm-core/src/main/java/sonia/scm/web/cgi/EnvList.java b/scm-core/src/main/java/sonia/scm/web/cgi/EnvList.java index 13491bd875..ac731a5240 100644 --- a/scm-core/src/main/java/sonia/scm/web/cgi/EnvList.java +++ b/scm-core/src/main/java/sonia/scm/web/cgi/EnvList.java @@ -21,76 +21,51 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.web.cgi; -//~--- non-JDK imports -------------------------------------------------------- - import com.google.common.base.Strings; -import com.google.common.collect.ForwardingMap; import com.google.common.collect.ImmutableSet; -import sonia.scm.util.Util; - -//~--- JDK imports ------------------------------------------------------------ - +import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; /** * * @author Sebastian Sdorra */ -public class EnvList -{ +public class EnvList { - /** Field description */ private static final ImmutableSet SENSITIVE = - ImmutableSet.of("HTTP_AUTHORIZATION", "SCM_CHALLENGE", "SCM_CREDENTIALS"); + ImmutableSet.of("HTTP_AUTHORIZATION", "SCM_CHALLENGE", "SCM_CREDENTIALS", "SCM_BEARER_TOKEN"); - //~--- constructors --------------------------------------------------------- + private final Map envMap; - /** - * Constructs ... - * - */ - public EnvList() - { + public EnvList() { envMap = new HashMap<>(); } - /** - * Constructs ... - * - * - * @param l - */ - public EnvList(EnvList l) - { - envMap = new HashMap<>(l.envMap); + public EnvList(EnvList list) { + envMap = new HashMap<>(list.envMap); } - //~--- methods -------------------------------------------------------------- /** - * Returns environment as mutable map. + * Set a name/value pair, null values will be treated as an empty String * - * @return environment as mutable map - * @since 1.31 + * @param name name of environment variable + * @param value value of environment variable */ - public Map asMutableMap() - { - return new MapDelegate(envMap); + public void set(String name, String value) { + envMap.put(name, Strings.nullToEmpty(value)); } /** - * Method description + * Return {@code true} if the list contains an environment variable with the given key. * - * - * @param key - * - * @return + * @param key name of environment variable + * @return {@code true} if contains environment variable */ public boolean containsKey(String key) { @@ -98,143 +73,69 @@ public class EnvList } /** - * Method description + * Representation suitable for passing to exec. * - * - * @return + * @return array of environment variables + * @since 2.12.0 */ + public String[] asArray() { + return envMap.entrySet() + .stream() + .map(e -> e.getKey() + "=" + e.getValue()) + .toArray(String[]::new); + } + + /** + * Representation suitable for passing to process builder. + * + * @return environment as immutable map + * @since 2.12.0 + */ + public Map asMap() { + return Collections.unmodifiableMap(envMap); + } + @Override - public String toString() - { + public String toString() { String s = System.getProperty("line.separator"); StringBuilder out = new StringBuilder("Environment:"); - - Iterator it = envMap.values().iterator(); - - String v; - - while (it.hasNext()) - { - v = converSensitive(it.next()); - out.append(s).append(" ").append(v); + for (Map.Entry e : envMap.entrySet()) { + out + .append(s).append(" ") + .append(e.getKey()).append("=").append(convertSensitive(e.getKey(), e.getValue())); } - return out.toString(); } - //~--- get methods ---------------------------------------------------------- + private String convertSensitive(String name, String value) { + if (SENSITIVE.contains(name)) { + return "(is set)"; + } + return value; + } /** * Get representation suitable for passing to exec. * - * @return + * @return array of environment variables + * @deprecated use {@link #asArray()} instead */ - public String[] getEnvArray() - { - return envMap.values().toArray(new String[envMap.size()]); + @Deprecated + public String[] getEnvArray() { + return asArray(); } - //~--- set methods ---------------------------------------------------------- - /** - * Set a name/value pair, null values will be treated as an empty String + * Returns environment as mutable map. * - * @param name - * @param value + * @return environment as mutable map + * @since 1.31 + * + * @deprecated the environment should only be modified by {@link #set(String, String)}. + * Of a {@link Map} is required, a immutable {@link Map} can be created with {@link #asMap()}. */ - public void set(String name, String value) - { - envMap.put(name, name.concat("=").concat(Util.nonNull(value))); + @Deprecated + public Map asMutableMap() { + return envMap; } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param v - * - * @return - */ - private String converSensitive(String v) - { - String result = v; - - for (String s : SENSITIVE) - { - if (v.startsWith(s)) - { - result = s.concat("=(is set)"); - - break; - } - } - - return result; - } - - //~--- inner classes -------------------------------------------------------- - - /** - * Class description - * - * - * @version Enter version here..., 13/05/15 - * @author Enter your name here... - */ - private static class MapDelegate extends ForwardingMap - { - - /** - * Constructs ... - * - * - * @param delegate - */ - private MapDelegate(Map delegate) - { - this.delegate = delegate; - } - - //~--- methods ------------------------------------------------------------ - - /** - * Method description - * - * - * @param key - * @param value - * - * @return - */ - @Override - public String put(String key, String value) - { - return super.put(key, key.concat("=").concat(Strings.nullToEmpty(value))); - } - - /** - * Method description - * - * - * @return - */ - @Override - protected Map delegate() - { - return delegate; - } - - //~--- fields ------------------------------------------------------------- - - /** Field description */ - private Map delegate; - } - - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private Map envMap; } diff --git a/scm-core/src/test/java/sonia/scm/web/cgi/EnvListTest.java b/scm-core/src/test/java/sonia/scm/web/cgi/EnvListTest.java index ba3b9d97d7..ce2b1cabf6 100644 --- a/scm-core/src/test/java/sonia/scm/web/cgi/EnvListTest.java +++ b/scm-core/src/test/java/sonia/scm/web/cgi/EnvListTest.java @@ -21,38 +21,39 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.web.cgi; -//~--- non-JDK imports -------------------------------------------------------- +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +class EnvListTest { -import static org.junit.Assert.*; - -/** - * - * @author Sebastian Sdorra - */ -public class EnvListTest -{ - - /** - * Method description - * - */ @Test - public void testToString() - { - EnvList envList = new EnvList(); + void shouldNotPrintAuthorizationValue() { + EnvList env = new EnvList(); + env.set("HTTP_AUTHORIZATION", "Basic xxx"); + env.set("SOME_OTHER", "other"); + env.set("SCM_BEARER_TOKEN", "secret"); - envList.set("HTTP_AUTHORIZATION", "Basic xxx"); - envList.set("SOME_OTHER", "other"); + String value = env.toString(); - String value = envList.toString(); + assertThat(value) + .contains("SOME_OTHER=other") + .contains("HTTP_AUTHORIZATION=(is set)") + .contains("SCM_BEARER_TOKEN=(is set)") + .doesNotContain("HTTP_AUTHORIZATION=Basic xxx") + .doesNotContain("SCM_BEARER_TOKEN=secret"); + } - assertTrue(value.contains("SOME_OTHER=other")); - assertFalse(value.contains("HTTP_AUTHORIZATION=Basic xxx")); - assertTrue(value.contains("HTTP_AUTHORIZATION=(is set)")); + @Test + void shouldReturnAsArray() { + EnvList env = new EnvList(); + env.set("SPACESHIPT", "Heart of Gold"); + env.set("DOMAIN", "hitchhiker.com"); + + assertThat(env.getEnvArray()) + .contains("SPACESHIPT=Heart of Gold") + .contains("DOMAIN=hitchhiker.com"); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDto.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDto.java index 3ddb33052f..ec9c28587f 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDto.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDto.java @@ -41,9 +41,6 @@ public class HgConfigDto extends HalRepresentation implements UpdateHgConfigDto private String encoding; private String hgBinary; - private String pythonBinary; - private String pythonPath; - private boolean useOptimizedBytecode; private boolean showRevisionInId; private boolean enableHttpPostArgs; diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsResource.java deleted file mode 100644 index 01cb7600ad..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsResource.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.api.v2.resources; - -import de.otto.edison.hal.HalRepresentation; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import sonia.scm.config.ConfigurationPermissions; -import sonia.scm.installer.HgInstallerFactory; -import sonia.scm.repository.HgConfig; -import sonia.scm.web.HgVndMediaType; -import sonia.scm.web.VndMediaType; - -import javax.inject.Inject; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; - -public class HgConfigInstallationsResource { - - public static final String PATH_HG = "hg"; - public static final String PATH_PYTHON = "python"; - private final HgConfigInstallationsToDtoMapper hgConfigInstallationsToDtoMapper; - - @Inject - public HgConfigInstallationsResource(HgConfigInstallationsToDtoMapper hgConfigInstallationsToDtoMapper) { - this.hgConfigInstallationsToDtoMapper = hgConfigInstallationsToDtoMapper; - } - - /** - * Returns the hg installations. - */ - @GET - @Path(PATH_HG) - @Produces(HgVndMediaType.INSTALLATIONS) - @Operation(summary = "Hg installations", description = "Returns the mercurial installations.", tags = "Mercurial") - @ApiResponse( - responseCode = "200", - description = "success", - content = @Content( - mediaType = HgVndMediaType.INSTALLATIONS, - schema = @Schema(implementation = HgConfigInstallationsDto.class) - ) - ) - @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") - @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"configuration:read:hg\" privilege") - @ApiResponse( - responseCode = "500", - description = "internal server error", - content = @Content( - mediaType = VndMediaType.ERROR_TYPE, - schema = @Schema(implementation = ErrorDto.class) - )) - public HalRepresentation getHgInstallations() { - - ConfigurationPermissions.read(HgConfig.PERMISSION).check(); - - return hgConfigInstallationsToDtoMapper.map( - HgInstallerFactory.createInstaller().getHgInstallations(), PATH_HG); - } - - /** - * Returns the python installations. - */ - @GET - @Path(PATH_PYTHON) - @Produces(HgVndMediaType.INSTALLATIONS) - @Operation(summary = "Python installations", description = "Returns the python installations.", tags = "Mercurial") - @ApiResponse( - responseCode = "200", - description = "success", - content = @Content( - mediaType = HgVndMediaType.INSTALLATIONS, - schema = @Schema(implementation = HgConfigInstallationsDto.class) - ) - ) - @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") - @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"configuration:read:hg\" privilege") - @ApiResponse( - responseCode = "500", - description = "internal server error", - content = @Content( - mediaType = VndMediaType.ERROR_TYPE, - schema = @Schema(implementation = ErrorDto.class) - )) - public HalRepresentation getPythonInstallations() { - - ConfigurationPermissions.read(HgConfig.PERMISSION).check(); - - return hgConfigInstallationsToDtoMapper.map( - HgInstallerFactory.createInstaller().getPythonInstallations(), PATH_PYTHON); - } -} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java deleted file mode 100644 index 909dc91a84..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.api.v2.resources; - -import de.otto.edison.hal.HalRepresentation; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import sonia.scm.SCMContext; -import sonia.scm.config.ConfigurationPermissions; -import sonia.scm.installer.HgInstallerFactory; -import sonia.scm.installer.HgPackage; -import sonia.scm.installer.HgPackageReader; -import sonia.scm.net.ahc.AdvancedHttpClient; -import sonia.scm.repository.HgConfig; -import sonia.scm.repository.HgRepositoryHandler; -import sonia.scm.web.HgVndMediaType; -import sonia.scm.web.VndMediaType; - -import javax.inject.Inject; -import javax.ws.rs.GET; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Response; - -public class HgConfigPackageResource { - - private final HgPackageReader pkgReader; - private final AdvancedHttpClient client; - private final HgRepositoryHandler handler; - private final HgConfigPackagesToDtoMapper configPackageCollectionToDtoMapper; - - @Inject - public HgConfigPackageResource(HgPackageReader pkgReader, AdvancedHttpClient client, HgRepositoryHandler handler, - HgConfigPackagesToDtoMapper hgConfigPackagesToDtoMapper) { - this.pkgReader = pkgReader; - this.client = client; - this.handler = handler; - this.configPackageCollectionToDtoMapper = hgConfigPackagesToDtoMapper; - } - - /** - * Returns all mercurial packages. - */ - @GET - @Path("") - @Produces(HgVndMediaType.PACKAGES) - @Operation(summary = "Hg configuration packages", description = "Returns all mercurial packages.", tags = "Mercurial") - @ApiResponse( - responseCode = "204", - description = "update success" - ) - @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") - @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"configuration:read:hg\" privilege") - @ApiResponse( - responseCode = "500", - description = "internal server error", - content = @Content( - mediaType = VndMediaType.ERROR_TYPE, - schema = @Schema(implementation = ErrorDto.class) - )) - public HalRepresentation getPackages() { - - ConfigurationPermissions.read(HgConfig.PERMISSION).check(); - - return configPackageCollectionToDtoMapper.map(pkgReader.getPackages()); - } - - /** - * Installs a mercurial package - * - * @param pkgId Identifier of the package to install - */ - @PUT - @Path("{pkgId}") - @Operation(summary = "Modifies hg configuration package", description = "Installs a mercurial package.", tags = "Mercurial") - @ApiResponse( - responseCode = "204", - description = "update success" - ) - @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") - @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"configuration:write:hg\" privilege") - @ApiResponse( - responseCode = "404", - description = "no package found for id", - content = @Content( - mediaType = VndMediaType.ERROR_TYPE, - schema = @Schema(implementation = ErrorDto.class) - )) - @ApiResponse( - responseCode = "500", - description = "internal server error", - content = @Content( - mediaType = VndMediaType.ERROR_TYPE, - schema = @Schema(implementation = ErrorDto.class) - )) - public Response installPackage(@PathParam("pkgId") String pkgId) { - Response response; - - ConfigurationPermissions.write(HgConfig.PERMISSION).check(); - - HgPackage pkg = pkgReader.getPackage(pkgId); - - if (pkg != null) { - if (HgInstallerFactory.createInstaller() - .installPackage(client, handler, SCMContext.getContext().getBaseDirectory(), pkg)) { - response = Response.noContent().build(); - } else { - response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); - } - } else { - response = Response.status(Response.Status.NOT_FOUND).build(); - } - - return response; - } -} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackagesToDtoMapper.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackagesToDtoMapper.java deleted file mode 100644 index 64b982114b..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackagesToDtoMapper.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.api.v2.resources; - -import de.otto.edison.hal.Links; -import lombok.Getter; -import org.mapstruct.AfterMapping; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.MappingTarget; -import sonia.scm.installer.HgPackage; -import sonia.scm.installer.HgPackages; - -import javax.inject.Inject; -import java.util.List; - -import static de.otto.edison.hal.Links.linkingTo; - -// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. -@SuppressWarnings("squid:S3306") -@Mapper -public abstract class HgConfigPackagesToDtoMapper { - - @Inject - private ScmPathInfoStore scmPathInfoStore; - - public HgConfigPackagesDto map(HgPackages hgpackages) { - return map(new HgPackagesNonIterable(hgpackages)); - } - - @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes - /* Favor warning "Unmapped target property: "attributes", to packages[].hgConfigTemplate" - Over error "Unknown property "packages[].hgConfigTemplate.attributes" - @Mapping(target = "packages[].hgConfigTemplate.attributes", ignore = true) // Also not for nested DTOs - */ - protected abstract HgConfigPackagesDto map(HgPackagesNonIterable hgPackagesNonIterable); - - @AfterMapping - void appendLinks(@MappingTarget HgConfigPackagesDto target) { - Links.Builder linksBuilder = linkingTo().self(createSelfLink()); - target.add(linksBuilder.build()); - } - - private String createSelfLink() { - LinkBuilder linkBuilder = new LinkBuilder(scmPathInfoStore.get(), HgConfigResource.class); - return linkBuilder.method("getPackagesResource").parameters().href(); - } - - /** - * Unfortunately, HgPackages is iterable, HgConfigPackagesDto does not need to be iterable and MapStruct refuses to - * map an iterable to a non-iterable. So use this little non-iterable "proxy". - */ - @Getter - static class HgPackagesNonIterable { - private List packages; - - HgPackagesNonIterable(HgPackages hgPackages) { - this.packages = hgPackages.getPackages(); - } - } -} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java index 0c715ce8c3..00ef38ade1 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.api.v2.resources; import io.swagger.v3.oas.annotations.OpenAPIDefinition; @@ -60,21 +60,17 @@ public class HgConfigResource { private final HgConfigDtoToHgConfigMapper dtoToConfigMapper; private final HgConfigToHgConfigDtoMapper configToDtoMapper; private final HgRepositoryHandler repositoryHandler; - private final Provider packagesResource; private final Provider autoconfigResource; - private final Provider installationsResource; @Inject - public HgConfigResource(HgConfigDtoToHgConfigMapper dtoToConfigMapper, HgConfigToHgConfigDtoMapper configToDtoMapper, - HgRepositoryHandler repositoryHandler, Provider packagesResource, - Provider autoconfigResource, - Provider installationsResource) { + public HgConfigResource(HgConfigDtoToHgConfigMapper dtoToConfigMapper, + HgConfigToHgConfigDtoMapper configToDtoMapper, + HgRepositoryHandler repositoryHandler, + Provider autoconfigResource) { this.dtoToConfigMapper = dtoToConfigMapper; this.configToDtoMapper = configToDtoMapper; this.repositoryHandler = repositoryHandler; - this.packagesResource = packagesResource; this.autoconfigResource = autoconfigResource; - this.installationsResource = installationsResource; } /** @@ -134,7 +130,7 @@ public class HgConfigResource { schema = @Schema(implementation = UpdateHgConfigDto.class), examples = @ExampleObject( name = "Overwrites current configuration with this one.", - value = "{\n \"disabled\":false,\n \"hgBinary\":\"hg\",\n \"pythonBinary\":\"python\",\n \"pythonPath\":\"\",\n \"encoding\":\"UTF-8\",\n \"useOptimizedBytecode\":false,\n \"showRevisionInId\":false,\n \"disableHookSSLValidation\":false,\n \"enableHttpPostArgs\":false\n}", + value = "{\n \"disabled\":false,\n \"hgBinary\":\"hg\",\n \"encoding\":\"UTF-8\",\n \"showRevisionInId\":false,\n \"enableHttpPostArgs\":false\n}", summary = "Simple update configuration" ) ) @@ -165,18 +161,8 @@ public class HgConfigResource { return Response.noContent().build(); } - @Path("packages") - public HgConfigPackageResource getPackagesResource() { - return packagesResource.get(); - } - @Path("auto-configuration") public HgConfigAutoConfigurationResource getAutoConfigurationResource() { return autoconfigResource.get(); } - - @Path("installations") - public HgConfigInstallationsResource getInstallationsResource() { - return installationsResource.get(); - } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/UpdateHgConfigDto.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/UpdateHgConfigDto.java index 7106c7c85a..126ca999c8 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/UpdateHgConfigDto.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/UpdateHgConfigDto.java @@ -29,14 +29,8 @@ interface UpdateHgConfigDto { String getHgBinary(); - String getPythonBinary(); - - String getPythonPath(); - String getEncoding(); - boolean isUseOptimizedBytecode(); - boolean isShowRevisionInId(); boolean isEnableHttpPostArgs(); diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapper.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/AutoConfigModule.java similarity index 59% rename from scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapper.java rename to scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/AutoConfigModule.java index 978b6d9408..35544448f0 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapper.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/AutoConfigModule.java @@ -21,30 +21,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - -package sonia.scm.api.v2.resources; +package sonia.scm.autoconfig; -import javax.inject.Inject; -import java.util.List; +import com.google.inject.AbstractModule; +import sonia.scm.plugin.Extension; -import static de.otto.edison.hal.Links.linkingTo; - -public class HgConfigInstallationsToDtoMapper { - - private ScmPathInfoStore scmPathInfoStore; - - @Inject - public HgConfigInstallationsToDtoMapper(ScmPathInfoStore scmPathInfoStore) { - this.scmPathInfoStore = scmPathInfoStore; - } - - public HgConfigInstallationsDto map(List installations, String path) { - return new HgConfigInstallationsDto(linkingTo().self(createSelfLink(path)).build(), installations); - } - - private String createSelfLink(String path) { - LinkBuilder linkBuilder = new LinkBuilder(scmPathInfoStore.get(), HgConfigResource.class); - return linkBuilder.method("getInstallationsResource").parameters().href() + '/' + path; +@Extension +public class AutoConfigModule extends AbstractModule { + @Override + protected void configure() { + bind(AutoConfigurator.class).toProvider(AutoConfiguratorProvider.class); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/AutoConfigurator.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/AutoConfigurator.java index 534166b4ad..837daba9ee 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/AutoConfigurator.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/AutoConfigurator.java @@ -24,26 +24,10 @@ package sonia.scm.autoconfig; -import sonia.scm.Platform; import sonia.scm.repository.HgConfig; -import sonia.scm.util.SystemUtil; - -import java.nio.file.Path; -import java.util.Optional; public interface AutoConfigurator { - HgConfig configure(); - - HgConfig configure(Path hg); - - static Optional get() { - // at the moment we have only support for posix based systems - Platform platform = SystemUtil.getPlatform(); - if (platform.isPosix()) { - return Optional.of(new PosixAutoConfigurator(System.getenv())); - } - return Optional.empty(); - } + void configure(HgConfig config); } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackagesDto.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/AutoConfiguratorProvider.java similarity index 54% rename from scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackagesDto.java rename to scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/AutoConfiguratorProvider.java index 10bcd1e1e2..a1ee4d3213 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackagesDto.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/AutoConfiguratorProvider.java @@ -21,42 +21,41 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - -package sonia.scm.api.v2.resources; -import de.otto.edison.hal.HalRepresentation; -import de.otto.edison.hal.Links; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +package sonia.scm.autoconfig; -import java.util.List; +import com.google.common.annotations.VisibleForTesting; +import sonia.scm.Platform; +import sonia.scm.repository.HgVerifier; +import sonia.scm.util.SystemUtil; -@NoArgsConstructor -@Getter -@Setter -public class HgConfigPackagesDto extends HalRepresentation { +import javax.inject.Inject; +import javax.inject.Provider; - private List packages; +public class AutoConfiguratorProvider implements Provider { + + private final HgVerifier verifier; + private final Platform platform; + + @Inject + public AutoConfiguratorProvider(HgVerifier verifier) { + this(verifier, SystemUtil.getPlatform()); + } + + @VisibleForTesting + AutoConfiguratorProvider(HgVerifier verifier, Platform platform) { + this.verifier = verifier; + this.platform = platform; + } @Override - @SuppressWarnings("squid:S1185") // We want to have this method available in this package - protected HalRepresentation add(Links links) { - return super.add(links); - } - - @NoArgsConstructor - @Getter - @Setter - public static class HgConfigPackageDto { - - private String arch; - private HgConfigDto hgConfigTemplate; - private String hgVersion; - private String id; - private String platform; - private String pythonVersion; - private long size; - private String url; + public AutoConfigurator get() { + if (platform.isPosix()) { + return new PosixAutoConfigurator(verifier, System.getenv()); + } else if (platform.isWindows()) { + return new WindowsAutoConfigurator(verifier, new WindowsRegistry(), System.getenv()); + } else { + return new NoOpAutoConfigurator(); + } } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgInstallerFactory.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/NoOpAutoConfigurator.java similarity index 60% rename from scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgInstallerFactory.java rename to scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/NoOpAutoConfigurator.java index c108d46fc7..ef13b4da46 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgInstallerFactory.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/NoOpAutoConfigurator.java @@ -21,51 +21,23 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - -package sonia.scm.installer; -//~--- non-JDK imports -------------------------------------------------------- +package sonia.scm.autoconfig; -import sonia.scm.util.SystemUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.repository.HgConfig; -/** - * - * @author Sebastian Sdorra - */ -public final class HgInstallerFactory -{ +public class NoOpAutoConfigurator implements AutoConfigurator { - /** - * Constructs ... - * - */ - private HgInstallerFactory() {} + private static final Logger LOG = LoggerFactory.getLogger(NoOpAutoConfigurator.class); - //~--- methods -------------------------------------------------------------- + NoOpAutoConfigurator() { + } - /** - * Method description - * - * - * @return - */ - public static HgInstaller createInstaller() - { - HgInstaller installer = null; - - if (SystemUtil.isWindows()) - { - installer = new WindowsHgInstaller(); - } - else if (SystemUtil.isMac()) - { - installer = new MacOSHgInstaller(); - } - else - { - installer = new UnixHgInstaller(); - } - - return installer; + @Override + public void configure(HgConfig config) { + // if we do not know the environment, we could not configure mercurial + LOG.debug("no mercurial autoconfiguration available on this platform"); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/PosixAutoConfigurator.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/PosixAutoConfigurator.java index ff27fa6af1..e1f3c0aa62 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/PosixAutoConfigurator.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/PosixAutoConfigurator.java @@ -27,19 +27,12 @@ package sonia.scm.autoconfig; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; -import com.google.common.io.MoreFiles; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.repository.HgConfig; +import sonia.scm.repository.HgVerifier; -import java.io.BufferedReader; import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.LinkedHashSet; @@ -58,155 +51,40 @@ public class PosixAutoConfigurator implements AutoConfigurator { "/opt/local/bin" ); + private final HgVerifier verifier; private final Set fsPaths; - private Executor executor = (Path binary, String... args) -> { - ProcessBuilder builder = new ProcessBuilder( - Lists.asList(binary.toString(), args).toArray(new String[0]) - ); - Process process = builder.start(); - int rc = process.waitFor(); - if (rc != 0) { - throw new IOException(binary.toString() + " failed with return code " + rc); - } - return process.getInputStream(); - }; - - PosixAutoConfigurator(Map env) { - this(env, ADDITIONAL_PATH); + PosixAutoConfigurator(HgVerifier verifier, Map env) { + this(verifier, env, ADDITIONAL_PATH); } - PosixAutoConfigurator(Map env, List additionalPaths) { + @VisibleForTesting + PosixAutoConfigurator(HgVerifier verifier, Map env, List additionalPaths) { + this.verifier = verifier; String path = env.getOrDefault("PATH", ""); fsPaths = new LinkedHashSet<>(); fsPaths.addAll(Splitter.on(File.pathSeparator).splitToList(path)); fsPaths.addAll(additionalPaths); } - @VisibleForTesting - void setExecutor(Executor executor) { - this.executor = executor; - } - @Override - public HgConfig configure() { - Optional hg = findInPath("hg"); + public void configure(HgConfig config) { + Optional hg = findInPath(); if (hg.isPresent()) { - return configure(hg.get()); + config.setHgBinary(hg.get().toAbsolutePath().toString()); + } else { + LOG.warn("could not find valid mercurial installation"); } - return new HgConfig(); } - private Optional findInPath(String binary) { + private Optional findInPath() { for (String directory : fsPaths) { - Path binaryPath = Paths.get(directory, binary); - if (Files.exists(binaryPath)) { + Path binaryPath = Paths.get(directory, "hg"); + if (verifier.isValid(binaryPath)) { return Optional.of(binaryPath); } } return Optional.empty(); } - - private Optional findModulePath(Path hg) { - if (!Files.isExecutable(hg)) { - LOG.warn("{} is not executable", hg); - return Optional.empty(); - } - try { - InputStream debuginstall = executor.execute(hg, "debuginstall"); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(debuginstall))) { - while (reader.ready()) { - String line = reader.readLine(); - if (line.contains("installed modules")) { - int start = line.indexOf("("); - int end = line.indexOf(")"); - Path modulePath = Paths.get(line.substring(start + 1, end)); - if (Files.exists(modulePath)) { - // installed modules contains the path to the mercurial module, - // but we need the parent for the python path - return Optional.of(modulePath.getParent()); - } else { - LOG.warn("could not find module path at {}", modulePath); - } - } - } - } - } catch (IOException ex) { - LOG.warn("failed to parse debuginstall of {}", hg); - } catch (InterruptedException e) { - LOG.warn("interrupted during debuginstall parsing of {}", hg); - Thread.currentThread().interrupt(); - } - return Optional.empty(); - } - - @Override - public HgConfig configure(Path hg) { - HgConfig config = new HgConfig(); - try { - if (Files.exists(hg)) { - configureWithExistingHg(hg, config); - } else { - LOG.warn("{} does not exists", hg); - } - } catch (IOException e) { - LOG.warn("failed to read first line of {}", hg); - } - return config; - } - - private void configureWithExistingHg(Path hg, HgConfig config) throws IOException { - config.setHgBinary(hg.toAbsolutePath().toString()); - Optional pythonFromShebang = findPythonFromShebang(hg); - if (pythonFromShebang.isPresent()) { - config.setPythonBinary(pythonFromShebang.get().toAbsolutePath().toString()); - } else { - LOG.warn("could not find python from shebang, searching for python in path"); - Optional python = findInPath("python"); - if (!python.isPresent()) { - LOG.warn("could not find python in path, searching for python3 instead"); - python = findInPath("python3"); - } - if (python.isPresent()) { - config.setPythonBinary(python.get().toAbsolutePath().toString()); - } else { - LOG.warn("could not find python in path"); - } - } - - Optional modulePath = findModulePath(hg); - if (modulePath.isPresent()) { - config.setPythonPath(modulePath.get().toAbsolutePath().toString()); - } else { - LOG.warn("could not find module path"); - } - - } - - private Optional findPythonFromShebang(Path hg) throws IOException { - String shebang = MoreFiles.asCharSource(hg, StandardCharsets.UTF_8).readFirstLine(); - if (shebang != null && shebang.startsWith("#!")) { - String substring = shebang.substring(2); - String[] parts = substring.split("\\s+"); - if (parts.length > 1) { - return findInPath(parts[1]); - } else { - Path python = Paths.get(parts[0]); - if (Files.exists(python)) { - return Optional.of(python); - } else { - LOG.warn("python binary from shebang {} does not exists", python); - } - } - } else { - LOG.warn("first line does not look like a shebang: {}", shebang); - } - return Optional.empty(); - } - - @FunctionalInterface - interface Executor { - InputStream execute(Path binary, String... args) throws IOException, InterruptedException; - } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/WindowsAutoConfigurator.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/WindowsAutoConfigurator.java new file mode 100644 index 0000000000..fae599a602 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/WindowsAutoConfigurator.java @@ -0,0 +1,129 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.autoconfig; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Splitter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.repository.HgConfig; +import sonia.scm.repository.HgVerifier; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public class WindowsAutoConfigurator implements AutoConfigurator { + + private static final Logger LOG = LoggerFactory.getLogger(WindowsAutoConfigurator.class); + + @VisibleForTesting + static final String REGISTRY_KEY_TORTOISE_HG = "HKEY_LOCAL_MACHINE\\Software\\TortoiseHg"; + @VisibleForTesting + static final String REGISTRY_KEY_MERCURIAL = "HKEY_LOCAL_MACHINE\\Software\\Mercurial\\InstallDir"; + + private static final String[] REGISTRY_KEYS = {REGISTRY_KEY_TORTOISE_HG, REGISTRY_KEY_MERCURIAL}; + + @VisibleForTesting + static final String BINARY_HG_EXE = "hg.exe"; + @VisibleForTesting + static final String BINARY_HG_BAT = "hg.bat"; + + private static final String[] BINARIES = {BINARY_HG_EXE, BINARY_HG_BAT}; + + @VisibleForTesting + static final String ENV_PATH = "Path"; + + private final HgVerifier verifier; + private final WindowsRegistry registry; + private final Map env; + + WindowsAutoConfigurator(HgVerifier verifier, WindowsRegistry registry, Map env) { + this.verifier = verifier; + this.registry = registry; + this.env = env; + } + + @Override + public void configure(HgConfig config) { + Set fsPaths = new LinkedHashSet<>(pathFromEnv()); + resolveRegistryKeys(fsPaths); + + Optional hg = findInPath(fsPaths); + if (hg.isPresent()) { + String hgBinary = hg.get(); + LOG.info("found hg at {}", hgBinary); + config.setHgBinary(hgBinary); + } else { + LOG.warn("could not find valid mercurial installation"); + } + } + + private void resolveRegistryKeys(Set fsPaths) { + for (String registryKey : REGISTRY_KEYS) { + Optional registryValue = registry.get(registryKey); + if (registryValue.isPresent()) { + String directory = registryValue.get(); + LOG.trace("resolved registry key {} to directory {}", registryKey, directory); + fsPaths.add(directory); + } else { + LOG.trace("could not find value for registry key {}", registryKey); + } + } + } + + private Collection pathFromEnv() { + String path = env.getOrDefault(ENV_PATH, ""); + LOG.trace("try to find hg in PATH {}", path); + return Splitter.on(File.pathSeparator).splitToList(path); + } + + private Optional findInPath(Set fsPaths) { + for (String directory : fsPaths) { + Optional binaryPath = findInDirectory(directory); + if (binaryPath.isPresent()) { + return binaryPath; + } + } + return Optional.empty(); + } + + private Optional findInDirectory(String directory) { + LOG.trace("check directory {} for mercurial installations", directory); + for (String binary : BINARIES) { + Path hg = Paths.get(directory, binary); + if (verifier.isValid(hg)) { + return Optional.of(hg.toAbsolutePath().toString()); + } + } + return Optional.empty(); + } + +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsDto.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/WindowsRegistry.java similarity index 74% rename from scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsDto.java rename to scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/WindowsRegistry.java index c7d66e82bb..c259ccfe2c 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsDto.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/autoconfig/WindowsRegistry.java @@ -21,25 +21,19 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - -package sonia.scm.api.v2.resources; -import de.otto.edison.hal.HalRepresentation; -import de.otto.edison.hal.Links; -import lombok.Getter; -import lombok.Setter; +package sonia.scm.autoconfig; -import java.util.List; +import sonia.scm.util.RegistryUtil; -@Getter -@Setter -public class HgConfigInstallationsDto extends HalRepresentation { +import java.util.Optional; - private List paths; +public class WindowsRegistry { - public HgConfigInstallationsDto(Links links, List paths) { - super(links); - this.paths = paths; + public Optional get(String key) { + return Optional.ofNullable( + RegistryUtil.getRegistryValue(key) + ); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/AbstractHgInstaller.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/AbstractHgInstaller.java deleted file mode 100644 index 7e5548a567..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/AbstractHgInstaller.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.installer; - -//~--- non-JDK imports -------------------------------------------------------- - -import sonia.scm.repository.HgRepositoryHandler; - -//~--- JDK imports ------------------------------------------------------------ - -import java.io.File; - -import sonia.scm.net.ahc.AdvancedHttpClient; - -/** - * - * @author Sebastian Sdorra - */ -public abstract class AbstractHgInstaller implements HgInstaller -{ - - - /** - * Method description - * - * - * - * - * @param client - * @param handler - * @param baseDirectory - * @param pkg - * - * @return - */ - @Override - public boolean installPackage(AdvancedHttpClient client, HgRepositoryHandler handler, - File baseDirectory, HgPackage pkg) - { - return new HgPackageInstaller(client, handler, baseDirectory, - pkg).install(); - } -} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgInstaller.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgInstaller.java deleted file mode 100644 index 16ab6ac0e2..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgInstaller.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.installer; - -//~--- non-JDK imports -------------------------------------------------------- - -import sonia.scm.net.ahc.AdvancedHttpClient; -import sonia.scm.repository.HgConfig; -import sonia.scm.repository.HgRepositoryHandler; - -//~--- JDK imports ------------------------------------------------------------ - -import java.io.File; -import java.io.IOException; - -import java.util.List; - -/** - * - * @author Sebastian Sdorra - */ -public interface HgInstaller -{ - - /** - * Method description - * - * - * - * @param baseDirectory - * @param config - * - * @throws IOException - */ - public void install(File baseDirectory, HgConfig config) throws IOException; - - /** - * Method description - * - * - * - * - * @param client - * @param handler - * @param baseDirectory - * @param pkg - * - * @return - */ - public boolean installPackage(AdvancedHttpClient client, - HgRepositoryHandler handler, File baseDirectory, HgPackage pkg); - - /** - * Method description - * - * - * - * @param baseDirectory - * @param config - * - * @throws IOException - */ - public void update(File baseDirectory, HgConfig config) throws IOException; - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - public List getHgInstallations(); - - /** - * Method description - * - * - * @return - */ - public List getPythonInstallations(); -} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackage.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackage.java deleted file mode 100644 index c34ed61e63..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackage.java +++ /dev/null @@ -1,253 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.installer; - -//~--- non-JDK imports -------------------------------------------------------- - -import sonia.scm.repository.HgConfig; - -//~--- JDK imports ------------------------------------------------------------ - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -/** - * - * @author Sebastian Sdorra - */ -@XmlRootElement(name = "package") -@XmlAccessorType(XmlAccessType.FIELD) -public class HgPackage -{ - - /** - * Method description - * - * - * @return - */ - public String getArch() - { - return arch; - } - - /** - * Method description - * - * - * @return - */ - public HgConfig getHgConfigTemplate() - { - return hgConfigTemplate; - } - - /** - * Method description - * - * - * @return - */ - public String getHgVersion() - { - return hgVersion; - } - - /** - * Method description - * - * - * @return - */ - public String getId() - { - return id; - } - - /** - * Method description - * - * - * @return - */ - public String getPlatform() - { - return platform; - } - - /** - * Method description - * - * - * @return - */ - public String getPythonVersion() - { - return pythonVersion; - } - - /** - * Method description - * - * - * @return - */ - public long getSize() - { - return size; - } - - /** - * Method description - * - * - * @return - */ - public String getUrl() - { - return url; - } - - //~--- set methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param arch - */ - public void setArch(String arch) - { - this.arch = arch; - } - - /** - * Method description - * - * - * @param hgConfigTemplate - */ - public void setHgConfigTemplate(HgConfig hgConfigTemplate) - { - this.hgConfigTemplate = hgConfigTemplate; - } - - /** - * Method description - * - * - * @param hgVersion - */ - public void setHgVersion(String hgVersion) - { - this.hgVersion = hgVersion; - } - - /** - * Method description - * - * - * @param id - */ - public void setId(String id) - { - this.id = id; - } - - /** - * Method description - * - * - * @param platform - */ - public void setPlatform(String platform) - { - this.platform = platform; - } - - /** - * Method description - * - * - * @param pythonVersion - */ - public void setPythonVersion(String pythonVersion) - { - this.pythonVersion = pythonVersion; - } - - /** - * Method description - * - * - * @param size - */ - public void setSize(long size) - { - this.size = size; - } - - /** - * Method description - * - * - * @param url - */ - public void setUrl(String url) - { - this.url = url; - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private String arch; - - /** Field description */ - @XmlElement(name = "hg-config-template") - private HgConfig hgConfigTemplate; - - /** Field description */ - @XmlElement(name = "hg-version") - private String hgVersion; - - /** Field description */ - private String id; - - /** Field description */ - private String platform; - - /** Field description */ - @XmlElement(name = "python-version") - private String pythonVersion; - - /** Field description */ - private long size; - - /** Field description */ - private String url; -} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageInstaller.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageInstaller.java deleted file mode 100644 index f184d7ad73..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageInstaller.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.installer; - -//~--- non-JDK imports -------------------------------------------------------- - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import sonia.scm.SCMContext; -import sonia.scm.io.ZipUnArchiver; -import sonia.scm.net.ahc.AdvancedHttpClient; -import sonia.scm.repository.HgConfig; -import sonia.scm.repository.HgRepositoryHandler; -import sonia.scm.repository.HgWindowsPackageFix; -import sonia.scm.util.IOUtil; - -//~--- JDK imports ------------------------------------------------------------ - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import java.text.MessageFormat; - -/** - * - * @author Sebastian Sdorra - */ -public class HgPackageInstaller implements Runnable -{ - - /** the logger for HgPackageInstaller */ - private static final Logger logger = - LoggerFactory.getLogger(HgPackageInstaller.class); - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - * - * - * - * @param client - * @param handler - * @param baseDirectory - * @param pkg - */ - public HgPackageInstaller(AdvancedHttpClient client, - HgRepositoryHandler handler, File baseDirectory, HgPackage pkg) - { - this.client = client; - this.handler = handler; - this.baseDirectory = baseDirectory; - this.pkg = pkg; - } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - public boolean install() - { - boolean success = false; - File downloadedFile = downloadFile(); - - if ((downloadedFile != null) && downloadedFile.exists()) - { - File directory = extractPackage(downloadedFile); - - if ((directory != null) && directory.exists()) - { - updateConfig(directory); - success = true; - } - } - - return success; - } - - /** - * Method description - * - */ - @Override - public void run() - { - if (!install()) - { - logger.error("installation of pkg {} failed", pkg.getId()); - } - else if (logger.isInfoEnabled()) - { - logger.info("successfully installed pkg {}", pkg.getId()); - } - } - - /** - * Method description - * - * - * @return - */ - private File downloadFile() - { - File file = null; - InputStream input = null; - OutputStream output = null; - - try - { - file = File.createTempFile("scm-hg-", ".pkg"); - - if (logger.isDebugEnabled()) - { - logger.debug("download package to {}", file.getAbsolutePath()); - } - - // TODO error handling - input = client.get(pkg.getUrl()).request().contentAsStream(); - output = new FileOutputStream(file); - IOUtil.copy(input, output); - } - catch (IOException ex) - { - logger.error("could not downlaod file ".concat(pkg.getUrl()), ex); - file = null; - } - finally - { - IOUtil.close(input); - IOUtil.close(output); - } - - return file; - } - - /** - * Method description - * - * - * @param file - * - * @return - */ - private File extractPackage(File file) - { - File directory = new File(baseDirectory, - "pkg".concat(File.separator).concat(pkg.getId())); - - IOUtil.mkdirs(directory); - - try - { - IOUtil.extract(file, directory, ZipUnArchiver.EXTENSION); - } - catch (IOException ex) - { - directory = null; - logger.error("could not extract pacakge ".concat(pkg.getId()), ex); - } - finally - { - - // delete temp file - try - { - IOUtil.delete(file, true); - } - catch (IOException ex) - { - logger.error(ex.getMessage(), ex); - } - } - - return directory; - } - - /** - * Method description - * - * - * @param directory - */ - private void updateConfig(File directory) - { - String path = directory.getAbsolutePath(); - HgConfig template = pkg.getHgConfigTemplate(); - HgConfig config = handler.getConfig(); - - config.setHgBinary(getTemplateValue(template.getHgBinary(), path)); - config.setPythonBinary(getTemplateValue(template.getPythonBinary(), path)); - config.setPythonPath(getTemplateValue(template.getPythonPath(), path)); - config.setUseOptimizedBytecode(template.isUseOptimizedBytecode()); - - // fix wrong hg.bat - HgWindowsPackageFix.fixHgPackage(SCMContext.getContext(), config); - - handler.storeConfig(); - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param template - * @param path - * - * @return - */ - private String getTemplateValue(String template, String path) - { - String result = null; - - if (template != null) - { - result = MessageFormat.format(template, path); - } - - return result; - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private File baseDirectory; - - /** Field description */ - private AdvancedHttpClient client; - - /** Field description */ - private HgRepositoryHandler handler; - - /** Field description */ - private HgPackage pkg; -} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageReader.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageReader.java deleted file mode 100644 index 2c61303847..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageReader.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.installer; - -//~--- non-JDK imports -------------------------------------------------------- - -import com.google.inject.Inject; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import sonia.scm.PlatformType; -import sonia.scm.cache.Cache; -import sonia.scm.cache.CacheManager; -import sonia.scm.net.ahc.AdvancedHttpClient; -import sonia.scm.util.SystemUtil; -import sonia.scm.util.Util; - -//~--- JDK imports ------------------------------------------------------------ - -import java.io.IOException; - -import java.util.ArrayList; -import java.util.List; - -/** - * - * @author Sebastian Sdorra - */ -public class HgPackageReader -{ - - /** Field description */ - public static final String CACHENAME = "sonia.scm.hg.packages"; - - /** Field description */ - public static final String PACKAGEURL = - "http://download.scm-manager.org/pkg/mercurial/packages.xml"; - - /** the logger for HgPackageReader */ - private static final Logger logger = - LoggerFactory.getLogger(HgPackageReader.class); - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - * - * @param cacheManager - * @param httpClient - */ - @Inject - public HgPackageReader(CacheManager cacheManager, AdvancedHttpClient httpClient) - { - this.cache = cacheManager.getCache(CACHENAME); - this.httpClient = httpClient; - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param id - * - * @return - */ - public HgPackage getPackage(String id) - { - HgPackage pkg = null; - - for (HgPackage p : getPackages()) - { - if (id.equals(p.getId())) - { - pkg = p; - - break; - } - } - - return pkg; - } - - /** - * Method description - * - * - * @return - */ - public HgPackages getPackages() - { - HgPackages packages = cache.get(HgPackages.class.getName()); - - if (packages == null) - { - packages = getRemptePackages(); - filterPackage(packages); - cache.put(HgPackages.class.getName(), packages); - } - - return packages; - } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param packages - */ - private void filterPackage(HgPackages packages) - { - List pkgList = new ArrayList<>(); - - for (HgPackage pkg : packages) - { - boolean add = true; - - if (Util.isNotEmpty(pkg.getPlatform())) - { - PlatformType pt = PlatformType.createPlatformType(pkg.getPlatform()); - - if (SystemUtil.getPlatform().getType() != pt) - { - if (logger.isDebugEnabled()) - { - logger.debug("reject package {}, because of wrong platform {}", - pkg.getId(), pkg.getPlatform()); - } - - add = false; - } - } - - if (add && Util.isNotEmpty(pkg.getArch())) - { - if (!SystemUtil.getArch().equals(pkg.getArch())) - { - if (logger.isDebugEnabled()) - { - logger.debug("reject package {}, because of wrong arch {}", - pkg.getId(), pkg.getArch()); - } - - add = false; - } - } - - if (add) - { - if (logger.isDebugEnabled()) - { - logger.debug("added HgPackage {}", pkg.getId()); - } - - pkgList.add(pkg); - } - } - - packages.setPackages(pkgList); - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - private HgPackages getRemptePackages() - { - if (logger.isInfoEnabled()) - { - logger.info("fetch HgPackages from {}", PACKAGEURL); - } - - HgPackages packages = null; - - try - { - //J- - packages = httpClient.get(PACKAGEURL) - .request() - .contentFromXml(HgPackages.class); - //J+ - } - catch (IOException ex) - { - logger.error("could not read HgPackages from ".concat(PACKAGEURL), ex); - } - - if (packages == null) - { - packages = new HgPackages(); - packages.setPackages(new ArrayList<>()); - } - - return packages; - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private final Cache cache; - - /** Field description */ - private final AdvancedHttpClient httpClient; -} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackages.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackages.java deleted file mode 100644 index 9aa52ee04c..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackages.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.installer; - -//~--- JDK imports ------------------------------------------------------------ - -import java.util.Iterator; -import java.util.List; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -/** - * - * @author Sebastian Sdorra - */ -@XmlRootElement(name = "packages") -@XmlAccessorType(XmlAccessType.FIELD) -public class HgPackages implements Iterable -{ - - /** - * Method description - * - * - * @return - */ - @Override - public Iterator iterator() - { - return packages.iterator(); - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - public List getPackages() - { - return packages; - } - - //~--- set methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param packages - */ - public void setPackages(List packages) - { - this.packages = packages; - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - @XmlElement(name = "package") - private List packages; -} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/MacOSHgInstaller.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/MacOSHgInstaller.java deleted file mode 100644 index 52a48d1cad..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/MacOSHgInstaller.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.installer; - -//~--- non-JDK imports -------------------------------------------------------- - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import sonia.scm.io.DirectoryFileFilter; -import sonia.scm.repository.HgConfig; - -//~--- JDK imports ------------------------------------------------------------ - -import java.io.File; -import java.io.IOException; - -/** - * - * @author Sebastian Sdorra - * @deprecated use {@link sonia.scm.autoconfig.AutoConfigurator} - */ -@Deprecated -public class MacOSHgInstaller extends UnixHgInstaller -{ - - /** Field description */ - public static final String ENV_PATH = "PATH"; - - /** Field description */ - public static final String PATH_HG = "hg"; - - /** Field description */ - public static final String PATH_HG_BREW = "/usr/local/bin/hg"; - - /** Field description */ - public static final String PATH_HG_BREW_INSTALLATION = - "/usr/local/Cellar/mercurial"; - - /** Field description */ - public static final String PATH_PYTHON = "python"; - - /** the logger for MacOSHgInstaller */ - private static final Logger logger = - LoggerFactory.getLogger(MacOSHgInstaller.class); - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param baseDirectory - * @param config - * - * @throws IOException - */ - @Override - public void install(File baseDirectory, HgConfig config) throws IOException - { - super.install(baseDirectory, config); - - String hg = config.getHgBinary(); - - if (PATH_HG.equals(hg)) - { - hg = resolvePath(hg); - } - - if (PATH_HG_BREW.equals(hg)) - { - File file = new File(PATH_HG_BREW); - - file = file.getCanonicalFile(); - - if (file.getAbsolutePath().startsWith(PATH_HG_BREW_INSTALLATION)) - { - useHomebrewInstallation(config, file); - } - } - } - - /** - * Method description - * - * - * @param parent - * - * @return - */ - private File findPythonDirectory(File parent) - { - File pythonDirectory = null; - - for (File d : parent.listFiles(DirectoryFileFilter.instance)) - { - if (d.getName().startsWith("python")) - { - pythonDirectory = d; - - break; - } - } - - return pythonDirectory; - } - - /** - * Method description - * - * - * - * @param binaryName - * @return - */ - private String resolvePath(String binaryName) - { - String binary = binaryName; - - try - { - String path = System.getenv(ENV_PATH); - - for (String p : path.split(":")) - { - File file = new File(p, binaryName); - - if (file.exists()) - { - binary = file.getAbsolutePath(); - - if (logger.isDebugEnabled()) - { - logger.debug("resolve {} path to {}", binaryName, binary); - } - - break; - } - } - } - catch (Exception ex) - { - logger.error("could not resolve binary path", ex); - } - - return binary; - } - - /** - * Method description - * - * - * @param config - * @param file - */ - private void useHomebrewInstallation(HgConfig config, File file) - { - File parent = file.getParentFile().getParentFile(); - File libDirectory = new File(parent, "lib"); - - if (!libDirectory.exists()) - { - libDirectory = new File(parent, "libexec"); - } - - if (libDirectory.exists()) - { - File pythonDirectory = findPythonDirectory(libDirectory); - - if (pythonDirectory != null) - { - File sitePackageDirectory = new File(pythonDirectory, "site-packages"); - - if (sitePackageDirectory.exists()) - { - libDirectory = sitePackageDirectory; - } - - String pythonPath = libDirectory.getPath(); - - if (logger.isInfoEnabled()) - { - logger.info("found mercurial brew install set python path to {}", - pythonPath); - } - - config.setPythonPath(pythonPath); - } - } - - String python = config.getPythonBinary(); - - if (PATH_PYTHON.equals(python)) - { - config.setPythonBinary(resolvePath(python)); - } - } -} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/UnixHgInstaller.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/UnixHgInstaller.java deleted file mode 100644 index ba65b943e8..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/UnixHgInstaller.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.installer; - -//~--- non-JDK imports -------------------------------------------------------- - -import sonia.scm.repository.HgConfig; -import sonia.scm.util.IOUtil; -import sonia.scm.util.Util; - -//~--- JDK imports ------------------------------------------------------------ - -import java.io.File; -import java.io.IOException; - -import java.util.List; - -/** - * - * @author Sebastian Sdorra - * @deprecated use {@link sonia.scm.autoconfig.AutoConfigurator} - */ -@Deprecated -public class UnixHgInstaller extends AbstractHgInstaller -{ - - /** Field description */ - public static final String COMMAND_HG = "hg"; - - /** Field description */ - public static final String COMMAND_PYTHON = "python"; - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * - * @param baseDirectory - * @param config - * - * @throws IOException - */ - @Override - public void install(File baseDirectory, HgConfig config) throws IOException - { - // search mercurial (hg) - if (Util.isEmpty(config.getHgBinary())) - { - String hg = IOUtil.search(COMMAND_HG); - - if (Util.isNotEmpty(hg)) - { - config.setHgBinary(hg); - - // search python in the same folder - File hgFile = new File(hg); - - if (hgFile.exists()) - { - File pythonFile = new File(hgFile.getParentFile(), COMMAND_PYTHON); - - if (pythonFile.exists()) - { - config.setPythonBinary(pythonFile.getAbsolutePath()); - } - } - } - } - - // search python - if (Util.isEmpty(config.getPythonBinary())) - { - config.setPythonBinary(IOUtil.search(COMMAND_PYTHON)); - } - } - - /** - * Method description - * - * - * - * @param baseDirectory - * @param config - */ - @Override - public void update(File baseDirectory, HgConfig config) - { - - // do nothing - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - @Override - public List getHgInstallations() - { - return IOUtil.searchAll(COMMAND_HG); - } - - /** - * Method description - * - * - * @return - */ - @Override - public List getPythonInstallations() - { - return IOUtil.searchAll(COMMAND_PYTHON); - } -} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/WindowsHgInstaller.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/WindowsHgInstaller.java deleted file mode 100644 index 379bbff40e..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/WindowsHgInstaller.java +++ /dev/null @@ -1,418 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.installer; - -//~--- non-JDK imports -------------------------------------------------------- - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import sonia.scm.repository.HgConfig; -import sonia.scm.util.IOUtil; -import sonia.scm.util.RegistryUtil; -import sonia.scm.util.Util; - -//~--- JDK imports ------------------------------------------------------------ - -import java.io.File; -import java.io.FilenameFilter; -import java.io.IOException; - -import java.util.ArrayList; -import java.util.List; - -/** - * - * @author Sebastian Sdorra - */ -public class WindowsHgInstaller extends AbstractHgInstaller -{ - - /** Field description */ - private static final String FILE_LIBRARY_ZIP = "library.zip"; - - /** Field description */ - private static final String FILE_LIB_MERCURIAL = - "Lib\\site-packages\\mercurial"; - - /** Field description */ - private static final String FILE_MERCURIAL_EXE = "hg.exe"; - - /** Field description */ - private static final String FILE_MERCURIAL_SCRIPT = "hg.bat"; - - /** Field description */ - private static final String FILE_SCRIPTS = "Scripts"; - - /** Field description */ - private static final String FILE_TEMPLATES = "templates"; - - /** Field description */ - private static final String[] REGISTRY_HG = new String[] - { - - // TortoiseHg - "HKEY_CURRENT_USER\\Software\\TortoiseHg", - - // Mercurial - "HKEY_CURRENT_USER\\Software\\Mercurial\\InstallDir" - }; - - /** Field description */ - private static final String[] REGISTRY_PYTHON = new String[] - { - - // .py files - "HKEY_CLASSES_ROOT\\Python.File\\shell\\open\\command" - }; - - /** the logger for WindowsHgInstaller */ - private static final Logger logger = - LoggerFactory.getLogger(WindowsHgInstaller.class); - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * - * @param baseDirectory - * @param config - * - * @throws IOException - */ - @Override - public void install(File baseDirectory, HgConfig config) throws IOException - { - if (Util.isEmpty(config.getPythonBinary())) - { - String pythonBinary = getPythonBinary(); - - config.setPythonBinary(pythonBinary); - } - - if (Util.isEmpty(config.getHgBinary())) - { - File hgScript = getMercurialScript(config.getPythonBinary()); - - if (hgScript != null) - { - config.setHgBinary(hgScript.getAbsolutePath()); - } - } - - File hgDirectory = getMercurialDirectory(config.getHgBinary()); - - if (hgDirectory != null) - { - installHg(baseDirectory, config, hgDirectory); - } - - checkForOptimizedByteCode(config); - } - - /** - * Method description - * - * - * - * @param baseDirectory - * @param config - */ - @Override - public void update(File baseDirectory, HgConfig config) {} - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - @Override - public List getHgInstallations() - { - return getInstallations(REGISTRY_HG); - } - - /** - * Method description - * - * - * @return - */ - @Override - public List getPythonInstallations() - { - return getInstallations(REGISTRY_PYTHON); - } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param config - */ - private void checkForOptimizedByteCode(HgConfig config) - { - boolean optimized = false; - String path = config.getPythonPath(); - - if (Util.isNotEmpty(path)) - { - for (String part : path.split(";")) - { - if (checkForOptimizedByteCode(part)) - { - optimized = true; - - break; - } - } - } - - config.setUseOptimizedBytecode(optimized); - } - - /** - * Method description - * - * - * @param part - * - * @return - */ - private boolean checkForOptimizedByteCode(String part) - { - File libDir = new File(part); - String[] pyoFiles = libDir.list(new FilenameFilter() - { - @Override - public boolean accept(File file, String name) - { - return name.toLowerCase().endsWith(".pyo"); - } - }); - - return Util.isNotEmpty(pyoFiles); - } - - /** - * Method description - * - * - * - * @param baseDirectory - * @param config - * @param hgDirectory - * - * @throws IOException - */ - private void installHg(File baseDirectory, HgConfig config, File hgDirectory) - throws IOException - { - if (logger.isInfoEnabled()) - { - logger.info("installing mercurial {}", hgDirectory.getAbsolutePath()); - } - - File libDir = new File(baseDirectory, "lib\\hg"); - - IOUtil.mkdirs(libDir); - - File libraryZip = new File(hgDirectory, FILE_LIBRARY_ZIP); - - if (libraryZip.exists()) - { - IOUtil.extract(libraryZip, libDir); - config.setPythonPath(libDir.getAbsolutePath()); - } - - File templateDirectory = new File(hgDirectory, FILE_TEMPLATES); - - if (templateDirectory.exists()) - { - IOUtil.copy(templateDirectory, new File(libDir, FILE_TEMPLATES)); - } - - File hg = new File(hgDirectory, FILE_MERCURIAL_EXE); - - if (hg.exists()) - { - config.setHgBinary(hg.getAbsolutePath()); - } - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param registryKeys - * - * @return - */ - private List getInstallations(String[] registryKeys) - { - List installations = new ArrayList(); - - for (String registryKey : registryKeys) - { - String path = RegistryUtil.getRegistryValue(registryKey); - - if (path != null) - { - File file = new File(path, FILE_MERCURIAL_EXE); - - if (file.exists()) - { - installations.add(file.getAbsolutePath()); - } - } - } - - return installations; - } - - /** - * Method description - * - * - * - * @param hgBinary - * @return - */ - private File getMercurialDirectory(String hgBinary) - { - File directory = null; - - if (Util.isNotEmpty(hgBinary)) - { - File hg = new File(hgBinary); - - if (hg.exists() && hg.isFile()) - { - directory = hg.getParentFile(); - } - } - - if (directory == null) - { - directory = getMercurialDirectoryFromRegistry(); - } - - return directory; - } - - /** - * Method description - * - * - * @return - */ - private File getMercurialDirectoryFromRegistry() - { - File directory = null; - - for (String registryKey : REGISTRY_HG) - { - String path = RegistryUtil.getRegistryValue(registryKey); - - if (path != null) - { - directory = new File(path); - - if (!directory.exists()) - { - directory = null; - } - else - { - break; - } - } - } - - return directory; - } - - /** - * Returns the location of the script to run Mercurial, if Mercurial is - * installed as a Python package from source. Only packages that include a - * templates directory will be recognized. - * - * @param pythonBinary - * - * @return - */ - private File getMercurialScript(String pythonBinary) - { - File hgScript = null; - - if (pythonBinary != null) - { - File pythonBinaryFile = new File(pythonBinary); - - if (pythonBinaryFile.exists()) - { - File pythonDir = pythonBinaryFile.getParentFile(); - File scriptsDir = new File(pythonDir, FILE_SCRIPTS); - File potentialHgScript = new File(scriptsDir, FILE_MERCURIAL_SCRIPT); - File mercurialPackageDir = new File(pythonDir, FILE_LIB_MERCURIAL); - File templatesDir = new File(mercurialPackageDir, FILE_TEMPLATES); - - if (potentialHgScript.exists() && templatesDir.exists()) - { - hgScript = potentialHgScript; - } - } - } - - return hgScript; - } - - /** - * Method description - * - * - * @return - */ - private String getPythonBinary() - { - String python = RegistryUtil.getRegistryValue(REGISTRY_PYTHON[0]); - - if (python == null) - { - python = IOUtil.search(new String[0], "python"); - } - - return python; - } -} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/DefaultHgEnvironmentBuilder.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/DefaultHgEnvironmentBuilder.java index 1888f0aeae..ec7d1c6e15 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/DefaultHgEnvironmentBuilder.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/DefaultHgEnvironmentBuilder.java @@ -34,7 +34,6 @@ import sonia.scm.security.AccessToken; import sonia.scm.security.AccessTokenBuilderFactory; import sonia.scm.security.CipherUtil; import sonia.scm.security.Xsrf; -import sonia.scm.web.HgUtil; import javax.inject.Inject; import javax.inject.Singleton; @@ -45,8 +44,6 @@ import java.util.Map; @Singleton public class DefaultHgEnvironmentBuilder implements HgEnvironmentBuilder { - @VisibleForTesting - static final String ENV_PYTHON_PATH = "PYTHONPATH"; @VisibleForTesting static final String ENV_HOOK_PORT = "SCM_HOOK_PORT"; @VisibleForTesting @@ -100,7 +97,6 @@ public class DefaultHgEnvironmentBuilder implements HgEnvironmentBuilder { private void read(ImmutableMap.Builder env, Repository repository) { HgConfig config = repositoryHandler.getConfig(); - env.put(ENV_PYTHON_PATH, HgUtil.getPythonPath(config)); File directory = repositoryHandler.getDirectory(repository.getId()); diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java index 3189239dbd..5909a22158 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java @@ -69,28 +69,6 @@ public class HgConfig extends RepositoryConfig { return hgBinary; } - /** - * Method description - * - * - * @return - */ - public String getPythonBinary() - { - return pythonBinary; - } - - /** - * Method description - * - * - * @return - */ - public String getPythonPath() - { - return pythonPath; - } - /** * Method description * @@ -102,17 +80,6 @@ public class HgConfig extends RepositoryConfig { return showRevisionInId; } - /** - * Method description - * - * - * @return - */ - public boolean isUseOptimizedBytecode() - { - return useOptimizedBytecode; - } - public boolean isEnableHttpPostArgs() { return enableHttpPostArgs; } @@ -126,8 +93,7 @@ public class HgConfig extends RepositoryConfig { @Override public boolean isValid() { - return super.isValid() && Util.isNotEmpty(hgBinary) - && Util.isNotEmpty(pythonBinary); + return super.isValid() && Util.isNotEmpty(hgBinary); } //~--- set methods ---------------------------------------------------------- @@ -154,28 +120,6 @@ public class HgConfig extends RepositoryConfig { this.hgBinary = hgBinary; } - /** - * Method description - * - * - * @param pythonBinary - */ - public void setPythonBinary(String pythonBinary) - { - this.pythonBinary = pythonBinary; - } - - /** - * Method description - * - * - * @param pythonPath - */ - public void setPythonPath(String pythonPath) - { - this.pythonPath = pythonPath; - } - /** * Method description * @@ -191,17 +135,6 @@ public class HgConfig extends RepositoryConfig { this.enableHttpPostArgs = enableHttpPostArgs; } - /** - * Method description - * - * - * @param useOptimizedBytecode - */ - public void setUseOptimizedBytecode(boolean useOptimizedBytecode) - { - this.useOptimizedBytecode = useOptimizedBytecode; - } - //~--- fields --------------------------------------------------------------- /** Field description */ @@ -210,15 +143,6 @@ public class HgConfig extends RepositoryConfig { /** Field description */ private String hgBinary; - /** Field description */ - private String pythonBinary; - - /** Field description */ - private String pythonPath = ""; - - /** Field description */ - private boolean useOptimizedBytecode = false; - /** Field description */ private boolean showRevisionInId = false; diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgPythonScript.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgExtensions.java similarity index 88% rename from scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgPythonScript.java rename to scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgExtensions.java index 0e4b05b101..9d613fe07c 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgPythonScript.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgExtensions.java @@ -26,6 +26,7 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- +import sonia.scm.SCMContext; import sonia.scm.SCMContextProvider; //~--- JDK imports ------------------------------------------------------------ @@ -36,16 +37,19 @@ import java.io.File; * * @author Sebastian Sdorra */ -public enum HgPythonScript { +public enum HgExtensions { - HOOK("scmhooks.py"), HGWEB("hgweb.py"); + HOOK("scmhooks.py"), + CGISERVE("cgiserve.py"), + VERSION("scmversion.py"), + FILEVIEW("fileview.py"); private static final String BASE_DIRECTORY = "lib".concat(File.separator).concat("python"); private static final String BASE_RESOURCE = "/sonia/scm/python/"; private final String name; - HgPythonScript(String name) { + HgExtensions(String name) { this.name = name; } @@ -53,6 +57,10 @@ public enum HgPythonScript { return new File(context.getBaseDirectory(), BASE_DIRECTORY); } + public File getFile() { + return getFile(SCMContext.getContext()); + } + public File getFile(SCMContextProvider context) { return new File(getScriptDirectory(context), name); } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java index 14cd54866d..a99df0a415 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java @@ -30,10 +30,9 @@ import com.google.inject.Inject; import com.google.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.SCMContext; import sonia.scm.SCMContextProvider; import sonia.scm.autoconfig.AutoConfigurator; -import sonia.scm.installer.HgInstaller; -import sonia.scm.installer.HgInstallerFactory; import sonia.scm.io.ExtendedCommand; import sonia.scm.io.INIConfiguration; import sonia.scm.io.INIConfigurationWriter; @@ -45,7 +44,6 @@ import sonia.scm.repository.spi.HgVersionCommand; import sonia.scm.repository.spi.HgWorkingCopyFactory; import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.util.IOUtil; -import sonia.scm.util.SystemUtil; import java.io.File; import java.io.FileNotFoundException; @@ -53,15 +51,12 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.text.MessageFormat; -import java.util.Optional; @Singleton @Extension public class HgRepositoryHandler extends AbstractSimpleRepositoryHandler { - public static final String RESOURCE_VERSION = "sonia/scm/version/scm-hg-plugin"; public static final String TYPE_DISPLAYNAME = "Mercurial"; public static final String TYPE_NAME = "hg"; public static final RepositoryType TYPE = new RepositoryType( @@ -78,43 +73,25 @@ public class HgRepositoryHandler private static final String CONFIG_KEY_REPOSITORY_ID = "repositoryid"; private final HgWorkingCopyFactory workingCopyFactory; + private final AutoConfigurator configurator; @Inject public HgRepositoryHandler(ConfigurationStoreFactory storeFactory, RepositoryLocationResolver repositoryLocationResolver, - PluginLoader pluginLoader, HgWorkingCopyFactory workingCopyFactory) { + PluginLoader pluginLoader, HgWorkingCopyFactory workingCopyFactory, AutoConfigurator configurator) { super(storeFactory, repositoryLocationResolver, pluginLoader); this.workingCopyFactory = workingCopyFactory; + this.configurator = configurator; } public void doAutoConfiguration(HgConfig autoConfig) { - HgInstaller installer = HgInstallerFactory.createInstaller(); - - try { - if (logger.isDebugEnabled()) { - logger.debug("installing mercurial with {}", installer.getClass().getName()); - } - - installer.install(baseDirectory, autoConfig); - config = autoConfig; - storeConfig(); - } catch (IOException ioe) { - if (logger.isErrorEnabled()) { - logger.error("Could not write Hg CGI for inital config. " - + "HgWeb may not function until a new Hg config is set", ioe); - } - } + configurator.configure(autoConfig); } @Override public void init(SCMContextProvider context) { super.init(context); - writePythonScripts(context); - - // fix wrong hg.bat from package installation - if (SystemUtil.isWindows()) { - HgWindowsPackageFix.fixHgPackage(context, getConfig()); - } + writeHgExtensions(context); } @Override @@ -122,20 +99,18 @@ public class HgRepositoryHandler super.loadConfig(); if (config == null) { - HgConfig config = null; - Optional autoConfigurator = AutoConfigurator.get(); - if (autoConfigurator.isPresent()) { - config = autoConfigurator.get().configure(); - } - - if (config != null && config.isValid()) { - this.config = config; - storeConfig(); - } else { - // do the old configuration - doAutoConfiguration(config != null ? config : new HgConfig()); - } + config = new HgConfig(); + storeConfig(); } + + if (!isConfigValid(config)) { + doAutoConfiguration(config); + storeConfig(); + } + } + + private boolean isConfigValid(HgConfig config) { + return config.isValid() && new HgVerifier().isValid(config); } @Override @@ -154,10 +129,10 @@ public class HgRepositoryHandler } String getVersionInformation(HgVersionCommand command) { - String version = getStringFromResource(RESOURCE_VERSION, DEFAULT_VERSION_INFORMATION); - HgVersion hgVersion = command.get(); - logger.debug("mercurial/python informations: {}", hgVersion); - return MessageFormat.format(version, hgVersion.getPython(), hgVersion.getMercurial()); + return String.format("scm-hg-version/%s %s", + SCMContext.getContext().getVersion(), + command.get() + ); } @Override @@ -176,8 +151,7 @@ public class HgRepositoryHandler } /** - * Writes .hg/hgrc, disables hg access control and added scm hook support. - * see HgPermissionFilter + * Writes repository to .hg/hgrc. * * @param repository * @param directory @@ -205,10 +179,10 @@ public class HgRepositoryHandler return HgConfig.class; } - private void writePythonScripts(SCMContextProvider context) { - IOUtil.mkdirs(HgPythonScript.getScriptDirectory(context)); + private void writeHgExtensions(SCMContextProvider context) { + IOUtil.mkdirs(HgExtensions.getScriptDirectory(context)); - for (HgPythonScript script : HgPythonScript.values()) { + for (HgExtensions script : HgExtensions.values()) { if (logger.isDebugEnabled()) { logger.debug("write python script {}", script.getName()); } @@ -216,16 +190,16 @@ public class HgRepositoryHandler try (InputStream content = input(script); OutputStream output = output(context, script)) { IOUtil.copy(content, output); } catch (IOException ex) { - logger.error("could not write script", ex); + throw new IllegalStateException("could not write hg extension", ex); } } } - private InputStream input(HgPythonScript script) { + private InputStream input(HgExtensions script) { return HgRepositoryHandler.class.getResourceAsStream(script.getResourcePath()); } - private OutputStream output(SCMContextProvider context, HgPythonScript script) throws FileNotFoundException { + private OutputStream output(SCMContextProvider context, HgExtensions script) throws FileNotFoundException { return new FileOutputStream(script.getFile(context)); } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVerifier.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVerifier.java new file mode 100644 index 0000000000..0bca79987c --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVerifier.java @@ -0,0 +1,118 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.io.SimpleCommand; +import sonia.scm.io.SimpleCommandResult; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class HgVerifier { + + private static final Logger LOG = LoggerFactory.getLogger(HgVerifier.class); + private final VersionResolver versionResolver; + + public HgVerifier() { + this.versionResolver = defaultVersionResolver(); + } + + HgVerifier(VersionResolver versionResolver) { + this.versionResolver = versionResolver; + } + + public boolean isValid(HgConfig config) { + return isValid(config.getHgBinary()); + } + + public boolean isValid(String hg) { + return isValid(Paths.get(hg)); + } + + public boolean isValid(Path hg) { + LOG.trace("check if hg binary {} is valid", hg); + if (!Files.isRegularFile(hg)) { + LOG.warn("{} is not a regular file", hg); + return false; + } + + if (!Files.isExecutable(hg)) { + LOG.warn("{} is not executable", hg); + return false; + } + + try { + String version = versionResolver.resolveVersion(hg); + return isVersionValid(hg, version); + } catch (IOException ex) { + LOG.warn("failed to resolve version of {}: ", hg, ex); + return false; + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + LOG.warn("failed to resolve version of {}: ", hg, ex); + return false; + } + } + + private boolean isVersionValid(Path hg, String version) { + String[] parts = version.split("\\."); + if (parts.length < 2) { + LOG.warn("{} returned invalid version: {}", hg, version); + return false; + } + try { + int major = Integer.parseInt(parts[0]); + if (major < 4) { + LOG.warn("{} is too old, we need at least mercurial 4.x", hg); + return false; + } + } catch (NumberFormatException ex) { + LOG.warn("{} returned invalid version {}", hg, version); + return false; + } + return true; + } + + private VersionResolver defaultVersionResolver() { + return hg -> { + SimpleCommand command = new SimpleCommand(hg.toString(), "version", "--template", "{ver}"); + SimpleCommandResult result = command.execute(); + if (!result.isSuccessfull()) { + throw new IOException("failed to get version from hg"); + } + return result.getOutput().trim(); + }; + } + + @FunctionalInterface + interface VersionResolver { + String resolveVersion(Path hg) throws IOException, InterruptedException; + } + +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgWindowsPackageFix.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgWindowsPackageFix.java deleted file mode 100644 index 479bd28385..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgWindowsPackageFix.java +++ /dev/null @@ -1,330 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.repository; - -//~--- non-JDK imports -------------------------------------------------------- - -import com.google.common.base.Charsets; -import com.google.common.base.Throwables; -import com.google.common.io.Files; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import sonia.scm.SCMContextProvider; -import sonia.scm.util.IOUtil; -import sonia.scm.web.HgUtil; - -//~--- JDK imports ------------------------------------------------------------ - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.IOException; - -/** - * - * @author Sebastian Sdorra - */ -public final class HgWindowsPackageFix -{ - - /** Field description */ - static final String MODIFY_MARK_01 = ".setbinary"; - - /** Field description */ - static final String MODIFY_MARK_02 = ".setpythonpath"; - - /** Field description */ - private static final String HG_BAT = "hg.bat"; - - /** Field description */ - private static final String HG_PY = "hg.py"; - - /** Field description */ - private static final String PYTHONPATH_FIXED = - "set PYTHONPATH=%~dp0..\\lib;%PYTHONHOME%\\Lib;%PYTHONPATH%"; - - /** Field description */ - private static final String PYTHONPATH_WRONG = - "set PYTHONPATH=%~dp0..\\lib;%PYTHONHOME%\\Lib"; - - /** - * the logger for HgUtil - */ - private static final Logger logger = LoggerFactory.getLogger(HgUtil.class); - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - */ - private HgWindowsPackageFix() {} - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param context - * @param config - */ - public static void fixHgPackage(SCMContextProvider context, HgConfig config) - { - if ((config != null) && config.isValid()) - { - String basePath = context.getBaseDirectory().getAbsolutePath(); - - String hg = config.getHgBinary(); - - if (hg.startsWith(basePath) && hg.endsWith(HG_BAT)) - { - File file = new File(hg); - - fixHgBat(file); - - file = new File(file.getParentFile(), HG_PY); - fixHgPy(file); - } - } - else if (logger.isDebugEnabled()) - { - logger.debug( - "could not fix hg.py, because the configuration is not valid"); - } - } - - /** - * Visible for testing - * - * @param hgBat - */ - static void fixHgBat(File hgBat) - { - if (hgBat.exists()) - { - File binDirectory = hgBat.getParentFile(); - File modifyMark = new File(binDirectory, MODIFY_MARK_02); - - if (!modifyMark.exists()) - { - try - { - String content = Files.toString(hgBat, Charsets.UTF_8); - - if (!content.contains(PYTHONPATH_FIXED)) - { - content = content.replace(PYTHONPATH_WRONG, PYTHONPATH_FIXED); - Files.write(content, hgBat, Charsets.UTF_8); - } - - createModifyMark(modifyMark); - } - catch (IOException ex) - { - logger.error("could not read content of {}", hgBat); - - throw Throwables.propagate(ex); - } - } - else - { - logger.debug("hg.bat allready fixed"); - } - } - else - { - logger.warn("could not find hg.bat at {}", hgBat); - } - } - - /** - * Visible for testing - * - * - * - * @param hgPy - */ - static void fixHgPy(File hgPy) - { - - if (hgPy.exists()) - { - - File binDirectory = hgPy.getParentFile(); - File modifyMark = new File(binDirectory, MODIFY_MARK_01); - - if (!modifyMark.exists()) - { - if (logger.isDebugEnabled()) - { - logger.debug("check hg.py for setbinary at {}", hgPy); - } - - if (!isSetBinaryAvailable(hgPy)) - { - injectSetBinary(hgPy); - } - else - { - createModifyMark(modifyMark); - } - } - else if (logger.isDebugEnabled()) - { - logger.debug("hg.py allready fixed"); - } - - } - else if (logger.isWarnEnabled()) - { - logger.warn("could not find hg.py at {}", hgPy); - } - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Visible for testing - * - * - * @param hg - * - * @return - */ - static boolean isSetBinaryAvailable(File hg) - { - boolean setBinaryAvailable = false; - - BufferedReader reader = null; - - try - { - reader = Files.newReader(hg, Charsets.UTF_8); - - String line = reader.readLine(); - - while (line != null) - { - line = line.trim(); - - if (line.contains("mercurial.util.setbinary") &&!line.startsWith("#")) - { - setBinaryAvailable = true; - - break; - } - - line = reader.readLine(); - } - } - catch (IOException ex) - { - logger.error("could not check hg.bat", ex); - } - finally - { - IOUtil.close(reader); - } - - return setBinaryAvailable; - } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param modifyMark - */ - private static void createModifyMark(File modifyMark) - { - try - { - if (!modifyMark.createNewFile()) - { - throw new RuntimeException("could not create modify mark"); - } - } - catch (IOException ex) - { - throw new RuntimeException("could not create modify mark", ex); - } - } - - /** - * Method description - * - * - * @param hg - */ - private static void injectSetBinary(File hg) - { - String lineSeparator = System.getProperty("line.separator"); - File mod = new File(hg.getParentFile(), MODIFY_MARK_01); - - hg.renameTo(mod); - - BufferedWriter writer = null; - BufferedReader reader = null; - - try - { - writer = Files.newWriter(hg, Charsets.UTF_8); - reader = Files.newReader(mod, Charsets.UTF_8); - - String line = reader.readLine(); - - while (line != null) - { - - if (line.trim().equals("mercurial.dispatch.run()")) - { - writer.write("for fp in (sys.stdin, sys.stdout, sys.stderr):"); - writer.write(lineSeparator); - writer.write(" mercurial.util.setbinary(fp)"); - writer.write(lineSeparator); - writer.write(lineSeparator); - } - - writer.write(line); - writer.write(lineSeparator); - line = reader.readLine(); - } - } - catch (IOException ex) - { - logger.error("could not check hg.bat", ex); - } - finally - { - IOUtil.close(reader); - IOUtil.close(writer); - } - } -} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgVersionCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgVersionCommand.java index 2d4e44f5a7..52a513067a 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgVersionCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgVersionCommand.java @@ -24,93 +24,71 @@ package sonia.scm.repository.spi; -import com.google.common.annotations.VisibleForTesting; import com.google.common.io.ByteStreams; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.SCMContext; import sonia.scm.repository.HgConfig; -import sonia.scm.repository.HgVersion; +import sonia.scm.repository.HgExtensions; -import javax.annotation.Nonnull; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; public class HgVersionCommand { private static final Logger LOG = LoggerFactory.getLogger(HgVersionCommand.class); - - @VisibleForTesting - static final String[] HG_ARGS = { - "version", "--template", "{ver}" - }; - - @VisibleForTesting - static final String[] PYTHON_ARGS = { - "-c", "import sys; print(sys.version)" - }; + public static final String UNKNOWN = "python/x.y.z mercurial/x.y.z"; private final HgConfig config; + private final String extension; private final ProcessExecutor executor; public HgVersionCommand(HgConfig config) { - this(config, command -> new ProcessBuilder(command).start()); + this(config, extension(), command -> new ProcessBuilder(command).start()); } - HgVersionCommand(HgConfig config, ProcessExecutor executor) { + HgVersionCommand(HgConfig config, String extension, ProcessExecutor executor) { this.config = config; + this.extension = extension; this.executor = executor; } - public HgVersion get() { - return new HgVersion(getHgVersion(), getPythonVersion()); + private static String extension() { + return HgExtensions.VERSION.getFile(SCMContext.getContext()).getAbsolutePath(); } - @Nonnull - private String getPythonVersion() { + public String get() { + List command = createCommand(); try { - String content = exec(config.getPythonBinary(), PYTHON_ARGS); - int index = content.indexOf(' '); - if (index > 0) { - return content.substring(0, index); - } - } catch (IOException ex) { - LOG.warn("failed to get python version", ex); - } catch (InterruptedException ex) { - LOG.warn("failed to get python version", ex); - Thread.currentThread().interrupt(); - } - return HgVersion.UNKNOWN; - } - - @Nonnull - private String getHgVersion() { - try { - return exec(config.getHgBinary(), HG_ARGS).trim(); + return exec(command).trim(); } catch (IOException ex) { LOG.warn("failed to get mercurial version", ex); } catch (InterruptedException ex) { LOG.warn("failed to get mercurial version", ex); Thread.currentThread().interrupt(); } - return HgVersion.UNKNOWN; + return UNKNOWN; } - @SuppressWarnings("UnstableApiUsage") - private String exec(String command, String[] args) throws IOException, InterruptedException { - List cmd = new ArrayList<>(); - cmd.add(command); - cmd.addAll(Arrays.asList(args)); + private List createCommand() { + List command = new ArrayList<>(); + command.add(config.getHgBinary()); + command.add("--config"); + command.add("extensions.scmversion=" + extension); + command.add("scmversion"); + return command; + } - Process process = executor.execute(cmd); + private String exec(List command) throws IOException, InterruptedException { + Process process = executor.execute(command); byte[] bytes = ByteStreams.toByteArray(process.getInputStream()); int exitCode = process.waitFor(); if (exitCode != 0) { throw new IOException("process ends with exit code " + exitCode); } - return new String(bytes, StandardCharsets.UTF_8); + return new String(bytes, StandardCharsets.UTF_8).trim(); } @FunctionalInterface diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkingCopyFactory.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkingCopyFactory.java index 8c01c369ff..57b032e466 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkingCopyFactory.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkingCopyFactory.java @@ -32,6 +32,7 @@ import com.aragost.javahg.commands.PullCommand; import com.aragost.javahg.commands.StatusCommand; import com.aragost.javahg.commands.UpdateCommand; import com.aragost.javahg.commands.flags.CloneCommandFlags; +import sonia.scm.repository.HgExtensions; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.work.SimpleWorkingCopyFactory; import sonia.scm.repository.work.WorkingCopyPool; @@ -99,7 +100,8 @@ public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory createArgs() { + List args = new ArrayList<>(); + config(args, "extensions.cgiserve", extension.getAbsolutePath()); - /** - * Method description - * - * - * @return - */ - private String getInterpreter() - { - HgConfig config = handler.getConfig(); + String hooks = HgExtensions.HOOK.getFile().getAbsolutePath(); + config(args, "hooks.pretxnchangegroup.scm", String.format("python:%s:pre_hook", hooks)); + config(args, "hooks.changegroup.scm", String.format("python:%s:post_hook", hooks)); - AssertUtil.assertIsNotNull(config); + config(args, "web.push_ssl", "false"); + config(args, "web.allow_read", "*"); + config(args, "web.allow_push", "*"); + args.add("cgiserve"); + return args; + } - String python = config.getPythonBinary(); - - if ((python != null) && config.isUseOptimizedBytecode()) - { - python = python.concat(" -O"); - } - - return python; + private void config(List args, String key, String value) { + args.add("--config"); + args.add(key + "=" + value); } //~--- fields --------------------------------------------------------------- @@ -202,7 +176,7 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet private final CGIExecutorFactory cgiExecutorFactory; /** Field description */ - private final File command; + private final File extension; /** Field description */ private final ScmConfiguration configuration; diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java index 64a27990a8..a574971529 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java @@ -29,10 +29,7 @@ package sonia.scm.web; import com.google.inject.servlet.ServletModule; import org.mapstruct.factory.Mappers; import sonia.scm.api.v2.resources.HgConfigDtoToHgConfigMapper; -import sonia.scm.api.v2.resources.HgConfigInstallationsToDtoMapper; -import sonia.scm.api.v2.resources.HgConfigPackagesToDtoMapper; import sonia.scm.api.v2.resources.HgConfigToHgConfigDtoMapper; -import sonia.scm.installer.HgPackageReader; import sonia.scm.plugin.Extension; import sonia.scm.repository.spi.HgWorkingCopyFactory; import sonia.scm.repository.spi.SimpleHgWorkingCopyFactory; @@ -46,12 +43,8 @@ public class HgServletModule extends ServletModule { @Override protected void configureServlets() { - bind(HgPackageReader.class); - bind(HgConfigDtoToHgConfigMapper.class).to(Mappers.getMapper(HgConfigDtoToHgConfigMapper.class).getClass()); bind(HgConfigToHgConfigDtoMapper.class).to(Mappers.getMapper(HgConfigToHgConfigDtoMapper.class).getClass()); - bind(HgConfigPackagesToDtoMapper.class).to(Mappers.getMapper(HgConfigPackagesToDtoMapper.class).getClass()); - bind(HgConfigInstallationsToDtoMapper.class); bind(HgWorkingCopyFactory.class).to(SimpleHgWorkingCopyFactory.class); } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java index 3ba797aff2..4353ec74e9 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java @@ -24,13 +24,8 @@ package sonia.scm.web; -import sonia.scm.SCMContext; -import sonia.scm.repository.HgConfig; -import sonia.scm.repository.HgPythonScript; import sonia.scm.util.Util; -import java.io.File; - /** * * @author Sebastian Sdorra @@ -41,26 +36,6 @@ public final class HgUtil { private HgUtil() {} - public static String getPythonPath(HgConfig config) { - String pythonPath = Util.EMPTY_STRING; - - if (config != null) { - pythonPath = Util.nonNull(config.getPythonPath()); - } - - if (Util.isNotEmpty(pythonPath)) { - pythonPath = pythonPath.concat(File.pathSeparator); - } - - pythonPath = pythonPath.concat( - HgPythonScript.getScriptDirectory( - SCMContext.getContext() - ).getAbsolutePath() - ); - - return pythonPath; - } - public static String getRevision(String revision) { return Util.isEmpty(revision) ? REVISION_TIP : revision; } diff --git a/scm-plugins/scm-hg-plugin/src/main/js/HgConfigurationForm.tsx b/scm-plugins/scm-hg-plugin/src/main/js/HgConfigurationForm.tsx index bbcf11c012..c56ead70af 100644 --- a/scm-plugins/scm-hg-plugin/src/main/js/HgConfigurationForm.tsx +++ b/scm-plugins/scm-hg-plugin/src/main/js/HgConfigurationForm.tsx @@ -28,10 +28,7 @@ import { InputField, Checkbox } from "@scm-manager/ui-components"; type Configuration = { hgBinary: string; - pythonBinary: string; - pythonPath?: string; encoding: string; - useOptimizedBytecode: boolean; showRevisionInId: boolean; enableHttpPostArgs: boolean; _links: Links; @@ -58,7 +55,7 @@ class HgConfigurationForm extends React.Component { } updateValidationStatus = () => { - const requiredFields = ["hgBinary", "pythonBinary", "encoding"]; + const requiredFields = ["hgBinary", "encoding"]; const validationErrors = []; for (const field of requiredFields) { @@ -130,11 +127,8 @@ class HgConfigurationForm extends React.Component { return (
{this.inputField("hgBinary")} - {this.inputField("pythonBinary")} - {this.inputField("pythonPath")} {this.inputField("encoding")}
- {this.checkbox("useOptimizedBytecode")} {this.checkbox("showRevisionInId")}
diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json b/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json index 2d4b08cd2d..9c3f458d26 100644 --- a/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json +++ b/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json @@ -13,14 +13,8 @@ "title": "Mercurial Konfiguration", "hgBinary": "HG Binary", "hgBinaryHelpText": "Pfad des Mercurial Binary.", - "pythonBinary": "Python Binary", - "pythonBinaryHelpText": "Pfad des Python binary.", - "pythonPath": "Python Module Such Pfad", - "pythonPathHelpText": "Python Module Such Pfad (PYTHONPATH).", "encoding": "Encoding", "encodingHelpText": "Repository Encoding.", - "useOptimizedBytecode": "Optimized Bytecode (.pyo)", - "useOptimizedBytecodeHelpText": "Verwende den Python '-O' Switch.", "showRevisionInId": "Revision anzeigen", "showRevisionInIdHelpText": "Die Revision als Teil der Node ID anzeigen.", "enableHttpPostArgs": "HttpPostArgs Protocol aktivieren", diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json b/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json index ed488876d5..c542511227 100644 --- a/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json +++ b/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json @@ -13,14 +13,8 @@ "title": "Mercurial Configuration", "hgBinary": "HG Binary", "hgBinaryHelpText": "Location of Mercurial binary.", - "pythonBinary": "Python Binary", - "pythonBinaryHelpText": "Location of Python binary.", - "pythonPath": "Python Module Search Path", - "pythonPathHelpText": "Python Module Search Path (PYTHONPATH).", "encoding": "Encoding", "encodingHelpText": "Repository Encoding.", - "useOptimizedBytecode": "Optimized Bytecode (.pyo)", - "useOptimizedBytecodeHelpText": "Use the Python '-O' switch.", "showRevisionInId": "Show Revision", "showRevisionInIdHelpText": "Show revision as part of the node id.", "enableHttpPostArgs": "Enable HttpPostArgs Protocol", diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/hgweb.py b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/cgiserve.py similarity index 55% rename from scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/hgweb.py rename to scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/cgiserve.py index de7907f403..747c3c7024 100644 --- a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/hgweb.py +++ b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/cgiserve.py @@ -22,33 +22,20 @@ # SOFTWARE. # - -import os -from mercurial import demandimport, ui as uimod, hg +from mercurial import registrar from mercurial.hgweb import hgweb, wsgicgi -demandimport.enable() +cmdtable = {} try: - u = uimod.ui.load() -except AttributeError: - # For installations earlier than Mercurial 4.1 - u = uimod.ui() + from mercurial import registrar + command = registrar.command(cmdtable) +except (AttributeError, ImportError): + # Fallback to hg < 4.3 support + from mercurial import cmdutil + command = cmdutil.command(cmdtable) -u.setconfig(b'web', b'push_ssl', b'false') -u.setconfig(b'web', b'allow_read', b'*') -u.setconfig(b'web', b'allow_push', b'*') - -u.setconfig(b'hooks', b'changegroup.scm', b'python:scmhooks.post_hook') -u.setconfig(b'hooks', b'pretxnchangegroup.scm', b'python:scmhooks.pre_hook') - -# pass SCM_HTTP_POST_ARGS to enable experimental httppostargs protocol of mercurial -# SCM_HTTP_POST_ARGS is set by HgCGIServlet -# Issue 970: https://goo.gl/poascp -u.setconfig(b'experimental', b'httppostargs', os.environ['SCM_HTTP_POST_ARGS'].encode()) - -# open repository -# SCM_REPOSITORY_PATH contains the repository path and is set by HgCGIServlet -r = hg.repository(u, os.environ['SCM_REPOSITORY_PATH'].encode()) -application = hgweb(r) -wsgicgi.launch(application) +@command(b'cgiserve') +def cgiserve(ui, repo, **opts): + application = hgweb(repo) + wsgicgi.launch(application) diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview.py b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/fileview.py similarity index 100% rename from scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview.py rename to scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/fileview.py diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview_test.py b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/fileview_test.py similarity index 100% rename from scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview_test.py rename to scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/fileview_test.py diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmversion.py b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmversion.py new file mode 100644 index 0000000000..41e047367f --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmversion.py @@ -0,0 +1,41 @@ +# +# MIT License +# +# Copyright (c) 2020-present Cloudogu GmbH and Contributors +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +from mercurial import util +import platform + +cmdtable = {} + +try: + from mercurial import registrar + command = registrar.command(cmdtable) +except (AttributeError, ImportError): + # Fallback to hg < 4.3 support + from mercurial import cmdutil + command = cmdutil.command(cmdtable) + +@command(b'scmversion', norepo=True) +def scmversion(ui, **opts): + pythonVersion = platform.python_version().encode("utf-8") + ui.write(b"python/%s mercurial/%s\n" % (pythonVersion, util.version())) diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/version/scm-hg-plugin b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/version/scm-hg-plugin deleted file mode 100644 index 7ca58dea27..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/version/scm-hg-plugin +++ /dev/null @@ -1 +0,0 @@ -scm-hg-version/${project.version} python/{0} mercurial/{1} \ No newline at end of file diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java index 2694657bd5..5820c58cad 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.api.v2.resources; import com.github.sdorra.shiro.ShiroRule; @@ -79,8 +79,8 @@ public class HgConfigAutoConfigurationResourceTest { when(resourceProvider.get()).thenReturn(resource); dispatcher.addSingletonResource( - new HgConfigResource(null, null, null, null, - resourceProvider, null)); + new HgConfigResource(null, null, null, + resourceProvider)); } @Test diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapperTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapperTest.java index 7779380638..887ae234a6 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapperTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapperTest.java @@ -48,10 +48,7 @@ public class HgConfigDtoToHgConfigMapperTest { assertEquals("ABC", config.getEncoding()); assertEquals("/etc/hg", config.getHgBinary()); - assertEquals("/py", config.getPythonBinary()); - assertEquals("/etc/", config.getPythonPath()); assertTrue(config.isShowRevisionInId()); - assertTrue(config.isUseOptimizedBytecode()); assertTrue(config.isEnableHttpPostArgs()); } @@ -60,10 +57,7 @@ public class HgConfigDtoToHgConfigMapperTest { configDto.setDisabled(true); configDto.setEncoding("ABC"); configDto.setHgBinary("/etc/hg"); - configDto.setPythonBinary("/py"); - configDto.setPythonPath("/etc/"); configDto.setShowRevisionInId(true); - configDto.setUseOptimizedBytecode(true); configDto.setEnableHttpPostArgs(true); return configDto; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java deleted file mode 100644 index 4f3c6e9419..0000000000 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.api.v2.resources; - -import com.github.sdorra.shiro.ShiroRule; -import com.github.sdorra.shiro.SubjectAware; -import org.jboss.resteasy.mock.MockHttpRequest; -import org.jboss.resteasy.mock.MockHttpResponse; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Answers; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import sonia.scm.web.RestDispatcher; - -import javax.inject.Provider; -import javax.servlet.http.HttpServletResponse; -import java.net.URI; -import java.net.URISyntaxException; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.when; - -@SubjectAware( - configuration = "classpath:sonia/scm/configuration/shiro.ini", - password = "secret" -) -@RunWith(MockitoJUnitRunner.class) -public class HgConfigInstallationsResourceTest { - - @Rule - public ShiroRule shiro = new ShiroRule(); - - private RestDispatcher dispatcher = new RestDispatcher(); - - private final URI baseUri = URI.create("/"); - - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private ScmPathInfoStore scmPathInfoStore; - - @InjectMocks - private HgConfigInstallationsToDtoMapper mapper; - - @Mock - private Provider resourceProvider; - - - @Before - public void prepareEnvironment() { - HgConfigInstallationsResource resource = new HgConfigInstallationsResource(mapper); - - when(resourceProvider.get()).thenReturn(resource); - dispatcher.addSingletonResource( - new HgConfigResource(null, null, null, null, - null, resourceProvider)); - - when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri); - } - - @Test - @SubjectAware(username = "readOnly") - public void shouldGetHgInstallations() throws Exception { - MockHttpResponse response = get("hg"); - - assertEquals(HttpServletResponse.SC_OK, response.getStatus()); - - String contentAsString = response.getContentAsString(); - assertThat(contentAsString).contains("{\"paths\":["); - assertThat(contentAsString).contains("hg"); - assertThat(contentAsString).doesNotContain("python"); - - assertThat(contentAsString).contains("\"self\":{\"href\":\"/v2/config/hg/installations/hg"); - } - - @Test - @SubjectAware(username = "writeOnly") - public void shouldNotGetHgInstallationsWhenNotAuthorized() throws Exception { - MockHttpResponse response = get("hg"); - - assertEquals("Subject does not have permission [configuration:read:hg]", response.getContentAsString()); - assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); - } - - @Test - @SubjectAware(username = "readOnly") - public void shouldGetPythonInstallations() throws Exception { - MockHttpResponse response = get("python"); - - assertEquals(HttpServletResponse.SC_OK, response.getStatus()); - - String contentAsString = response.getContentAsString(); - assertThat(contentAsString).contains("{\"paths\":["); - assertThat(contentAsString).contains("python"); - - assertThat(contentAsString).contains("\"self\":{\"href\":\"/v2/config/hg/installations/python"); - } - - @Test - @SubjectAware(username = "writeOnly") - public void shouldNotGetPythonInstallationsWhenNotAuthorized() throws Exception { - MockHttpResponse response = get("python"); - - assertEquals("Subject does not have permission [configuration:read:hg]", response.getContentAsString()); - assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); - } - - private MockHttpResponse get(String path) throws URISyntaxException { - MockHttpRequest request = MockHttpRequest.get("/" + HgConfigResource.HG_CONFIG_PATH_V2 + "/installations/" + path); - MockHttpResponse response = new MockHttpResponse(); - dispatcher.invoke(request, response); - return response; - } -} diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java deleted file mode 100644 index 2fa72b7fd9..0000000000 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.api.v2.resources; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.github.sdorra.shiro.ShiroRule; -import com.github.sdorra.shiro.SubjectAware; -import org.jboss.resteasy.mock.MockHttpRequest; -import org.jboss.resteasy.mock.MockHttpResponse; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Answers; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import sonia.scm.installer.HgPackage; -import sonia.scm.installer.HgPackageReader; -import sonia.scm.net.ahc.AdvancedHttpClient; -import sonia.scm.repository.HgConfig; -import sonia.scm.repository.HgRepositoryHandler; -import sonia.scm.web.RestDispatcher; - -import javax.inject.Provider; -import javax.servlet.http.HttpServletResponse; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Arrays; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.when; -import static sonia.scm.api.v2.resources.HgConfigTests.createPackage; - -@SubjectAware( - configuration = "classpath:sonia/scm/configuration/shiro.ini", - password = "secret" -) -@RunWith(MockitoJUnitRunner.class) -public class HgConfigPackageResourceTest { - - public static final String URI = "/" + HgConfigResource.HG_CONFIG_PATH_V2 + "/packages"; - @Rule - public ShiroRule shiro = new ShiroRule(); - - private RestDispatcher dispatcher = new RestDispatcher(); - - private final URI baseUri = java.net.URI.create("/"); - - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private ScmPathInfoStore scmPathInfoStore; - - @InjectMocks - private HgConfigPackagesToDtoMapperImpl mapper; - - @Mock - private HgRepositoryHandler repositoryHandler; - - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private HgPackageReader hgPackageReader; - - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private AdvancedHttpClient advancedHttpClient; - - @Mock - private Provider hgConfigPackageResourceProvider; - - @Mock - private HgPackage hgPackage; - - @Before - public void prepareEnvironment() { - setupResources(); - - when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri); - - when(hgPackageReader.getPackages().getPackages()).thenReturn(createPackages()); - } - - @Test - @SubjectAware(username = "readOnly") - public void shouldGetPackages() throws Exception { - MockHttpResponse response = get(); - - assertEquals(HttpServletResponse.SC_OK, response.getStatus()); - - String responseString = response.getContentAsString(); - ObjectNode responseJson = new ObjectMapper().readValue(responseString, ObjectNode.class); - - JsonNode packages = responseJson.get("packages"); - assertThat(packages).isNotNull(); - assertThat(packages).hasSize(2); - - JsonNode package1 = packages.get(0); - assertThat(package1.get("_links")).isNull(); - - JsonNode hgConfigTemplate = package1.get("hgConfigTemplate"); - assertThat(hgConfigTemplate).isNotNull(); - assertThat(hgConfigTemplate.get("_links")).isNull(); - - assertThat(responseString).contains("\"_links\":{\"self\":{\"href\":\"/v2/config/hg/packages"); - } - - @Test - @SubjectAware(username = "writeOnly") - public void shouldNotGetPackagesWhenNotAuthorized() throws Exception { - MockHttpResponse response = get(); - - assertEquals("Subject does not have permission [configuration:read:hg]", response.getContentAsString()); - assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); - } - - @Test - @SubjectAware(username = "writeOnly") - public void shouldInstallPackage() throws Exception { - String packgeId = "ourPackage"; - String url = "http://url"; - - setupPackageInstallation(packgeId, url); - when(advancedHttpClient.get(url).request().contentAsStream()) - .thenReturn(new ByteArrayInputStream("mockedFile".getBytes())); - - MockHttpResponse response = put(packgeId); - assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus()); - } - - @Test - @SubjectAware(username = "writeOnly") - public void shouldHandleFailingInstallation() throws Exception { - String packgeId = "ourPackage"; - String url = "http://url"; - - setupPackageInstallation(packgeId, url); - when(advancedHttpClient.get(url).request().contentAsStream()) - .thenThrow(new IOException("mocked Exception")); - - MockHttpResponse response = put(packgeId); - assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, response.getStatus()); - } - - @Test - @SubjectAware(username = "writeOnly") - public void shouldHandlePackagesThatAreNotFound() throws Exception { - String packageId = "this-package-does-not-ex"; - when(hgPackageReader.getPackage(packageId)).thenReturn(null); - MockHttpResponse response = put(packageId); - assertEquals(HttpServletResponse.SC_NOT_FOUND, response.getStatus()); - } - - @Test - @SubjectAware(username = "readOnly") - public void shouldNotInstallPackageWhenNotAuthorized() throws Exception { - MockHttpResponse response = put("don-t-care"); - - assertEquals("Subject does not have permission [configuration:write:hg]", response.getContentAsString()); - assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); - } - - private List createPackages() { - return Arrays.asList(createPackage(), new HgPackage()); - } - - private MockHttpResponse get() throws URISyntaxException { - MockHttpRequest request = MockHttpRequest.get(URI); - MockHttpResponse response = new MockHttpResponse(); - dispatcher.invoke(request, response); - return response; - } - - private MockHttpResponse put(String pckgId) throws URISyntaxException { - String packgeIdParam = ""; - if (pckgId != null) { - packgeIdParam = "/" + pckgId; - } - MockHttpRequest request = MockHttpRequest.put(URI + packgeIdParam); - - MockHttpResponse response = new MockHttpResponse(); - dispatcher.invoke(request, response); - return response; - } - - private void setupResources() { - HgConfigPackageResource hgConfigPackageResource = - new HgConfigPackageResource(hgPackageReader, advancedHttpClient, repositoryHandler, mapper); - - when(hgConfigPackageResourceProvider.get()).thenReturn(hgConfigPackageResource); - dispatcher.addSingletonResource( - new HgConfigResource(null, null, null, - hgConfigPackageResourceProvider, null, null)); - } - - private void setupPackageInstallation(String packgeId, String url) throws IOException { - when(hgPackage.getId()).thenReturn(packgeId); - when(hgPackageReader.getPackage(packgeId)).thenReturn(hgPackage); - when(repositoryHandler.getConfig()).thenReturn(new HgConfig()); - when(hgPackage.getHgConfigTemplate()).thenReturn(new HgConfig()); - when(hgPackage.getUrl()).thenReturn(url); - } - -} diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackagesToDtoMapperTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackagesToDtoMapperTest.java deleted file mode 100644 index ca98f94dde..0000000000 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackagesToDtoMapperTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.api.v2.resources; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Answers; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import sonia.scm.installer.HgPackage; -import sonia.scm.installer.HgPackages; - -import java.net.URI; -import java.util.Arrays; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.when; -import static sonia.scm.api.v2.resources.HgConfigTests.assertEqualsPackage; -import static sonia.scm.api.v2.resources.HgConfigTests.createPackage; - -@RunWith(MockitoJUnitRunner.class) -public class HgConfigPackagesToDtoMapperTest { - - private URI baseUri = URI.create("http://example.com/base/"); - - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private ScmPathInfoStore scmPathInfoStore; - - @InjectMocks - private HgConfigPackagesToDtoMapperImpl mapper; - - private URI expectedBaseUri; - - @Before - public void init() { - when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri); - expectedBaseUri = baseUri.resolve(HgConfigResource.HG_CONFIG_PATH_V2 + "/packages"); - } - - @Test - public void shouldMapFields() { - HgPackages hgPackages = new HgPackages(); - hgPackages.setPackages(createPackages()); - - HgConfigPackagesDto dto = mapper.map(hgPackages); - - assertThat(dto.getPackages()).hasSize(2); - - HgConfigPackagesDto.HgConfigPackageDto hgPackageDto1 = dto.getPackages().get(0); - assertEqualsPackage(hgPackageDto1); - - HgConfigPackagesDto.HgConfigPackageDto hgPackageDto2 = dto.getPackages().get(1); - // Just verify a random field - assertThat(hgPackageDto2.getId()).isNull(); - - assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); - } - - - private List createPackages() { - return Arrays.asList(createPackage(), new HgPackage()); - } - -} diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java index f30d8e50cc..ec686dc1e6 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.api.v2.resources; import com.fasterxml.jackson.databind.ObjectMapper; @@ -81,22 +81,15 @@ public class HgConfigResourceTest { @Mock private HgRepositoryHandler repositoryHandler; - @Mock - private Provider packagesResource; - @Mock private Provider autoconfigResource; - @Mock - private Provider installationsResource; - @Before public void prepareEnvironment() { HgConfig gitConfig = createConfiguration(); when(repositoryHandler.getConfig()).thenReturn(gitConfig); HgConfigResource gitConfigResource = - new HgConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler, packagesResource, - autoconfigResource, installationsResource); + new HgConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler, autoconfigResource); dispatcher.addSingletonResource(gitConfigResource); when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri); } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigTests.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigTests.java index f1b5d5576a..d793180d1c 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigTests.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigTests.java @@ -21,10 +21,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.api.v2.resources; -import sonia.scm.installer.HgPackage; import sonia.scm.repository.HgConfig; import static org.junit.Assert.assertEquals; @@ -41,10 +40,7 @@ class HgConfigTests { config.setEncoding("ABC"); config.setHgBinary("/etc/hg"); - config.setPythonBinary("/py"); - config.setPythonPath("/etc/"); config.setShowRevisionInId(true); - config.setUseOptimizedBytecode(true); return config; } @@ -54,36 +50,7 @@ class HgConfigTests { assertEquals("ABC", dto.getEncoding()); assertEquals("/etc/hg", dto.getHgBinary()); - assertEquals("/py", dto.getPythonBinary()); - assertEquals("/etc/", dto.getPythonPath()); assertTrue(dto.isShowRevisionInId()); - assertTrue(dto.isUseOptimizedBytecode()); - } - - static HgPackage createPackage() { - HgPackage hgPackage= new HgPackage(); - hgPackage.setArch("arch"); - hgPackage.setId("1"); - hgPackage.setHgVersion("2"); - hgPackage.setPlatform("someOs"); - hgPackage.setPythonVersion("3"); - hgPackage.setSize(4); - hgPackage.setUrl("https://package"); - hgPackage.setHgConfigTemplate(createConfiguration()); - return hgPackage; - } - - static void assertEqualsPackage(HgConfigPackagesDto.HgConfigPackageDto dto) { - assertEquals("arch", dto.getArch()); - assertEquals("1", dto.getId()); - assertEquals("2", dto.getHgVersion()); - assertEquals("someOs", dto.getPlatform()); - assertEquals("3", dto.getPythonVersion()); - assertEquals(4, dto.getSize()); - assertEquals("https://package", dto.getUrl()); - - assertEqualsConfiguration(dto.getHgConfigTemplate()); - assertTrue(dto.getHgConfigTemplate().getLinks().isEmpty()); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVersion.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/autoconfig/AutoConfigModuleTest.java similarity index 69% rename from scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVersion.java rename to scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/autoconfig/AutoConfigModuleTest.java index a517c2fe08..d1275b716c 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVersion.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/autoconfig/AutoConfigModuleTest.java @@ -22,27 +22,21 @@ * SOFTWARE. */ -package sonia.scm.repository; +package sonia.scm.autoconfig; -import lombok.AllArgsConstructor; -import lombok.Data; +import com.google.inject.Guice; +import com.google.inject.Injector; +import org.junit.jupiter.api.Test; -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlRootElement; +import static org.assertj.core.api.Assertions.assertThat; -/** - * - * @author Sebastian Sdorra - */ -@Data -@AllArgsConstructor -@XmlRootElement(name = "version") -@XmlAccessorType(XmlAccessType.FIELD) -public class HgVersion { +class AutoConfigModuleTest { - public static final String UNKNOWN = "x.y.z (unknown)"; + @Test + void shouldBindAutoConfigurator() { + Injector injector = Guice.createInjector(new AutoConfigModule()); + AutoConfigurator configurator = injector.getInstance(AutoConfigurator.class); + assertThat(configurator).isNotNull(); + } - private String mercurial; - private String python; } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapperTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/autoconfig/AutoConfiguratorProviderTest.java similarity index 52% rename from scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapperTest.java rename to scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/autoconfig/AutoConfiguratorProviderTest.java index 0c55afbbe0..b322c588cf 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapperTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/autoconfig/AutoConfiguratorProviderTest.java @@ -21,55 +21,48 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - -package sonia.scm.api.v2.resources; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Answers; +package sonia.scm.autoconfig; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; - -import java.net.URI; -import java.util.Arrays; -import java.util.List; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.Platform; +import sonia.scm.repository.HgVerifier; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.when; -@RunWith(MockitoJUnitRunner.class) -public class HgConfigInstallationsToDtoMapperTest { +@ExtendWith(MockitoExtension.class) +class AutoConfiguratorProviderTest { + @Mock + private HgVerifier verifier; - private URI baseUri = URI.create("http://example.com/base/"); - - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private ScmPathInfoStore scmPathInfoStore; + @Mock + private Platform platform; @InjectMocks - private HgConfigInstallationsToDtoMapper mapper; + private AutoConfiguratorProvider provider; - private URI expectedBaseUri; + @Test + void shouldReturnPosixAutoConfiguration() { + when(platform.isPosix()).thenReturn(true); - private String expectedPath = "path"; - - @Before - public void init() { - when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri); - expectedBaseUri = baseUri.resolve(HgConfigResource.HG_CONFIG_PATH_V2 + "/installations/" + expectedPath); + assertThat(provider.get()).isInstanceOf(PosixAutoConfigurator.class); } @Test - public void shouldMapFields() { - List installations = Arrays.asList("/hg", "/bin/hg"); + void shouldReturnWindowsAutoConfiguration() { + when(platform.isWindows()).thenReturn(true); - HgConfigInstallationsDto dto = mapper.map(installations, expectedPath); + assertThat(provider.get()).isInstanceOf(WindowsAutoConfigurator.class); + } - assertThat(dto.getPaths()).isEqualTo(installations); - - assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); + @Test + void shouldReturnNoOpAutoConfiguration() { + assertThat(provider.get()).isInstanceOf(NoOpAutoConfigurator.class); } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/autoconfig/PosixAutoConfiguratorTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/autoconfig/PosixAutoConfiguratorTest.java index 2eaf69c94f..823f52376c 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/autoconfig/PosixAutoConfiguratorTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/autoconfig/PosixAutoConfiguratorTest.java @@ -28,139 +28,101 @@ import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import sonia.scm.repository.HgConfig; +import sonia.scm.repository.HgVerifier; -import java.io.ByteArrayInputStream; import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +@ExtendWith(MockitoExtension.class) class PosixAutoConfiguratorTest { - @Test - void shouldConfigureWithShebangPath(@TempDir Path directory) throws IOException { - Path hg = directory.resolve("hg"); - Path python = directory.resolve("python"); + @Mock + private HgVerifier verifier; - Files.write(hg, ("#!" + python.toAbsolutePath().toString()).getBytes(StandardCharsets.UTF_8)); - Files.createFile(python); + @Test + void shouldConfigureMercurial(@TempDir Path directory) { + Path hg = directory.resolve("hg"); + when(verifier.isValid(hg)).thenReturn(true); PosixAutoConfigurator configurator = create(directory); - HgConfig config = configurator.configure(); + + HgConfig config = new HgConfig(); + configurator.configure(config); assertThat(config.getHgBinary()).isEqualTo(hg.toString()); - assertThat(config.getPythonBinary()).isEqualTo(python.toString()); } - private PosixAutoConfigurator create(@TempDir Path directory) { - return new PosixAutoConfigurator(createEnv(directory), Collections.emptyList()); + private PosixAutoConfigurator create(Path directory) { + return new PosixAutoConfigurator(verifier, createEnv(directory), Collections.emptyList()); } private Map createEnv(Path... paths) { return ImmutableMap.of("PATH", Joiner.on(File.pathSeparator).join(paths)); } - @Test - void shouldConfigureWithShebangEnv(@TempDir Path directory) throws IOException { - Path hg = directory.resolve("hg"); - Path python = directory.resolve("python3.8"); - - Files.write(hg, "#!/usr/bin/env python3.8".getBytes(StandardCharsets.UTF_8)); - Files.createFile(python); - - PosixAutoConfigurator configurator = create(directory); - HgConfig config = configurator.configure(); - - assertThat(config.getHgBinary()).isEqualTo(hg.toString()); - assertThat(config.getPythonBinary()).isEqualTo(python.toString()); - } @Test - void shouldConfigureWithoutShebang(@TempDir Path directory) throws IOException { - Path hg = directory.resolve("hg"); - Path python = directory.resolve("python"); - - Files.createFile(hg); - Files.createFile(python); - - PosixAutoConfigurator configurator = create(directory); - HgConfig config = configurator.configure(); - - assertThat(config.getHgBinary()).isEqualTo(hg.toString()); - assertThat(config.getPythonBinary()).isEqualTo(python.toString()); - } - - @Test - void shouldConfigureWithoutShebangButWithPython3(@TempDir Path directory) throws IOException { - Path hg = directory.resolve("hg"); - Path python = directory.resolve("python3"); - - Files.createFile(hg); - Files.createFile(python); - - PosixAutoConfigurator configurator = create(directory); - HgConfig config = configurator.configure(); - - assertThat(config.getHgBinary()).isEqualTo(hg.toString()); - assertThat(config.getPythonBinary()).isEqualTo(python.toString()); - } - - @Test - void shouldConfigureFindPythonInAdditionalPath(@TempDir Path directory) throws IOException { + void shouldFindMercurialInAdditionalPath(@TempDir Path directory) { Path def = directory.resolve("default"); - Files.createDirectory(def); Path additional = directory.resolve("additional"); - Files.createDirectory(additional); - Path hg = def.resolve("hg"); - Path python = additional.resolve("python"); - Files.createFile(hg); - Files.createFile(python); + when(verifier.isValid(hg)).thenReturn(true); PosixAutoConfigurator configurator = new PosixAutoConfigurator( - createEnv(def), ImmutableList.of(additional.toAbsolutePath().toString()) + verifier, createEnv(def), ImmutableList.of(additional.toAbsolutePath().toString()) ); - HgConfig config = configurator.configure(); + HgConfig config = new HgConfig(); + configurator.configure(config); + assertThat(config.getHgBinary()).isEqualTo(hg.toString()); - assertThat(config.getPythonBinary()).isEqualTo(python.toString()); } @Test - void shouldFindModulePathFromDebuginstallOutput(@TempDir Path directory) throws IOException { - Path hg = directory.resolve("hg"); - Files.createFile(hg); - hg.toFile().setExecutable(true); + void shouldSkipInvalidMercurialInstallations(@TempDir Path directory) { + Path one = directory.resolve("one"); + Path two = directory.resolve("two"); + Path three = directory.resolve("three"); + Path hg = three.resolve("hg"); - Path modules = directory.resolve("modules"); - Files.createDirectories(modules); - - Path mercurialModule = modules.resolve("mercurial"); - Files.createDirectories(mercurialModule); - - PosixAutoConfigurator configurator = create(directory); - configurator.setExecutor((Path binary, String... args) -> { - String content = String.join("\n", - "checking Python executable (/python3.8)", - "checking Python lib (/python3.8)...", - "checking installed modules (" + mercurialModule.toString() + ")...", - "checking templates (/mercurial/templates)...", - "checking default template (/mercurial/templates/map-cmdline.default))" - ); - return new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)); + when(verifier.isValid(any(Path.class))).then(ic -> { + Path path = ic.getArgument(0, Path.class); + return path.equals(hg); }); - HgConfig config = configurator.configure(); + + PosixAutoConfigurator configurator = new PosixAutoConfigurator( + verifier, createEnv(one), ImmutableList.of( + two.toAbsolutePath().toString(), + three.toAbsolutePath().toString() + ) + ); + + HgConfig config = new HgConfig(); + configurator.configure(config); assertThat(config.getHgBinary()).isEqualTo(hg.toString()); - assertThat(config.getPythonPath()).isEqualTo(modules.toString()); + } + + @Test + void shouldNotConfigureMercurial(@TempDir Path directory) { + PosixAutoConfigurator configurator = create(directory); + + HgConfig config = new HgConfig(); + configurator.configure(config); + + assertThat(config.getHgBinary()).isNull(); } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/autoconfig/WindowsAutoConfiguratorTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/autoconfig/WindowsAutoConfiguratorTest.java new file mode 100644 index 0000000000..22e13e2bfb --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/autoconfig/WindowsAutoConfiguratorTest.java @@ -0,0 +1,143 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.autoconfig; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.repository.HgConfig; +import sonia.scm.repository.HgVerifier; + +import java.io.File; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static sonia.scm.autoconfig.WindowsAutoConfigurator.*; + +@ExtendWith(MockitoExtension.class) +class WindowsAutoConfiguratorTest { + + @Mock + private HgVerifier verifier; + + @Mock + private WindowsRegistry registry; + + @ParameterizedTest + @ValueSource(strings = { BINARY_HG_EXE, BINARY_HG_BAT }) + void shouldConfigureHgFromPath(String binary, @TempDir Path directory) { + Path hgPath = directory.resolve(binary); + mockIsValid(hgPath); + + String hg = configure(directory); + assertThat(hg).isEqualTo(hgPath.toString()); + } + + @ParameterizedTest + @ValueSource(strings = { BINARY_HG_EXE, BINARY_HG_BAT }) + void shouldConfigureHgFromSecondPath(String binary, @TempDir Path directory) { + Path one = directory.resolve("one"); + Path two = directory.resolve("two"); + Path hgPath = two.resolve(binary); + mockIsValid(hgPath); + + String hg = configure(one, two); + assertThat(hg).isEqualTo(hgPath.toString()); + } + + @ParameterizedTest + @ValueSource(strings = { REGISTRY_KEY_TORTOISE_HG, REGISTRY_KEY_MERCURIAL }) + void shouldConfigureHgFromHgInstallation(String registryKey, @TempDir Path directory) { + Path one = directory.resolve("one"); + Path two = directory.resolve("two"); + Path hgPath = two.resolve(BINARY_HG_EXE); + + mockIsValid(hgPath); + mockRegistryKey(registryKey, two.toString()); + + String hg = configure(one); + assertThat(hg).isEqualTo(hgPath.toString()); + } + + @Test + void shouldNotConfigureMercurial(@TempDir Path directory) { + String hg = configure(directory); + assertThat(hg).isNull(); + } + + private void mockRegistryKey(String key, String value) { + when(registry.get(anyString())).then(ic -> { + String k = ic.getArgument(0, String.class); + if (key.equals(k)) { + return Optional.of(value); + } + return Optional.empty(); + }); + } + + private void mockIsValid(Path path) { + when(verifier.isValid(any(Path.class))).then(ic -> { + Path p = ic.getArgument(0, Path.class); + return path.equals(p); + }); + } + + private String path(Path... paths) { + return Arrays.stream(paths) + .map(Path::toString) + .collect( + Collectors.joining(File.pathSeparator) + ); + } + + private String configure(Path... paths) { + return configure(path(paths)); + } + + private String configure(String path) { + HgConfig config = new HgConfig(); + configurator(path).configure(config); + return config.getHgBinary(); + } + + private WindowsAutoConfigurator configurator(String path) { + Map env = new HashMap<>(); + env.put(ENV_PATH, path); + return new WindowsAutoConfigurator(verifier, registry, env); + } + +} diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/DefaultHgEnvironmentBuilderTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/DefaultHgEnvironmentBuilderTest.java index 7bf3a3c5bf..145552cca9 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/DefaultHgEnvironmentBuilderTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/DefaultHgEnvironmentBuilderTest.java @@ -53,7 +53,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static sonia.scm.repository.DefaultHgEnvironmentBuilder.*; - @ExtendWith(MockitoExtension.class) class DefaultHgEnvironmentBuilderTest { @@ -83,18 +82,18 @@ class DefaultHgEnvironmentBuilderTest { @Test void shouldReturnReadEnvironment() { - Repository heartOfGold = prepareForRead("/usr/lib/python", "42"); + Repository heartOfGold = prepareForRead("42"); Map env = builder.read(heartOfGold); - assertReadEnv(env, "/usr/lib/python", "42"); + assertReadEnv(env, "42"); } @Test void shouldReturnWriteEnvironment() throws IOException { - Repository heartOfGold = prepareForWrite("/opt/python", "21"); + Repository heartOfGold = prepareForWrite("21"); Map env = builder.write(heartOfGold); - assertReadEnv(env, "/opt/python", "21"); + assertReadEnv(env, "21"); String bearer = CipherUtil.getInstance().decode(env.get(ENV_BEARER_TOKEN)); assertThat(bearer).isEqualTo("secretAC"); @@ -106,7 +105,7 @@ class DefaultHgEnvironmentBuilderTest { @Test void shouldSetTransactionId() throws IOException { TransactionId.set("ti42"); - Repository heartOfGold = prepareForWrite("/opt/python", "21"); + Repository heartOfGold = prepareForWrite("21"); Map env = builder.write(heartOfGold); assertThat(env).containsEntry(ENV_TRANSACTION_ID, "ti42"); } @@ -114,12 +113,12 @@ class DefaultHgEnvironmentBuilderTest { @Test void shouldThrowIllegalStateIfServerCouldNotBeStarted() throws IOException { when(server.start()).thenThrow(new IOException("failed to start")); - Repository repository = prepareForRead("/usr", "42"); + Repository repository = prepareForRead("42"); assertThrows(IllegalStateException.class, () -> builder.write(repository)); } - private Repository prepareForWrite(String pythonPath, String id) throws IOException { - Repository heartOfGold = prepareForRead(pythonPath, id); + private Repository prepareForWrite(String id) throws IOException { + Repository heartOfGold = prepareForRead(id); applyAccessToken("secretAC"); when(server.start()).thenReturn(2042); when(hookEnvironment.getChallenge()).thenReturn("challenge"); @@ -133,21 +132,19 @@ class DefaultHgEnvironmentBuilderTest { } - private void assertReadEnv(Map env, String pythonPath, String repositoryId) { + private void assertReadEnv(Map env, String repositoryId) { assertThat(env) .containsEntry(ENV_REPOSITORY_ID, repositoryId) .containsEntry(ENV_REPOSITORY_NAME, "hitchhiker/HeartOfGold") .containsEntry(ENV_HTTP_POST_ARGS, "false") - .containsEntry(ENV_REPOSITORY_PATH, directory.resolve("repo").toAbsolutePath().toString()) - .containsEntry(ENV_PYTHON_PATH, pythonPath + File.pathSeparator + directory.resolve("home/lib/python")); + .containsEntry(ENV_REPOSITORY_PATH, directory.resolve("repo").toAbsolutePath().toString()); } @Nonnull - private Repository prepareForRead(String pythonPath, String id) { + private Repository prepareForRead(String id) { when(repositoryHandler.getDirectory(id)).thenReturn(directory.resolve("repo").toFile()); HgConfig config = new HgConfig(); - config.setPythonPath(pythonPath); when(repositoryHandler.getConfig()).thenReturn(config); Repository heartOfGold = RepositoryTestData.createHeartOfGold(); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java index 7d0dc6f6db..516584bd33 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java @@ -32,10 +32,12 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import sonia.scm.autoconfig.AutoConfiguratorProvider; import sonia.scm.plugin.PluginLoader; import sonia.scm.repository.spi.HgVersionCommand; import sonia.scm.store.ConfigurationStoreFactory; +import javax.annotation.Nonnull; import java.io.File; import static org.assertj.core.api.Assertions.assertThat; @@ -71,7 +73,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Override protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, RepositoryLocationResolver locationResolver, File directory) { - HgRepositoryHandler handler = new HgRepositoryHandler(factory, locationResolver, null, null); + HgRepositoryHandler handler = createHandler(factory, locationResolver); handler.init(contextProvider); HgTestUtil.checkForSkip(handler); @@ -81,11 +83,10 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Test public void getDirectory() { - HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, locationResolver, null, null); + HgRepositoryHandler repositoryHandler = createHandler(factory, locationResolver); HgConfig hgConfig = new HgConfig(); hgConfig.setHgBinary("hg"); - hgConfig.setPythonBinary("python"); repositoryHandler.setConfig(hgConfig); initRepository(); @@ -93,16 +94,22 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { assertEquals(repoPath.toString() + File.separator + RepositoryDirectoryHandler.REPOSITORIES_NATIVE_DIRECTORY, path.getAbsolutePath()); } + @Nonnull + private HgRepositoryHandler createHandler(ConfigurationStoreFactory factory, RepositoryLocationResolver locationResolver) { + AutoConfiguratorProvider provider = new AutoConfiguratorProvider(new HgVerifier()); + return new HgRepositoryHandler(factory, locationResolver, null, null, provider.get()); + } + @Test public void shouldReturnVersionInformation() { PluginLoader pluginLoader = mock(PluginLoader.class); when(pluginLoader.getUberClassLoader()).thenReturn(HgRepositoryHandler.class.getClassLoader()); HgVersionCommand versionCommand = mock(HgVersionCommand.class); - when(versionCommand.get()).thenReturn(new HgVersion("5.2.0", "3.7.2")); + when(versionCommand.get()).thenReturn("python/3.7.2 mercurial/5.2.0"); HgRepositoryHandler handler = new HgRepositoryHandler( - factory, locationResolver, pluginLoader, null + factory, locationResolver, pluginLoader, null, new AutoConfiguratorProvider(new HgVerifier()).get() ); String versionInformation = handler.getVersionInformation(versionCommand); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java index 48b8e4c506..49a0fc1a36 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java @@ -29,6 +29,7 @@ package sonia.scm.repository; import org.junit.Assume; import sonia.scm.SCMContext; import sonia.scm.TempDirRepositoryLocationResolver; +import sonia.scm.autoconfig.AutoConfiguratorProvider; import sonia.scm.repository.hooks.HookEnvironment; import sonia.scm.store.InMemoryConfigurationStoreFactory; @@ -87,7 +88,8 @@ public final class HgTestUtil new InMemoryConfigurationStoreFactory(), repositoryLocationResolver, null, - null + null, + new AutoConfiguratorProvider(new HgVerifier()).get() ); handler.init(context); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgVerifierTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgVerifierTest.java new file mode 100644 index 0000000000..ff8f095dcd --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgVerifierTest.java @@ -0,0 +1,163 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository; + +import com.google.common.base.Splitter; +import org.assertj.core.util.Strings; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import sonia.scm.util.SystemUtil; + +import javax.annotation.Nonnull; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.assertj.core.api.Assertions.assertThat; + +class HgVerifierTest { + + @Nested + class WithDefaultVersionResolver { + + private final HgVerifier verifier = new HgVerifier(); + + @Test + void shouldReturnFalseIfFileDoesNotExists(@TempDir Path directory) { + Path hg = directory.resolve("hg"); + + boolean isValid = verify(hg); + + assertThat(isValid).isFalse(); + } + + @Test + void shouldReturnFalseIfFileIsADirectory(@TempDir Path directory) throws IOException { + Path hg = directory.resolve("hg"); + Files.createDirectories(hg); + + boolean isValid = verify(hg); + + assertThat(isValid).isFalse(); + } + + @Test + void shouldReturnFalseIfFileIsNotExecutable(@TempDir Path directory) throws IOException { + Path hg = directory.resolve("hg"); + Files.createFile(hg); + + boolean isValid = verify(hg); + + assertThat(isValid).isFalse(); + } + + @Test + void shouldReturnTrueForIfMercurialIsAvailable() { + Path hg = findHg(); + + // skip test if we could not find mercurial + Assumptions.assumeTrue(hg != null, "skip test, because we could not find mercurial on path"); + + boolean isValid = verify(hg); + + assertThat(isValid).isTrue(); + } + + private Path findHg() { + String pathEnv = System.getenv("PATH"); + if (Strings.isNullOrEmpty(pathEnv)) { + return null; + } + + for (String path : Splitter.on(File.pathSeparator).splitToList(pathEnv)) { + Path hg = Paths.get(path, SystemUtil.isWindows() ? "hg.exe" : "hg"); + if (Files.exists(hg)) { + return hg; + } + } + return null; + } + + private boolean verify(Path hg) { + HgConfig config = new HgConfig(); + config.setHgBinary(hg.toString()); + return verifier.isValid(config); + } + + } + + @ParameterizedTest + @ValueSource(strings = { "3-2-1", "x.y.z", "3.2.0" }) + void shouldReturnFalseForInvalidVersions(String version, @TempDir Path directory) throws IOException { + HgVerifier verifier = new HgVerifier(hg -> version); + + Path hg = createHg(directory); + + boolean isValid = verifier.isValid(hg); + + assertThat(isValid).isFalse(); + } + + @Test + void shouldReturnFalseOnIOException(@TempDir Path directory) throws IOException { + HgVerifier verifier = new HgVerifier(hg -> { + throw new IOException("failed"); + }); + + Path hg = createHg(directory); + + boolean isValid = verifier.isValid(hg); + + assertThat(isValid).isFalse(); + } + + @Test + void shouldReturnTrue(@TempDir Path directory) throws IOException { + HgVerifier verifier = new HgVerifier(hg -> "4.2.0"); + + Path hg = createHg(directory); + + boolean isValid = verifier.isValid(hg); + + assertThat(isValid).isTrue(); + } + + @Nonnull + private Path createHg(Path directory) throws IOException { + Path hg = directory.resolve("hg"); + Files.createFile(hg); + + // skip test if we could not set executable flag + Assumptions.assumeTrue(hg.toFile().setExecutable(true)); + return hg; + } + +} diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgWindowsPackageFixTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgWindowsPackageFixTest.java deleted file mode 100644 index 568d2cd60c..0000000000 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgWindowsPackageFixTest.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.repository; - -//~--- non-JDK imports -------------------------------------------------------- - -import com.google.common.io.Resources; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import static org.junit.Assert.*; - -//~--- JDK imports ------------------------------------------------------------ - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; - -import java.net.URL; - -/** - * - * @author Sebastian Sdorra - */ -public class HgWindowsPackageFixTest -{ - - /** - * Method description - * - * - * @throws IOException - */ - @Test - public void testHgBatModify() throws IOException - { - testModify(createHgBat("01")); - - } - - /** - * Method description - * - * - * @throws IOException - */ - @Test - public void testHgBatModifyWithComment() throws IOException - { - testModify(createHgBat("02")); - } - - /** - * Method description - * - * - * @throws IOException - */ - @Test - public void testHgBatWithoutModify() throws IOException - { - long length = testModify(createHgBat("03")).length(); - - assertEquals(0, length); - } - - /** - * Method description - * - * - * @param number - * - * @return - * - * @throws IOException - */ - private File createHgBat(String number) throws IOException - { - URL url = Resources.getResource("sonia/scm/repository/hg.bat.".concat(number)); - File file = tempFolder.newFile(number); - - try (FileOutputStream fos = new FileOutputStream(file)) - { - Resources.copy(url, fos); - } - - return file; - } - - /** - * Method description - * - * - * @param file - * - * @return - */ - private File testModify(File file) - { - HgWindowsPackageFix.fixHgPy(file); - assertTrue(HgWindowsPackageFix.isSetBinaryAvailable(file)); - - File mod = new File(file.getParentFile(), HgWindowsPackageFix.MODIFY_MARK_01); - - assertTrue(mod.exists()); - - return mod; - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - @Rule - public TemporaryFolder tempFolder = new TemporaryFolder(); -} diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgVersionCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgVersionCommandTest.java index 404e004fc2..6ecf631a96 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgVersionCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgVersionCommandTest.java @@ -24,114 +24,41 @@ package sonia.scm.repository.spi; -import com.google.common.base.Joiner; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; import org.mockito.junit.jupiter.MockitoExtension; import sonia.scm.repository.HgConfig; -import sonia.scm.repository.HgVersion; +import sonia.scm.repository.HgRepositoryHandler; +import sonia.scm.repository.HgTestUtil; -import javax.annotation.Nonnull; -import java.io.ByteArrayInputStream; import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.nio.file.Path; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class HgVersionCommandTest { - private static final String PYTHON_OUTPUT = String.join("\n", - "3.9.0 (default, Oct 27 2020, 14:15:17)", - "[Clang 12.0.0 (clang-1200.0.32.21)]" - ); - - private Map outputs; - - @BeforeEach - void setUpOutputs() { - outputs = new HashMap<>(); - } - @Test - void shouldReturnHgVersion() throws InterruptedException { - command("/usr/local/bin/hg", HgVersionCommand.HG_ARGS, "5.5.2", 0); - command("/opt/python/bin/python", HgVersionCommand.PYTHON_ARGS, PYTHON_OUTPUT, 0); + void shouldReturnVersion(@TempDir Path temp) { + HgRepositoryHandler handler = HgTestUtil.createHandler(temp.toFile()); + HgTestUtil.checkForSkip(handler); - HgVersion hgVersion = getVersion("/usr/local/bin/hg", "/opt/python/bin/python"); - assertThat(hgVersion.getMercurial()).isEqualTo("5.5.2"); - assertThat(hgVersion.getPython()).isEqualTo("3.9.0"); - } - - @Test - void shouldReturnUnknownMercurialVersionOnNonZeroExitCode() throws InterruptedException { - command("hg", HgVersionCommand.HG_ARGS, "", 1); - command("python", HgVersionCommand.PYTHON_ARGS, PYTHON_OUTPUT, 0); - - HgVersion hgVersion = getVersion("hg", "python"); - assertThat(hgVersion.getMercurial()).isEqualTo(HgVersion.UNKNOWN); - assertThat(hgVersion.getPython()).isEqualTo("3.9.0"); - } - - @Test - void shouldReturnUnknownPythonVersionOnNonZeroExitCode() throws InterruptedException { - command("hg", HgVersionCommand.HG_ARGS, "4.4.2", 0); - command("python", HgVersionCommand.PYTHON_ARGS, "", 1); - - HgVersion hgVersion = getVersion("hg", "python"); - assertThat(hgVersion.getMercurial()).isEqualTo("4.4.2"); - assertThat(hgVersion.getPython()).isEqualTo(HgVersion.UNKNOWN); - } - - @Test - void shouldReturnUnknownForInvalidPythonOutput() throws InterruptedException { - command("hg", HgVersionCommand.HG_ARGS, "1.0.0", 0); - command("python", HgVersionCommand.PYTHON_ARGS, "abcdef", 0); - - HgVersion hgVersion = getVersion("hg", "python"); - assertThat(hgVersion.getMercurial()).isEqualTo("1.0.0"); - assertThat(hgVersion.getPython()).isEqualTo(HgVersion.UNKNOWN); + HgVersionCommand command = new HgVersionCommand(handler.getConfig()); + assertThat(command.get()) + .contains("python/") + .contains("mercurial/") + .isNotEqualTo(HgVersionCommand.UNKNOWN); } @Test void shouldReturnUnknownForIOException() { - HgVersionCommand command = new HgVersionCommand(new HgConfig(), cmd -> { + HgVersionCommand command = new HgVersionCommand(new HgConfig(), "/i/dont/know", cmd -> { throw new IOException("failed"); }); - HgVersion hgVersion = command.get(); - assertThat(hgVersion.getMercurial()).isEqualTo(HgVersion.UNKNOWN); - assertThat(hgVersion.getPython()).isEqualTo(HgVersion.UNKNOWN); - } - - private Process command(String command, String[] args, String content, int exitValue) throws InterruptedException { - Process process = mock(Process.class); - when(process.getInputStream()).thenReturn(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))); - when(process.waitFor()).thenReturn(exitValue); - - List cmdLine = new ArrayList<>(); - cmdLine.add(command); - cmdLine.addAll(Arrays.asList(args)); - - outputs.put(Joiner.on(' ').join(cmdLine), process); - - return process; - } - - @Nonnull - private HgVersion getVersion(String hg, String python) { - HgConfig config = new HgConfig(); - config.setHgBinary(hg); - config.setPythonBinary(python); - return new HgVersionCommand(config, command -> outputs.get(Joiner.on(' ').join(command))).get(); + assertThat(command.get()).isEqualTo(HgVersionCommand.UNKNOWN); } } diff --git a/scm-webapp/src/main/java/sonia/scm/web/cgi/DefaultCGIExecutor.java b/scm-webapp/src/main/java/sonia/scm/web/cgi/DefaultCGIExecutor.java index dcf40f122d..2d8a374e5e 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/cgi/DefaultCGIExecutor.java +++ b/scm-webapp/src/main/java/sonia/scm/web/cgi/DefaultCGIExecutor.java @@ -21,12 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.web.cgi; //~--- non-JDK imports -------------------------------------------------------- import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; import com.google.common.base.Strings; import com.google.common.io.ByteStreams; @@ -48,7 +49,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; import java.util.Enumeration; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; @@ -110,18 +113,8 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor //~--- methods -------------------------------------------------------------- - /** - * Method description - * - * - * - * - * @param cmd - * - * @throws IOException - */ @Override - public void execute(String cmd) throws IOException + public void execute(String cmd) { File command = new File(cmd); EnvList env = new EnvList(environment); @@ -150,21 +143,16 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor env.set(ENV_PATH_TRANSLATED, pathTranslated); - String execCmd = path; - - if ((execCmd.charAt(0) != '"') && (execCmd.indexOf(' ') >= 0)) - { - execCmd = "\"".concat(execCmd).concat("\""); - } - - if (interpreter != null) - { - execCmd = interpreter.concat(" ").concat(execCmd); + List execCmd = new ArrayList<>(); + if (interpreter != null) { + execCmd.add(interpreter); } + execCmd.add(command.getAbsolutePath()); + execCmd.addAll(getArgs()); if (logger.isDebugEnabled()) { - logger.debug("execute cgi: {}", execCmd); + logger.debug("execute cgi: {}", Joiner.on(' ').join(execCmd)); if (logger.isTraceEnabled()) { @@ -173,10 +161,11 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor } Process p = null; - - try - { - p = Runtime.getRuntime().exec(execCmd, env.getEnvArray(), workDirectory); + try { + ProcessBuilder builder = new ProcessBuilder(execCmd); + builder.directory(workDirectory); + builder.environment().putAll(env.asMap()); + p = builder.start(); execute(p); } catch (IOException ex) @@ -329,15 +318,14 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor env.set(ENV_SERVER_SOFTWARE, SERVER_SOFTWARE_PREFIX.concat(SCMContext.getContext().getVersion())); - Enumeration enm = request.getHeaderNames(); + Enumeration enm = request.getHeaderNames(); while (enm.hasMoreElements()) { - String name = (String) enm.nextElement(); + String name = enm.nextElement(); String value = request.getHeader(name); - env.set(ENV_HTTP_HEADER_PREFIX + name.toUpperCase().replace('-', '_'), - value); + env.set(ENV_HTTP_HEADER_PREFIX + name.toUpperCase().replace('-', '_'), value); } // these extra ones were from printenv on www.dev.nomura.co.uk @@ -394,6 +382,7 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor * * @throws IOException */ + @SuppressWarnings("UnstableApiUsage") private void execute(Process process) throws IOException { InputStream processIS = null; @@ -418,61 +407,26 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor } } - /** - * Method description - * - * - * @param is - * - * - * @throws IOException - */ - private void parseHeaders(InputStream is) throws IOException - { + private void parseHeaders(InputStream is) throws IOException { String line = null; - while ((line = getTextLineFromStream(is)).length() > 0) - { - if (logger.isTraceEnabled()) - { + while ((line = getTextLineFromStream(is)).length() > 0) { + if (logger.isTraceEnabled()) { logger.trace(" ".concat(line)); } - if (!line.startsWith(RESPONSE_HEADER_HTTP_PREFIX)) - { + if (!line.startsWith(RESPONSE_HEADER_HTTP_PREFIX)) { int k = line.indexOf(':'); - if (k > 0) - { + if (k > 0) { String key = line.substring(0, k).trim(); String value = line.substring(k + 1).trim(); - if (RESPONSE_HEADER_LOCATION.equalsIgnoreCase(key)) - { + if (RESPONSE_HEADER_LOCATION.equalsIgnoreCase(key)) { response.sendRedirect(response.encodeRedirectURL(value)); - } - else if (RESPONSE_HEADER_STATUS.equalsIgnoreCase(key)) - { - String[] token = value.split(" "); - int status = Integer.parseInt(token[0]); - - if (logger.isDebugEnabled()) - { - logger.debug("CGI returned with status {}", status); - } - - if (status < 304) - { - response.setStatus(status); - } - else - { - response.sendError(status); - } - } - else - { - + } else if (RESPONSE_HEADER_STATUS.equalsIgnoreCase(key)) { + handleStatus(value); + } else { // add remaining header items to our response header response.addHeader(key, value); } @@ -481,6 +435,19 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor } } + private void handleStatus(String value) throws IOException { + String[] token = value.split(" "); + int status = Integer.parseInt(token[0]); + + logger.debug("CGI returned with status {}", status); + + if (status < 304) { + response.setStatus(status); + } else { + response.sendError(status); + } + } + /** * Method description * @@ -513,26 +480,21 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor */ private void processErrorStreamAsync(final Process process) { - executor.execute(new Runnable() - { - @Override - public void run() - { - InputStream errorStream = null; + executor.execute(() -> { + InputStream errorStream = null; - try - { - errorStream = process.getErrorStream(); - processErrorStream(errorStream); - } - catch (IOException ex) - { - logger.error("could not read errorstream", ex); - } - finally - { - IOUtil.close(errorStream); - } + try + { + errorStream = process.getErrorStream(); + processErrorStream(errorStream); + } + catch (IOException ex) + { + logger.error("could not read errorstream", ex); + } + finally + { + IOUtil.close(errorStream); } }); } @@ -615,6 +577,7 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor catch (InterruptedException ex) { getExceptionHandler().handleException(request, response, ex); + Thread.currentThread().interrupt(); } }