Bug 968259 - Redirect content to new repository location; r=ted
authorGregory Szorc <gps@mozilla.com>
Wed, 05 Feb 2014 13:56:54 -0800
changeset 135 ccdd4e552096
parent 134 b2e26e4759bc
child 136 b64c2cba9a46
push id64
push usergszorc@mozilla.com
push dateThu, 06 Feb 2014 16:07:27 +0000
reviewersted
bugs968259
Bug 968259 - Redirect content to new repository location; r=ted The extension now aborts at load time. The error message and documentation references the new location.
README
__init__.py
bz.py
bzauth.py
bzexport.py
--- a/README
+++ b/README
@@ -1,43 +1,13 @@
 bzexport: a Mercurial extension for attaching patches from a
   Mercurial repository to bugzilla from the command line.
 
-INSTALLING
-----------
+THIS EXTENSION HAS MOVED REPOSITORIES. IT CAN NOW BE FOUND AT
 
-1) Clone this repository somewhere on your local machine:
-   hg clone http://hg.mozilla.org/users/tmielczarek_mozilla.com/bzexport/
-2) Edit your ~/.hgrc and add the following to enable the extension:
-   [extensions]
-   bzexport = /path/to/bzexport/bzexport.py
-
-NOTE FOR WINDOWS USERS
-----------------------
+https://hg.mozilla.org/hgcustom/version-control-tools/
 
-bzexport attempts to be clever and borrow your Bugzilla login cookies
-from your Firefox profile. This does not work on the windows binary
-releases of Mercurial, as shipped with MozillaBuild, so you'll need
-to add your username and password to your .hgrc as described below.
-(The technical reason is that the binary Mercurial releases use
-py2exe to freeze a Python install into the hg binary, which leaves
-out modules that aren't used, including the sqlite3 module which
-bzexport needs to read cookies.sqlite.)
-
-EDITING OPTIONS
----------------
+Please delete this repository, clone the new one, and adjust your
+hgrc to reference the new location.
 
-bzexport options can be added in a [bzexport] section in your .hgrc.
-Currently supported options are:
-[bzexport]
-# Your bugzilla login, only necessary if bzexport can't locate
-# your Firefox profile, or if you're on Windows using
-# a binary Mercurial release.
-username = username@example.com
-
-# Your bugzilla password, see above.
-password = secret
-
-# URL of the Bugzilla server
-bugzilla = https://bugzilla.mozilla.org
-
-# URL of the corresponding BzAPI server
-api_server = https://api-dev.bugzilla.mozilla.org/latest/
+If you are using mach to build Firefox, you can run
+`mach mercurial-setup` to have your configuration updated
+automatically.
--- a/__init__.py
+++ b/__init__.py
@@ -14,1168 +14,23 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 """
 bzexport
 
 Attach a patch from a HG repository to a bugzilla bug.
 
-To enable this extension, edit your ~/.hgrc (or %APPDATA%/Mercurial.ini)
-and add:
-
-    [extensions]
-
-    bzexport = /path/to/bzexport.py
-
-You can then use it like so:
-hg bzexport [-e] [REV] [BUG|--new]
-
-Where REV is any local revision, and BUG is a bug number on
-bugzilla.mozilla.org or the option '--new' to create a new bug. The extension
-is tuned to work best with MQ changesets (it can only currently work with
-applied patches).
-
-If no revision is specified, it will default to '.' (the revision your checkout
-is based on). If no bug is specified, the changeset commit message will be
-scanned for a bug number to use.
-
-This extension also adds a 'newbug' command for creating a new bug without
-attaching anything to it.
-
+This extension has moved repositories and now lives in
+https://hg.mozilla.org/hgcustom/version-control-tools/
 """
