commit: 6a7d42b66a51a6438068c685c8df37d0872e8bf2 Author: Brian Evans <grknight <AT> gentoo <DOT> org> AuthorDate: Thu Jan 11 17:37:47 2018 +0000 Commit: Zac Medico <zmedico <AT> gentoo <DOT> org> CommitDate: Sun Sep 20 08:18:32 2020 +0000 URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=6a7d42b6
Add mercurial sync support Bug: https://bugs.gentoo.org/644246 Signed-off-by: Zac Medico <zmedico <AT> gentoo.org> lib/portage/const.py | 1 + lib/portage/sync/modules/mercurial/__init__.py | 39 ++++++ lib/portage/sync/modules/mercurial/mercurial.py | 174 ++++++++++++++++++++++++ lib/portage/tests/sync/test_sync_local.py | 67 ++++++++- man/portage.5 | 24 +++- 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-...@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.