mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-13 00:45:44 +01:00
Refactor nearly the whole scm-hg-plugin for new hook implementation
This commit is contained in:
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
@@ -30,9 +30,10 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@SuppressWarnings("java:S2160") // we don't need equals for dto
|
||||
public class HgConfigDto extends HalRepresentation {
|
||||
|
||||
private boolean disabled;
|
||||
@@ -44,7 +45,6 @@ public class HgConfigDto extends HalRepresentation {
|
||||
private boolean useOptimizedBytecode;
|
||||
private boolean showRevisionInId;
|
||||
private boolean enableHttpPostArgs;
|
||||
private boolean disableHookSSLValidation;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
|
||||
@@ -1,391 +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 org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.util.IOUtil;
|
||||
import sonia.scm.util.Util;
|
||||
import sonia.scm.web.HgUtil;
|
||||
|
||||
import javax.xml.bind.JAXBException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class AbstractHgHandler
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
protected static final String ENV_ID_REVISION = "SCM_ID_REVISION";
|
||||
|
||||
/** Field description */
|
||||
protected static final String ENV_NODE = "HG_NODE";
|
||||
|
||||
/** Field description */
|
||||
protected static final String ENV_PAGE_LIMIT = "SCM_PAGE_LIMIT";
|
||||
|
||||
/** Field description */
|
||||
protected static final String ENV_PAGE_START = "SCM_PAGE_START";
|
||||
|
||||
/** Field description */
|
||||
protected static final String ENV_PATH = "SCM_PATH";
|
||||
|
||||
/** Field description */
|
||||
protected static final String ENV_REPOSITORY_PATH = "SCM_REPOSITORY_PATH";
|
||||
|
||||
/** Field description */
|
||||
protected static final String ENV_REVISION = "SCM_REVISION";
|
||||
|
||||
/** Field description */
|
||||
protected static final String ENV_REVISION_END = "SCM_REVISION_END";
|
||||
|
||||
/** Field description */
|
||||
protected static final String ENV_REVISION_START = "SCM_REVISION_START";
|
||||
|
||||
/** Field description */
|
||||
private static final String ENCODING = "UTF-8";
|
||||
|
||||
/** mercurial encoding */
|
||||
private static final String ENV_HGENCODING = "HGENCODING";
|
||||
|
||||
/** Field description */
|
||||
private static final String ENV_PENDING = "HG_PENDING";
|
||||
|
||||
/** python encoding */
|
||||
private static final String ENV_PYTHONIOENCODING = "PYTHONIOENCODING";
|
||||
|
||||
/** Field description */
|
||||
private static final String ENV_PYTHONPATH = "PYTHONPATH";
|
||||
|
||||
/**
|
||||
* the logger for AbstractHgCommand
|
||||
*/
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(AbstractHgHandler.class);
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param handler
|
||||
* @param context
|
||||
* @param repository
|
||||
*/
|
||||
protected AbstractHgHandler(HgRepositoryHandler handler, HgContext context,
|
||||
Repository repository)
|
||||
{
|
||||
this(handler, context, repository, handler.getDirectory(repository.getId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param handler
|
||||
* @param context
|
||||
* @param repository
|
||||
* @param repositoryDirectory
|
||||
*/
|
||||
protected AbstractHgHandler(HgRepositoryHandler handler, HgContext context,
|
||||
Repository repository, File repositoryDirectory)
|
||||
{
|
||||
this.handler = handler;
|
||||
this.context = context;
|
||||
this.repository = repository;
|
||||
this.repositoryDirectory = repositoryDirectory;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param revision
|
||||
* @param path
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected Map<String, String> createEnvironment(String revision, String path)
|
||||
{
|
||||
Map<String, String> env = new HashMap<>();
|
||||
|
||||
env.put(ENV_REVISION, HgUtil.getRevision(revision));
|
||||
env.put(ENV_PATH, Util.nonNull(path));
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param args
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
protected Process createHgProcess(String... args) throws IOException
|
||||
{
|
||||
return createHgProcess(new HashMap<String, String>(), args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param extraEnv
|
||||
* @param args
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
protected Process createHgProcess(Map<String, String> extraEnv,
|
||||
String... args)
|
||||
throws IOException
|
||||
{
|
||||
return createProcess(extraEnv, handler.getConfig().getHgBinary(), args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param script
|
||||
* @param extraEnv
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
protected Process createScriptProcess(HgPythonScript script,
|
||||
Map<String, String> extraEnv)
|
||||
throws IOException
|
||||
{
|
||||
return createProcess(extraEnv, handler.getConfig().getPythonBinary(),
|
||||
script.getFile(SCMContext.getContext()).getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param errorStream
|
||||
*/
|
||||
protected void handleErrorStream(final InputStream errorStream)
|
||||
{
|
||||
if (errorStream != null)
|
||||
{
|
||||
new Thread(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
String content = IOUtil.getContent(errorStream);
|
||||
|
||||
if (Util.isNotEmpty(content))
|
||||
{
|
||||
logger.error(content.trim());
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
logger.error("error during logging", ex);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
protected <T> T getResultFromScript(Class<T> resultType, HgPythonScript script) throws IOException {
|
||||
return getResultFromScript(resultType, script,
|
||||
new HashMap<String, String>());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> T getResultFromScript(Class<T> resultType,
|
||||
HgPythonScript script, Map<String, String> extraEnv)
|
||||
throws IOException
|
||||
{
|
||||
Process p = createScriptProcess(script, extraEnv);
|
||||
|
||||
handleErrorStream(p.getErrorStream());
|
||||
try (InputStream input = p.getInputStream()) {
|
||||
return (T) handler.getJaxbContext().createUnmarshaller().unmarshal(input);
|
||||
} catch (JAXBException ex) {
|
||||
logger.error("could not parse result", ex);
|
||||
|
||||
throw new InternalRepositoryException(repository, "could not parse result", ex);
|
||||
}
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param extraEnv
|
||||
* @param cmd
|
||||
* @param args
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private Process createProcess(Map<String, String> extraEnv, String cmd,
|
||||
String... args)
|
||||
throws IOException
|
||||
{
|
||||
HgConfig config = handler.getConfig();
|
||||
List<String> cmdList = new ArrayList<String>();
|
||||
|
||||
cmdList.add(cmd);
|
||||
|
||||
if (Util.isNotEmpty(args))
|
||||
{
|
||||
cmdList.addAll(Arrays.asList(args));
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
StringBuilder msg = new StringBuilder("create process for [");
|
||||
Iterator<String> it = cmdList.iterator();
|
||||
|
||||
while (it.hasNext())
|
||||
{
|
||||
msg.append(it.next());
|
||||
|
||||
if (it.hasNext())
|
||||
{
|
||||
msg.append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
msg.append("]");
|
||||
logger.debug(msg.toString());
|
||||
}
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(cmdList);
|
||||
|
||||
pb.directory(repositoryDirectory);
|
||||
|
||||
Map<String, String> env = pb.environment();
|
||||
|
||||
// force utf-8 encoding for mercurial and python
|
||||
env.put(ENV_PYTHONIOENCODING, ENCODING);
|
||||
env.put(ENV_HGENCODING, ENCODING);
|
||||
|
||||
//J-
|
||||
env.put(ENV_ID_REVISION,
|
||||
String.valueOf(handler.getConfig().isShowRevisionInId())
|
||||
);
|
||||
//J+
|
||||
|
||||
if (context.isSystemEnvironment())
|
||||
{
|
||||
env.putAll(System.getenv());
|
||||
}
|
||||
|
||||
if (context.isPending())
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("enable hg pending for {}",
|
||||
repositoryDirectory.getAbsolutePath());
|
||||
}
|
||||
|
||||
env.put(ENV_PENDING, repositoryDirectory.getAbsolutePath());
|
||||
|
||||
if (extraEnv.containsKey(ENV_REVISION_START))
|
||||
{
|
||||
env.put(ENV_NODE, extraEnv.get(ENV_REVISION_START));
|
||||
}
|
||||
}
|
||||
|
||||
env.put(ENV_PYTHONPATH, HgUtil.getPythonPath(config));
|
||||
env.put(ENV_REPOSITORY_PATH, repositoryDirectory.getAbsolutePath());
|
||||
env.putAll(extraEnv);
|
||||
|
||||
if (logger.isTraceEnabled())
|
||||
{
|
||||
StringBuilder msg = new StringBuilder("start process in directory '");
|
||||
|
||||
msg.append(repositoryDirectory.getAbsolutePath()).append(
|
||||
"' with env: \n");
|
||||
|
||||
for (Map.Entry<String, String> e : env.entrySet())
|
||||
{
|
||||
msg.append(" ").append(e.getKey());
|
||||
msg.append(" = ").append(e.getValue());
|
||||
msg.append("\n");
|
||||
}
|
||||
|
||||
logger.trace(msg.toString());
|
||||
}
|
||||
|
||||
return pb.start();
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
protected Repository repository;
|
||||
|
||||
/** Field description */
|
||||
protected File repositoryDirectory;
|
||||
|
||||
/** Field description */
|
||||
private HgContext context;
|
||||
|
||||
/** Field description */
|
||||
private HgRepositoryHandler handler;
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import sonia.scm.repository.hooks.HookEnvironment;
|
||||
import sonia.scm.repository.hooks.HookServer;
|
||||
import sonia.scm.security.AccessToken;
|
||||
import sonia.scm.security.AccessTokenBuilderFactory;
|
||||
import sonia.scm.security.CipherUtil;
|
||||
import sonia.scm.web.HgUtil;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
public class DefaultHgEnvironmentBuilder implements HgEnvironmentBuilder {
|
||||
|
||||
@VisibleForTesting
|
||||
static final String ENV_PYTHON_PATH = "PYTHONPATH";
|
||||
@VisibleForTesting
|
||||
static final String ENV_HOOK_PORT = "SCM_HOOK_PORT";
|
||||
@VisibleForTesting
|
||||
static final String ENV_CHALLENGE = "SCM_CHALLENGE";
|
||||
@VisibleForTesting
|
||||
static final String ENV_BEARER_TOKEN = "SCM_BEARER_TOKEN";
|
||||
@VisibleForTesting
|
||||
static final String ENV_REPOSITORY_NAME = "REPO_NAME";
|
||||
@VisibleForTesting
|
||||
static final String ENV_REPOSITORY_PATH = "SCM_REPOSITORY_PATH";
|
||||
@VisibleForTesting
|
||||
static final String ENV_REPOSITORY_ID = "SCM_REPOSITORY_ID";
|
||||
@VisibleForTesting
|
||||
static final String ENV_HTTP_POST_ARGS = "SCM_HTTP_POST_ARGS";
|
||||
|
||||
private final AccessTokenBuilderFactory accessTokenBuilderFactory;
|
||||
private final HgRepositoryHandler repositoryHandler;
|
||||
private final HookEnvironment hookEnvironment;
|
||||
private final HookServer server;
|
||||
|
||||
private int hookPort = -1;
|
||||
|
||||
@Inject
|
||||
public DefaultHgEnvironmentBuilder(
|
||||
AccessTokenBuilderFactory accessTokenBuilderFactory, HgRepositoryHandler repositoryHandler,
|
||||
HookEnvironment hookEnvironment, HookServer server
|
||||
) {
|
||||
this.accessTokenBuilderFactory = accessTokenBuilderFactory;
|
||||
this.repositoryHandler = repositoryHandler;
|
||||
this.hookEnvironment = hookEnvironment;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Map<String, String> read(Repository repository) {
|
||||
ImmutableMap.Builder<String, String> env = ImmutableMap.builder();
|
||||
read(env, repository);
|
||||
return env.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> write(Repository repository) {
|
||||
ImmutableMap.Builder<String, String> env = ImmutableMap.builder();
|
||||
read(env, repository);
|
||||
write(env);
|
||||
return env.build();
|
||||
}
|
||||
|
||||
private void read(ImmutableMap.Builder<String, String> env, Repository repository) {
|
||||
HgConfig config = repositoryHandler.getConfig();
|
||||
env.put(ENV_PYTHON_PATH, HgUtil.getPythonPath(config));
|
||||
|
||||
File directory = repositoryHandler.getDirectory(repository.getId());
|
||||
|
||||
env.put(ENV_REPOSITORY_NAME, repository.getNamespace() + "/" + repository.getName());
|
||||
env.put(ENV_REPOSITORY_ID, repository.getId());
|
||||
env.put(ENV_REPOSITORY_PATH, directory.getAbsolutePath());
|
||||
|
||||
// enable experimental httppostargs protocol of mercurial
|
||||
// Issue 970: https://goo.gl/poascp
|
||||
env.put(ENV_HTTP_POST_ARGS, String.valueOf(config.isEnableHttpPostArgs()));
|
||||
}
|
||||
|
||||
private void write(ImmutableMap.Builder<String, String> env) {
|
||||
env.put(ENV_HOOK_PORT, String.valueOf(getHookPort()));
|
||||
AccessToken accessToken = accessTokenBuilderFactory.create().build();
|
||||
env.put(ENV_BEARER_TOKEN, CipherUtil.getInstance().encode(accessToken.compact()));
|
||||
env.put(ENV_CHALLENGE, hookEnvironment.getChallenge());
|
||||
}
|
||||
|
||||
private synchronized int getHookPort() {
|
||||
if (hookPort > 0) {
|
||||
return hookPort;
|
||||
}
|
||||
try {
|
||||
hookPort = server.start();
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException("failed to start mercurial hook server");
|
||||
}
|
||||
return hookPort;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
|
||||
@@ -36,20 +36,10 @@ import javax.xml.bind.annotation.XmlTransient;
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@XmlRootElement(name = "config")
|
||||
public class HgConfig extends RepositoryConfig
|
||||
{
|
||||
public class HgConfig extends RepositoryConfig {
|
||||
|
||||
public static final String PERMISSION = "hg";
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*/
|
||||
public HgConfig() {}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
|
||||
@Override
|
||||
@XmlTransient // Only for permission checks, don't serialize to XML
|
||||
public String getId() {
|
||||
@@ -123,10 +113,6 @@ public class HgConfig extends RepositoryConfig
|
||||
return useOptimizedBytecode;
|
||||
}
|
||||
|
||||
public boolean isDisableHookSSLValidation() {
|
||||
return disableHookSSLValidation;
|
||||
}
|
||||
|
||||
public boolean isEnableHttpPostArgs() {
|
||||
return enableHttpPostArgs;
|
||||
}
|
||||
@@ -216,10 +202,6 @@ public class HgConfig extends RepositoryConfig
|
||||
this.useOptimizedBytecode = useOptimizedBytecode;
|
||||
}
|
||||
|
||||
public void setDisableHookSSLValidation(boolean disableHookSSLValidation) {
|
||||
this.disableHookSSLValidation = disableHookSSLValidation;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
@@ -242,9 +224,4 @@ public class HgConfig extends RepositoryConfig
|
||||
|
||||
private boolean enableHttpPostArgs = false;
|
||||
|
||||
/**
|
||||
* disable validation of ssl certificates for mercurial hook
|
||||
* @see <a href="https://goo.gl/zH5eY8">Issue 959</a>
|
||||
*/
|
||||
private boolean disableHookSSLValidation = false;
|
||||
}
|
||||
|
||||
@@ -1,121 +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 --------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class HgContext
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*/
|
||||
public HgContext() {}
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param pending
|
||||
*/
|
||||
public HgContext(boolean pending)
|
||||
{
|
||||
this.pending = pending;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param pending
|
||||
* @param systemEnvironment
|
||||
*/
|
||||
public HgContext(boolean pending, boolean systemEnvironment)
|
||||
{
|
||||
this.pending = pending;
|
||||
this.systemEnvironment = systemEnvironment;
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isPending()
|
||||
{
|
||||
return pending;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isSystemEnvironment()
|
||||
{
|
||||
return systemEnvironment;
|
||||
}
|
||||
|
||||
//~--- set methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param pending
|
||||
*/
|
||||
public void setPending(boolean pending)
|
||||
{
|
||||
this.pending = pending;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param systemEnvironment
|
||||
*/
|
||||
public void setSystemEnvironment(boolean systemEnvironment)
|
||||
{
|
||||
this.systemEnvironment = systemEnvironment;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private boolean pending = false;
|
||||
|
||||
/** Field description */
|
||||
private boolean systemEnvironment = true;
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.inject.OutOfScopeException;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.ProvisionException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Injection provider for {@link HgContext}.
|
||||
* This provider returns an instance {@link HgContext} from request scope, if no {@link HgContext} could be found in
|
||||
* request scope (mostly because the scope is not available) a new {@link HgContext} gets returned.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class HgContextProvider implements Provider<HgContext>
|
||||
{
|
||||
|
||||
/**
|
||||
* the LOG for HgContextProvider
|
||||
*/
|
||||
private static final Logger LOG =
|
||||
LoggerFactory.getLogger(HgContextProvider.class);
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
private Provider<HgContextRequestStore> requestStoreProvider;
|
||||
|
||||
@Inject
|
||||
public HgContextProvider(Provider<HgContextRequestStore> requestStoreProvider) {
|
||||
this.requestStoreProvider = requestStoreProvider;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public HgContextProvider() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public HgContext get() {
|
||||
HgContext context = fetchContextFromRequest();
|
||||
if (context != null) {
|
||||
LOG.trace("return HgContext from request store");
|
||||
return context;
|
||||
}
|
||||
LOG.trace("could not find context in request scope, returning new instance");
|
||||
return new HgContext();
|
||||
}
|
||||
|
||||
private HgContext fetchContextFromRequest() {
|
||||
try {
|
||||
if (requestStoreProvider != null) {
|
||||
return requestStoreProvider.get().get();
|
||||
} else {
|
||||
LOG.trace("no request store provider defined, could not return context from request");
|
||||
return null;
|
||||
}
|
||||
} catch (ProvisionException ex) {
|
||||
if (ex.getCause() instanceof OutOfScopeException) {
|
||||
LOG.trace("we are currently out of request scope, failed to retrieve context");
|
||||
return null;
|
||||
} else {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.inject.ProvisionException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.security.AccessToken;
|
||||
import sonia.scm.security.CipherUtil;
|
||||
import sonia.scm.security.Xsrf;
|
||||
import sonia.scm.web.HgUtil;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Map;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public final class HgEnvironment
|
||||
{
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HgEnvironment.class);
|
||||
|
||||
/** Field description */
|
||||
public static final String ENV_PYTHON_PATH = "PYTHONPATH";
|
||||
|
||||
/** Field description */
|
||||
private static final String ENV_CHALLENGE = "SCM_CHALLENGE";
|
||||
|
||||
/** Field description */
|
||||
private static final String ENV_URL = "SCM_URL";
|
||||
|
||||
private static final String SCM_BEARER_TOKEN = "SCM_BEARER_TOKEN";
|
||||
|
||||
private static final String SCM_XSRF = "SCM_XSRF";
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*/
|
||||
private HgEnvironment() {}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param environment
|
||||
* @param handler
|
||||
* @param hookManager
|
||||
*/
|
||||
public static void prepareEnvironment(Map<String, String> environment,
|
||||
HgRepositoryHandler handler, HgHookManager hookManager)
|
||||
{
|
||||
prepareEnvironment(environment, handler, hookManager, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param environment
|
||||
* @param handler
|
||||
* @param hookManager
|
||||
* @param request
|
||||
*/
|
||||
public static void prepareEnvironment(Map<String, String> environment,
|
||||
HgRepositoryHandler handler, HgHookManager hookManager,
|
||||
HttpServletRequest request)
|
||||
{
|
||||
String hookUrl;
|
||||
|
||||
if (request != null)
|
||||
{
|
||||
hookUrl = hookManager.createUrl(request);
|
||||
}
|
||||
else
|
||||
{
|
||||
hookUrl = hookManager.createUrl();
|
||||
}
|
||||
|
||||
try {
|
||||
AccessToken accessToken = hookManager.getAccessToken();
|
||||
environment.put(SCM_BEARER_TOKEN, CipherUtil.getInstance().encode(accessToken.compact()));
|
||||
extractXsrfKey(environment, accessToken);
|
||||
} catch (ProvisionException e) {
|
||||
LOG.debug("could not create bearer token; looks like currently we are not in a request; probably you can ignore the following exception:", e);
|
||||
}
|
||||
environment.put(ENV_PYTHON_PATH, HgUtil.getPythonPath(handler.getConfig()));
|
||||
environment.put(ENV_URL, hookUrl);
|
||||
environment.put(ENV_CHALLENGE, hookManager.getChallenge());
|
||||
}
|
||||
|
||||
private static void extractXsrfKey(Map<String, String> environment, AccessToken accessToken) {
|
||||
environment.put(SCM_XSRF, accessToken.<String>getCustom(Xsrf.TOKEN_KEY).orElse("-"));
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*/
|
||||
@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<String, String> read(Repository repository);
|
||||
Map<String, String> write(Repository repository);
|
||||
}
|
||||
@@ -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<HttpServletRequest> 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<HttpServletRequest> httpServletRequestProvider;
|
||||
|
||||
private final AccessTokenBuilderFactory accessTokenBuilderFactory;
|
||||
}
|
||||
@@ -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<Repository, File> 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<Repository, File> 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<String, String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<HgConfig> {
|
||||
|
||||
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<HgContext> hgContextProvider;
|
||||
|
||||
private final HgWorkingCopyFactory workingCopyFactory;
|
||||
|
||||
private final JAXBContext jaxbContext;
|
||||
|
||||
@Inject
|
||||
public HgRepositoryHandler(ConfigurationStoreFactory storeFactory,
|
||||
Provider<HgContext> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
package sonia.scm.repository.hooks;
|
||||
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
@@ -31,7 +32,7 @@ import org.apache.shiro.authc.AuthenticationException;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.repository.RepositoryHookType;
|
||||
import sonia.scm.repository.api.HgHookMessage;
|
||||
import sonia.scm.repository.spi.HgHookContextProvider;
|
||||
@@ -39,9 +40,7 @@ import sonia.scm.repository.spi.HookEventFacade;
|
||||
import sonia.scm.security.BearerToken;
|
||||
import sonia.scm.security.CipherUtil;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.inject.Provider;
|
||||
import java.io.File;
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@@ -50,19 +49,20 @@ import java.util.List;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
class HgHookHandler implements Runnable {
|
||||
class DefaultHookHandler implements HookHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HgHookHandler.class);
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultHookHandler.class);
|
||||
|
||||
private final HgRepositoryHandler handler;
|
||||
private final HookEventFacade hookEventFacade;
|
||||
private final Provider<HookEnvironment> environmentProvider;
|
||||
private final HookEnvironment environment;
|
||||
private final HookContextProviderFactory hookContextProviderFactory;
|
||||
private final Socket socket;
|
||||
|
||||
HgHookHandler(HgRepositoryHandler handler, HookEventFacade hookEventFacade, Provider<HookEnvironment> environmentProvider, Socket socket) {
|
||||
this.handler = handler;
|
||||
@Inject
|
||||
public DefaultHookHandler(HookContextProviderFactory hookContextProviderFactory, HookEventFacade hookEventFacade, HookEnvironment environment, @Assisted Socket socket) {
|
||||
this.hookContextProviderFactory = hookContextProviderFactory;
|
||||
this.hookEventFacade = hookEventFacade;
|
||||
this.environmentProvider = environmentProvider;
|
||||
this.environment = environment;
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
@@ -84,7 +84,6 @@ class HgHookHandler implements Runnable {
|
||||
}
|
||||
|
||||
private Response handleHookRequest(Request request) {
|
||||
HookEnvironment environment = environmentProvider.get();
|
||||
try {
|
||||
if (!environment.isAcceptAble(request.getChallenge())) {
|
||||
return error("invalid hook challenge");
|
||||
@@ -93,13 +92,16 @@ class HgHookHandler implements Runnable {
|
||||
authenticate(request);
|
||||
environment.setPending(request.getType() == RepositoryHookType.PRE_RECEIVE);
|
||||
|
||||
HgHookContextProvider context = createHookContextProvider(request);
|
||||
HgHookContextProvider context = hookContextProviderFactory.create(request.getRepositoryId(), request.getNode());
|
||||
hookEventFacade.handle(request.getRepositoryId()).fireHookEvent(request.getType(), context);
|
||||
|
||||
return new Response(context.getHgMessageProvider().getMessages(), false);
|
||||
} catch (AuthenticationException ex) {
|
||||
LOG.warn("hook authentication failed", ex);
|
||||
return error("hook authentication failed");
|
||||
} catch (NotFoundException ex) {
|
||||
LOG.warn("could not find repository with id {}", request.getRepositoryId(), ex);
|
||||
return error("repository not found");
|
||||
} catch (Exception ex) {
|
||||
LOG.warn("unknown error on hook occurred", ex);
|
||||
return error("unknown error");
|
||||
@@ -108,14 +110,6 @@ class HgHookHandler implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private HgHookContextProvider createHookContextProvider(Request request) {
|
||||
File repositoryDirectory = handler.getDirectory(request.getRepositoryId());
|
||||
return new HgHookContextProvider(
|
||||
handler, repositoryDirectory, null, request.node, request.type
|
||||
);
|
||||
}
|
||||
|
||||
private void authenticate(Request request) {
|
||||
String token = CipherUtil.getInstance().decode(request.getToken());
|
||||
BearerToken bearer = BearerToken.valueOf(token);
|
||||
@@ -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<String,
|
||||
String> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -24,14 +24,11 @@
|
||||
|
||||
package sonia.scm.repository.hooks;
|
||||
|
||||
import com.google.inject.ImplementedBy;
|
||||
|
||||
import java.net.Socket;
|
||||
|
||||
@FunctionalInterface
|
||||
@ImplementedBy(DefaultHookHandlerFactory.class)
|
||||
interface HookHandlerFactory {
|
||||
|
||||
Runnable create(Socket socket);
|
||||
HookHandler create(Socket socket);
|
||||
|
||||
}
|
||||
|
||||
@@ -24,28 +24,17 @@
|
||||
|
||||
package sonia.scm.repository.hooks;
|
||||
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.spi.HookEventFacade;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import java.net.Socket;
|
||||
|
||||
public class DefaultHookHandlerFactory implements HookHandlerFactory {
|
||||
|
||||
private final HgRepositoryHandler handler;
|
||||
private final HookEventFacade hookEventFacade;
|
||||
private final Provider<HookEnvironment> hookEnvironment;
|
||||
|
||||
@Inject
|
||||
public DefaultHookHandlerFactory(HgRepositoryHandler handler, HookEventFacade hookEventFacade, Provider<HookEnvironment> hookEnvironment) {
|
||||
this.handler = handler;
|
||||
this.hookEventFacade = hookEventFacade;
|
||||
this.hookEnvironment = hookEnvironment;
|
||||
}
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.assistedinject.FactoryModuleBuilder;
|
||||
import sonia.scm.plugin.Extension;
|
||||
|
||||
@Extension
|
||||
public class HookModule extends AbstractModule {
|
||||
@Override
|
||||
public HgHookHandler create(Socket socket) {
|
||||
return new HgHookHandler(handler, hookEventFacade, hookEnvironment, socket);
|
||||
protected void configure() {
|
||||
install(new FactoryModuleBuilder()
|
||||
.implement(HookHandler.class, DefaultHookHandler.class)
|
||||
.build(HookHandlerFactory.class)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,9 @@
|
||||
package sonia.scm.repository.hooks;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import org.apache.shiro.concurrent.SubjectAwareExecutorService;
|
||||
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;
|
||||
|
||||
@@ -38,6 +40,7 @@ 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 {
|
||||
@@ -56,18 +59,24 @@ public class HookServer implements AutoCloseable {
|
||||
}
|
||||
|
||||
private ExecutorService createAcceptor() {
|
||||
return new SubjectAwareExecutorService(Executors.newSingleThreadExecutor(
|
||||
new ThreadFactoryBuilder().setNameFormat("HgHookAcceptor").build()
|
||||
));
|
||||
return Executors.newSingleThreadExecutor(
|
||||
createThreadFactory("HgHookAcceptor")
|
||||
);
|
||||
}
|
||||
|
||||
private ExecutorService createWorkerPool() {
|
||||
return new SubjectAwareExecutorService(Executors.newCachedThreadPool(
|
||||
new ThreadFactoryBuilder().setNameFormat("HgHookWorker-%d").build()
|
||||
)
|
||||
return Executors.newCachedThreadPool(
|
||||
createThreadFactory("HgHookWorker-%d")
|
||||
);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private ThreadFactory createThreadFactory(String hgHookAcceptor) {
|
||||
return new ThreadFactoryBuilder()
|
||||
.setNameFormat(hgHookAcceptor)
|
||||
.build();
|
||||
}
|
||||
|
||||
public int start() throws IOException {
|
||||
acceptor = createAcceptor();
|
||||
workerPool = createWorkerPool();
|
||||
@@ -83,12 +92,14 @@ public class HookServer implements AutoCloseable {
|
||||
}
|
||||
|
||||
private void accept() {
|
||||
SecurityManager securityManager = SecurityUtils.getSecurityManager();
|
||||
acceptor.submit(() -> {
|
||||
while (!serverSocket.isClosed()) {
|
||||
try {
|
||||
Socket clientSocket = serverSocket.accept();
|
||||
LOG.trace("accept incoming hook client from {}", clientSocket.getInetAddress());
|
||||
workerPool.submit(handlerFactory.create(clientSocket));
|
||||
HookHandler hookHandler = handlerFactory.create(clientSocket);
|
||||
workerPool.submit(associateSecurityManager(securityManager, hookHandler));
|
||||
} catch (IOException ex) {
|
||||
LOG.debug("failed to accept socket, possible closed", ex);
|
||||
}
|
||||
@@ -96,6 +107,18 @@ public class HookServer implements AutoCloseable {
|
||||
});
|
||||
}
|
||||
|
||||
private Runnable associateSecurityManager(SecurityManager securityManager, 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());
|
||||
|
||||
@@ -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<sonia.scm.repository.Repository, Map<String, String>> 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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<HookFeature> SUPPORTED_FEATURES =
|
||||
EnumSet.of(HookFeature.CHANGESET_PROVIDER, HookFeature.MESSAGE_PROVIDER,
|
||||
HookFeature.BRANCH_PROVIDER, HookFeature.TAG_PROVIDER);
|
||||
private static final Set<HookFeature> 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;
|
||||
}
|
||||
|
||||
@@ -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<Command> 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<Feature> FEATURES =
|
||||
EnumSet.of(Feature.COMBINED_DEFAULT_BRANCH);
|
||||
public static final Set<Feature> 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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
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);
|
||||
return HgVersion.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
private String exec(String command, String[] args) throws IOException {
|
||||
List<String> 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.exitValue();
|
||||
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<String> command) throws IOException;
|
||||
}
|
||||
}
|
||||
@@ -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<Repository, Repository, HgCommandContext> implements HgWorkingCopyFactory {
|
||||
|
||||
private final Provider<HgRepositoryEnvironmentBuilder> hgRepositoryEnvironmentBuilder;
|
||||
|
||||
@Inject
|
||||
public SimpleHgWorkingCopyFactory(Provider<HgRepositoryEnvironmentBuilder> hgRepositoryEnvironmentBuilder, WorkingCopyPool workdirProvider) {
|
||||
public SimpleHgWorkingCopyFactory(WorkingCopyPool workdirProvider) {
|
||||
super(workdirProvider);
|
||||
this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParentAndClone<Repository, Repository> 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<Reposit
|
||||
// The hg api to create a command is meant to be used from the command classes, not from their "flags" base classes.
|
||||
@SuppressWarnings("java:S3252")
|
||||
protected ParentAndClone<Repository, Repository> 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<Reposit
|
||||
}
|
||||
}
|
||||
|
||||
public Repository openCentral(HgCommandContext context) {
|
||||
BiConsumer<sonia.scm.repository.Repository, Map<String, String>> 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));
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.web;
|
||||
|
||||
import com.google.common.base.Stopwatch;
|
||||
@@ -32,6 +32,7 @@ import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.repository.HgEnvironmentBuilder;
|
||||
import sonia.scm.repository.HgPythonScript;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.Repository;
|
||||
@@ -42,29 +43,21 @@ import sonia.scm.web.cgi.CGIExecutor;
|
||||
import sonia.scm.web.cgi.CGIExecutorFactory;
|
||||
import sonia.scm.web.cgi.EnvList;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Enumeration;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Singleton
|
||||
public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
public static final String ENV_SESSION_PREFIX = "SCM_";
|
||||
public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet {
|
||||
|
||||
/** Field description */
|
||||
private static final long serialVersionUID = -3492811300905099810L;
|
||||
@@ -80,13 +73,13 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
||||
ScmConfiguration configuration,
|
||||
HgRepositoryHandler handler,
|
||||
RepositoryRequestListenerUtil requestListenerUtil,
|
||||
HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder)
|
||||
HgEnvironmentBuilder environmentBuilder)
|
||||
{
|
||||
this.cgiExecutorFactory = cgiExecutorFactory;
|
||||
this.configuration = configuration;
|
||||
this.handler = handler;
|
||||
this.requestListenerUtil = requestListenerUtil;
|
||||
this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder;
|
||||
this.environmentBuilder = environmentBuilder;
|
||||
this.exceptionHandler = new HgCGIExceptionHandler();
|
||||
this.command = HgPythonScript.HGWEB.getFile(SCMContext.getContext());
|
||||
}
|
||||
@@ -108,11 +101,7 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
||||
{
|
||||
handleRequest(request, response, repository);
|
||||
}
|
||||
catch (ServletException ex)
|
||||
{
|
||||
exceptionHandler.handleException(request, response, ex);
|
||||
}
|
||||
catch (IOException ex)
|
||||
catch (ServletException | IOException ex)
|
||||
{
|
||||
exceptionHandler.handleException(request, response, ex);
|
||||
}
|
||||
@@ -146,29 +135,6 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param env
|
||||
* @param session
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void passSessionAttributes(EnvList env, HttpSession session)
|
||||
{
|
||||
Enumeration<String> 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;
|
||||
}
|
||||
|
||||
@@ -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<HgContext> 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<HgHookMessage> 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<HgHookMessage> 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<HgHookMessage> 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<HgContext> contextProvider;
|
||||
|
||||
/** Field description */
|
||||
private final HgRepositoryHandler handler;
|
||||
|
||||
/** Field description */
|
||||
private final HookEventFacade hookEventFacade;
|
||||
|
||||
/** Field description */
|
||||
private final HgHookManager hookManager;
|
||||
}
|
||||
@@ -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<String, String> 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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Map<String, String>> prepareEnvironment)
|
||||
{
|
||||
String enc = encoding;
|
||||
|
||||
if (Strings.isNullOrEmpty(enc))
|
||||
{
|
||||
enc = handler.getConfig().getEncoding();
|
||||
}
|
||||
|
||||
RepositoryConfiguration repoConfiguration = RepositoryConfiguration.DEFAULT;
|
||||
|
||||
prepareEnvironment.accept(repoConfiguration.getEnvironment());
|
||||
|
||||
repoConfiguration.addExtension(HgFileviewExtension.class);
|
||||
repoConfiguration.setEnablePendingChangesets(pending);
|
||||
|
||||
try
|
||||
{
|
||||
Charset charset = Charset.forName(enc);
|
||||
|
||||
logger.trace("set encoding {} for mercurial", enc);
|
||||
|
||||
repoConfiguration.setEncoding(charset);
|
||||
}
|
||||
catch (IllegalArgumentException ex)
|
||||
{
|
||||
logger.error("could not set encoding for mercurial", ex);
|
||||
}
|
||||
|
||||
repoConfiguration.setHgBin(handler.getConfig().getHgBinary());
|
||||
|
||||
logger.debug("open hg repository {}: encoding: {}, pending: {}", directory, enc, pending);
|
||||
|
||||
return Repository.open(repoConfiguration, directory);
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param config
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getPythonPath(HgConfig config)
|
||||
{
|
||||
public static String getPythonPath(HgConfig config) {
|
||||
String pythonPath = Util.EMPTY_STRING;
|
||||
|
||||
if (config != null)
|
||||
{
|
||||
if (config != null) {
|
||||
pythonPath = Util.nonNull(config.getPythonPath());
|
||||
}
|
||||
|
||||
if (Util.isNotEmpty(pythonPath))
|
||||
{
|
||||
if (Util.isNotEmpty(pythonPath)) {
|
||||
pythonPath = pythonPath.concat(File.pathSeparator);
|
||||
}
|
||||
|
||||
//J-
|
||||
pythonPath = pythonPath.concat(
|
||||
HgPythonScript.getScriptDirectory(
|
||||
SCMContext.getContext()
|
||||
).getAbsolutePath()
|
||||
);
|
||||
//J+
|
||||
|
||||
return pythonPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param revision
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getRevision(String revision)
|
||||
{
|
||||
return Util.isEmpty(revision)
|
||||
? REVISION_TIP
|
||||
: revision;
|
||||
public static String getRevision(String revision) {
|
||||
return Util.isEmpty(revision) ? REVISION_TIP : revision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the request comes from a mercurial client.
|
||||
*
|
||||
*
|
||||
* @param request servlet request
|
||||
*
|
||||
* @return true if the client is mercurial
|
||||
*/
|
||||
public static boolean isHgClient(HttpServletRequest request)
|
||||
{
|
||||
return HttpUtil.userAgentStartsWith(request, USERAGENT_HG);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,87 +29,40 @@
|
||||
# changegroup.scm = python:scmhooks.callback
|
||||
#
|
||||
|
||||
import os, sys
|
||||
|
||||
client = None
|
||||
|
||||
# compatibility layer between python 2 and 3 urllib implementations
|
||||
if sys.version_info[0] < 3:
|
||||
import urllib, urllib2
|
||||
# create type alias for url error
|
||||
URLError = urllib2.URLError
|
||||
|
||||
class Python2Client:
|
||||
def post(self, url, values):
|
||||
data = urllib.urlencode(values)
|
||||
# open url but ignore proxy settings
|
||||
proxy_handler = urllib2.ProxyHandler({})
|
||||
opener = urllib2.build_opener(proxy_handler)
|
||||
req = urllib2.Request(url, data)
|
||||
req.add_header("X-XSRF-Token", xsrf)
|
||||
return opener.open(req)
|
||||
|
||||
client = Python2Client()
|
||||
else:
|
||||
import urllib.parse, urllib.request, urllib.error
|
||||
# create type alias for url error
|
||||
URLError = urllib.error.URLError
|
||||
|
||||
class Python3Client:
|
||||
def post(self, url, values):
|
||||
data = urllib.parse.urlencode(values)
|
||||
# open url but ignore proxy settings
|
||||
proxy_handler = urllib.request.ProxyHandler({})
|
||||
opener = urllib.request.build_opener(proxy_handler)
|
||||
req = urllib.request.Request(url, data.encode())
|
||||
req.add_header("X-XSRF-Token", xsrf)
|
||||
return opener.open(req)
|
||||
|
||||
client = Python3Client()
|
||||
import os, sys, json, socket
|
||||
|
||||
# read environment
|
||||
baseUrl = os.environ['SCM_URL']
|
||||
port = os.environ['SCM_HOOK_PORT']
|
||||
challenge = os.environ['SCM_CHALLENGE']
|
||||
token = os.environ['SCM_BEARER_TOKEN']
|
||||
xsrf = os.environ['SCM_XSRF']
|
||||
repositoryId = os.environ['SCM_REPOSITORY_ID']
|
||||
|
||||
def printMessages(ui, msgs):
|
||||
for raw in msgs:
|
||||
line = raw
|
||||
if hasattr(line, "encode"):
|
||||
line = line.encode()
|
||||
if line.startswith(b"_e") or line.startswith(b"_n"):
|
||||
line = line[2:]
|
||||
ui.warn(b'%s\n' % line.rstrip())
|
||||
def print_messages(ui, messages):
|
||||
for message in messages:
|
||||
ui.warn(b'%s: %s\n' % message['severity'], message['message'])
|
||||
|
||||
def callHookUrl(ui, repo, hooktype, node):
|
||||
def fire_hook(ui, repo, hooktype, node):
|
||||
abort = True
|
||||
try:
|
||||
url = baseUrl + hooktype.decode("utf-8")
|
||||
ui.debug( b"send scm-hook to " + url.encode() + b" and " + node + b"\n" )
|
||||
values = {'node': node.decode("utf-8"), 'challenge': challenge, 'token': token, 'repositoryPath': repo.root, 'repositoryId': repositoryId}
|
||||
conn = client.post(url, values)
|
||||
if 200 <= conn.code < 300:
|
||||
ui.debug( b"scm-hook " + hooktype + b" success with status code " + str(conn.code).encode() + b"\n" )
|
||||
printMessages(ui, conn)
|
||||
abort = False
|
||||
else:
|
||||
ui.warn( b"ERROR: scm-hook failed with error code " + str(conn.code).encode() + b"\n" )
|
||||
except URLError as e:
|
||||
msg = None
|
||||
# some URLErrors have no read method
|
||||
if hasattr(e, "read"):
|
||||
msg = e.read()
|
||||
elif hasattr(e, "code"):
|
||||
msg = "scm-hook failed with error code " + e.code + "\n"
|
||||
else:
|
||||
msg = str(e)
|
||||
if len(msg) > 0:
|
||||
printMessages(ui, msg.splitlines(True))
|
||||
else:
|
||||
ui.warn( b"ERROR: scm-hook failed with an unknown error\n" )
|
||||
ui.traceback()
|
||||
values = {'token': token, 'type': hooktype, 'repositoryId': repositoryId, 'challenge': challenge, 'node': node.decode('utf8') }
|
||||
ui.debug( b"send scm-hook for " + node + b"\n" )
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect(("127.0.0.1", int(port)))
|
||||
s.sendall(json.dumps(values).encode('utf-8'))
|
||||
s.sendall(b'\0')
|
||||
|
||||
received = []
|
||||
received = s.recv(1)
|
||||
while b != b'\0':
|
||||
received.append(b)
|
||||
received = s.recv(1)
|
||||
|
||||
message = b''.join(bytes).decode('utf-8')
|
||||
response = json.loads(message)
|
||||
|
||||
abort = response['abort']
|
||||
print_messages(ui, response['messages'])
|
||||
|
||||
except ValueError:
|
||||
ui.warn( b"scm-hook failed with an exception\n" )
|
||||
ui.traceback()
|
||||
@@ -118,8 +71,8 @@ def callHookUrl(ui, repo, hooktype, node):
|
||||
def callback(ui, repo, hooktype, node=None):
|
||||
abort = True
|
||||
if node != None:
|
||||
if len(baseUrl) > 0:
|
||||
abort = callHookUrl(ui, repo, hooktype, node)
|
||||
if len(port) > 0:
|
||||
abort = fire_hook(ui, repo, hooktype, node)
|
||||
else:
|
||||
ui.warn(b"ERROR: scm-manager hooks are disabled, please check your configuration and the scm-manager log for details\n")
|
||||
abort = False
|
||||
@@ -127,7 +80,7 @@ def callback(ui, repo, hooktype, node=None):
|
||||
ui.warn(b"changeset node is not available")
|
||||
return abort
|
||||
|
||||
def preHook(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs):
|
||||
def pre_hook(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs):
|
||||
# older mercurial versions
|
||||
if pending != None:
|
||||
pending()
|
||||
@@ -146,7 +99,7 @@ def preHook(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs):
|
||||
ui.debug(b"mercurial does not support currenttransation")
|
||||
# do nothing
|
||||
|
||||
return callback(ui, repo, hooktype, node)
|
||||
return callback(ui, repo, "PRE_RECEIVE", node)
|
||||
|
||||
def postHook(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs):
|
||||
return callback(ui, repo, hooktype, node)
|
||||
def post_hook(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs):
|
||||
return callback(ui, repo, "POST_RECEIVE", node)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import org.junit.Test;
|
||||
@@ -52,7 +52,6 @@ public class HgConfigDtoToHgConfigMapperTest {
|
||||
assertEquals("/etc/", config.getPythonPath());
|
||||
assertTrue(config.isShowRevisionInId());
|
||||
assertTrue(config.isUseOptimizedBytecode());
|
||||
assertTrue(config.isDisableHookSSLValidation());
|
||||
assertTrue(config.isEnableHttpPostArgs());
|
||||
}
|
||||
|
||||
@@ -65,7 +64,6 @@ public class HgConfigDtoToHgConfigMapperTest {
|
||||
configDto.setPythonPath("/etc/");
|
||||
configDto.setShowRevisionInId(true);
|
||||
configDto.setUseOptimizedBytecode(true);
|
||||
configDto.setDisableHookSSLValidation(true);
|
||||
configDto.setEnableHttpPostArgs(true);
|
||||
|
||||
return configDto;
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.repository.hooks.HookEnvironment;
|
||||
import sonia.scm.repository.hooks.HookServer;
|
||||
import sonia.scm.security.AccessToken;
|
||||
import sonia.scm.security.AccessTokenBuilderFactory;
|
||||
import sonia.scm.security.CipherUtil;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static sonia.scm.repository.DefaultHgEnvironmentBuilder.*;
|
||||
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class DefaultHgEnvironmentBuilderTest {
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private AccessTokenBuilderFactory accessTokenBuilderFactory;
|
||||
|
||||
@Mock
|
||||
private HgRepositoryHandler repositoryHandler;
|
||||
|
||||
@Mock
|
||||
private HookEnvironment hookEnvironment;
|
||||
|
||||
@Mock
|
||||
private HookServer server;
|
||||
|
||||
@InjectMocks
|
||||
private DefaultHgEnvironmentBuilder builder;
|
||||
|
||||
private Path directory;
|
||||
|
||||
@BeforeEach
|
||||
void setBaseDir(@TempDir Path directory) {
|
||||
this.directory = directory;
|
||||
TempSCMContextProvider context = (TempSCMContextProvider) SCMContext.getContext();
|
||||
context.setBaseDirectory(directory.resolve("home").toFile());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnReadEnvironment() {
|
||||
Repository heartOfGold = prepareForRead("/usr/lib/python", "42");
|
||||
|
||||
Map<String, String> env = builder.read(heartOfGold);
|
||||
assertReadEnv(env, "/usr/lib/python", "42");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnWriteEnvironment() throws IOException {
|
||||
Repository heartOfGold = prepareForWrite("/opt/python", "21");
|
||||
|
||||
Map<String, String> env = builder.write(heartOfGold);
|
||||
assertReadEnv(env, "/opt/python", "21");
|
||||
|
||||
String bearer = CipherUtil.getInstance().decode(env.get(ENV_BEARER_TOKEN));
|
||||
assertThat(bearer).isEqualTo("secretAC");
|
||||
assertThat(env)
|
||||
.containsEntry(ENV_CHALLENGE, "challenge")
|
||||
.containsEntry(ENV_HOOK_PORT, "2042");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowIllegalStateIfServerCouldNotBeStarted() throws IOException {
|
||||
when(server.start()).thenThrow(new IOException("failed to start"));
|
||||
Repository repository = prepareForRead("/usr", "42");
|
||||
assertThrows(IllegalStateException.class, () -> builder.write(repository));
|
||||
}
|
||||
|
||||
private Repository prepareForWrite(String pythonPath, String id) throws IOException {
|
||||
Repository heartOfGold = prepareForRead(pythonPath, id);
|
||||
applyAccessToken("secretAC");
|
||||
when(server.start()).thenReturn(2042);
|
||||
when(hookEnvironment.getChallenge()).thenReturn("challenge");
|
||||
return heartOfGold;
|
||||
}
|
||||
|
||||
private void applyAccessToken(String compact) {
|
||||
AccessToken accessToken = mock(AccessToken.class);
|
||||
when(accessTokenBuilderFactory.create().build()).thenReturn(accessToken);
|
||||
when(accessToken.compact()).thenReturn(compact);
|
||||
}
|
||||
|
||||
|
||||
private void assertReadEnv(Map<String, String> env, String pythonPath, String repositoryId) {
|
||||
assertThat(env)
|
||||
.containsEntry(ENV_REPOSITORY_ID, repositoryId)
|
||||
.containsEntry(ENV_REPOSITORY_NAME, "hitchhiker/HeartOfGold")
|
||||
.containsEntry(ENV_HTTP_POST_ARGS, "false")
|
||||
.containsEntry(ENV_REPOSITORY_PATH, directory.resolve("repo").toAbsolutePath().toString())
|
||||
.containsEntry(ENV_PYTHON_PATH, pythonPath + File.pathSeparator + directory.resolve("home/lib/python"));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private Repository prepareForRead(String pythonPath, String id) {
|
||||
when(repositoryHandler.getDirectory(id)).thenReturn(directory.resolve("repo").toFile());
|
||||
|
||||
HgConfig config = new HgConfig();
|
||||
config.setPythonPath(pythonPath);
|
||||
when(repositoryHandler.getConfig()).thenReturn(config);
|
||||
|
||||
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
|
||||
heartOfGold.setId(id);
|
||||
|
||||
return heartOfGold;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,30 +21,20 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class HgVersionHandler extends AbstractHgHandler
|
||||
{
|
||||
|
||||
public HgVersionHandler(HgRepositoryHandler handler, HgContext context,
|
||||
File directory)
|
||||
{
|
||||
super(handler, context, null, directory);
|
||||
public class EmptyHgEnvironmentBuilder implements HgEnvironmentBuilder {
|
||||
@Override
|
||||
public Map<String, String> read(Repository repository) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
public HgVersion getVersion() throws IOException {
|
||||
return getResultFromScript(HgVersion.class, HgPythonScript.VERSION);
|
||||
@Override
|
||||
public Map<String, String> write(Repository repository) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
@@ -1,111 +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;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.OutOfScopeException;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.ProvisionException;
|
||||
import com.google.inject.Scope;
|
||||
import com.google.inject.servlet.RequestScoped;
|
||||
import com.google.inject.util.Providers;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class HgContextProviderTest {
|
||||
|
||||
@Mock
|
||||
private Scope scope;
|
||||
|
||||
@Test
|
||||
void shouldThrowNonOutOfScopeProvisionExceptions() {
|
||||
Provider<HgContextRequestStore> provider = () -> {
|
||||
throw new RuntimeException("something different");
|
||||
};
|
||||
|
||||
when(scope.scope(any(Key.class), any(Provider.class))).thenReturn(provider);
|
||||
|
||||
Injector injector = Guice.createInjector(new HgContextModule(scope));
|
||||
|
||||
assertThrows(ProvisionException.class, () -> injector.getInstance(HgContext.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateANewInstanceIfOutOfRequestScope() {
|
||||
Provider<HgContextRequestStore> provider = () -> {
|
||||
throw new OutOfScopeException("no request");
|
||||
};
|
||||
when(scope.scope(any(Key.class), any(Provider.class))).thenReturn(provider);
|
||||
|
||||
Injector injector = Guice.createInjector(new HgContextModule(scope));
|
||||
|
||||
HgContext contextOne = injector.getInstance(HgContext.class);
|
||||
HgContext contextTwo = injector.getInstance(HgContext.class);
|
||||
|
||||
assertThat(contextOne).isNotSameAs(contextTwo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInjectFromRequestScope() {
|
||||
HgContextRequestStore requestStore = new HgContextRequestStore();
|
||||
Provider<HgContextRequestStore> provider = Providers.of(requestStore);
|
||||
|
||||
when(scope.scope(any(Key.class), any(Provider.class))).thenReturn(provider);
|
||||
|
||||
Injector injector = Guice.createInjector(new HgContextModule(scope));
|
||||
|
||||
HgContext contextOne = injector.getInstance(HgContext.class);
|
||||
HgContext contextTwo = injector.getInstance(HgContext.class);
|
||||
|
||||
assertThat(contextOne).isSameAs(contextTwo);
|
||||
}
|
||||
|
||||
private static class HgContextModule extends AbstractModule {
|
||||
|
||||
private Scope scope;
|
||||
|
||||
private HgContextModule(Scope scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bindScope(RequestScoped.class, scope);
|
||||
bind(HgContextRequestStore.class);
|
||||
bind(HgContext.class).toProvider(HgContextProvider.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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.Repository;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.repository.hooks.HookEnvironment;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class HgRepositoryFactoryTest {
|
||||
|
||||
private HgRepositoryHandler handler;
|
||||
|
||||
@Mock
|
||||
private HookEnvironment hookEnvironment;
|
||||
|
||||
@Mock
|
||||
private HgEnvironmentBuilder environmentBuilder;
|
||||
|
||||
private HgRepositoryFactory factory;
|
||||
|
||||
private sonia.scm.repository.Repository heartOfGold;
|
||||
|
||||
@BeforeEach
|
||||
void setUpFactory(@TempDir Path directory) {
|
||||
handler = HgTestUtil.createHandler(directory.toFile());
|
||||
assumeTrue(handler.isConfigured());
|
||||
|
||||
factory = new HgRepositoryFactory(handler, hookEnvironment, environmentBuilder);
|
||||
heartOfGold = createRepository();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldOpenRepositoryForRead() {
|
||||
Repository repository = factory.openForRead(heartOfGold);
|
||||
|
||||
assertThat(repository).isNotNull();
|
||||
verify(environmentBuilder).read(heartOfGold);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldOpenRepositoryForWrite() {
|
||||
Repository repository = factory.openForWrite(heartOfGold);
|
||||
|
||||
assertThat(repository).isNotNull();
|
||||
verify(environmentBuilder).write(heartOfGold);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFallbackToUTF8OnUnknownEncoding() {
|
||||
handler.getConfig().setEncoding("unknown");
|
||||
|
||||
Repository repository = factory.openForRead(heartOfGold);
|
||||
|
||||
assertThat(repository.getBaseRepository().getConfiguration().getEncoding()).isEqualTo(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSetPendingChangesetState() {
|
||||
when(hookEnvironment.isPending()).thenReturn(true);
|
||||
|
||||
Repository repository = factory.openForRead(heartOfGold);
|
||||
|
||||
assertThat(repository.getBaseRepository().getConfiguration().isEnablePendingChangesets())
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPassEnvironment() {
|
||||
when(environmentBuilder.read(heartOfGold)).thenReturn(ImmutableMap.of("spaceship", "heartOfGold"));
|
||||
|
||||
Repository repository = factory.openForRead(heartOfGold);
|
||||
|
||||
assertThat(repository.getBaseRepository().getConfiguration().getEnvironment())
|
||||
.containsEntry("spaceship", "heartOfGold");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private sonia.scm.repository.Repository createRepository() {
|
||||
sonia.scm.repository.Repository heartOfGold = RepositoryTestData.createHeartOfGold("hg");
|
||||
heartOfGold.setId("42");
|
||||
|
||||
handler.create(heartOfGold);
|
||||
return heartOfGold;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
@@ -32,13 +32,17 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.repository.spi.HgVersionCommand;
|
||||
import sonia.scm.store.ConfigurationStoreFactory;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
@@ -52,9 +56,6 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
||||
@Mock
|
||||
private ConfigurationStoreFactory factory;
|
||||
|
||||
@Mock
|
||||
private com.google.inject.Provider<HgContext> provider;
|
||||
|
||||
@Override
|
||||
protected void checkDirectory(File directory) {
|
||||
File hgDirectory = new File(directory, ".hg");
|
||||
@@ -70,7 +71,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
||||
|
||||
@Override
|
||||
protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, RepositoryLocationResolver locationResolver, File directory) {
|
||||
HgRepositoryHandler handler = new HgRepositoryHandler(factory, new HgContextProvider(), locationResolver, null, null);
|
||||
HgRepositoryHandler handler = new HgRepositoryHandler(factory, locationResolver, null, null);
|
||||
|
||||
handler.init(contextProvider);
|
||||
HgTestUtil.checkForSkip(handler);
|
||||
@@ -80,7 +81,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
||||
|
||||
@Test
|
||||
public void getDirectory() {
|
||||
HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, provider, locationResolver, null, null);
|
||||
HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, locationResolver, null, null);
|
||||
|
||||
HgConfig hgConfig = new HgConfig();
|
||||
hgConfig.setHgBinary("hg");
|
||||
@@ -91,4 +92,20 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
||||
File path = repositoryHandler.getDirectory(repository.getId());
|
||||
assertEquals(repoPath.toString() + File.separator + RepositoryDirectoryHandler.REPOSITORIES_NATIVE_DIRECTORY, path.getAbsolutePath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnVersionInformation() {
|
||||
PluginLoader pluginLoader = mock(PluginLoader.class);
|
||||
when(pluginLoader.getUberClassLoader()).thenReturn(HgRepositoryHandler.class.getClassLoader());
|
||||
|
||||
HgVersionCommand versionCommand = mock(HgVersionCommand.class);
|
||||
when(versionCommand.get()).thenReturn(new HgVersion("5.2.0", "3.7.2"));
|
||||
|
||||
HgRepositoryHandler handler = new HgRepositoryHandler(
|
||||
factory, locationResolver, pluginLoader, null
|
||||
);
|
||||
|
||||
String versionInformation = handler.getVersionInformation(versionCommand);
|
||||
assertThat(versionInformation).startsWith("scm-hg-version/").endsWith("python/3.7.2 mercurial/5.2.0");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
@@ -29,16 +29,13 @@ package sonia.scm.repository;
|
||||
import org.junit.Assume;
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.TempDirRepositoryLocationResolver;
|
||||
import sonia.scm.security.AccessToken;
|
||||
import sonia.scm.repository.hooks.HookEnvironment;
|
||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
@@ -80,49 +77,26 @@ public final class HgTestUtil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param directory
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static HgRepositoryHandler createHandler(File directory) {
|
||||
TempSCMContextProvider context =
|
||||
(TempSCMContextProvider) SCMContext.getContext();
|
||||
public static HgRepositoryHandler createHandler(File directory) {
|
||||
TempSCMContextProvider context = (TempSCMContextProvider) SCMContext.getContext();
|
||||
|
||||
context.setBaseDirectory(directory);
|
||||
|
||||
RepositoryDAO repoDao = mock(RepositoryDAO.class);
|
||||
|
||||
RepositoryLocationResolver repositoryLocationResolver = new TempDirRepositoryLocationResolver(directory);
|
||||
HgRepositoryHandler handler =
|
||||
new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver, null, null);
|
||||
HgRepositoryHandler handler = new HgRepositoryHandler(
|
||||
new InMemoryConfigurationStoreFactory(),
|
||||
repositoryLocationResolver,
|
||||
null,
|
||||
null
|
||||
);
|
||||
handler.init(context);
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static HgHookManager createHookManager()
|
||||
{
|
||||
HgHookManager hookManager = mock(HgHookManager.class);
|
||||
|
||||
when(hookManager.getChallenge()).thenReturn("challenge");
|
||||
when(hookManager.createUrl()).thenReturn(
|
||||
"http://localhost:8081/scm/hook/hg/");
|
||||
when(hookManager.createUrl(any(HttpServletRequest.class))).thenReturn(
|
||||
"http://localhost:8081/scm/hook/hg/");
|
||||
AccessToken accessToken = mock(AccessToken.class);
|
||||
when(accessToken.compact()).thenReturn("");
|
||||
when(hookManager.getAccessToken()).thenReturn(accessToken);
|
||||
|
||||
return hookManager;
|
||||
public static HgRepositoryFactory createFactory(HgRepositoryHandler handler, File directory) {
|
||||
return new HgRepositoryFactory(
|
||||
handler, new HookEnvironment(), new EmptyHgEnvironmentBuilder(), repository -> directory
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
package sonia.scm.repository.hooks;
|
||||
|
||||
import com.google.inject.util.Providers;
|
||||
import org.apache.shiro.authc.AuthenticationException;
|
||||
import org.apache.shiro.authc.AuthenticationToken;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
@@ -35,10 +34,10 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.repository.RepositoryHookType;
|
||||
import sonia.scm.repository.api.HgHookMessage;
|
||||
import sonia.scm.repository.api.HgHookMessageProvider;
|
||||
import sonia.scm.repository.spi.HgHookContextProvider;
|
||||
import sonia.scm.repository.spi.HookEventFacade;
|
||||
import sonia.scm.security.CipherUtil;
|
||||
@@ -48,6 +47,7 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -55,10 +55,13 @@ import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class HgHookHandlerTest {
|
||||
class DefaultHookHandlerTest {
|
||||
|
||||
@Mock
|
||||
private HgRepositoryHandler repositoryHandler;
|
||||
private HookContextProviderFactory hookContextProviderFactory;
|
||||
|
||||
@Mock
|
||||
private HgHookContextProvider contextProvider;
|
||||
|
||||
@Mock
|
||||
private HookEventFacade hookEventFacade;
|
||||
@@ -71,7 +74,7 @@ class HgHookHandlerTest {
|
||||
|
||||
private HookEnvironment hookEnvironment;
|
||||
|
||||
private HgHookHandler handler;
|
||||
private DefaultHookHandler handler;
|
||||
|
||||
@Mock
|
||||
private Subject subject;
|
||||
@@ -81,11 +84,13 @@ class HgHookHandlerTest {
|
||||
ThreadContext.bind(subject);
|
||||
|
||||
hookEnvironment = new HookEnvironment();
|
||||
DefaultHookHandlerFactory factory = new DefaultHookHandlerFactory(
|
||||
repositoryHandler, hookEventFacade, Providers.of(hookEnvironment)
|
||||
);
|
||||
|
||||
handler = factory.create(socket);
|
||||
handler = new DefaultHookHandler(hookContextProviderFactory, hookEventFacade, hookEnvironment, socket);
|
||||
}
|
||||
|
||||
private void mockMessageProvider() {
|
||||
when(hookContextProviderFactory.create("42", "abc")).thenReturn(contextProvider);
|
||||
when(contextProvider.getHgMessageProvider()).thenReturn(new HgHookMessageProvider());
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@@ -95,10 +100,11 @@ class HgHookHandlerTest {
|
||||
|
||||
@Test
|
||||
void shouldFireHook() throws IOException {
|
||||
mockMessageProvider();
|
||||
when(hookEventFacade.handle("42")).thenReturn(hookEventHandler);
|
||||
|
||||
HgHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE);
|
||||
HgHookHandler.Response response = send(request);
|
||||
DefaultHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE);
|
||||
DefaultHookHandler.Response response = send(request);
|
||||
|
||||
assertSuccess(response, RepositoryHookType.POST_RECEIVE);
|
||||
assertThat(hookEnvironment.isPending()).isFalse();
|
||||
@@ -106,13 +112,22 @@ class HgHookHandlerTest {
|
||||
|
||||
@Test
|
||||
void shouldSetPendingStateOnPreReceiveHooks() throws IOException {
|
||||
mockMessageProvider();
|
||||
when(hookEventFacade.handle("42")).thenReturn(hookEventHandler);
|
||||
|
||||
HgHookHandler.Request request = createRequest(RepositoryHookType.PRE_RECEIVE);
|
||||
HgHookHandler.Response response = send(request);
|
||||
// we have to capture the pending state, when the hook is fired
|
||||
// because the state is cleared before the method ends
|
||||
AtomicReference<Boolean> ref = new AtomicReference<>(Boolean.FALSE);
|
||||
doAnswer(ic -> {
|
||||
ref.set(hookEnvironment.isPending());
|
||||
return null;
|
||||
}).when(hookEventHandler).fireHookEvent(RepositoryHookType.PRE_RECEIVE, contextProvider);
|
||||
|
||||
DefaultHookHandler.Request request = createRequest(RepositoryHookType.PRE_RECEIVE);
|
||||
DefaultHookHandler.Response response = send(request);
|
||||
|
||||
assertSuccess(response, RepositoryHookType.PRE_RECEIVE);
|
||||
assertThat(hookEnvironment.isPending()).isTrue();
|
||||
assertThat(ref.get()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -121,8 +136,8 @@ class HgHookHandlerTest {
|
||||
.when(hookEventFacade)
|
||||
.handle("42");
|
||||
|
||||
HgHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE);
|
||||
HgHookHandler.Response response = send(request);
|
||||
DefaultHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE);
|
||||
DefaultHookHandler.Response response = send(request);
|
||||
|
||||
assertError(response, "unknown");
|
||||
}
|
||||
@@ -133,28 +148,40 @@ class HgHookHandlerTest {
|
||||
.when(subject)
|
||||
.login(any(AuthenticationToken.class));
|
||||
|
||||
HgHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE);
|
||||
HgHookHandler.Response response = send(request);
|
||||
DefaultHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE);
|
||||
DefaultHookHandler.Response response = send(request);
|
||||
|
||||
assertError(response, "authentication");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleNotFoundException() throws IOException {
|
||||
doThrow(NotFoundException.class)
|
||||
.when(hookEventFacade)
|
||||
.handle("42");
|
||||
|
||||
DefaultHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE);
|
||||
DefaultHookHandler.Response response = send(request);
|
||||
|
||||
assertError(response, "not found");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnErrorWithInvalidChallenge() throws IOException {
|
||||
HgHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE, "something-different");
|
||||
HgHookHandler.Response response = send(request);
|
||||
DefaultHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE, "something-different");
|
||||
DefaultHookHandler.Response response = send(request);
|
||||
|
||||
assertError(response, "challenge");
|
||||
}
|
||||
|
||||
private void assertSuccess(HgHookHandler.Response response, RepositoryHookType type) {
|
||||
private void assertSuccess(DefaultHookHandler.Response response, RepositoryHookType type) {
|
||||
assertThat(response.getMessages()).isEmpty();
|
||||
assertThat(response.isAbort()).isFalse();
|
||||
|
||||
verify(hookEventHandler).fireHookEvent(eq(type), any(HgHookContextProvider.class));
|
||||
}
|
||||
|
||||
private void assertError(HgHookHandler.Response response, String message) {
|
||||
private void assertError(DefaultHookHandler.Response response, String message) {
|
||||
assertThat(response.isAbort()).isTrue();
|
||||
assertThat(response.getMessages()).hasSize(1);
|
||||
HgHookMessage hgHookMessage = response.getMessages().get(0);
|
||||
@@ -163,19 +190,19 @@ class HgHookHandlerTest {
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private HgHookHandler.Request createRequest(RepositoryHookType type) {
|
||||
private DefaultHookHandler.Request createRequest(RepositoryHookType type) {
|
||||
return createRequest(type, hookEnvironment.getChallenge());
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private HgHookHandler.Request createRequest(RepositoryHookType type, String challenge) {
|
||||
private DefaultHookHandler.Request createRequest(RepositoryHookType type, String challenge) {
|
||||
String secret = CipherUtil.getInstance().encode("secret");
|
||||
return new HgHookHandler.Request(
|
||||
return new DefaultHookHandler.Request(
|
||||
secret, type, "42", challenge, "abc"
|
||||
);
|
||||
}
|
||||
|
||||
private HgHookHandler.Response send(HgHookHandler.Request request) throws IOException {
|
||||
private DefaultHookHandler.Response send(DefaultHookHandler.Request request) throws IOException {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
Sockets.send(buffer, request);
|
||||
ByteArrayInputStream input = new ByteArrayInputStream(buffer.toByteArray());
|
||||
@@ -185,7 +212,7 @@ class HgHookHandlerTest {
|
||||
|
||||
handler.run();
|
||||
|
||||
return Sockets.read(new ByteArrayInputStream(output.toByteArray()), HgHookHandler.Response.class);
|
||||
return Sockets.read(new ByteArrayInputStream(output.toByteArray()), DefaultHookHandler.Response.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,58 +21,50 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
package sonia.scm.repository.hooks;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.security.AccessToken;
|
||||
import sonia.scm.security.Xsrf;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.repository.HgRepositoryFactory;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.RepositoryManager;
|
||||
import sonia.scm.repository.RepositoryTestData;
|
||||
import sonia.scm.repository.spi.HgHookContextProvider;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class HgEnvironmentTest {
|
||||
class HookContextProviderFactoryTest {
|
||||
|
||||
@Mock
|
||||
HgRepositoryHandler handler;
|
||||
private RepositoryManager repositoryManager;
|
||||
|
||||
@Mock
|
||||
HgHookManager hookManager;
|
||||
private HgRepositoryHandler repositoryHandler;
|
||||
|
||||
@Mock
|
||||
private HgRepositoryFactory repositoryFactory;
|
||||
|
||||
@InjectMocks
|
||||
private HookContextProviderFactory factory;
|
||||
|
||||
@Test
|
||||
void shouldExtractXsrfTokenWhenSet() {
|
||||
AccessToken accessToken = mock(AccessToken.class);
|
||||
when(accessToken.compact()).thenReturn("");
|
||||
when(accessToken.getCustom(Xsrf.TOKEN_KEY)).thenReturn(of("XSRF Token"));
|
||||
when(hookManager.getAccessToken()).thenReturn(accessToken);
|
||||
|
||||
Map<String, String> environment = new HashMap<>();
|
||||
HgEnvironment.prepareEnvironment(environment, handler, hookManager);
|
||||
|
||||
assertThat(environment).contains(entry("SCM_XSRF", "XSRF Token"));
|
||||
void shouldCreateHookContextProvider() {
|
||||
when(repositoryManager.get("42")).thenReturn(RepositoryTestData.create42Puzzle());
|
||||
HgHookContextProvider provider = factory.create("42", "xyz");
|
||||
assertThat(provider).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIgnoreXsrfWhenNotSetButStillContainDummy() {
|
||||
AccessToken accessToken = mock(AccessToken.class);
|
||||
when(accessToken.compact()).thenReturn("");
|
||||
when(accessToken.getCustom(Xsrf.TOKEN_KEY)).thenReturn(empty());
|
||||
when(hookManager.getAccessToken()).thenReturn(accessToken);
|
||||
|
||||
Map<String, String> environment = new HashMap<>();
|
||||
HgEnvironment.prepareEnvironment(environment, handler, hookManager);
|
||||
|
||||
assertThat(environment).containsKeys("SCM_XSRF");
|
||||
void shouldThrowNotFoundExceptionWithoutRepository() {
|
||||
assertThrows(NotFoundException.class, () -> factory.create("42", "xyz"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -89,7 +89,7 @@ class HookServerTest {
|
||||
}
|
||||
}
|
||||
|
||||
public static class HelloHandler implements Runnable {
|
||||
public static class HelloHandler implements HookHandler {
|
||||
|
||||
private final Socket socket;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* 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 --------------------------------------------------------
|
||||
@@ -29,13 +29,16 @@ package sonia.scm.repository.spi;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
import sonia.scm.repository.HgRepositoryFactory;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.HgTestUtil;
|
||||
import sonia.scm.repository.RepositoryTestData;
|
||||
import sonia.scm.repository.hooks.HookEnvironment;
|
||||
import sonia.scm.util.MockUtil;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
@@ -49,31 +52,22 @@ public class AbstractHgCommandTestBase extends ZippedRepositoryTestBase
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@After
|
||||
public void close() throws IOException
|
||||
{
|
||||
public void close() {
|
||||
if (cmdContext != null)
|
||||
{
|
||||
cmdContext.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Before
|
||||
public void initHgHandler() throws IOException {
|
||||
this.handler = HgTestUtil.createHandler(tempFolder.newFolder());
|
||||
|
||||
HgTestUtil.checkForSkip(handler);
|
||||
|
||||
cmdContext = new HgCommandContext(HgTestUtil.createHookManager(), handler,
|
||||
RepositoryTestData.createHeartOfGold(), repositoryDirectory);
|
||||
HgRepositoryFactory factory = HgTestUtil.createFactory(handler, repositoryDirectory);
|
||||
cmdContext = new HgCommandContext(handler, factory, RepositoryTestData.createHeartOfGold());
|
||||
}
|
||||
|
||||
//~--- set methods ----------------------------------------------------------
|
||||
|
||||
@@ -29,12 +29,10 @@ import com.google.inject.util.Providers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import sonia.scm.repository.Branch;
|
||||
import sonia.scm.repository.HgTestUtil;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.api.BranchRequest;
|
||||
import sonia.scm.repository.work.NoneCachingWorkingCopyPool;
|
||||
import sonia.scm.repository.work.WorkdirProvider;
|
||||
import sonia.scm.web.HgRepositoryEnvironmentBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -47,10 +45,8 @@ public class HgBranchCommandTest extends AbstractHgCommandTestBase {
|
||||
|
||||
@Before
|
||||
public void initWorkingCopyFactory() {
|
||||
HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder =
|
||||
new HgRepositoryEnvironmentBuilder(handler, HgTestUtil.createHookManager());
|
||||
|
||||
workingCopyFactory = new SimpleHgWorkingCopyFactory(Providers.of(hgRepositoryEnvironmentBuilder), new NoneCachingWorkingCopyPool(new WorkdirProvider())) {
|
||||
workingCopyFactory = new SimpleHgWorkingCopyFactory(new NoneCachingWorkingCopyPool(new WorkdirProvider())) {
|
||||
@Override
|
||||
public void configure(PullCommand pullCommand) {
|
||||
// we do not want to configure http hooks in this unit test
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* 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 --------------------------------------------------------
|
||||
@@ -110,17 +110,10 @@ public class HgIncomingCommandTest extends IncomingOutgoingTestBase
|
||||
cmd.getIncomingChangesets(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private HgIncomingCommand createIncomingCommand()
|
||||
{
|
||||
private HgIncomingCommand createIncomingCommand() {
|
||||
return new HgIncomingCommand(
|
||||
new HgCommandContext(
|
||||
HgTestUtil.createHookManager(), handler, incomingRepository,
|
||||
incomingDirectory), handler);
|
||||
new HgCommandContext(handler, HgTestUtil.createFactory(handler, incomingDirectory), incomingRepository),
|
||||
handler
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,12 +40,11 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
||||
|
||||
|
||||
private HgModificationsCommand outgoingModificationsCommand;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
HgCommandContext outgoingContext = new HgCommandContext(HgTestUtil.createHookManager(), handler, outgoingRepository, outgoingDirectory);
|
||||
HgCommandContext outgoingContext = new HgCommandContext(handler, HgTestUtil.createFactory(handler, outgoingDirectory), outgoingRepository);
|
||||
outgoingModificationsCommand = new HgModificationsCommand(outgoingContext);
|
||||
}
|
||||
|
||||
@@ -116,10 +115,10 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
||||
assertThat(modifications).isNotNull();
|
||||
assertThat(modifications.getAdded())
|
||||
.as("added files modifications")
|
||||
.hasSize(0);
|
||||
.isEmpty();
|
||||
assertThat(modifications.getModified())
|
||||
.as("modified files modifications")
|
||||
.hasSize(0);
|
||||
.isEmpty();
|
||||
assertThat(modifications.getRemoved())
|
||||
.as("removed files modifications")
|
||||
.hasSize(1)
|
||||
@@ -136,10 +135,10 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
||||
assertThat(modifications).isNotNull();
|
||||
assertThat(modifications.getAdded())
|
||||
.as("added files modifications")
|
||||
.hasSize(0);
|
||||
.isEmpty();
|
||||
assertThat(modifications.getModified())
|
||||
.as("modified files modifications")
|
||||
.hasSize(0);
|
||||
.isEmpty();
|
||||
assertThat(modifications.getRemoved())
|
||||
.as("removed files modifications")
|
||||
.isEmpty();
|
||||
@@ -161,10 +160,10 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
||||
assertThat(modifications).isNotNull();
|
||||
assertThat(modifications.getAdded())
|
||||
.as("added files modifications")
|
||||
.hasSize(0);
|
||||
.isEmpty();
|
||||
assertThat(modifications.getModified())
|
||||
.as("modified files modifications")
|
||||
.hasSize(0);
|
||||
.isEmpty();
|
||||
assertThat(modifications.getRemoved())
|
||||
.as("removed files modifications")
|
||||
.isEmpty();
|
||||
@@ -189,7 +188,7 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
||||
assertThat(modifications).isNotNull();
|
||||
assertThat(modifications.getAdded())
|
||||
.as("added files modifications")
|
||||
.hasSize(0);
|
||||
.isEmpty();
|
||||
assertThat(modifications.getModified())
|
||||
.as("modified files modifications")
|
||||
.hasSize(1)
|
||||
@@ -197,10 +196,10 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
||||
.containsOnly(file);
|
||||
assertThat(modifications.getRemoved())
|
||||
.as("removed files modifications")
|
||||
.hasSize(0);
|
||||
.isEmpty();
|
||||
assertThat(modifications.getRenamed())
|
||||
.as("renamed files modifications")
|
||||
.hasSize(0);
|
||||
.isEmpty();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -214,13 +213,13 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
||||
.containsOnly(addedFile);
|
||||
assertThat(modifications.getModified())
|
||||
.as("modified files modifications")
|
||||
.hasSize(0);
|
||||
.isEmpty();
|
||||
assertThat(modifications.getRemoved())
|
||||
.as("removed files modifications")
|
||||
.hasSize(0);
|
||||
.isEmpty();
|
||||
assertThat(modifications.getRenamed())
|
||||
.as("renamed files modifications")
|
||||
.hasSize(0);
|
||||
.isEmpty();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import com.google.inject.util.Providers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
@@ -32,12 +31,9 @@ import org.junit.rules.TemporaryFolder;
|
||||
import sonia.scm.AlreadyExistsException;
|
||||
import sonia.scm.NoChangesMadeException;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.repository.HgHookManager;
|
||||
import sonia.scm.repository.HgTestUtil;
|
||||
import sonia.scm.repository.Person;
|
||||
import sonia.scm.repository.work.NoneCachingWorkingCopyPool;
|
||||
import sonia.scm.repository.work.WorkdirProvider;
|
||||
import sonia.scm.web.HgRepositoryEnvironmentBuilder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
@@ -54,9 +50,7 @@ public class HgModifyCommandTest extends AbstractHgCommandTestBase {
|
||||
|
||||
@Before
|
||||
public void initHgModifyCommand() {
|
||||
HgHookManager hookManager = HgTestUtil.createHookManager();
|
||||
HgRepositoryEnvironmentBuilder environmentBuilder = new HgRepositoryEnvironmentBuilder(handler, hookManager);
|
||||
SimpleHgWorkingCopyFactory workingCopyFactory = new SimpleHgWorkingCopyFactory(Providers.of(environmentBuilder), new NoneCachingWorkingCopyPool(new WorkdirProvider())) {
|
||||
SimpleHgWorkingCopyFactory workingCopyFactory = new SimpleHgWorkingCopyFactory(new NoneCachingWorkingCopyPool(new WorkdirProvider())) {
|
||||
@Override
|
||||
public void configure(com.aragost.javahg.commands.PullCommand pullCommand) {
|
||||
// we do not want to configure http hooks in this unit test
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* 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 --------------------------------------------------------
|
||||
@@ -106,17 +106,10 @@ public class HgOutgoingCommandTest extends IncomingOutgoingTestBase
|
||||
System.out.println(cpr.getChangesets());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private HgOutgoingCommand createOutgoingCommand()
|
||||
{
|
||||
private HgOutgoingCommand createOutgoingCommand() {
|
||||
return new HgOutgoingCommand(
|
||||
new HgCommandContext(
|
||||
HgTestUtil.createHookManager(), handler, outgoingRepository,
|
||||
outgoingDirectory), handler);
|
||||
new HgCommandContext(handler, HgTestUtil.createFactory(handler, outgoingDirectory), outgoingRepository),
|
||||
handler
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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.base.Joiner;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.repository.HgVersion;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class HgVersionCommandTest {
|
||||
|
||||
private static final String PYTHON_OUTPUT = String.join("\n",
|
||||
"3.9.0 (default, Oct 27 2020, 14:15:17)",
|
||||
"[Clang 12.0.0 (clang-1200.0.32.21)]"
|
||||
);
|
||||
|
||||
private Map<String, Process> outputs;
|
||||
|
||||
@BeforeEach
|
||||
void setUpOutputs() {
|
||||
outputs = new HashMap<>();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnHgVersion() {
|
||||
command("/usr/local/bin/hg", HgVersionCommand.HG_ARGS, "5.5.2", 0);
|
||||
command("/opt/python/bin/python", HgVersionCommand.PYTHON_ARGS, PYTHON_OUTPUT, 0);
|
||||
|
||||
HgVersion hgVersion = getVersion("/usr/local/bin/hg", "/opt/python/bin/python");
|
||||
assertThat(hgVersion.getMercurial()).isEqualTo("5.5.2");
|
||||
assertThat(hgVersion.getPython()).isEqualTo("3.9.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnUnknownMercurialVersionOnNonZeroExitCode() {
|
||||
command("hg", HgVersionCommand.HG_ARGS, "", 1);
|
||||
command("python", HgVersionCommand.PYTHON_ARGS, PYTHON_OUTPUT, 0);
|
||||
|
||||
HgVersion hgVersion = getVersion("hg", "python");
|
||||
assertThat(hgVersion.getMercurial()).isEqualTo(HgVersion.UNKNOWN);
|
||||
assertThat(hgVersion.getPython()).isEqualTo("3.9.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnUnknownPythonVersionOnNonZeroExitCode() {
|
||||
command("hg", HgVersionCommand.HG_ARGS, "4.4.2", 0);
|
||||
command("python", HgVersionCommand.PYTHON_ARGS, "", 1);
|
||||
|
||||
HgVersion hgVersion = getVersion("hg", "python");
|
||||
assertThat(hgVersion.getMercurial()).isEqualTo("4.4.2");
|
||||
assertThat(hgVersion.getPython()).isEqualTo(HgVersion.UNKNOWN);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnUnknownForInvalidPythonOutput() {
|
||||
command("hg", HgVersionCommand.HG_ARGS, "1.0.0", 0);
|
||||
command("python", HgVersionCommand.PYTHON_ARGS, "abcdef", 0);
|
||||
|
||||
HgVersion hgVersion = getVersion("hg", "python");
|
||||
assertThat(hgVersion.getMercurial()).isEqualTo("1.0.0");
|
||||
assertThat(hgVersion.getPython()).isEqualTo(HgVersion.UNKNOWN);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnUnknownForIOException() {
|
||||
HgVersionCommand command = new HgVersionCommand(new HgConfig(), cmd -> {
|
||||
throw new IOException("failed");
|
||||
});
|
||||
|
||||
HgVersion hgVersion = command.get();
|
||||
assertThat(hgVersion.getMercurial()).isEqualTo(HgVersion.UNKNOWN);
|
||||
assertThat(hgVersion.getPython()).isEqualTo(HgVersion.UNKNOWN);
|
||||
}
|
||||
|
||||
private Process command(String command, String[] args, String content, int exitValue) {
|
||||
Process process = mock(Process.class);
|
||||
when(process.getInputStream()).thenReturn(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)));
|
||||
when(process.exitValue()).thenReturn(exitValue);
|
||||
|
||||
List<String> cmdLine = new ArrayList<>();
|
||||
cmdLine.add(command);
|
||||
cmdLine.addAll(Arrays.asList(args));
|
||||
|
||||
outputs.put(Joiner.on(' ').join(cmdLine), process);
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private HgVersion getVersion(String hg, String python) {
|
||||
HgConfig config = new HgConfig();
|
||||
config.setHgBinary(hg);
|
||||
config.setPythonBinary(python);
|
||||
return new HgVersionCommand(config, command -> outputs.get(Joiner.on(' ').join(command))).get();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
* 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 --------------------------------------------------------
|
||||
@@ -39,7 +39,6 @@ import org.junit.Rule;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import sonia.scm.AbstractTestBase;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.repository.HgContext;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.HgTestUtil;
|
||||
import sonia.scm.user.User;
|
||||
@@ -88,7 +87,6 @@ public abstract class IncomingOutgoingTestBase extends AbstractTestBase
|
||||
when(handler.getDirectory(outgoingRepository.getId())).thenReturn(
|
||||
outgoingDirectory);
|
||||
when(handler.getConfig()).thenReturn(temp.getConfig());
|
||||
when(handler.getHgContext()).thenReturn(new HgContext());
|
||||
}
|
||||
|
||||
//~--- set methods ----------------------------------------------------------
|
||||
|
||||
@@ -30,12 +30,11 @@ import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import sonia.scm.repository.HgHookManager;
|
||||
import sonia.scm.repository.HgEnvironmentBuilder;
|
||||
import sonia.scm.repository.HgTestUtil;
|
||||
import sonia.scm.repository.work.SimpleCachingWorkingCopyPool;
|
||||
import sonia.scm.repository.work.WorkdirProvider;
|
||||
import sonia.scm.repository.work.WorkingCopy;
|
||||
import sonia.scm.web.HgRepositoryEnvironmentBuilder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -57,9 +56,7 @@ public class SimpleHgWorkingCopyFactoryTest extends AbstractHgCommandTestBase {
|
||||
@Before
|
||||
public void bindScmProtocol() throws IOException {
|
||||
workdirProvider = new WorkdirProvider(temporaryFolder.newFolder());
|
||||
HgHookManager hookManager = HgTestUtil.createHookManager();
|
||||
HgRepositoryEnvironmentBuilder environmentBuilder = new HgRepositoryEnvironmentBuilder(handler, hookManager);
|
||||
workingCopyFactory = new SimpleHgWorkingCopyFactory(Providers.of(environmentBuilder), new SimpleCachingWorkingCopyPool(workdirProvider)) {
|
||||
workingCopyFactory = new SimpleHgWorkingCopyFactory(new SimpleCachingWorkingCopyPool(workdirProvider)) {
|
||||
@Override
|
||||
public void configure(com.aragost.javahg.commands.PullCommand pullCommand) {
|
||||
// we do not want to configure http hooks in this unit test
|
||||
|
||||
@@ -1,60 +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 org.junit.Test;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static sonia.scm.web.HgHookCallbackServlet.PARAM_REPOSITORYID;
|
||||
|
||||
public class HgHookCallbackServletTest {
|
||||
|
||||
@Test
|
||||
public void shouldExtractCorrectRepositoryId() throws ServletException, IOException {
|
||||
HgRepositoryHandler handler = mock(HgRepositoryHandler.class);
|
||||
HgHookCallbackServlet servlet = new HgHookCallbackServlet(null, handler, null, null);
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||
|
||||
when(request.getContextPath()).thenReturn("http://example.com/scm");
|
||||
when(request.getRequestURI()).thenReturn("http://example.com/scm/hook/hg/pretxnchangegroup");
|
||||
String path = "/tmp/hg/12345";
|
||||
when(request.getParameter(PARAM_REPOSITORYID)).thenReturn(path);
|
||||
|
||||
servlet.doPost(request, response);
|
||||
|
||||
verify(response, never()).sendError(anyInt());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user