mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-13 17:05:43 +01:00
Refactor nearly the whole scm-hg-plugin for new hook implementation
This commit is contained in:
@@ -30,9 +30,10 @@ import lombok.Getter;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@NoArgsConstructor
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@SuppressWarnings("java:S2160") // we don't need equals for dto
|
||||||
public class HgConfigDto extends HalRepresentation {
|
public class HgConfigDto extends HalRepresentation {
|
||||||
|
|
||||||
private boolean disabled;
|
private boolean disabled;
|
||||||
@@ -44,7 +45,6 @@ public class HgConfigDto extends HalRepresentation {
|
|||||||
private boolean useOptimizedBytecode;
|
private boolean useOptimizedBytecode;
|
||||||
private boolean showRevisionInId;
|
private boolean showRevisionInId;
|
||||||
private boolean enableHttpPostArgs;
|
private boolean enableHttpPostArgs;
|
||||||
private boolean disableHookSSLValidation;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -36,20 +36,10 @@ import javax.xml.bind.annotation.XmlTransient;
|
|||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@XmlRootElement(name = "config")
|
@XmlRootElement(name = "config")
|
||||||
public class HgConfig extends RepositoryConfig
|
public class HgConfig extends RepositoryConfig {
|
||||||
{
|
|
||||||
|
|
||||||
public static final String PERMISSION = "hg";
|
public static final String PERMISSION = "hg";
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public HgConfig() {}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@XmlTransient // Only for permission checks, don't serialize to XML
|
@XmlTransient // Only for permission checks, don't serialize to XML
|
||||||
public String getId() {
|
public String getId() {
|
||||||
@@ -123,10 +113,6 @@ public class HgConfig extends RepositoryConfig
|
|||||||
return useOptimizedBytecode;
|
return useOptimizedBytecode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDisableHookSSLValidation() {
|
|
||||||
return disableHookSSLValidation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEnableHttpPostArgs() {
|
public boolean isEnableHttpPostArgs() {
|
||||||
return enableHttpPostArgs;
|
return enableHttpPostArgs;
|
||||||
}
|
}
|
||||||
@@ -216,10 +202,6 @@ public class HgConfig extends RepositoryConfig
|
|||||||
this.useOptimizedBytecode = useOptimizedBytecode;
|
this.useOptimizedBytecode = useOptimizedBytecode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDisableHookSSLValidation(boolean disableHookSSLValidation) {
|
|
||||||
this.disableHookSSLValidation = disableHookSSLValidation;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
@@ -242,9 +224,4 @@ public class HgConfig extends RepositoryConfig
|
|||||||
|
|
||||||
private boolean enableHttpPostArgs = false;
|
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("-"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -24,25 +24,12 @@
|
|||||||
|
|
||||||
package sonia.scm.repository;
|
package sonia.scm.repository;
|
||||||
|
|
||||||
import com.google.inject.servlet.RequestScoped;
|
import com.google.inject.ImplementedBy;
|
||||||
|
|
||||||
/**
|
import java.util.Map;
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@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 --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.ConfigurationException;
|
|
||||||
import sonia.scm.SCMContextProvider;
|
import sonia.scm.SCMContextProvider;
|
||||||
import sonia.scm.autoconfig.AutoConfigurator;
|
import sonia.scm.autoconfig.AutoConfigurator;
|
||||||
import sonia.scm.installer.HgInstaller;
|
import sonia.scm.installer.HgInstaller;
|
||||||
@@ -43,14 +41,14 @@ import sonia.scm.io.INISection;
|
|||||||
import sonia.scm.plugin.Extension;
|
import sonia.scm.plugin.Extension;
|
||||||
import sonia.scm.plugin.PluginLoader;
|
import sonia.scm.plugin.PluginLoader;
|
||||||
import sonia.scm.repository.spi.HgRepositoryServiceProvider;
|
import sonia.scm.repository.spi.HgRepositoryServiceProvider;
|
||||||
|
import sonia.scm.repository.spi.HgVersionCommand;
|
||||||
import sonia.scm.repository.spi.HgWorkingCopyFactory;
|
import sonia.scm.repository.spi.HgWorkingCopyFactory;
|
||||||
import sonia.scm.store.ConfigurationStoreFactory;
|
import sonia.scm.store.ConfigurationStoreFactory;
|
||||||
import sonia.scm.util.IOUtil;
|
import sonia.scm.util.IOUtil;
|
||||||
import sonia.scm.util.SystemUtil;
|
import sonia.scm.util.SystemUtil;
|
||||||
|
|
||||||
import javax.xml.bind.JAXBContext;
|
|
||||||
import javax.xml.bind.JAXBException;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -63,14 +61,15 @@ import java.util.Optional;
|
|||||||
public class HgRepositoryHandler
|
public class HgRepositoryHandler
|
||||||
extends AbstractSimpleRepositoryHandler<HgConfig> {
|
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 RESOURCE_VERSION = "sonia/scm/version/scm-hg-plugin";
|
||||||
public static final String TYPE_DISPLAYNAME = "Mercurial";
|
public static final String TYPE_DISPLAYNAME = "Mercurial";
|
||||||
public static final String TYPE_NAME = "hg";
|
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,
|
TYPE_DISPLAYNAME,
|
||||||
HgRepositoryServiceProvider.COMMANDS,
|
HgRepositoryServiceProvider.COMMANDS,
|
||||||
HgRepositoryServiceProvider.FEATURES);
|
HgRepositoryServiceProvider.FEATURES
|
||||||
|
);
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(HgRepositoryHandler.class);
|
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_SECTION_SCMM = "scmm";
|
||||||
private static final String CONFIG_KEY_REPOSITORY_ID = "repositoryid";
|
private static final String CONFIG_KEY_REPOSITORY_ID = "repositoryid";
|
||||||
|
|
||||||
private final Provider<HgContext> hgContextProvider;
|
|
||||||
|
|
||||||
private final HgWorkingCopyFactory workingCopyFactory;
|
private final HgWorkingCopyFactory workingCopyFactory;
|
||||||
|
|
||||||
private final JAXBContext jaxbContext;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public HgRepositoryHandler(ConfigurationStoreFactory storeFactory,
|
public HgRepositoryHandler(ConfigurationStoreFactory storeFactory,
|
||||||
Provider<HgContext> hgContextProvider,
|
|
||||||
RepositoryLocationResolver repositoryLocationResolver,
|
RepositoryLocationResolver repositoryLocationResolver,
|
||||||
PluginLoader pluginLoader, HgWorkingCopyFactory workingCopyFactory) {
|
PluginLoader pluginLoader, HgWorkingCopyFactory workingCopyFactory) {
|
||||||
super(storeFactory, repositoryLocationResolver, pluginLoader);
|
super(storeFactory, repositoryLocationResolver, pluginLoader);
|
||||||
this.hgContextProvider = hgContextProvider;
|
|
||||||
this.workingCopyFactory = workingCopyFactory;
|
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) {
|
public void doAutoConfiguration(HgConfig autoConfig) {
|
||||||
@@ -107,8 +92,7 @@ public class HgRepositoryHandler
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("installing mercurial with {}",
|
logger.debug("installing mercurial with {}", installer.getClass().getName());
|
||||||
installer.getClass().getName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
installer.install(baseDirectory, autoConfig);
|
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
|
@Override
|
||||||
public ImportHandler getImportHandler() {
|
public ImportHandler getImportHandler() {
|
||||||
return new HgImportHandler(this);
|
return new HgImportHandler(this);
|
||||||
@@ -176,28 +150,14 @@ public class HgRepositoryHandler
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getVersionInformation() {
|
public String getVersionInformation() {
|
||||||
String version = getStringFromResource(RESOURCE_VERSION,
|
return getVersionInformation(new HgVersionCommand(getConfig()));
|
||||||
DEFAULT_VERSION_INFORMATION);
|
}
|
||||||
|
|
||||||
try {
|
String getVersionInformation(HgVersionCommand command) {
|
||||||
HgVersion hgVersion = new HgVersionHandler(this, hgContextProvider.get(),
|
String version = getStringFromResource(RESOURCE_VERSION, DEFAULT_VERSION_INFORMATION);
|
||||||
baseDirectory).getVersion();
|
HgVersion hgVersion = command.get();
|
||||||
|
|
||||||
if (hgVersion != null) {
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("mercurial/python informations: {}", hgVersion);
|
logger.debug("mercurial/python informations: {}", hgVersion);
|
||||||
}
|
return MessageFormat.format(version, hgVersion.getPython(), hgVersion.getMercurial());
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -253,28 +213,24 @@ public class HgRepositoryHandler
|
|||||||
logger.debug("write python script {}", script.getName());
|
logger.debug("write python script {}", script.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
InputStream content = null;
|
try (InputStream content = input(script); OutputStream output = output(context, script)) {
|
||||||
OutputStream output = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
content = HgRepositoryHandler.class.getResourceAsStream(
|
|
||||||
script.getResourcePath());
|
|
||||||
output = new FileOutputStream(script.getFile(context));
|
|
||||||
IOUtil.copy(content, output);
|
IOUtil.copy(content, output);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
logger.error("could not write script", 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() {
|
public HgWorkingCopyFactory getWorkingCopyFactory() {
|
||||||
return workingCopyFactory;
|
return workingCopyFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public JAXBContext getJaxbContext() {
|
|
||||||
return jaxbContext;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,10 +24,8 @@
|
|||||||
|
|
||||||
package sonia.scm.repository;
|
package sonia.scm.repository;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Data;
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
@@ -37,13 +35,14 @@ import javax.xml.bind.annotation.XmlRootElement;
|
|||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
@XmlRootElement(name = "version")
|
@XmlRootElement(name = "version")
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
@EqualsAndHashCode
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@ToString
|
|
||||||
public class HgVersion {
|
public class HgVersion {
|
||||||
|
|
||||||
|
public static final String UNKNOWN = "x.y.z (unknown)";
|
||||||
|
|
||||||
private String mercurial;
|
private String mercurial;
|
||||||
private String python;
|
private String python;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,70 +26,26 @@ package sonia.scm.repository.api;
|
|||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @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;
|
private static final long serialVersionUID = 1804492842452344326L;
|
||||||
|
|
||||||
//~--- constant enums -------------------------------------------------------
|
private Severity severity;
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 String message;
|
private String message;
|
||||||
|
|
||||||
/** Field description */
|
public enum Severity { NOTE, ERROR }
|
||||||
private Severity severity;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.hooks;
|
package sonia.scm.repository.hooks;
|
||||||
|
|
||||||
|
import com.google.inject.assistedinject.Assisted;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
@@ -31,7 +32,7 @@ import org.apache.shiro.authc.AuthenticationException;
|
|||||||
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.subject.Subject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import sonia.scm.NotFoundException;
|
||||||
import sonia.scm.repository.RepositoryHookType;
|
import sonia.scm.repository.RepositoryHookType;
|
||||||
import sonia.scm.repository.api.HgHookMessage;
|
import sonia.scm.repository.api.HgHookMessage;
|
||||||
import sonia.scm.repository.spi.HgHookContextProvider;
|
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.BearerToken;
|
||||||
import sonia.scm.security.CipherUtil;
|
import sonia.scm.security.CipherUtil;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Provider;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
@@ -50,19 +49,20 @@ import java.util.List;
|
|||||||
|
|
||||||
import static java.util.Collections.singletonList;
|
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 HookEventFacade hookEventFacade;
|
||||||
private final Provider<HookEnvironment> environmentProvider;
|
private final HookEnvironment environment;
|
||||||
|
private final HookContextProviderFactory hookContextProviderFactory;
|
||||||
private final Socket socket;
|
private final Socket socket;
|
||||||
|
|
||||||
HgHookHandler(HgRepositoryHandler handler, HookEventFacade hookEventFacade, Provider<HookEnvironment> environmentProvider, Socket socket) {
|
@Inject
|
||||||
this.handler = handler;
|
public DefaultHookHandler(HookContextProviderFactory hookContextProviderFactory, HookEventFacade hookEventFacade, HookEnvironment environment, @Assisted Socket socket) {
|
||||||
|
this.hookContextProviderFactory = hookContextProviderFactory;
|
||||||
this.hookEventFacade = hookEventFacade;
|
this.hookEventFacade = hookEventFacade;
|
||||||
this.environmentProvider = environmentProvider;
|
this.environment = environment;
|
||||||
this.socket = socket;
|
this.socket = socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +84,6 @@ class HgHookHandler implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Response handleHookRequest(Request request) {
|
private Response handleHookRequest(Request request) {
|
||||||
HookEnvironment environment = environmentProvider.get();
|
|
||||||
try {
|
try {
|
||||||
if (!environment.isAcceptAble(request.getChallenge())) {
|
if (!environment.isAcceptAble(request.getChallenge())) {
|
||||||
return error("invalid hook challenge");
|
return error("invalid hook challenge");
|
||||||
@@ -93,13 +92,16 @@ class HgHookHandler implements Runnable {
|
|||||||
authenticate(request);
|
authenticate(request);
|
||||||
environment.setPending(request.getType() == RepositoryHookType.PRE_RECEIVE);
|
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);
|
hookEventFacade.handle(request.getRepositoryId()).fireHookEvent(request.getType(), context);
|
||||||
|
|
||||||
return new Response(context.getHgMessageProvider().getMessages(), false);
|
return new Response(context.getHgMessageProvider().getMessages(), false);
|
||||||
} catch (AuthenticationException ex) {
|
} catch (AuthenticationException ex) {
|
||||||
LOG.warn("hook authentication failed", ex);
|
LOG.warn("hook authentication failed", ex);
|
||||||
return error("hook authentication failed");
|
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) {
|
} catch (Exception ex) {
|
||||||
LOG.warn("unknown error on hook occurred", ex);
|
LOG.warn("unknown error on hook occurred", ex);
|
||||||
return error("unknown error");
|
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) {
|
private void authenticate(Request request) {
|
||||||
String token = CipherUtil.getInstance().decode(request.getToken());
|
String token = CipherUtil.getInstance().decode(request.getToken());
|
||||||
BearerToken bearer = BearerToken.valueOf(token);
|
BearerToken bearer = BearerToken.valueOf(token);
|
||||||
@@ -22,59 +22,36 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.hooks;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
import sonia.scm.NotFoundException;
|
||||||
|
import sonia.scm.repository.HgRepositoryFactory;
|
||||||
import sonia.scm.repository.AbstractHgHandler;
|
|
||||||
import sonia.scm.repository.HgContext;
|
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
import sonia.scm.repository.Repository;
|
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;
|
||||||
|
|
||||||
/**
|
@Inject
|
||||||
*
|
public HookContextProviderFactory(RepositoryManager repositoryManager, HgRepositoryHandler repositoryHandler, HgRepositoryFactory repositoryFactory) {
|
||||||
* @author Sebastian Sdorra
|
this.repositoryManager = repositoryManager;
|
||||||
*/
|
this.repositoryHandler = repositoryHandler;
|
||||||
public class AbstractHgCommand extends AbstractHgHandler
|
this.repositoryFactory = repositoryFactory;
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param handler
|
|
||||||
* @param context
|
|
||||||
* @param repository
|
|
||||||
* @param repositoryDirectory
|
|
||||||
*/
|
|
||||||
protected AbstractHgCommand(HgRepositoryHandler handler, HgContext context,
|
|
||||||
Repository repository, File repositoryDirectory)
|
|
||||||
{
|
|
||||||
super(handler, context, repository, repositoryDirectory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
HgHookContextProvider create(String repositoryId, String node) {
|
||||||
|
Repository repository = repositoryManager.get(repositoryId);
|
||||||
/**
|
if (repository == null) {
|
||||||
* Method description
|
throw new NotFoundException(Repository.class, repositoryId);
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param revision
|
|
||||||
* @param path
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected Map<String,
|
|
||||||
String> createEnvironment(FileBaseCommandRequest request)
|
|
||||||
{
|
|
||||||
return createEnvironment(request.getRevision(), request.getPath());
|
|
||||||
}
|
}
|
||||||
|
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;
|
package sonia.scm.repository.hooks;
|
||||||
|
|
||||||
import com.google.inject.ImplementedBy;
|
|
||||||
|
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
@ImplementedBy(DefaultHookHandlerFactory.class)
|
|
||||||
interface HookHandlerFactory {
|
interface HookHandlerFactory {
|
||||||
|
|
||||||
Runnable create(Socket socket);
|
HookHandler create(Socket socket);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,28 +24,17 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.hooks;
|
package sonia.scm.repository.hooks;
|
||||||
|
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import com.google.inject.AbstractModule;
|
||||||
import sonia.scm.repository.spi.HookEventFacade;
|
import com.google.inject.assistedinject.FactoryModuleBuilder;
|
||||||
|
import sonia.scm.plugin.Extension;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@Extension
|
||||||
|
public class HookModule extends AbstractModule {
|
||||||
@Override
|
@Override
|
||||||
public HgHookHandler create(Socket socket) {
|
protected void configure() {
|
||||||
return new HgHookHandler(handler, hookEventFacade, hookEnvironment, socket);
|
install(new FactoryModuleBuilder()
|
||||||
|
.implement(HookHandler.class, DefaultHookHandler.class)
|
||||||
|
.build(HookHandlerFactory.class)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,9 @@
|
|||||||
package sonia.scm.repository.hooks;
|
package sonia.scm.repository.hooks;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -38,6 +40,7 @@ import java.net.ServerSocket;
|
|||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class HookServer implements AutoCloseable {
|
public class HookServer implements AutoCloseable {
|
||||||
@@ -56,18 +59,24 @@ public class HookServer implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ExecutorService createAcceptor() {
|
private ExecutorService createAcceptor() {
|
||||||
return new SubjectAwareExecutorService(Executors.newSingleThreadExecutor(
|
return Executors.newSingleThreadExecutor(
|
||||||
new ThreadFactoryBuilder().setNameFormat("HgHookAcceptor").build()
|
createThreadFactory("HgHookAcceptor")
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExecutorService createWorkerPool() {
|
private ExecutorService createWorkerPool() {
|
||||||
return new SubjectAwareExecutorService(Executors.newCachedThreadPool(
|
return Executors.newCachedThreadPool(
|
||||||
new ThreadFactoryBuilder().setNameFormat("HgHookWorker-%d").build()
|
createThreadFactory("HgHookWorker-%d")
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private ThreadFactory createThreadFactory(String hgHookAcceptor) {
|
||||||
|
return new ThreadFactoryBuilder()
|
||||||
|
.setNameFormat(hgHookAcceptor)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
public int start() throws IOException {
|
public int start() throws IOException {
|
||||||
acceptor = createAcceptor();
|
acceptor = createAcceptor();
|
||||||
workerPool = createWorkerPool();
|
workerPool = createWorkerPool();
|
||||||
@@ -83,12 +92,14 @@ public class HookServer implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void accept() {
|
private void accept() {
|
||||||
|
SecurityManager securityManager = SecurityUtils.getSecurityManager();
|
||||||
acceptor.submit(() -> {
|
acceptor.submit(() -> {
|
||||||
while (!serverSocket.isClosed()) {
|
while (!serverSocket.isClosed()) {
|
||||||
try {
|
try {
|
||||||
Socket clientSocket = serverSocket.accept();
|
Socket clientSocket = serverSocket.accept();
|
||||||
LOG.trace("accept incoming hook client from {}", clientSocket.getInetAddress());
|
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) {
|
} catch (IOException ex) {
|
||||||
LOG.debug("failed to accept socket, possible closed", 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
|
@Nonnull
|
||||||
private ServerSocket createServerSocket() throws IOException {
|
private ServerSocket createServerSocket() throws IOException {
|
||||||
return new ServerSocket(0, 0, InetAddress.getLoopbackAddress());
|
return new ServerSocket(0, 0, InetAddress.getLoopbackAddress());
|
||||||
|
|||||||
@@ -27,18 +27,12 @@ package sonia.scm.repository.spi;
|
|||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.aragost.javahg.Repository;
|
import com.aragost.javahg.Repository;
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import sonia.scm.repository.HgConfig;
|
import sonia.scm.repository.HgConfig;
|
||||||
import sonia.scm.repository.HgHookManager;
|
import sonia.scm.repository.HgRepositoryFactory;
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
import sonia.scm.repository.RepositoryProvider;
|
import sonia.scm.repository.RepositoryProvider;
|
||||||
import sonia.scm.web.HgUtil;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
@@ -46,105 +40,32 @@ import java.util.function.BiConsumer;
|
|||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
public class HgCommandContext implements Closeable, RepositoryProvider
|
public class HgCommandContext implements Closeable, RepositoryProvider {
|
||||||
{
|
|
||||||
|
|
||||||
/** Field description */
|
private final HgRepositoryHandler handler;
|
||||||
private static final String PROPERTY_ENCODING = "hg.encoding";
|
private final HgRepositoryFactory factory;
|
||||||
|
private final sonia.scm.repository.Repository scmRepository;
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
private Repository repository;
|
||||||
|
|
||||||
/**
|
public HgCommandContext(HgRepositoryHandler handler, HgRepositoryFactory factory, sonia.scm.repository.Repository scmRepository) {
|
||||||
* 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;
|
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
this.directory = directory;
|
this.factory = factory;
|
||||||
this.scmRepository = repository;
|
this.scmRepository = scmRepository;
|
||||||
this.encoding = repository.getProperty(PROPERTY_ENCODING);
|
|
||||||
this.pending = pending;
|
|
||||||
|
|
||||||
if (Strings.isNullOrEmpty(encoding))
|
|
||||||
{
|
|
||||||
encoding = handler.getConfig().getEncoding();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
public Repository open() {
|
||||||
|
if (repository == null) {
|
||||||
/**
|
repository = factory.openForRead(scmRepository);
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException
|
|
||||||
{
|
|
||||||
if (repository != null)
|
|
||||||
{
|
|
||||||
repository.close();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Repository open()
|
|
||||||
{
|
|
||||||
if (repository == null)
|
|
||||||
{
|
|
||||||
repository = HgUtil.open(handler, hookManager, directory, encoding, pending);
|
|
||||||
}
|
|
||||||
|
|
||||||
return repository;
|
return repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Repository openWithSpecialEnvironment(BiConsumer<sonia.scm.repository.Repository, Map<String, String>> prepareEnvironment)
|
public Repository openForWrite() {
|
||||||
{
|
return factory.openForWrite(scmRepository);
|
||||||
return HgUtil.open(handler, directory, encoding,
|
|
||||||
pending, environment -> prepareEnvironment.accept(scmRepository, environment));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public HgConfig getConfig()
|
public HgConfig getConfig()
|
||||||
{
|
{
|
||||||
return handler.getConfig();
|
return handler.getConfig();
|
||||||
@@ -159,25 +80,12 @@ public class HgCommandContext implements Closeable, RepositoryProvider
|
|||||||
return getScmRepository();
|
return getScmRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
@Override
|
||||||
private File directory;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,82 +24,53 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.aragost.javahg.Repository;
|
import com.aragost.javahg.Repository;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.repository.HgHookManager;
|
import sonia.scm.repository.HgRepositoryFactory;
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
import sonia.scm.repository.RepositoryHookType;
|
|
||||||
import sonia.scm.repository.spi.javahg.HgLogChangesetCommand;
|
import sonia.scm.repository.spi.javahg.HgLogChangesetCommand;
|
||||||
import sonia.scm.web.HgUtil;
|
import sonia.scm.web.HgUtil;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
public class HgHookChangesetProvider implements HookChangesetProvider
|
public class HgHookChangesetProvider implements HookChangesetProvider {
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
private static final Logger LOG = LoggerFactory.getLogger(HgHookChangesetProvider.class);
|
||||||
* the logger for HgHookChangesetProvider
|
|
||||||
*/
|
|
||||||
private static final Logger logger =
|
|
||||||
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,
|
private HookChangesetResponse response;
|
||||||
File repositoryDirectory, HgHookManager hookManager, String startRev,
|
|
||||||
RepositoryHookType type)
|
public HgHookChangesetProvider(HgRepositoryHandler handler, HgRepositoryFactory factory, sonia.scm.repository.Repository scmRepository, String startRev) {
|
||||||
{
|
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
this.repositoryDirectory = repositoryDirectory;
|
this.factory = factory;
|
||||||
this.hookManager = hookManager;
|
this.scmRepository = scmRepository;
|
||||||
this.startRev = startRev;
|
this.startRev = startRev;
|
||||||
this.type = type;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized HookChangesetResponse handleRequest(HookChangesetRequest request)
|
public synchronized HookChangesetResponse handleRequest(HookChangesetRequest request) {
|
||||||
{
|
if (response == null) {
|
||||||
if (response == null)
|
|
||||||
{
|
|
||||||
Repository repository = null;
|
Repository repository = null;
|
||||||
|
|
||||||
try
|
try {
|
||||||
{
|
repository = factory.openForRead(scmRepository);
|
||||||
repository = open();
|
|
||||||
|
|
||||||
HgLogChangesetCommand cmd = HgLogChangesetCommand.on(repository,
|
HgLogChangesetCommand cmd = HgLogChangesetCommand.on(repository, handler.getConfig());
|
||||||
handler.getConfig());
|
|
||||||
|
|
||||||
response = new HookChangesetResponse(
|
response = new HookChangesetResponse(
|
||||||
cmd.rev(startRev.concat(":").concat(HgUtil.REVISION_TIP)).execute());
|
cmd.rev(startRev.concat(":").concat(HgUtil.REVISION_TIP)).execute()
|
||||||
}
|
);
|
||||||
catch (Exception ex)
|
} catch (Exception ex) {
|
||||||
{
|
LOG.error("could not retrieve changesets", ex);
|
||||||
logger.error("could not retrieve changesets", ex);
|
} finally {
|
||||||
}
|
if (repository != null) {
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (repository != null)
|
|
||||||
{
|
|
||||||
repository.close();
|
repository.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,39 +79,4 @@ public class HgHookChangesetProvider implements HookChangesetProvider
|
|||||||
return response;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ package sonia.scm.repository.spi;
|
|||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import sonia.scm.repository.HgHookManager;
|
import sonia.scm.repository.HgRepositoryFactory;
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
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.HgHookBranchProvider;
|
||||||
import sonia.scm.repository.api.HgHookMessageProvider;
|
import sonia.scm.repository.api.HgHookMessageProvider;
|
||||||
import sonia.scm.repository.api.HgHookTagProvider;
|
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.HookMessageProvider;
|
||||||
import sonia.scm.repository.api.HookTagProvider;
|
import sonia.scm.repository.api.HookTagProvider;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -48,52 +47,37 @@ import java.util.Set;
|
|||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
public class HgHookContextProvider extends HookContextProvider
|
public class HgHookContextProvider extends HookContextProvider {
|
||||||
{
|
|
||||||
|
|
||||||
private static final Set<HookFeature> SUPPORTED_FEATURES =
|
private static final Set<HookFeature> SUPPORTED_FEATURES = EnumSet.of(
|
||||||
EnumSet.of(HookFeature.CHANGESET_PROVIDER, HookFeature.MESSAGE_PROVIDER,
|
HookFeature.CHANGESET_PROVIDER,
|
||||||
HookFeature.BRANCH_PROVIDER, HookFeature.TAG_PROVIDER);
|
HookFeature.MESSAGE_PROVIDER,
|
||||||
|
HookFeature.BRANCH_PROVIDER,
|
||||||
|
HookFeature.TAG_PROVIDER
|
||||||
|
);
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
private final HgHookChangesetProvider hookChangesetProvider;
|
||||||
|
private HgHookMessageProvider hgMessageProvider;
|
||||||
|
private HgHookBranchProvider hookBranchProvider;
|
||||||
|
private HgHookTagProvider hookTagProvider;
|
||||||
|
|
||||||
/**
|
public HgHookContextProvider(HgRepositoryHandler handler, HgRepositoryFactory factory, Repository repository, String startRev) {
|
||||||
* Constructs a new instance.
|
this.hookChangesetProvider = new HgHookChangesetProvider(handler, factory, repository, startRev);
|
||||||
*
|
|
||||||
* @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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HookBranchProvider getBranchProvider()
|
public HookBranchProvider getBranchProvider() {
|
||||||
{
|
if (hookBranchProvider == null) {
|
||||||
if (hookBranchProvider == null)
|
|
||||||
{
|
|
||||||
hookBranchProvider = new HgHookBranchProvider(hookChangesetProvider);
|
hookBranchProvider = new HgHookBranchProvider(hookChangesetProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
return hookBranchProvider;
|
return hookBranchProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HookTagProvider getTagProvider()
|
public HookTagProvider getTagProvider() {
|
||||||
{
|
if (hookTagProvider == null) {
|
||||||
if (hookTagProvider == null)
|
|
||||||
{
|
|
||||||
hookTagProvider = new HgHookTagProvider(hookChangesetProvider);
|
hookTagProvider = new HgHookTagProvider(hookChangesetProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
return hookTagProvider;
|
return hookTagProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,13 +87,10 @@ public class HgHookContextProvider extends HookContextProvider
|
|||||||
return hookChangesetProvider;
|
return hookChangesetProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HgHookMessageProvider getHgMessageProvider()
|
public HgHookMessageProvider getHgMessageProvider() {
|
||||||
{
|
if (hgMessageProvider == null) {
|
||||||
if (hgMessageProvider == null)
|
|
||||||
{
|
|
||||||
hgMessageProvider = new HgHookMessageProvider();
|
hgMessageProvider = new HgHookMessageProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
return hgMessageProvider;
|
return hgMessageProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,21 +100,9 @@ public class HgHookContextProvider extends HookContextProvider
|
|||||||
return SUPPORTED_FEATURES;
|
return SUPPORTED_FEATURES;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected HookMessageProvider createMessageProvider()
|
protected HookMessageProvider createMessageProvider()
|
||||||
{
|
{
|
||||||
return getHgMessageProvider();
|
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 com.google.common.io.Closeables;
|
||||||
import sonia.scm.repository.Feature;
|
import sonia.scm.repository.Feature;
|
||||||
import sonia.scm.repository.HgHookManager;
|
import sonia.scm.repository.HgRepositoryFactory;
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.api.Command;
|
import sonia.scm.repository.api.Command;
|
||||||
import sonia.scm.repository.api.CommandNotSupportedException;
|
import sonia.scm.repository.api.CommandNotSupportedException;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -41,11 +40,8 @@ import java.util.Set;
|
|||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
public class HgRepositoryServiceProvider extends RepositoryServiceProvider
|
public class HgRepositoryServiceProvider extends RepositoryServiceProvider {
|
||||||
{
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
//J-
|
|
||||||
public static final Set<Command> COMMANDS = EnumSet.of(
|
public static final Set<Command> COMMANDS = EnumSet.of(
|
||||||
Command.BLAME,
|
Command.BLAME,
|
||||||
Command.BROWSE,
|
Command.BROWSE,
|
||||||
@@ -61,25 +57,19 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider
|
|||||||
Command.PULL,
|
Command.PULL,
|
||||||
Command.MODIFY
|
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,
|
HgRepositoryServiceProvider(HgRepositoryHandler handler, HgRepositoryFactory factory, Repository repository) {
|
||||||
HgHookManager hookManager, Repository repository)
|
|
||||||
{
|
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
this.repositoryDirectory = handler.getDirectory(repository.getId());
|
this.context = new HgCommandContext(handler, factory, repository);
|
||||||
this.context = new HgCommandContext(hookManager, handler, repository,
|
|
||||||
repositoryDirectory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
@@ -91,9 +81,9 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider
|
|||||||
{
|
{
|
||||||
Closeables.close(context, true);
|
Closeables.close(context, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
@@ -271,14 +261,4 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider
|
|||||||
return new HgTagsCommand(context);
|
return new HgTagsCommand(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private HgCommandContext context;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private HgRepositoryHandler handler;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private File repositoryDirectory;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ package sonia.scm.repository.spi;
|
|||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import sonia.scm.plugin.Extension;
|
import sonia.scm.plugin.Extension;
|
||||||
import sonia.scm.repository.HgHookManager;
|
import sonia.scm.repository.HgRepositoryFactory;
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
@@ -35,18 +35,15 @@ import sonia.scm.repository.Repository;
|
|||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@Extension
|
@Extension
|
||||||
public class HgRepositoryServiceResolver implements RepositoryServiceResolver
|
public class HgRepositoryServiceResolver implements RepositoryServiceResolver {
|
||||||
{
|
|
||||||
|
|
||||||
private final HgRepositoryHandler handler;
|
private final HgRepositoryHandler handler;
|
||||||
private final HgHookManager hookManager;
|
private final HgRepositoryFactory factory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public HgRepositoryServiceResolver(HgRepositoryHandler handler,
|
public HgRepositoryServiceResolver(HgRepositoryHandler handler, HgRepositoryFactory factory) {
|
||||||
HgHookManager hookManager)
|
|
||||||
{
|
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
this.hookManager = hookManager;
|
this.factory = factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -54,7 +51,7 @@ public class HgRepositoryServiceResolver implements RepositoryServiceResolver
|
|||||||
HgRepositoryServiceProvider provider = null;
|
HgRepositoryServiceProvider provider = null;
|
||||||
|
|
||||||
if (HgRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) {
|
if (HgRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) {
|
||||||
provider = new HgRepositoryServiceProvider(handler, hookManager, repository);
|
provider = new HgRepositoryServiceProvider(handler, factory, repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider;
|
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.SimpleWorkingCopyFactory;
|
||||||
import sonia.scm.repository.work.WorkingCopyPool;
|
import sonia.scm.repository.work.WorkingCopyPool;
|
||||||
import sonia.scm.util.IOUtil;
|
import sonia.scm.util.IOUtil;
|
||||||
import sonia.scm.web.HgRepositoryEnvironmentBuilder;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Provider;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
|
|
||||||
public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory<Repository, Repository, HgCommandContext> implements HgWorkingCopyFactory {
|
public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory<Repository, Repository, HgCommandContext> implements HgWorkingCopyFactory {
|
||||||
|
|
||||||
private final Provider<HgRepositoryEnvironmentBuilder> hgRepositoryEnvironmentBuilder;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SimpleHgWorkingCopyFactory(Provider<HgRepositoryEnvironmentBuilder> hgRepositoryEnvironmentBuilder, WorkingCopyPool workdirProvider) {
|
public SimpleHgWorkingCopyFactory(WorkingCopyPool workdirProvider) {
|
||||||
super(workdirProvider);
|
super(workdirProvider);
|
||||||
this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ParentAndClone<Repository, Repository> initialize(HgCommandContext context, File target, String initialBranch) {
|
public ParentAndClone<Repository, Repository> initialize(HgCommandContext context, File target, String initialBranch) {
|
||||||
Repository centralRepository = openCentral(context);
|
Repository centralRepository = context.openForWrite();
|
||||||
CloneCommand cloneCommand = CloneCommandFlags.on(centralRepository);
|
CloneCommand cloneCommand = CloneCommandFlags.on(centralRepository);
|
||||||
if (initialBranch != null) {
|
if (initialBranch != null) {
|
||||||
cloneCommand.updaterev(initialBranch);
|
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.
|
// 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")
|
@SuppressWarnings("java:S3252")
|
||||||
protected ParentAndClone<Repository, Repository> reclaim(HgCommandContext context, File target, String initialBranch) throws ReclaimFailedException {
|
protected ParentAndClone<Repository, Repository> reclaim(HgCommandContext context, File target, String initialBranch) throws ReclaimFailedException {
|
||||||
Repository centralRepository = openCentral(context);
|
Repository centralRepository = context.openForWrite();
|
||||||
try {
|
try {
|
||||||
BaseRepository clone = Repository.open(target);
|
BaseRepository clone = Repository.open(target);
|
||||||
for (String unknown : StatusCommand.on(clone).execute().getUnknown()) {
|
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 {
|
private void delete(File directory, String unknownFile) throws IOException {
|
||||||
IOUtil.delete(new File(directory, unknownFile));
|
IOUtil.delete(new File(directory, unknownFile));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
import sonia.scm.SCMContext;
|
import sonia.scm.SCMContext;
|
||||||
import sonia.scm.config.ScmConfiguration;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.repository.HgConfig;
|
import sonia.scm.repository.HgConfig;
|
||||||
|
import sonia.scm.repository.HgEnvironmentBuilder;
|
||||||
import sonia.scm.repository.HgPythonScript;
|
import sonia.scm.repository.HgPythonScript;
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
import sonia.scm.repository.Repository;
|
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.CGIExecutorFactory;
|
||||||
import sonia.scm.web.cgi.EnvList;
|
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.ServletException;
|
||||||
import javax.servlet.http.HttpServlet;
|
import javax.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpSession;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet {
|
||||||
{
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String ENV_SESSION_PREFIX = "SCM_";
|
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private static final long serialVersionUID = -3492811300905099810L;
|
private static final long serialVersionUID = -3492811300905099810L;
|
||||||
@@ -80,13 +73,13 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
|||||||
ScmConfiguration configuration,
|
ScmConfiguration configuration,
|
||||||
HgRepositoryHandler handler,
|
HgRepositoryHandler handler,
|
||||||
RepositoryRequestListenerUtil requestListenerUtil,
|
RepositoryRequestListenerUtil requestListenerUtil,
|
||||||
HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder)
|
HgEnvironmentBuilder environmentBuilder)
|
||||||
{
|
{
|
||||||
this.cgiExecutorFactory = cgiExecutorFactory;
|
this.cgiExecutorFactory = cgiExecutorFactory;
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
this.requestListenerUtil = requestListenerUtil;
|
this.requestListenerUtil = requestListenerUtil;
|
||||||
this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder;
|
this.environmentBuilder = environmentBuilder;
|
||||||
this.exceptionHandler = new HgCGIExceptionHandler();
|
this.exceptionHandler = new HgCGIExceptionHandler();
|
||||||
this.command = HgPythonScript.HGWEB.getFile(SCMContext.getContext());
|
this.command = HgPythonScript.HGWEB.getFile(SCMContext.getContext());
|
||||||
}
|
}
|
||||||
@@ -108,11 +101,7 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
|||||||
{
|
{
|
||||||
handleRequest(request, response, repository);
|
handleRequest(request, response, repository);
|
||||||
}
|
}
|
||||||
catch (ServletException ex)
|
catch (ServletException | IOException ex)
|
||||||
{
|
|
||||||
exceptionHandler.handleException(request, response, ex);
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
{
|
||||||
exceptionHandler.handleException(request, response, 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
|
* Method description
|
||||||
*
|
*
|
||||||
@@ -192,7 +158,9 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
|||||||
executor.setExceptionHandler(exceptionHandler);
|
executor.setExceptionHandler(exceptionHandler);
|
||||||
executor.setStatusCodeHandler(exceptionHandler);
|
executor.setStatusCodeHandler(exceptionHandler);
|
||||||
executor.setContentLengthWorkaround(true);
|
executor.setContentLengthWorkaround(true);
|
||||||
hgRepositoryEnvironmentBuilder.buildFor(repository, request, executor.getEnvironment().asMutableMap());
|
|
||||||
|
EnvList env = executor.getEnvironment();
|
||||||
|
environmentBuilder.write(repository).forEach(env::set);
|
||||||
|
|
||||||
String interpreter = getInterpreter();
|
String interpreter = getInterpreter();
|
||||||
|
|
||||||
@@ -248,5 +216,5 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
|||||||
/** Field description */
|
/** Field description */
|
||||||
private final RepositoryRequestListenerUtil requestListenerUtil;
|
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.api.v2.resources.HgConfigToHgConfigDtoMapper;
|
||||||
import sonia.scm.installer.HgPackageReader;
|
import sonia.scm.installer.HgPackageReader;
|
||||||
import sonia.scm.plugin.Extension;
|
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.HgWorkingCopyFactory;
|
||||||
import sonia.scm.repository.spi.SimpleHgWorkingCopyFactory;
|
import sonia.scm.repository.spi.SimpleHgWorkingCopyFactory;
|
||||||
|
|
||||||
@@ -45,26 +42,10 @@ import sonia.scm.repository.spi.SimpleHgWorkingCopyFactory;
|
|||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@Extension
|
@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
|
@Override
|
||||||
protected void configureServlets()
|
protected void configureServlets() {
|
||||||
{
|
|
||||||
bind(HgContext.class).toProvider(HgContextProvider.class);
|
|
||||||
bind(HgHookManager.class);
|
|
||||||
bind(HgPackageReader.class);
|
bind(HgPackageReader.class);
|
||||||
|
|
||||||
bind(HgConfigDtoToHgConfigMapper.class).to(Mappers.getMapper(HgConfigDtoToHgConfigMapper.class).getClass());
|
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(HgConfigPackagesToDtoMapper.class).to(Mappers.getMapper(HgConfigPackagesToDtoMapper.class).getClass());
|
||||||
bind(HgConfigInstallationsToDtoMapper.class);
|
bind(HgConfigInstallationsToDtoMapper.class);
|
||||||
|
|
||||||
// bind servlets
|
|
||||||
serve(MAPPING_HOOK).with(HgHookCallbackServlet.class);
|
|
||||||
|
|
||||||
bind(HgWorkingCopyFactory.class).to(SimpleHgWorkingCopyFactory.class);
|
bind(HgWorkingCopyFactory.class).to(SimpleHgWorkingCopyFactory.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,186 +24,45 @@
|
|||||||
|
|
||||||
package sonia.scm.web;
|
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.SCMContext;
|
||||||
import sonia.scm.repository.HgConfig;
|
import sonia.scm.repository.HgConfig;
|
||||||
import sonia.scm.repository.HgEnvironment;
|
|
||||||
import sonia.scm.repository.HgHookManager;
|
|
||||||
import sonia.scm.repository.HgPythonScript;
|
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;
|
import sonia.scm.util.Util;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import java.io.File;
|
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
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
public final class HgUtil
|
public final class HgUtil {
|
||||||
{
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String REVISION_TIP = "tip";
|
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() {}
|
private HgUtil() {}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
public static String getPythonPath(HgConfig config) {
|
||||||
|
|
||||||
/**
|
|
||||||
* 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)
|
|
||||||
{
|
|
||||||
String pythonPath = Util.EMPTY_STRING;
|
String pythonPath = Util.EMPTY_STRING;
|
||||||
|
|
||||||
if (config != null)
|
if (config != null) {
|
||||||
{
|
|
||||||
pythonPath = Util.nonNull(config.getPythonPath());
|
pythonPath = Util.nonNull(config.getPythonPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Util.isNotEmpty(pythonPath))
|
if (Util.isNotEmpty(pythonPath)) {
|
||||||
{
|
|
||||||
pythonPath = pythonPath.concat(File.pathSeparator);
|
pythonPath = pythonPath.concat(File.pathSeparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
//J-
|
|
||||||
pythonPath = pythonPath.concat(
|
pythonPath = pythonPath.concat(
|
||||||
HgPythonScript.getScriptDirectory(
|
HgPythonScript.getScriptDirectory(
|
||||||
SCMContext.getContext()
|
SCMContext.getContext()
|
||||||
).getAbsolutePath()
|
).getAbsolutePath()
|
||||||
);
|
);
|
||||||
//J+
|
|
||||||
|
|
||||||
return pythonPath;
|
return pythonPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static String getRevision(String revision) {
|
||||||
* Method description
|
return Util.isEmpty(revision) ? REVISION_TIP : revision;
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param revision
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
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
|
# changegroup.scm = python:scmhooks.callback
|
||||||
#
|
#
|
||||||
|
|
||||||
import os, sys
|
import os, sys, json, socket
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
# read environment
|
# read environment
|
||||||
baseUrl = os.environ['SCM_URL']
|
port = os.environ['SCM_HOOK_PORT']
|
||||||
challenge = os.environ['SCM_CHALLENGE']
|
challenge = os.environ['SCM_CHALLENGE']
|
||||||
token = os.environ['SCM_BEARER_TOKEN']
|
token = os.environ['SCM_BEARER_TOKEN']
|
||||||
xsrf = os.environ['SCM_XSRF']
|
|
||||||
repositoryId = os.environ['SCM_REPOSITORY_ID']
|
repositoryId = os.environ['SCM_REPOSITORY_ID']
|
||||||
|
|
||||||
def printMessages(ui, msgs):
|
def print_messages(ui, messages):
|
||||||
for raw in msgs:
|
for message in messages:
|
||||||
line = raw
|
ui.warn(b'%s: %s\n' % message['severity'], message['message'])
|
||||||
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 callHookUrl(ui, repo, hooktype, node):
|
def fire_hook(ui, repo, hooktype, node):
|
||||||
abort = True
|
abort = True
|
||||||
try:
|
try:
|
||||||
url = baseUrl + hooktype.decode("utf-8")
|
values = {'token': token, 'type': hooktype, 'repositoryId': repositoryId, 'challenge': challenge, 'node': node.decode('utf8') }
|
||||||
ui.debug( b"send scm-hook to " + url.encode() + b" and " + node + b"\n" )
|
ui.debug( b"send scm-hook for " + node + b"\n" )
|
||||||
values = {'node': node.decode("utf-8"), 'challenge': challenge, 'token': token, 'repositoryPath': repo.root, 'repositoryId': repositoryId}
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
conn = client.post(url, values)
|
s.connect(("127.0.0.1", int(port)))
|
||||||
if 200 <= conn.code < 300:
|
s.sendall(json.dumps(values).encode('utf-8'))
|
||||||
ui.debug( b"scm-hook " + hooktype + b" success with status code " + str(conn.code).encode() + b"\n" )
|
s.sendall(b'\0')
|
||||||
printMessages(ui, conn)
|
|
||||||
abort = False
|
received = []
|
||||||
else:
|
received = s.recv(1)
|
||||||
ui.warn( b"ERROR: scm-hook failed with error code " + str(conn.code).encode() + b"\n" )
|
while b != b'\0':
|
||||||
except URLError as e:
|
received.append(b)
|
||||||
msg = None
|
received = s.recv(1)
|
||||||
# some URLErrors have no read method
|
|
||||||
if hasattr(e, "read"):
|
message = b''.join(bytes).decode('utf-8')
|
||||||
msg = e.read()
|
response = json.loads(message)
|
||||||
elif hasattr(e, "code"):
|
|
||||||
msg = "scm-hook failed with error code " + e.code + "\n"
|
abort = response['abort']
|
||||||
else:
|
print_messages(ui, response['messages'])
|
||||||
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()
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
ui.warn( b"scm-hook failed with an exception\n" )
|
ui.warn( b"scm-hook failed with an exception\n" )
|
||||||
ui.traceback()
|
ui.traceback()
|
||||||
@@ -118,8 +71,8 @@ def callHookUrl(ui, repo, hooktype, node):
|
|||||||
def callback(ui, repo, hooktype, node=None):
|
def callback(ui, repo, hooktype, node=None):
|
||||||
abort = True
|
abort = True
|
||||||
if node != None:
|
if node != None:
|
||||||
if len(baseUrl) > 0:
|
if len(port) > 0:
|
||||||
abort = callHookUrl(ui, repo, hooktype, node)
|
abort = fire_hook(ui, repo, hooktype, node)
|
||||||
else:
|
else:
|
||||||
ui.warn(b"ERROR: scm-manager hooks are disabled, please check your configuration and the scm-manager log for details\n")
|
ui.warn(b"ERROR: scm-manager hooks are disabled, please check your configuration and the scm-manager log for details\n")
|
||||||
abort = False
|
abort = False
|
||||||
@@ -127,7 +80,7 @@ def callback(ui, repo, hooktype, node=None):
|
|||||||
ui.warn(b"changeset node is not available")
|
ui.warn(b"changeset node is not available")
|
||||||
return abort
|
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
|
# older mercurial versions
|
||||||
if pending != None:
|
if pending != None:
|
||||||
pending()
|
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")
|
ui.debug(b"mercurial does not support currenttransation")
|
||||||
# do nothing
|
# 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):
|
def post_hook(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs):
|
||||||
return callback(ui, repo, hooktype, node)
|
return callback(ui, repo, "POST_RECEIVE", node)
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ public class HgConfigDtoToHgConfigMapperTest {
|
|||||||
assertEquals("/etc/", config.getPythonPath());
|
assertEquals("/etc/", config.getPythonPath());
|
||||||
assertTrue(config.isShowRevisionInId());
|
assertTrue(config.isShowRevisionInId());
|
||||||
assertTrue(config.isUseOptimizedBytecode());
|
assertTrue(config.isUseOptimizedBytecode());
|
||||||
assertTrue(config.isDisableHookSSLValidation());
|
|
||||||
assertTrue(config.isEnableHttpPostArgs());
|
assertTrue(config.isEnableHttpPostArgs());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +64,6 @@ public class HgConfigDtoToHgConfigMapperTest {
|
|||||||
configDto.setPythonPath("/etc/");
|
configDto.setPythonPath("/etc/");
|
||||||
configDto.setShowRevisionInId(true);
|
configDto.setShowRevisionInId(true);
|
||||||
configDto.setUseOptimizedBytecode(true);
|
configDto.setUseOptimizedBytecode(true);
|
||||||
configDto.setDisableHookSSLValidation(true);
|
|
||||||
configDto.setEnableHttpPostArgs(true);
|
configDto.setEnableHttpPostArgs(true);
|
||||||
|
|
||||||
return configDto;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -24,27 +24,17 @@
|
|||||||
|
|
||||||
package sonia.scm.repository;
|
package sonia.scm.repository;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import java.io.File;
|
public class EmptyHgEnvironmentBuilder implements HgEnvironmentBuilder {
|
||||||
import java.io.IOException;
|
@Override
|
||||||
|
public Map<String, String> read(Repository repository) {
|
||||||
/**
|
return Collections.emptyMap();
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
public class HgVersionHandler extends AbstractHgHandler
|
|
||||||
{
|
|
||||||
|
|
||||||
public HgVersionHandler(HgRepositoryHandler handler, HgContext context,
|
|
||||||
File directory)
|
|
||||||
{
|
|
||||||
super(handler, context, null, directory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
@Override
|
||||||
|
public Map<String, String> write(Repository repository) {
|
||||||
public HgVersion getVersion() throws IOException {
|
return Collections.emptyMap();
|
||||||
return getResultFromScript(HgVersion.class, HgPythonScript.VERSION);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -32,13 +32,17 @@ import org.junit.Test;
|
|||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
import sonia.scm.plugin.PluginLoader;
|
||||||
|
import sonia.scm.repository.spi.HgVersionCommand;
|
||||||
import sonia.scm.store.ConfigurationStoreFactory;
|
import sonia.scm.store.ConfigurationStoreFactory;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
@@ -52,9 +56,6 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
|||||||
@Mock
|
@Mock
|
||||||
private ConfigurationStoreFactory factory;
|
private ConfigurationStoreFactory factory;
|
||||||
|
|
||||||
@Mock
|
|
||||||
private com.google.inject.Provider<HgContext> provider;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void checkDirectory(File directory) {
|
protected void checkDirectory(File directory) {
|
||||||
File hgDirectory = new File(directory, ".hg");
|
File hgDirectory = new File(directory, ".hg");
|
||||||
@@ -70,7 +71,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, RepositoryLocationResolver locationResolver, File directory) {
|
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);
|
handler.init(contextProvider);
|
||||||
HgTestUtil.checkForSkip(handler);
|
HgTestUtil.checkForSkip(handler);
|
||||||
@@ -80,7 +81,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getDirectory() {
|
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 hgConfig = new HgConfig();
|
||||||
hgConfig.setHgBinary("hg");
|
hgConfig.setHgBinary("hg");
|
||||||
@@ -91,4 +92,20 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
|||||||
File path = repositoryHandler.getDirectory(repository.getId());
|
File path = repositoryHandler.getDirectory(repository.getId());
|
||||||
assertEquals(repoPath.toString() + File.separator + RepositoryDirectoryHandler.REPOSITORIES_NATIVE_DIRECTORY, path.getAbsolutePath());
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,16 +29,13 @@ package sonia.scm.repository;
|
|||||||
import org.junit.Assume;
|
import org.junit.Assume;
|
||||||
import sonia.scm.SCMContext;
|
import sonia.scm.SCMContext;
|
||||||
import sonia.scm.TempDirRepositoryLocationResolver;
|
import sonia.scm.TempDirRepositoryLocationResolver;
|
||||||
import sonia.scm.security.AccessToken;
|
import sonia.scm.repository.hooks.HookEnvironment;
|
||||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
import static org.mockito.Mockito.any;
|
import static org.mockito.Mockito.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
@@ -80,49 +77,26 @@ public final class HgTestUtil
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param directory
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static HgRepositoryHandler createHandler(File directory) {
|
public static HgRepositoryHandler createHandler(File directory) {
|
||||||
TempSCMContextProvider context =
|
TempSCMContextProvider context = (TempSCMContextProvider) SCMContext.getContext();
|
||||||
(TempSCMContextProvider) SCMContext.getContext();
|
|
||||||
|
|
||||||
context.setBaseDirectory(directory);
|
context.setBaseDirectory(directory);
|
||||||
|
|
||||||
RepositoryDAO repoDao = mock(RepositoryDAO.class);
|
|
||||||
|
|
||||||
RepositoryLocationResolver repositoryLocationResolver = new TempDirRepositoryLocationResolver(directory);
|
RepositoryLocationResolver repositoryLocationResolver = new TempDirRepositoryLocationResolver(directory);
|
||||||
HgRepositoryHandler handler =
|
HgRepositoryHandler handler = new HgRepositoryHandler(
|
||||||
new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver, null, null);
|
new InMemoryConfigurationStoreFactory(),
|
||||||
|
repositoryLocationResolver,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
handler.init(context);
|
handler.init(context);
|
||||||
|
|
||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static HgRepositoryFactory createFactory(HgRepositoryHandler handler, File directory) {
|
||||||
* Method description
|
return new HgRepositoryFactory(
|
||||||
*
|
handler, new HookEnvironment(), new EmptyHgEnvironmentBuilder(), repository -> directory
|
||||||
*
|
);
|
||||||
* @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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.hooks;
|
package sonia.scm.repository.hooks;
|
||||||
|
|
||||||
import com.google.inject.util.Providers;
|
|
||||||
import org.apache.shiro.authc.AuthenticationException;
|
import org.apache.shiro.authc.AuthenticationException;
|
||||||
import org.apache.shiro.authc.AuthenticationToken;
|
import org.apache.shiro.authc.AuthenticationToken;
|
||||||
import org.apache.shiro.subject.Subject;
|
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.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import sonia.scm.NotFoundException;
|
||||||
import sonia.scm.repository.Repository;
|
|
||||||
import sonia.scm.repository.RepositoryHookType;
|
import sonia.scm.repository.RepositoryHookType;
|
||||||
import sonia.scm.repository.api.HgHookMessage;
|
import sonia.scm.repository.api.HgHookMessage;
|
||||||
|
import sonia.scm.repository.api.HgHookMessageProvider;
|
||||||
import sonia.scm.repository.spi.HgHookContextProvider;
|
import sonia.scm.repository.spi.HgHookContextProvider;
|
||||||
import sonia.scm.repository.spi.HookEventFacade;
|
import sonia.scm.repository.spi.HookEventFacade;
|
||||||
import sonia.scm.security.CipherUtil;
|
import sonia.scm.security.CipherUtil;
|
||||||
@@ -48,6 +47,7 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
@@ -55,10 +55,13 @@ import static org.mockito.ArgumentMatchers.eq;
|
|||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
class HgHookHandlerTest {
|
class DefaultHookHandlerTest {
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private HgRepositoryHandler repositoryHandler;
|
private HookContextProviderFactory hookContextProviderFactory;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private HgHookContextProvider contextProvider;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private HookEventFacade hookEventFacade;
|
private HookEventFacade hookEventFacade;
|
||||||
@@ -71,7 +74,7 @@ class HgHookHandlerTest {
|
|||||||
|
|
||||||
private HookEnvironment hookEnvironment;
|
private HookEnvironment hookEnvironment;
|
||||||
|
|
||||||
private HgHookHandler handler;
|
private DefaultHookHandler handler;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private Subject subject;
|
private Subject subject;
|
||||||
@@ -81,11 +84,13 @@ class HgHookHandlerTest {
|
|||||||
ThreadContext.bind(subject);
|
ThreadContext.bind(subject);
|
||||||
|
|
||||||
hookEnvironment = new HookEnvironment();
|
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
|
@AfterEach
|
||||||
@@ -95,10 +100,11 @@ class HgHookHandlerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldFireHook() throws IOException {
|
void shouldFireHook() throws IOException {
|
||||||
|
mockMessageProvider();
|
||||||
when(hookEventFacade.handle("42")).thenReturn(hookEventHandler);
|
when(hookEventFacade.handle("42")).thenReturn(hookEventHandler);
|
||||||
|
|
||||||
HgHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE);
|
DefaultHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE);
|
||||||
HgHookHandler.Response response = send(request);
|
DefaultHookHandler.Response response = send(request);
|
||||||
|
|
||||||
assertSuccess(response, RepositoryHookType.POST_RECEIVE);
|
assertSuccess(response, RepositoryHookType.POST_RECEIVE);
|
||||||
assertThat(hookEnvironment.isPending()).isFalse();
|
assertThat(hookEnvironment.isPending()).isFalse();
|
||||||
@@ -106,13 +112,22 @@ class HgHookHandlerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldSetPendingStateOnPreReceiveHooks() throws IOException {
|
void shouldSetPendingStateOnPreReceiveHooks() throws IOException {
|
||||||
|
mockMessageProvider();
|
||||||
when(hookEventFacade.handle("42")).thenReturn(hookEventHandler);
|
when(hookEventFacade.handle("42")).thenReturn(hookEventHandler);
|
||||||
|
|
||||||
HgHookHandler.Request request = createRequest(RepositoryHookType.PRE_RECEIVE);
|
// we have to capture the pending state, when the hook is fired
|
||||||
HgHookHandler.Response response = send(request);
|
// 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);
|
assertSuccess(response, RepositoryHookType.PRE_RECEIVE);
|
||||||
assertThat(hookEnvironment.isPending()).isTrue();
|
assertThat(ref.get()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -121,8 +136,8 @@ class HgHookHandlerTest {
|
|||||||
.when(hookEventFacade)
|
.when(hookEventFacade)
|
||||||
.handle("42");
|
.handle("42");
|
||||||
|
|
||||||
HgHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE);
|
DefaultHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE);
|
||||||
HgHookHandler.Response response = send(request);
|
DefaultHookHandler.Response response = send(request);
|
||||||
|
|
||||||
assertError(response, "unknown");
|
assertError(response, "unknown");
|
||||||
}
|
}
|
||||||
@@ -133,28 +148,40 @@ class HgHookHandlerTest {
|
|||||||
.when(subject)
|
.when(subject)
|
||||||
.login(any(AuthenticationToken.class));
|
.login(any(AuthenticationToken.class));
|
||||||
|
|
||||||
HgHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE);
|
DefaultHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE);
|
||||||
HgHookHandler.Response response = send(request);
|
DefaultHookHandler.Response response = send(request);
|
||||||
|
|
||||||
assertError(response, "authentication");
|
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
|
@Test
|
||||||
void shouldReturnErrorWithInvalidChallenge() throws IOException {
|
void shouldReturnErrorWithInvalidChallenge() throws IOException {
|
||||||
HgHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE, "something-different");
|
DefaultHookHandler.Request request = createRequest(RepositoryHookType.POST_RECEIVE, "something-different");
|
||||||
HgHookHandler.Response response = send(request);
|
DefaultHookHandler.Response response = send(request);
|
||||||
|
|
||||||
assertError(response, "challenge");
|
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.getMessages()).isEmpty();
|
||||||
assertThat(response.isAbort()).isFalse();
|
assertThat(response.isAbort()).isFalse();
|
||||||
|
|
||||||
verify(hookEventHandler).fireHookEvent(eq(type), any(HgHookContextProvider.class));
|
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.isAbort()).isTrue();
|
||||||
assertThat(response.getMessages()).hasSize(1);
|
assertThat(response.getMessages()).hasSize(1);
|
||||||
HgHookMessage hgHookMessage = response.getMessages().get(0);
|
HgHookMessage hgHookMessage = response.getMessages().get(0);
|
||||||
@@ -163,19 +190,19 @@ class HgHookHandlerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private HgHookHandler.Request createRequest(RepositoryHookType type) {
|
private DefaultHookHandler.Request createRequest(RepositoryHookType type) {
|
||||||
return createRequest(type, hookEnvironment.getChallenge());
|
return createRequest(type, hookEnvironment.getChallenge());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private HgHookHandler.Request createRequest(RepositoryHookType type, String challenge) {
|
private DefaultHookHandler.Request createRequest(RepositoryHookType type, String challenge) {
|
||||||
String secret = CipherUtil.getInstance().encode("secret");
|
String secret = CipherUtil.getInstance().encode("secret");
|
||||||
return new HgHookHandler.Request(
|
return new DefaultHookHandler.Request(
|
||||||
secret, type, "42", challenge, "abc"
|
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();
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
Sockets.send(buffer, request);
|
Sockets.send(buffer, request);
|
||||||
ByteArrayInputStream input = new ByteArrayInputStream(buffer.toByteArray());
|
ByteArrayInputStream input = new ByteArrayInputStream(buffer.toByteArray());
|
||||||
@@ -185,7 +212,7 @@ class HgHookHandlerTest {
|
|||||||
|
|
||||||
handler.run();
|
handler.run();
|
||||||
|
|
||||||
return Sockets.read(new ByteArrayInputStream(output.toByteArray()), HgHookHandler.Response.class);
|
return Sockets.read(new ByteArrayInputStream(output.toByteArray()), DefaultHookHandler.Response.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -22,57 +22,49 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.repository;
|
package sonia.scm.repository.hooks;
|
||||||
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import sonia.scm.security.AccessToken;
|
import sonia.scm.NotFoundException;
|
||||||
import sonia.scm.security.Xsrf;
|
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.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.entry;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
class HgEnvironmentTest {
|
class HookContextProviderFactoryTest {
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
HgRepositoryHandler handler;
|
private RepositoryManager repositoryManager;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
HgHookManager hookManager;
|
private HgRepositoryHandler repositoryHandler;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private HgRepositoryFactory repositoryFactory;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private HookContextProviderFactory factory;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldExtractXsrfTokenWhenSet() {
|
void shouldCreateHookContextProvider() {
|
||||||
AccessToken accessToken = mock(AccessToken.class);
|
when(repositoryManager.get("42")).thenReturn(RepositoryTestData.create42Puzzle());
|
||||||
when(accessToken.compact()).thenReturn("");
|
HgHookContextProvider provider = factory.create("42", "xyz");
|
||||||
when(accessToken.getCustom(Xsrf.TOKEN_KEY)).thenReturn(of("XSRF Token"));
|
assertThat(provider).isNotNull();
|
||||||
when(hookManager.getAccessToken()).thenReturn(accessToken);
|
|
||||||
|
|
||||||
Map<String, String> environment = new HashMap<>();
|
|
||||||
HgEnvironment.prepareEnvironment(environment, handler, hookManager);
|
|
||||||
|
|
||||||
assertThat(environment).contains(entry("SCM_XSRF", "XSRF Token"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldIgnoreXsrfWhenNotSetButStillContainDummy() {
|
void shouldThrowNotFoundExceptionWithoutRepository() {
|
||||||
AccessToken accessToken = mock(AccessToken.class);
|
assertThrows(NotFoundException.class, () -> factory.create("42", "xyz"));
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ class HookServerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class HelloHandler implements Runnable {
|
public static class HelloHandler implements HookHandler {
|
||||||
|
|
||||||
private final Socket socket;
|
private final Socket socket;
|
||||||
|
|
||||||
|
|||||||
@@ -29,13 +29,16 @@ package sonia.scm.repository.spi;
|
|||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
|
||||||
|
import sonia.scm.repository.HgRepositoryFactory;
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
import sonia.scm.repository.HgTestUtil;
|
import sonia.scm.repository.HgTestUtil;
|
||||||
import sonia.scm.repository.RepositoryTestData;
|
import sonia.scm.repository.RepositoryTestData;
|
||||||
|
import sonia.scm.repository.hooks.HookEnvironment;
|
||||||
import sonia.scm.util.MockUtil;
|
import sonia.scm.util.MockUtil;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,31 +52,22 @@ public class AbstractHgCommandTestBase extends ZippedRepositoryTestBase
|
|||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @throws IOException
|
|
||||||
*/
|
*/
|
||||||
@After
|
@After
|
||||||
public void close() throws IOException
|
public void close() {
|
||||||
{
|
|
||||||
if (cmdContext != null)
|
if (cmdContext != null)
|
||||||
{
|
{
|
||||||
cmdContext.close();
|
cmdContext.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
@Before
|
@Before
|
||||||
public void initHgHandler() throws IOException {
|
public void initHgHandler() throws IOException {
|
||||||
this.handler = HgTestUtil.createHandler(tempFolder.newFolder());
|
this.handler = HgTestUtil.createHandler(tempFolder.newFolder());
|
||||||
|
|
||||||
HgTestUtil.checkForSkip(handler);
|
HgTestUtil.checkForSkip(handler);
|
||||||
|
|
||||||
cmdContext = new HgCommandContext(HgTestUtil.createHookManager(), handler,
|
HgRepositoryFactory factory = HgTestUtil.createFactory(handler, repositoryDirectory);
|
||||||
RepositoryTestData.createHeartOfGold(), repositoryDirectory);
|
cmdContext = new HgCommandContext(handler, factory, RepositoryTestData.createHeartOfGold());
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- set methods ----------------------------------------------------------
|
//~--- set methods ----------------------------------------------------------
|
||||||
|
|||||||
@@ -29,12 +29,10 @@ import com.google.inject.util.Providers;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import sonia.scm.repository.Branch;
|
import sonia.scm.repository.Branch;
|
||||||
import sonia.scm.repository.HgTestUtil;
|
|
||||||
import sonia.scm.repository.InternalRepositoryException;
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
import sonia.scm.repository.api.BranchRequest;
|
import sonia.scm.repository.api.BranchRequest;
|
||||||
import sonia.scm.repository.work.NoneCachingWorkingCopyPool;
|
import sonia.scm.repository.work.NoneCachingWorkingCopyPool;
|
||||||
import sonia.scm.repository.work.WorkdirProvider;
|
import sonia.scm.repository.work.WorkdirProvider;
|
||||||
import sonia.scm.web.HgRepositoryEnvironmentBuilder;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -47,10 +45,8 @@ public class HgBranchCommandTest extends AbstractHgCommandTestBase {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void initWorkingCopyFactory() {
|
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
|
@Override
|
||||||
public void configure(PullCommand pullCommand) {
|
public void configure(PullCommand pullCommand) {
|
||||||
// we do not want to configure http hooks in this unit test
|
// we do not want to configure http hooks in this unit test
|
||||||
|
|||||||
@@ -110,17 +110,10 @@ public class HgIncomingCommandTest extends IncomingOutgoingTestBase
|
|||||||
cmd.getIncomingChangesets(request);
|
cmd.getIncomingChangesets(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private HgIncomingCommand createIncomingCommand() {
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private HgIncomingCommand createIncomingCommand()
|
|
||||||
{
|
|
||||||
return new HgIncomingCommand(
|
return new HgIncomingCommand(
|
||||||
new HgCommandContext(
|
new HgCommandContext(handler, HgTestUtil.createFactory(handler, incomingDirectory), incomingRepository),
|
||||||
HgTestUtil.createHookManager(), handler, incomingRepository,
|
handler
|
||||||
incomingDirectory), handler);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,12 +40,11 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
|
|
||||||
public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
||||||
|
|
||||||
|
|
||||||
private HgModificationsCommand outgoingModificationsCommand;
|
private HgModificationsCommand outgoingModificationsCommand;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void init() {
|
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);
|
outgoingModificationsCommand = new HgModificationsCommand(outgoingContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,10 +115,10 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
|||||||
assertThat(modifications).isNotNull();
|
assertThat(modifications).isNotNull();
|
||||||
assertThat(modifications.getAdded())
|
assertThat(modifications.getAdded())
|
||||||
.as("added files modifications")
|
.as("added files modifications")
|
||||||
.hasSize(0);
|
.isEmpty();
|
||||||
assertThat(modifications.getModified())
|
assertThat(modifications.getModified())
|
||||||
.as("modified files modifications")
|
.as("modified files modifications")
|
||||||
.hasSize(0);
|
.isEmpty();
|
||||||
assertThat(modifications.getRemoved())
|
assertThat(modifications.getRemoved())
|
||||||
.as("removed files modifications")
|
.as("removed files modifications")
|
||||||
.hasSize(1)
|
.hasSize(1)
|
||||||
@@ -136,10 +135,10 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
|||||||
assertThat(modifications).isNotNull();
|
assertThat(modifications).isNotNull();
|
||||||
assertThat(modifications.getAdded())
|
assertThat(modifications.getAdded())
|
||||||
.as("added files modifications")
|
.as("added files modifications")
|
||||||
.hasSize(0);
|
.isEmpty();
|
||||||
assertThat(modifications.getModified())
|
assertThat(modifications.getModified())
|
||||||
.as("modified files modifications")
|
.as("modified files modifications")
|
||||||
.hasSize(0);
|
.isEmpty();
|
||||||
assertThat(modifications.getRemoved())
|
assertThat(modifications.getRemoved())
|
||||||
.as("removed files modifications")
|
.as("removed files modifications")
|
||||||
.isEmpty();
|
.isEmpty();
|
||||||
@@ -161,10 +160,10 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
|||||||
assertThat(modifications).isNotNull();
|
assertThat(modifications).isNotNull();
|
||||||
assertThat(modifications.getAdded())
|
assertThat(modifications.getAdded())
|
||||||
.as("added files modifications")
|
.as("added files modifications")
|
||||||
.hasSize(0);
|
.isEmpty();
|
||||||
assertThat(modifications.getModified())
|
assertThat(modifications.getModified())
|
||||||
.as("modified files modifications")
|
.as("modified files modifications")
|
||||||
.hasSize(0);
|
.isEmpty();
|
||||||
assertThat(modifications.getRemoved())
|
assertThat(modifications.getRemoved())
|
||||||
.as("removed files modifications")
|
.as("removed files modifications")
|
||||||
.isEmpty();
|
.isEmpty();
|
||||||
@@ -189,7 +188,7 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
|||||||
assertThat(modifications).isNotNull();
|
assertThat(modifications).isNotNull();
|
||||||
assertThat(modifications.getAdded())
|
assertThat(modifications.getAdded())
|
||||||
.as("added files modifications")
|
.as("added files modifications")
|
||||||
.hasSize(0);
|
.isEmpty();
|
||||||
assertThat(modifications.getModified())
|
assertThat(modifications.getModified())
|
||||||
.as("modified files modifications")
|
.as("modified files modifications")
|
||||||
.hasSize(1)
|
.hasSize(1)
|
||||||
@@ -197,10 +196,10 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
|||||||
.containsOnly(file);
|
.containsOnly(file);
|
||||||
assertThat(modifications.getRemoved())
|
assertThat(modifications.getRemoved())
|
||||||
.as("removed files modifications")
|
.as("removed files modifications")
|
||||||
.hasSize(0);
|
.isEmpty();
|
||||||
assertThat(modifications.getRenamed())
|
assertThat(modifications.getRenamed())
|
||||||
.as("renamed files modifications")
|
.as("renamed files modifications")
|
||||||
.hasSize(0);
|
.isEmpty();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,13 +213,13 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
|||||||
.containsOnly(addedFile);
|
.containsOnly(addedFile);
|
||||||
assertThat(modifications.getModified())
|
assertThat(modifications.getModified())
|
||||||
.as("modified files modifications")
|
.as("modified files modifications")
|
||||||
.hasSize(0);
|
.isEmpty();
|
||||||
assertThat(modifications.getRemoved())
|
assertThat(modifications.getRemoved())
|
||||||
.as("removed files modifications")
|
.as("removed files modifications")
|
||||||
.hasSize(0);
|
.isEmpty();
|
||||||
assertThat(modifications.getRenamed())
|
assertThat(modifications.getRenamed())
|
||||||
.as("renamed files modifications")
|
.as("renamed files modifications")
|
||||||
.hasSize(0);
|
.isEmpty();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
import com.google.inject.util.Providers;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -32,12 +31,9 @@ import org.junit.rules.TemporaryFolder;
|
|||||||
import sonia.scm.AlreadyExistsException;
|
import sonia.scm.AlreadyExistsException;
|
||||||
import sonia.scm.NoChangesMadeException;
|
import sonia.scm.NoChangesMadeException;
|
||||||
import sonia.scm.NotFoundException;
|
import sonia.scm.NotFoundException;
|
||||||
import sonia.scm.repository.HgHookManager;
|
|
||||||
import sonia.scm.repository.HgTestUtil;
|
|
||||||
import sonia.scm.repository.Person;
|
import sonia.scm.repository.Person;
|
||||||
import sonia.scm.repository.work.NoneCachingWorkingCopyPool;
|
import sonia.scm.repository.work.NoneCachingWorkingCopyPool;
|
||||||
import sonia.scm.repository.work.WorkdirProvider;
|
import sonia.scm.repository.work.WorkdirProvider;
|
||||||
import sonia.scm.web.HgRepositoryEnvironmentBuilder;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
@@ -54,9 +50,7 @@ public class HgModifyCommandTest extends AbstractHgCommandTestBase {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void initHgModifyCommand() {
|
public void initHgModifyCommand() {
|
||||||
HgHookManager hookManager = HgTestUtil.createHookManager();
|
SimpleHgWorkingCopyFactory workingCopyFactory = new SimpleHgWorkingCopyFactory(new NoneCachingWorkingCopyPool(new WorkdirProvider())) {
|
||||||
HgRepositoryEnvironmentBuilder environmentBuilder = new HgRepositoryEnvironmentBuilder(handler, hookManager);
|
|
||||||
SimpleHgWorkingCopyFactory workingCopyFactory = new SimpleHgWorkingCopyFactory(Providers.of(environmentBuilder), new NoneCachingWorkingCopyPool(new WorkdirProvider())) {
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(com.aragost.javahg.commands.PullCommand pullCommand) {
|
public void configure(com.aragost.javahg.commands.PullCommand pullCommand) {
|
||||||
// we do not want to configure http hooks in this unit test
|
// we do not want to configure http hooks in this unit test
|
||||||
|
|||||||
@@ -106,17 +106,10 @@ public class HgOutgoingCommandTest extends IncomingOutgoingTestBase
|
|||||||
System.out.println(cpr.getChangesets());
|
System.out.println(cpr.getChangesets());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private HgOutgoingCommand createOutgoingCommand() {
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private HgOutgoingCommand createOutgoingCommand()
|
|
||||||
{
|
|
||||||
return new HgOutgoingCommand(
|
return new HgOutgoingCommand(
|
||||||
new HgCommandContext(
|
new HgCommandContext(handler, HgTestUtil.createFactory(handler, outgoingDirectory), outgoingRepository),
|
||||||
HgTestUtil.createHookManager(), handler, outgoingRepository,
|
handler
|
||||||
outgoingDirectory), 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -39,7 +39,6 @@ import org.junit.Rule;
|
|||||||
import org.junit.rules.TemporaryFolder;
|
import org.junit.rules.TemporaryFolder;
|
||||||
import sonia.scm.AbstractTestBase;
|
import sonia.scm.AbstractTestBase;
|
||||||
import sonia.scm.repository.HgConfig;
|
import sonia.scm.repository.HgConfig;
|
||||||
import sonia.scm.repository.HgContext;
|
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
import sonia.scm.repository.HgTestUtil;
|
import sonia.scm.repository.HgTestUtil;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
@@ -88,7 +87,6 @@ public abstract class IncomingOutgoingTestBase extends AbstractTestBase
|
|||||||
when(handler.getDirectory(outgoingRepository.getId())).thenReturn(
|
when(handler.getDirectory(outgoingRepository.getId())).thenReturn(
|
||||||
outgoingDirectory);
|
outgoingDirectory);
|
||||||
when(handler.getConfig()).thenReturn(temp.getConfig());
|
when(handler.getConfig()).thenReturn(temp.getConfig());
|
||||||
when(handler.getHgContext()).thenReturn(new HgContext());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- set methods ----------------------------------------------------------
|
//~--- set methods ----------------------------------------------------------
|
||||||
|
|||||||
@@ -30,12 +30,11 @@ import org.junit.Before;
|
|||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.TemporaryFolder;
|
import org.junit.rules.TemporaryFolder;
|
||||||
import sonia.scm.repository.HgHookManager;
|
import sonia.scm.repository.HgEnvironmentBuilder;
|
||||||
import sonia.scm.repository.HgTestUtil;
|
import sonia.scm.repository.HgTestUtil;
|
||||||
import sonia.scm.repository.work.SimpleCachingWorkingCopyPool;
|
import sonia.scm.repository.work.SimpleCachingWorkingCopyPool;
|
||||||
import sonia.scm.repository.work.WorkdirProvider;
|
import sonia.scm.repository.work.WorkdirProvider;
|
||||||
import sonia.scm.repository.work.WorkingCopy;
|
import sonia.scm.repository.work.WorkingCopy;
|
||||||
import sonia.scm.web.HgRepositoryEnvironmentBuilder;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -57,9 +56,7 @@ public class SimpleHgWorkingCopyFactoryTest extends AbstractHgCommandTestBase {
|
|||||||
@Before
|
@Before
|
||||||
public void bindScmProtocol() throws IOException {
|
public void bindScmProtocol() throws IOException {
|
||||||
workdirProvider = new WorkdirProvider(temporaryFolder.newFolder());
|
workdirProvider = new WorkdirProvider(temporaryFolder.newFolder());
|
||||||
HgHookManager hookManager = HgTestUtil.createHookManager();
|
workingCopyFactory = new SimpleHgWorkingCopyFactory(new SimpleCachingWorkingCopyPool(workdirProvider)) {
|
||||||
HgRepositoryEnvironmentBuilder environmentBuilder = new HgRepositoryEnvironmentBuilder(handler, hookManager);
|
|
||||||
workingCopyFactory = new SimpleHgWorkingCopyFactory(Providers.of(environmentBuilder), new SimpleCachingWorkingCopyPool(workdirProvider)) {
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(com.aragost.javahg.commands.PullCommand pullCommand) {
|
public void configure(com.aragost.javahg.commands.PullCommand pullCommand) {
|
||||||
// we do not want to configure http hooks in this unit test
|
// 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -114,14 +114,14 @@ public class ScmSecurityModule extends ShiroWebModule
|
|||||||
}
|
}
|
||||||
|
|
||||||
// bind constant
|
// bind constant
|
||||||
bindConstant().annotatedWith(Names.named("shiro.loginUrl")).to(
|
bindConstant().annotatedWith(Names.named("shiro.loginUrl")).to("/index.html");
|
||||||
"/index.html");
|
|
||||||
|
|
||||||
// disable access to mustache resources
|
// disable access to mustache resources
|
||||||
addFilterChain("/**.mustache", filterConfig(ROLES, "nobody"));
|
addFilterChain("/**.mustache", filterConfig(ROLES, "nobody"));
|
||||||
|
|
||||||
// disable session
|
// disable session
|
||||||
addFilterChain("/**", NO_SESSION_CREATION);
|
addFilterChain("/**", NO_SESSION_CREATION);
|
||||||
|
bindConstant().annotatedWith(Names.named("shiro.sessionStorageEnabled")).to(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user