getCustom(Xsrf.TOKEN_KEY).orElse("-"));
- }
-}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironmentBuilder.java
similarity index 59%
rename from scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java
rename to scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironmentBuilder.java
index 3fa04bd1e6..3738d92d88 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironmentBuilder.java
@@ -21,28 +21,15 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository;
-import com.google.inject.servlet.RequestScoped;
+import com.google.inject.ImplementedBy;
-/**
- * Holds an instance of {@link HgContext} in the request scope.
- *
- * The problem seems to be that guice had multiple options for injecting HgContext. {@link HgContextProvider}
- * bound via Module and {@link HgContext} bound void {@link RequestScoped} annotation. It looks like that Guice 4
- * injects randomly the one or the other, in SCMv1 (Guice 3) everything works as expected.
- *
- * To fix the problem we have created this class annotated with {@link RequestScoped}, which holds an instance
- * of {@link HgContext}. This way only the {@link HgContextProvider} is used for injection.
- */
-@RequestScoped
-public class HgContextRequestStore {
-
- private final HgContext context = new HgContext();
-
- public HgContext get() {
- return context;
- }
+import java.util.Map;
+@ImplementedBy(DefaultHgEnvironmentBuilder.class)
+public interface HgEnvironmentBuilder {
+ Map read(Repository repository);
+ Map write(Repository repository);
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgHookManager.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgHookManager.java
deleted file mode 100644
index 3d333015ee..0000000000
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgHookManager.java
+++ /dev/null
@@ -1,354 +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.github.legman.Subscribe;
-import com.google.common.base.MoreObjects;
-import com.google.inject.Inject;
-import com.google.inject.OutOfScopeException;
-import com.google.inject.Provider;
-import com.google.inject.ProvisionException;
-import com.google.inject.Singleton;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import sonia.scm.config.ScmConfiguration;
-import sonia.scm.config.ScmConfigurationChangedEvent;
-import sonia.scm.net.ahc.AdvancedHttpClient;
-import sonia.scm.security.AccessToken;
-import sonia.scm.security.AccessTokenBuilderFactory;
-import sonia.scm.util.HttpUtil;
-import sonia.scm.util.Util;
-
-import javax.servlet.http.HttpServletRequest;
-import java.io.IOException;
-import java.util.UUID;
-
-//~--- JDK imports ------------------------------------------------------------
-
-/**
- *
- * @author Sebastian Sdorra
- */
-@Singleton
-public class HgHookManager {
-
- @SuppressWarnings("java:S1075") // this url is fixed
- private static final String URL_HOOKPATH = "/hook/hg/";
-
- /**
- * the logger for HgHookManager
- */
- private static final Logger logger =
- LoggerFactory.getLogger(HgHookManager.class);
-
- //~--- constructors ---------------------------------------------------------
-
- /**
- * Constructs ...
- *
- * @param configuration
- * @param httpServletRequestProvider
- * @param httpClient
- * @param accessTokenBuilderFactory
- */
- @Inject
- public HgHookManager(ScmConfiguration configuration,
- Provider httpServletRequestProvider,
- AdvancedHttpClient httpClient, AccessTokenBuilderFactory accessTokenBuilderFactory)
- {
- this.configuration = configuration;
- this.httpServletRequestProvider = httpServletRequestProvider;
- this.httpClient = httpClient;
- this.accessTokenBuilderFactory = accessTokenBuilderFactory;
- }
-
- //~--- methods --------------------------------------------------------------
-
- /**
- * Method description
- *
- *
- * @param config
- */
- @Subscribe(async = false)
- public void configChanged(ScmConfigurationChangedEvent config)
- {
- hookUrl = null;
- }
-
- /**
- * Method description
- *
- *
- * @param request
- *
- * @return
- */
- public String createUrl(HttpServletRequest request)
- {
- if (hookUrl == null)
- {
- synchronized (this)
- {
- if (hookUrl == null)
- {
- buildHookUrl(request);
-
- if (logger.isInfoEnabled() && Util.isNotEmpty(hookUrl))
- {
- logger.info("use {} for mercurial hooks", hookUrl);
- }
- }
- }
- }
-
- return hookUrl;
- }
-
- /**
- * Method description
- *
- *
- * @return
- */
- public String createUrl()
- {
- String url = hookUrl;
-
- if (url == null)
- {
- HttpServletRequest request = getHttpServletRequest();
-
- if (request != null)
- {
- url = createUrl(request);
- }
- else
- {
- url = createConfiguredUrl();
- logger.warn(
- "created url {} without request, in some cases this could cause problems",
- url);
- }
- }
-
- return url;
- }
-
- //~--- get methods ----------------------------------------------------------
-
- /**
- * Method description
- *
- *
- * @return
- */
- public String getChallenge()
- {
- return challenge;
- }
-
- /**
- * Method description
- *
- *
- * @param challenge
- *
- * @return
- */
- public boolean isAcceptAble(String challenge)
- {
- return this.challenge.equals(challenge);
- }
-
- public AccessToken getAccessToken()
- {
- return accessTokenBuilderFactory.create().build();
- }
-
- private void buildHookUrl(HttpServletRequest request) {
- if (configuration.isForceBaseUrl()) {
- logger.debug("create hook url from configured base url because force base url is enabled");
-
- hookUrl = createConfiguredUrl();
- if (!isUrlWorking(hookUrl)) {
- disableHooks();
- }
- } else {
- logger.debug("create hook url from request");
-
- hookUrl = HttpUtil.getCompleteUrl(request, URL_HOOKPATH);
- if (!isUrlWorking(hookUrl)) {
- logger.warn("hook url {} from request does not work, try now localhost", hookUrl);
-
- hookUrl = createLocalUrl(request);
- if (!isUrlWorking(hookUrl)) {
- logger.warn("localhost hook url {} does not work, try now from configured base url", hookUrl);
-
- hookUrl = createConfiguredUrl();
- if (!isUrlWorking(hookUrl)) {
- disableHooks();
- }
- }
- }
- }
- }
-
- /**
- * Method description
- *
- *
- * @return
- */
- private String createConfiguredUrl()
- {
- //J-
- return HttpUtil.getUriWithoutEndSeperator(
- MoreObjects.firstNonNull(
- configuration.getBaseUrl(),
- "http://localhost:8080/scm"
- )
- ).concat(URL_HOOKPATH);
- //J+
- }
-
- /**
- * Method description
- *
- *
- * @param request
- *
- * @return
- */
- private String createLocalUrl(HttpServletRequest request)
- {
- StringBuilder sb = new StringBuilder(request.getScheme());
-
- sb.append("://localhost:").append(request.getLocalPort());
- sb.append(request.getContextPath()).append(URL_HOOKPATH);
-
- return sb.toString();
- }
-
- /**
- * Method description
- *
- */
- private void disableHooks()
- {
- if (logger.isErrorEnabled())
- {
- logger.error(
- "disabling mercurial hooks, because hook url {} seems not to work",
- hookUrl);
- }
-
- hookUrl = Util.EMPTY_STRING;
- }
-
- //~--- get methods ----------------------------------------------------------
-
- /**
- * Method description
- *
- *
- * @return
- */
- private HttpServletRequest getHttpServletRequest()
- {
- HttpServletRequest request = null;
-
- try
- {
- request = httpServletRequestProvider.get();
- }
- catch (ProvisionException | OutOfScopeException ex)
- {
- logger.debug("http servlet request is not available");
- }
-
- return request;
- }
-
- /**
- * Method description
- *
- *
- * @param url
- *
- * @return
- */
- private boolean isUrlWorking(String url)
- {
- boolean result = false;
-
- try
- {
- url = url.concat("?ping=true");
-
- logger.trace("check hook url {}", url);
- //J-
- int sc = httpClient.get(url)
- .disableHostnameValidation(true)
- .disableCertificateValidation(true)
- .ignoreProxySettings(true)
- .disableTracing()
- .request()
- .getStatus();
- //J+
- result = sc == 204;
- }
- catch (IOException ex)
- {
- if (logger.isTraceEnabled())
- {
- logger.trace("url test failed for url ".concat(url), ex);
- }
- }
-
- return result;
- }
-
- //~--- fields ---------------------------------------------------------------
-
- /** Field description */
- private String challenge = UUID.randomUUID().toString();
-
- /** Field description */
- private ScmConfiguration configuration;
-
- /** Field description */
- private volatile String hookUrl;
-
- /** Field description */
- private AdvancedHttpClient httpClient;
-
- /** Field description */
- private Provider httpServletRequestProvider;
-
- private final AccessTokenBuilderFactory accessTokenBuilderFactory;
-}
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/HgPythonScript.java
index a7c5ef6069..0e4b05b101 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/HgPythonScript.java
@@ -38,80 +38,30 @@ import java.io.File;
*/
public enum HgPythonScript {
- HOOK("scmhooks.py"), HGWEB("hgweb.py"), VERSION("version.py");
+ HOOK("scmhooks.py"), HGWEB("hgweb.py");
- /** Field description */
- private static final String BASE_DIRECTORY =
- "lib".concat(File.separator).concat("python");
-
- /** Field description */
+ private static final String BASE_DIRECTORY = "lib".concat(File.separator).concat("python");
private static final String BASE_RESOURCE = "/sonia/scm/python/";
- //~--- constructors ---------------------------------------------------------
+ private final String name;
- /**
- * Constructs ...
- *
- *
- * @param name
- */
- private HgPythonScript(String name)
- {
+ HgPythonScript(String name) {
this.name = name;
}
- //~--- get methods ----------------------------------------------------------
-
- /**
- * Method description
- *
- *
- * @param context
- *
- * @return
- */
- public static File getScriptDirectory(SCMContextProvider context)
- {
+ public static File getScriptDirectory(SCMContextProvider context) {
return new File(context.getBaseDirectory(), BASE_DIRECTORY);
}
- /**
- * Method description
- *
- *
- * @param context
- *
- * @return
- */
- public File getFile(SCMContextProvider context)
- {
+ public File getFile(SCMContextProvider context) {
return new File(getScriptDirectory(context), name);
}
- /**
- * Method description
- *
- *
- * @return
- */
- public String getName()
- {
+ public String getName() {
return name;
}
- /**
- * Method description
- *
- *
- * @return
- */
- public String getResourcePath()
- {
+ public String getResourcePath() {
return BASE_RESOURCE.concat(name);
}
-
- //~--- fields ---------------------------------------------------------------
-
- /** Field description */
- private String name;
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryFactory.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryFactory.java
new file mode 100644
index 0000000000..5b17ad8bd4
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryFactory.java
@@ -0,0 +1,106 @@
+/*
+ * 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.aragost.javahg.RepositoryConfiguration;
+import com.google.common.annotations.VisibleForTesting;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sonia.scm.repository.hooks.HookEnvironment;
+import sonia.scm.repository.spi.javahg.HgFileviewExtension;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.io.File;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
+import java.util.Map;
+import java.util.function.Function;
+
+@Singleton
+public class HgRepositoryFactory {
+
+ private static final Logger LOG = LoggerFactory.getLogger(HgRepositoryFactory.class);
+
+ private final HgRepositoryHandler handler;
+ private final HookEnvironment hookEnvironment;
+ private final HgEnvironmentBuilder environmentBuilder;
+ private final Function directoryResolver;
+
+ @Inject
+ public HgRepositoryFactory(HgRepositoryHandler handler, HookEnvironment hookEnvironment, HgEnvironmentBuilder environmentBuilder) {
+ this(
+ handler, hookEnvironment, environmentBuilder,
+ repository -> handler.getDirectory(repository.getId())
+ );
+ }
+
+ @VisibleForTesting
+ public HgRepositoryFactory(HgRepositoryHandler handler, HookEnvironment hookEnvironment, HgEnvironmentBuilder environmentBuilder, Function directoryResolver) {
+ this.handler = handler;
+ this.hookEnvironment = hookEnvironment;
+ this.environmentBuilder = environmentBuilder;
+ this.directoryResolver = directoryResolver;
+ }
+
+ public com.aragost.javahg.Repository openForRead(Repository repository) {
+ return open(repository, environmentBuilder.read(repository));
+ }
+
+ public com.aragost.javahg.Repository openForWrite(Repository repository) {
+ return open(repository, environmentBuilder.write(repository));
+ }
+
+ private com.aragost.javahg.Repository open(Repository repository, Map environment) {
+ File directory = directoryResolver.apply(repository);
+
+ RepositoryConfiguration repoConfiguration = RepositoryConfiguration.DEFAULT;
+ repoConfiguration.getEnvironment().putAll(environment);
+ repoConfiguration.addExtension(HgFileviewExtension.class);
+
+ boolean pending = hookEnvironment.isPending();
+ repoConfiguration.setEnablePendingChangesets(pending);
+
+ Charset encoding = encoding();
+ repoConfiguration.setEncoding(encoding);
+
+ repoConfiguration.setHgBin(handler.getConfig().getHgBinary());
+
+ LOG.trace("open hg repository {}: encoding: {}, pending: {}", directory, encoding, pending);
+
+ return com.aragost.javahg.Repository.open(repoConfiguration, directory);
+ }
+
+ private Charset encoding() {
+ String charset = handler.getConfig().getEncoding();
+ try {
+ return Charset.forName(charset);
+ } catch (UnsupportedCharsetException ex) {
+ LOG.warn("unknown charset {} in hg config, fallback to utf-8", charset);
+ return StandardCharsets.UTF_8;
+ }
+ }
+}
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 bdc36eb54b..14cd54866d 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
@@ -27,11 +27,9 @@ package sonia.scm.repository;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import sonia.scm.ConfigurationException;
import sonia.scm.SCMContextProvider;
import sonia.scm.autoconfig.AutoConfigurator;
import sonia.scm.installer.HgInstaller;
@@ -43,14 +41,14 @@ import sonia.scm.io.INISection;
import sonia.scm.plugin.Extension;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.repository.spi.HgRepositoryServiceProvider;
+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 javax.xml.bind.JAXBContext;
-import javax.xml.bind.JAXBException;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -63,14 +61,15 @@ import java.util.Optional;
public class HgRepositoryHandler
extends AbstractSimpleRepositoryHandler {
- public static final String PATH_HOOK = ".hook-1.8";
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(TYPE_NAME,
+ public static final RepositoryType TYPE = new RepositoryType(
+ TYPE_NAME,
TYPE_DISPLAYNAME,
HgRepositoryServiceProvider.COMMANDS,
- HgRepositoryServiceProvider.FEATURES);
+ HgRepositoryServiceProvider.FEATURES
+ );
private static final Logger logger = LoggerFactory.getLogger(HgRepositoryHandler.class);
@@ -78,28 +77,14 @@ public class HgRepositoryHandler
private static final String CONFIG_SECTION_SCMM = "scmm";
private static final String CONFIG_KEY_REPOSITORY_ID = "repositoryid";
- private final Provider hgContextProvider;
-
private final HgWorkingCopyFactory workingCopyFactory;
- private final JAXBContext jaxbContext;
-
@Inject
public HgRepositoryHandler(ConfigurationStoreFactory storeFactory,
- Provider hgContextProvider,
RepositoryLocationResolver repositoryLocationResolver,
PluginLoader pluginLoader, HgWorkingCopyFactory workingCopyFactory) {
super(storeFactory, repositoryLocationResolver, pluginLoader);
- this.hgContextProvider = hgContextProvider;
this.workingCopyFactory = workingCopyFactory;
-
- try {
- this.jaxbContext = JAXBContext.newInstance(BrowserResult.class,
- BlameResult.class, Changeset.class, ChangesetPagingResult.class,
- HgVersion.class);
- } catch (JAXBException ex) {
- throw new ConfigurationException("could not create jaxbcontext", ex);
- }
}
public void doAutoConfiguration(HgConfig autoConfig) {
@@ -107,8 +92,7 @@ public class HgRepositoryHandler
try {
if (logger.isDebugEnabled()) {
- logger.debug("installing mercurial with {}",
- installer.getClass().getName());
+ logger.debug("installing mercurial with {}", installer.getClass().getName());
}
installer.install(baseDirectory, autoConfig);
@@ -154,16 +138,6 @@ public class HgRepositoryHandler
}
}
- public HgContext getHgContext() {
- HgContext context = hgContextProvider.get();
-
- if (context == null) {
- context = new HgContext();
- }
-
- return context;
- }
-
@Override
public ImportHandler getImportHandler() {
return new HgImportHandler(this);
@@ -176,28 +150,14 @@ public class HgRepositoryHandler
@Override
public String getVersionInformation() {
- String version = getStringFromResource(RESOURCE_VERSION,
- DEFAULT_VERSION_INFORMATION);
+ return getVersionInformation(new HgVersionCommand(getConfig()));
+ }
- try {
- HgVersion hgVersion = new HgVersionHandler(this, hgContextProvider.get(),
- baseDirectory).getVersion();
-
- if (hgVersion != null) {
- if (logger.isDebugEnabled()) {
- logger.debug("mercurial/python informations: {}", hgVersion);
- }
-
- version = MessageFormat.format(version, hgVersion.getPython(),
- hgVersion.getMercurial());
- } else if (logger.isWarnEnabled()) {
- logger.warn("could not retrieve version informations");
- }
- } catch (Exception ex) {
- logger.error("could not read version informations", ex);
- }
-
- return version;
+ 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());
}
@Override
@@ -253,28 +213,24 @@ public class HgRepositoryHandler
logger.debug("write python script {}", script.getName());
}
- InputStream content = null;
- OutputStream output = null;
-
- try {
- content = HgRepositoryHandler.class.getResourceAsStream(
- script.getResourcePath());
- output = new FileOutputStream(script.getFile(context));
+ try (InputStream content = input(script); OutputStream output = output(context, script)) {
IOUtil.copy(content, output);
} catch (IOException ex) {
logger.error("could not write script", ex);
- } finally {
- IOUtil.close(content);
- IOUtil.close(output);
}
}
}
+ private InputStream input(HgPythonScript script) {
+ return HgRepositoryHandler.class.getResourceAsStream(script.getResourcePath());
+ }
+
+ private OutputStream output(SCMContextProvider context, HgPythonScript script) throws FileNotFoundException {
+ return new FileOutputStream(script.getFile(context));
+ }
+
public HgWorkingCopyFactory getWorkingCopyFactory() {
return workingCopyFactory;
}
- public JAXBContext getJaxbContext() {
- return jaxbContext;
- }
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVersion.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVersion.java
index 36bd6abfa3..a517c2fe08 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVersion.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVersion.java
@@ -24,10 +24,8 @@
package sonia.scm.repository;
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.Setter;
-import lombok.ToString;
+import lombok.AllArgsConstructor;
+import lombok.Data;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@@ -37,13 +35,14 @@ import javax.xml.bind.annotation.XmlRootElement;
*
* @author Sebastian Sdorra
*/
+@Data
+@AllArgsConstructor
@XmlRootElement(name = "version")
@XmlAccessorType(XmlAccessType.FIELD)
-@EqualsAndHashCode
-@Getter
-@Setter
-@ToString
public class HgVersion {
+
+ public static final String UNKNOWN = "x.y.z (unknown)";
+
private String mercurial;
private String python;
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookMessage.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookMessage.java
index ea1671d7ab..0cd78ac932 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookMessage.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookMessage.java
@@ -21,75 +21,31 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository.api;
//~--- JDK imports ------------------------------------------------------------
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
import java.io.Serializable;
/**
*
* @author Sebastian Sdorra
*/
-public final class HgHookMessage implements Serializable
-{
+@Getter
+@AllArgsConstructor
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public final class HgHookMessage implements Serializable {
- /** Field description */
private static final long serialVersionUID = 1804492842452344326L;
- //~--- constant enums -------------------------------------------------------
-
- /**
- * Enum description
- *
- */
- public static enum Severity { NOTE, ERROR; }
-
- //~--- constructors ---------------------------------------------------------
-
- /**
- * Constructs ...
- *
- *
- * @param severity
- * @param message
- */
- public HgHookMessage(Severity severity, String message)
- {
- this.severity = severity;
- this.message = message;
- }
-
- //~--- get methods ----------------------------------------------------------
-
- /**
- * Method description
- *
- *
- * @return
- */
- public String getMessage()
- {
- return message;
- }
-
- /**
- * Method description
- *
- *
- * @return
- */
- public Severity getSeverity()
- {
- return severity;
- }
-
- //~--- fields ---------------------------------------------------------------
-
- /** Field description */
+ private Severity severity;
private String message;
- /** Field description */
- private Severity severity;
+ public enum Severity { NOTE, ERROR }
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/DefaultHookHandler.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/DefaultHookHandler.java
new file mode 100644
index 0000000000..c189299d72
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/DefaultHookHandler.java
@@ -0,0 +1,188 @@
+/*
+ * 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.hooks;
+
+import com.google.inject.assistedinject.Assisted;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.subject.Subject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sonia.scm.ExceptionWithContext;
+import sonia.scm.NotFoundException;
+import sonia.scm.TransactionId;
+import sonia.scm.repository.RepositoryHookType;
+import sonia.scm.repository.api.HgHookMessage;
+import sonia.scm.repository.spi.HgHookContextProvider;
+import sonia.scm.repository.spi.HookEventFacade;
+import sonia.scm.security.BearerToken;
+import sonia.scm.security.CipherUtil;
+
+import javax.annotation.Nonnull;
+import javax.inject.Inject;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.List;
+
+import static java.util.Collections.singletonList;
+
+class DefaultHookHandler implements HookHandler {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultHookHandler.class);
+
+ private final HookEventFacade hookEventFacade;
+ private final HookEnvironment environment;
+ private final HookContextProviderFactory hookContextProviderFactory;
+ private final Socket socket;
+
+ @Inject
+ public DefaultHookHandler(HookContextProviderFactory hookContextProviderFactory, HookEventFacade hookEventFacade, HookEnvironment environment, @Assisted Socket socket) {
+ this.hookContextProviderFactory = hookContextProviderFactory;
+ this.hookEventFacade = hookEventFacade;
+ this.environment = environment;
+ this.socket = socket;
+ }
+
+ @Override
+ public void run() {
+ LOG.trace("start handling hook protocol");
+ try (InputStream input = socket.getInputStream(); OutputStream output = socket.getOutputStream()) {
+ handleHookRequest(input, output);
+ } catch (IOException e) {
+ LOG.warn("failed to read hook request", e);
+ } finally {
+ LOG.trace("close client socket");
+ TransactionId.clear();
+ close();
+ }
+ }
+
+ private void handleHookRequest(InputStream input, OutputStream output) throws IOException {
+ Request request = Sockets.receive(input, Request.class);
+ TransactionId.set(request.getTransactionId());
+ Response response = handleHookRequest(request);
+ Sockets.send(output, response);
+ }
+
+ private Response handleHookRequest(Request request) {
+ LOG.trace("process {} hook for node {}", request.getType(), request.getNode());
+
+ if (!environment.isAcceptAble(request.getChallenge())) {
+ LOG.warn("received hook with invalid challenge: {}", request.getChallenge());
+ return error("invalid hook challenge");
+ }
+
+ try {
+ authenticate(request);
+
+ return fireHook(request);
+ } catch (AuthenticationException ex) {
+ LOG.warn("hook authentication failed", ex);
+ return error("hook authentication failed");
+ }
+ }
+
+ @Nonnull
+ private Response fireHook(Request request) {
+ HgHookContextProvider context = hookContextProviderFactory.create(request.getRepositoryId(), request.getNode());
+
+ try {
+ environment.setPending(request.getType() == RepositoryHookType.PRE_RECEIVE);
+
+ hookEventFacade.handle(request.getRepositoryId()).fireHookEvent(request.getType(), context);
+
+ return new Response(context.getHgMessageProvider().getMessages(), false);
+
+ } catch (NotFoundException ex) {
+ LOG.warn("could not find repository with id {}", request.getRepositoryId(), ex);
+ return error("repository not found");
+ } catch (ExceptionWithContext ex) {
+ LOG.debug("scm exception on hook occurred", ex);
+ return error(context, ex.getMessage());
+ } catch (Exception ex) {
+ LOG.warn("unknown error on hook occurred", ex);
+ return error(context, "unknown error");
+ } finally {
+ environment.clearPendingState();
+ }
+ }
+
+ private void authenticate(Request request) {
+ LOG.trace("authenticate hook request");
+ String token = CipherUtil.getInstance().decode(request.getToken());
+ BearerToken bearer = BearerToken.valueOf(token);
+ Subject subject = SecurityUtils.getSubject();
+ subject.login(bearer);
+ }
+
+ private Response error(HgHookContextProvider context, String message) {
+ List messages = new ArrayList<>(context.getHgMessageProvider().getMessages());
+ messages.add(createErrorMessage(message));
+ return new Response(messages, true);
+ }
+
+ private Response error(String message) {
+ return new Response(
+ singletonList(createErrorMessage(message)),
+ true
+ );
+ }
+
+ @Nonnull
+ private HgHookMessage createErrorMessage(String message) {
+ return new HgHookMessage(HgHookMessage.Severity.ERROR, message);
+ }
+
+ private void close() {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ LOG.debug("failed to close hook socket", e);
+ }
+ }
+
+ @Data
+ @AllArgsConstructor
+ public static class Request {
+ private String token;
+ private RepositoryHookType type;
+ private String transactionId;
+ private String repositoryId;
+ private String challenge;
+ private String node;
+ }
+
+ @Data
+ @AllArgsConstructor
+ public static class Response {
+ private List messages;
+ private boolean abort;
+ }
+}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/AbstractHgCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookContextProviderFactory.java
similarity index 52%
rename from scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/AbstractHgCommand.java
rename to scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookContextProviderFactory.java
index 259996e2ed..04f88189ce 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/AbstractHgCommand.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookContextProviderFactory.java
@@ -21,60 +21,37 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
-package sonia.scm.repository.spi;
-//~--- non-JDK imports --------------------------------------------------------
+package sonia.scm.repository.hooks;
-import sonia.scm.repository.AbstractHgHandler;
-import sonia.scm.repository.HgContext;
+import sonia.scm.NotFoundException;
+import sonia.scm.repository.HgRepositoryFactory;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.Repository;
+import sonia.scm.repository.RepositoryManager;
+import sonia.scm.repository.spi.HgHookContextProvider;
-//~--- JDK imports ------------------------------------------------------------
+import javax.inject.Inject;
-import java.io.File;
+public class HookContextProviderFactory {
-import java.util.Map;
+ private final RepositoryManager repositoryManager;
+ private final HgRepositoryHandler repositoryHandler;
+ private final HgRepositoryFactory repositoryFactory;
-/**
- *
- * @author Sebastian Sdorra
- */
-public class AbstractHgCommand extends AbstractHgHandler
-{
-
- /**
- * Constructs ...
- *
- *
- * @param handler
- * @param context
- * @param repository
- * @param repositoryDirectory
- */
- protected AbstractHgCommand(HgRepositoryHandler handler, HgContext context,
- Repository repository, File repositoryDirectory)
- {
- super(handler, context, repository, repositoryDirectory);
+ @Inject
+ public HookContextProviderFactory(RepositoryManager repositoryManager, HgRepositoryHandler repositoryHandler, HgRepositoryFactory repositoryFactory) {
+ this.repositoryManager = repositoryManager;
+ this.repositoryHandler = repositoryHandler;
+ this.repositoryFactory = repositoryFactory;
}
- //~--- methods --------------------------------------------------------------
-
- /**
- * Method description
- *
- *
- * @param revision
- * @param path
- *
- * @param request
- *
- * @return
- */
- protected Map createEnvironment(FileBaseCommandRequest request)
- {
- return createEnvironment(request.getRevision(), request.getPath());
+ HgHookContextProvider create(String repositoryId, String node) {
+ Repository repository = repositoryManager.get(repositoryId);
+ if (repository == null) {
+ throw new NotFoundException(Repository.class, repositoryId);
+ }
+ return new HgHookContextProvider(repositoryHandler, repositoryFactory, repository, node);
}
+
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookEnvironment.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookEnvironment.java
new file mode 100644
index 0000000000..a1d205a9d5
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookEnvironment.java
@@ -0,0 +1,60 @@
+/*
+ * 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.hooks;
+
+import javax.inject.Singleton;
+import java.util.UUID;
+
+@Singleton
+public class HookEnvironment {
+
+ private final ThreadLocal threadEnvironment = new ThreadLocal<>();
+ private final String challenge = UUID.randomUUID().toString();
+
+ public String getChallenge() {
+ return challenge;
+ }
+
+ public boolean isAcceptAble(String challenge) {
+ return this.challenge.equals(challenge);
+ }
+
+ void setPending(boolean pending) {
+ threadEnvironment.set(pending);
+ }
+
+ void clearPendingState() {
+ threadEnvironment.remove();
+ }
+
+ public boolean isPending() {
+ Boolean threadState = threadEnvironment.get();
+ if (threadState != null) {
+ return threadState;
+ }
+ return false;
+ }
+
+}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookHandler.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookHandler.java
new file mode 100644
index 0000000000..f2f7e7ccdd
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookHandler.java
@@ -0,0 +1,28 @@
+/*
+ * 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.hooks;
+
+public interface HookHandler extends Runnable {
+}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookHandlerFactory.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookHandlerFactory.java
new file mode 100644
index 0000000000..4eaf205bdd
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookHandlerFactory.java
@@ -0,0 +1,34 @@
+/*
+ * 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.hooks;
+
+import java.net.Socket;
+
+@FunctionalInterface
+interface HookHandlerFactory {
+
+ HookHandler create(Socket socket);
+
+}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookModule.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookModule.java
new file mode 100644
index 0000000000..2eed226a09
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookModule.java
@@ -0,0 +1,40 @@
+/*
+ * 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.hooks;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
+import sonia.scm.plugin.Extension;
+
+@Extension
+public class HookModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ install(new FactoryModuleBuilder()
+ .implement(HookHandler.class, DefaultHookHandler.class)
+ .build(HookHandlerFactory.class)
+ );
+ }
+}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookServer.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookServer.java
new file mode 100644
index 0000000000..90a15c6fb4
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/HookServer.java
@@ -0,0 +1,153 @@
+/*
+ * 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.hooks;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.util.ThreadContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nonnull;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+@Singleton
+public class HookServer implements AutoCloseable {
+
+ private static final Logger LOG = LoggerFactory.getLogger(HookServer.class);
+
+ private final HookHandlerFactory handlerFactory;
+
+ private ExecutorService acceptor;
+ private ExecutorService workerPool;
+ private ServerSocket serverSocket;
+ private SecurityManager securityManager;
+
+ @Inject
+ public HookServer(HookHandlerFactory handlerFactory) {
+ this.handlerFactory = handlerFactory;
+ }
+
+ public int start() throws IOException {
+ securityManager = SecurityUtils.getSecurityManager();
+
+ acceptor = createAcceptor();
+ workerPool = createWorkerPool();
+ serverSocket = createServerSocket();
+ // set timeout to 2 min, to avoid blocking clients
+ serverSocket.setSoTimeout(2 * 60 * 1000);
+
+ accept();
+
+ int port = serverSocket.getLocalPort();
+ LOG.info("open hg hook server on port {}", port);
+ return port;
+ }
+
+ private void accept() {
+ acceptor.submit(() -> {
+ while (!serverSocket.isClosed()) {
+ try {
+ LOG.trace("wait for next hook connection");
+ Socket clientSocket = serverSocket.accept();
+ LOG.trace("accept incoming hook client from {}", clientSocket.getInetAddress());
+ HookHandler hookHandler = handlerFactory.create(clientSocket);
+ workerPool.submit(associateSecurityManager(hookHandler));
+ } catch (IOException ex) {
+ LOG.debug("failed to accept socket, possible closed", ex);
+ }
+ }
+ LOG.warn("ServerSocket is closed");
+ });
+ }
+
+ private Runnable associateSecurityManager(HookHandler hookHandler) {
+ return () -> {
+ ThreadContext.bind(securityManager);
+ try {
+ hookHandler.run();
+ } finally {
+ ThreadContext.unbindSubject();
+ ThreadContext.unbindSecurityManager();
+ }
+ };
+ }
+
+ @Nonnull
+ private ServerSocket createServerSocket() throws IOException {
+ return new ServerSocket(0, 0, InetAddress.getLoopbackAddress());
+ }
+
+ private ExecutorService createAcceptor() {
+ return Executors.newSingleThreadExecutor(
+ createThreadFactory("HgHookAcceptor")
+ );
+ }
+
+ private ExecutorService createWorkerPool() {
+ return Executors.newCachedThreadPool(
+ createThreadFactory("HgHookWorker-%d")
+ );
+ }
+
+ @Nonnull
+ private ThreadFactory createThreadFactory(String hgHookAcceptor) {
+ return new ThreadFactoryBuilder()
+ .setNameFormat(hgHookAcceptor)
+ .build();
+ }
+
+ @Override
+ public void close() {
+ closeSocket();
+ shutdown(acceptor);
+ shutdown(workerPool);
+ }
+
+ private void closeSocket() {
+ if (serverSocket != null) {
+ try {
+ serverSocket.close();
+ } catch (IOException ex) {
+ LOG.warn("failed to close server socket", ex);
+ }
+ }
+ }
+
+ private void shutdown(ExecutorService acceptor) {
+ if (acceptor != null) {
+ acceptor.shutdown();
+ }
+ }
+}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/Sockets.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/Sockets.java
new file mode 100644
index 0000000000..4a0389a33c
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/hooks/Sockets.java
@@ -0,0 +1,81 @@
+/*
+ * 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.hooks;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+class Sockets {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Sockets.class);
+
+ private static final int READ_LIMIT = 8192;
+
+ private static final ObjectMapper objectMapper = new ObjectMapper();
+
+ private Sockets() {
+ }
+
+ static void send(OutputStream out, Object object) throws IOException {
+ byte[] bytes = objectMapper.writeValueAsBytes(object);
+ LOG.trace("send message length of {} to socket", bytes.length);
+
+ DataOutputStream dataOutputStream = new DataOutputStream(out);
+ dataOutputStream.writeInt(bytes.length);
+
+ LOG.trace("send message to socket");
+ dataOutputStream.write(bytes);
+
+ LOG.trace("flush socket");
+ out.flush();
+ }
+
+ static T receive(InputStream in, Class type) throws IOException {
+ LOG.trace("read {} from socket", type);
+
+ DataInputStream dataInputStream = new DataInputStream(in);
+
+ int length = dataInputStream.readInt();
+ LOG.trace("read message length of {} from socket", length);
+ if (length > READ_LIMIT) {
+ String message = String.format("received length of %d, which exceeds the limit of %d", length, READ_LIMIT);
+ throw new IOException(message);
+ }
+
+ byte[] data = new byte[length];
+ dataInputStream.readFully(data);
+
+ LOG.trace("convert message to {}", type);
+ return objectMapper.readValue(data, type);
+ }
+
+}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCommandContext.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCommandContext.java
index 39181d3d67..5169d3c389 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCommandContext.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCommandContext.java
@@ -27,18 +27,12 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import com.aragost.javahg.Repository;
-import com.google.common.base.Strings;
import sonia.scm.repository.HgConfig;
-import sonia.scm.repository.HgHookManager;
+import sonia.scm.repository.HgRepositoryFactory;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.RepositoryProvider;
-import sonia.scm.web.HgUtil;
import java.io.Closeable;
-import java.io.File;
-import java.io.IOException;
-import java.util.Map;
-import java.util.function.BiConsumer;
//~--- JDK imports ------------------------------------------------------------
@@ -46,105 +40,32 @@ import java.util.function.BiConsumer;
*
* @author Sebastian Sdorra
*/
-public class HgCommandContext implements Closeable, RepositoryProvider
-{
+public class HgCommandContext implements Closeable, RepositoryProvider {
- /** Field description */
- private static final String PROPERTY_ENCODING = "hg.encoding";
+ private final HgRepositoryHandler handler;
+ private final HgRepositoryFactory factory;
+ private final sonia.scm.repository.Repository scmRepository;
- //~--- constructors ---------------------------------------------------------
+ private Repository repository;
- /**
- * Constructs ...
- *
- *
- * @param hookManager
- * @param handler
- * @param repository
- * @param directory
- */
- public HgCommandContext(HgHookManager hookManager,
- HgRepositoryHandler handler, sonia.scm.repository.Repository repository,
- File directory)
- {
- this(hookManager, handler, repository, directory,
- handler.getHgContext().isPending());
- }
-
- /**
- * Constructs ...
- *
- *
- * @param hookManager
- * @param handler
- * @param repository
- * @param directory
- * @param pending
- */
- public HgCommandContext(HgHookManager hookManager,
- HgRepositoryHandler handler, sonia.scm.repository.Repository repository,
- File directory, boolean pending)
- {
- this.hookManager = hookManager;
+ public HgCommandContext(HgRepositoryHandler handler, HgRepositoryFactory factory, sonia.scm.repository.Repository scmRepository) {
this.handler = handler;
- this.directory = directory;
- this.scmRepository = repository;
- this.encoding = repository.getProperty(PROPERTY_ENCODING);
- this.pending = pending;
-
- if (Strings.isNullOrEmpty(encoding))
- {
- encoding = handler.getConfig().getEncoding();
- }
+ this.factory = factory;
+ this.scmRepository = scmRepository;
}
- //~--- methods --------------------------------------------------------------
-
- /**
- * Method description
- *
- *
- * @throws IOException
- */
- @Override
- public void close() throws IOException
- {
- if (repository != null)
- {
- repository.close();
+ public Repository open() {
+ if (repository == null) {
+ repository = factory.openForRead(scmRepository);
}
- }
-
- /**
- * Method description
- *
- *
- * @return
- */
- public Repository open()
- {
- if (repository == null)
- {
- repository = HgUtil.open(handler, hookManager, directory, encoding, pending);
- }
-
return repository;
}
- public Repository openWithSpecialEnvironment(BiConsumer> prepareEnvironment)
- {
- return HgUtil.open(handler, directory, encoding,
- pending, environment -> prepareEnvironment.accept(scmRepository, environment));
+ public Repository openForWrite() {
+ return factory.openForWrite(scmRepository);
}
- //~--- get methods ----------------------------------------------------------
- /**
- * Method description
- *
- *
- * @return
- */
public HgConfig getConfig()
{
return handler.getConfig();
@@ -159,25 +80,12 @@ public class HgCommandContext implements Closeable, RepositoryProvider
return getScmRepository();
}
- //~--- fields ---------------------------------------------------------------
- /** Field description */
- private File directory;
+ @Override
+ public void close() {
+ if (repository != null) {
+ repository.close();
+ }
+ }
- /** Field description */
- private String encoding;
-
- /** Field description */
- private HgRepositoryHandler handler;
-
- /** Field description */
- private HgHookManager hookManager;
-
- /** Field description */
- private boolean pending;
-
- /** Field description */
- private Repository repository;
-
- private final sonia.scm.repository.Repository scmRepository;
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java
index 10fe43bcf7..98bddd8c7c 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java
@@ -21,85 +21,56 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
-package sonia.scm.repository.spi;
-//~--- non-JDK imports --------------------------------------------------------
+package sonia.scm.repository.spi;
import com.aragost.javahg.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import sonia.scm.repository.HgHookManager;
+import sonia.scm.repository.HgRepositoryFactory;
import sonia.scm.repository.HgRepositoryHandler;
-import sonia.scm.repository.RepositoryHookType;
import sonia.scm.repository.spi.javahg.HgLogChangesetCommand;
import sonia.scm.web.HgUtil;
-import java.io.File;
-
-//~--- JDK imports ------------------------------------------------------------
-
/**
*
* @author Sebastian Sdorra
*/
-public class HgHookChangesetProvider implements HookChangesetProvider
-{
+public class HgHookChangesetProvider implements HookChangesetProvider {
- /**
- * the logger for HgHookChangesetProvider
- */
- private static final Logger logger =
- LoggerFactory.getLogger(HgHookChangesetProvider.class);
+ private static final Logger LOG = LoggerFactory.getLogger(HgHookChangesetProvider.class);
- //~--- constructors ---------------------------------------------------------
+ private final HgRepositoryHandler handler;
+ private final HgRepositoryFactory factory;
+ private final sonia.scm.repository.Repository scmRepository;
+ private final String startRev;
- public HgHookChangesetProvider(HgRepositoryHandler handler,
- File repositoryDirectory, HgHookManager hookManager, String startRev,
- RepositoryHookType type)
- {
+ private HookChangesetResponse response;
+
+ public HgHookChangesetProvider(HgRepositoryHandler handler, HgRepositoryFactory factory, sonia.scm.repository.Repository scmRepository, String startRev) {
this.handler = handler;
- this.repositoryDirectory = repositoryDirectory;
- this.hookManager = hookManager;
+ this.factory = factory;
+ this.scmRepository = scmRepository;
this.startRev = startRev;
- this.type = type;
}
- //~--- methods --------------------------------------------------------------
-
- /**
- * Method description
- *
- *
- * @param request
- *
- * @return
- */
@Override
- public synchronized HookChangesetResponse handleRequest(HookChangesetRequest request)
- {
- if (response == null)
- {
+ public synchronized HookChangesetResponse handleRequest(HookChangesetRequest request) {
+ if (response == null) {
Repository repository = null;
- try
- {
- repository = open();
+ try {
+ repository = factory.openForRead(scmRepository);
- HgLogChangesetCommand cmd = HgLogChangesetCommand.on(repository,
- handler.getConfig());
+ HgLogChangesetCommand cmd = HgLogChangesetCommand.on(repository, handler.getConfig());
response = new HookChangesetResponse(
- cmd.rev(startRev.concat(":").concat(HgUtil.REVISION_TIP)).execute());
- }
- catch (Exception ex)
- {
- logger.error("could not retrieve changesets", ex);
- }
- finally
- {
- if (repository != null)
- {
+ cmd.rev(startRev.concat(":").concat(HgUtil.REVISION_TIP)).execute()
+ );
+ } catch (Exception ex) {
+ LOG.error("could not retrieve changesets", ex);
+ } finally {
+ if (repository != null) {
repository.close();
}
}
@@ -108,39 +79,4 @@ public class HgHookChangesetProvider implements HookChangesetProvider
return response;
}
- /**
- * Method description
- *
- *
- * @return
- */
- private Repository open()
- {
- // use HG_PENDING only for pre receive hooks
- boolean pending = type == RepositoryHookType.PRE_RECEIVE;
-
- // TODO get repository encoding
- return HgUtil.open(handler, hookManager, repositoryDirectory, null,
- pending);
- }
-
- //~--- fields ---------------------------------------------------------------
-
- /** Field description */
- private HgRepositoryHandler handler;
-
- /** Field description */
- private HgHookManager hookManager;
-
- /** Field description */
- private File repositoryDirectory;
-
- /** Field description */
- private HookChangesetResponse response;
-
- /** Field description */
- private String startRev;
-
- /** Field description */
- private RepositoryHookType type;
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java
index 1f72d56e24..0282729baa 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java
@@ -21,14 +21,14 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
-import sonia.scm.repository.HgHookManager;
+import sonia.scm.repository.HgRepositoryFactory;
import sonia.scm.repository.HgRepositoryHandler;
-import sonia.scm.repository.RepositoryHookType;
+import sonia.scm.repository.Repository;
import sonia.scm.repository.api.HgHookBranchProvider;
import sonia.scm.repository.api.HgHookMessageProvider;
import sonia.scm.repository.api.HgHookTagProvider;
@@ -37,7 +37,6 @@ import sonia.scm.repository.api.HookFeature;
import sonia.scm.repository.api.HookMessageProvider;
import sonia.scm.repository.api.HookTagProvider;
-import java.io.File;
import java.util.EnumSet;
import java.util.Set;
@@ -45,55 +44,40 @@ import java.util.Set;
/**
* Mercurial implementation of {@link HookContextProvider}.
- *
+ *
* @author Sebastian Sdorra
*/
-public class HgHookContextProvider extends HookContextProvider
-{
+public class HgHookContextProvider extends HookContextProvider {
- private static final Set SUPPORTED_FEATURES =
- EnumSet.of(HookFeature.CHANGESET_PROVIDER, HookFeature.MESSAGE_PROVIDER,
- HookFeature.BRANCH_PROVIDER, HookFeature.TAG_PROVIDER);
+ private static final Set SUPPORTED_FEATURES = EnumSet.of(
+ HookFeature.CHANGESET_PROVIDER,
+ HookFeature.MESSAGE_PROVIDER,
+ HookFeature.BRANCH_PROVIDER,
+ HookFeature.TAG_PROVIDER
+ );
- //~--- constructors ---------------------------------------------------------
+ private final HgHookChangesetProvider hookChangesetProvider;
+ private HgHookMessageProvider hgMessageProvider;
+ private HgHookBranchProvider hookBranchProvider;
+ private HgHookTagProvider hookTagProvider;
- /**
- * Constructs a new instance.
- *
- * @param handler mercurial repository handler
- * @param repositoryDirectory the directory of the changed repository
- * @param hookManager mercurial hook manager
- * @param startRev start revision
- * @param type type of hook
- */
- public HgHookContextProvider(HgRepositoryHandler handler,
- File repositoryDirectory, HgHookManager hookManager, String startRev,
- RepositoryHookType type)
- {
- this.hookChangesetProvider = new HgHookChangesetProvider(handler, repositoryDirectory, hookManager, startRev, type);
+ public HgHookContextProvider(HgRepositoryHandler handler, HgRepositoryFactory factory, Repository repository, String startRev) {
+ this.hookChangesetProvider = new HgHookChangesetProvider(handler, factory, repository, startRev);
}
- //~--- get methods ----------------------------------------------------------
-
@Override
- public HookBranchProvider getBranchProvider()
- {
- if (hookBranchProvider == null)
- {
+ public HookBranchProvider getBranchProvider() {
+ if (hookBranchProvider == null) {
hookBranchProvider = new HgHookBranchProvider(hookChangesetProvider);
}
-
return hookBranchProvider;
}
@Override
- public HookTagProvider getTagProvider()
- {
- if (hookTagProvider == null)
- {
+ public HookTagProvider getTagProvider() {
+ if (hookTagProvider == null) {
hookTagProvider = new HgHookTagProvider(hookChangesetProvider);
}
-
return hookTagProvider;
}
@@ -102,14 +86,11 @@ public class HgHookContextProvider extends HookContextProvider
{
return hookChangesetProvider;
}
-
- public HgHookMessageProvider getHgMessageProvider()
- {
- if (hgMessageProvider == null)
- {
+
+ public HgHookMessageProvider getHgMessageProvider() {
+ if (hgMessageProvider == null) {
hgMessageProvider = new HgHookMessageProvider();
}
-
return hgMessageProvider;
}
@@ -119,21 +100,9 @@ public class HgHookContextProvider extends HookContextProvider
return SUPPORTED_FEATURES;
}
- //~--- methods --------------------------------------------------------------
-
@Override
protected HookMessageProvider createMessageProvider()
{
return getHgMessageProvider();
}
-
- //~--- fields ---------------------------------------------------------------
-
- private final HgHookChangesetProvider hookChangesetProvider;
-
- private HgHookMessageProvider hgMessageProvider;
-
- private HgHookBranchProvider hookBranchProvider;
-
- private HgHookTagProvider hookTagProvider;
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java
index 302bea6d4f..aaea12eeac 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java
@@ -31,6 +31,8 @@ import com.aragost.javahg.commands.ExecutionException;
import com.aragost.javahg.commands.PullCommand;
import com.aragost.javahg.commands.RemoveCommand;
import com.aragost.javahg.commands.StatusCommand;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import sonia.scm.NoChangesMadeException;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.work.WorkingCopy;
@@ -41,11 +43,13 @@ import java.nio.file.Path;
import java.util.List;
import java.util.regex.Pattern;
+@SuppressWarnings("java:S3252") // it is ok for javahg classes to access static method of subtype
public class HgModifyCommand implements ModifyCommand {
+ private static final Logger LOG = LoggerFactory.getLogger(HgModifyCommand.class);
static final Pattern HG_MESSAGE_PATTERN = Pattern.compile(".*\\[SCM\\](?: Error:)? (.*)");
- private HgCommandContext context;
+ private final HgCommandContext context;
private final HgWorkingCopyFactory workingCopyFactory;
public HgModifyCommand(HgCommandContext context, HgWorkingCopyFactory workingCopyFactory) {
@@ -55,7 +59,6 @@ public class HgModifyCommand implements ModifyCommand {
@Override
public String execute(ModifyCommandRequest request) {
-
try (WorkingCopy workingCopy = workingCopyFactory.createWorkingCopy(context, request.getBranch())) {
Repository workingRepository = workingCopy.getWorkingRepository();
request.getRequests().forEach(
@@ -100,12 +103,21 @@ public class HgModifyCommand implements ModifyCommand {
}
}
);
+
if (StatusCommand.on(workingRepository).lines().isEmpty()) {
throw new NoChangesMadeException(context.getScmRepository());
}
- CommitCommand.on(workingRepository).user(String.format("%s <%s>", request.getAuthor().getName(), request.getAuthor().getMail())).message(request.getCommitMessage()).execute();
+
+ LOG.trace("commit changes in working copy");
+ CommitCommand.on(workingRepository)
+ .user(String.format("%s <%s>", request.getAuthor().getName(), request.getAuthor().getMail()))
+ .message(request.getCommitMessage()).execute();
+
List execute = pullModifyChangesToCentralRepository(request, workingCopy);
- return execute.get(0).getNode();
+
+ String node = execute.get(0).getNode();
+ LOG.debug("successfully pulled changes from working copy, new node {}", node);
+ return node;
} catch (ExecutionException e) {
throwInternalRepositoryException("could not execute command on repository", e);
return null;
@@ -113,6 +125,7 @@ public class HgModifyCommand implements ModifyCommand {
}
private List pullModifyChangesToCentralRepository(ModifyCommandRequest request, WorkingCopy workingCopy) {
+ LOG.trace("pull changes from working copy");
try {
com.aragost.javahg.commands.PullCommand pullCommand = PullCommand.on(workingCopy.getCentralRepository());
workingCopyFactory.configure(pullCommand);
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java
index ce64b06982..0188078b77 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java
@@ -26,13 +26,12 @@ package sonia.scm.repository.spi;
import com.google.common.io.Closeables;
import sonia.scm.repository.Feature;
-import sonia.scm.repository.HgHookManager;
+import sonia.scm.repository.HgRepositoryFactory;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.Repository;
import sonia.scm.repository.api.Command;
import sonia.scm.repository.api.CommandNotSupportedException;
-import java.io.File;
import java.io.IOException;
import java.util.EnumSet;
import java.util.Set;
@@ -41,11 +40,8 @@ import java.util.Set;
*
* @author Sebastian Sdorra
*/
-public class HgRepositoryServiceProvider extends RepositoryServiceProvider
-{
+public class HgRepositoryServiceProvider extends RepositoryServiceProvider {
- /** Field description */
- //J-
public static final Set COMMANDS = EnumSet.of(
Command.BLAME,
Command.BROWSE,
@@ -61,25 +57,19 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider
Command.PULL,
Command.MODIFY
);
- //J+
- /** Field description */
- public static final Set FEATURES =
- EnumSet.of(Feature.COMBINED_DEFAULT_BRANCH);
+ public static final Set FEATURES = EnumSet.of(Feature.COMBINED_DEFAULT_BRANCH);
- //~--- constructors ---------------------------------------------------------
+ private final HgRepositoryHandler handler;
+ private final HgCommandContext context;
- HgRepositoryServiceProvider(HgRepositoryHandler handler,
- HgHookManager hookManager, Repository repository)
- {
+ HgRepositoryServiceProvider(HgRepositoryHandler handler, HgRepositoryFactory factory, Repository repository) {
this.handler = handler;
- this.repositoryDirectory = handler.getDirectory(repository.getId());
- this.context = new HgCommandContext(hookManager, handler, repository,
- repositoryDirectory);
+ this.context = new HgCommandContext(handler, factory, repository);
}
-
//~--- methods --------------------------------------------------------------
+
/**
* Method description
*
@@ -91,9 +81,9 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider
{
Closeables.close(context, true);
}
-
//~--- get methods ----------------------------------------------------------
+
/**
* Method description
*
@@ -271,14 +261,4 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider
return new HgTagsCommand(context);
}
- //~--- fields ---------------------------------------------------------------
-
- /** Field description */
- private HgCommandContext context;
-
- /** Field description */
- private HgRepositoryHandler handler;
-
- /** Field description */
- private File repositoryDirectory;
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java
index 4af799f30a..87a5d6d6fb 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java
@@ -21,12 +21,12 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository.spi;
import com.google.inject.Inject;
import sonia.scm.plugin.Extension;
-import sonia.scm.repository.HgHookManager;
+import sonia.scm.repository.HgRepositoryFactory;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.Repository;
@@ -35,18 +35,15 @@ import sonia.scm.repository.Repository;
* @author Sebastian Sdorra
*/
@Extension
-public class HgRepositoryServiceResolver implements RepositoryServiceResolver
-{
+public class HgRepositoryServiceResolver implements RepositoryServiceResolver {
private final HgRepositoryHandler handler;
- private final HgHookManager hookManager;
+ private final HgRepositoryFactory factory;
@Inject
- public HgRepositoryServiceResolver(HgRepositoryHandler handler,
- HgHookManager hookManager)
- {
+ public HgRepositoryServiceResolver(HgRepositoryHandler handler, HgRepositoryFactory factory) {
this.handler = handler;
- this.hookManager = hookManager;
+ this.factory = factory;
}
@Override
@@ -54,7 +51,7 @@ public class HgRepositoryServiceResolver implements RepositoryServiceResolver
HgRepositoryServiceProvider provider = null;
if (HgRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) {
- provider = new HgRepositoryServiceProvider(handler, hookManager, repository);
+ provider = new HgRepositoryServiceProvider(handler, factory, repository);
}
return provider;
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
new file mode 100644
index 0000000000..2d4e44f5a7
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgVersionCommand.java
@@ -0,0 +1,120 @@
+/*
+ * 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.spi;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.io.ByteStreams;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sonia.scm.repository.HgConfig;
+import sonia.scm.repository.HgVersion;
+
+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)"
+ };
+
+ private final HgConfig config;
+ private final ProcessExecutor executor;
+
+ public HgVersionCommand(HgConfig config) {
+ this(config, command -> new ProcessBuilder(command).start());
+ }
+
+ HgVersionCommand(HgConfig config, ProcessExecutor executor) {
+ this.config = config;
+ this.executor = executor;
+ }
+
+ public HgVersion get() {
+ return new HgVersion(getHgVersion(), getPythonVersion());
+ }
+
+ @Nonnull
+ private String getPythonVersion() {
+ 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();
+ } 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;
+ }
+
+ @SuppressWarnings("UnstableApiUsage")
+ private String exec(String command, String[] args) throws IOException, InterruptedException {
+ List cmd = new ArrayList<>();
+ cmd.add(command);
+ cmd.addAll(Arrays.asList(args));
+
+ Process process = executor.execute(cmd);
+ 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);
+ }
+
+ @FunctionalInterface
+ interface ProcessExecutor {
+ Process execute(List command) throws IOException;
+ }
+}
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 4e4c4cf80c..8c01c369ff 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
@@ -36,27 +36,21 @@ import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.work.SimpleWorkingCopyFactory;
import sonia.scm.repository.work.WorkingCopyPool;
import sonia.scm.util.IOUtil;
-import sonia.scm.web.HgRepositoryEnvironmentBuilder;
import javax.inject.Inject;
-import javax.inject.Provider;
import java.io.File;
import java.io.IOException;
-import java.util.Map;
-import java.util.function.BiConsumer;
public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory implements HgWorkingCopyFactory {
- private final Provider hgRepositoryEnvironmentBuilder;
-
@Inject
- public SimpleHgWorkingCopyFactory(Provider hgRepositoryEnvironmentBuilder, WorkingCopyPool workdirProvider) {
+ public SimpleHgWorkingCopyFactory(WorkingCopyPool workdirProvider) {
super(workdirProvider);
- this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder;
}
+
@Override
public ParentAndClone initialize(HgCommandContext context, File target, String initialBranch) {
- Repository centralRepository = openCentral(context);
+ Repository centralRepository = context.openForWrite();
CloneCommand cloneCommand = CloneCommandFlags.on(centralRepository);
if (initialBranch != null) {
cloneCommand.updaterev(initialBranch);
@@ -76,7 +70,7 @@ public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory reclaim(HgCommandContext context, File target, String initialBranch) throws ReclaimFailedException {
- Repository centralRepository = openCentral(context);
+ Repository centralRepository = context.openForWrite();
try {
BaseRepository clone = Repository.open(target);
for (String unknown : StatusCommand.on(clone).execute().getUnknown()) {
@@ -89,12 +83,6 @@ public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory> repositoryMapBiConsumer =
- (repository, environment) -> hgRepositoryEnvironmentBuilder.get().buildFor(repository, null, environment);
- return context.openWithSpecialEnvironment(repositoryMapBiConsumer);
- }
-
private void delete(File directory, String unknownFile) throws IOException {
IOUtil.delete(new File(directory, unknownFile));
}
@@ -111,7 +99,7 @@ public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory enm = session.getAttributeNames();
-
- while (enm.hasMoreElements())
- {
- String key = enm.nextElement();
-
- if (key.startsWith(ENV_SESSION_PREFIX))
- {
- env.set(key, session.getAttribute(key).toString());
- }
- }
- }
-
/**
* Method description
*
@@ -192,7 +158,9 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
executor.setExceptionHandler(exceptionHandler);
executor.setStatusCodeHandler(exceptionHandler);
executor.setContentLengthWorkaround(true);
- hgRepositoryEnvironmentBuilder.buildFor(repository, request, executor.getEnvironment().asMutableMap());
+
+ EnvList env = executor.getEnvironment();
+ environmentBuilder.write(repository).forEach(env::set);
String interpreter = getInterpreter();
@@ -248,5 +216,5 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
/** Field description */
private final RepositoryRequestListenerUtil requestListenerUtil;
- private final HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder;
+ private final HgEnvironmentBuilder environmentBuilder;
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java
deleted file mode 100644
index 5b0609cc72..0000000000
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java
+++ /dev/null
@@ -1,446 +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.web;
-
-//~--- non-JDK imports --------------------------------------------------------
-
-import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
-import com.google.common.io.Closeables;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import org.apache.shiro.SecurityUtils;
-import org.apache.shiro.authc.AuthenticationToken;
-import org.apache.shiro.subject.Subject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import sonia.scm.NotFoundException;
-import sonia.scm.repository.HgContext;
-import sonia.scm.repository.HgHookManager;
-import sonia.scm.repository.HgRepositoryHandler;
-import sonia.scm.repository.RepositoryHookType;
-import sonia.scm.repository.api.HgHookMessage;
-import sonia.scm.repository.api.HgHookMessage.Severity;
-import sonia.scm.repository.spi.HgHookContextProvider;
-import sonia.scm.repository.spi.HookEventFacade;
-import sonia.scm.security.BearerToken;
-import sonia.scm.security.CipherUtil;
-import sonia.scm.util.HttpUtil;
-import sonia.scm.util.Util;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-//~--- JDK imports ------------------------------------------------------------
-
-/**
- *
- * @author Sebastian Sdorra
- */
-@Singleton
-public class HgHookCallbackServlet extends HttpServlet
-{
-
- /** Field description */
- public static final String HGHOOK_POST_RECEIVE = "changegroup";
-
- /** Field description */
- public static final String HGHOOK_PRE_RECEIVE = "pretxnchangegroup";
-
- /** Field description */
- public static final String PARAM_REPOSITORYID = "repositoryId";
-
- /** Field description */
- private static final String PARAM_CHALLENGE = "challenge";
-
- /** Field description */
- private static final String PARAM_TOKEN = "token";
-
- /** Field description */
- private static final String PARAM_NODE = "node";
-
- /** Field description */
- private static final String PARAM_PING = "ping";
-
- /** Field description */
- private static final Pattern REGEX_URL =
- Pattern.compile("^/hook/hg/([^/]+)$");
-
- /** the logger for HgHookCallbackServlet */
- private static final Logger logger =
- LoggerFactory.getLogger(HgHookCallbackServlet.class);
-
- /** Field description */
- private static final long serialVersionUID = 3531596724828189353L;
-
- //~--- constructors ---------------------------------------------------------
-
- @Inject
- public HgHookCallbackServlet(HookEventFacade hookEventFacade,
- HgRepositoryHandler handler, HgHookManager hookManager,
- Provider contextProvider)
- {
- this.hookEventFacade = hookEventFacade;
- this.handler = handler;
- this.hookManager = hookManager;
- this.contextProvider = contextProvider;
- }
-
- //~--- methods --------------------------------------------------------------
-
- /**
- * Method description
- *
- *
- * @param request
- * @param response
- *
- * @throws IOException
- * @throws ServletException
- */
- @Override
- protected void doGet(HttpServletRequest request, HttpServletResponse response)
- {
- String ping = request.getParameter(PARAM_PING);
-
- if (Util.isNotEmpty(ping) && Boolean.parseBoolean(ping))
- {
- response.setStatus(HttpServletResponse.SC_NO_CONTENT);
- }
- else
- {
- response.setStatus(HttpServletResponse.SC_NOT_FOUND);
- }
- }
-
- @Override
- protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- try {
- handlePostRequest(request, response);
- } catch (IOException ex) {
- logger.warn("error in hook callback execution, sending internal server error", ex);
- response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
- }
- }
-
- private void handlePostRequest(HttpServletRequest request, HttpServletResponse response) throws IOException
- {
- String strippedURI = HttpUtil.getStrippedURI(request);
- Matcher m = REGEX_URL.matcher(strippedURI);
-
- if (m.matches())
- {
- String repositoryId = getRepositoryId(request);
- String type = m.group(1);
- String challenge = request.getParameter(PARAM_CHALLENGE);
-
- if (Util.isNotEmpty(challenge))
- {
- String node = request.getParameter(PARAM_NODE);
-
- if (Util.isNotEmpty(node))
- {
- String token = request.getParameter(PARAM_TOKEN);
-
- if (Util.isNotEmpty(token))
- {
- authenticate(token);
- }
-
- hookCallback(response, type, repositoryId, challenge, node);
- }
- else if (logger.isDebugEnabled())
- {
- logger.debug("node parameter not found");
- }
- }
- else if (logger.isDebugEnabled())
- {
- logger.debug("challenge parameter not found");
- }
- }
- else
- {
- if (logger.isDebugEnabled())
- {
- logger.debug("url does not match");
- }
-
- response.sendError(HttpServletResponse.SC_BAD_REQUEST);
- }
- }
-
- private void authenticate(String token)
- {
- try
- {
- token = CipherUtil.getInstance().decode(token);
-
- if (Util.isNotEmpty(token))
- {
- Subject subject = SecurityUtils.getSubject();
-
- AuthenticationToken accessToken = createToken(token);
-
- //J-
- subject.login(accessToken);
- }
- }
- catch (Exception ex)
- {
- logger.error("could not authenticate user", ex);
- }
- }
-
- private AuthenticationToken createToken(String tokenString)
- {
- return BearerToken.valueOf(tokenString);
- }
-
- private void fireHook(HttpServletResponse response, String repositoryId, String node, RepositoryHookType type)
- throws IOException
- {
- HgHookContextProvider context = null;
-
- try
- {
- if (type == RepositoryHookType.PRE_RECEIVE)
- {
- contextProvider.get().setPending(true);
- }
-
- File repositoryDirectory = handler.getDirectory(repositoryId);
- context = new HgHookContextProvider(handler, repositoryDirectory, hookManager,
- node, type);
-
- hookEventFacade.handle(repositoryId).fireHookEvent(type, context);
-
- printMessages(response, context);
- }
- catch (NotFoundException ex)
- {
- logger.error(ex.getMessage());
-
- logger.trace("repository not found", ex);
-
- response.sendError(HttpServletResponse.SC_NOT_FOUND);
- }
- catch (Exception ex)
- {
- sendError(response, context, ex);
- }
- }
-
- private void hookCallback(HttpServletResponse response, String typeName, String repositoryId, String challenge, String node) throws IOException {
- if (hookManager.isAcceptAble(challenge))
- {
- RepositoryHookType type = null;
-
- if (HGHOOK_PRE_RECEIVE.equals(typeName))
- {
- type = RepositoryHookType.PRE_RECEIVE;
- }
- else if (HGHOOK_POST_RECEIVE.equals(typeName))
- {
- type = RepositoryHookType.POST_RECEIVE;
- }
-
- if (type != null)
- {
- fireHook(response, repositoryId, node, type);
- }
- else
- {
- if (logger.isWarnEnabled())
- {
- logger.warn("unknown hook type {}", typeName);
- }
-
- response.sendError(HttpServletResponse.SC_BAD_REQUEST);
- }
- }
- else
- {
- if (logger.isWarnEnabled())
- {
- logger.warn("hg hook challenge is not accept able");
- }
-
- response.sendError(HttpServletResponse.SC_BAD_REQUEST);
- }
- }
-
- /**
- * Method description
- *
- *
- * @param writer
- * @param msg
- */
- private void printMessage(PrintWriter writer, HgHookMessage msg)
- {
- writer.append('_');
-
- if (msg.getSeverity() == Severity.ERROR)
- {
- writer.append("e[SCM] Error: ");
- }
- else
- {
- writer.append("n[SCM] ");
- }
-
- writer.println(msg.getMessage());
- }
-
- /**
- * Method description
- *
- *
- * @param response
- * @param context
- *
- * @throws IOException
- */
- private void printMessages(HttpServletResponse response,
- HgHookContextProvider context)
- throws IOException
- {
- List msgs = context.getHgMessageProvider().getMessages();
-
- if (Util.isNotEmpty(msgs))
- {
- PrintWriter writer = null;
-
- try
- {
- writer = response.getWriter();
-
- printMessages(writer, msgs);
- }
- finally
- {
- Closeables.close(writer, false);
- }
- }
- }
-
- /**
- * Method description
- *
- *
- * @param writer
- * @param msgs
- */
- private void printMessages(PrintWriter writer, List msgs)
- {
- for (HgHookMessage msg : msgs)
- {
- printMessage(writer, msg);
- }
- }
-
- /**
- * Method description
- *
- *
- * @param response
- * @param context
- * @param ex
- *
- * @throws IOException
- */
- private void sendError(HttpServletResponse response,
- HgHookContextProvider context, Exception ex)
- throws IOException
- {
- logger.warn("hook ended with exception", ex);
- response.setStatus(HttpServletResponse.SC_CONFLICT);
-
- String msg = ex.getMessage();
- List msgs = null;
-
- if (context != null)
- {
- msgs = context.getHgMessageProvider().getMessages();
- }
-
- if (!Strings.isNullOrEmpty(msg) || Util.isNotEmpty(msgs))
- {
- PrintWriter writer = null;
-
- try
- {
- writer = response.getWriter();
-
- if (Util.isNotEmpty(msgs))
- {
- printMessages(writer, msgs);
- }
-
- if (!Strings.isNullOrEmpty(msg))
- {
- printMessage(writer, new HgHookMessage(Severity.ERROR, msg));
- }
- }
- finally
- {
- Closeables.close(writer, true);
- }
- }
- }
-
- //~--- get methods ----------------------------------------------------------
-
- private String getRepositoryId(HttpServletRequest request)
- {
- String id = request.getParameter(PARAM_REPOSITORYID);
- Preconditions.checkArgument(!Strings.isNullOrEmpty(id), "repository id not found in request");
- return id;
- }
-
- //~--- fields ---------------------------------------------------------------
-
- /** Field description */
- private final Provider contextProvider;
-
- /** Field description */
- private final HgRepositoryHandler handler;
-
- /** Field description */
- private final HookEventFacade hookEventFacade;
-
- /** Field description */
- private final HgHookManager hookManager;
-}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java
deleted file mode 100644
index 6b626b5c33..0000000000
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java
+++ /dev/null
@@ -1,80 +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.web;
-
-import sonia.scm.repository.HgEnvironment;
-import sonia.scm.repository.HgHookManager;
-import sonia.scm.repository.HgRepositoryHandler;
-import sonia.scm.repository.Repository;
-
-import javax.inject.Inject;
-import javax.servlet.http.HttpServletRequest;
-import java.io.File;
-import java.util.Map;
-
-public class HgRepositoryEnvironmentBuilder {
-
- private static final String ENV_REPOSITORY_NAME = "REPO_NAME";
- private static final String ENV_REPOSITORY_PATH = "SCM_REPOSITORY_PATH";
- private static final String ENV_REPOSITORY_ID = "SCM_REPOSITORY_ID";
- private static final String ENV_PYTHON_HTTPS_VERIFY = "PYTHONHTTPSVERIFY";
- private static final String ENV_HTTP_POST_ARGS = "SCM_HTTP_POST_ARGS";
-
- private final HgRepositoryHandler handler;
- private final HgHookManager hookManager;
-
- @Inject
- public HgRepositoryEnvironmentBuilder(HgRepositoryHandler handler, HgHookManager hookManager) {
- this.handler = handler;
- this.hookManager = hookManager;
- }
-
- public void buildFor(Repository repository, HttpServletRequest request, Map environment) {
- File directory = handler.getDirectory(repository.getId());
-
- environment.put(ENV_REPOSITORY_NAME, repository.getNamespace() + "/" + repository.getName());
- environment.put(ENV_REPOSITORY_ID, repository.getId());
- environment.put(ENV_REPOSITORY_PATH,
- directory.getAbsolutePath());
-
- // add hook environment
- if (handler.getConfig().isDisableHookSSLValidation()) {
- // disable ssl validation
- // Issue 959: https://goo.gl/zH5eY8
- environment.put(ENV_PYTHON_HTTPS_VERIFY, "0");
- }
-
- // enable experimental httppostargs protocol of mercurial
- // Issue 970: https://goo.gl/poascp
- environment.put(ENV_HTTP_POST_ARGS, String.valueOf(handler.getConfig().isEnableHttpPostArgs()));
-
- HgEnvironment.prepareEnvironment(
- environment,
- handler,
- hookManager,
- request
- );
- }
-}
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 5271751faa..64a27990a8 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
@@ -34,9 +34,6 @@ 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.HgContext;
-import sonia.scm.repository.HgContextProvider;
-import sonia.scm.repository.HgHookManager;
import sonia.scm.repository.spi.HgWorkingCopyFactory;
import sonia.scm.repository.spi.SimpleHgWorkingCopyFactory;
@@ -45,26 +42,10 @@ import sonia.scm.repository.spi.SimpleHgWorkingCopyFactory;
* @author Sebastian Sdorra
*/
@Extension
-public class HgServletModule extends ServletModule
-{
+public class HgServletModule extends ServletModule {
- /** Field description */
- public static final String MAPPING_HG = "/hg/*";
-
- /** Field description */
- public static final String MAPPING_HOOK = "/hook/hg/*";
-
- //~--- methods --------------------------------------------------------------
-
- /**
- * Method description
- *
- */
@Override
- protected void configureServlets()
- {
- bind(HgContext.class).toProvider(HgContextProvider.class);
- bind(HgHookManager.class);
+ protected void configureServlets() {
bind(HgPackageReader.class);
bind(HgConfigDtoToHgConfigMapper.class).to(Mappers.getMapper(HgConfigDtoToHgConfigMapper.class).getClass());
@@ -72,9 +53,6 @@ public class HgServletModule extends ServletModule
bind(HgConfigPackagesToDtoMapper.class).to(Mappers.getMapper(HgConfigPackagesToDtoMapper.class).getClass());
bind(HgConfigInstallationsToDtoMapper.class);
- // bind servlets
- serve(MAPPING_HOOK).with(HgHookCallbackServlet.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 234580ed6f..3ba797aff2 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
@@ -21,189 +21,48 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.web;
-//~--- non-JDK imports --------------------------------------------------------
-
-import com.aragost.javahg.Repository;
-import com.aragost.javahg.RepositoryConfiguration;
-
-import com.google.common.base.Strings;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import sonia.scm.SCMContext;
import sonia.scm.repository.HgConfig;
-import sonia.scm.repository.HgEnvironment;
-import sonia.scm.repository.HgHookManager;
import sonia.scm.repository.HgPythonScript;
-import sonia.scm.repository.HgRepositoryHandler;
-import sonia.scm.repository.spi.javahg.HgFileviewExtension;
-import sonia.scm.util.HttpUtil;
import sonia.scm.util.Util;
-//~--- JDK imports ------------------------------------------------------------
-
import java.io.File;
-import java.nio.charset.Charset;
-import java.util.Map;
-import java.util.function.Consumer;
-
-import javax.servlet.http.HttpServletRequest;
-
/**
*
* @author Sebastian Sdorra
*/
-public final class HgUtil
-{
+public final class HgUtil {
- /** Field description */
public static final String REVISION_TIP = "tip";
- /** Field description */
- private static final String USERAGENT_HG = "mercurial/";
-
- /**
- * the logger for HgUtil
- */
- private static final Logger logger = LoggerFactory.getLogger(HgUtil.class);
-
- //~--- constructors ---------------------------------------------------------
-
- /**
- * Constructs ...
- *
- */
private HgUtil() {}
- //~--- methods --------------------------------------------------------------
-
- /**
- * Method description
- *
- *
- * @param handler
- * @param hookManager
- * @param directory
- * @param encoding
- * @param pending
- *
- * @return
- */
- public static Repository open(HgRepositoryHandler handler,
- HgHookManager hookManager, File directory, String encoding, boolean pending)
- {
- return open(
- handler,
- directory,
- encoding,
- pending,
- environment -> HgEnvironment.prepareEnvironment(environment, handler, hookManager)
- );
- }
-
- public static Repository open(HgRepositoryHandler handler,
- File directory, String encoding, boolean pending,
- Consumer