-from mercurial.i18n import _
-from mercurial import cmdutil, util, patch
-from hgext import mq
-from cStringIO import StringIO
-import json
-import sys
-import os
-import re
-import urllib2
-import urlparse
-import bzauth
-import bz
-
-# This is stolen from buglink.py
-bug_re = re.compile(r'''# bug followed by any sequence of numbers, or
-                        # a standalone sequence of numbers
-                     (
-                        (?:
-                          bug |
-                          b= |
-                          # a sequence of 5+ numbers preceded by whitespace
-                          (?=\b\#?\d{5,}) |
-                          # numbers at the very beginning
-                          ^(?=\d)
-                        )
-                        (?:\s*\#?)(\d+)
-                     )''', re.I | re.X)
-review_re = re.compile(r'[ra][=?]+(\w[^ ]+)')
-
-BINARY_CACHE_FILENAME = ".bzexport.cache"
-INI_CACHE_FILENAME = ".bzexport"
-
-
-def get_default_version(ui, api_server, product):
-    c = bzauth.load_configuration(ui, api_server, BINARY_CACHE_FILENAME)
-    versions = c['product'].get(product, {}).get('version')
-    if not versions:
-        raise util.Abort(_("Product %s has no versions") % product)
-    # Ugh! /configuration returns the versions in sorted order, which makes it
-    # impossible to determine the default. If there's something like
-    # "unspecified" in the list, prefer that for now, until bzapi gets fixed.
-    # https://bugzilla.mozilla.org/show_bug.cgi?id=723170
-    uns = [ v for v in versions if v.startswith("un") ]
-    if uns:
-        return uns[-1]
-    return versions[-1]
-
-
-# ui.promptchoice only allows single-character responses and was changed in
-# 2.7.1 to not be backwards compatible. So ignore it completely and just use
-# ui.prompt.
-def prompt_manychoice(ui, message, prompts):
-    while True:
-        choice = ui.prompt(message, 'default')
-        if choice == 'default':
-            return 0
-        choice = '&' + choice
-        if choice in prompts:
-            return prompts.index(choice)
-        ui.write("unrecognized response\n")
-
-def prompt_menu(ui, name, values,
-                readable_values=None,
-                message='',
-                allow_none=False):
-    if message and not message.endswith('\n'):
-        message += "\n"
-    prompts = []
-    for i in range(0, len(values)):
-        prompts.append("&" + str(i + 1))
-        value = (readable_values or values)[i]
-        message += "  %d. %s\n" % ((i + 1), value.encode('utf-8', 'replace'))
-    if allow_none:
-        prompts.append("&n")
-        message += "  n. None\n\n"
-    prompts.append("&a")
-    message += "  a. Abort\n\n"
-    message += _("Select %s:") % name
-
-    choice = prompt_manychoice(ui, message, prompts)
-
-    if allow_none and choice == len(prompts) - 2:
-        return None
-    if choice == len(prompts) - 1:
-        raise util.Abort("User requested abort while choosing %s" % name)
-    else:
-        return values[choice]
-
-
-def filter_strings(collection, substring):
-    substring = substring.lower()
-    ret = [ s for s in collection if s.lower() == substring ]
-    if ret:
-        return ret
-    return [ v for v in collection if v.lower().find(substring) != -1 ]
-
-
-def choose_value(ui, desc, options, message="", usemenu=True):
-    if len(options) == 0:
-        return None
-    elif len(options) == 1:
-        return options.pop()
-    elif usemenu:
-        return prompt_menu(ui, desc, list(options), message=message)
-    else:
-        return None
-
-
-def multi_user_prompt(ui, desc, search_results):
-    return prompt_menu(ui, desc, search_results['names'],
-                       readable_values=search_results['real_names'],
-                       message="Multiple bugzilla users matching \"%s\":\n\n" % search_results["search_string"],
-                       allow_none=True)
-
-
-# Returns [ { search_string: original, names: [ str ], real_names: [ str ] } ]
-def find_users(ui, api_server, user_cache_filename, token, search_strings):
-    c = bzauth.load_user_cache(ui, api_server, user_cache_filename)
-    section = api_server
-
-    search_results = []
-    for search_string in search_strings:
-        name = c.get(section, search_string)
-        if name:
-            search_results.append({"search_string": search_string,
-                                   "names": [name],
-                                   "real_names": ["not_a_real_name"]})
-            continue
-
-        try:
-            users = json.load(urlopen(ui, bz.find_users(api_server, token, search_string)))
-            name = None
-            real_names = map(lambda user: "%s <%s>" % (user["real_name"], user["email"]) if user["real_name"] else user["email"], users["users"])
-            names = map(lambda user: user["name"], users["users"])
-            search_results.append({"search_string": search_string,
-                                   "names": names,
-                                   "real_names": real_names})
-            if len(real_names) == 1:
-                c.set(section, search_string, names[0])
-        except Exception, e:
-            search_results.append({"search_string": search_string,
-                                   "error": str(e),
-                                   "real_names": None})
-            raise
-    bzauth.store_user_cache(c, user_cache_filename)
-    return search_results
-
-
-# search_strings is a simple list of strings
-def validate_users(ui, api_server, auth, search_strings, multi_callback, multi_desc):
-    search_results = find_users(ui, api_server, INI_CACHE_FILENAME, auth, search_strings)
-    search_failed = False
-    results = {}
-    for search_result in search_results:
-        if search_result["real_names"] is None:
-            ui.write_err("Error: couldn't find user with search string \"%s\": %s\n" % (search_result["search_string"], search_result["error"]))
-            search_failed = True
-        elif len(search_result["real_names"]) > 10:
-            ui.write_err("Error: too many bugzilla users matching \"%s\":\n\n" % search_result["search_string"])
-            for real_name in search_result["real_names"]:
-                ui.write_err("  %s\n" % real_name.encode('ascii', 'replace'))
-            search_failed = True
-        elif len(search_result["real_names"]) > 1:
-            user = multi_callback(ui, multi_desc, search_result)
-            if user is not None:
-                results[search_result['search_string']] = [ user ]
-        elif len(search_result["real_names"]) == 1:
-            results[search_result['search_string']] = search_result['names']
-        else:
-            ui.write_err("Couldn't find a bugzilla user matching \"%s\"!\n" % search_result["search_string"])
-            search_failed = True
-    if search_failed:
-        return
-    return results
-
-
-def select_users(valid, keys):
-    if valid is None:
-        return None
-    users = []
-    for key in keys:
-        users.extend(valid[key])
-    return users
-
-
-# Copied from savecommitmessage in localrepo.py (but with variable filename and unicode)
-def savefile(repo, basename, text):
-    fp = repo.opener(basename, 'wb')
-    try:
-        fp.write(text.encode('utf-8'))
-    finally:
-        fp.close()
-    return repo.pathto(fp.name[len(repo.root) + 1:])
-
-# Sure sign of a poor developer: they implement their own half-assed, one-off
-# templating engine instead of reusing an existing one.
-
-# Simple templating engine: scan a template for @KEYWORDS@ (keywords surrounded
-# in @ signs). First, replace them with corresponding values in the 'fields'
-# dictionary and show the result to the user. Allow user to edit. Then convert
-# the whole template into a regex with /(.*?)/ in place of each keyword and
-# match the edited output against that. Pull out the possibly-updated field
-# values.
-templates = { 'new_both_template': '''Title: @BUGTITLE@
-Product: @PRODUCT@
-Component: @COMPONENT@
-Version: @PRODVERSION@
-CC: @CC@
-Depends: @DEPENDS@
-Blocks: @BLOCKS@
-
-Bug Description (aka comment 0):
-
-@BUGCOMMENT0@
-
---- END Bug Description ---
-
-Attachment Filename: @ATTACHMENT_FILENAME@
-Attachment Description: @ATTACHMENT_DESCRIPTION@
-Reviewers: @REVIEWERS@
-Feedback: @FEEDBACK@
-Attachment Comment (appears as a regular comment on the bug):
-
-@ATTACHCOMMENT@
-
----- END Attachment Comment ----
-''',
-              'new_bug_template': '''Title: @BUGTITLE@
-Product: @PRODUCT@
-Component: @COMPONENT@
-Version: @PRODVERSION@
-CC: @CC@
-Depends: @DEPENDS@
-Blocks: @BLOCKS@
-
-Bug Description (aka comment 0):
-
-@BUGCOMMENT0@
-
---- END Bug Description ---
-''',
-              'existing_bug_template': '''Bug: @BUGNUM@
-
-Attachment Filename: @ATTACHMENT_FILENAME@
-Attachment Description: @ATTACHMENT_DESCRIPTION@
-Reviewers: @REVIEWERS@
-Feedback: @FEEDBACK@
-Attachment Comment (appears as a regular comment on the bug):
-
-@ATTACHCOMMENT@
-
----- END Attachment Comment ----
-''' }
-
-field_re = re.compile(r'@([^@]+)@')
-
-
-def edit_form(ui, repo, fields, template_name):
-    template_fields = []
-
-    def substitute_field(m):
-        field_name = m.group(1)
-        template_fields.append(field_name)
-        value = fields[field_name]
-        if not value:
-            return '<none>'
-        elif isinstance(value, list):
-            return ', '.join(value)
-        else:
-            return value or '<none>'
-
-    # Fill in a template with the passed-in fields
-    template = templates[template_name]
-    orig = field_re.sub(substitute_field, template)
-
-    # Convert "template with @KEYWORD1@ and @KEYWORD2@" into "template with
-    # (.*?) and (.*?)". But also allow simple fields (eg "Product: @PRODUCT@")
-    # to have the space after the colon omitted, to handle the case where you
-    # set a default for the field in your .hgrc and you want to clear it out
-    # and be prompted instead. (The regex will end up being /Product\: *(.*?)/s
-    # instead.)
-    pattern = template
-    pattern = re.sub(r'[^\w@]', lambda m: '\\' + m.group(0), pattern)
-    pattern = re.sub(r'\\:\\ ', '\\: *', pattern)
-    pattern = field_re.sub('(.*?)', pattern)
-    pattern = re.compile(pattern, re.S)
-
-    # Allow user to edit the form
-    new = ui.edit(orig.encode('utf-8'), ui.username()).decode('utf-8')
-
-    saved = savefile(repo, "last_bzexport.txt", new)
-    ui.write("saved edited form in %s\n" % saved)
-
-    # Use the previously-created pattern to pull out the new keyword values
-    m = pattern.match(new)
-    if not m:
-        raise util.Abort("Edited form %s has invalid format" % saved)
-
-    new_fields = fields.copy()
-    marker_found = False
-    for field, value in zip(template_fields, m.groups()):
-        if value == '<required>':
-            raise util.Abort("Required field %s not filled in" % (field,))
-        elif value == '<none>' or value == '':
-            if isinstance(fields[field], list):
-                new_fields[field] = []
-            else:
-                new_fields[field] = None
-        else:
-            if value == '<choose-from-menu>':
-                marker_found = True
-                new_fields[field] = value
-            else:
-                if isinstance(fields[field], list):
-                    new_fields[field] = re.split(', *', value)
-                else:
-                    new_fields[field] = value
-
-    if new == orig and not marker_found:
-        if ui.prompt(_("No changes made; continue with current values (y/n)?")) != 'y':
-            sys.exit(0)
-
-    return new_fields
-
-
-def bugzilla_info(ui, profile):
-    api_server = ui.config("bzexport", "api_server", "https://api-dev.bugzilla.mozilla.org/latest/")
-    bugzilla = ui.config("bzexport", "bugzilla", "https://bugzilla.mozilla.org/")
-    username = ui.config("bzexport", "username", None)
-    password = ui.config("bzexport", "password", None)
-
-    auth = bzauth.get_auth(ui, bugzilla, profile, username, password)
-    return (auth, api_server, bugzilla)
-
-
-def urlopen(ui, req):
-    """Wraps urllib2.urlopen() to provide error handling."""
-    ui.progress('Accessing bugzilla server', None, item=req.get_full_url())
-    #ui.debug("%s %s\n" % (req.get_method(), req.get_data()))
-    try:
-        return urllib2.urlopen(req, timeout=30)
-    except urllib2.HTTPError, e:
-        msg = ''
-        try:
-            err = json.load(e)
-            msg = err['message']
-        except:
-            msg = e
-            pass
-
-        if msg:
-            ui.warn('Error: %s\n' % msg)
-        raise
-
-
-def infer_arguments(ui, repo, args, opts):
-    rev = None
-    bug = None
-    if len(args) < 2:
-        # We need to guess at some args.
-        if len(args) == 1:
-            # Just one arg. Could be a revision or a bug number.
-            # Check this first, because a series of digits
-            # can be a revision number, but it's unlikely a user
-            # would use it to mean a revision here.
-            if not args[0].isdigit() and args[0] in repo:
-                # Looks like a changeset
-                rev = args[0]
-            else:
-                # Don't do any validation here, to allow
-                # users to use bug aliases. The BzAPI call
-                # will fail with bad bug numbers.
-                bug = args[0]
-
-        # With zero args we'll guess at both, and if we fail we'll
-        # fail later.
-    elif len(args) > 2:
-        raise util.Abort(_("Too many arguments!"))
-    else:
-        # Just right.
-        rev, bug = args
-
-    if rev is None:
-        # Default to '.'
-        rev = '.'
-
-    # If no revision or '.' was given, complain about local changes
-    if rev == '.' and not opts['force']:
-        m, a, r, d = repo.status()[:4]
-        if (m or a or r or d):
-            raise util.Abort(_("Local changes found; refresh first!"))
-
-    if rev in [".", "tip", "qtip", "default"]:
-        # Look for a nicer name in the MQ.
-        if hasattr(repo, 'mq') and repo.mq.applied:
-            rev = repo.mq.applied[-1].name
-
-    # Check for bug number in the patch filename
-    if bug is None:
-        m = re.match(r'bug[_\-]?(\d+)', rev)
-        if m:
-            bug = m.group(1)
-
-    return (rev, bug)
-
-
-def choose_prodcomponent(ui, cache, orig_product, orig_component, finalize=False):
-    def canon(v):
-        if not v or v == '<choose-from-menu>':
-            return None
-        return v
-
-    product = canon(orig_product)
-    component = canon(orig_component)
-
-    products_info = cache.get('product', {})
-    all_products = products_info.keys()
-
-    def products_with_component_match(component):
-        products = []
-        for p in all_products:
-            if len(filter_strings(products_info[p]['component'].keys(), component)) > 0:
-                products.append(p)
-        return products
-
-    # Tricky case: components can legitimately contain '/'. If the user
-    # specified such a component with no product, then we will have generated a
-    # bogus list of candidate products and a bogus list of candidate components
-    # (one of which is the correct one, since it contains the substring.)
-    # Restrict to just that component.
-    #
-    # Note that using a '/' as a separator is deprecated. If you use '::',
-    # there is no ambiguity.
-    if component and not product:
-        doublecolon = component.find('::')
-        slash = component.find('/')
-        if doublecolon != -1:
-            product = component[0:doublecolon].rstrip()
-            component = component[doublecolon + 2:].lstrip()
-        elif slash != -1:
-            all_components = set()
-            for p in all_products:
-                all_components.update(products_info[p]['component'].keys())
-            if component.lower() not in [ c.lower() for c in all_components ]:
-                product = component[0:slash].rstrip()
-                component = component[slash + 1:].lstrip()
-
-    # 'products' and 'components' will be the set of valid products/components
-    # remaining after filtering by the 'product' and 'component' passed in
-    products = all_products
-    components = set()
-
-    if product is None:
-        if component is None:
-            product = choose_value(ui, 'product', sorted(all_products),
-                                   message="Possible Products:",
-                                   usemenu=finalize)
-            if product is not None:
-                products = [ product ]
-        else:
-            # Inverted lookup: find products matching the given component (or
-            # substring of a component)
-            products = products_with_component_match(component)
-    else:
-        products = filter_strings(all_products, product)
-
-    for p in products:
-        components.update(products_info[p]['component'].keys())
-    if component is not None:
-        components = filter_strings(components, component)
-
-    # Now choose a final product::component (unless finalize is false, in which
-    # case if there are multiple possibilities, the passed-in value will be
-    # preserved)
-
-    if len(products) == 0:
-        product = None
-    elif len(products) == 1:
-        product = products.pop()
-    else:
-        product = choose_value(ui, 'product', sorted(products),
-                               message="Select from these products:",
-                               usemenu=finalize)
-        if product is not None:
-            prodcomponents = products_info[product]['component'].keys()
-            components = set(components).intersection(prodcomponents)
-        else:
-            product = orig_product
-
-    if len(components) == 0:
-        component = None
-    elif len(components) == 1:
-        component = components.pop()
-    else:
-        component = choose_value(ui, 'component', sorted(components),
-                                 message="Select from these components:",
-                                 usemenu=finalize)
-        if component is None:
-            component = orig_component
-
-    return (product, component)
-
-
-def fill_values(values, ui, api_server, finalize=False):
-    cache = bzauth.load_configuration(ui, api_server, BINARY_CACHE_FILENAME)
-
-    if 'PRODUCT' in values:
-        values['PRODUCT'], values['COMPONENT'] = choose_prodcomponent(ui, cache, values['PRODUCT'], values['COMPONENT'], finalize=finalize)
-
-    if 'PRODVERSION' in values:
-        if values['PRODVERSION'] == '<default>' and values['PRODUCT'] not in [None, '<choose-from-menu>']:
-            values['PRODVERSION'] = get_default_version(ui, api_server, values['PRODUCT'])
-            ui.write("Using default version '%s' of product %s\n" % (values['PRODVERSION'].encode('utf-8'), values['PRODUCT'].encode('utf-8')))
-
-    # 'finalize' means we need the final values. (finalize will be set to false
-    # for prepopulating fields that will be displayed in a form)
-    if not finalize:
-        return values
-
-    if 'BUGTITLE' in values:
-        if values['BUGTITLE'] in [None, '<required>']:
-            values['BUGTITLE'] = ui.prompt(_("Bug title:"))
-
-    if 'BUGCOMMENT0' in values:
-        if values['BUGCOMMENT0'] in [None, '<required>']:
-            values['BUGCOMMENT0'] = ui.prompt(_("Bug description:"))
-
-    if 'ATTACHMENT_DESCRIPTION' in values:
-        if values['ATTACHMENT_DESCRIPTION'] in [None, '<required>']:
-            values['ATTACHMENT_DESCRIPTION'] = ui.prompt(_("Patch description:"), default=values['ATTACHMENT_FILENAME'])
-
-    return values
-
-
-def update_patch(ui, repo, rev, bug, update_patch, rename_patch, interactive):
-    q = repo.mq
-    try:
-        rev = q.lookup(rev)
-    except util.error.Abort:
-        # If the patch is not coming from mq, don't complain that the name is not found
-        update_patch = False
-        rename_patch = False
-
-    todo = []
-    if rename_patch:
-        todo.append("name")
-    if update_patch:
-        todo.append("description")
-    if todo:
-        if interactive and ui.prompt("Update patch " + " and ".join(todo) + " (y/n)?") != 'y':
-            ui.write(_("Exiting without updating patch\n"))
-            return
 
