Bug 1257478 - Turn mercurial-setup into vcs-setup and add git support. r=gps
authorPanos Astithas <past@mozilla.com>
Wed, 04 Jul 2018 21:48:42 +0300
changeset 486980 138014f66617183f27d3f5da44acf5bcaa6f1450
parent 486979 b0ac567c436cd22147c97fa16a983c30e145a944
child 486981 5b1525668d52bb62975db3a7379640e71e73a923
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgps
bugs1257478
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1257478 - Turn mercurial-setup into vcs-setup and add git support. r=gps MozReview-Commit-ID: AD6gLqFm8Nn
build/mach_bootstrap.py
python/mozboot/mozboot/bootstrap.py
python/mozboot/mozboot/mach_commands.py
testing/marionette/doc/NewContributors.md
tools/tryselect/docs/configuration.rst
tools/tryselect/mach_commands.py
tools/tryselect/push.py
--- a/build/mach_bootstrap.py
+++ b/build/mach_bootstrap.py
@@ -229,17 +229,17 @@ def bootstrap(topsrcdir, mozilla_dir=Non
             data['system']['mac_ver'] = [r, list(v), m]
 
         with open(os.path.join(outgoing_dir, str(uuid.uuid4()) + '.json'),
                   'w') as f:
             json.dump(data, f, sort_keys=True)
 
     def should_skip_dispatch(context, handler):
         # The user is performing a maintenance command.
-        if handler.name in ('bootstrap', 'doctor', 'mach-commands', 'mercurial-setup'):
+        if handler.name in ('bootstrap', 'doctor', 'mach-commands', 'vcs-setup'):
             return True
 
         # We are running in automation.
         if 'MOZ_AUTOMATION' in os.environ or 'TASK_ID' in os.environ:
             return True
 
         # The environment is likely a machine invocation.
         if sys.stdin.closed or not sys.stdin.isatty():
--- a/python/mozboot/mozboot/bootstrap.py
+++ b/python/mozboot/mozboot/bootstrap.py
@@ -94,18 +94,18 @@ FINISHED = '''
 Your system should be ready to build %s!
 '''
 
 SOURCE_ADVERTISE = '''
 Source code can be obtained by running
 
     hg clone https://hg.mozilla.org/mozilla-unified
 
-Or, if you prefer Git, you should install git-cinnabar, and follow the
-instruction here to clone from the Mercurial repository:
+Or, if you prefer Git, by following the instruction here to clone from the
+Mercurial repository:
 
     https://github.com/glandium/git-cinnabar/wiki/Mozilla:-A-git-workflow-for-Gecko-development
 
 Or, if you really prefer vanilla flavor Git:
 
     git clone https://github.com/mozilla/gecko-dev.git
 '''
 
@@ -116,42 +116,51 @@ experience with it.
 Would you like to run a configuration wizard to ensure Mercurial is
 optimally configured?
 
   1. Yes
   2. No
 
 Please enter your reply: '''
 
-CLONE_MERCURIAL = '''
-If you would like to clone the mozilla-unified Mercurial repository, please
+CONFIGURE_GIT = '''
+Mozilla recommends using git-cinnabar to work with mozilla-central.
+
+Would you like to run a few configuration steps to ensure Git is
+optimally configured?
+
+  1. Yes
+  2. No
+
+Please enter your reply: '''
+
+CLONE_VCS = '''
+If you would like to clone the {} {} repository, please
 enter the destination path below.
-
-(If you prefer to use Git, leave this blank.)
 '''
 
-CLONE_MERCURIAL_PROMPT = '''
-Destination directory for Mercurial clone (leave empty to not clone): '''.lstrip()
+CLONE_VCS_PROMPT = '''
+Destination directory for {} clone (leave empty to not clone): '''.lstrip()
 
-CLONE_MERCURIAL_NOT_EMPTY = '''
+CLONE_VCS_NOT_EMPTY = '''
 ERROR! Destination directory '{}' is not empty.
 
 Would you like to clone to '{}'?
 
   1. Yes
   2. No, let me enter another path
   3. No, stop cloning
 
 Please enter your reply: '''.lstrip()
 
-CLONE_MERCURIAL_NOT_EMPTY_FALLBACK_FAILED = '''
+CLONE_VCS_NOT_EMPTY_FALLBACK_FAILED = '''
 ERROR! Destination directory '{}' is not empty.
 '''
 
-CLONE_MERCURIAL_NOT_DIR = '''
+CLONE_VCS_NOT_DIR = '''
 ERROR! Destination '{}' exists but is not a directory.
 '''
 
 DEBIAN_DISTROS = (
     'Debian',
     'debian',
     'Ubuntu',
     # Most Linux Mint editions are based on Ubuntu. One is based on Debian.
@@ -161,16 +170,26 @@ DEBIAN_DISTROS = (
     'Mint',
     'LinuxMint',
     'Elementary OS',
     'Elementary',
     '"elementary OS"',
     '"elementary"'
 )
 
+ADD_GIT_TOOLS_PATH = '''
+To add git-cinnabar to the PATH, edit your shell initialization script, which
+may be called ~/.bashrc or ~/.bash_profile or ~/.profile, and add the following
+lines:
+
+    export PATH="{}:$PATH"
+
+Then restart your shell.
+'''
+
 
 class Bootstrapper(object):
     """Main class that performs system bootstrap."""
 
     def __init__(self, finished=FINISHED, choice=None, no_interactive=False,
                  hg_configure=False, no_system_changes=False):
         self.instance = None
         self.finished = finished
@@ -226,42 +245,47 @@ class Bootstrapper(object):
                 cls = WindowsBootstrapper
 
         if cls is None:
             raise NotImplementedError('Bootstrap support is not yet available '
                                       'for your OS.')
 
         self.instance = cls(**args)
 
-    def input_clone_dest(self):
-        print(CLONE_MERCURIAL)
+    def input_clone_dest(self, with_hg=True):
+        repo_name = 'mozilla-unified'
+        vcs = 'Mercurial'
+        if not with_hg:
+            repo_name = 'gecko'
+            vcs = 'Git'
+        print(CLONE_VCS.format(repo_name, vcs))
 
         while True:
-            dest = raw_input(CLONE_MERCURIAL_PROMPT)
+            dest = raw_input(CLONE_VCS_PROMPT.format(vcs))
             dest = dest.strip()
             if not dest:
                 return ''
 
             dest = os.path.expanduser(dest)
             if not os.path.exists(dest):
                 return dest
 
             if not os.path.isdir(dest):
-                print(CLONE_MERCURIAL_NOT_DIR.format(dest))
+                print(CLONE_VCS_NOT_DIR.format(dest))
                 continue
 
             if os.listdir(dest) == []:
                 return dest
 
-            newdest = os.path.join(dest, 'mozilla-unified')
+            newdest = os.path.join(dest, repo_name)
             if os.path.exists(newdest):
-                print(CLONE_MERCURIAL_NOT_EMPTY_FALLBACK_FAILED.format(dest))
+                print(CLONE_VCS_NOT_EMPTY_FALLBACK_FAILED.format(dest))
                 continue
 
-            choice = self.instance.prompt_int(prompt=CLONE_MERCURIAL_NOT_EMPTY.format(dest,
+            choice = self.instance.prompt_int(prompt=CLONE_VCS_NOT_EMPTY.format(dest,
                                               newdest), low=1, high=3)
             if choice == 1:
                 return newdest
             if choice == 2:
                 continue
             return ''
 
     # The state directory code is largely duplicated from mach_bootstrap.py.
@@ -355,39 +379,60 @@ class Bootstrapper(object):
         # We need to enable the loading of hgrc in case extensions are
         # required to open the repo.
         r = current_firefox_checkout(check_output=self.instance.check_output,
                                      env=self.instance._hg_cleanenv(load_hgrc=True),
                                      hg=self.instance.which('hg'))
         (checkout_type, checkout_root) = r
 
         # Possibly configure Mercurial, but not if the current checkout is Git.
-        # TODO offer to configure Git.
         if hg_installed and state_dir_available and checkout_type != 'git':
             configure_hg = False
             if not self.instance.no_interactive:
                 choice = self.instance.prompt_int(prompt=CONFIGURE_MERCURIAL,
                                                   low=1, high=2)
                 if choice == 1:
                     configure_hg = True
             else:
                 configure_hg = self.hg_configure
 
             if configure_hg:
                 configure_mercurial(self.instance.which('hg'), state_dir)
 
+        # Offer to configure Git, if the current checkout is Git.
+        elif self.instance.which('git') and checkout_type == 'git':
+            should_configure_git = False
+            if not self.instance.no_interactive:
+                choice = self.instance.prompt_int(prompt=CONFIGURE_GIT,
+                                                  low=1, high=2)
+                if choice == 1:
+                    should_configure_git = True
+            else:
+                # Assuming default configuration setting applies to all VCS.
+                should_configure_git = self.hg_configure
+
+            if should_configure_git:
+                configure_git(self.instance.which('git'), state_dir)
+
         # Offer to clone if we're not inside a clone.
         have_clone = False
 
         if checkout_type:
             have_clone = True
         elif hg_installed and not self.instance.no_interactive:
             dest = self.input_clone_dest()
             if dest:
-                have_clone = clone_firefox(self.instance.which('hg'), dest)
+                have_clone = hg_clone_firefox(self.instance.which('hg'), dest)
+                checkout_root = dest
+        elif self.instance.which('git') and checkout_type == 'git':
+            dest = self.input_clone_dest(False)
+            if dest:
+                git = self.instance.which('git')
+                watchman = self.instance.which('watchman')
+                have_clone = git_clone_firefox(git, dest, watchman)
                 checkout_root = dest
 
         if not have_clone:
             print(SOURCE_ADVERTISE)
 
         self.maybe_install_private_packages_or_exit(state_dir,
                                                     state_dir_available,
                                                     have_clone,
@@ -465,17 +510,17 @@ def update_mercurial_repo(hg, url, dest,
 
     try:
         subprocess.check_call(pull_args, cwd=cwd)
         subprocess.check_call(update_args, cwd=dest)
     finally:
         print('=' * 80)
 
 
-def clone_firefox(hg, dest):
+def hg_clone_firefox(hg, dest):
     """Clone the Firefox repository to a specified destination."""
     print('Cloning Firefox Mercurial repository to %s' % dest)
 
     # We create an empty repo then modify the config before adding data.
     # This is necessary to ensure storage settings are optimally
     # configured.
     args = [
         hg,
@@ -557,8 +602,95 @@ def current_firefox_checkout(check_outpu
             if os.path.exists(moz_configure):
                 return ('git', path)
 
         path, child = os.path.split(path)
         if child == '':
             break
 
     return (None, None)
+
+
+def update_git_tools(git, root_state_dir):
+    """Ensure git-cinnabar is up to date."""
+    cinnabar_dir = os.path.join(root_state_dir, 'git-cinnabar')
+
+    # Ensure the latest revision of git-cinnabar is present.
+    update_git_repo(git, 'https://github.com/glandium/git-cinnabar.git',
+                    cinnabar_dir)
+
+    # Perform a download of cinnabar.
+    download_args = [git, 'cinnabar', 'download']
+
+    try:
+        subprocess.check_call(download_args, cwd=cinnabar_dir)
+    except subprocess.CalledProcessError as e:
+        print(e)
+    return cinnabar_dir
+
+
+def update_git_repo(git, url, dest):
+    """Perform a clone/pull + update of a Git repository."""
+    pull_args = [git]
+
+    if os.path.exists(dest):
+        pull_args.extend(['pull'])
+        cwd = dest
+    else:
+        pull_args.extend(['clone', '--no-checkout', url, dest])
+        cwd = '/'
+
+    update_args = [git, 'checkout']
+
+    print('=' * 80)
+    print('Ensuring %s is up to date at %s' % (url, dest))
+
+    try:
+        subprocess.check_call(pull_args, cwd=cwd)
+        subprocess.check_call(update_args, cwd=dest)
+    finally:
+        print('=' * 80)
+
+
+def configure_git(git, root_state_dir):
+    """Run the Git configuration steps."""
+    cinnabar_dir = update_git_tools(git, root_state_dir)
+
+    print(ADD_GIT_TOOLS_PATH.format(cinnabar_dir))
+
+
+def git_clone_firefox(git, dest, watchman=None):
+    """Clone the Firefox repository to a specified destination."""
+    print('Cloning Firefox repository to %s' % dest)
+
+    try:
+        # Configure git per the git-cinnabar requirements.
+        subprocess.check_call([git, 'clone', '-b', 'bookmarks/central',
+                               'hg::https://hg.mozilla.org/mozilla-unified', dest])
+        subprocess.check_call([git, 'remote', 'add', 'inbound',
+                               'hg::ssh://hg.mozilla.org/integration/mozilla-inbound'],
+                              cwd=dest)
+        subprocess.check_call([git, 'config', 'remote.inbound.skipDefaultUpdate',
+                               'true'], cwd=dest)
+        subprocess.check_call([git, 'config', 'remote.inbound.push',
+                               '+HEAD:refs/heads/branches/default/tip'], cwd=dest)
+        subprocess.check_call([git, 'config', 'fetch.prune', 'true'], cwd=dest)
+        subprocess.check_call([git, 'config', 'pull.ff', 'only'], cwd=dest)
+
+        watchman_sample = os.path.join(dest, '.git/hooks/fsmonitor-watchman.sample')
+        # Older versions of git didn't include fsmonitor-watchman.sample.
+        if watchman and watchman_sample:
+            print('Configuring watchman')
+            watchman_config = os.path.join(dest, '.git/hooks/query-watchman')
+            if not os.path.exists(watchman_config):
+                print('Copying %s to %s' % (watchman_sample, watchman_config))
+                copy_args = ['cp', '.git/hooks/fsmonitor-watchman.sample',
+                             '.git/hooks/query-watchman']
+                subprocess.check_call(copy_args, cwd=dest)
+
+            config_args = [git, 'config', 'core.fsmonitor', '.git/hooks/query-watchman']
+            subprocess.check_call(config_args, cwd=dest)
+    except Exception as e:
+        print(e)
+        return False
+
+    print('Firefox source code available at %s' % dest)
+    return True
--- a/python/mozboot/mozboot/mach_commands.py
+++ b/python/mozboot/mozboot/mach_commands.py
@@ -38,42 +38,58 @@ class Bootstrap(object):
         bootstrapper.bootstrap()
 
 
 @CommandProvider
 class VersionControlCommands(object):
     def __init__(self, context):
         self._context = context
 
-    @Command('mercurial-setup', category='devenv',
-             description='Help configure Mercurial for optimal development.')
+    @Command('vcs-setup', category='devenv',
+             description='Help configure a VCS for optimal development.')
     @CommandArgument('-u', '--update-only', action='store_true',
                      help='Only update recommended extensions, don\'t run the wizard.')
-    def mercurial_setup(self, update_only=False):
-        """Ensure Mercurial is optimally configured.
+    @CommandArgument('-g', '--git', action='store_true',
+                     help='Use Git instead of Mercurial.')
+    def vcs_setup(self, update_only=False, git=False):
+        """Ensure a Version Control System (Mercurial or Git) is optimally
+        configured.
 
-        This command will inspect your Mercurial configuration and
+        This command will inspect your VCS configuration and
         guide you through an interactive wizard helping you configure
-        Mercurial for optimal use on Mozilla projects.
+        VCS for optimal use on Mozilla projects.
 
         User choice is respected: no changes are made without explicit
         confirmation from you.
 
         If "--update-only" is used, the interactive wizard is disabled
         and this command only ensures that remote repositories providing
-        Mercurial extensions are up to date.
+        VCS extensions are up to date.
+
+        If "--git" is used, then Git is selected as the VCS instead of Mercurial,
+        which is the default.
         """
         import which
         import mozboot.bootstrap as bootstrap
 
+        vcs = 'hg'
+        if git:
+            vcs = 'git'
+
         # "hg" is an executable script with a shebang, which will be found
-        # be which.which. We need to pass a win32 executable to the function
+        # by which.which. We need to pass a win32 executable to the function
         # because we spawn a process
         # from it.
         if sys.platform in ('win32', 'msys'):
-            hg = which.which('hg.exe')
+            vcs = which.which(vcs + '.exe')
         else:
-            hg = which.which('hg')
+            vcs = which.which(vcs)
 
         if update_only:
-            bootstrap.update_vct(hg, self._context.state_dir)
+            if git:
+                bootstrap.update_git_tools(vcs, self._context.state_dir)
+            else:
+                bootstrap.update_vct(vcs, self._context.state_dir)
         else:
-            bootstrap.configure_mercurial(hg, self._context.state_dir)
+            if git:
+                bootstrap.configure_git(vcs, self._context.state_dir)
+            else:
+                bootstrap.configure_mercurial(vcs, self._context.state_dir)
--- a/testing/marionette/doc/NewContributors.md
+++ b/testing/marionette/doc/NewContributors.md
@@ -91,17 +91,17 @@ Getting the code, running tests
        --binary $FF_NIGHTLY_PATH`.
 
        * These are the same tests that you ran with `./mach
          marionette test`, but they are testing the Firefox Nightly
          that you just installed.  (`./mach marionette test`
          just calls code in runtests.py).
 
      * Configure Mercurial with helpful extensions for Mozilla
-       development by running `./mach mercurial-setup`.
+       development by running `./mach vcs-setup`.
 
        * It should install extensions like firefox-trees and set
          you up to be able to use MozReview, our code-review tool.
 
        * If it asks you about activating the mq extension, I suggest
          you respond with 'No'.
 
 [mach]: https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/mach
--- a/tools/tryselect/docs/configuration.rst
+++ b/tools/tryselect/docs/configuration.rst
@@ -19,33 +19,37 @@ After you have level 1 access, you'll ne
 Configuring Try with Mercurial
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 The recommended way to push to try is via the ``mach try`` command. This requires the
 ``push-to-try`` extension which can be installed by running:
 
 .. code-block:: shell
 
-    $ mach mercurial-setup
+    $ mach vcs-setup
 
 You should also enable the ``firefoxtree`` extension which will provide a handy ``try`` path alias.
 You can also create this alias manually by adding
 
 .. code-block:: ini
 
     [paths] try = ssh://hg.mozilla.org/try
 
 This is only necessary if not using ``firefoxtree``.
 
 
 Configuring Try with Git Cinnabar
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-The recommended way to use try with git is with `git cinnabar`_. You can follow `this tutorial`_ for
-a workflow which includes setting up the ability to push to try.
+The recommended way to use try with git is with `git cinnabar`, which can be
+installed by running:
+
+.. code-block:: shell
+
+    $ mach vcs-setup --git
 
 
 Configuring Try with Vanilla Git
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 This workflow isn't well supported, but is possible using `moz-git-tools`_, and specifically the
 `git push-to-try`_ command.
 
--- a/tools/tryselect/mach_commands.py
+++ b/tools/tryselect/mach_commands.py
@@ -207,18 +207,18 @@ class TrySelect(MachCommandBase):
         suites in -u will be run in full. Where tests are selected by
         positional argument they will be run in a single chunk.
 
         If no build option is selected, both debug and opt will be
         scheduled. If no platform is selected a default is taken from
         the AUTOTRY_PLATFORM_HINT environment variable, if set.
 
         The command requires either its own mercurial extension ("push-to-try",
-        installable from mach mercurial-setup) or a git repo using git-cinnabar
-        (available at https://github.com/glandium/git-cinnabar).
+        installable from mach vcs-setup) or a git repo using git-cinnabar
+        (installable from mach vcs-setup --git).
 
         """
         from tryselect.selectors.syntax import AutoTry
 
         try:
             if self.substs.get("MOZ_ARTIFACT_BUILDS"):
                 kwargs['local_artifact_build'] = True
         except BuildEnvironmentNotFoundException:
--- a/tools/tryselect/push.py
+++ b/tools/tryselect/push.py
@@ -12,29 +12,28 @@ import sys
 from mozboot.util import get_state_dir
 from mozbuild.base import MozbuildObject
 from mozversioncontrol import get_repository_object, MissingVCSExtension
 
 GIT_CINNABAR_NOT_FOUND = """
 Could not detect `git-cinnabar`.
 
 The `mach try` command requires git-cinnabar to be installed when
-pushing from git. For more information and installation instruction,
-please see:
+pushing from git. Please install it by running:
 
-    https://github.com/glandium/git-cinnabar
+    $ ./mach vcs-setup --git
 """.lstrip()
 
 HG_PUSH_TO_TRY_NOT_FOUND = """
 Could not detect `push-to-try`.
 
 The `mach try` command requires the push-to-try extension enabled
 when pushing from hg. Please install it by running:
 
-    $ ./mach mercurial-setup
+    $ ./mach vcs-setup
 """.lstrip()
 
 VCS_NOT_FOUND = """
 Could not detect version control. Only `hg` or `git` are supported.
 """.strip()
 
 UNCOMMITTED_CHANGES = """
 ERROR please commit changes before continuing