Bug 1393242 - Hook up hglib to HgRepository; r?mshal draft
authorGregory Szorc <gps@mozilla.com>
Wed, 23 Aug 2017 15:09:27 -0700
changeset 657535 78e75f2d7e7ea6c7d18099d3654030d901783124
parent 657534 9f27c0c0f7a9a75d18fbe905145960d929321806
child 657536 be93678e693d1fa9f36433fac50ba8d5ad9b55bd
push id77552
push usergszorc@mozilla.com
push dateFri, 01 Sep 2017 16:20:16 +0000
reviewersmshal
bugs1393242
milestone57.0a1
Bug 1393242 - Hook up hglib to HgRepository; r?mshal Because hglib spawns a persistent process, we introduce a context manager for Repository. It no-ops by default. On HgRepository it controls the lifetime of the persistent hg process. A helper method for running an hg command via hglib has been added. We can't transition existing methods to hglib because hglib requires a context manager, which no consumer is using yet. MozReview-Commit-ID: 8z0fcGFeAm5
python/mozversioncontrol/mozversioncontrol/__init__.py
--- a/python/mozversioncontrol/mozversioncontrol/__init__.py
+++ b/python/mozversioncontrol/mozversioncontrol/__init__.py
@@ -37,25 +37,39 @@ def get_tool_path(tool):
             pass
 
     raise MissingVCSTool('Unable to obtain %s path. Try running '
                          '|mach bootstrap| to ensure your environment is up to '
                          'date.' % tool)
 
 
 class Repository(object):
+    """A class wrapping utility methods around version control repositories.
+
+    This class is abstract and never instantiated. Obtain an instance by
+    calling a ``get_repository_*()`` helper function.
+
+    Clients are recommended to use the object as a context manager. But not
+    all methods require this.
+    """
+
     __metaclass__ = abc.ABCMeta
 
-    '''A class wrapping utility methods around version control repositories.'''
     def __init__(self, path, tool):
         self.path = os.path.abspath(path)
         self._tool = get_tool_path(tool)
         self._env = os.environ.copy()
         self._version = None
 
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_value, exc_tb):
+        pass
+
     def _run(self, *args):
         return subprocess.check_output((self._tool, ) + args,
                                        cwd=self.path,
                                        env=self._env)
 
     @property
     def tool_version(self):
         '''Return the version of the VCS tool in use as a `LooseVersion`.'''
@@ -106,23 +120,58 @@ class Repository(object):
     @abc.abstractmethod
     def get_files_in_working_directory(self):
         """Obtain a list of managed files in the working directory."""
 
 
 class HgRepository(Repository):
     '''An implementation of `Repository` for Mercurial repositories.'''
     def __init__(self, path, hg='hg'):
+        import hglib.client
+
         super(HgRepository, self).__init__(path, tool=hg)
         self._env[b'HGPLAIN'] = b'1'
 
+        # Setting this modifies a global variable and makes all future hglib
+        # instances use this binary. Since the tool path was validated, this
+        # should be OK. But ideally hglib would offer an API that defines
+        # per-instance binaries.
+        hglib.HGPATH = self._tool
+
+        # Without connect=False this spawns a persistent process. We want
+        # the process lifetime tied to a context manager.
+        self._client = hglib.client.hgclient(self.path, encoding=b'UTF-8',
+                                             configs=None, connect=False)
+
     @property
     def name(self):
         return 'hg'
 
+    def __enter__(self):
+        if self._client.server is None:
+            # The cwd if the spawned process should be the repo root to ensure
+            # relative paths are normalized to it.
+            old_cwd = os.getcwd()
+            try:
+                os.chdir(self.path)
+                self._client.open()
+            finally:
+                os.chdir(old_cwd)
+
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self._client.close()
+
+    def _run_in_client(self, args):
+        if not self._client.server:
+            raise Exception('active HgRepository context manager required')
+
+        return self._client.rawcommand(args)
+
     def sparse_checkout_present(self):
         # We assume a sparse checkout is enabled if the .hg/sparse file
         # has data. Strictly speaking, we should look for a requirement in
         # .hg/requires. But since the requirement is still experimental
         # as of Mercurial 4.3, it's probably more trouble than its worth
         # to verify it.
         sparse = os.path.join(self.path, '.hg', 'sparse')