-    if rename_patch:
-        newname = str("bug-%s-%s" % (bug, re.sub(r'^bug-\d+-', '', rev)))
-        if newname != rev:
-            try:
-                mq.rename(ui, repo, rev, newname)
-            except:
-                # mq.rename has a tendency to leave things in an inconsistent
-                # state. Fix things up.
-                q.invalidate()
-                if os.path.exists(q.join(newname)) and newname not in q.fullseries:
-                    os.rename(q.join(newname), q.join(rev))
-                raise
-            rev = newname
-
-    if update_patch:
-        # Add "Bug nnnn - " to the beginning of the description
-        ph = mq.patchheader(q.join(rev), q.plainmode)
-        msg = [ s.decode('utf-8') for s in ph.message ]
-        if not msg:
-            msg = ["Bug %s patch" % bug]
-        elif not bug_re.search(msg[0]):
-            msg[0] = "Bug %s - %s" % (bug, msg[0])
-        opts = { 'git': True, 'message': '\n'.join(msg).encode('utf-8'), 'include': ["re:."] }
-        mq.refresh(ui, repo, **opts)
-
-    return rev
-
-
-def obsolete_old_patches(ui, api_server, token, bugid, bugzilla, filename, ignore_id, pre_hook=None):
-    bug = None
-    req = bz.get_attachments(api_server, token, bugid)
-    try:
-        bug = json.load(urlopen(ui, req))
-    except Exception, e:
-        raise util.Abort(_("Could not load info for bug %s: %s") % (bug, str(e)))
-
-    patches = [p
-               for p in bug["attachments"]
-               if p["is_patch"]
-                  and not p["is_obsolete"]
-                  and p["file_name"] == filename
-                  and int(p["id"]) != int(ignore_id)]
-    if not len(patches):
-        return True
-
-    for p in patches:
-        #TODO: "?last_change_time=" + p["last_change_time"] to avoid conflicts?
-        attachment_url = urlparse.urljoin(bugzilla, "attachment.cgi?id=%s" % (p['id']))
-        if pre_hook and not pre_hook(url=attachment_url, filename=p['file_name'], description=p["description"]):
-            continue
-
-        req = bz.obsolete_attachment(api_server, token, p)
-        try:
-            json.load(urlopen(ui, req))
-        except Exception, e:
-            raise util.Abort(_("Could not update attachment %s: %s") % (p["id"], str(e)))
-
-    return True
-
-
-def find_reviewers(ui, api_server, user_cache_filename, token, search_strings):
-    cache = bzauth.load_user_cache(ui, api_server, user_cache_filename)
-    section = api_server
-
-    search_results = []
-    for search_string in search_strings:
-        name = cache.get(section, search_string)
-        if name:
-            search_results.append({"search_string": search_string,
-                                   "names": [name],
-                                   "real_names": ["not_a_real_name"]})
-            continue
-
-        try:
-            users = json.load(urlopen(ui, bz.find_users(api_server, token, search_string)))
-            name = None
-            real_names = map(lambda user: "%s <%s>" % (user["real_name"], user["email"]) if user["real_name"] else user["email"], users["users"])
-            names = map(lambda user: user["name"], users["users"])
-            search_results.append({"search_string": search_string,
-                                   "names": names,
-                                   "real_names": real_names})
-            if len(real_names) == 1:
-                cache.set(section, search_string, names[0])
-        except Exception, e:
-            search_results.append({"search_string": search_string,
-                                   "error": str(e),
-                                   "real_names": None})
-            raise
-    bzauth.store_user_cache(cache, user_cache_filename)
-    return search_results
-
-
-def flag_type_id(ui, api_server, config_cache_filename, flag_name, product, component):
-    """
-    Look up the numeric type id for the 'review' flag from the given bugzilla server
-    """
-    configuration = bzauth.load_configuration(ui, api_server, config_cache_filename)
-    if not configuration or not configuration["flag_type"]:
-        raise util.Abort(_("Could not find configuration object"))
-
-    # Get the set of flag ids used for this product::component
-    prodflags = configuration['product'][product]['component'][component]['flag_type']
-    flagdefs = configuration['flag_type']
-
-    flag_ids = [ id for id in prodflags if flagdefs[str(id)]['name'] == flag_name ]
-
-    if len(flag_ids) != 1:
-        raise util.Abort(_("Could not find unique %s flag id") % flag_name)
-
-    return flag_ids[0]
-
-
-def review_flag_type_id(ui, api_server, config_cache_filename, product, component):
-    return flag_type_id(ui, api_server, config_cache_filename, 'review', product, component)
-
-
-def feedback_flag_type_id(ui, api_server, config_cache_filename, product, component):
-    return flag_type_id(ui, api_server, config_cache_filename, 'feedback', product, component)
-
-def create_attachment(ui, api_server, token, bug,
-                      config_cache_filename,
-                      attachment_contents, description="attachment",
-                      filename="attachment", comment="",
-                      reviewers=None, feedback=None, product=None, component=None):
-
-    opts = {}
-    if reviewers:
-        opts['review_flag_id'] = review_flag_type_id(ui, api_server, config_cache_filename, product, component)
-        opts['reviewers'] = reviewers
-
-    if feedback:
-        opts['feedback_flag_id'] = feedback_flag_type_id(ui, api_server, config_cache_filename, product, component)
-        opts['feedback'] = feedback
-
-    req = bz.create_attachment(api_server, token, bug, attachment_contents,
-                               description=description, filename=filename, comment=comment,
-                               **opts)
-    return json.load(urlopen(ui, req))
-
-
-def bzexport(ui, repo, *args, **opts):
-    """
-    Export changesets to bugzilla attachments.
-
-    The -e option may be used to bring up an editor that will allow editing all
-    fields of the attachment and bug (if creating one).
-
-    The --new option may be used to create a new bug rather than using an
-    existing bug. See the newbug command for details.
-
-    The -u (--update) option is equivalent to setting both 'update-patch'
-    and 'rename-patch' to True in the [bzexport] section of your config file.
-    """
-    auth, api_server, bugzilla = bugzilla_info(ui, opts.get('ffprofile'))
-
-    rev, bug = infer_arguments(ui, repo, args, opts)
-
-    if not opts['new']:
-        for o in ('cc', 'depends', 'blocks'):
-            if opts[o]:
-                ui.write("Warning: ignoring --%s option when not creating a bug\n" % o)
-
-    contents = StringIO()
-    diffopts = patch.diffopts(ui, opts)
-    context = ui.config("bzexport", "unified", None)
-    if context:
-        diffopts.context = int(context)
-    if hasattr(cmdutil, "export"):
-        cmdutil.export(repo, [rev], fp=contents, opts=diffopts)
-    else:
-        # Support older hg versions
-        patch.export(repo, [rev], fp=contents, opts=diffopts)
-
-    # Just always use the rev name as the patch name. Doesn't matter much,
-    # unless you want to avoid obsoleting existing patches when uploading a
-    # version that doesn't include whitespace changes.
-    filename = rev
-    if opts['ignore_all_space']:
-        filename += "_ws"
-
-    patch_comment = None
-    reviewers = []
-    desc = opts['description'] or repo[rev].description().decode('utf-8')
-    if not desc or desc.startswith('[mq]'):
-        desc = '<required>'
-    else:
-        # Lightly reformat changeset messages into attachment descriptions.
-        # First, strip off a "bug NNN" or "b=NNN" in the first line, but save
-        # it in case a bug number was not provided.
-        bzexport.newbug = None
-
-        def grab_bug(m):
-            bzexport.newbug = m.group(2)
-            return ''
-
-        parts = desc.split('\n', 1)
-        parts[0] = bug_re.sub(grab_bug, parts[0], 1)
-        desc = ''.join(parts)
-        if not bzexport.newbug:
-            # Try to find it in the original revision description, if
-            # it wasn't found in desc.
-            orig_desc = repo[rev].description().decode('utf-8')
-            bug_re.sub(grab_bug, orig_desc.split('\n', 1)[0], 1)
-        if bzexport.newbug:
-            if bug and bug != bzexport.newbug:
-                ui.warn("Warning: Bug number %s from commandline doesn't match "
-                        "bug number %s from changeset description\n"
-                        % (bug, bzexport.newbug))
-            else:
-                bug = bzexport.newbug
-
-        # Next strip any remaining leading separator with whitespace,
-        # if the original was something like "bug NNN - "
-        desc = desc.lstrip()
-        if desc[0] in ['-', ':', '.']:
-            desc = desc[1:].lstrip()
-
-        # Next, just take the first line in case. If there is more than one
-        # line, use it as a comment.
-        m = re.match(r'([^\n]*)\n+(.*)', desc, re.DOTALL)
-        if m:
-            desc = m.group(1)
-            patch_comment = m.group(2)
-
-        # Next strip off review and approval annotations, grabbing the
-        # reviewers from the patch comments only if -r auto was given
-        def grab_reviewer(m):
-            if opts['review'] == 'auto':
-                reviewers.append(m.group(1))
-            return ''
-        desc = review_re.sub(grab_reviewer, desc).rstrip()
-        if len(reviewers) > 0:
-            opts['review'] = ''
-
-    attachment_comment = opts['comment']
-    bug_comment = opts['bug_description']
-
-    if not attachment_comment:
-        # New bugs get first shot at the patch comment
-        if not opts['new'] or bug_comment:
-            attachment_comment = patch_comment
-
-    if not bug_comment and opts['new']:
-        bug_comment = patch_comment
-
-    if opts["review"]:
-        search_strings = opts["review"].split(",")
-        valid_users = validate_users(ui, api_server, auth, search_strings, multi_user_prompt, 'reviewer')
-        reviewers = select_users(valid_users, search_strings)
-    elif len(reviewers) > 0:
-        # Pulled reviewers out of commit message
-        valid_users = validate_users(ui, api_server, auth, reviewers, multi_user_prompt, 'reviewer')
-        reviewers = select_users(valid_users, search_strings)
-
-    if reviewers is None:
-        raise util.Abort(_("Invalid reviewers"))
-
-    feedback = []
-    if opts["feedback"]:
-        search_strings = opts["feedback"].split(",")
-        valid_users = validate_users(ui, api_server, auth, search_strings, multi_user_prompt, 'feedback from')
-        feedback = select_users(valid_users, search_strings)
-
-    values = { 'BUGNUM': bug,
-               'ATTACHMENT_FILENAME': filename,
-               'ATTACHMENT_DESCRIPTION': desc,
-               'ATTACHCOMMENT': attachment_comment,
-               'REVIEWERS': reviewers,
-               'FEEDBACK': feedback,
-               }
-
-    cc = []
-    depends = opts["depends"].split(",")
-    blocks = opts["blocks"].split(",")
-    if opts['new']:
-        if opts["cc"]:
-            search_strings = opts["cc"].split(",")
-            valid_users = validate_users(ui, api_server, auth, search_strings, multi_user_prompt, 'CC')
-            cc = select_users(valid_users, search_strings)
-
-        values['BUGTITLE'] = opts['title'] or desc
-        values['PRODUCT'] = opts.get('product', '') or ui.config("bzexport", "product", '<choose-from-menu>')
-        values['COMPONENT'] = opts.get('component', '') or ui.config("bzexport", "component", '<choose-from-menu>')
-        values['PRODVERSION'] = opts.get('prodversion', '') or ui.config("bzexport", "prodversion", '<default>')
-        values['BUGCOMMENT0'] = bug_comment
-        values['CC'] = cc
-        values['BLOCKS'] = blocks
-        values['DEPENDS'] = depends
-
-    values = fill_values(values, ui, api_server, finalize=False)
+ERROR = 'The bzexport extension has switched repositories and \
+now lives in the repository at \
+https://hg.mozilla.org/hgcustom/version-control-tools/. Please \
+clone this repository and update your hgrc to point to the new \
+location. If you use mach to build Firefox, run \
+`mach mercurial-setup` to do this for you.'
 
