Bug 1234913 - Support git in |mach artifact install|. r=nalexander draft
authorChris Manchester <cmanchester@mozilla.com>
Wed, 24 Feb 2016 23:20:42 -0800
changeset 335870 2012b41a8125361bc066f5d137aecf91eb34960f
parent 335867 4d839248cb0ba0e86d4e2370a255acb530115149
child 515235 695533ebc51d1864356e734e832f477115fbab32
push id11898
push usercmanchester@mozilla.com
push dateTue, 01 Mar 2016 19:20:10 +0000
reviewersnalexander
bugs1234913
milestone47.0a1
Bug 1234913 - Support git in |mach artifact install|. r=nalexander MozReview-Commit-ID: LL6kO8QS5p9
python/mozbuild/mozbuild/artifacts.py
python/mozbuild/mozbuild/mach_commands.py
--- a/python/mozbuild/mozbuild/artifacts.py
+++ b/python/mozbuild/mozbuild/artifacts.py
@@ -713,21 +713,28 @@ class ArtifactCache(CacheManager):
         self.log(logging.INFO, 'artifact',
             {'filename': result + PROCESSED_SUFFIX},
             'Last installed binaries from local processed file {filename}')
 
 
 class Artifacts(object):
     '''Maintain state to efficiently fetch build artifacts from a Firefox tree.'''
 
-    def __init__(self, tree, job=None, log=None, cache_dir='.', hg='hg', skip_cache=False):
+    def __init__(self, tree, job=None, log=None, cache_dir='.', hg=None, git=None, skip_cache=False):
+        if (hg and git) or (not hg and not git):
+            raise ValueError("Must provide path to exactly one of hg and git")
+
         self._tree = tree
         self._job = job or self._guess_artifact_job()
         self._log = log
         self._hg = hg
+        self._git = git
+        if self._git:
+            import which
+            self._cinnabar = which.which('git-cinnabar')
         self._cache_dir = cache_dir
         self._skip_cache = skip_cache
 
         try:
             self._artifact_job = get_job_details(self._job, log=self._log)
         except KeyError:
             self.log(logging.INFO, 'artifact',
                 {'job': self._job},
@@ -800,20 +807,47 @@ class Artifacts(object):
                          {'tree': tree,
                           'pushid': pushid,
                           'num': NUM_PUSHHEADS_TO_QUERY_PER_PARENT},
                          'Retrieving the last {num} pushheads starting with id {pushid} on {tree}')
                 candidate_pushheads.extend(pushhead_cache.pushid_range(tree, start, end))
 
         return candidate_pushheads
 
+    def _get_hg_revisions_from_git(self):
+
+        # First commit is HEAD, next is HEAD~1, etc.
+        rev_list = subprocess.check_output([
+            self._git, 'rev-list', '--topo-order',
+            'HEAD~{num}..HEAD'.format(num=NUM_REVISIONS_TO_QUERY),
+        ])
+
+        hg_hash_list = subprocess.check_output([
+            self._cinnabar, 'git2hg'
+        ] + rev_list.splitlines())
+
+        zeroes = "0" * 40
+
+        hashes = []
+        for hg_hash in hg_hash_list.splitlines():
+            hg_hash = hg_hash.strip()
+            if not hg_hash or hg_hash == zeroes:
+                continue
+            hashes.append(hg_hash)
+        return hashes
+
     def _get_recent_public_revisions(self):
         """Returns recent ancestors of the working parent that are likely to
         to be known to mozilla automation.
+
+        If we're using git, retrieves hg revisions from git-cinnabar.
         """
+        if self._git:
+            return self._get_hg_revisions_from_git()
+
         return subprocess.check_output([
             self._hg, 'log',
             '--template', '{node}\n',
             '-r', 'last(public() and ::., {num})'.format(
                 num=NUM_REVISIONS_TO_QUERY)
         ]).splitlines()
 
     def _find_pushheads(self):
@@ -940,25 +974,32 @@ class Artifacts(object):
                             return 1
                     return 0
 
         self.log(logging.ERROR, 'artifact',
                  {'count': count},
                  'Tried {count} pushheads, no built artifacts found.')
         return 1
 
-    def install_from_hg_recent(self, distdir):
+    def install_from_recent(self, distdir):
         hg_pushheads = self._find_pushheads()
         return self._install_from_hg_pushheads(hg_pushheads, distdir)
 
