mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-01-24 08:19:10 +01:00
Merge pull request #1232 from scm-manager/feature/support_python_3
Support for mercurial on python 3
This commit is contained in:
@@ -19,9 +19,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Fixed restart with deb or rpm installation ([#1222](https://github.com/scm-manager/scm-manager/issues/1222) and [#1227](https://github.com/scm-manager/scm-manager/pull/1227))
|
||||
- Fixed broken migration with empty security.xml ([#1219](https://github.com/scm-manager/scm-manager/issues/1219) and [#1221](https://github.com/scm-manager/scm-manager/pull/1221))
|
||||
- Added missing architecture to debian installation documentation ([#1230](https://github.com/scm-manager/scm-manager/pull/1230))
|
||||
- Mercurial on Python 3 ([#1232](https://github.com/scm-manager/scm-manager/pull/1232))
|
||||
- Fixed wrong package information for deb and rpm packages ([#1229](https://github.com/scm-manager/scm-manager/pull/1229))
|
||||
|
||||
|
||||
## [2.1.1] - 2020-06-23
|
||||
### Fixed
|
||||
- Wait until recommended java installation is available for deb packages ([#1209](https://github.com/scm-manager/scm-manager/pull/1209))
|
||||
|
||||
@@ -22,20 +22,6 @@
|
||||
# SOFTWARE.
|
||||
#
|
||||
|
||||
# we need to build mercurial by ourself,
|
||||
# because we need mercurial with python 2 for now
|
||||
# TODO remove if python3 support is available for scm-manager hg extensions
|
||||
FROM adoptopenjdk/openjdk11:jdk-11.0.7_10-alpine-slim as build
|
||||
WORKDIR /src
|
||||
RUN apk add --upgrade alpine-sdk python2 python2-dev
|
||||
RUN set -x \
|
||||
&& wget https://www.mercurial-scm.org/release/mercurial-4.9.1.tar.gz \
|
||||
&& tar xvfz mercurial-4.9.1.tar.gz \
|
||||
&& rm -f mercurial-4.9.1.tar.gz
|
||||
WORKDIR /src/mercurial-4.9.1
|
||||
RUN make build
|
||||
RUN make PREFIX=/usr install-bin
|
||||
|
||||
FROM adoptopenjdk/openjdk11:jdk-11.0.7_10-alpine-slim
|
||||
ENV SCM_HOME=/var/lib/scm
|
||||
ENV CACHE_DIR=/var/cache/scm/work
|
||||
@@ -43,17 +29,13 @@ ENV CACHE_DIR=/var/cache/scm/work
|
||||
COPY . /
|
||||
|
||||
RUN set -x \
|
||||
&& apk add --no-cache python2 bash ca-certificates \
|
||||
&& apk add --no-cache mercurial bash ca-certificates \
|
||||
&& addgroup -S -g 1000 scm \
|
||||
&& adduser -S -s /bin/false -G scm -h ${SCM_HOME} -D -H -u 1000 scm \
|
||||
&& mkdir -p ${SCM_HOME} ${CACHE_DIR} \
|
||||
&& chmod +x /opt/scm-server/bin/scm-server \
|
||||
&& chown scm:scm ${SCM_HOME} ${CACHE_DIR}
|
||||
|
||||
# copy mercurial installation
|
||||
COPY --from=build /usr/bin/hg /usr/bin/hg
|
||||
COPY --from=build /usr/lib/python2.7 /usr/lib/python2.7
|
||||
|
||||
WORKDIR "/opt/scm-server"
|
||||
VOLUME ["${SCM_HOME}", "${CACHE_DIR}"]
|
||||
EXPOSE 8080
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<dependency>
|
||||
<groupId>com.aragost.javahg</groupId>
|
||||
<artifactId>javahg</artifactId>
|
||||
<version>0.14</version>
|
||||
<version>0.15-scm1</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.google.guava</groupId>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.autoconfig;
|
||||
|
||||
import sonia.scm.Platform;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.util.SystemUtil;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface AutoConfigurator {
|
||||
|
||||
HgConfig configure();
|
||||
|
||||
HgConfig configure(Path hg);
|
||||
|
||||
static Optional<AutoConfigurator> get() {
|
||||
// at the moment we have only support for posix based systems
|
||||
Platform platform = SystemUtil.getPlatform();
|
||||
if (platform.isPosix()) {
|
||||
return Optional.of(new PosixAutoConfigurator(System.getenv()));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.autoconfig;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.MoreFiles;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
public class PosixAutoConfigurator implements AutoConfigurator {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PosixAutoConfigurator.class);
|
||||
|
||||
private static final List<String> ADDITIONAL_PATH = ImmutableList.of(
|
||||
"/usr/bin",
|
||||
"/usr/local/bin",
|
||||
"/opt/local/bin"
|
||||
);
|
||||
|
||||
private final Set<String> fsPaths;
|
||||
|
||||
private Executor executor = (Path binary, String... args) -> {
|
||||
ProcessBuilder builder = new ProcessBuilder(
|
||||
Lists.asList(binary.toString(), args).toArray(new String[0])
|
||||
);
|
||||
Process process = builder.start();
|
||||
int rc = process.waitFor();
|
||||
if (rc != 0) {
|
||||
throw new IOException(binary.toString() + " failed with return code " + rc);
|
||||
}
|
||||
return process.getInputStream();
|
||||
};
|
||||
|
||||
PosixAutoConfigurator(Map<String, String> env) {
|
||||
this(env, ADDITIONAL_PATH);
|
||||
}
|
||||
|
||||
PosixAutoConfigurator(Map<String, String> env, List<String> additionalPaths) {
|
||||
String path = env.getOrDefault("PATH", "");
|
||||
fsPaths = new LinkedHashSet<>();
|
||||
fsPaths.addAll(Splitter.on(File.pathSeparator).splitToList(path));
|
||||
fsPaths.addAll(additionalPaths);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setExecutor(Executor executor) {
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HgConfig configure() {
|
||||
Optional<Path> hg = findInPath("hg");
|
||||
if (hg.isPresent()) {
|
||||
return configure(hg.get());
|
||||
}
|
||||
return new HgConfig();
|
||||
}
|
||||
|
||||
private Optional<Path> findInPath(String binary) {
|
||||
for (String directory : fsPaths) {
|
||||
Path binaryPath = Paths.get(directory, binary);
|
||||
if (Files.exists(binaryPath)) {
|
||||
return Optional.of(binaryPath);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
private Optional<Path> findModulePath(Path hg) {
|
||||
if (!Files.isExecutable(hg)) {
|
||||
LOG.warn("{} is not executable", hg);
|
||||
return Optional.empty();
|
||||
}
|
||||
try {
|
||||
InputStream debuginstall = executor.execute(hg, "debuginstall");
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(debuginstall))) {
|
||||
while (reader.ready()) {
|
||||
String line = reader.readLine();
|
||||
if (line.contains("installed modules")) {
|
||||
int start = line.indexOf("(");
|
||||
int end = line.indexOf(")");
|
||||
Path modulePath = Paths.get(line.substring(start + 1, end));
|
||||
if (Files.exists(modulePath)) {
|
||||
// installed modules contains the path to the mercurial module,
|
||||
// but we need the parent for the python path
|
||||
return Optional.of(modulePath.getParent());
|
||||
} else {
|
||||
LOG.warn("could not find module path at {}", modulePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
LOG.warn("failed to parse debuginstall of {}", hg);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warn("interrupted during debuginstall parsing of {}", hg);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HgConfig configure(Path hg) {
|
||||
HgConfig config = new HgConfig();
|
||||
try {
|
||||
if (Files.exists(hg)) {
|
||||
configureWithExistingHg(hg, config);
|
||||
} else {
|
||||
LOG.warn("{} does not exists", hg);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warn("failed to read first line of {}", hg);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
private void configureWithExistingHg(Path hg, HgConfig config) throws IOException {
|
||||
config.setHgBinary(hg.toAbsolutePath().toString());
|
||||
Optional<Path> pythonFromShebang = findPythonFromShebang(hg);
|
||||
if (pythonFromShebang.isPresent()) {
|
||||
config.setPythonBinary(pythonFromShebang.get().toAbsolutePath().toString());
|
||||
} else {
|
||||
LOG.warn("could not find python from shebang, searching for python in path");
|
||||
Optional<Path> python = findInPath("python");
|
||||
if (!python.isPresent()) {
|
||||
LOG.warn("could not find python in path, searching for python3 instead");
|
||||
python = findInPath("python3");
|
||||
}
|
||||
if (python.isPresent()) {
|
||||
config.setPythonBinary(python.get().toAbsolutePath().toString());
|
||||
} else {
|
||||
LOG.warn("could not find python in path");
|
||||
}
|
||||
}
|
||||
|
||||
Optional<Path> modulePath = findModulePath(hg);
|
||||
if (modulePath.isPresent()) {
|
||||
config.setPythonPath(modulePath.get().toAbsolutePath().toString());
|
||||
} else {
|
||||
LOG.warn("could not find module path");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Optional<Path> findPythonFromShebang(Path hg) throws IOException {
|
||||
String shebang = MoreFiles.asCharSource(hg, StandardCharsets.UTF_8).readFirstLine();
|
||||
if (shebang != null && shebang.startsWith("#!")) {
|
||||
String substring = shebang.substring(2);
|
||||
String[] parts = substring.split("\\s+");
|
||||
if (parts.length > 1) {
|
||||
return findInPath(parts[1]);
|
||||
} else {
|
||||
Path python = Paths.get(parts[0]);
|
||||
if (Files.exists(python)) {
|
||||
return Optional.of(python);
|
||||
} else {
|
||||
LOG.warn("python binary from shebang {} does not exists", python);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG.warn("first line does not look like a shebang: {}", shebang);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface Executor {
|
||||
InputStream execute(Path binary, String... args) throws IOException, InterruptedException;
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.installer;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
@@ -40,7 +40,9 @@ import java.io.IOException;
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @deprecated use {@link sonia.scm.autoconfig.AutoConfigurator}
|
||||
*/
|
||||
@Deprecated
|
||||
public class MacOSHgInstaller extends UnixHgInstaller
|
||||
{
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.installer;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
@@ -40,7 +40,9 @@ import java.util.List;
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @deprecated use {@link sonia.scm.autoconfig.AutoConfigurator}
|
||||
*/
|
||||
@Deprecated
|
||||
public class UnixHgInstaller extends AbstractHgInstaller
|
||||
{
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
@@ -36,11 +36,9 @@ import java.io.File;
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public enum HgPythonScript
|
||||
{
|
||||
BLAME("blame.py"), CHANGELOG("changelog.py"), FILELOG("filelog.py"),
|
||||
LOG("log.py"), UTIL("util.py"), HOOK("scmhooks.py"), HGWEB("hgweb.py"),
|
||||
VERSION("version.py");
|
||||
public enum HgPythonScript {
|
||||
|
||||
HOOK("scmhooks.py"), HGWEB("hgweb.py"), VERSION("version.py");
|
||||
|
||||
/** Field description */
|
||||
private static final String BASE_DIRECTORY =
|
||||
|
||||
@@ -33,6 +33,7 @@ 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;
|
||||
import sonia.scm.installer.HgInstallerFactory;
|
||||
import sonia.scm.io.ExtendedCommand;
|
||||
@@ -55,6 +56,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Optional;
|
||||
|
||||
@Singleton
|
||||
@Extension
|
||||
@@ -91,14 +93,11 @@ public class HgRepositoryHandler
|
||||
this.hgContextProvider = hgContextProvider;
|
||||
this.workingCopyFactory = workingCopyFactory;
|
||||
|
||||
try
|
||||
{
|
||||
try {
|
||||
this.jaxbContext = JAXBContext.newInstance(BrowserResult.class,
|
||||
BlameResult.class, Changeset.class, ChangesetPagingResult.class,
|
||||
HgVersion.class);
|
||||
}
|
||||
catch (JAXBException ex)
|
||||
{
|
||||
} catch (JAXBException ex) {
|
||||
throw new ConfigurationException("could not create jaxbcontext", ex);
|
||||
}
|
||||
}
|
||||
@@ -139,7 +138,19 @@ public class HgRepositoryHandler
|
||||
super.loadConfig();
|
||||
|
||||
if (config == null) {
|
||||
doAutoConfiguration(new HgConfig());
|
||||
HgConfig config = null;
|
||||
Optional<AutoConfigurator> autoConfigurator = AutoConfigurator.get();
|
||||
if (autoConfigurator.isPresent()) {
|
||||
config = autoConfigurator.get().configure();
|
||||
}
|
||||
|
||||
if (config != null && config.isValid()) {
|
||||
this.config = config;
|
||||
storeConfig();
|
||||
} else {
|
||||
// do the old configuration
|
||||
doAutoConfiguration(config != null ? config : new HgConfig());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ package sonia.scm.repository.spi;
|
||||
|
||||
import com.aragost.javahg.Changeset;
|
||||
import com.aragost.javahg.commands.AnnotateCommand;
|
||||
import com.aragost.javahg.commands.AnnotateLine;
|
||||
import com.aragost.javahg.AnnotateLine;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
@@ -60,16 +60,16 @@ public class HgBranchesCommand extends AbstractCommand
|
||||
|
||||
@Override
|
||||
public List<Branch> getBranches() {
|
||||
List<com.aragost.javahg.commands.Branch> hgBranches =
|
||||
List<com.aragost.javahg.Branch> hgBranches =
|
||||
com.aragost.javahg.commands.BranchesCommand.on(open()).execute();
|
||||
|
||||
List<Branch> branches = Lists.transform(hgBranches,
|
||||
new Function<com.aragost.javahg.commands.Branch,
|
||||
new Function<com.aragost.javahg.Branch,
|
||||
Branch>()
|
||||
{
|
||||
|
||||
@Override
|
||||
public Branch apply(com.aragost.javahg.commands.Branch hgBranch)
|
||||
public Branch apply(com.aragost.javahg.Branch hgBranch)
|
||||
{
|
||||
String node = null;
|
||||
Changeset changeset = hgBranch.getBranchTip();
|
||||
|
||||
@@ -69,7 +69,7 @@ public class HgTagsCommand extends AbstractCommand implements TagsCommand
|
||||
com.aragost.javahg.commands.TagsCommand cmd =
|
||||
com.aragost.javahg.commands.TagsCommand.on(open());
|
||||
|
||||
List<com.aragost.javahg.commands.Tag> tagList = cmd.includeTip().execute();
|
||||
List<com.aragost.javahg.Tag> tagList = cmd.includeTip().execute();
|
||||
|
||||
List<Tag> tags = null;
|
||||
|
||||
@@ -97,7 +97,7 @@ public class HgTagsCommand extends AbstractCommand implements TagsCommand
|
||||
* @author Enter your name here...
|
||||
*/
|
||||
private static class TagTransformer
|
||||
implements Function<com.aragost.javahg.commands.Tag, Tag>
|
||||
implements Function<com.aragost.javahg.Tag, Tag>
|
||||
{
|
||||
|
||||
/**
|
||||
@@ -109,7 +109,7 @@ public class HgTagsCommand extends AbstractCommand implements TagsCommand
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Tag apply(com.aragost.javahg.commands.Tag f)
|
||||
public Tag apply(com.aragost.javahg.Tag f)
|
||||
{
|
||||
Tag t = null;
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ except ImportError:
|
||||
from mercurial import util
|
||||
_parsedate = util.parsedate
|
||||
|
||||
FILE_MARKER = '<files>'
|
||||
FILE_MARKER = b'<files>'
|
||||
|
||||
class File_Collector:
|
||||
|
||||
@@ -62,7 +62,7 @@ class File_Collector:
|
||||
self.attach(self.extract_name_without_parent(path, p), self.structure, dir_only)
|
||||
|
||||
def attach(self, branch, trunk, dir_only = False):
|
||||
parts = branch.split('/', 1)
|
||||
parts = branch.split(b'/', 1)
|
||||
if len(parts) == 1: # branch is a file
|
||||
if dir_only:
|
||||
trunk[parts[0]] = defaultdict(dict, ((FILE_MARKER, []),))
|
||||
@@ -78,7 +78,7 @@ class File_Collector:
|
||||
def extract_name_without_parent(self, parent, name_with_parent):
|
||||
if len(parent) > 0:
|
||||
name_without_parent = name_with_parent[len(parent):]
|
||||
if name_without_parent.startswith("/"):
|
||||
if name_without_parent.startswith(b"/"):
|
||||
name_without_parent = name_without_parent[1:]
|
||||
return name_without_parent
|
||||
return name_with_parent
|
||||
@@ -91,11 +91,11 @@ class File_Object:
|
||||
self.sub_repository = None
|
||||
|
||||
def get_name(self):
|
||||
parts = self.path.split("/")
|
||||
parts = self.path.split(b"/")
|
||||
return parts[len(parts) - 1]
|
||||
|
||||
def get_parent(self):
|
||||
idx = self.path.rfind("/")
|
||||
idx = self.path.rfind(b"/")
|
||||
if idx > 0:
|
||||
return self.path[0:idx]
|
||||
return ""
|
||||
@@ -112,7 +112,7 @@ class File_Object:
|
||||
def __repr__(self):
|
||||
result = self.path
|
||||
if self.directory:
|
||||
result += "/"
|
||||
result += b"/"
|
||||
return result
|
||||
|
||||
class File_Walker:
|
||||
@@ -143,11 +143,15 @@ class File_Walker:
|
||||
|
||||
def create_path(self, parent, path):
|
||||
if len(parent) > 0:
|
||||
return parent + "/" + path
|
||||
return parent + b"/" + path
|
||||
return path
|
||||
|
||||
def walk(self, structure, parent = ""):
|
||||
sortedItems = sorted(structure.iteritems(), key = lambda item: self.sortKey(item))
|
||||
def walk(self, structure, parent = b""):
|
||||
if hasattr(structure, "iteritems"):
|
||||
items = structure.iteritems()
|
||||
else:
|
||||
items = structure.items()
|
||||
sortedItems = sorted(items, key = lambda item: self.sortKey(item))
|
||||
for key, value in sortedItems:
|
||||
if key == FILE_MARKER:
|
||||
if value:
|
||||
@@ -162,9 +166,9 @@ class File_Walker:
|
||||
|
||||
def sortKey(self, item):
|
||||
if (item[0] == FILE_MARKER):
|
||||
return "2"
|
||||
return b"2"
|
||||
else:
|
||||
return "1" + item[0]
|
||||
return b"1" + item[0]
|
||||
|
||||
class SubRepository:
|
||||
url = None
|
||||
@@ -173,9 +177,9 @@ class SubRepository:
|
||||
def collect_sub_repositories(revCtx):
|
||||
subrepos = {}
|
||||
try:
|
||||
hgsub = revCtx.filectx('.hgsub').data().split('\n')
|
||||
hgsub = revCtx.filectx(b'.hgsub').data().split('\n')
|
||||
for line in hgsub:
|
||||
parts = line.split('=')
|
||||
parts = line.split(b'=')
|
||||
if len(parts) > 1:
|
||||
subrepo = SubRepository()
|
||||
subrepo.url = parts[1].strip()
|
||||
@@ -184,7 +188,7 @@ def collect_sub_repositories(revCtx):
|
||||
pass
|
||||
|
||||
try:
|
||||
hgsubstate = revCtx.filectx('.hgsubstate').data().split('\n')
|
||||
hgsubstate = revCtx.filectx(b'.hgsubstate').data().split('\n')
|
||||
for line in hgsubstate:
|
||||
parts = line.split(' ')
|
||||
if len(parts) > 1:
|
||||
@@ -219,31 +223,31 @@ class File_Printer:
|
||||
def print_directory(self, path):
|
||||
if not self.initial_path_printed or self.offset == 0 or self.shouldPrintResult():
|
||||
self.initial_path_printed = True
|
||||
format = '%s/\n'
|
||||
format = b'%s/\n'
|
||||
if self.transport:
|
||||
format = 'd%s/\0'
|
||||
format = b'd%s/\0'
|
||||
self.writer.write( format % path)
|
||||
|
||||
def print_file(self, path):
|
||||
self.result_count += 1
|
||||
if self.shouldPrintResult():
|
||||
file = self.revCtx[path]
|
||||
date = '0 0'
|
||||
description = 'n/a'
|
||||
date = b'0 0'
|
||||
description = b'n/a'
|
||||
if not self.disableLastCommit:
|
||||
linkrev = self.repo[file.linkrev()]
|
||||
date = '%d %d' % _parsedate(linkrev.date())
|
||||
date = b'%d %d' % _parsedate(linkrev.date())
|
||||
description = linkrev.description()
|
||||
format = '%s %i %s %s\n'
|
||||
format = b'%s %i %s %s\n'
|
||||
if self.transport:
|
||||
format = 'f%s\n%i %s %s\0'
|
||||
format = b'f%s\n%i %s %s\0'
|
||||
self.writer.write( format % (file.path(), file.size(), date, description) )
|
||||
|
||||
def print_sub_repository(self, path, subrepo):
|
||||
if self.shouldPrintResult():
|
||||
format = '%s/ %s %s\n'
|
||||
format = b'%s/ %s %s\n'
|
||||
if self.transport:
|
||||
format = 's%s/\n%s %s\0'
|
||||
format = b's%s/\n%s %s\0'
|
||||
self.writer.write( format % (path, subrepo.revision, subrepo.url))
|
||||
|
||||
def visit(self, file):
|
||||
@@ -263,9 +267,9 @@ class File_Printer:
|
||||
def finish(self):
|
||||
if self.isTruncated():
|
||||
if self.transport:
|
||||
self.writer.write( "t")
|
||||
self.writer.write(b"t")
|
||||
else:
|
||||
self.writer.write("truncated")
|
||||
self.writer.write(b"truncated")
|
||||
|
||||
class File_Viewer:
|
||||
def __init__(self, revCtx, visitor):
|
||||
@@ -275,11 +279,11 @@ class File_Viewer:
|
||||
self.recursive = False
|
||||
|
||||
def remove_ending_slash(self, path):
|
||||
if path.endswith("/"):
|
||||
if path.endswith(b"/"):
|
||||
return path[:-1]
|
||||
return path
|
||||
|
||||
def view(self, path = ""):
|
||||
def view(self, path = b""):
|
||||
manifest = self.revCtx.manifest()
|
||||
if len(path) > 0 and path in manifest:
|
||||
self.visitor.visit(File_Object(False, path))
|
||||
@@ -294,15 +298,15 @@ class File_Viewer:
|
||||
collector.collect(self.sub_repositories.keys(), p, True)
|
||||
walker.walk(collector.structure, p)
|
||||
|
||||
@command('fileview', [
|
||||
('r', 'revision', 'tip', 'revision to print'),
|
||||
('p', 'path', '', 'path to print'),
|
||||
('c', 'recursive', False, 'browse repository recursive'),
|
||||
('d', 'disableLastCommit', False, 'disables last commit description and date'),
|
||||
('s', 'disableSubRepositoryDetection', False, 'disables detection of sub repositories'),
|
||||
('t', 'transport', False, 'format the output for command server'),
|
||||
('l', 'limit', 100, 'limit the number of results'),
|
||||
('o', 'offset', 0, 'proceed from the given result number (zero based)'),
|
||||
@command(b'fileview', [
|
||||
(b'r', b'revision', b'tip', b'revision to print'),
|
||||
(b'p', b'path', b'', b'path to print'),
|
||||
(b'c', b'recursive', False, b'browse repository recursive'),
|
||||
(b'd', b'disableLastCommit', False, b'disables last commit description and date'),
|
||||
(b's', b'disableSubRepositoryDetection', False, b'disables detection of sub repositories'),
|
||||
(b't', b'transport', False, b'format the output for command server'),
|
||||
(b'l', b'limit', 100, b'limit the number of results'),
|
||||
(b'o', b'offset', 0, b'proceed from the given result number (zero based)'),
|
||||
])
|
||||
def fileview(ui, repo, **opts):
|
||||
revCtx = scmutil.revsingle(repo, opts["revision"])
|
||||
|
||||
@@ -1,57 +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.
|
||||
#
|
||||
|
||||
import os
|
||||
from util import *
|
||||
from xml.dom.minidom import Document
|
||||
|
||||
def appendBlameLine(doc, parent, lineCtx, lineNumber):
|
||||
ctx = lineCtx[0].changectx()
|
||||
lineNode = createChildNode(doc, parent, 'blameline')
|
||||
appendTextNode(doc, lineNode, 'lineNumber', str(lineNumber))
|
||||
appendTextNode(doc, lineNode, 'revision', getId(ctx))
|
||||
appendDateNode(doc, lineNode, 'when', ctx.date())
|
||||
appendAuthorNodes(doc, lineNode, ctx)
|
||||
appendTextNode(doc, lineNode, 'description', ctx.description())
|
||||
appendTextNode(doc, lineNode, 'code', lineCtx[1][:-1])
|
||||
|
||||
def appendBlameLines(doc, repo, revision, path):
|
||||
blameResult = createChildNode(doc, doc, 'blame-result')
|
||||
blameLines = createChildNode(doc, blameResult, 'blamelines')
|
||||
linesCtx = repo[revision][path].annotate()
|
||||
lineNumber = 0
|
||||
for lineCtx in linesCtx:
|
||||
lineNumber += 1
|
||||
appendBlameLine(doc, blameLines, lineCtx, lineNumber)
|
||||
appendTextNode(doc, blameResult, 'total', str(lineNumber))
|
||||
|
||||
# main method
|
||||
|
||||
repo = openRepository()
|
||||
revision = os.environ['SCM_REVISION']
|
||||
path = os.environ['SCM_PATH']
|
||||
|
||||
doc = Document()
|
||||
appendBlameLines(doc, repo, revision, path)
|
||||
writeXml(doc)
|
||||
@@ -1,143 +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.
|
||||
#
|
||||
|
||||
|
||||
import os
|
||||
from util import *
|
||||
from xml.dom.minidom import Document
|
||||
|
||||
# changeset methods
|
||||
|
||||
def appendIdNode(doc, parentNode, ctx):
|
||||
id = getId(ctx)
|
||||
appendTextNode(doc, parentNode, 'id', id)
|
||||
|
||||
def appendParentNodes(doc, parentNode, ctx):
|
||||
parents = ctx.parents()
|
||||
if parents:
|
||||
for parent in parents:
|
||||
parentId = getId(parent)
|
||||
appendTextNode(doc, parentNode, 'parents', parentId)
|
||||
|
||||
def appendBranchesNode(doc, parentNode, ctx):
|
||||
branch = ctx.branch()
|
||||
if branch != 'default':
|
||||
appendTextNode(doc, parentNode, 'branches', branch)
|
||||
|
||||
def appendModifications(doc, parentNode, ctx):
|
||||
status = repo.status(ctx.p1().node(), ctx.node())
|
||||
if status:
|
||||
modificationsNode = createChildNode(doc, parentNode, 'modifications')
|
||||
appendWrappedListNodes(doc, modificationsNode, 'added', 'file', status[1])
|
||||
appendWrappedListNodes(doc, modificationsNode, 'modified', 'file', status[0])
|
||||
appendWrappedListNodes(doc, modificationsNode, 'removed', 'file', status[2])
|
||||
|
||||
def appendChangesetNode(doc, parentNode, ctx):
|
||||
changesetNode = createChildNode(doc, parentNode, 'changeset')
|
||||
appendIdNode(doc, changesetNode, ctx)
|
||||
appendParentNodes(doc, changesetNode, ctx)
|
||||
appendTextNode(doc, changesetNode, 'description', ctx.description())
|
||||
appendDateNode(doc, changesetNode, 'date', ctx.date())
|
||||
appendAuthorNodes(doc, changesetNode, ctx)
|
||||
appendBranchesNode(doc, changesetNode, ctx)
|
||||
appendListNodes(doc, changesetNode, 'tags', ctx.tags())
|
||||
appendModifications(doc, changesetNode, ctx)
|
||||
|
||||
# changeset methods end
|
||||
|
||||
# change log methods
|
||||
|
||||
def createBasicNodes(doc, ctxs):
|
||||
rootNode = doc.createElement('changeset-paging')
|
||||
doc.appendChild(rootNode)
|
||||
total = str(len(repo))
|
||||
appendTextNode(doc, rootNode, 'total', total)
|
||||
return createChildNode(doc, rootNode, 'changesets')
|
||||
|
||||
def appendChangesetsForPath(doc, repo, rev, path):
|
||||
if len(rev) <= 0:
|
||||
rev = "tip"
|
||||
fctxs = repo[rev].filectx(path)
|
||||
maxRev = fctxs.rev()
|
||||
revs = []
|
||||
for i in fctxs.filelog():
|
||||
fctx = fctxs.filectx(i)
|
||||
if fctx.rev() <= maxRev:
|
||||
revs.append(fctx.changectx())
|
||||
# reverse changesets
|
||||
revs.reverse()
|
||||
# handle paging
|
||||
start = os.environ['SCM_PAGE_START']
|
||||
limit = os.environ['SCM_PAGE_LIMIT']
|
||||
if len(start) > 0:
|
||||
revs = revs[int(start):]
|
||||
if len(limit) > 0:
|
||||
revs = revs[:int(limit)]
|
||||
# output
|
||||
changesets = createBasicNodes(doc, revs)
|
||||
for ctx in revs:
|
||||
appendChangesetNode(doc, changesets, ctx)
|
||||
|
||||
def appendChangesetsForStartAndEnd(doc, repo, startRev, endRev):
|
||||
changesets = createBasicNodes(doc, repo)
|
||||
for i in range(endRev, startRev, -1):
|
||||
appendChangesetNode(doc, changesets, repo[i])
|
||||
|
||||
# change log methods
|
||||
|
||||
# main method
|
||||
repo = openRepository()
|
||||
doc = Document()
|
||||
|
||||
path = os.environ['SCM_PATH']
|
||||
startNode = os.environ['SCM_REVISION_START']
|
||||
endNode = os.environ['SCM_REVISION_END']
|
||||
rev = os.environ['SCM_REVISION']
|
||||
|
||||
if len(path) > 0:
|
||||
appendChangesetsForPath(doc, repo, rev, path)
|
||||
elif len(rev) > 0:
|
||||
ctx = repo[rev]
|
||||
appendChangesetNode(doc, doc, ctx)
|
||||
else:
|
||||
if len(startNode) > 0 and len(endNode) > 0:
|
||||
# start and end revision
|
||||
startRev = repo[startNode].rev() -1
|
||||
endRev = repo[endNode].rev()
|
||||
else:
|
||||
# paging
|
||||
start = os.environ['SCM_PAGE_START']
|
||||
limit = os.environ['SCM_PAGE_LIMIT']
|
||||
limit = int(limit)
|
||||
end = int(start)
|
||||
endRev = len(repo) - end - 1
|
||||
startRev = endRev - limit
|
||||
# fix negative start revisions
|
||||
if startRev < -1:
|
||||
startRev = -1
|
||||
# print
|
||||
appendChangesetsForStartAndEnd(doc, repo, startRev, endRev)
|
||||
|
||||
# write document
|
||||
writeXml(doc)
|
||||
@@ -1,159 +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.
|
||||
#
|
||||
|
||||
import os
|
||||
from util import *
|
||||
from xml.dom.minidom import Document
|
||||
|
||||
class SubRepository:
|
||||
url = None
|
||||
revision = None
|
||||
|
||||
def getName(path):
|
||||
parts = path.split('/')
|
||||
length = len(parts)
|
||||
if path.endswith('/'):
|
||||
length =- 1
|
||||
return parts[length - 1]
|
||||
|
||||
def removeTrailingSlash(path):
|
||||
if path.endswith('/'):
|
||||
path = path[0:-1]
|
||||
return path
|
||||
|
||||
def appendSubRepositoryNode(doc, parentNode, path, subRepositories):
|
||||
if path in subRepositories:
|
||||
subRepository = subRepositories[path]
|
||||
subRepositoryNode = createChildNode(doc, parentNode, 'subrepository')
|
||||
if subRepository.revision != None:
|
||||
appendTextNode(doc, subRepositoryNode, 'revision', subRepository.revision)
|
||||
appendTextNode(doc, subRepositoryNode, 'repository-url', subRepository.url)
|
||||
|
||||
def createBasicFileNode(doc, parentNode, path, directory):
|
||||
fileNode = createChildNode(doc, parentNode, 'file')
|
||||
appendTextNode(doc, fileNode, 'name', getName(path))
|
||||
appendTextNode(doc, fileNode, 'path', removeTrailingSlash(path))
|
||||
appendTextNode(doc, fileNode, 'directory', directory)
|
||||
return fileNode
|
||||
|
||||
def appendDirectoryNode(doc, parentNode, path, subRepositories):
|
||||
fileNode = createBasicFileNode(doc, parentNode, path, 'true')
|
||||
appendSubRepositoryNode(doc, fileNode, path, subRepositories)
|
||||
|
||||
def appendFileNode(doc, parentNode, repo, file):
|
||||
linkrev = repo[file.linkrev()]
|
||||
fileNode = createBasicFileNode(doc, parentNode, file.path(), 'false')
|
||||
appendTextNode(doc, fileNode, 'length', str(file.size()))
|
||||
appendDateNode(doc, fileNode, 'lastModified', linkrev.date())
|
||||
appendTextNode(doc, fileNode, 'description', linkrev.description())
|
||||
|
||||
def createSubRepositoryMap(revCtx):
|
||||
subrepos = {}
|
||||
try:
|
||||
hgsub = revCtx.filectx('.hgsub').data().split('\n')
|
||||
for line in hgsub:
|
||||
parts = line.split('=')
|
||||
if len(parts) > 1:
|
||||
subrepo = SubRepository()
|
||||
subrepo.url = parts[1].strip()
|
||||
subrepos[parts[0].strip()] = subrepo
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
hgsubstate = revCtx.filectx('.hgsubstate').data().split('\n')
|
||||
for line in hgsubstate:
|
||||
parts = line.split(' ')
|
||||
if len(parts) > 1:
|
||||
subrev = parts[0].strip()
|
||||
subrepo = subrepos[parts[1].strip()]
|
||||
subrepo.revision = subrev
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return subrepos
|
||||
|
||||
def appendSubRepositoryDirectories(directories, subRepositories):
|
||||
for k, v in subRepositories.iteritems():
|
||||
if k.startswith(path):
|
||||
directories.append(k)
|
||||
|
||||
def collectFiles(repo, revCtx, files, directories):
|
||||
length = 0
|
||||
paths = []
|
||||
mf = revCtx.manifest()
|
||||
if path is "":
|
||||
length = 1
|
||||
for f in mf:
|
||||
paths.append(f)
|
||||
else:
|
||||
length = len(path.split('/')) + 1
|
||||
directory = path
|
||||
if not directory.endswith('/'):
|
||||
directory += '/'
|
||||
|
||||
for f in mf:
|
||||
if f.startswith(directory):
|
||||
paths.append(f)
|
||||
|
||||
for p in paths:
|
||||
parts = p.split('/')
|
||||
depth = len(parts)
|
||||
if depth is length:
|
||||
file = repo[revision][p]
|
||||
files.append(file)
|
||||
elif depth > length:
|
||||
dirpath = ''
|
||||
for i in range(0, length):
|
||||
dirpath += parts[i] + '/'
|
||||
if not dirpath in directories:
|
||||
directories.append(dirpath)
|
||||
|
||||
def appendFileNodes(doc, parentNode, repo, revision):
|
||||
files = []
|
||||
directories = []
|
||||
revCtx = repo[revision]
|
||||
subRepositories = createSubRepositoryMap(revCtx)
|
||||
appendSubRepositoryDirectories(directories, subRepositories)
|
||||
collectFiles(repo, revCtx, files, directories)
|
||||
for dir in directories:
|
||||
appendDirectoryNode(doc, parentNode, dir, subRepositories)
|
||||
for file in files:
|
||||
appendFileNode(doc, parentNode, repo, file)
|
||||
|
||||
|
||||
# main method
|
||||
|
||||
repo = openRepository()
|
||||
revision = os.environ['SCM_REVISION']
|
||||
path = os.environ['SCM_PATH']
|
||||
|
||||
# create document and append nodes
|
||||
|
||||
doc = Document()
|
||||
browserResultNode = createChildNode(doc, doc, 'browser-result')
|
||||
appendTextNode(doc, browserResultNode, 'revision', revision)
|
||||
filesNode = createChildNode(doc, browserResultNode, 'files')
|
||||
appendFileNodes(doc, filesNode, repo, revision)
|
||||
writeXml(doc)
|
||||
@@ -35,20 +35,20 @@ except AttributeError:
|
||||
# For installations earlier than Mercurial 4.1
|
||||
u = uimod.ui()
|
||||
|
||||
u.setconfig('web', 'push_ssl', 'false')
|
||||
u.setconfig('web', 'allow_read', '*')
|
||||
u.setconfig('web', 'allow_push', '*')
|
||||
u.setconfig(b'web', b'push_ssl', b'false')
|
||||
u.setconfig(b'web', b'allow_read', b'*')
|
||||
u.setconfig(b'web', b'allow_push', b'*')
|
||||
|
||||
u.setconfig('hooks', 'changegroup.scm', 'python:scmhooks.postHook')
|
||||
u.setconfig('hooks', 'pretxnchangegroup.scm', 'python:scmhooks.preHook')
|
||||
u.setconfig(b'hooks', b'changegroup.scm', b'python:scmhooks.postHook')
|
||||
u.setconfig(b'hooks', b'pretxnchangegroup.scm', b'python:scmhooks.preHook')
|
||||
|
||||
# pass SCM_HTTP_POST_ARGS to enable experimental httppostargs protocol of mercurial
|
||||
# SCM_HTTP_POST_ARGS is set by HgCGIServlet
|
||||
# Issue 970: https://goo.gl/poascp
|
||||
u.setconfig('experimental', 'httppostargs', os.environ['SCM_HTTP_POST_ARGS'])
|
||||
u.setconfig(b'experimental', b'httppostargs', os.environ['SCM_HTTP_POST_ARGS'].encode())
|
||||
|
||||
# open repository
|
||||
# SCM_REPOSITORY_PATH contains the repository path and is set by HgCGIServlet
|
||||
r = hg.repository(u, os.environ['SCM_REPOSITORY_PATH'])
|
||||
r = hg.repository(u, os.environ['SCM_REPOSITORY_PATH'].encode())
|
||||
application = hgweb(r)
|
||||
wsgicgi.launch(application)
|
||||
|
||||
@@ -1,140 +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.
|
||||
#
|
||||
|
||||
|
||||
import os
|
||||
from util import *
|
||||
from xml.dom.minidom import Document
|
||||
|
||||
# changeset methods
|
||||
|
||||
def appendIdNode(doc, parentNode, ctx):
|
||||
id = getId(ctx)
|
||||
appendTextNode(doc, parentNode, 'id', id)
|
||||
|
||||
def appendParentNodes(doc, parentNode, ctx):
|
||||
parents = ctx.parents()
|
||||
if parents:
|
||||
for parent in parents:
|
||||
parentId = getId(parent)
|
||||
appendTextNode(doc, parentNode, 'parents', parentId)
|
||||
|
||||
def appendBranchesNode(doc, parentNode, ctx):
|
||||
branch = ctx.branch()
|
||||
if branch != 'default':
|
||||
appendTextNode(doc, parentNode, 'branches', branch)
|
||||
|
||||
def appendModifications(doc, parentNode, ctx):
|
||||
status = repo.status(ctx.p1().node(), ctx.node())
|
||||
if status:
|
||||
modificationsNode = createChildNode(doc, parentNode, 'modifications')
|
||||
appendWrappedListNodes(doc, modificationsNode, 'added', 'file', status[1])
|
||||
appendWrappedListNodes(doc, modificationsNode, 'modified', 'file', status[0])
|
||||
appendWrappedListNodes(doc, modificationsNode, 'removed', 'file', status[2])
|
||||
|
||||
def appendChangesetNode(doc, parentNode, ctx):
|
||||
changesetNode = createChildNode(doc, parentNode, 'changeset')
|
||||
appendIdNode(doc, changesetNode, ctx)
|
||||
appendParentNodes(doc, changesetNode, ctx)
|
||||
appendTextNode(doc, changesetNode, 'description', ctx.description())
|
||||
appendDateNode(doc, changesetNode, 'date', ctx.date())
|
||||
appendAuthorNodes(doc, changesetNode, ctx)
|
||||
appendBranchesNode(doc, changesetNode, ctx)
|
||||
appendListNodes(doc, changesetNode, 'tags', ctx.tags())
|
||||
appendModifications(doc, changesetNode, ctx)
|
||||
|
||||
# changeset methods end
|
||||
|
||||
# change log methods
|
||||
|
||||
def createBasicNodes(doc, ctxs):
|
||||
rootNode = doc.createElement('changeset-paging')
|
||||
doc.appendChild(rootNode)
|
||||
total = str(len(ctxs))
|
||||
appendTextNode(doc, rootNode, 'total', total)
|
||||
return createChildNode(doc, rootNode, 'changesets')
|
||||
|
||||
def collectChangesets(repo, path, startNode, endNode):
|
||||
start = 'tip'
|
||||
end = '0'
|
||||
if len(startNode) > 0:
|
||||
start = startNode
|
||||
if len(endNode) > 0:
|
||||
end = endNode
|
||||
|
||||
ctxs = []
|
||||
startRev = repo[start].rev()
|
||||
endRev = repo[end].rev() - 1
|
||||
|
||||
onlyWithPath = len(path) > 0
|
||||
|
||||
for i in range(startRev, endRev, -1):
|
||||
ctx = repo[i]
|
||||
if onlyWithPath:
|
||||
if path in ctx.files():
|
||||
ctxs.append(ctx)
|
||||
else:
|
||||
ctxs.append(ctx)
|
||||
|
||||
return ctxs
|
||||
|
||||
def stripChangesets(ctxs, start, limit):
|
||||
if limit < 0:
|
||||
ctxs = ctxs[start:]
|
||||
else:
|
||||
limit = limit + start
|
||||
if limit > len(ctxs):
|
||||
ctxs = ctxs[start:]
|
||||
else:
|
||||
ctxs = ctxs[start:limit]
|
||||
return ctxs
|
||||
|
||||
# change log methods
|
||||
|
||||
# main method
|
||||
repo = openRepository()
|
||||
doc = Document()
|
||||
|
||||
# parameter
|
||||
path = os.environ['SCM_PATH']
|
||||
startNode = os.environ['SCM_REVISION_START']
|
||||
endNode = os.environ['SCM_REVISION_END']
|
||||
rev = os.environ['SCM_REVISION']
|
||||
# paging parameter
|
||||
start = int(os.environ['SCM_PAGE_START'])
|
||||
limit = int(os.environ['SCM_PAGE_LIMIT'])
|
||||
|
||||
if len(rev) > 0:
|
||||
ctx = repo[rev]
|
||||
appendChangesetNode(doc, doc, ctx)
|
||||
else:
|
||||
ctxs = collectChangesets(repo, path, startNode, endNode)
|
||||
changesetsNode = createBasicNodes(doc, ctxs)
|
||||
ctxs = stripChangesets(ctxs, start, limit)
|
||||
for ctx in ctxs:
|
||||
appendChangesetNode(doc, changesetsNode, ctx)
|
||||
|
||||
|
||||
# write document
|
||||
writeXml(doc)
|
||||
@@ -29,8 +29,45 @@
|
||||
# changegroup.scm = python:scmhooks.callback
|
||||
#
|
||||
|
||||
import os, urllib, urllib2
|
||||
import os, sys
|
||||
|
||||
client = None
|
||||
|
||||
# compatibility layer between python 2 and 3 urllib implementations
|
||||
if sys.version_info[0] < 3:
|
||||
import urllib, urllib2
|
||||
# create type alias for url error
|
||||
URLError = urllib2.URLError
|
||||
|
||||
class Python2Client:
|
||||
def post(self, url, values):
|
||||
data = urllib.urlencode(values)
|
||||
# open url but ignore proxy settings
|
||||
proxy_handler = urllib2.ProxyHandler({})
|
||||
opener = urllib2.build_opener(proxy_handler)
|
||||
req = urllib2.Request(url, data)
|
||||
req.add_header("X-XSRF-Token", xsrf)
|
||||
return opener.open(req)
|
||||
|
||||
client = Python2Client()
|
||||
else:
|
||||
import urllib.parse, urllib.request, urllib.error
|
||||
# create type alias for url error
|
||||
URLError = urllib.error.URLError
|
||||
|
||||
class Python3Client:
|
||||
def post(self, url, values):
|
||||
data = urllib.parse.urlencode(values)
|
||||
# open url but ignore proxy settings
|
||||
proxy_handler = urllib.request.ProxyHandler({})
|
||||
opener = urllib.request.build_opener(proxy_handler)
|
||||
req = urllib.request.Request(url, data.encode())
|
||||
req.add_header("X-XSRF-Token", xsrf)
|
||||
return opener.open(req)
|
||||
|
||||
client = Python3Client()
|
||||
|
||||
# read environment
|
||||
baseUrl = os.environ['SCM_URL']
|
||||
challenge = os.environ['SCM_CHALLENGE']
|
||||
token = os.environ['SCM_BEARER_TOKEN']
|
||||
@@ -38,45 +75,43 @@ xsrf = os.environ['SCM_XSRF']
|
||||
repositoryId = os.environ['SCM_REPOSITORY_ID']
|
||||
|
||||
def printMessages(ui, msgs):
|
||||
for line in msgs:
|
||||
if line.startswith("_e") or line.startswith("_n"):
|
||||
line = line[2:];
|
||||
ui.warn('%s\n' % line.rstrip())
|
||||
for raw in msgs:
|
||||
line = raw
|
||||
if hasattr(line, "encode"):
|
||||
line = line.encode()
|
||||
if line.startswith(b"_e") or line.startswith(b"_n"):
|
||||
line = line[2:]
|
||||
ui.warn(b'%s\n' % line.rstrip())
|
||||
|
||||
def callHookUrl(ui, repo, hooktype, node):
|
||||
abort = True
|
||||
try:
|
||||
url = baseUrl + hooktype
|
||||
ui.debug( "send scm-hook to " + url + " and " + node + "\n" )
|
||||
data = urllib.urlencode({'node': node, 'challenge': challenge, 'token': token, 'repositoryPath': repo.root, 'repositoryId': repositoryId})
|
||||
# open url but ignore proxy settings
|
||||
proxy_handler = urllib2.ProxyHandler({})
|
||||
opener = urllib2.build_opener(proxy_handler)
|
||||
req = urllib2.Request(url, data)
|
||||
req.add_header("X-XSRF-Token", xsrf)
|
||||
conn = opener.open(req)
|
||||
url = baseUrl + hooktype.decode("utf-8")
|
||||
ui.debug( b"send scm-hook to " + url.encode() + b" and " + node + b"\n" )
|
||||
values = {'node': node.decode("utf-8"), 'challenge': challenge, 'token': token, 'repositoryPath': repo.root, 'repositoryId': repositoryId}
|
||||
conn = client.post(url, values)
|
||||
if 200 <= conn.code < 300:
|
||||
ui.debug( "scm-hook " + hooktype + " success with status code " + str(conn.code) + "\n" )
|
||||
ui.debug( b"scm-hook " + hooktype + b" success with status code " + str(conn.code).encode() + b"\n" )
|
||||
printMessages(ui, conn)
|
||||
abort = False
|
||||
else:
|
||||
ui.warn( "ERROR: scm-hook failed with error code " + str(conn.code) + "\n" )
|
||||
except urllib2.URLError, e:
|
||||
ui.warn( b"ERROR: scm-hook failed with error code " + str(conn.code).encode() + b"\n" )
|
||||
except URLError as e:
|
||||
msg = None
|
||||
# some URLErrors have no read method
|
||||
if hasattr(e, "read"):
|
||||
msg = e.read()
|
||||
elif hasattr(e, "code"):
|
||||
msg = "scm-hook failed with error code " + str(e.code) + "\n"
|
||||
msg = "scm-hook failed with error code " + e.code + "\n"
|
||||
else:
|
||||
msg = str(e)
|
||||
if len(msg) > 0:
|
||||
printMessages(ui, msg.splitlines(True))
|
||||
else:
|
||||
ui.warn( "ERROR: scm-hook failed with an unknown error\n" )
|
||||
ui.warn( b"ERROR: scm-hook failed with an unknown error\n" )
|
||||
ui.traceback()
|
||||
except ValueError:
|
||||
ui.warn( "scm-hook failed with an exception\n" )
|
||||
ui.warn( b"scm-hook failed with an exception\n" )
|
||||
ui.traceback()
|
||||
return abort
|
||||
|
||||
@@ -86,10 +121,10 @@ def callback(ui, repo, hooktype, node=None):
|
||||
if len(baseUrl) > 0:
|
||||
abort = callHookUrl(ui, repo, hooktype, node)
|
||||
else:
|
||||
ui.warn("ERROR: scm-manager hooks are disabled, please check your configuration and the scm-manager log for details\n")
|
||||
ui.warn(b"ERROR: scm-manager hooks are disabled, please check your configuration and the scm-manager log for details\n")
|
||||
abort = False
|
||||
else:
|
||||
ui.warn("changeset node is not available")
|
||||
ui.warn(b"changeset node is not available")
|
||||
return abort
|
||||
|
||||
def preHook(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs):
|
||||
@@ -106,13 +141,12 @@ def preHook(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs):
|
||||
tr = repo.currenttransaction()
|
||||
repo.dirstate.write(tr)
|
||||
if tr and not tr.writepending():
|
||||
ui.warn("no pending write transaction found")
|
||||
ui.warn(b"no pending write transaction found")
|
||||
except AttributeError:
|
||||
ui.debug("mercurial does not support currenttransation")
|
||||
ui.debug(b"mercurial does not support currenttransation")
|
||||
# do nothing
|
||||
|
||||
return callback(ui, repo, hooktype, node)
|
||||
|
||||
def postHook(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs):
|
||||
return callback(ui, repo, hooktype, node)
|
||||
|
||||
|
||||
@@ -1,87 +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.
|
||||
#
|
||||
|
||||
# import basic modules
|
||||
import sys, os
|
||||
|
||||
# import mercurial modules
|
||||
from mercurial import hg, ui, commands, encoding
|
||||
from mercurial.node import hex
|
||||
from xml.dom.minidom import Document
|
||||
|
||||
# util methods
|
||||
def openRepository():
|
||||
repositoryPath = os.environ['SCM_REPOSITORY_PATH']
|
||||
return hg.repository(ui.ui(), path = repositoryPath)
|
||||
|
||||
def writeXml(doc):
|
||||
# print doc.toprettyxml(indent=" ")
|
||||
doc.writexml(sys.stdout, encoding='UTF-8')
|
||||
|
||||
def createChildNode(doc, parentNode, name):
|
||||
node = doc.createElement(name)
|
||||
parentNode.appendChild(node)
|
||||
return node
|
||||
|
||||
def appendValue(doc, node, value):
|
||||
textNode = doc.createTextNode(encoding.tolocal(value))
|
||||
node.appendChild(textNode)
|
||||
|
||||
def appendTextNode(doc, parentNode, name, value):
|
||||
node = createChildNode(doc, parentNode, name)
|
||||
appendValue(doc, node, value)
|
||||
|
||||
def appendDateNode(doc, parentNode, nodeName, date):
|
||||
time = int(date[0]) * 1000
|
||||
date = str(time).split('.')[0]
|
||||
appendTextNode(doc, parentNode, nodeName, date)
|
||||
|
||||
def appendListNodes(doc, parentNode, name, values):
|
||||
if values:
|
||||
for value in values:
|
||||
appendTextNode(doc, parentNode, name, value)
|
||||
|
||||
def appendWrappedListNodes(doc, parentNode, wrapperName, name, values):
|
||||
if values:
|
||||
wrapperNode = createChildNode(doc, parentNode, wrapperName)
|
||||
appendListNodes(doc, wrapperNode, name, values)
|
||||
|
||||
def getId(ctx):
|
||||
id = ''
|
||||
if os.environ['SCM_ID_REVISION'] == 'true':
|
||||
id = str(ctx.rev()) + ':'
|
||||
return id + hex(ctx.node())
|
||||
|
||||
def appendAuthorNodes(doc, parentNode, ctx):
|
||||
authorName = ctx.user()
|
||||
authorMail = None
|
||||
if authorName:
|
||||
authorNode = createChildNode(doc, parentNode, 'author')
|
||||
s = authorName.find('<')
|
||||
e = authorName.find('>')
|
||||
if s > 0 and e > 0:
|
||||
authorMail = authorName[s + 1:e].strip()
|
||||
authorName = authorName[0:s].strip()
|
||||
appendTextNode(doc, authorNode, 'mail', authorMail)
|
||||
appendTextNode(doc, authorNode, 'name', authorName)
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.autoconfig;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PosixAutoConfiguratorTest {
|
||||
|
||||
@Test
|
||||
void shouldConfigureWithShebangPath(@TempDir Path directory) throws IOException {
|
||||
Path hg = directory.resolve("hg");
|
||||
Path python = directory.resolve("python");
|
||||
|
||||
Files.write(hg, ("#!" + python.toAbsolutePath().toString()).getBytes(StandardCharsets.UTF_8));
|
||||
Files.createFile(python);
|
||||
|
||||
PosixAutoConfigurator configurator = create(directory);
|
||||
HgConfig config = configurator.configure();
|
||||
|
||||
assertThat(config.getHgBinary()).isEqualTo(hg.toString());
|
||||
assertThat(config.getPythonBinary()).isEqualTo(python.toString());
|
||||
}
|
||||
|
||||
private PosixAutoConfigurator create(@TempDir Path directory) {
|
||||
return new PosixAutoConfigurator(createEnv(directory), Collections.emptyList());
|
||||
}
|
||||
|
||||
private Map<String, String> createEnv(Path... paths) {
|
||||
return ImmutableMap.of("PATH", Joiner.on(File.pathSeparator).join(paths));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConfigureWithShebangEnv(@TempDir Path directory) throws IOException {
|
||||
Path hg = directory.resolve("hg");
|
||||
Path python = directory.resolve("python3.8");
|
||||
|
||||
Files.write(hg, "#!/usr/bin/env python3.8".getBytes(StandardCharsets.UTF_8));
|
||||
Files.createFile(python);
|
||||
|
||||
PosixAutoConfigurator configurator = create(directory);
|
||||
HgConfig config = configurator.configure();
|
||||
|
||||
assertThat(config.getHgBinary()).isEqualTo(hg.toString());
|
||||
assertThat(config.getPythonBinary()).isEqualTo(python.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConfigureWithoutShebang(@TempDir Path directory) throws IOException {
|
||||
Path hg = directory.resolve("hg");
|
||||
Path python = directory.resolve("python");
|
||||
|
||||
Files.createFile(hg);
|
||||
Files.createFile(python);
|
||||
|
||||
PosixAutoConfigurator configurator = create(directory);
|
||||
HgConfig config = configurator.configure();
|
||||
|
||||
assertThat(config.getHgBinary()).isEqualTo(hg.toString());
|
||||
assertThat(config.getPythonBinary()).isEqualTo(python.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConfigureWithoutShebangButWithPython3(@TempDir Path directory) throws IOException {
|
||||
Path hg = directory.resolve("hg");
|
||||
Path python = directory.resolve("python3");
|
||||
|
||||
Files.createFile(hg);
|
||||
Files.createFile(python);
|
||||
|
||||
PosixAutoConfigurator configurator = create(directory);
|
||||
HgConfig config = configurator.configure();
|
||||
|
||||
assertThat(config.getHgBinary()).isEqualTo(hg.toString());
|
||||
assertThat(config.getPythonBinary()).isEqualTo(python.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConfigureFindPythonInAdditionalPath(@TempDir Path directory) throws IOException {
|
||||
Path def = directory.resolve("default");
|
||||
Files.createDirectory(def);
|
||||
Path additional = directory.resolve("additional");
|
||||
Files.createDirectory(additional);
|
||||
|
||||
Path hg = def.resolve("hg");
|
||||
Path python = additional.resolve("python");
|
||||
|
||||
Files.createFile(hg);
|
||||
Files.createFile(python);
|
||||
|
||||
PosixAutoConfigurator configurator = new PosixAutoConfigurator(
|
||||
createEnv(def), ImmutableList.of(additional.toAbsolutePath().toString())
|
||||
);
|
||||
|
||||
HgConfig config = configurator.configure();
|
||||
assertThat(config.getHgBinary()).isEqualTo(hg.toString());
|
||||
assertThat(config.getPythonBinary()).isEqualTo(python.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindModulePathFromDebuginstallOutput(@TempDir Path directory) throws IOException {
|
||||
Path hg = directory.resolve("hg");
|
||||
Files.createFile(hg);
|
||||
hg.toFile().setExecutable(true);
|
||||
|
||||
Path modules = directory.resolve("modules");
|
||||
Files.createDirectories(modules);
|
||||
|
||||
Path mercurialModule = modules.resolve("mercurial");
|
||||
Files.createDirectories(mercurialModule);
|
||||
|
||||
PosixAutoConfigurator configurator = create(directory);
|
||||
configurator.setExecutor((Path binary, String... args) -> {
|
||||
String content = String.join("\n",
|
||||
"checking Python executable (/python3.8)",
|
||||
"checking Python lib (/python3.8)...",
|
||||
"checking installed modules (" + mercurialModule.toString() + ")...",
|
||||
"checking templates (/mercurial/templates)...",
|
||||
"checking default template (/mercurial/templates/map-cmdline.default))"
|
||||
);
|
||||
return new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
|
||||
});
|
||||
HgConfig config = configurator.configure();
|
||||
|
||||
assertThat(config.getHgBinary()).isEqualTo(hg.toString());
|
||||
assertThat(config.getPythonPath()).isEqualTo(modules.toString());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user