-    if opts['edit']:
-        if opts['new']:
-            values = edit_form(ui, repo, values, 'new_both_template')
-        else:
-            values = edit_form(ui, repo, values, 'existing_bug_template')
-            bug = values['BUGNUM']
-
-        search_strings = []
-        for key in ('REVIEWERS', 'CC', 'FEEDBACK'):
-            # TODO: Handle <choose-from-menu>
-            search_strings.extend(values.get(key, []))
-        users = validate_users(ui, api_server, auth, search_strings, multi_user_prompt, 'reviewer')
-        if users is None:
-            raise util.Abort("Invalid users")
-
-        if 'REVIEWERS' in values:  # Always true
-            reviewers = select_users(users, values['REVIEWERS'])
-        if 'CC' in values:         # Only when opts['new']
-            cc = select_users(users, values['CC'])
-        if 'BLOCKS' in values:     # Only when opts['new']
-            blocks = values['BLOCKS']
-        if 'DEPENDS' in values:    # Only when opts['new'] 
-           depends = values['DEPENDS']
-        if 'FEEDBACK' in values:   # Always true
-            feedback = select_users(users, values['FEEDBACK'])
-        if 'ATTACHMENT_FILENAME' in values:
-            filename = values['ATTACHMENT_FILENAME']
-
-    values = fill_values(values, ui, api_server, finalize=True)
-
-    if opts["new"]:
-        if bug is not None:
-            raise util.Abort("Bug %s given but creation of new bug requested!" % bug)
-
-        if opts['interactive'] and ui.prompt(_("Create bug in '%s' :: '%s' (y/n)?") % (values['PRODUCT'], values['COMPONENT'])) != 'y':
-            ui.write(_("Exiting without creating bug\n"))
-            return
-
-        try:
-            create_opts = {}
-            if not opts['no_take_bug']:
-                create_opts['assign_to'] = auth.username(api_server)
-            req = bz.create_bug(api_server, auth,
-                                product=values['PRODUCT'],
-                                component=values['COMPONENT'],
-                                version=values['PRODVERSION'],
-                                title=values['BUGTITLE'],
-                                description=values['BUGCOMMENT0'],
-                                cc=cc,
-                                depends=depends,
-                                blocks=blocks,
-                                **create_opts)
-            result = json.load(urlopen(ui, req))
-            bug = result['id']
-            ui.write("Created bug %s at %s\n" % (bug, bugzilla + "show_bug.cgi?id=" + bug))
-        except Exception, e:
-            raise util.Abort(_("Error creating bug: %s\n" % str(e)))
-    else:
-        if bug is None:
-            raise util.Abort(_("No bug number specified and no bug number "
-                               "listed in changeset message!"))
-
-    if len(reviewers) > 0:
-        for reviewer in reviewers:
-            ui.write("Requesting review from " + reviewer + "\n")
-    if len(cc) > 0:
-        for user in cc:
-            ui.write("CC'ing %s\n" % user)
-    if len(feedback) > 0:
-        for user in feedback:
-            ui.write("Requesting feedback from %s\n" % user)
-
-    if not opts['no_update']:
-        if opts['update']:
-            update = True
-        elif opts['new']:
-            update = ui.configbool("bzexport", "update-patch", True)
-        else:
-            update = ui.configbool("bzexport", "update-patch", False)
-
-        if opts['update']:
-            rename = opts['update']
-        else:
-            rename = ui.configbool("bzexport", "rename-patch", False)
-
-        newname = update_patch(ui, repo, rev, bug, update, rename, opts['interactive'])
-        if filename == rev:
-            filename = newname
-
-    if opts['interactive'] and ui.prompt(_("Attach patch (y/n)?")) != 'y':
-        ui.write(_("Exiting without creating attachment\n"))
-        return
-
-    extra_args = {}
-    if feedback:
-        extra_args['feedback'] = feedback
-
-    if reviewers:
-        extra_args['reviewers'] = reviewers
-
-    if feedback or reviewers:
-        # Need product and component to get the right flag id
-        if 'PRODUCT' in values and 'COMPONENT' in values:
-            extra_args['product'] = values['PRODUCT']
-            extra_args['component'] = values['COMPONENT']
-        else:
-            buginfo = json.load(urlopen(ui, bz.get_bug(api_server, auth, bug, include_fields=['product', 'component'])))
-            extra_args['product'] = buginfo['product']
-            extra_args['component'] = buginfo['component']
-
-    description = values['ATTACHMENT_DESCRIPTION']
-    if opts['number']:
-        description = "Patch " + opts['number'] + " - " + description
-
-    result = create_attachment(ui, api_server, auth,
-                               bug, BINARY_CACHE_FILENAME, contents.getvalue(),
-                               filename=filename,
-                               description=description,
-                               comment=values['ATTACHCOMMENT'],
-                               **extra_args)
-    attachment_url = urlparse.urljoin(bugzilla,
-                                      "attachment.cgi?id=" + result["id"] + "&action=edit")
-    print "%s uploaded as %s" % (rev, attachment_url)
-
-    def pre_obsolete(**kwargs):
-        if not opts['interactive']:
-            return True
-        url, filename, description = [ kwargs[k] for k in ['url', 'filename', 'description' ] ]
-        return ui.prompt(_("Obsolete patch %s (%s) - %s (y/n)?") % (url, filename, description)) == 'y'
-
-    obsolete_old_patches(ui, api_server, auth, bug, bugzilla, filename, result['id'], pre_hook=pre_obsolete)
+from mercurial.util import Abort
 