-    def install_from_hg_revset(self, revset, distdir):
-        revision = subprocess.check_output([self._hg, 'log', '--template', '{node}\n',
-                                            '-r', revset]).strip()
-        if len(revision.split('\n')) != 1:
-            raise ValueError('hg revision specification must resolve to exactly one commit')
+    def install_from_revset(self, revset, distdir):
+        if self._hg:
+            revision = subprocess.check_output([self._hg, 'log', '--template', '{node}\n',
+                                                '-r', revset]).strip()
+            if len(revision.split('\n')) != 1:
+                raise ValueError('hg revision specification must resolve to exactly one commit')
+        else:
+            revision = subprocess.check_output([self._git, 'rev-parse', revset]).strip()
+            revision = subprocess.check_output([self._cinnabar, 'git2hg', revision]).strip()
+            if len(revision.split('\n')) != 1:
+                raise ValueError('hg revision specification must resolve to exactly one commit')
+
         self.log(logging.INFO, 'artifact',
                  {'revset': revset,
                   'revision': revision},
                  'Will only accept artifacts from a pushhead at {revision} '
                  '(matched revset "{revset}").')
         return self._install_from_hg_pushheads([revision], distdir)
 
     def install_from(self, source, distdir):
@@ -968,19 +1009,19 @@ class Artifacts(object):
             return self.install_from_file(source, distdir)
         elif source and urlparse.urlparse(source).scheme:
             return self.install_from_url(source, distdir)
         else:
             if source is None and 'MOZ_ARTIFACT_REVISION' in os.environ:
                 source = os.environ['MOZ_ARTIFACT_REVISION']
 
             if source:
-                return self.install_from_hg_revset(source, distdir)
+                return self.install_from_revset(source, distdir)
 
-            return self.install_from_hg_recent(distdir)
+            return self.install_from_recent(distdir)
 
 
     def print_last(self):
         self.log(logging.INFO, 'artifact',
             {},
             'Printing last used artifact details.')
         self._tree_cache.print_last()
         self._task_cache.print_last()
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -1454,20 +1454,17 @@ class ArtifactSubCommand(SubCommand):
         return after
 
 
 @CommandProvider
 class PackageFrontend(MachCommandBase):
     """Fetch and install binary artifacts from Mozilla automation."""
 
     @Command('artifact', category='post-build',
-        description='Use pre-built artifacts to build Firefox.',
-        conditions=[
-            conditions.is_hg,  # mercurial only for now.
-        ])
+        description='Use pre-built artifacts to build Firefox.')
     def artifact(self):
         '''Download, cache, and install pre-built binary artifacts to build Firefox.
 
         Use |mach build| as normal to freshen your installed binary libraries:
         artifact builds automatically download, cache, and install binary
         artifacts from Mozilla automation, replacing whatever may be in your
         object directory.  Use |mach artifact last| to see what binary artifacts
         were last used.
@@ -1491,24 +1488,37 @@ class PackageFrontend(MachCommandBase):
 
         try:
             os.makedirs(cache_dir)
         except OSError as e:
             if e.errno != errno.EEXIST:
                 raise
 
         import which
-        if self._is_windows():
-          hg = which.which('hg.exe')
-        else:
-          hg = which.which('hg')
+
+        here = os.path.abspath(os.path.dirname(__file__))
+        build_obj = MozbuildObject.from_environment(cwd=here)
+
+        hg = None
+        if conditions.is_hg(build_obj):
+            if self._is_windows():
+                hg = which.which('hg.exe')
+            else:
+                hg = which.which('hg')
+
+        git = None
+        if conditions.is_git(build_obj):
+            if self._is_windows():
+                git = which.which('git.exe')
+            else:
+                git = which.which('git')
 
         # Absolutely must come after the virtualenv is populated!
         from mozbuild.artifacts import Artifacts
-        artifacts = Artifacts(tree, job, log=self.log, cache_dir=cache_dir, hg=hg, skip_cache=skip_cache)
+        artifacts = Artifacts(tree, job, log=self.log, cache_dir=cache_dir, skip_cache=skip_cache, hg=hg, git=git)
         return artifacts
 
     @ArtifactSubCommand('artifact', 'install',
         'Install a good pre-built artifact.')
     @CommandArgument('source', metavar='SRC', nargs='?', type=str,
         help='Where to fetch and install artifacts from.  Can be omitted, in '
             'which case the current hg repository is inspected; an hg revision; '
             'a remote URL; or a local file.',