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.

Reply via email to