-    # If attaching to an existing bug (and not suppressed on the command line), take the bug
-    if not opts['new'] and not opts['no_take_bug']:
-        req = bz.get_bug(api_server, auth, bug, include_fields=[ 'assigned_to', 'status' ])
-        result = json.load(urlopen(ui, req))
-        taker = auth.username(api_server)
-        if result['assigned_to']['name'] != taker:
-            result['assigned_to'] = { 'name': taker }
-            if result['status'] != 'RESOLVED':
-                result['status'] = 'ASSIGNED'
-            req = bz.update_bug(api_server, auth, result)
-            try:
-                result = json.load(urlopen(ui, req))
-                assert result.get('ok', None)
-            except Exception, e:
-                raise util.Abort(_("Error when updating bug %s: %s") % (bug, result))
-
-
-def newbug(ui, repo, *args, **opts):
-    """
-    Create a new bug in bugzilla
-
-    A menu will be displayed for the product and component unless a default has
-    been set in the [bzexport] section of the config file (keys are 'product'
-    and 'component'), or if something has been specified on the command line.
-
-    The -e option brings up an editor that will allow editing all handled
-    fields of the bug.
-
-    The product and/or component given on the command line or the edited form
-    may be case-insensitive substrings rather than exact matches of valid
-    values. Ambiguous matches will be resolved with a menu. The -C
-    (--component) option may be used to set both the product and component by
-    separating them with a double colon ('::'), though usually just giving the
-    component should be sufficient.
-    """
-    auth, api_server, bugzilla = bugzilla_info(ui, opts.get('ffprofile'))
-
-    if args:
-        args = list(args)
-
-    if args and not opts['title']:
-        opts['title'] = args.pop(0)
-    if args and not opts['comment']:
-        opts['comment'] = args.pop(0)
-    if args:
-        raise util.Abort(_("Too many arguments to newbug command (only title and comment may be given)"))
-
-    bug_comment = opts['comment'] or '<required>'
-
-    values = { 'BUGTITLE': opts['title'] or '<required>',
-               'PRODUCT': opts.get('product', '') or ui.config("bzexport", "product", '<choose-from-menu>'),
-               'COMPONENT': opts.get('component', '') or ui.config("bzexport", "component", '<choose-from-menu>'),
-               'PRODVERSION': opts.get('prodversion', '') or ui.config("bzexport", "prodversion", '<default>'),
-               'BUGCOMMENT0': bug_comment,
-               'CC': [],
-               'DEPENDS': opts["depends"].split(","),
-               'BLOCKS': opts["blocks"].split(","),
-               }
-
-    fill_values(values, ui, api_server, finalize=False)
-
-    if opts['edit']:
-        values = edit_form(ui, repo, values, 'new_bug_template')
-
-    fill_values(values, ui, api_server, finalize=True)
-
-    cc = validate_users(ui, api_server, auth, values['CC'], multi_user_prompt, 'reviewer')
-    if cc is None:
-        raise util.Abort("Invalid users")
-    cc = select_users(cc, values['CC'])
-
-    if opts['interactive'] and ui.prompt(_("Create bug in '%s' :: '%s' (y/n)?") % (values['PRODUCT'], values['COMPONENT'])) != 'y':
-        ui.write(_("Exiting without creating bug\n"))
-        return
-
-    create_opts = {}
-    if opts['take_bug']:
-        create_opts['assign_to'] = auth.username(api_server)
-
-    req = bz.create_bug(api_server, auth,
-                        product=values['PRODUCT'],
-                        component=values['COMPONENT'],
-                        version=values['PRODVERSION'],
-                        title=values['BUGTITLE'],
-                        description=values['BUGCOMMENT0'],
-                        cc=cc,
-                        depends=values['DEPENDS'],
-                        blocks=values['BLOCKS'],
-                        **create_opts)
-    result = json.load(urlopen(ui, req))
-    bug = result['id']
-    ui.write("Created bug %s at %s\n" % (bug, bugzilla + "show_bug.cgi?id=" + bug))
-
-newbug_opts = [
-    ('t', 'title', '',
-     'New bug title'),
-    ('', 'product', '',
-     'New bug product'),
-    ('C', 'component', '',
-     'New bug component'),
-    ('', 'prodversion', '',
-     'New bug product version'),
-    ('', 'cc', '',
-     'List of users to CC on the bug (comma-separated search strings)'),
-    ('D', 'depends', '',
-     'Make new bug depend on given bug number'),
-    ('B', 'blocks', '',
-     'Comma-separated list of bugs that should depend on this one'),
-    ('P', 'ffprofile', '',
-     'Name of Firefox profile to pull bugzilla cookies from'),
-]
-
-cmdtable = {
-    'bzexport':
-        (bzexport,
-         [('d', 'description', '', 'Bugzilla attachment description'),
-          ('c', 'comment', '', 'Comment to add with the attachment'),
-          ('e', 'edit', False,
-           'Open a text editor to modify bug fields'),
-          ('r', 'review', '',
-           'List of users to request review from (comma-separated search strings), or "auto" to parse the reviewers out of the patch comment'),
-          ('F', 'feedback', '',
-           'List of users to request feedback from (comma-separated search strings)'),
-          ('', 'cc', '',
-           'List of users to CC on the bug (comma-separated search strings)'),
-          ('', 'new', False,
-           'Create a new bug'),
-          ('i', 'interactive', False,
-           'Interactive -- request confirmation before any permanent action'),
-          ('', 'no-take-bug', False,
-           'Do not assign bug to myself'),
-          ('', 'bug-description', '',
-           'New bug description (aka comment 0)'),
-          ('u', 'update', None,
-           'Update patch name and description to include bug number (only valid with --new)'),
-          ('', 'no-update', None,
-           'Suppress patch name/description update (override config file)'),
-          ('', 'number', '',
-           'When posting, prefix the patch description with "Patch <number> - "'),
-          # The following option is passed through directly to patch.diffopts
-          ('w', 'ignore_all_space', False,
-           'Generate a diff that ignores whitespace changes'),
-          ('f', 'force', False,
-           'Proceed even if the working directory contains changes'),
-          ] + newbug_opts,
-         _('hg bzexport [options] [REV] [BUG]')),
-
-    'newbug':
-        (newbug,
-         [('c', 'comment', '', 'Comment to add with the bug'),
-          ('e', 'edit', False,
-           'Open a text editor to modify bug fields'),
-          ('i', 'interactive', False,
-           'Interactive -- request confirmation before any permanent action'),
-          ('f', 'force', False,
-           'Proceed even if the working directory contains changes'),
-          ('', 'take-bug', False,
-           'Assign bug to myself'),
-          ] + newbug_opts,
-         _('hg newbug [-e] [[-t] TITLE] [[-c] COMMENT]' )),
-}
+def extsetup(ui):
+    raise Abort(ERROR)
deleted file mode 100644
--- a/bz.py
+++ /dev/null
@@ -1,143 +0,0 @@
-# Copyright (C) 2010 Mozilla Foundation
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-
-import base64
-import urllib
-import urllib2
-import urlparse
-import json
-
-JSON_HEADERS = {"Accept": "application/json",
-                "Content-Type": "application/json"}
-
-def make_url(api_server, auth, command, args = {}):
-    url = urlparse.urljoin(api_server, command)
-    if auth is None and not args.keys():
-        return url
-    params = [ auth.auth() ] if auth else []
-    params.extend([ k + "=" + urllib.quote(str(v)) for k,v in args.iteritems() ])
-    return url + "?" + '&'.join(params)
-
-def create_bug(api_server, token, product, component, version, title, description,
-               assign_to=None, cc=[], depends=[], blocks=[]):
-    """
-    Create a bugzilla bug using BzAPI.
-    """
-    url = make_url(api_server, token, 'bug')
-    json_data = {'product'  : product,
-                 'component': component,
-                 'summary'  : title,
-                 'version'  : version,
-                 'comments' : [{ 'text': description }],
-                 'op_sys'   : 'All',
-                 'platform' : 'All',
-                 'depends_on' : depends,
-                 'blocks'   : blocks,
-                 'cc'       : [ {'name': u} for u in cc ],
-                 }
-
-    if assign_to:
-        json_data['assigned_to'] = {"name": assign_to}
-        json_data['status'] = 'ASSIGNED'
-
-    return urllib2.Request(url, json.dumps(json_data), JSON_HEADERS)
-
-def create_attachment(api_server, token, bug, contents,
-                      description="attachment",
-                      filename="attachment", comment="",
-                      reviewers=None, review_flag_id=None,
-                      feedback=None, feedback_flag_id=None):
-    """
-    Post an attachment to a bugzilla bug using BzAPI.
-    """
-    attachment = base64.b64encode(contents)
-    url = make_url(api_server, token, 'bug/%s/attachment' % bug)
-
-    json_data = {'data': attachment,
-                 'encoding': 'base64',
-                 'file_name': filename,
-                 'description': description,
-                 'is_patch': True,
-                 'content_type': 'text/plain',
-                 'flags': []}
-    if reviewers:
-        flags = []
-        assert review_flag_id
-        flags.append({"name": "review",
-                      "requestee": {"name": ", ".join(reviewers)},
-                      "status": "?",
-                      "type_id": review_flag_id})
-        json_data["flags"].extend(flags)
-
-    if feedback:
-        flags = []
-        assert feedback_flag_id
-        flags.append({"name": "feedback",
-                      "requestee": {"name": ", ".join(feedback)},
-                      "status": "?",
-                      "type_id": feedback_flag_id})
-        json_data["flags"].extend(flags)
-
-    if comment:
-        json_data["comments"] = [{'text': comment}]
-
-    attachment_json = json.dumps(json_data)
-    return urllib2.Request(url, attachment_json, JSON_HEADERS)
-
-class PUTRequest(urllib2.Request):
-    def get_method(self):
-        return "PUT"
-
-def get_attachments(api_server, token, bug):
-    url = make_url(api_server, token, 'bug/%s/attachment' % bug)
-    return urllib2.Request(url, None, JSON_HEADERS)
-
-def obsolete_attachment(api_server, token, attachment):
-    url = make_url(api_server, token, 'attachment/%s' % str(attachment['id']))
-
-    info = attachment.copy()
-    info["is_obsolete"] = True
-    return PUTRequest(url, json.dumps(info), JSON_HEADERS)
-
-def find_users(api_server, token, search_string):
-    url = make_url(api_server, token, 'user', { 'match': search_string })
-    return urllib2.Request(url, None, JSON_HEADERS)
-
-def get_user(api_server, bzauth):
-    url = make_url(api_server, bzauth, 'user/%s' % bzauth._userid)
-    return urllib2.Request(url, None, JSON_HEADERS)
-
-def get_configuration(api_server):
-    url = make_url(api_server, None, 'configuration', { 'cached_ok': 1 })
-    return urllib2.Request(url, None, JSON_HEADERS)
-
-def get_bug(api_server, token, bug, **opts):
-    """
-    Retrieve an existing bug
-    """
-    args = {}
-    if 'include_fields' in opts:
-        args['include_fields'] = ",".join(set(opts['include_fields'] + ['id','ref','token','last_change_time','update_token']))
-    url = make_url(api_server, token, 'bug/%s' % str(bug), args)
-    return urllib2.Request(url, None, JSON_HEADERS)
-
-def update_bug(api_server, token, bugdata):
-    """
-    Update an existing bug. Must pass in an existing bug as a data structure.
-    Mid-air collisions are possible.
-    """
-    url = make_url(api_server, token, 'bug/%s' % str(bugdata['id']))
-    return PUTRequest(url, json.dumps(bugdata), JSON_HEADERS)
deleted file mode 100644
--- a/bzauth.py
+++ /dev/null
@@ -1,316 +0,0 @@
-# Copyright (C) 2010 Mozilla Foundation
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-
-import os
-import platform
-import time
-import tempfile
-import shutil
-import urlparse
-import urllib
-import urllib2
-import json
-from mercurial import config, util
-from mercurial.i18n import _
-try:
-  import cPickle as pickle
-except:
-  import pickle
-import bz
-
-global_cache = None
-
-class bzAuth:
-    """
-    A helper class to abstract away authentication details.  There are two
-    allowable types of authentication: userid/cookie and username/password.
-    We encapsulate it here so that functions that interact with bugzilla
-    need only call the 'auth' method on the token to get a correct URL.
-    """
-    typeCookie = 1
-    typeExplicit = 2
-    def __init__(self, userid=None, cookie=None, username=None, password=None):
-        assert (userid and cookie) or (username and password)
-        assert not ((userid or cookie) and (username or password))
-        if userid:
-            self._type = self.typeCookie
-            self._userid = userid
-            self._cookie = cookie
-            self._username = None
-        else:
-            self._type = self.typeExplicit
-            self._username = username
-            self._password = password
-
-    def auth(self):
-        if self._type == self.typeCookie:
-            return "userid=%s&cookie=%s" % (self._userid, self._cookie)
-        else:
-            return "username=%s&password=%s" % (urllib.quote(self._username), urllib.quote(self._password))
-
-    def username(self, api_server):
-        # This returns and caches the email-address-like username of the user's ID
-        if self._type == self.typeCookie and self._username is None:
-            return get_username(api_server, self)
-        else:
-            return self._username
-
-def get_global_path(filename):
-    path = None
-    if platform.system() == "Windows":
-        CSIDL_PERSONAL = 5
-        path = win_get_folder_path(CSIDL_PERSONAL)
-    else:
-        path = os.path.expanduser("~")
-    if path:
-        path = os.path.join(path, filename)
-    return path
-
-def store_global_cache(filename):
-    fp = open(get_global_path(filename), "wb")
-    pickle.dump(global_cache, fp)
-    fp.close()
-
-def load_global_cache(ui, api_server, filename):
-    global global_cache
-    if global_cache:
-      return global_cache
-
-    cache_file = get_global_path(filename)
-
-    try:
-        fp = open(cache_file, "rb");
-        global_cache = pickle.load(fp)
-    except IOError, e:
-        global_cache = { api_server: { 'real_names': {} } }
-    except Exception, e:
-        raise util.Abort("Error loading user cache: " + str(e))
-
-    return global_cache
-
-def store_user_cache(cache, filename):
-    user_cache = get_global_path(filename)
-    fp = open(user_cache, "wb")
-    for section in cache.sections():
-        fp.write("[" + section + "]\n")
-        for (user, name) in cache.items(section):
-            fp.write(user + " = " + name + "\n")
-        fp.write("\n")
-    fp.close()
-
-def load_user_cache(ui, api_server, filename):
-    user_cache = get_global_path(filename)
-
-    # Ensure that the cache exists before attempting to use it
-    fp = open(user_cache, "a");
-    fp.close()
-
-    c = config.config()
-    c.read(user_cache)
-    return c
-
-def load_configuration(ui, api_server, filename):
-    global_cache = load_global_cache(ui, api_server, filename)
-    cache = {}
-    try:
-        cache = global_cache[api_server]
-    except:
-        global_cache[api_server] = cache
-    now = time.time()
-    if cache.get('configuration', None) and now - cache['configuration_timestamp'] < 24*60*60*7:
-        return cache['configuration']
-
-    ui.write("Refreshing configuration cache for " + api_server + "\n")
-    try:
-        cache['configuration'] = json.load(urllib2.urlopen(bz.get_configuration(api_server), timeout=30))
-    except Exception, e:
-        raise util.Abort("Error loading bugzilla configuration: " + str(e))
-
-    cache['configuration_timestamp'] = now
-    store_global_cache(filename)
-    return cache['configuration']
-
-def win_get_folder_path(folder):
-    # Use SHGetFolderPath
-    import ctypes
-    SHGetFolderPath = ctypes.windll.shell32.SHGetFolderPathW
-    SHGetFolderPath.argtypes = [ctypes.c_void_p,
-                                ctypes.c_int,
-                                ctypes.c_void_p,
-                                ctypes.c_int32,
-                                ctypes.c_wchar_p]
-    path_buf = ctypes.create_unicode_buffer(1024)
-    if SHGetFolderPath(0, folder, 0, 0, path_buf) != 0:
-        return None
-
-    return path_buf.value
-
-def find_profile(ui, profileName):
-    """
-    Find the default Firefox profile location. Returns None
-    if no profile could be located.
-
-    """
-    path = None
-    if platform.system() == "Darwin":
-        # Use FSFindFolder
-        from Carbon import Folder, Folders
-        pathref = Folder.FSFindFolder(Folders.kUserDomain,
-                                      Folders.kApplicationSupportFolderType,
-                                      Folders.kDontCreateFolder)
-        basepath = pathref.FSRefMakePath()
-        path = os.path.join(basepath, "Firefox")
-    elif platform.system() == "Windows":
-        CSIDL_APPDATA = 26
-        path = win_get_folder_path(CSIDL_APPDATA)
-        if path:
-            path = os.path.join(path, "Mozilla", "Firefox")
-    else: # Assume POSIX
-        # Pretty simple in comparison, eh?
-        path = os.path.expanduser("~/.mozilla/firefox")
-    if path is None:
-        raise util.Abort(_("Could not find a Firefox profile"))
-
-    profileini = os.path.join(path, "profiles.ini")
-    c = config.config()
-    c.read(profileini)
-
-    if profileName:
-        sections = [ s for s in c.sections() if profileName in [ s, c.get(s, "Name", None) ] ]
-    else:
-        sections = [ s for s in c.sections() if c.get(s, "Default", None) ]
-        if len(sections) == 0:
-            sections = c.sections()
-
-    sections = [ s for s in sections if c.get(s, "Path", None) is not None ]
-    if len(sections) == 0:
-        raise util.Abort(_("Could not find a Firefox profile"))
-
-    section = sections.pop(0)
-    profile = c[section].get("Path")
-    if c.get(section, "IsRelative", "0") == "1":
-        profile = os.path.join(path, profile)
-    return profile
-
-# Choose the cookie to use based on how much of its path matches the URL.
-# Useful if you happen to have cookies for both
-# https://landfill.bugzilla.org/bzapi_sandbox/ and
-# https://landfill.bugzilla.org/bugzilla-3.6-branch/, for example.
-def matching_path_len(cookie_path, url_path):
-    return len(cookie_path) if url_path.startswith(cookie_path) else 0
-
-def get_cookies_from_profile(ui, profile, bugzilla):
-    """
-    Given a Firefox profile, try to find the login cookies
-    for the given bugzilla URL.
-
-    """
-    try:
-        import sqlite3
-    except:
-        ui.write("Import of sqlite3 failed - this is expected if on Windows (see README).\n")
-        return None, None
-
-    cookies = os.path.join(profile, "cookies.sqlite")
-    if not os.path.exists(cookies):
-        return None, None
-
-    # Get bugzilla hostname
-    host = urlparse.urlparse(bugzilla).hostname
-    path = urlparse.urlparse(bugzilla).path
-
-    # Firefox locks this file, so if we can't open it (browser is running)
-    # then copy it somewhere else and try to open it.
-    tempdir = None
-    try:
-        tempdir = tempfile.mkdtemp()
-        tempcookies = os.path.join(tempdir, "cookies.sqlite")
-        shutil.copyfile(cookies, tempcookies)
-        # Firefox uses sqlite's WAL feature, which bumps the sqlite
-        # version number. Older sqlites will refuse to open the db,
-        # but the actual format is the same (just the journalling is different).
-        # Patch the file to give it an older version number so we can open it.
-        with open(tempcookies, 'r+b') as f:
-            f.seek(18, 0)
-            f.write("\x01\x01")
-        conn = sqlite3.connect(tempcookies)
-        logins = conn.execute("select value, path from moz_cookies where name = 'Bugzilla_login' and (host = ? or host = ?)", (host, "." + host)).fetchall()
-        row = sorted(logins, key = lambda row: -matching_path_len(row[1], path))[0]
-        login = row[0]
-        cookie = conn.execute("select value from moz_cookies "
-                              "where name = 'Bugzilla_logincookie' "
-                              " and (host = ? or host= ?) "
-                              " and path = ?",
-                              (host, "." + host, row[1])).fetchone()[0]
-        ui.debug("host=%s path=%s login=%s cookie=%s\n" % (host, row[1], login, cookie))
-        if isinstance(login, unicode):
-            login = login.encode("utf-8")
-            cookie = cookie.encode("utf-8")
-        return login, cookie
-
-    except Exception, e:
-        if not isinstance(e, IndexError):
-            ui.write_err(_("Failed to get bugzilla login cookies from "
-                           "Firefox profile at %s: %s\n") % (profile, str(e)))
-        pass
-
-    finally:
-        if tempdir:
-            shutil.rmtree(tempdir)
-
-def get_auth(ui, bugzilla, profile, username, password):
-    if not password:
-        # If the password wasn't specified in the hgrc, then see if the
-        # credentials can be retrieved from Bugzilla cookies
-        userid = None
-        cookie = None
-        profile = find_profile(ui, profile)
-        if profile:
-            try:
-                userid, cookie = get_cookies_from_profile(ui, profile, bugzilla)
-            except Exception, e:
-                print("Warning: " + str(e))
-                pass
-        if userid and cookie:
-            return bzAuth(userid=userid, cookie=cookie)
-        ui.write("Credentials not found in .hgrc & unable to retrieve bugzilla login cookies.\n")
-
-    if not username:
-        username = ui.prompt("Enter username for %s:" % bugzilla)
-    if not password:
-        password = ui.getpass("Enter password for %s: " % username)
-
-    return bzAuth(username=username, password=password)
-
-def get_username(api_server, token):
-    req = bz.get_user(api_server, token)
-    try:
-        user = json.load(urllib2.urlopen(req, timeout=30))
-        return user["name"]
-    except urllib2.HTTPError, e:
-        msg = ''
-        try:
-            err = json.load(e)
-            msg = err['message']
-        except:
-            msg = e
-            pass
-
-        if msg:
-            raise util.Abort('Unable to get username: %s\n' % msg)
-        raise
-    except Exception, e:
-        raise util.Abort(_("Unable to get username: %s") % str(e))
deleted file mode 100644
--- a/bzexport.py
+++ /dev/null
@@ -1,7 +0,0 @@
-import sys
-import os.path
-from mercurial import extensions
-
-# Redirect to __init__.py
-myfile = sys.modules['hgext_bzexport'].__file__
-extensions.loadpath(os.path.dirname(myfile), 'hgext.bzexport')