aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/portage/const.py1
-rw-r--r--lib/portage/sync/modules/mercurial/__init__.py39
-rw-r--r--lib/portage/sync/modules/mercurial/mercurial.py174
-rw-r--r--lib/portage/tests/sync/test_sync_local.py67
-rw-r--r--man/portage.524
5 files changed, 303 insertions, 2 deletions
diff --git a/lib/portage/const.py b/lib/portage/const.py
index b895f0fa9..7effcd85d 100644
--- a/lib/portage/const.py
+++ b/lib/portage/const.py
@@ -82,6 +82,7 @@ LIBC_PACKAGE_ATOM = "virtual/libc"
OS_HEADERS_PACKAGE_ATOM = "virtual/os-headers"
CVS_PACKAGE_ATOM = "dev-vcs/cvs"
GIT_PACKAGE_ATOM = "dev-vcs/git"
+HG_PACKAGE_ATOM = "dev-vcs/mercurial"
RSYNC_PACKAGE_ATOM = "net-misc/rsync"
INCREMENTALS = (
diff --git a/lib/portage/sync/modules/mercurial/__init__.py b/lib/portage/sync/modules/mercurial/__init__.py
new file mode 100644
index 000000000..c4efbdcf4
--- /dev/null
+++ b/lib/portage/sync/modules/mercurial/__init__.py
@@ -0,0 +1,39 @@
+# Copyright 2018-2020 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+doc = """Mercurial plug-in module for portage.
+Performs a hg pull on repositories."""
+__doc__ = doc[:]
+
+from portage.localization import _
+from portage.sync.config_checks import CheckSyncConfig
+from portage.util import writemsg_level
+
+module_spec = {
+ "name": "mercurial",
+ "description": doc,
+ "provides": {
+ "mercurial-module": {
+ "name": "mercurial",
+ "sourcefile": "mercurial",
+ "class": "MercurialSync",
+ "description": doc,
+ "functions": ["sync", "new", "exists", "retrieve_head"],
+ "func_desc": {
+ "sync": "Performs a hg pull on the repository",
+ "new": "Creates the new repository at the specified location",
+ "exists": "Returns a boolean of whether the specified dir "
+ + "exists and is a valid Mercurial repository",
+ "retrieve_head": "Returns the head commit hash",
+ },
+ "validate_config": CheckSyncConfig,
+ "module_specific_options": (
+ "sync-mercurial-clone-env",
+ "sync-mercurial-clone-extra-opts",
+ "sync-mercurial-env",
+ "sync-mercurial-pull-env",
+ "sync-mercurial-pull-extra-opts",
+ ),
+ }
+ },
+}
diff --git a/lib/portage/sync/modules/mercurial/mercurial.py b/lib/portage/sync/modules/mercurial/mercurial.py
new file mode 100644
index 000000000..2cc4cd1ea
--- /dev/null
+++ b/lib/portage/sync/modules/mercurial/mercurial.py
@@ -0,0 +1,174 @@
+# Copyright 2018-2020 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+import logging
+import subprocess
+
+import portage
+from portage import os
+from portage.util import writemsg_level, shlex_split
+
+from portage.sync.syncbase import NewBase
+
+
+class MercurialSync(NewBase):
+ """Mercurial sync class"""
+
+ short_desc = "Perform sync operations on mercurial based repositories"
+
+ @staticmethod
+ def name():
+ return "MercurialSync"
+
+ def __init__(self):
+ NewBase.__init__(self, "hg", portage.const.HG_PACKAGE_ATOM)
+
+ def exists(self, **kwargs):
+ """Tests whether the repo actually exists"""
+ return os.path.exists(os.path.join(self.repo.location, ".hg"))
+
+ def new(self, **kwargs):
+ """Do the initial clone of the repository"""
+ if kwargs:
+ self._kwargs(kwargs)
+ try:
+ if not os.path.exists(self.repo.location):
+ os.makedirs(self.repo.location)
+ self.logger(
+ self.xterm_titles, "Created new directory %s" % self.repo.location
+ )
+ except IOError:
+ return (1, False)
+
+ sync_uri = self.repo.sync_uri
+ if sync_uri.startswith("file://"):
+ sync_uri = sync_uri[7:]
+
+ hg_cmd_opts = ""
+ if self.repo.module_specific_options.get("sync-mercurial-env"):
+ shlexed_env = shlex_split(
+ self.repo.module_specific_options["sync-mercurial-env"]
+ )
+ env = dict(
+ (k, v)
+ for k, _, v in (assignment.partition("=") for assignment in shlexed_env)
+ if k
+ )
+ self.spawn_kwargs["env"].update(env)
+
+ if self.repo.module_specific_options.get("sync-mercurial-clone-env"):
+ shlexed_env = shlex_split(
+ self.repo.module_specific_options["sync-mercurial-clone-env"]
+ )
+ clone_env = dict(
+ (k, v)
+ for k, _, v in (assignment.partition("=") for assignment in shlexed_env)
+ if k
+ )
+ self.spawn_kwargs["env"].update(clone_env)
+
+ if self.settings.get("PORTAGE_QUIET") == "1":
+ hg_cmd_opts += " --quiet"
+ if self.repo.module_specific_options.get("sync-mercurial-clone-extra-opts"):
+ hg_cmd_opts += (
+ " %s"
+ % self.repo.module_specific_options["sync-mercurial-clone-extra-opts"]
+ )
+ hg_cmd = "%s clone%s %s ." % (
+ self.bin_command,
+ hg_cmd_opts,
+ portage._shell_quote(sync_uri),
+ )
+ writemsg_level(hg_cmd + "\n")
+
+ exitcode = portage.process.spawn(
+ shlex_split(hg_cmd),
+ cwd=portage._unicode_encode(self.repo.location),
+ **self.spawn_kwargs
+ )
+ if exitcode != os.EX_OK:
+ msg = "!!! hg clone error in %s" % self.repo.location
+ self.logger(self.xterm_titles, msg)
+ writemsg_level(msg + "\n", level=logging.ERROR, noiselevel=-1)
+ return (exitcode, False)
+ return (os.EX_OK, True)
+
+ def update(self):
+ """Update existing mercurial repository, and ignore the syncuri. We are
+ going to trust the user and assume that the user is in the branch
+ that he/she wants updated. We'll let the user manage branches with
+ hg directly.
+ """
+
+ hg_cmd_opts = ""
+ if self.repo.module_specific_options.get("sync-mercurial-env"):
+ shlexed_env = shlex_split(
+ self.repo.module_specific_options["sync-mercurial-env"]
+ )
+ env = dict(
+ (k, v)
+ for k, _, v in (assignment.partition("=") for assignment in shlexed_env)
+ if k
+ )
+ self.spawn_kwargs["env"].update(env)
+
+ if self.repo.module_specific_options.get("sync-mercurial-pull-env"):
+ shlexed_env = shlex_split(
+ self.repo.module_specific_options["sync-mercurial-pull-env"]
+ )
+ pull_env = dict(
+ (k, v)
+ for k, _, v in (assignment.partition("=") for assignment in shlexed_env)
+ if k
+ )
+ self.spawn_kwargs["env"].update(pull_env)
+
+ if self.settings.get("PORTAGE_QUIET") == "1":
+ hg_cmd_opts += " --quiet"
+ if self.repo.module_specific_options.get("sync-mercurial-pull-extra-opts"):
+ hg_cmd_opts += (
+ " %s"
+ % self.repo.module_specific_options["sync-mercurial-pull-extra-opts"]
+ )
+ hg_cmd = "%s pull -u%s" % (self.bin_command, hg_cmd_opts)
+ writemsg_level(hg_cmd + "\n")
+
+ rev_cmd = [self.bin_command, "id", "--id", "--rev", "tip"]
+ previous_rev = subprocess.check_output(
+ rev_cmd, cwd=portage._unicode_encode(self.repo.location)
+ )
+
+ exitcode = portage.process.spawn(
+ shlex_split(hg_cmd),
+ cwd=portage._unicode_encode(self.repo.location),
+ **self.spawn_kwargs
+ )
+ if exitcode != os.EX_OK:
+ msg = "!!! hg pull error in %s" % self.repo.location
+ self.logger(self.xterm_titles, msg)
+ writemsg_level(msg + "\n", level=logging.ERROR, noiselevel=-1)
+ return (exitcode, False)
+
+ current_rev = subprocess.check_output(
+ rev_cmd, cwd=portage._unicode_encode(self.repo.location)
+ )
+
+ return (os.EX_OK, current_rev != previous_rev)
+
+ def retrieve_head(self, **kwargs):
+ """Get information about the head commit"""
+ if kwargs:
+ self._kwargs(kwargs)
+ rev_cmd = [self.bin_command, "id", "--id", "--rev", "tip"]
+ try:
+ ret = (
+ os.EX_OK,
+ portage._unicode_decode(
+ subprocess.check_output(
+ rev_cmd, cwd=portage._unicode_encode(self.repo.location)
+ )
+ ),
+ )
+ except subprocess.CalledProcessError:
+ ret = (1, False)
+ return ret
diff --git a/lib/portage/tests/sync/test_sync_local.py b/lib/portage/tests/sync/test_sync_local.py
index efd61aac8..21c03a98b 100644
--- a/lib/portage/tests/sync/test_sync_local.py
+++ b/lib/portage/tests/sync/test_sync_local.py
@@ -88,6 +88,9 @@ class SyncLocalTestCase(TestCase):
git_binary = find_binary("git")
git_cmd = (git_binary,)
+ hg_binary = find_binary("hg")
+ hg_cmd = (hg_binary,)
+
committer_name = "Gentoo Dev"
committer_email = "gentoo-dev@gentoo.org"
@@ -242,6 +245,68 @@ class SyncLocalTestCase(TestCase):
(homedir, lambda: repos_set_conf("rsync", sync_rcu=True)),
)
+ delete_git_dir = (
+ (homedir, lambda: shutil.rmtree(
+ os.path.join(repo.location, ".git"))),
+ )
+
+ def hg_init_global_config():
+ with open(os.path.join(homedir, ".hgrc"), "wt") as f:
+ f.write(
+ "[ui]\nusername = {} <{}>\n".format(committer_name, committer_email)
+ )
+
+ hg_repo_create = (
+ (repo.location, hg_init_global_config),
+ (repo.location, hg_cmd + ("init",)),
+ (repo.location, hg_cmd + ("add", ".")),
+ (repo.location, hg_cmd + ("commit", "-A", "-m", "add whole repo")),
+ )
+
+ sync_type_mercurial = ((homedir, lambda: repos_set_conf("mercurial")),)
+
+ def append_newline(path):
+ with open(path, "at") as f:
+ f.write("\n")
+
+ upstream_hg_commit = (
+ (
+ repo.location + "_sync",
+ lambda: append_newline(
+ os.path.join(repo.location + "_sync", "metadata/layout.conf")
+ ),
+ ),
+ (
+ repo.location + "_sync",
+ hg_cmd + ("commit", "metadata/layout.conf", "-m", "test empty commit"),
+ ),
+ (
+ repo.location + "_sync",
+ lambda: append_newline(
+ os.path.join(repo.location + "_sync", "metadata/layout.conf")
+ ),
+ ),
+ (
+ repo.location + "_sync",
+ hg_cmd
+ + ("commit", "metadata/layout.conf", "-m", "test empty commit 2"),
+ ),
+ )
+
+ if hg_binary is None:
+ mercurial_tests = ()
+ else:
+ mercurial_tests = (
+ delete_sync_repo
+ + delete_git_dir
+ + hg_repo_create
+ + sync_type_mercurial
+ + rename_repo
+ + sync_cmds
+ + upstream_hg_commit
+ + sync_cmds
+ )
+
pythonpath = os.environ.get("PYTHONPATH")
if pythonpath is not None and not pythonpath.strip():
pythonpath = None
@@ -300,7 +365,7 @@ class SyncLocalTestCase(TestCase):
bump_timestamp_cmds + sync_cmds + revert_rcu_layout + \
delete_sync_repo + git_repo_create + sync_type_git + \
rename_repo + sync_cmds + upstream_git_commit + sync_cmds + \
- sync_type_git_shallow + upstream_git_commit + sync_cmds:
+ sync_type_git_shallow + upstream_git_commit + sync_cmds + mercurial_tests:
if hasattr(cmd, '__call__'):
cmd()
diff --git a/man/portage.5 b/man/portage.5
index 82dd8a509..f6ec1b0fa 100644
--- a/man/portage.5
+++ b/man/portage.5
@@ -1075,6 +1075,27 @@ hooks unless hooks would have executed for a master repository or the
repository has changed since the previous sync operation. Defaults to
no, false.
.TP
+.B sync\-mercurial\-clone\-env
+Extra options to give to mercurial when cloning repository (hg clone).
+See also example for sync\-git\-clone\-env.
+.TP
+.B sync\-mercurial\-clone\-extra\-opts
+Extra options to give to mercurial when cloning repository (hg clone).
+.TP
+.B sync\-mercurial\-env
+Set environment variables for mercurial when cloning or pulling the repository.
+These will be overridden by setting them again in sync\-mercurial\-clone\-env and
+sync\-mercurial\-pull\-env.
+See also example for sync\-git\-clone\-env.
+.TP
+.B sync\-mercurial\-pull\-env
+Set environment variables for mercurial when updating repository (hg pull).
+This will override settings from sync\-mercurial\-env.
+See also example for sync\-git\-clone\-env.
+.TP
+.B sync\-mercurial\-pull\-extra\-opts
+Extra options to give to mercurial when updating repository (hg pull).
+.TP
.B sync\-rcu = yes|no|true|false
Enable read\-copy\-update (RCU) behavior for sync operations. The current
latest immutable version of a repository will be referenced by a symlink
@@ -1113,7 +1134,8 @@ expire while it is in use by a running process.
.B sync\-type
Specifies type of synchronization performed by `emerge \-\-sync`.
.br
-Valid non\-empty values: cvs, git, rsync, svn, webrsync (emerge-webrsync)
+Valid non\-empty values: cvs, git, mercurial, rsync, svn, webrsync
+(emerge\-webrsync)
.br
This attribute can be set to empty value to disable synchronization of given
repository. Empty value is default.