mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-03-06 12:20:56 +01:00
Merge branch 'develop' into feature/import_git_from_url
This commit is contained in:
@@ -30,11 +30,13 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@SuppressWarnings("java:S2160") // we don't need equals for dto
|
||||
public class HgConfigDto extends HalRepresentation implements UpdateHgConfigDto {
|
||||
|
||||
|
||||
private boolean disabled;
|
||||
|
||||
private String encoding;
|
||||
@@ -44,7 +46,6 @@ public class HgConfigDto extends HalRepresentation implements UpdateHgConfigDto
|
||||
private boolean useOptimizedBytecode;
|
||||
private boolean showRevisionInId;
|
||||
private boolean enableHttpPostArgs;
|
||||
private boolean disableHookSSLValidation;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
|
||||
@@ -39,7 +39,5 @@ interface UpdateHgConfigDto {
|
||||
|
||||
boolean isShowRevisionInId();
|
||||
|
||||
boolean isDisableHookSSLValidation();
|
||||
|
||||
boolean isEnableHttpPostArgs();
|
||||
}
|
||||
|
||||
@@ -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,143 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import sonia.scm.TransactionId;
|
||||
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.security.Xsrf;
|
||||
import sonia.scm.web.HgUtil;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
@Singleton
|
||||
public class DefaultHgEnvironmentBuilder implements HgEnvironmentBuilder {
|
||||
|
||||
@VisibleForTesting
|
||||
static final String ENV_PYTHON_PATH = "PYTHONPATH";
|
||||
@VisibleForTesting
|
||||
static final String ENV_HOOK_PORT = "SCM_HOOK_PORT";
|
||||
@VisibleForTesting
|
||||
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";
|
||||
@VisibleForTesting
|
||||
static final String ENV_TRANSACTION_ID = "SCM_TRANSACTION_ID";
|
||||
|
||||
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()));
|
||||
env.put(ENV_BEARER_TOKEN, accessToken());
|
||||
env.put(ENV_CHALLENGE, hookEnvironment.getChallenge());
|
||||
TransactionId.get().ifPresent(transactionId -> env.put(ENV_TRANSACTION_ID, transactionId));
|
||||
}
|
||||
|
||||
private String accessToken() {
|
||||
AccessToken accessToken = accessTokenBuilderFactory.create()
|
||||
// disable xsrf protection, because we can not access the http servlet request for verification
|
||||
.custom(Xsrf.TOKEN_KEY, null)
|
||||
.build();
|
||||
return CipherUtil.getInstance().encode(accessToken.compact());
|
||||
}
|
||||
|
||||
private synchronized int getHookPort() {
|
||||
if (hookPort > 0) {
|
||||
return hookPort;
|
||||
}
|
||||
try {
|
||||
hookPort = server.start();
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException("failed to start mercurial hook server");
|
||||
}
|
||||
return hookPort;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
|
||||
@@ -36,20 +36,10 @@ import javax.xml.bind.annotation.XmlTransient;
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@XmlRootElement(name = "config")
|
||||
public class HgConfig extends RepositoryConfig
|
||||
{
|
||||
public class HgConfig extends RepositoryConfig {
|
||||
|
||||
public static final String PERMISSION = "hg";
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*/
|
||||
public HgConfig() {}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
|
||||
@Override
|
||||
@XmlTransient // Only for permission checks, don't serialize to XML
|
||||
public String getId() {
|
||||
@@ -123,10 +113,6 @@ public class HgConfig extends RepositoryConfig
|
||||
return useOptimizedBytecode;
|
||||
}
|
||||
|
||||
public boolean isDisableHookSSLValidation() {
|
||||
return disableHookSSLValidation;
|
||||
}
|
||||
|
||||
public boolean isEnableHttpPostArgs() {
|
||||
return enableHttpPostArgs;
|
||||
}
|
||||
@@ -216,10 +202,6 @@ public class HgConfig extends RepositoryConfig
|
||||
this.useOptimizedBytecode = useOptimizedBytecode;
|
||||
}
|
||||
|
||||
public void setDisableHookSSLValidation(boolean disableHookSSLValidation) {
|
||||
this.disableHookSSLValidation = disableHookSSLValidation;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
@@ -242,9 +224,4 @@ public class HgConfig extends RepositoryConfig
|
||||
|
||||
private boolean enableHttpPostArgs = false;
|
||||
|
||||
/**
|
||||
* disable validation of ssl certificates for mercurial hook
|
||||
* @see <a href="https://goo.gl/zH5eY8">Issue 959</a>
|
||||
*/
|
||||
private boolean disableHookSSLValidation = false;
|
||||
}
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class HgContext
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*/
|
||||
public HgContext() {}
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param pending
|
||||
*/
|
||||
public HgContext(boolean pending)
|
||||
{
|
||||
this.pending = pending;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param pending
|
||||
* @param systemEnvironment
|
||||
*/
|
||||
public HgContext(boolean pending, boolean systemEnvironment)
|
||||
{
|
||||
this.pending = pending;
|
||||
this.systemEnvironment = systemEnvironment;
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isPending()
|
||||
{
|
||||
return pending;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isSystemEnvironment()
|
||||
{
|
||||
return systemEnvironment;
|
||||
}
|
||||
|
||||
//~--- set methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param pending
|
||||
*/
|
||||
public void setPending(boolean pending)
|
||||
{
|
||||
this.pending = pending;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param systemEnvironment
|
||||
*/
|
||||
public void setSystemEnvironment(boolean systemEnvironment)
|
||||
{
|
||||
this.systemEnvironment = systemEnvironment;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private boolean pending = false;
|
||||
|
||||
/** Field description */
|
||||
private boolean systemEnvironment = true;
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.inject.OutOfScopeException;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.ProvisionException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Injection provider for {@link HgContext}.
|
||||
* This provider returns an instance {@link HgContext} from request scope, if no {@link HgContext} could be found in
|
||||
* request scope (mostly because the scope is not available) a new {@link HgContext} gets returned.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class HgContextProvider implements Provider<HgContext>
|
||||
{
|
||||
|
||||
/**
|
||||
* the LOG for HgContextProvider
|
||||
*/
|
||||
private static final Logger LOG =
|
||||
LoggerFactory.getLogger(HgContextProvider.class);
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
private Provider<HgContextRequestStore> requestStoreProvider;
|
||||
|
||||
@Inject
|
||||
public HgContextProvider(Provider<HgContextRequestStore> requestStoreProvider) {
|
||||
this.requestStoreProvider = requestStoreProvider;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public HgContextProvider() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public HgContext get() {
|
||||
HgContext context = fetchContextFromRequest();
|
||||
if (context != null) {
|
||||
LOG.trace("return HgContext from request store");
|
||||
return context;
|
||||
}
|
||||
LOG.trace("could not find context in request scope, returning new instance");
|
||||
return new HgContext();
|
||||
}
|
||||
|
||||
private HgContext fetchContextFromRequest() {
|
||||
try {
|
||||
if (requestStoreProvider != null) {
|
||||
return requestStoreProvider.get().get();
|
||||
} else {
|
||||
LOG.trace("no request store provider defined, could not return context from request");
|
||||
return null;
|
||||
}
|
||||
} catch (ProvisionException ex) {
|
||||
if (ex.getCause() instanceof OutOfScopeException) {
|
||||
LOG.trace("we are currently out of request scope, failed to retrieve context");
|
||||
return null;
|
||||
} else {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.inject.ProvisionException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.security.AccessToken;
|
||||
import sonia.scm.security.CipherUtil;
|
||||
import sonia.scm.security.Xsrf;
|
||||
import sonia.scm.web.HgUtil;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Map;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public final class HgEnvironment
|
||||
{
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HgEnvironment.class);
|
||||
|
||||
/** Field description */
|
||||
public static final String ENV_PYTHON_PATH = "PYTHONPATH";
|
||||
|
||||
/** Field description */
|
||||
private static final String ENV_CHALLENGE = "SCM_CHALLENGE";
|
||||
|
||||
/** Field description */
|
||||
private static final String ENV_URL = "SCM_URL";
|
||||
|
||||
private static final String SCM_BEARER_TOKEN = "SCM_BEARER_TOKEN";
|
||||
|
||||
private static final String SCM_XSRF = "SCM_XSRF";
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*/
|
||||
private HgEnvironment() {}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param environment
|
||||
* @param handler
|
||||
* @param hookManager
|
||||
*/
|
||||
public static void prepareEnvironment(Map<String, String> environment,
|
||||
HgRepositoryHandler handler, HgHookManager hookManager)
|
||||
{
|
||||
prepareEnvironment(environment, handler, hookManager, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param environment
|
||||
* @param handler
|
||||
* @param hookManager
|
||||
* @param request
|
||||
*/
|
||||
public static void prepareEnvironment(Map<String, String> environment,
|
||||
HgRepositoryHandler handler, HgHookManager hookManager,
|
||||
HttpServletRequest request)
|
||||
{
|
||||
String hookUrl;
|
||||
|
||||
if (request != null)
|
||||
{
|
||||
hookUrl = hookManager.createUrl(request);
|
||||
}
|
||||
else
|
||||
{
|
||||
hookUrl = hookManager.createUrl();
|
||||
}
|
||||
|
||||
try {
|
||||
AccessToken accessToken = hookManager.getAccessToken();
|
||||
environment.put(SCM_BEARER_TOKEN, CipherUtil.getInstance().encode(accessToken.compact()));
|
||||
extractXsrfKey(environment, accessToken);
|
||||
} catch (ProvisionException e) {
|
||||
LOG.debug("could not create bearer token; looks like currently we are not in a request; probably you can ignore the following exception:", e);
|
||||
}
|
||||
environment.put(ENV_PYTHON_PATH, HgUtil.getPythonPath(handler.getConfig()));
|
||||
environment.put(ENV_URL, hookUrl);
|
||||
environment.put(ENV_CHALLENGE, hookManager.getChallenge());
|
||||
}
|
||||
|
||||
private static void extractXsrfKey(Map<String, String> environment, AccessToken accessToken) {
|
||||
environment.put(SCM_XSRF, accessToken.<String>getCustom(Xsrf.TOKEN_KEY).orElse("-"));
|
||||
}
|
||||
}
|
||||
@@ -21,30 +21,15 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
import com.google.inject.ImplementedBy;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class HgVersionHandler extends AbstractHgHandler
|
||||
{
|
||||
|
||||
public HgVersionHandler(HgRepositoryHandler handler, HgContext context,
|
||||
File directory)
|
||||
{
|
||||
super(handler, context, null, directory);
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
public HgVersion getVersion() throws IOException {
|
||||
return getResultFromScript(HgVersion.class, HgPythonScript.VERSION);
|
||||
}
|
||||
@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;
|
||||
}
|
||||
@@ -38,80 +38,30 @@ import java.io.File;
|
||||
*/
|
||||
public enum HgPythonScript {
|
||||
|
||||
HOOK("scmhooks.py"), HGWEB("hgweb.py"), VERSION("version.py");
|
||||
HOOK("scmhooks.py"), HGWEB("hgweb.py");
|
||||
|
||||
/** Field description */
|
||||
private static final String BASE_DIRECTORY =
|
||||
"lib".concat(File.separator).concat("python");
|
||||
|
||||
/** Field description */
|
||||
private static final String BASE_DIRECTORY = "lib".concat(File.separator).concat("python");
|
||||
private static final String BASE_RESOURCE = "/sonia/scm/python/";
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
private HgPythonScript(String name)
|
||||
{
|
||||
HgPythonScript(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param context
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static File getScriptDirectory(SCMContextProvider context)
|
||||
{
|
||||
public static File getScriptDirectory(SCMContextProvider context) {
|
||||
return new File(context.getBaseDirectory(), BASE_DIRECTORY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param context
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public File getFile(SCMContextProvider context)
|
||||
{
|
||||
public File getFile(SCMContextProvider context) {
|
||||
return new File(getScriptDirectory(context), name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getName()
|
||||
{
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getResourcePath()
|
||||
{
|
||||
public String getResourcePath() {
|
||||
return BASE_RESOURCE.concat(name);
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private String name;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import com.aragost.javahg.RepositoryConfiguration;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.repository.hooks.HookEnvironment;
|
||||
import sonia.scm.repository.spi.javahg.HgFileviewExtension;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.File;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.charset.UnsupportedCharsetException;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Singleton
|
||||
public class HgRepositoryFactory {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HgRepositoryFactory.class);
|
||||
|
||||
private final HgRepositoryHandler handler;
|
||||
private final HookEnvironment hookEnvironment;
|
||||
private final HgEnvironmentBuilder environmentBuilder;
|
||||
private final Function<Repository, File> directoryResolver;
|
||||
|
||||
@Inject
|
||||
public HgRepositoryFactory(HgRepositoryHandler handler, HookEnvironment hookEnvironment, HgEnvironmentBuilder environmentBuilder) {
|
||||
this(
|
||||
handler, hookEnvironment, environmentBuilder,
|
||||
repository -> handler.getDirectory(repository.getId())
|
||||
);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public HgRepositoryFactory(HgRepositoryHandler handler, HookEnvironment hookEnvironment, HgEnvironmentBuilder environmentBuilder, Function<Repository, File> directoryResolver) {
|
||||
this.handler = handler;
|
||||
this.hookEnvironment = hookEnvironment;
|
||||
this.environmentBuilder = environmentBuilder;
|
||||
this.directoryResolver = directoryResolver;
|
||||
}
|
||||
|
||||
public com.aragost.javahg.Repository openForRead(Repository repository) {
|
||||
return open(repository, environmentBuilder.read(repository));
|
||||
}
|
||||
|
||||
public com.aragost.javahg.Repository openForWrite(Repository repository) {
|
||||
return open(repository, environmentBuilder.write(repository));
|
||||
}
|
||||
|
||||
private com.aragost.javahg.Repository open(Repository repository, Map<String, String> environment) {
|
||||
File directory = directoryResolver.apply(repository);
|
||||
|
||||
RepositoryConfiguration repoConfiguration = RepositoryConfiguration.DEFAULT;
|
||||
repoConfiguration.getEnvironment().putAll(environment);
|
||||
repoConfiguration.addExtension(HgFileviewExtension.class);
|
||||
|
||||
boolean pending = hookEnvironment.isPending();
|
||||
repoConfiguration.setEnablePendingChangesets(pending);
|
||||
|
||||
Charset encoding = encoding();
|
||||
repoConfiguration.setEncoding(encoding);
|
||||
|
||||
repoConfiguration.setHgBin(handler.getConfig().getHgBinary());
|
||||
|
||||
LOG.trace("open hg repository {}: encoding: {}, pending: {}", directory, encoding, pending);
|
||||
|
||||
return com.aragost.javahg.Repository.open(repoConfiguration, directory);
|
||||
}
|
||||
|
||||
private Charset encoding() {
|
||||
String charset = handler.getConfig().getEncoding();
|
||||
try {
|
||||
return Charset.forName(charset);
|
||||
} catch (UnsupportedCharsetException ex) {
|
||||
LOG.warn("unknown charset {} in hg config, fallback to utf-8", charset);
|
||||
return StandardCharsets.UTF_8;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,11 +27,9 @@ package sonia.scm.repository;
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.ConfigurationException;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.autoconfig.AutoConfigurator;
|
||||
import sonia.scm.installer.HgInstaller;
|
||||
@@ -43,14 +41,14 @@ import sonia.scm.io.INISection;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.repository.spi.HgRepositoryServiceProvider;
|
||||
import sonia.scm.repository.spi.HgVersionCommand;
|
||||
import sonia.scm.repository.spi.HgWorkingCopyFactory;
|
||||
import sonia.scm.store.ConfigurationStoreFactory;
|
||||
import sonia.scm.util.IOUtil;
|
||||
import sonia.scm.util.SystemUtil;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -63,14 +61,15 @@ import java.util.Optional;
|
||||
public class HgRepositoryHandler
|
||||
extends AbstractSimpleRepositoryHandler<HgConfig> {
|
||||
|
||||
public static final String PATH_HOOK = ".hook-1.8";
|
||||
public static final String RESOURCE_VERSION = "sonia/scm/version/scm-hg-plugin";
|
||||
public static final String TYPE_DISPLAYNAME = "Mercurial";
|
||||
public static final String TYPE_NAME = "hg";
|
||||
public static final RepositoryType TYPE = new RepositoryType(TYPE_NAME,
|
||||
public static final RepositoryType TYPE = new RepositoryType(
|
||||
TYPE_NAME,
|
||||
TYPE_DISPLAYNAME,
|
||||
HgRepositoryServiceProvider.COMMANDS,
|
||||
HgRepositoryServiceProvider.FEATURES);
|
||||
HgRepositoryServiceProvider.FEATURES
|
||||
);
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(HgRepositoryHandler.class);
|
||||
|
||||
@@ -78,28 +77,14 @@ public class HgRepositoryHandler
|
||||
private static final String CONFIG_SECTION_SCMM = "scmm";
|
||||
private static final String CONFIG_KEY_REPOSITORY_ID = "repositoryid";
|
||||
|
||||
private final Provider<HgContext> hgContextProvider;
|
||||
|
||||
private final HgWorkingCopyFactory workingCopyFactory;
|
||||
|
||||
private final JAXBContext jaxbContext;
|
||||
|
||||
@Inject
|
||||
public HgRepositoryHandler(ConfigurationStoreFactory storeFactory,
|
||||
Provider<HgContext> hgContextProvider,
|
||||
RepositoryLocationResolver repositoryLocationResolver,
|
||||
PluginLoader pluginLoader, HgWorkingCopyFactory workingCopyFactory) {
|
||||
super(storeFactory, repositoryLocationResolver, pluginLoader);
|
||||
this.hgContextProvider = hgContextProvider;
|
||||
this.workingCopyFactory = workingCopyFactory;
|
||||
|
||||
try {
|
||||
this.jaxbContext = JAXBContext.newInstance(BrowserResult.class,
|
||||
BlameResult.class, Changeset.class, ChangesetPagingResult.class,
|
||||
HgVersion.class);
|
||||
} catch (JAXBException ex) {
|
||||
throw new ConfigurationException("could not create jaxbcontext", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void doAutoConfiguration(HgConfig autoConfig) {
|
||||
@@ -107,8 +92,7 @@ public class HgRepositoryHandler
|
||||
|
||||
try {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("installing mercurial with {}",
|
||||
installer.getClass().getName());
|
||||
logger.debug("installing mercurial with {}", installer.getClass().getName());
|
||||
}
|
||||
|
||||
installer.install(baseDirectory, autoConfig);
|
||||
@@ -154,16 +138,6 @@ public class HgRepositoryHandler
|
||||
}
|
||||
}
|
||||
|
||||
public HgContext getHgContext() {
|
||||
HgContext context = hgContextProvider.get();
|
||||
|
||||
if (context == null) {
|
||||
context = new HgContext();
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImportHandler getImportHandler() {
|
||||
return new HgImportHandler(this);
|
||||
@@ -176,28 +150,14 @@ public class HgRepositoryHandler
|
||||
|
||||
@Override
|
||||
public String getVersionInformation() {
|
||||
String version = getStringFromResource(RESOURCE_VERSION,
|
||||
DEFAULT_VERSION_INFORMATION);
|
||||
return getVersionInformation(new HgVersionCommand(getConfig()));
|
||||
}
|
||||
|
||||
try {
|
||||
HgVersion hgVersion = new HgVersionHandler(this, hgContextProvider.get(),
|
||||
baseDirectory).getVersion();
|
||||
|
||||
if (hgVersion != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("mercurial/python informations: {}", hgVersion);
|
||||
}
|
||||
|
||||
version = MessageFormat.format(version, hgVersion.getPython(),
|
||||
hgVersion.getMercurial());
|
||||
} else if (logger.isWarnEnabled()) {
|
||||
logger.warn("could not retrieve version informations");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logger.error("could not read version informations", ex);
|
||||
}
|
||||
|
||||
return version;
|
||||
String getVersionInformation(HgVersionCommand command) {
|
||||
String version = getStringFromResource(RESOURCE_VERSION, DEFAULT_VERSION_INFORMATION);
|
||||
HgVersion hgVersion = command.get();
|
||||
logger.debug("mercurial/python informations: {}", hgVersion);
|
||||
return MessageFormat.format(version, hgVersion.getPython(), hgVersion.getMercurial());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -253,28 +213,24 @@ public class HgRepositoryHandler
|
||||
logger.debug("write python script {}", script.getName());
|
||||
}
|
||||
|
||||
InputStream content = null;
|
||||
OutputStream output = null;
|
||||
|
||||
try {
|
||||
content = HgRepositoryHandler.class.getResourceAsStream(
|
||||
script.getResourcePath());
|
||||
output = new FileOutputStream(script.getFile(context));
|
||||
try (InputStream content = input(script); OutputStream output = output(context, script)) {
|
||||
IOUtil.copy(content, output);
|
||||
} catch (IOException ex) {
|
||||
logger.error("could not write script", ex);
|
||||
} finally {
|
||||
IOUtil.close(content);
|
||||
IOUtil.close(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream input(HgPythonScript script) {
|
||||
return HgRepositoryHandler.class.getResourceAsStream(script.getResourcePath());
|
||||
}
|
||||
|
||||
private OutputStream output(SCMContextProvider context, HgPythonScript script) throws FileNotFoundException {
|
||||
return new FileOutputStream(script.getFile(context));
|
||||
}
|
||||
|
||||
public HgWorkingCopyFactory getWorkingCopyFactory() {
|
||||
return workingCopyFactory;
|
||||
}
|
||||
|
||||
public JAXBContext getJaxbContext() {
|
||||
return jaxbContext;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,8 @@
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
@@ -37,13 +35,14 @@ import javax.xml.bind.annotation.XmlRootElement;
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@XmlRootElement(name = "version")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@EqualsAndHashCode
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class HgVersion {
|
||||
|
||||
public static final String UNKNOWN = "x.y.z (unknown)";
|
||||
|
||||
private String mercurial;
|
||||
private String python;
|
||||
}
|
||||
|
||||
@@ -21,75 +21,31 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public final class HgHookMessage implements Serializable
|
||||
{
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
public final class HgHookMessage implements Serializable {
|
||||
|
||||
/** Field description */
|
||||
private static final long serialVersionUID = 1804492842452344326L;
|
||||
|
||||
//~--- constant enums -------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Enum description
|
||||
*
|
||||
*/
|
||||
public static enum Severity { NOTE, ERROR; }
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param severity
|
||||
* @param message
|
||||
*/
|
||||
public HgHookMessage(Severity severity, String message)
|
||||
{
|
||||
this.severity = severity;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getMessage()
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Severity getSeverity()
|
||||
{
|
||||
return severity;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private Severity severity;
|
||||
private String message;
|
||||
|
||||
/** Field description */
|
||||
private Severity severity;
|
||||
public enum Severity { NOTE, ERROR }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.hooks;
|
||||
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.authc.AuthenticationException;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.ExceptionWithContext;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.TransactionId;
|
||||
import sonia.scm.repository.RepositoryHookType;
|
||||
import sonia.scm.repository.api.HgHookMessage;
|
||||
import sonia.scm.repository.spi.HgHookContextProvider;
|
||||
import sonia.scm.repository.spi.HookEventFacade;
|
||||
import sonia.scm.security.BearerToken;
|
||||
import sonia.scm.security.CipherUtil;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
class DefaultHookHandler implements HookHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultHookHandler.class);
|
||||
|
||||
private final HookEventFacade hookEventFacade;
|
||||
private final HookEnvironment environment;
|
||||
private final HookContextProviderFactory hookContextProviderFactory;
|
||||
private final Socket socket;
|
||||
|
||||
@Inject
|
||||
public DefaultHookHandler(HookContextProviderFactory hookContextProviderFactory, HookEventFacade hookEventFacade, HookEnvironment environment, @Assisted Socket socket) {
|
||||
this.hookContextProviderFactory = hookContextProviderFactory;
|
||||
this.hookEventFacade = hookEventFacade;
|
||||
this.environment = environment;
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
LOG.trace("start handling hook protocol");
|
||||
try (InputStream input = socket.getInputStream(); OutputStream output = socket.getOutputStream()) {
|
||||
handleHookRequest(input, output);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("failed to read hook request", e);
|
||||
} finally {
|
||||
LOG.trace("close client socket");
|
||||
TransactionId.clear();
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleHookRequest(InputStream input, OutputStream output) throws IOException {
|
||||
Request request = Sockets.receive(input, Request.class);
|
||||
TransactionId.set(request.getTransactionId());
|
||||
Response response = handleHookRequest(request);
|
||||
Sockets.send(output, response);
|
||||
}
|
||||
|
||||
private Response handleHookRequest(Request request) {
|
||||
LOG.trace("process {} hook for node {}", request.getType(), request.getNode());
|
||||
|
||||
if (!environment.isAcceptAble(request.getChallenge())) {
|
||||
LOG.warn("received hook with invalid challenge: {}", request.getChallenge());
|
||||
return error("invalid hook challenge");
|
||||
}
|
||||
|
||||
try {
|
||||
authenticate(request);
|
||||
|
||||
return fireHook(request);
|
||||
} catch (AuthenticationException ex) {
|
||||
LOG.warn("hook authentication failed", ex);
|
||||
return error("hook authentication failed");
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private Response fireHook(Request request) {
|
||||
HgHookContextProvider context = hookContextProviderFactory.create(request.getRepositoryId(), request.getNode());
|
||||
|
||||
try {
|
||||
environment.setPending(request.getType() == RepositoryHookType.PRE_RECEIVE);
|
||||
|
||||
hookEventFacade.handle(request.getRepositoryId()).fireHookEvent(request.getType(), context);
|
||||
|
||||
return new Response(context.getHgMessageProvider().getMessages(), false);
|
||||
|
||||
} catch (NotFoundException ex) {
|
||||
LOG.warn("could not find repository with id {}", request.getRepositoryId(), ex);
|
||||
return error("repository not found");
|
||||
} catch (ExceptionWithContext ex) {
|
||||
LOG.debug("scm exception on hook occurred", ex);
|
||||
return error(context, ex.getMessage());
|
||||
} catch (Exception ex) {
|
||||
LOG.warn("unknown error on hook occurred", ex);
|
||||
return error(context, "unknown error");
|
||||
} finally {
|
||||
environment.clearPendingState();
|
||||
}
|
||||
}
|
||||
|
||||
private void authenticate(Request request) {
|
||||
LOG.trace("authenticate hook request");
|
||||
String token = CipherUtil.getInstance().decode(request.getToken());
|
||||
BearerToken bearer = BearerToken.valueOf(token);
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
subject.login(bearer);
|
||||
}
|
||||
|
||||
private Response error(HgHookContextProvider context, String message) {
|
||||
List<HgHookMessage> messages = new ArrayList<>(context.getHgMessageProvider().getMessages());
|
||||
messages.add(createErrorMessage(message));
|
||||
return new Response(messages, true);
|
||||
}
|
||||
|
||||
private Response error(String message) {
|
||||
return new Response(
|
||||
singletonList(createErrorMessage(message)),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private HgHookMessage createErrorMessage(String message) {
|
||||
return new HgHookMessage(HgHookMessage.Severity.ERROR, message);
|
||||
}
|
||||
|
||||
private void close() {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
LOG.debug("failed to close hook socket", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class Request {
|
||||
private String token;
|
||||
private RepositoryHookType type;
|
||||
private String transactionId;
|
||||
private String repositoryId;
|
||||
private String challenge;
|
||||
private String node;
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class Response {
|
||||
private List<HgHookMessage> messages;
|
||||
private boolean abort;
|
||||
}
|
||||
}
|
||||
@@ -21,60 +21,37 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
package sonia.scm.repository.hooks;
|
||||
|
||||
import sonia.scm.repository.AbstractHgHandler;
|
||||
import sonia.scm.repository.HgContext;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.repository.HgRepositoryFactory;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryManager;
|
||||
import sonia.scm.repository.spi.HgHookContextProvider;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
import javax.inject.Inject;
|
||||
|
||||
import java.io.File;
|
||||
public class HookContextProviderFactory {
|
||||
|
||||
import java.util.Map;
|
||||
private final RepositoryManager repositoryManager;
|
||||
private final HgRepositoryHandler repositoryHandler;
|
||||
private final HgRepositoryFactory repositoryFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class AbstractHgCommand extends AbstractHgHandler
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param handler
|
||||
* @param context
|
||||
* @param repository
|
||||
* @param repositoryDirectory
|
||||
*/
|
||||
protected AbstractHgCommand(HgRepositoryHandler handler, HgContext context,
|
||||
Repository repository, File repositoryDirectory)
|
||||
{
|
||||
super(handler, context, repository, repositoryDirectory);
|
||||
@Inject
|
||||
public HookContextProviderFactory(RepositoryManager repositoryManager, HgRepositoryHandler repositoryHandler, HgRepositoryFactory repositoryFactory) {
|
||||
this.repositoryManager = repositoryManager;
|
||||
this.repositoryHandler = repositoryHandler;
|
||||
this.repositoryFactory = repositoryFactory;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param revision
|
||||
* @param path
|
||||
*
|
||||
* @param request
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected Map<String,
|
||||
String> createEnvironment(FileBaseCommandRequest request)
|
||||
{
|
||||
return createEnvironment(request.getRevision(), request.getPath());
|
||||
HgHookContextProvider create(String repositoryId, String node) {
|
||||
Repository repository = repositoryManager.get(repositoryId);
|
||||
if (repository == null) {
|
||||
throw new NotFoundException(Repository.class, repositoryId);
|
||||
}
|
||||
return new HgHookContextProvider(repositoryHandler, repositoryFactory, repository, node);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,28 +21,40 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import com.google.inject.servlet.RequestScoped;
|
||||
package sonia.scm.repository.hooks;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
import javax.inject.Singleton;
|
||||
import java.util.UUID;
|
||||
|
||||
private final HgContext context = new HgContext();
|
||||
@Singleton
|
||||
public class HookEnvironment {
|
||||
|
||||
public HgContext get() {
|
||||
return context;
|
||||
private final ThreadLocal<Boolean> threadEnvironment = new ThreadLocal<>();
|
||||
private final String challenge = UUID.randomUUID().toString();
|
||||
|
||||
public String getChallenge() {
|
||||
return challenge;
|
||||
}
|
||||
|
||||
public boolean isAcceptAble(String challenge) {
|
||||
return this.challenge.equals(challenge);
|
||||
}
|
||||
|
||||
void setPending(boolean pending) {
|
||||
threadEnvironment.set(pending);
|
||||
}
|
||||
|
||||
void clearPendingState() {
|
||||
threadEnvironment.remove();
|
||||
}
|
||||
|
||||
public boolean isPending() {
|
||||
Boolean threadState = threadEnvironment.get();
|
||||
if (threadState != null) {
|
||||
return threadState;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.hooks;
|
||||
|
||||
import java.net.Socket;
|
||||
|
||||
@FunctionalInterface
|
||||
interface HookHandlerFactory {
|
||||
|
||||
HookHandler create(Socket socket);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.hooks;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.assistedinject.FactoryModuleBuilder;
|
||||
import sonia.scm.plugin.Extension;
|
||||
|
||||
@Extension
|
||||
public class HookModule extends AbstractModule {
|
||||
@Override
|
||||
protected void configure() {
|
||||
install(new FactoryModuleBuilder()
|
||||
.implement(HookHandler.class, DefaultHookHandler.class)
|
||||
.build(HookHandlerFactory.class)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.hooks;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.mgt.SecurityManager;
|
||||
import org.apache.shiro.util.ThreadContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
@Singleton
|
||||
public class HookServer implements AutoCloseable {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HookServer.class);
|
||||
|
||||
private final HookHandlerFactory handlerFactory;
|
||||
|
||||
private ExecutorService acceptor;
|
||||
private ExecutorService workerPool;
|
||||
private ServerSocket serverSocket;
|
||||
private SecurityManager securityManager;
|
||||
|
||||
@Inject
|
||||
public HookServer(HookHandlerFactory handlerFactory) {
|
||||
this.handlerFactory = handlerFactory;
|
||||
}
|
||||
|
||||
public int start() throws IOException {
|
||||
securityManager = SecurityUtils.getSecurityManager();
|
||||
|
||||
acceptor = createAcceptor();
|
||||
workerPool = createWorkerPool();
|
||||
serverSocket = createServerSocket();
|
||||
// set timeout to 2 min, to avoid blocking clients
|
||||
serverSocket.setSoTimeout(2 * 60 * 1000);
|
||||
|
||||
accept();
|
||||
|
||||
int port = serverSocket.getLocalPort();
|
||||
LOG.info("open hg hook server on port {}", port);
|
||||
return port;
|
||||
}
|
||||
|
||||
private void accept() {
|
||||
acceptor.submit(() -> {
|
||||
while (!serverSocket.isClosed()) {
|
||||
try {
|
||||
LOG.trace("wait for next hook connection");
|
||||
Socket clientSocket = serverSocket.accept();
|
||||
LOG.trace("accept incoming hook client from {}", clientSocket.getInetAddress());
|
||||
HookHandler hookHandler = handlerFactory.create(clientSocket);
|
||||
workerPool.submit(associateSecurityManager(hookHandler));
|
||||
} catch (IOException ex) {
|
||||
LOG.debug("failed to accept socket, possible closed", ex);
|
||||
}
|
||||
}
|
||||
LOG.warn("ServerSocket is closed");
|
||||
});
|
||||
}
|
||||
|
||||
private Runnable associateSecurityManager(HookHandler hookHandler) {
|
||||
return () -> {
|
||||
ThreadContext.bind(securityManager);
|
||||
try {
|
||||
hookHandler.run();
|
||||
} finally {
|
||||
ThreadContext.unbindSubject();
|
||||
ThreadContext.unbindSecurityManager();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private ServerSocket createServerSocket() throws IOException {
|
||||
return new ServerSocket(0, 0, InetAddress.getLoopbackAddress());
|
||||
}
|
||||
|
||||
private ExecutorService createAcceptor() {
|
||||
return Executors.newSingleThreadExecutor(
|
||||
createThreadFactory("HgHookAcceptor")
|
||||
);
|
||||
}
|
||||
|
||||
private ExecutorService createWorkerPool() {
|
||||
return Executors.newCachedThreadPool(
|
||||
createThreadFactory("HgHookWorker-%d")
|
||||
);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private ThreadFactory createThreadFactory(String hgHookAcceptor) {
|
||||
return new ThreadFactoryBuilder()
|
||||
.setNameFormat(hgHookAcceptor)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
closeSocket();
|
||||
shutdown(acceptor);
|
||||
shutdown(workerPool);
|
||||
}
|
||||
|
||||
private void closeSocket() {
|
||||
if (serverSocket != null) {
|
||||
try {
|
||||
serverSocket.close();
|
||||
} catch (IOException ex) {
|
||||
LOG.warn("failed to close server socket", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void shutdown(ExecutorService acceptor) {
|
||||
if (acceptor != null) {
|
||||
acceptor.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.hooks;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
class Sockets {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Sockets.class);
|
||||
|
||||
private static final int READ_LIMIT = 8192;
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private Sockets() {
|
||||
}
|
||||
|
||||
static void send(OutputStream out, Object object) throws IOException {
|
||||
byte[] bytes = objectMapper.writeValueAsBytes(object);
|
||||
LOG.trace("send message length of {} to socket", bytes.length);
|
||||
|
||||
DataOutputStream dataOutputStream = new DataOutputStream(out);
|
||||
dataOutputStream.writeInt(bytes.length);
|
||||
|
||||
LOG.trace("send message to socket");
|
||||
dataOutputStream.write(bytes);
|
||||
|
||||
LOG.trace("flush socket");
|
||||
out.flush();
|
||||
}
|
||||
|
||||
static <T> T receive(InputStream in, Class<T> type) throws IOException {
|
||||
LOG.trace("read {} from socket", type);
|
||||
|
||||
DataInputStream dataInputStream = new DataInputStream(in);
|
||||
|
||||
int length = dataInputStream.readInt();
|
||||
LOG.trace("read message length of {} from socket", length);
|
||||
if (length > READ_LIMIT) {
|
||||
String message = String.format("received length of %d, which exceeds the limit of %d", length, READ_LIMIT);
|
||||
throw new IOException(message);
|
||||
}
|
||||
|
||||
byte[] data = new byte[length];
|
||||
dataInputStream.readFully(data);
|
||||
|
||||
LOG.trace("convert message to {}", type);
|
||||
return objectMapper.readValue(data, type);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,18 +27,12 @@ package sonia.scm.repository.spi;
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.aragost.javahg.Repository;
|
||||
import com.google.common.base.Strings;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.repository.HgHookManager;
|
||||
import sonia.scm.repository.HgRepositoryFactory;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.RepositoryProvider;
|
||||
import sonia.scm.web.HgUtil;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
@@ -46,105 +40,32 @@ import java.util.function.BiConsumer;
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class HgCommandContext implements Closeable, RepositoryProvider
|
||||
{
|
||||
public class HgCommandContext implements Closeable, RepositoryProvider {
|
||||
|
||||
/** Field description */
|
||||
private static final String PROPERTY_ENCODING = "hg.encoding";
|
||||
private final HgRepositoryHandler handler;
|
||||
private final HgRepositoryFactory factory;
|
||||
private final sonia.scm.repository.Repository scmRepository;
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
private Repository repository;
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param hookManager
|
||||
* @param handler
|
||||
* @param repository
|
||||
* @param directory
|
||||
*/
|
||||
public HgCommandContext(HgHookManager hookManager,
|
||||
HgRepositoryHandler handler, sonia.scm.repository.Repository repository,
|
||||
File directory)
|
||||
{
|
||||
this(hookManager, handler, repository, directory,
|
||||
handler.getHgContext().isPending());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param hookManager
|
||||
* @param handler
|
||||
* @param repository
|
||||
* @param directory
|
||||
* @param pending
|
||||
*/
|
||||
public HgCommandContext(HgHookManager hookManager,
|
||||
HgRepositoryHandler handler, sonia.scm.repository.Repository repository,
|
||||
File directory, boolean pending)
|
||||
{
|
||||
this.hookManager = hookManager;
|
||||
public HgCommandContext(HgRepositoryHandler handler, HgRepositoryFactory factory, sonia.scm.repository.Repository scmRepository) {
|
||||
this.handler = handler;
|
||||
this.directory = directory;
|
||||
this.scmRepository = repository;
|
||||
this.encoding = repository.getProperty(PROPERTY_ENCODING);
|
||||
this.pending = pending;
|
||||
|
||||
if (Strings.isNullOrEmpty(encoding))
|
||||
{
|
||||
encoding = handler.getConfig().getEncoding();
|
||||
}
|
||||
this.factory = factory;
|
||||
this.scmRepository = scmRepository;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
if (repository != null)
|
||||
{
|
||||
repository.close();
|
||||
public Repository open() {
|
||||
if (repository == null) {
|
||||
repository = factory.openForRead(scmRepository);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Repository open()
|
||||
{
|
||||
if (repository == null)
|
||||
{
|
||||
repository = HgUtil.open(handler, hookManager, directory, encoding, pending);
|
||||
}
|
||||
|
||||
return repository;
|
||||
}
|
||||
|
||||
public Repository openWithSpecialEnvironment(BiConsumer<sonia.scm.repository.Repository, Map<String, String>> prepareEnvironment)
|
||||
{
|
||||
return HgUtil.open(handler, directory, encoding,
|
||||
pending, environment -> prepareEnvironment.accept(scmRepository, environment));
|
||||
public Repository openForWrite() {
|
||||
return factory.openForWrite(scmRepository);
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public HgConfig getConfig()
|
||||
{
|
||||
return handler.getConfig();
|
||||
@@ -159,25 +80,12 @@ public class HgCommandContext implements Closeable, RepositoryProvider
|
||||
return getScmRepository();
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private File directory;
|
||||
@Override
|
||||
public void close() {
|
||||
if (repository != null) {
|
||||
repository.close();
|
||||
}
|
||||
}
|
||||
|
||||
/** Field description */
|
||||
private String encoding;
|
||||
|
||||
/** Field description */
|
||||
private HgRepositoryHandler handler;
|
||||
|
||||
/** Field description */
|
||||
private HgHookManager hookManager;
|
||||
|
||||
/** Field description */
|
||||
private boolean pending;
|
||||
|
||||
/** Field description */
|
||||
private Repository repository;
|
||||
|
||||
private final sonia.scm.repository.Repository scmRepository;
|
||||
}
|
||||
|
||||
@@ -21,85 +21,56 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import com.aragost.javahg.Repository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.repository.HgHookManager;
|
||||
import sonia.scm.repository.HgRepositoryFactory;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.RepositoryHookType;
|
||||
import sonia.scm.repository.spi.javahg.HgLogChangesetCommand;
|
||||
import sonia.scm.web.HgUtil;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class HgHookChangesetProvider implements HookChangesetProvider
|
||||
{
|
||||
public class HgHookChangesetProvider implements HookChangesetProvider {
|
||||
|
||||
/**
|
||||
* the logger for HgHookChangesetProvider
|
||||
*/
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(HgHookChangesetProvider.class);
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HgHookChangesetProvider.class);
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
private final HgRepositoryHandler handler;
|
||||
private final HgRepositoryFactory factory;
|
||||
private final sonia.scm.repository.Repository scmRepository;
|
||||
private final String startRev;
|
||||
|
||||
public HgHookChangesetProvider(HgRepositoryHandler handler,
|
||||
File repositoryDirectory, HgHookManager hookManager, String startRev,
|
||||
RepositoryHookType type)
|
||||
{
|
||||
private HookChangesetResponse response;
|
||||
|
||||
public HgHookChangesetProvider(HgRepositoryHandler handler, HgRepositoryFactory factory, sonia.scm.repository.Repository scmRepository, String startRev) {
|
||||
this.handler = handler;
|
||||
this.repositoryDirectory = repositoryDirectory;
|
||||
this.hookManager = hookManager;
|
||||
this.factory = factory;
|
||||
this.scmRepository = scmRepository;
|
||||
this.startRev = startRev;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param request
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public synchronized HookChangesetResponse handleRequest(HookChangesetRequest request)
|
||||
{
|
||||
if (response == null)
|
||||
{
|
||||
public synchronized HookChangesetResponse handleRequest(HookChangesetRequest request) {
|
||||
if (response == null) {
|
||||
Repository repository = null;
|
||||
|
||||
try
|
||||
{
|
||||
repository = open();
|
||||
try {
|
||||
repository = factory.openForRead(scmRepository);
|
||||
|
||||
HgLogChangesetCommand cmd = HgLogChangesetCommand.on(repository,
|
||||
handler.getConfig());
|
||||
HgLogChangesetCommand cmd = HgLogChangesetCommand.on(repository, handler.getConfig());
|
||||
|
||||
response = new HookChangesetResponse(
|
||||
cmd.rev(startRev.concat(":").concat(HgUtil.REVISION_TIP)).execute());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.error("could not retrieve changesets", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (repository != null)
|
||||
{
|
||||
cmd.rev(startRev.concat(":").concat(HgUtil.REVISION_TIP)).execute()
|
||||
);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("could not retrieve changesets", ex);
|
||||
} finally {
|
||||
if (repository != null) {
|
||||
repository.close();
|
||||
}
|
||||
}
|
||||
@@ -108,39 +79,4 @@ public class HgHookChangesetProvider implements HookChangesetProvider
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private Repository open()
|
||||
{
|
||||
// use HG_PENDING only for pre receive hooks
|
||||
boolean pending = type == RepositoryHookType.PRE_RECEIVE;
|
||||
|
||||
// TODO get repository encoding
|
||||
return HgUtil.open(handler, hookManager, repositoryDirectory, null,
|
||||
pending);
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private HgRepositoryHandler handler;
|
||||
|
||||
/** Field description */
|
||||
private HgHookManager hookManager;
|
||||
|
||||
/** Field description */
|
||||
private File repositoryDirectory;
|
||||
|
||||
/** Field description */
|
||||
private HookChangesetResponse response;
|
||||
|
||||
/** Field description */
|
||||
private String startRev;
|
||||
|
||||
/** Field description */
|
||||
private RepositoryHookType type;
|
||||
}
|
||||
|
||||
@@ -21,14 +21,14 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import sonia.scm.repository.HgHookManager;
|
||||
import sonia.scm.repository.HgRepositoryFactory;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.RepositoryHookType;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.api.HgHookBranchProvider;
|
||||
import sonia.scm.repository.api.HgHookMessageProvider;
|
||||
import sonia.scm.repository.api.HgHookTagProvider;
|
||||
@@ -37,7 +37,6 @@ import sonia.scm.repository.api.HookFeature;
|
||||
import sonia.scm.repository.api.HookMessageProvider;
|
||||
import sonia.scm.repository.api.HookTagProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -45,55 +44,40 @@ import java.util.Set;
|
||||
|
||||
/**
|
||||
* Mercurial implementation of {@link HookContextProvider}.
|
||||
*
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class HgHookContextProvider extends HookContextProvider
|
||||
{
|
||||
public class HgHookContextProvider extends HookContextProvider {
|
||||
|
||||
private static final Set<HookFeature> SUPPORTED_FEATURES =
|
||||
EnumSet.of(HookFeature.CHANGESET_PROVIDER, HookFeature.MESSAGE_PROVIDER,
|
||||
HookFeature.BRANCH_PROVIDER, HookFeature.TAG_PROVIDER);
|
||||
private static final Set<HookFeature> SUPPORTED_FEATURES = EnumSet.of(
|
||||
HookFeature.CHANGESET_PROVIDER,
|
||||
HookFeature.MESSAGE_PROVIDER,
|
||||
HookFeature.BRANCH_PROVIDER,
|
||||
HookFeature.TAG_PROVIDER
|
||||
);
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
private final HgHookChangesetProvider hookChangesetProvider;
|
||||
private HgHookMessageProvider hgMessageProvider;
|
||||
private HgHookBranchProvider hookBranchProvider;
|
||||
private HgHookTagProvider hookTagProvider;
|
||||
|
||||
/**
|
||||
* Constructs a new instance.
|
||||
*
|
||||
* @param handler mercurial repository handler
|
||||
* @param repositoryDirectory the directory of the changed repository
|
||||
* @param hookManager mercurial hook manager
|
||||
* @param startRev start revision
|
||||
* @param type type of hook
|
||||
*/
|
||||
public HgHookContextProvider(HgRepositoryHandler handler,
|
||||
File repositoryDirectory, HgHookManager hookManager, String startRev,
|
||||
RepositoryHookType type)
|
||||
{
|
||||
this.hookChangesetProvider = new HgHookChangesetProvider(handler, repositoryDirectory, hookManager, startRev, type);
|
||||
public HgHookContextProvider(HgRepositoryHandler handler, HgRepositoryFactory factory, Repository repository, String startRev) {
|
||||
this.hookChangesetProvider = new HgHookChangesetProvider(handler, factory, repository, startRev);
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public HookBranchProvider getBranchProvider()
|
||||
{
|
||||
if (hookBranchProvider == null)
|
||||
{
|
||||
public HookBranchProvider getBranchProvider() {
|
||||
if (hookBranchProvider == null) {
|
||||
hookBranchProvider = new HgHookBranchProvider(hookChangesetProvider);
|
||||
}
|
||||
|
||||
return hookBranchProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HookTagProvider getTagProvider()
|
||||
{
|
||||
if (hookTagProvider == null)
|
||||
{
|
||||
public HookTagProvider getTagProvider() {
|
||||
if (hookTagProvider == null) {
|
||||
hookTagProvider = new HgHookTagProvider(hookChangesetProvider);
|
||||
}
|
||||
|
||||
return hookTagProvider;
|
||||
}
|
||||
|
||||
@@ -102,14 +86,11 @@ public class HgHookContextProvider extends HookContextProvider
|
||||
{
|
||||
return hookChangesetProvider;
|
||||
}
|
||||
|
||||
public HgHookMessageProvider getHgMessageProvider()
|
||||
{
|
||||
if (hgMessageProvider == null)
|
||||
{
|
||||
|
||||
public HgHookMessageProvider getHgMessageProvider() {
|
||||
if (hgMessageProvider == null) {
|
||||
hgMessageProvider = new HgHookMessageProvider();
|
||||
}
|
||||
|
||||
return hgMessageProvider;
|
||||
}
|
||||
|
||||
@@ -119,21 +100,9 @@ public class HgHookContextProvider extends HookContextProvider
|
||||
return SUPPORTED_FEATURES;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected HookMessageProvider createMessageProvider()
|
||||
{
|
||||
return getHgMessageProvider();
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
private final HgHookChangesetProvider hookChangesetProvider;
|
||||
|
||||
private HgHookMessageProvider hgMessageProvider;
|
||||
|
||||
private HgHookBranchProvider hookBranchProvider;
|
||||
|
||||
private HgHookTagProvider hookTagProvider;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ import com.aragost.javahg.commands.ExecutionException;
|
||||
import com.aragost.javahg.commands.PullCommand;
|
||||
import com.aragost.javahg.commands.RemoveCommand;
|
||||
import com.aragost.javahg.commands.StatusCommand;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.NoChangesMadeException;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.work.WorkingCopy;
|
||||
@@ -41,11 +43,13 @@ import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@SuppressWarnings("java:S3252") // it is ok for javahg classes to access static method of subtype
|
||||
public class HgModifyCommand implements ModifyCommand {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HgModifyCommand.class);
|
||||
static final Pattern HG_MESSAGE_PATTERN = Pattern.compile(".*\\[SCM\\](?: Error:)? (.*)");
|
||||
|
||||
private HgCommandContext context;
|
||||
private final HgCommandContext context;
|
||||
private final HgWorkingCopyFactory workingCopyFactory;
|
||||
|
||||
public HgModifyCommand(HgCommandContext context, HgWorkingCopyFactory workingCopyFactory) {
|
||||
@@ -55,7 +59,6 @@ public class HgModifyCommand implements ModifyCommand {
|
||||
|
||||
@Override
|
||||
public String execute(ModifyCommandRequest request) {
|
||||
|
||||
try (WorkingCopy<com.aragost.javahg.Repository, com.aragost.javahg.Repository> workingCopy = workingCopyFactory.createWorkingCopy(context, request.getBranch())) {
|
||||
Repository workingRepository = workingCopy.getWorkingRepository();
|
||||
request.getRequests().forEach(
|
||||
@@ -100,12 +103,21 @@ public class HgModifyCommand implements ModifyCommand {
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (StatusCommand.on(workingRepository).lines().isEmpty()) {
|
||||
throw new NoChangesMadeException(context.getScmRepository());
|
||||
}
|
||||
CommitCommand.on(workingRepository).user(String.format("%s <%s>", request.getAuthor().getName(), request.getAuthor().getMail())).message(request.getCommitMessage()).execute();
|
||||
|
||||
LOG.trace("commit changes in working copy");
|
||||
CommitCommand.on(workingRepository)
|
||||
.user(String.format("%s <%s>", request.getAuthor().getName(), request.getAuthor().getMail()))
|
||||
.message(request.getCommitMessage()).execute();
|
||||
|
||||
List<Changeset> execute = pullModifyChangesToCentralRepository(request, workingCopy);
|
||||
return execute.get(0).getNode();
|
||||
|
||||
String node = execute.get(0).getNode();
|
||||
LOG.debug("successfully pulled changes from working copy, new node {}", node);
|
||||
return node;
|
||||
} catch (ExecutionException e) {
|
||||
throwInternalRepositoryException("could not execute command on repository", e);
|
||||
return null;
|
||||
@@ -113,6 +125,7 @@ public class HgModifyCommand implements ModifyCommand {
|
||||
}
|
||||
|
||||
private List<Changeset> pullModifyChangesToCentralRepository(ModifyCommandRequest request, WorkingCopy<com.aragost.javahg.Repository, com.aragost.javahg.Repository> workingCopy) {
|
||||
LOG.trace("pull changes from working copy");
|
||||
try {
|
||||
com.aragost.javahg.commands.PullCommand pullCommand = PullCommand.on(workingCopy.getCentralRepository());
|
||||
workingCopyFactory.configure(pullCommand);
|
||||
|
||||
@@ -26,13 +26,12 @@ package sonia.scm.repository.spi;
|
||||
|
||||
import com.google.common.io.Closeables;
|
||||
import sonia.scm.repository.Feature;
|
||||
import sonia.scm.repository.HgHookManager;
|
||||
import sonia.scm.repository.HgRepositoryFactory;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.api.Command;
|
||||
import sonia.scm.repository.api.CommandNotSupportedException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
@@ -41,11 +40,8 @@ import java.util.Set;
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class HgRepositoryServiceProvider extends RepositoryServiceProvider
|
||||
{
|
||||
public class HgRepositoryServiceProvider extends RepositoryServiceProvider {
|
||||
|
||||
/** Field description */
|
||||
//J-
|
||||
public static final Set<Command> COMMANDS = EnumSet.of(
|
||||
Command.BLAME,
|
||||
Command.BROWSE,
|
||||
@@ -61,25 +57,19 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider
|
||||
Command.PULL,
|
||||
Command.MODIFY
|
||||
);
|
||||
//J+
|
||||
|
||||
/** Field description */
|
||||
public static final Set<Feature> FEATURES =
|
||||
EnumSet.of(Feature.COMBINED_DEFAULT_BRANCH);
|
||||
public static final Set<Feature> FEATURES = EnumSet.of(Feature.COMBINED_DEFAULT_BRANCH);
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
private final HgRepositoryHandler handler;
|
||||
private final HgCommandContext context;
|
||||
|
||||
HgRepositoryServiceProvider(HgRepositoryHandler handler,
|
||||
HgHookManager hookManager, Repository repository)
|
||||
{
|
||||
HgRepositoryServiceProvider(HgRepositoryHandler handler, HgRepositoryFactory factory, Repository repository) {
|
||||
this.handler = handler;
|
||||
this.repositoryDirectory = handler.getDirectory(repository.getId());
|
||||
this.context = new HgCommandContext(hookManager, handler, repository,
|
||||
repositoryDirectory);
|
||||
this.context = new HgCommandContext(handler, factory, repository);
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
@@ -91,9 +81,9 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider
|
||||
{
|
||||
Closeables.close(context, true);
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
@@ -271,14 +261,4 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider
|
||||
return new HgTagsCommand(context);
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private HgCommandContext context;
|
||||
|
||||
/** Field description */
|
||||
private HgRepositoryHandler handler;
|
||||
|
||||
/** Field description */
|
||||
private File repositoryDirectory;
|
||||
}
|
||||
|
||||
@@ -21,12 +21,12 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.HgHookManager;
|
||||
import sonia.scm.repository.HgRepositoryFactory;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.Repository;
|
||||
|
||||
@@ -35,18 +35,15 @@ import sonia.scm.repository.Repository;
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Extension
|
||||
public class HgRepositoryServiceResolver implements RepositoryServiceResolver
|
||||
{
|
||||
public class HgRepositoryServiceResolver implements RepositoryServiceResolver {
|
||||
|
||||
private final HgRepositoryHandler handler;
|
||||
private final HgHookManager hookManager;
|
||||
private final HgRepositoryFactory factory;
|
||||
|
||||
@Inject
|
||||
public HgRepositoryServiceResolver(HgRepositoryHandler handler,
|
||||
HgHookManager hookManager)
|
||||
{
|
||||
public HgRepositoryServiceResolver(HgRepositoryHandler handler, HgRepositoryFactory factory) {
|
||||
this.handler = handler;
|
||||
this.hookManager = hookManager;
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -54,7 +51,7 @@ public class HgRepositoryServiceResolver implements RepositoryServiceResolver
|
||||
HgRepositoryServiceProvider provider = null;
|
||||
|
||||
if (HgRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) {
|
||||
provider = new HgRepositoryServiceProvider(handler, hookManager, repository);
|
||||
provider = new HgRepositoryServiceProvider(handler, factory, repository);
|
||||
}
|
||||
|
||||
return provider;
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.repository.HgVersion;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class HgVersionCommand {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HgVersionCommand.class);
|
||||
|
||||
@VisibleForTesting
|
||||
static final String[] HG_ARGS = {
|
||||
"version", "--template", "{ver}"
|
||||
};
|
||||
|
||||
@VisibleForTesting
|
||||
static final String[] PYTHON_ARGS = {
|
||||
"-c", "import sys; print(sys.version)"
|
||||
};
|
||||
|
||||
private final HgConfig config;
|
||||
private final ProcessExecutor executor;
|
||||
|
||||
public HgVersionCommand(HgConfig config) {
|
||||
this(config, command -> new ProcessBuilder(command).start());
|
||||
}
|
||||
|
||||
HgVersionCommand(HgConfig config, ProcessExecutor executor) {
|
||||
this.config = config;
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
public HgVersion get() {
|
||||
return new HgVersion(getHgVersion(), getPythonVersion());
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private String getPythonVersion() {
|
||||
try {
|
||||
String content = exec(config.getPythonBinary(), PYTHON_ARGS);
|
||||
int index = content.indexOf(' ');
|
||||
if (index > 0) {
|
||||
return content.substring(0, index);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
LOG.warn("failed to get python version", ex);
|
||||
} catch (InterruptedException ex) {
|
||||
LOG.warn("failed to get python version", ex);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
return HgVersion.UNKNOWN;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private String getHgVersion() {
|
||||
try {
|
||||
return exec(config.getHgBinary(), HG_ARGS).trim();
|
||||
} catch (IOException ex) {
|
||||
LOG.warn("failed to get mercurial version", ex);
|
||||
} catch (InterruptedException ex) {
|
||||
LOG.warn("failed to get mercurial version", ex);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
return HgVersion.UNKNOWN;
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
private String exec(String command, String[] args) throws IOException, InterruptedException {
|
||||
List<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.waitFor();
|
||||
if (exitCode != 0) {
|
||||
throw new IOException("process ends with exit code " + exitCode);
|
||||
}
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface ProcessExecutor {
|
||||
Process execute(List<String> command) throws IOException;
|
||||
}
|
||||
}
|
||||
@@ -36,27 +36,21 @@ import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.work.SimpleWorkingCopyFactory;
|
||||
import sonia.scm.repository.work.WorkingCopyPool;
|
||||
import sonia.scm.util.IOUtil;
|
||||
import sonia.scm.web.HgRepositoryEnvironmentBuilder;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory<Repository, Repository, HgCommandContext> implements HgWorkingCopyFactory {
|
||||
|
||||
private final Provider<HgRepositoryEnvironmentBuilder> hgRepositoryEnvironmentBuilder;
|
||||
|
||||
@Inject
|
||||
public SimpleHgWorkingCopyFactory(Provider<HgRepositoryEnvironmentBuilder> hgRepositoryEnvironmentBuilder, WorkingCopyPool workdirProvider) {
|
||||
public SimpleHgWorkingCopyFactory(WorkingCopyPool workdirProvider) {
|
||||
super(workdirProvider);
|
||||
this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParentAndClone<Repository, Repository> initialize(HgCommandContext context, File target, String initialBranch) {
|
||||
Repository centralRepository = openCentral(context);
|
||||
Repository centralRepository = context.openForWrite();
|
||||
CloneCommand cloneCommand = CloneCommandFlags.on(centralRepository);
|
||||
if (initialBranch != null) {
|
||||
cloneCommand.updaterev(initialBranch);
|
||||
@@ -76,7 +70,7 @@ public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory<Reposit
|
||||
// The hg api to create a command is meant to be used from the command classes, not from their "flags" base classes.
|
||||
@SuppressWarnings("java:S3252")
|
||||
protected ParentAndClone<Repository, Repository> reclaim(HgCommandContext context, File target, String initialBranch) throws ReclaimFailedException {
|
||||
Repository centralRepository = openCentral(context);
|
||||
Repository centralRepository = context.openForWrite();
|
||||
try {
|
||||
BaseRepository clone = Repository.open(target);
|
||||
for (String unknown : StatusCommand.on(clone).execute().getUnknown()) {
|
||||
@@ -89,12 +83,6 @@ public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory<Reposit
|
||||
}
|
||||
}
|
||||
|
||||
public Repository openCentral(HgCommandContext context) {
|
||||
BiConsumer<sonia.scm.repository.Repository, Map<String, String>> repositoryMapBiConsumer =
|
||||
(repository, environment) -> hgRepositoryEnvironmentBuilder.get().buildFor(repository, null, environment);
|
||||
return context.openWithSpecialEnvironment(repositoryMapBiConsumer);
|
||||
}
|
||||
|
||||
private void delete(File directory, String unknownFile) throws IOException {
|
||||
IOUtil.delete(new File(directory, unknownFile));
|
||||
}
|
||||
@@ -111,7 +99,7 @@ public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory<Reposit
|
||||
|
||||
@Override
|
||||
public void configure(PullCommand pullCommand) {
|
||||
pullCommand.cmdAppend("--config", "hooks.changegroup.scm=python:scmhooks.postHook");
|
||||
pullCommand.cmdAppend("--config", "hooks.pretxnchangegroup.scm=python:scmhooks.preHook");
|
||||
pullCommand.cmdAppend("--config", "hooks.changegroup.scm=python:scmhooks.post_hook");
|
||||
pullCommand.cmdAppend("--config", "hooks.pretxnchangegroup.scm=python:scmhooks.pre_hook");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.web;
|
||||
|
||||
import com.google.common.base.Stopwatch;
|
||||
@@ -32,6 +32,7 @@ import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.repository.HgEnvironmentBuilder;
|
||||
import sonia.scm.repository.HgPythonScript;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.Repository;
|
||||
@@ -42,29 +43,21 @@ import sonia.scm.web.cgi.CGIExecutor;
|
||||
import sonia.scm.web.cgi.CGIExecutorFactory;
|
||||
import sonia.scm.web.cgi.EnvList;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Enumeration;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Singleton
|
||||
public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
public static final String ENV_SESSION_PREFIX = "SCM_";
|
||||
public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet {
|
||||
|
||||
/** Field description */
|
||||
private static final long serialVersionUID = -3492811300905099810L;
|
||||
@@ -80,13 +73,13 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
||||
ScmConfiguration configuration,
|
||||
HgRepositoryHandler handler,
|
||||
RepositoryRequestListenerUtil requestListenerUtil,
|
||||
HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder)
|
||||
HgEnvironmentBuilder environmentBuilder)
|
||||
{
|
||||
this.cgiExecutorFactory = cgiExecutorFactory;
|
||||
this.configuration = configuration;
|
||||
this.handler = handler;
|
||||
this.requestListenerUtil = requestListenerUtil;
|
||||
this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder;
|
||||
this.environmentBuilder = environmentBuilder;
|
||||
this.exceptionHandler = new HgCGIExceptionHandler();
|
||||
this.command = HgPythonScript.HGWEB.getFile(SCMContext.getContext());
|
||||
}
|
||||
@@ -108,11 +101,7 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
||||
{
|
||||
handleRequest(request, response, repository);
|
||||
}
|
||||
catch (ServletException ex)
|
||||
{
|
||||
exceptionHandler.handleException(request, response, ex);
|
||||
}
|
||||
catch (IOException ex)
|
||||
catch (ServletException | IOException ex)
|
||||
{
|
||||
exceptionHandler.handleException(request, response, ex);
|
||||
}
|
||||
@@ -146,29 +135,6 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param env
|
||||
* @param session
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void passSessionAttributes(EnvList env, HttpSession session)
|
||||
{
|
||||
Enumeration<String> enm = session.getAttributeNames();
|
||||
|
||||
while (enm.hasMoreElements())
|
||||
{
|
||||
String key = enm.nextElement();
|
||||
|
||||
if (key.startsWith(ENV_SESSION_PREFIX))
|
||||
{
|
||||
env.set(key, session.getAttribute(key).toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
@@ -192,7 +158,9 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
||||
executor.setExceptionHandler(exceptionHandler);
|
||||
executor.setStatusCodeHandler(exceptionHandler);
|
||||
executor.setContentLengthWorkaround(true);
|
||||
hgRepositoryEnvironmentBuilder.buildFor(repository, request, executor.getEnvironment().asMutableMap());
|
||||
|
||||
EnvList env = executor.getEnvironment();
|
||||
environmentBuilder.write(repository).forEach(env::set);
|
||||
|
||||
String interpreter = getInterpreter();
|
||||
|
||||
@@ -248,5 +216,5 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
||||
/** Field description */
|
||||
private final RepositoryRequestListenerUtil requestListenerUtil;
|
||||
|
||||
private final HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder;
|
||||
private final HgEnvironmentBuilder environmentBuilder;
|
||||
}
|
||||
|
||||
@@ -1,446 +0,0 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.web;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.io.Closeables;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.authc.AuthenticationToken;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.repository.HgContext;
|
||||
import sonia.scm.repository.HgHookManager;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.RepositoryHookType;
|
||||
import sonia.scm.repository.api.HgHookMessage;
|
||||
import sonia.scm.repository.api.HgHookMessage.Severity;
|
||||
import sonia.scm.repository.spi.HgHookContextProvider;
|
||||
import sonia.scm.repository.spi.HookEventFacade;
|
||||
import sonia.scm.security.BearerToken;
|
||||
import sonia.scm.security.CipherUtil;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Singleton
|
||||
public class HgHookCallbackServlet extends HttpServlet
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
public static final String HGHOOK_POST_RECEIVE = "changegroup";
|
||||
|
||||
/** Field description */
|
||||
public static final String HGHOOK_PRE_RECEIVE = "pretxnchangegroup";
|
||||
|
||||
/** Field description */
|
||||
public static final String PARAM_REPOSITORYID = "repositoryId";
|
||||
|
||||
/** Field description */
|
||||
private static final String PARAM_CHALLENGE = "challenge";
|
||||
|
||||
/** Field description */
|
||||
private static final String PARAM_TOKEN = "token";
|
||||
|
||||
/** Field description */
|
||||
private static final String PARAM_NODE = "node";
|
||||
|
||||
/** Field description */
|
||||
private static final String PARAM_PING = "ping";
|
||||
|
||||
/** Field description */
|
||||
private static final Pattern REGEX_URL =
|
||||
Pattern.compile("^/hook/hg/([^/]+)$");
|
||||
|
||||
/** the logger for HgHookCallbackServlet */
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(HgHookCallbackServlet.class);
|
||||
|
||||
/** Field description */
|
||||
private static final long serialVersionUID = 3531596724828189353L;
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
@Inject
|
||||
public HgHookCallbackServlet(HookEventFacade hookEventFacade,
|
||||
HgRepositoryHandler handler, HgHookManager hookManager,
|
||||
Provider<HgContext> contextProvider)
|
||||
{
|
||||
this.hookEventFacade = hookEventFacade;
|
||||
this.handler = handler;
|
||||
this.hookManager = hookManager;
|
||||
this.contextProvider = contextProvider;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws ServletException
|
||||
*/
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
String ping = request.getParameter(PARAM_PING);
|
||||
|
||||
if (Util.isNotEmpty(ping) && Boolean.parseBoolean(ping))
|
||||
{
|
||||
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
|
||||
}
|
||||
else
|
||||
{
|
||||
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||
try {
|
||||
handlePostRequest(request, response);
|
||||
} catch (IOException ex) {
|
||||
logger.warn("error in hook callback execution, sending internal server error", ex);
|
||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePostRequest(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
String strippedURI = HttpUtil.getStrippedURI(request);
|
||||
Matcher m = REGEX_URL.matcher(strippedURI);
|
||||
|
||||
if (m.matches())
|
||||
{
|
||||
String repositoryId = getRepositoryId(request);
|
||||
String type = m.group(1);
|
||||
String challenge = request.getParameter(PARAM_CHALLENGE);
|
||||
|
||||
if (Util.isNotEmpty(challenge))
|
||||
{
|
||||
String node = request.getParameter(PARAM_NODE);
|
||||
|
||||
if (Util.isNotEmpty(node))
|
||||
{
|
||||
String token = request.getParameter(PARAM_TOKEN);
|
||||
|
||||
if (Util.isNotEmpty(token))
|
||||
{
|
||||
authenticate(token);
|
||||
}
|
||||
|
||||
hookCallback(response, type, repositoryId, challenge, node);
|
||||
}
|
||||
else if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("node parameter not found");
|
||||
}
|
||||
}
|
||||
else if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("challenge parameter not found");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("url does not match");
|
||||
}
|
||||
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
private void authenticate(String token)
|
||||
{
|
||||
try
|
||||
{
|
||||
token = CipherUtil.getInstance().decode(token);
|
||||
|
||||
if (Util.isNotEmpty(token))
|
||||
{
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
|
||||
AuthenticationToken accessToken = createToken(token);
|
||||
|
||||
//J-
|
||||
subject.login(accessToken);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.error("could not authenticate user", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private AuthenticationToken createToken(String tokenString)
|
||||
{
|
||||
return BearerToken.valueOf(tokenString);
|
||||
}
|
||||
|
||||
private void fireHook(HttpServletResponse response, String repositoryId, String node, RepositoryHookType type)
|
||||
throws IOException
|
||||
{
|
||||
HgHookContextProvider context = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (type == RepositoryHookType.PRE_RECEIVE)
|
||||
{
|
||||
contextProvider.get().setPending(true);
|
||||
}
|
||||
|
||||
File repositoryDirectory = handler.getDirectory(repositoryId);
|
||||
context = new HgHookContextProvider(handler, repositoryDirectory, hookManager,
|
||||
node, type);
|
||||
|
||||
hookEventFacade.handle(repositoryId).fireHookEvent(type, context);
|
||||
|
||||
printMessages(response, context);
|
||||
}
|
||||
catch (NotFoundException ex)
|
||||
{
|
||||
logger.error(ex.getMessage());
|
||||
|
||||
logger.trace("repository not found", ex);
|
||||
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
sendError(response, context, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void hookCallback(HttpServletResponse response, String typeName, String repositoryId, String challenge, String node) throws IOException {
|
||||
if (hookManager.isAcceptAble(challenge))
|
||||
{
|
||||
RepositoryHookType type = null;
|
||||
|
||||
if (HGHOOK_PRE_RECEIVE.equals(typeName))
|
||||
{
|
||||
type = RepositoryHookType.PRE_RECEIVE;
|
||||
}
|
||||
else if (HGHOOK_POST_RECEIVE.equals(typeName))
|
||||
{
|
||||
type = RepositoryHookType.POST_RECEIVE;
|
||||
}
|
||||
|
||||
if (type != null)
|
||||
{
|
||||
fireHook(response, repositoryId, node, type);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (logger.isWarnEnabled())
|
||||
{
|
||||
logger.warn("unknown hook type {}", typeName);
|
||||
}
|
||||
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (logger.isWarnEnabled())
|
||||
{
|
||||
logger.warn("hg hook challenge is not accept able");
|
||||
}
|
||||
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param writer
|
||||
* @param msg
|
||||
*/
|
||||
private void printMessage(PrintWriter writer, HgHookMessage msg)
|
||||
{
|
||||
writer.append('_');
|
||||
|
||||
if (msg.getSeverity() == Severity.ERROR)
|
||||
{
|
||||
writer.append("e[SCM] Error: ");
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.append("n[SCM] ");
|
||||
}
|
||||
|
||||
writer.println(msg.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param response
|
||||
* @param context
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private void printMessages(HttpServletResponse response,
|
||||
HgHookContextProvider context)
|
||||
throws IOException
|
||||
{
|
||||
List<HgHookMessage> msgs = context.getHgMessageProvider().getMessages();
|
||||
|
||||
if (Util.isNotEmpty(msgs))
|
||||
{
|
||||
PrintWriter writer = null;
|
||||
|
||||
try
|
||||
{
|
||||
writer = response.getWriter();
|
||||
|
||||
printMessages(writer, msgs);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Closeables.close(writer, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param writer
|
||||
* @param msgs
|
||||
*/
|
||||
private void printMessages(PrintWriter writer, List<HgHookMessage> msgs)
|
||||
{
|
||||
for (HgHookMessage msg : msgs)
|
||||
{
|
||||
printMessage(writer, msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param response
|
||||
* @param context
|
||||
* @param ex
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private void sendError(HttpServletResponse response,
|
||||
HgHookContextProvider context, Exception ex)
|
||||
throws IOException
|
||||
{
|
||||
logger.warn("hook ended with exception", ex);
|
||||
response.setStatus(HttpServletResponse.SC_CONFLICT);
|
||||
|
||||
String msg = ex.getMessage();
|
||||
List<HgHookMessage> msgs = null;
|
||||
|
||||
if (context != null)
|
||||
{
|
||||
msgs = context.getHgMessageProvider().getMessages();
|
||||
}
|
||||
|
||||
if (!Strings.isNullOrEmpty(msg) || Util.isNotEmpty(msgs))
|
||||
{
|
||||
PrintWriter writer = null;
|
||||
|
||||
try
|
||||
{
|
||||
writer = response.getWriter();
|
||||
|
||||
if (Util.isNotEmpty(msgs))
|
||||
{
|
||||
printMessages(writer, msgs);
|
||||
}
|
||||
|
||||
if (!Strings.isNullOrEmpty(msg))
|
||||
{
|
||||
printMessage(writer, new HgHookMessage(Severity.ERROR, msg));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Closeables.close(writer, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
private String getRepositoryId(HttpServletRequest request)
|
||||
{
|
||||
String id = request.getParameter(PARAM_REPOSITORYID);
|
||||
Preconditions.checkArgument(!Strings.isNullOrEmpty(id), "repository id not found in request");
|
||||
return id;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private final Provider<HgContext> contextProvider;
|
||||
|
||||
/** Field description */
|
||||
private final HgRepositoryHandler handler;
|
||||
|
||||
/** Field description */
|
||||
private final HookEventFacade hookEventFacade;
|
||||
|
||||
/** Field description */
|
||||
private final HgHookManager hookManager;
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.web;
|
||||
|
||||
import sonia.scm.repository.HgEnvironment;
|
||||
import sonia.scm.repository.HgHookManager;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.Repository;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
public class HgRepositoryEnvironmentBuilder {
|
||||
|
||||
private static final String ENV_REPOSITORY_NAME = "REPO_NAME";
|
||||
private static final String ENV_REPOSITORY_PATH = "SCM_REPOSITORY_PATH";
|
||||
private static final String ENV_REPOSITORY_ID = "SCM_REPOSITORY_ID";
|
||||
private static final String ENV_PYTHON_HTTPS_VERIFY = "PYTHONHTTPSVERIFY";
|
||||
private static final String ENV_HTTP_POST_ARGS = "SCM_HTTP_POST_ARGS";
|
||||
|
||||
private final HgRepositoryHandler handler;
|
||||
private final HgHookManager hookManager;
|
||||
|
||||
@Inject
|
||||
public HgRepositoryEnvironmentBuilder(HgRepositoryHandler handler, HgHookManager hookManager) {
|
||||
this.handler = handler;
|
||||
this.hookManager = hookManager;
|
||||
}
|
||||
|
||||
public void buildFor(Repository repository, HttpServletRequest request, Map<String, String> environment) {
|
||||
File directory = handler.getDirectory(repository.getId());
|
||||
|
||||
environment.put(ENV_REPOSITORY_NAME, repository.getNamespace() + "/" + repository.getName());
|
||||
environment.put(ENV_REPOSITORY_ID, repository.getId());
|
||||
environment.put(ENV_REPOSITORY_PATH,
|
||||
directory.getAbsolutePath());
|
||||
|
||||
// add hook environment
|
||||
if (handler.getConfig().isDisableHookSSLValidation()) {
|
||||
// disable ssl validation
|
||||
// Issue 959: https://goo.gl/zH5eY8
|
||||
environment.put(ENV_PYTHON_HTTPS_VERIFY, "0");
|
||||
}
|
||||
|
||||
// enable experimental httppostargs protocol of mercurial
|
||||
// Issue 970: https://goo.gl/poascp
|
||||
environment.put(ENV_HTTP_POST_ARGS, String.valueOf(handler.getConfig().isEnableHttpPostArgs()));
|
||||
|
||||
HgEnvironment.prepareEnvironment(
|
||||
environment,
|
||||
handler,
|
||||
hookManager,
|
||||
request
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -34,9 +34,6 @@ import sonia.scm.api.v2.resources.HgConfigPackagesToDtoMapper;
|
||||
import sonia.scm.api.v2.resources.HgConfigToHgConfigDtoMapper;
|
||||
import sonia.scm.installer.HgPackageReader;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.HgContext;
|
||||
import sonia.scm.repository.HgContextProvider;
|
||||
import sonia.scm.repository.HgHookManager;
|
||||
import sonia.scm.repository.spi.HgWorkingCopyFactory;
|
||||
import sonia.scm.repository.spi.SimpleHgWorkingCopyFactory;
|
||||
|
||||
@@ -45,26 +42,10 @@ import sonia.scm.repository.spi.SimpleHgWorkingCopyFactory;
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Extension
|
||||
public class HgServletModule extends ServletModule
|
||||
{
|
||||
public class HgServletModule extends ServletModule {
|
||||
|
||||
/** Field description */
|
||||
public static final String MAPPING_HG = "/hg/*";
|
||||
|
||||
/** Field description */
|
||||
public static final String MAPPING_HOOK = "/hook/hg/*";
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
protected void configureServlets()
|
||||
{
|
||||
bind(HgContext.class).toProvider(HgContextProvider.class);
|
||||
bind(HgHookManager.class);
|
||||
protected void configureServlets() {
|
||||
bind(HgPackageReader.class);
|
||||
|
||||
bind(HgConfigDtoToHgConfigMapper.class).to(Mappers.getMapper(HgConfigDtoToHgConfigMapper.class).getClass());
|
||||
@@ -72,9 +53,6 @@ public class HgServletModule extends ServletModule
|
||||
bind(HgConfigPackagesToDtoMapper.class).to(Mappers.getMapper(HgConfigPackagesToDtoMapper.class).getClass());
|
||||
bind(HgConfigInstallationsToDtoMapper.class);
|
||||
|
||||
// bind servlets
|
||||
serve(MAPPING_HOOK).with(HgHookCallbackServlet.class);
|
||||
|
||||
bind(HgWorkingCopyFactory.class).to(SimpleHgWorkingCopyFactory.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,189 +21,48 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.web;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.aragost.javahg.Repository;
|
||||
import com.aragost.javahg.RepositoryConfiguration;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.repository.HgEnvironment;
|
||||
import sonia.scm.repository.HgHookManager;
|
||||
import sonia.scm.repository.HgPythonScript;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.spi.javahg.HgFileviewExtension;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public final class HgUtil
|
||||
{
|
||||
public final class HgUtil {
|
||||
|
||||
/** Field description */
|
||||
public static final String REVISION_TIP = "tip";
|
||||
|
||||
/** Field description */
|
||||
private static final String USERAGENT_HG = "mercurial/";
|
||||
|
||||
/**
|
||||
* the logger for HgUtil
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(HgUtil.class);
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*/
|
||||
private HgUtil() {}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param handler
|
||||
* @param hookManager
|
||||
* @param directory
|
||||
* @param encoding
|
||||
* @param pending
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Repository open(HgRepositoryHandler handler,
|
||||
HgHookManager hookManager, File directory, String encoding, boolean pending)
|
||||
{
|
||||
return open(
|
||||
handler,
|
||||
directory,
|
||||
encoding,
|
||||
pending,
|
||||
environment -> HgEnvironment.prepareEnvironment(environment, handler, hookManager)
|
||||
);
|
||||
}
|
||||
|
||||
public static Repository open(HgRepositoryHandler handler,
|
||||
File directory, String encoding, boolean pending,
|
||||
Consumer<Map<String, String>> prepareEnvironment)
|
||||
{
|
||||
String enc = encoding;
|
||||
|
||||
if (Strings.isNullOrEmpty(enc))
|
||||
{
|
||||
enc = handler.getConfig().getEncoding();
|
||||
}
|
||||
|
||||
RepositoryConfiguration repoConfiguration = RepositoryConfiguration.DEFAULT;
|
||||
|
||||
prepareEnvironment.accept(repoConfiguration.getEnvironment());
|
||||
|
||||
repoConfiguration.addExtension(HgFileviewExtension.class);
|
||||
repoConfiguration.setEnablePendingChangesets(pending);
|
||||
|
||||
try
|
||||
{
|
||||
Charset charset = Charset.forName(enc);
|
||||
|
||||
logger.trace("set encoding {} for mercurial", enc);
|
||||
|
||||
repoConfiguration.setEncoding(charset);
|
||||
}
|
||||
catch (IllegalArgumentException ex)
|
||||
{
|
||||
logger.error("could not set encoding for mercurial", ex);
|
||||
}
|
||||
|
||||
repoConfiguration.setHgBin(handler.getConfig().getHgBinary());
|
||||
|
||||
logger.debug("open hg repository {}: encoding: {}, pending: {}", directory, enc, pending);
|
||||
|
||||
return Repository.open(repoConfiguration, directory);
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param config
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getPythonPath(HgConfig config)
|
||||
{
|
||||
public static String getPythonPath(HgConfig config) {
|
||||
String pythonPath = Util.EMPTY_STRING;
|
||||
|
||||
if (config != null)
|
||||
{
|
||||
if (config != null) {
|
||||
pythonPath = Util.nonNull(config.getPythonPath());
|
||||
}
|
||||
|
||||
if (Util.isNotEmpty(pythonPath))
|
||||
{
|
||||
if (Util.isNotEmpty(pythonPath)) {
|
||||
pythonPath = pythonPath.concat(File.pathSeparator);
|
||||
}
|
||||
|
||||
//J-
|
||||
pythonPath = pythonPath.concat(
|
||||
HgPythonScript.getScriptDirectory(
|
||||
SCMContext.getContext()
|
||||
).getAbsolutePath()
|
||||
);
|
||||
//J+
|
||||
|
||||
return pythonPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param revision
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getRevision(String revision)
|
||||
{
|
||||
return Util.isEmpty(revision)
|
||||
? REVISION_TIP
|
||||
: revision;
|
||||
public static String getRevision(String revision) {
|
||||
return Util.isEmpty(revision) ? REVISION_TIP : revision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the request comes from a mercurial client.
|
||||
*
|
||||
*
|
||||
* @param request servlet request
|
||||
*
|
||||
* @return true if the client is mercurial
|
||||
*/
|
||||
public static boolean isHgClient(HttpServletRequest request)
|
||||
{
|
||||
return HttpUtil.userAgentStartsWith(request, USERAGENT_HG);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user