Automagical tree name resolution
authorGregory Szorc <gps@mozilla.com>
Sun, 21 Jul 2013 23:40:29 -0700
changeset 11 a5568c1aa771bcf04b9f0e0a44e6360f39c908bf
parent 10 72299b499208efe068499f72f7d8eacb300b178f
child 12 62331886f4afb9b2d88ffbe66b1e18ecbdfb2f30
push id3
push usergszorc@mozilla.com
push dateMon, 22 Jul 2013 06:40:35 +0000
Automagical tree name resolution
__init__.py
--- a/__init__.py
+++ b/__init__.py
@@ -7,49 +7,70 @@
 This extension adds Mozilla-centric commands and functionality to Mercurial
 to enable contributors to Firefox and related applications to be more
 productive.
 
 Included are commands that interface with Mozilla's automation infrastructure.
 These allow developers to quickly check the status of repositories and builds
 without having to leave the comfort of the terminal.
 
-Repository Aliases
-==================
+Known Mozilla Repositories
+==========================
+
+This extension teaches Mercurial about known Mozilla repositories.
 
-This extension installs aliases for common repository and tree names. Any time
-a command is looking for a tree or repository name, you can specify the
-canonical repository name, the common name, or any number of aliases.
+Each main mozilla-central clone is given a common name. e.g. "mozilla-central"
+becomes "central" and "mozilla-inbound" becomes "inbound." In addition,
+repositories have short aliases to quickly refer to them. e.g.
+"mozilla-central" is can be references by "m-c" or "mc."
+
+The mechanism to resolve a repository string to a repository instance is
+supplemented with these known aliases. For example, to pull from
+mozilla-central, you can simply run `hg pull central`. There is no need to set
+up the [paths] section in your hgrc!
 
 To view the list of known repository aliases, run `hg moztrees`.
 
 Unified Repositories
 ====================
 
 Gecko code is developed in parallel across a number of repositories that are
 cloned from the canonical repository, mozilla-central. Seasoned developers
 typically interact with multiple repositories.
 
 This extension provides mechanisms to create and maintain a single Mercurial
 repository that contains changesets from multiple "upstream" repositories.
 
 The recommended method to create a unified repository is to run `hg
-cloneunified`.
+cloneunified`. This will pull changesets from all major release branches and
+mozilla-inbound.
 
 Once you have a unified repository, you can pull changesets from repositories
-by running `hg pulltree`. e.g. `hg pulltree central fx-team` will pull from
-mozilla-central and fx-team.
+by using `hg pull`, likely by specifying one of the tree aliases (see `hg
+moztrees`).
+
+One needs to be careful when pushing changesets when operating a unified
+repository. By default, `hg push` will attempt to push all local changesets not
+in a remote. This is obviously not desired (you wouldn't want to push
+mozilla-central to mozilla-beta)! Instead, you'll want to limit outgoing
+changesets to a specific head or revision. You can specify `hg push -r REV`.
+Or, for your convenience, the `hg pushtree` is made available. By default, this
+command will push the current "tip" revision to the tree specified. e.g. if you
+have patches on the current tip that need to land in inbound, you can run
+`hg pushtree inbound`.
+
+You can also get creative and push a "remote tracking revision" to another
+repository. e.g. `hg pushtree -r central/default inbound`.
 
 Remote References
 =================
 
 When pulling from known Gecko repositories, this extension automatically
 creates references to branches on the remote. These can be referenced via
-the revision <tree>/<name>. e.g. 'central/default'. This makes it possible to
-update to revisions on the remote. e.g. `hg up central/default`.
+the revision <tree>/<name>. e.g. 'central/default'.
 
 Remote refs are read-only and are updated automatically during repository pull
 and push operations.
 
 This feature is similar to Git remote refs.
 """
 
 import errno
@@ -59,46 +80,69 @@ import sys
 import mercurial.commands as commands
 
 from mercurial.i18n import _
 from mercurial.commands import (
     bookmark,
     pull,
     push,
 )
+from mercurial.error import (
+    RepoError,
+)
 from mercurial.localrepo import (
     repofilecache,
 )
 from mercurial.node import (
-    bin,
     hex,
 )
 from mercurial import (
     cmdutil,
     encoding,
     hg,
     util,
 )
 
 from mozautomation.repository import (
+    resolve_trees_to_uris,
     resolve_uri_to_tree,
 )
 
 
 commands.norepo += ' cloneunified moztrees treestatus'
 cmdtable = {}
 command = cmdutil.command(cmdtable)
 
 colortable = {
     'buildstatus.success': 'green',
     'buildstatus.failed': 'red',
     'buildstatus.testfailed': 'cyan',
 }
 
 
+# Override peer path lookup such that common names magically get resolved to
+# known URIs.
+old_peerorrepo = hg._peerorrepo
+def peerorrepo(ui, path, *args, **kwargs):
+    # Always try the old mechanism first. That way if there is a local
+    # path that shares the name of a magic remote the local path is accessible.
+    try:
+        return old_peerorrepo(ui, path, *args, **kwargs)
+    except RepoError:
+        tree, uri = resolve_trees_to_uris([path])[0]
+
+        if not uri:
+            raise
+
+        path = uri
+        return old_peerorrepo(ui, path, *args, **kwargs)
+
+hg._peerorrepo = peerorrepo
+
+
 @command('moztrees', [], _('hg moztrees'))
 def moztrees(ui, **opts):
     """Show information about Mozilla source trees."""
     from mozautomation.repository import TREE_ALIASES, REPOS
 
     longest = max(len(tree) for tree in REPOS.keys())
     ui.write('%s  %s\n' % (_('Repo').rjust(longest), _('Aliases')))
 
@@ -122,28 +166,49 @@ def cloneunified(ui, dest='gecko', **opt
     add changesets and remote tracking markers into a common repository.
 
     If the destination path is not given, 'gecko' will be used.
 
     This command is effectively an alias for a number of other commands.
     However, due to the way Mercurial internally stores data, it is recommended
     to run this command to ensure optimal storage of data.
     """
-    from mozautomation.repository import resolve_trees_to_uris
-
     repo = hg.repository(ui, ui.expandpath(dest), create=True)
 
     for r in ('esr17', 'release', 'beta', 'aurora', 'central', 'inbound'):
         tree, uri = resolve_trees_to_uris([r])[0]
         ui.warn('Pulling changesets from %s\n' % uri)
         peer = hg.peer(ui, {}, uri)
         result = repo.pull(peer)
         ui.write('%s\n' % result)
 
 
+@command('pushtree',
+    [('r', 'rev', 'tip', _('revision'), _('REV'))],
+    _('hg pushtree [-r REV] TREE'))
+def pushtree(ui, repo, tree=None, rev=None, **opts):
+    """Push changesets to a Mozilla repository.
+
+    If only the tree argument is defined, we will attempt to push the current
+    tip to the repository specified. This may fail due to pushed mq patches,
+    local changes, etc. Please note we only attempt to push the current tip and
+    it's ancestors, not all changesets not in the remote repository. This is
+    different from the default behavior of |hg push| and is the distinguishing
+    difference from that command.
+
+    If you would like to push a non-active head, specify it with -r REV. For
+    example, if you are currently on mozilla-central but wish to push the
+    inbound bookmark to mozilla-inbound, run  |hg pushtree -r inbound inbound|.
+    """
+    if not tree:
+        raise util.Abort(_('A tree must be specified.'))
+
+    return push(ui, repo, rev=rev, dest=tree)
+
+
 @command('treestatus', [], _('hg treestatus [TREE] ...'))
 def treestatus(ui, *trees, **opts):
     """Show the status of the Mozilla repositories.
 
     If trees are open, it is OK to land code on them.
 
     If trees require approval, you'll need to obtain approval from
     release management to land changes.
@@ -164,74 +229,16 @@ def treestatus(ui, *trees, **opts):
 
     longest = max(len(s) for s in status)
 
     for tree in sorted(status):
         s = status[tree]
         ui.write('%s: %s\n' % (tree.rjust(longest), s.status))
 
 
-@command('pulltree', [], _('hg pulltree [TREE] ...'))
-def pulltree(ui, repo, *trees, **opts):
-    """Pull changesets from a Mozilla repository into this repository.
-
-    Trees can be specified by their common name or aliases (see |hg moztrees|).
-    When a tree is pulled, a reference to the current remote heads is created.
-    This allows updating to revisions of remote trees via e.g.
-    |hg up remote/central|.
-
-    If no arguments are specified, the main landing trees (central and inbound)
-    will be pulled.
-    """
-    from mozautomation.repository import resolve_trees_to_uris
-
-    if not trees:
-        trees = ['central', 'inbound']
-
-    uris = resolve_trees_to_uris(trees)
-
-    for tree, uri in uris:
-        if uri is None:
-            ui.warn('Unknown Mozilla repository: %s\n' % tree)
-            continue
-
-        if pull(ui, repo, uri):
-            ui.warn('Error pulling from %s\n' % uri)
-            continue
-
-
-@command('pushtree',
-    [('r', 'rev', 'tip', _('revision'), _('REV'))],
-    _('hg pushtree [-r REV] TREE'))
-def pushtree(ui, repo, tree=None, rev=None, **opts):
-    """Push changesets to a Mozilla repository.
-
-    If only the tree argument is defined, we will attempt to push the current
-    tip to the repository specified. This may fail due to pushed mq patches,
-    local changes, etc. Please note we only attempt to push the current tip and
-    it's ancestors, not all changesets not in the remote repository. This is
-    different from the default behavior of |hg push|.
-
-    If you would like to push a non-active head, specify it with -r REV. For
-    example, if you are currently on mozilla-central but wish to push the
-    inbound bookmark to mozilla-inbound, run  |hg pushtree -r inbound inbound|.
-    """
-    if not tree:
-        raise util.Abort(_('A tree must be specified.'))
-
-    from mozautomation.repository import resolve_trees_to_uris
-
-    tree, uri = resolve_trees_to_uris([tree], write_access=True)[0]
-
-    if not uri:
-        raise util.Abort("Don't know about tree %s" % tree)
-
-    return push(ui, repo, rev=rev, dest=uri)
-
-
 class remoterefs(dict):
     """Represents a remote refs file."""
 
     def __init__(self, repo):
         dict.__init__(self)
         self._repo = repo
 
         try: