Bug 1401309 - [mozversioncontrol] Add an option to make failed subprocess commands non-fatal, r?gps draft
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Wed, 20 Sep 2017 10:16:15 -0400
changeset 669109 33f1a940eb34ebd3effda623910508c55852f217
parent 669108 c46b110e05d955373ea2c39835a7e9ef9ec66148
child 669110 6e1172bc5fb224d54004b6c4688f3638947c89f9
push id81212
push userahalberstadt@mozilla.com
push dateFri, 22 Sep 2017 14:15:46 +0000
reviewersgps
bugs1401309
milestone58.0a1
Bug 1401309 - [mozversioncontrol] Add an option to make failed subprocess commands non-fatal, r?gps Sometimes commands return non-zero even though everything is ok. For example, 'hg outgoing' returns 1 if there are no outgoing files. This adds a way for consumers to tell mozversioncontrol not to abort if something goes wrong. Instead, stderr will be printed (if any) and an empty string is returned. MozReview-Commit-ID: 5pXjJLdzWeG
python/mozversioncontrol/mozversioncontrol/__init__.py
--- a/python/mozversioncontrol/mozversioncontrol/__init__.py
+++ b/python/mozversioncontrol/mozversioncontrol/__init__.py
@@ -64,33 +64,43 @@ class Repository(object):
     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
 
-    def __init__(self, path, tool):
+    def __init__(self, path, tool, fatal=True):
         self.path = os.path.abspath(path)
         self._tool = get_tool_path(tool)
+        self.fatal = fatal
         self._env = os.environ.copy()
         self._version = None
         self._valid_diff_filter = ('m', 'a', 'd')
 
     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)
+        cmd = (self._tool,) + args
+        try:
+            return subprocess.check_output(cmd,
+                                           cwd=self.path,
+                                           env=self._env)
+        except subprocess.CalledProcessError as e:
+            if self.fatal:
+                raise
+            if e.output:
+                print(' '.join(cmd))
+                print(e.output)
+            return ''
 
     @property
     def tool_version(self):
         '''Return the version of the VCS tool in use as a `LooseVersion`.'''
         if self._version:
             return self._version
         info = self._run('--version').strip()
         match = re.search('version ([^\+\)]+)', info)
@@ -170,20 +180,20 @@ class Repository(object):
         By default, untracked and ignored files are not considered. If
         ``untracked`` or ``ignored`` are set, they influence the clean check
         to factor these file classes into consideration.
         """
 
 
 class HgRepository(Repository):
     '''An implementation of `Repository` for Mercurial repositories.'''
-    def __init__(self, path, hg='hg'):
+    def __init__(self, path, hg='hg', **kwargs):
         import hglib.client
 
-        super(HgRepository, self).__init__(path, tool=hg)
+        super(HgRepository, self).__init__(path, tool=hg, **kwargs)
         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
 
@@ -294,18 +304,18 @@ class HgRepository(Repository):
 
         # If output is empty, there are no entries of requested status, which
         # means we are clean.
         return not len(self._run_in_client(args).strip())
 
 
 class GitRepository(Repository):
     '''An implementation of `Repository` for Git repositories.'''
-    def __init__(self, path, git='git'):
-        super(GitRepository, self).__init__(path, tool=git)
+    def __init__(self, path, git='git', **kwargs):
+        super(GitRepository, self).__init__(path, tool=git, **kwargs)
         self._upstream = None
 
     @property
     def upstream(self):
         if self._upstream:
             return self._upstream
 
         ref = self._run('symbolic-ref', '-q', 'HEAD').strip()
@@ -366,48 +376,48 @@ class GitRepository(Repository):
         if untracked:
             args.append('--untracked-files')
         if ignored:
             args.append('--ignored')
 
         return not len(self._run(*args).strip())
 
 
-def get_repository_object(path, hg='hg', git='git'):
+def get_repository_object(path, hg='hg', git='git', **kwargs):
     '''Get a repository object for the repository at `path`.
     If `path` is not a known VCS repository, raise an exception.
     '''
     if os.path.isdir(os.path.join(path, '.hg')):
-        return HgRepository(path, hg=hg)
+        return HgRepository(path, hg=hg, **kwargs)
     elif os.path.exists(os.path.join(path, '.git')):
-        return GitRepository(path, git=git)
+        return GitRepository(path, git=git, **kwargs)
     else:
         raise InvalidRepoPath('Unknown VCS, or not a source checkout: %s' %
                               path)
 
 
-def get_repository_from_build_config(config):
+def get_repository_from_build_config(config, **kwargs):
     """Obtain a repository from the build configuration.
 
     Accepts an object that has a ``topsrcdir`` and ``subst`` attribute.
     """
     flavor = config.substs.get('VCS_CHECKOUT_TYPE')
 
     # If in build mode, only use what configure found. That way we ensure
     # that everything in the build system can be controlled via configure.
     if not flavor:
         raise MissingConfigureInfo('could not find VCS_CHECKOUT_TYPE '
                                    'in build config; check configure '
                                    'output and verify it could find a '
                                    'VCS binary')
 
     if flavor == 'hg':
-        return HgRepository(config.topsrcdir, hg=config.substs['HG'])
+        return HgRepository(config.topsrcdir, hg=config.substs['HG'], **kwargs)
     elif flavor == 'git':
-        return GitRepository(config.topsrcdir, git=config.substs['GIT'])
+        return GitRepository(config.topsrcdir, git=config.substs['GIT'], **kwargs)
     else:
         raise MissingVCSInfo('unknown VCS_CHECKOUT_TYPE value: %s' % flavor)
 
 
 def get_repository_from_env():
     """Obtain a repository object by looking at the environment.
 
     If inside a build environment (denoted by presence of a ``buildconfig``