Patchwork D1722: githelp: vendor Facebook authored extension

login
register
mail settings
Submitter phabricator
Date Jan. 18, 2018, 7:58 p.m.
Message ID <7e1d83258a86fae865197ffc6bdbad19@localhost.localdomain>
Download mbox | patch
Permalink /patch/26921/
State Not Applicable
Headers show

Comments

phabricator - Jan. 18, 2018, 7:58 p.m.
This revision was automatically updated to reflect the committed changes.
Closed by commit rHG113281667205: githelp: vendor Facebook authored extension (authored by indygreg, committed by ).

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D1722?vs=4541&id=4918

REVISION DETAIL
  https://phab.mercurial-scm.org/D1722

AFFECTED FILES
  hgext/githelp.py
  tests/test-githelp.t
  tests/test-help.t

CHANGE DETAILS




To: indygreg, #hg-reviewers, durin42, pulkit
Cc: mercurial-devel

Patch

diff --git a/tests/test-help.t b/tests/test-help.t
--- a/tests/test-help.t
+++ b/tests/test-help.t
@@ -261,6 +261,7 @@ 
        eol           automatically manage newlines in repository files
        extdiff       command to allow external programs to compare revisions
        factotum      http authentication with factotum
+       githelp       try mapping git commands to Mercurial commands
        gpg           commands to sign and verify changesets
        hgk           browse the repository in a graphical way
        highlight     syntax highlighting for hgweb (requires Pygments)
diff --git a/tests/test-githelp.t b/tests/test-githelp.t
new file mode 100644
--- /dev/null
+++ b/tests/test-githelp.t
@@ -0,0 +1,258 @@ 
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > githelp =
+  > EOF
+
+  $ hg init repo
+  $ cd repo
+  $ echo foo > test_file
+  $ mkdir dir
+  $ echo foo > dir/file
+  $ echo foo > removed_file
+  $ echo foo > deleted_file
+  $ hg add -q .
+  $ hg commit -m 'bar'
+  $ hg bookmark both
+  $ touch both
+  $ touch untracked_file
+  $ hg remove removed_file
+  $ rm deleted_file
+
+githelp on a single command should succeed
+  $ hg githelp -- commit
+  hg commit
+  $ hg githelp -- git commit
+  hg commit
+
+githelp should fail nicely if we don't give it arguments
+  $ hg githelp
+  abort: missing git command - usage: hg githelp -- <git command>
+  [255]
+  $ hg githelp -- git
+  abort: missing git command - usage: hg githelp -- <git command>
+  [255]
+
+githelp on a command with options should succeed
+  $ hg githelp -- commit -pm "abc"
+  hg record -m 'abc'
+
+githelp on a command with standalone unrecognized option should succeed with warning
+  $ hg githelp -- commit -p -v
+  ignoring unknown option -v
+  hg record
+
+githelp on a command with unrecognized option packed with other options should fail with error
+  $ hg githelp -- commit -pv
+  abort: unknown option v packed with other options
+  Please try passing the option as it's own flag: -v
+  [255]
+
+githelp for git rebase --skip
+  $ hg githelp -- git rebase --skip
+  hg revert --all -r .
+  hg rebase --continue
+
+githelp for git commit --amend (hg commit --amend pulls up an editor)
+  $ hg githelp -- commit --amend
+  hg commit --amend
+
+githelp for git commit --amend --no-edit (hg amend does not pull up an editor)
+  $ hg githelp -- commit --amend --no-edit
+  hg amend
+
+githelp for git checkout -- . (checking out a directory)
+  $ hg githelp -- checkout -- .
+  note: use --no-backup to avoid creating .orig files
+  
+  hg revert .
+
+githelp for git checkout "HEAD^" (should still work to pass a rev)
+  $ hg githelp -- checkout "HEAD^"
+  hg update .^
+
+githelp checkout: args after -- should be treated as paths no matter what
+  $ hg githelp -- checkout -- HEAD
+  note: use --no-backup to avoid creating .orig files
+  
+  hg revert HEAD
+
+githelp for git checkout with rev and path
+  $ hg githelp -- checkout "HEAD^" -- file.txt
+  note: use --no-backup to avoid creating .orig files
+  
+  hg revert -r .^ file.txt
+
+githelp for git with rev and path, without separator
+  $ hg githelp -- checkout "HEAD^" file.txt
+  note: use --no-backup to avoid creating .orig files
+  
+  hg revert -r .^ file.txt
+
+githelp for checkout with a file as first argument
+  $ hg githelp -- checkout test_file
+  note: use --no-backup to avoid creating .orig files
+  
+  hg revert test_file
+
+githelp for checkout with a removed file as first argument
+  $ hg githelp -- checkout removed_file
+  note: use --no-backup to avoid creating .orig files
+  
+  hg revert removed_file
+
+githelp for checkout with a deleted file as first argument
+  $ hg githelp -- checkout deleted_file
+  note: use --no-backup to avoid creating .orig files
+  
+  hg revert deleted_file
+
+githelp for checkout with a untracked file as first argument
+  $ hg githelp -- checkout untracked_file
+  note: use --no-backup to avoid creating .orig files
+  
+  hg revert untracked_file
+
+githelp for checkout with a directory as first argument
+  $ hg githelp -- checkout dir
+  note: use --no-backup to avoid creating .orig files
+  
+  hg revert dir
+
+githelp for checkout when not in repo root
+  $ cd dir
+  $ hg githelp -- checkout file
+  note: use --no-backup to avoid creating .orig files
+  
+  hg revert file
+
+  $ cd ..
+
+githelp for checkout with an argument that is both a file and a revision
+  $ hg githelp -- checkout both
+  hg update both
+
+githelp for checkout with the -p option
+  $ hg githelp -- git checkout -p xyz
+  hg revert -i -r xyz
+
+  $ hg githelp -- git checkout -p xyz -- abc
+  note: use --no-backup to avoid creating .orig files
+  
+  hg revert -i -r xyz abc
+
+githelp for checkout with the -f option and a rev
+  $ hg githelp -- git checkout -f xyz
+  hg update -C xyz
+  $ hg githelp -- git checkout --force xyz
+  hg update -C xyz
+
+githelp for checkout with the -f option without an arg
+  $ hg githelp -- git checkout -f
+  hg revert --all
+  $ hg githelp -- git checkout --force
+  hg revert --all
+
+githelp for grep with pattern and path
+  $ hg githelp -- grep shrubbery flib/intern/
+  hg grep shrubbery flib/intern/
+
+githelp for reset, checking ~ in git becomes ~1 in mercurial
+  $ hg githelp -- reset HEAD~
+  hg reset .~1
+  $ hg githelp -- reset "HEAD^"
+  hg reset .^
+  $ hg githelp -- reset HEAD~3
+  hg reset .~3
+
+githelp for git show --name-status
+  $ hg githelp -- git show --name-status
+  hg log --style status -r tip
+
+githelp for git show --pretty=format: --name-status
+  $ hg githelp -- git show --pretty=format: --name-status
+  hg stat --change tip
+
+githelp for show with no arguments
+  $ hg githelp -- show
+  hg show
+
+githelp for show with a path
+  $ hg githelp -- show test_file
+  hg show . test_file
+
+githelp for show with not a path:
+  $ hg githelp -- show rev
+  hg show rev
+
+githelp for show with many arguments
+  $ hg githelp -- show argone argtwo
+  hg show argone argtwo
+  $ hg githelp -- show test_file argone argtwo
+  hg show . test_file argone argtwo
+
+githelp for show with --unified options
+  $ hg githelp -- show --unified=10
+  hg show --config diff.unified=10
+  $ hg githelp -- show -U100
+  hg show --config diff.unified=100
+
+githelp for show with a path and --unified
+  $ hg githelp -- show -U20 test_file
+  hg show . test_file --config diff.unified=20
+
+githelp for stash drop without name
+  $ hg githelp -- git stash drop
+  hg shelve -d <shelve name>
+
+githelp for stash drop with name
+  $ hg githelp -- git stash drop xyz
+  hg shelve -d xyz
+
+githelp for whatchanged should show deprecated message
+  $ hg githelp -- whatchanged -p
+  This command has been deprecated in the git project, thus isn't supported by this tool.
+  
+
+githelp for git branch -m renaming
+  $ hg githelp -- git branch -m old new
+  hg bookmark -m old new
+
+When the old name is omitted, git branch -m new renames the current branch.
+  $ hg githelp -- git branch -m new
+  hg bookmark -m `hg log -T"{activebookmark}" -r .` new
+
+Branch deletion in git strips commits
+  $ hg githelp -- git branch -d
+  hg strip -B
+  $ hg githelp -- git branch -d feature
+  hg strip -B feature -B
+  $ hg githelp -- git branch --delete experiment1 experiment2
+  hg strip -B experiment1 -B experiment2 -B
+
+githelp for reuse message using the shorthand
+  $ hg githelp -- git commit -C deadbeef
+  hg commit -M deadbeef
+
+githelp for reuse message using the the long version
+  $ hg githelp -- git commit --reuse-message deadbeef
+  hg commit -M deadbeef
+
+githelp for apply with no options
+  $ hg githelp -- apply
+  hg import --no-commit
+
+githelp for apply with directory strip custom
+  $ hg githelp -- apply -p 5
+  hg import --no-commit -p 5
+
+git merge-base
+  $ hg githelp -- git merge-base --is-ancestor
+  ignoring unknown option --is-ancestor
+  NOTE: ancestors() is part of the revset language.
+  Learn more about revsets with 'hg help revsets'
+  
+  hg log -T '{node}\n' -r 'ancestor(A,B)'
+
+githelp for git blame
+  $ hg githelp -- git blame
+  hg annotate -udl
diff --git a/hgext/githelp.py b/hgext/githelp.py
new file mode 100644
--- /dev/null
+++ b/hgext/githelp.py
@@ -0,0 +1,1081 @@ 
+# githelp.py - Try to map Git commands to Mercurial equivalents.
+#
+# Copyright 2013 Facebook, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+"""try mapping git commands to Mercurial commands
+
+Tries to map a given git command to a Mercurial command:
+
+  $ hg githelp -- git checkout master
+  hg update master
+
+If an unknown command or parameter combination is detected, an error is
+produced.
+"""
+
+from __future__ import absolute_import
+
+import getopt
+import re
+
+from mercurial.i18n import _
+from mercurial import (
+    error,
+    extensions,
+    fancyopts,
+    registrar,
+    util,
+)
+
+# Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
+# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
+# be specifying the version(s) of Mercurial they are tested with, or
+# leave the attribute unspecified.
+testedwith = 'ships-with-hg-core'
+
+cmdtable = {}
+command = registrar.command(cmdtable)
+
+def convert(s):
+    if s.startswith("origin/"):
+        return s[7:]
+    if 'HEAD' in s:
+        s = s.replace('HEAD', '.')
+    # HEAD~ in git is .~1 in mercurial
+    s = re.sub('~$', '~1', s)
+    return s
+
+@command('^githelp|git', [
+    ], _('hg githelp'))
+def githelp(ui, repo, *args, **kwargs):
+    '''suggests the Mercurial equivalent of the given git command
+
+    Usage: hg githelp -- <git command>
+    '''
+
+    if len(args) == 0 or (len(args) == 1 and args[0] =='git'):
+        raise error.Abort(_('missing git command - '
+                            'usage: hg githelp -- <git command>'))
+
+    if args[0] == 'git':
+        args = args[1:]
+
+    cmd = args[0]
+    if not cmd in gitcommands:
+        raise error.Abort("error: unknown git command %s" % (cmd))
+
+    ui.pager('githelp')
+    args = args[1:]
+    return gitcommands[cmd](ui, repo, *args, **kwargs)
+
+def parseoptions(ui, cmdoptions, args):
+    cmdoptions = list(cmdoptions)
+    opts = {}
+    args = list(args)
+    while True:
+        try:
+            args = fancyopts.fancyopts(list(args), cmdoptions, opts, True)
+            break
+        except getopt.GetoptError as ex:
+            flag = None
+            if "requires argument" in ex.msg:
+                raise
+            if ('--' + ex.opt) in ex.msg:
+                flag = '--' + ex.opt
+            elif ('-' + ex.opt) in ex.msg:
+                flag = '-' + ex.opt
+            else:
+                raise error.Abort("unknown option %s" % ex.opt)
+            try:
+                args.remove(flag)
+            except Exception:
+                raise error.Abort(
+                    "unknown option {0} packed with other options\n"
+                    "Please try passing the option as it's own flag: -{0}" \
+                    .format(ex.opt))
+
+            ui.warn(_("ignoring unknown option %s\n") % flag)
+
+    args = list([convert(x) for x in args])
+    opts = dict([(k, convert(v)) if isinstance(v, str) else (k, v)
+                                 for k, v in opts.iteritems()])
+
+    return args, opts
+
+class Command(object):
+    def __init__(self, name):
+        self.name = name
+        self.args = []
+        self.opts = {}
+
+    def __str__(self):
+        cmd = "hg " + self.name
+        if self.opts:
+            for k, values in sorted(self.opts.iteritems()):
+                for v in values:
+                    if v:
+                        cmd += " %s %s" % (k, v)
+                    else:
+                        cmd += " %s" % (k,)
+        if self.args:
+            cmd += " "
+            cmd += " ".join(self.args)
+        return cmd
+
+    def append(self, value):
+        self.args.append(value)
+
+    def extend(self, values):
+        self.args.extend(values)
+
+    def __setitem__(self, key, value):
+        values = self.opts.setdefault(key, [])
+        values.append(value)
+
+    def __and__(self, other):
+        return AndCommand(self, other)
+
+class AndCommand(object):
+    def __init__(self, left, right):
+        self.left = left
+        self.right = right
+
+    def __str__(self):
+        return "%s && %s" % (self.left, self.right)
+
+    def __and__(self, other):
+        return AndCommand(self, other)
+
+def add(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('A', 'all', None, ''),
+        ('p', 'patch', None, ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    if (opts.get('patch')):
+        ui.status(_("note: hg crecord has a better UI to record changes\n"))
+        ui.status(_("note: record and crecord will commit when complete, "
+                    "as there is no staging area in mercurial\n\n"))
+        cmd = Command('record')
+    else:
+        cmd = Command("add")
+
+        if not opts.get('all'):
+            cmd.extend(args)
+        else:
+            ui.status(_("note: use hg addremove to remove files that have "
+                        "been deleted.\n\n"))
+        if not opts.get('all'):
+            cmd.extend(args)
+        else:
+            ui.status(_("note: use hg addremove to remove files that have "
+                        "been deleted.\n\n"))
+
+    ui.status((str(cmd)), "\n")
+
+def am(ui, repo, *args, **kwargs):
+    cmdoptions=[
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+    cmd = Command('mimport -m')
+    ui.status(str(cmd), "\n\n")
+    ui.status(_("note: requires the MboxExtension and the MqExtension.\n"))
+
+def apply(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('p', 'p', int, ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    cmd = Command('import --no-commit')
+    if (opts.get('p')):
+        cmd['-p'] = opts.get('p')
+    cmd.extend(args)
+
+    ui.status((str(cmd)), "\n")
+
+def bisect(ui, repo, *args, **kwargs):
+    ui.status(_("See 'hg help bisect' for how to use bisect.\n\n"))
+
+def blame(ui, repo, *args, **kwargs):
+    cmdoptions = [
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+    try:
+        # If tweakdefaults is enabled then we have access to -p, which adds
+        # Phabricator diff ID
+        extensions.find('tweakdefaults')
+        cmd = Command('annotate -pudl')
+    except KeyError:
+        cmd = Command('annotate -udl')
+    cmd.extend([convert(v) for v in args])
+    ui.status((str(cmd)), "\n")
+
+def branch(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('', 'set-upstream', None, ''),
+        ('', 'set-upstream-to', '', ''),
+        ('d', 'delete', None, ''),
+        ('D', 'delete', None, ''),
+        ('m', 'move', None, ''),
+        ('M', 'move', None, ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    cmd = Command("bookmark")
+
+    if opts.get('set_upstream') or opts.get('set_upstream_to'):
+        ui.status(_("Mercurial has no concept of upstream branches\n"))
+        return
+    elif opts.get('delete'):
+        cmd = Command("strip")
+        for branch in args:
+            cmd['-B'] = branch
+        else:
+            cmd['-B'] = None
+    elif opts.get('move'):
+        if len(args) > 0:
+            if len(args) > 1:
+                old = args.pop(0)
+            else:
+                # shell command to output the active bookmark for the active
+                # revision
+                old = '`hg log -T"{activebookmark}" -r .`'
+        new = args[0]
+        cmd['-m'] = old
+        cmd.append(new)
+    else:
+        if len(args) > 1:
+            cmd['-r'] = args[1]
+            cmd.append(args[0])
+        elif len(args) == 1:
+            cmd.append(args[0])
+    ui.status((str(cmd)), "\n")
+
+def ispath(repo, string):
+    """
+    The first argument to git checkout can either be a revision or a path. Let's
+    generally assume it's a revision, unless it's obviously a path. There are
+    too many ways to spell revisions in git for us to reasonably catch all of
+    them, so let's be conservative.
+    """
+    if string in repo:
+        # if it's definitely a revision let's not even check if a file of the
+        # same name exists.
+        return False
+
+    cwd = repo.getcwd()
+    if cwd == '':
+        repopath = string
+    else:
+        repopath = cwd + '/' + string
+
+    exists = repo.wvfs.exists(repopath)
+    if exists:
+        return True
+
+    manifest = repo['.'].manifest()
+
+    didexist = (repopath in manifest) or manifest.hasdir(repopath)
+
+    return didexist
+
+def checkout(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('b', 'branch', '', ''),
+        ('B', 'branch', '', ''),
+        ('f', 'force', None, ''),
+        ('p', 'patch', None, ''),
+    ]
+    paths = []
+    if '--' in args:
+        sepindex = args.index('--')
+        paths.extend(args[sepindex + 1:])
+        args = args[:sepindex]
+
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    rev = None
+    if args and ispath(repo, args[0]):
+        paths = args + paths
+    elif args:
+        rev = args[0]
+        paths = args[1:] + paths
+
+    cmd = Command('update')
+
+    if opts.get('force'):
+        if paths or rev:
+            cmd['-C'] = None
+
+    if opts.get('patch'):
+        cmd = Command('revert')
+        cmd['-i'] = None
+
+    if opts.get('branch'):
+        if len(args) == 0:
+            cmd = Command('bookmark')
+            cmd.append(opts.get('branch'))
+        else:
+            cmd.append(args[0])
+            bookcmd = Command('bookmark')
+            bookcmd.append(opts.get('branch'))
+            cmd = cmd & bookcmd
+    # if there is any path argument supplied, use revert instead of update
+    elif len(paths) > 0:
+        ui.status(_("note: use --no-backup to avoid creating .orig files\n\n"))
+        cmd = Command('revert')
+        if opts.get('patch'):
+            cmd['-i'] = None
+        if rev:
+            cmd['-r'] = rev
+        cmd.extend(paths)
+    elif rev:
+        if opts.get('patch'):
+            cmd['-r'] = rev
+        else:
+            cmd.append(rev)
+    elif opts.get('force'):
+        cmd = Command('revert')
+        cmd['--all'] = None
+    else:
+        raise error.Abort("a commit must be specified")
+
+    ui.status((str(cmd)), "\n")
+
+def cherrypick(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('', 'continue', None, ''),
+        ('', 'abort', None, ''),
+        ('e', 'edit', None, ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    cmd = Command('graft')
+
+    if opts.get('edit'):
+        cmd['--edit'] = None
+    if opts.get('continue'):
+        cmd['--continue'] = None
+    elif opts.get('abort'):
+        ui.status(_("note: hg graft does not have --abort.\n\n"))
+        return
+    else:
+        cmd.extend(args)
+
+    ui.status((str(cmd)), "\n")
+
+def clean(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('d', 'd', None, ''),
+        ('f', 'force', None, ''),
+        ('x', 'x', None, ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    cmd = Command('purge')
+    if opts.get('x'):
+        cmd['--all'] = None
+    cmd.extend(args)
+
+    ui.status((str(cmd)), "\n")
+
+def clone(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('', 'bare', None, ''),
+        ('n', 'no-checkout', None, ''),
+        ('b', 'branch', '', ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    if len(args) == 0:
+        raise error.Abort("a repository to clone must be specified")
+
+    cmd = Command('clone')
+    cmd.append(args[0])
+    if len(args) > 1:
+        cmd.append(args[1])
+
+    if opts.get('bare'):
+        cmd['-U'] = None
+        ui.status(_("note: Mercurial does not have bare clones. " +
+            "-U will clone the repo without checking out a commit\n\n"))
+    elif opts.get('no_checkout'):
+        cmd['-U'] = None
+
+    if opts.get('branch'):
+        cocmd = Command("update")
+        cocmd.append(opts.get('branch'))
+        cmd = cmd & cocmd
+
+    ui.status((str(cmd)), "\n")
+
+def commit(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('a', 'all', None, ''),
+        ('m', 'message', '', ''),
+        ('p', 'patch', None, ''),
+        ('C', 'reuse-message', '', ''),
+        ('F', 'file', '', ''),
+        ('', 'author', '', ''),
+        ('', 'date', '', ''),
+        ('', 'amend', None, ''),
+        ('', 'no-edit', None, ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    cmd = Command('commit')
+    if opts.get('patch'):
+        cmd = Command('record')
+
+    if opts.get('amend'):
+        if opts.get('no_edit'):
+            cmd = Command('amend')
+        else:
+            cmd['--amend'] = None
+
+    if opts.get('reuse_message'):
+        cmd['-M'] = opts.get('reuse_message')
+
+    if opts.get('message'):
+        cmd['-m'] = "'%s'" % (opts.get('message'),)
+
+    if opts.get('all'):
+        ui.status(_("note: Mercurial doesn't have a staging area, " +
+            "so there is no --all. -A will add and remove files " +
+            "for you though.\n\n"))
+
+    if opts.get('file'):
+        cmd['-l'] = opts.get('file')
+
+    if opts.get('author'):
+        cmd['-u'] = opts.get('author')
+
+    if opts.get('date'):
+        cmd['-d'] = opts.get('date')
+
+    cmd.extend(args)
+
+    ui.status((str(cmd)), "\n")
+
+def deprecated(ui, repo, *args, **kwargs):
+    ui.warn(_('This command has been deprecated in the git project, ' +
+        'thus isn\'t supported by this tool.\n\n'))
+
+def diff(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('a', 'all', None, ''),
+        ('', 'cached', None, ''),
+        ('R', 'reverse', None, ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    cmd = Command('diff')
+
+    if opts.get('cached'):
+        ui.status(_('note: Mercurial has no concept of a staging area, ' +
+            'so --cached does nothing.\n\n'))
+
+    if opts.get('reverse'):
+        cmd['--reverse'] = None
+
+    for a in list(args):
+        args.remove(a)
+        try:
+            repo.revs(a)
+            cmd['-r'] = a
+        except Exception:
+            cmd.append(a)
+
+    ui.status((str(cmd)), "\n")
+
+def difftool(ui, repo, *args, **kwargs):
+    ui.status(_('Mercurial does not enable external difftool by default. You '
+        'need to enable the extdiff extension in your .hgrc file by adding\n'
+        'extdiff =\n'
+        'to the [extensions] section and then running\n\n'
+        'hg extdiff -p <program>\n\n'
+        'See \'hg help extdiff\' and \'hg help -e extdiff\' for more '
+        'information.\n'))
+
+def fetch(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('', 'all', None, ''),
+        ('f', 'force', None, ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    cmd = Command('pull')
+
+    if len(args) > 0:
+        cmd.append(args[0])
+        if len(args) > 1:
+            ui.status(_("note: Mercurial doesn't have refspecs. " +
+                "-r can be used to specify which commits you want to pull. " +
+                "-B can be used to specify which bookmark you want to pull." +
+                "\n\n"))
+            for v in args[1:]:
+                if v in repo._bookmarks:
+                    cmd['-B'] = v
+                else:
+                    cmd['-r'] = v
+
+    ui.status((str(cmd)), "\n")
+
+def grep(ui, repo, *args, **kwargs):
+    cmdoptions = [
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    cmd = Command('grep')
+
+    # For basic usage, git grep and hg grep are the same. They both have the
+    # pattern first, followed by paths.
+    cmd.extend(args)
+
+    ui.status((str(cmd)), "\n")
+
+def init(ui, repo, *args, **kwargs):
+    cmdoptions = [
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    cmd = Command('init')
+
+    if len(args) > 0:
+        cmd.append(args[0])
+
+    ui.status((str(cmd)), "\n")
+
+def log(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('', 'follow', None, ''),
+        ('', 'decorate', None, ''),
+        ('n', 'number', '', ''),
+        ('1', '1', None, ''),
+        ('', 'pretty', '', ''),
+        ('', 'format', '', ''),
+        ('', 'oneline', None, ''),
+        ('', 'stat', None, ''),
+        ('', 'graph', None, ''),
+        ('p', 'patch', None, ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+    ui.status(_('note: -v prints the entire commit message like Git does. To ' +
+              'print just the first line, drop the -v.\n\n'))
+    ui.status(_("note: see hg help revset for information on how to filter " +
+        "log output.\n\n"))
+
+    cmd = Command('log')
+    cmd['-v'] = None
+
+    if opts.get('number'):
+        cmd['-l'] = opts.get('number')
+    if opts.get('1'):
+        cmd['-l'] = '1'
+    if opts.get('stat'):
+        cmd['--stat'] = None
+    if opts.get('graph'):
+        cmd['-G'] = None
+    if opts.get('patch'):
+        cmd['-p'] = None
+
+    if opts.get('pretty') or opts.get('format') or opts.get('oneline'):
+        format = opts.get('format', '')
+        if 'format:' in format:
+            ui.status(_("note: --format format:??? equates to Mercurial's " +
+                "--template. See hg help templates for more info.\n\n"))
+            cmd['--template'] = '???'
+        else:
+            ui.status(_("note: --pretty/format/oneline equate to Mercurial's " +
+                "--style or --template. See hg help templates for more info." +
+                "\n\n"))
+            cmd['--style'] = '???'
+
+    if len(args) > 0:
+        if '..' in args[0]:
+            since, until = args[0].split('..')
+            cmd['-r'] = "'%s::%s'" % (since, until)
+            del args[0]
+        cmd.extend(args)
+
+    ui.status((str(cmd)), "\n")
+
+def lsfiles(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('c', 'cached', None, ''),
+        ('d', 'deleted', None, ''),
+        ('m', 'modified', None, ''),
+        ('o', 'others', None, ''),
+        ('i', 'ignored', None, ''),
+        ('s', 'stage', None, ''),
+        ('z', '_zero', None, ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    if (opts.get('modified') or opts.get('deleted')
+        or opts.get('others') or opts.get('ignored')):
+        cmd = Command('status')
+        if opts.get('deleted'):
+            cmd['-d'] = None
+        if opts.get('modified'):
+            cmd['-m'] = None
+        if opts.get('others'):
+            cmd['-o'] = None
+        if opts.get('ignored'):
+            cmd['-i'] = None
+    else:
+        cmd = Command('files')
+    if opts.get('stage'):
+        ui.status(_("note: Mercurial doesn't have a staging area, ignoring "
+                  "--stage\n"))
+    if opts.get('_zero'):
+        cmd['-0'] = None
+    cmd.append('.')
+    for include in args:
+        cmd['-I'] = util.shellquote(include)
+
+    ui.status((str(cmd)), "\n")
+
+def merge(ui, repo, *args, **kwargs):
+    cmdoptions = [
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    cmd = Command('merge')
+
+    if len(args) > 0:
+        cmd.append(args[len(args) - 1])
+
+    ui.status((str(cmd)), "\n")
+
+def mergebase(ui, repo, *args, **kwargs):
+    cmdoptions = []
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    if len(args) != 2:
+        args = ['A', 'B']
+
+    cmd = Command("log -T '{node}\\n' -r 'ancestor(%s,%s)'"
+                  % (args[0], args[1]))
+
+    ui.status(_('NOTE: ancestors() is part of the revset language.\n'),
+              _("Learn more about revsets with 'hg help revsets'\n\n"))
+    ui.status((str(cmd)), "\n")
+
+def mergetool(ui, repo, *args, **kwargs):
+    cmdoptions = []
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    cmd = Command("resolve")
+
+    if len(args) == 0:
+        cmd['--all'] = None
+    cmd.extend(args)
+    ui.status((str(cmd)), "\n")
+
+def mv(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('f', 'force', None, ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    cmd = Command('mv')
+    cmd.extend(args)
+
+    if opts.get('force'):
+        cmd['-f'] = None
+
+    ui.status((str(cmd)), "\n")
+
+def pull(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('', 'all', None, ''),
+        ('f', 'force', None, ''),
+        ('r', 'rebase', None, ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    cmd = Command('pull')
+    cmd['--rebase'] = None
+
+    if len(args) > 0:
+        cmd.append(args[0])
+        if len(args) > 1:
+            ui.status(_("note: Mercurial doesn't have refspecs. " +
+                "-r can be used to specify which commits you want to pull. " +
+                "-B can be used to specify which bookmark you want to pull." +
+                "\n\n"))
+            for v in args[1:]:
+                if v in repo._bookmarks:
+                    cmd['-B'] = v
+                else:
+                    cmd['-r'] = v
+
+    ui.status((str(cmd)), "\n")
+
+def push(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('', 'all', None, ''),
+        ('f', 'force', None, ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    cmd = Command('push')
+
+    if len(args) > 0:
+        cmd.append(args[0])
+        if len(args) > 1:
+            ui.status(_("note: Mercurial doesn't have refspecs. " +
+                "-r can be used to specify which commits you want to push. " +
+                "-B can be used to specify which bookmark you want to push." +
+                "\n\n"))
+            for v in args[1:]:
+                if v in repo._bookmarks:
+                    cmd['-B'] = v
+                else:
+                    cmd['-r'] = v
+
+    if opts.get('force'):
+        cmd['-f'] = None
+
+    ui.status((str(cmd)), "\n")
+
+def rebase(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('', 'all', None, ''),
+        ('i', 'interactive', None, ''),
+        ('', 'onto', '', ''),
+        ('', 'abort', None, ''),
+        ('', 'continue', None, ''),
+        ('', 'skip', None, ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    if opts.get('interactive'):
+        ui.status(_("note: hg histedit does not perform a rebase. " +
+            "It just edits history.\n\n"))
+        cmd = Command('histedit')
+        if len(args) > 0:
+            ui.status(_("also note: 'hg histedit' will automatically detect"
+                      " your stack, so no second argument is necessary.\n\n"))
+        ui.status((str(cmd)), "\n")
+        return
+
+    if opts.get('skip'):
+        cmd = Command('revert --all -r .')
+        ui.status((str(cmd)), "\n")
+
+    cmd = Command('rebase')
+
+    if opts.get('continue') or opts.get('skip'):
+        cmd['--continue'] = None
+    if opts.get('abort'):
+        cmd['--abort'] = None
+
+    if opts.get('onto'):
+        ui.status(_("note: if you're trying to lift a commit off one branch, " +
+            "try hg rebase -d <destination commit> -s <commit to be lifted>" +
+            "\n\n"))
+        cmd['-d'] = convert(opts.get('onto'))
+        if len(args) < 2:
+            raise error.Abort("Expected format: git rebase --onto X Y Z")
+        cmd['-s'] = "'::%s - ::%s'" % (convert(args[1]), convert(args[0]))
+    else:
+        if len(args) == 1:
+            cmd['-d'] = convert(args[0])
+        elif len(args) == 2:
+            cmd['-d'] = convert(args[0])
+            cmd['-b'] = convert(args[1])
+
+    ui.status((str(cmd)), "\n")
+
+def reflog(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('', 'all', None, ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    cmd = Command('journal')
+    if opts.get('all'):
+        cmd['--all'] = None
+    if len(args) > 0:
+        cmd.append(args[0])
+
+    ui.status(str(cmd), "\n\n")
+    ui.status(_("note: in hg commits can be deleted from repo but we always"
+              " have backups.\n"
+              "Please use 'hg backups --restore' or 'hg reset'" +
+              " to restore from backups.\n"))
+
+def reset(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('', 'soft', None, ''),
+        ('', 'hard', None, ''),
+        ('', 'mixed', None, ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    commit = convert(args[0] if len(args) > 0 else '.')
+    hard = opts.get('hard')
+
+    if opts.get('mixed'):
+        ui.status(_('NOTE: --mixed has no meaning since mercurial has no ' +
+                    'staging area\n\n'))
+
+    cmd = Command('reset')
+    if hard:
+        cmd.append('--clean')
+    cmd.append(commit)
+
+    ui.status((str(cmd)), "\n")
+
+def revert(ui, repo, *args, **kwargs):
+    cmdoptions = [
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    if len(args) > 1:
+        ui.status(_("note: hg backout doesn't support multiple commits at " +
+                    "once\n\n"))
+
+    cmd = Command('backout')
+    if args:
+        cmd.append(args[0])
+
+    ui.status((str(cmd)), "\n")
+
+def revparse(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('', 'show-cdup', None, ''),
+        ('', 'show-toplevel', None, ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    if opts.get('show_cdup') or opts.get('show_toplevel'):
+        cmd = Command('root')
+        if opts.get('show_cdup'):
+            ui.status(_("note: hg root prints the root of the repository\n\n"))
+        ui.status((str(cmd)), "\n")
+    else:
+        ui.status(_("note: see hg help revset for how to refer to commits\n"))
+
+def rm(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('f', 'force', None, ''),
+        ('n', 'dry-run', None, ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    cmd = Command('rm')
+    cmd.extend(args)
+
+    if opts.get('force'):
+        cmd['-f'] = None
+    if opts.get('dry_run'):
+        cmd['-n'] = None
+
+    ui.status((str(cmd)), "\n")
+
+def show(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('', 'name-status', None, ''),
+        ('', 'pretty', '', ''),
+        ('U', 'unified', int, ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    cmd = Command('show')
+    if opts.get('name_status'):
+        if opts.get('pretty') == 'format:':
+            cmd = Command('stat')
+            cmd['--change'] = 'tip'
+        else:
+            cmd = Command('log')
+            cmd.append('--style status')
+            cmd.append('-r tip')
+    elif len(args) > 0:
+        if ispath(repo, args[0]):
+            cmd.append('.')
+        cmd.extend(args)
+        if opts.get('unified'):
+            cmd.append('--config diff.unified=%d' % (opts['unified'],))
+    elif opts.get('unified'):
+        cmd.append('--config diff.unified=%d' % (opts['unified'],))
+
+    ui.status((str(cmd)), "\n")
+
+def stash(ui, repo, *args, **kwargs):
+    cmdoptions = [
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    cmd = Command('shelve')
+    action = args[0] if len(args) > 0 else None
+
+    if action == 'list':
+        cmd['-l'] = None
+    elif action == 'drop':
+        cmd['-d'] = None
+        if len(args) > 1:
+            cmd.append(args[1])
+        else:
+            cmd.append('<shelve name>')
+    elif action == 'pop' or action == 'apply':
+        cmd = Command('unshelve')
+        if len(args) > 1:
+            cmd.append(args[1])
+        if action == 'apply':
+            cmd['--keep'] = None
+    elif (action == 'branch' or action == 'show' or action == 'clear'
+        or action == 'create'):
+        ui.status(_("note: Mercurial doesn't have equivalents to the " +
+            "git stash branch, show, clear, or create actions.\n\n"))
+        return
+    else:
+        if len(args) > 0:
+            if args[0] != 'save':
+                cmd['--name'] = args[0]
+            elif len(args) > 1:
+                cmd['--name'] = args[1]
+
+    ui.status((str(cmd)), "\n")
+
+def status(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('', 'ignored', None, ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    cmd = Command('status')
+    cmd.extend(args)
+
+    if opts.get('ignored'):
+        cmd['-i'] = None
+
+    ui.status((str(cmd)), "\n")
+
+def svn(ui, repo, *args, **kwargs):
+    svncmd = args[0]
+    if not svncmd in gitsvncommands:
+        ui.warn(_("error: unknown git svn command %s\n") % (svncmd))
+
+    args = args[1:]
+    return gitsvncommands[svncmd](ui, repo, *args, **kwargs)
+
+def svndcommit(ui, repo, *args, **kwargs):
+    cmdoptions = [
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    cmd = Command('push')
+
+    ui.status((str(cmd)), "\n")
+
+def svnfetch(ui, repo, *args, **kwargs):
+    cmdoptions = [
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    cmd = Command('pull')
+    cmd.append('default-push')
+
+    ui.status((str(cmd)), "\n")
+
+def svnfindrev(ui, repo, *args, **kwargs):
+    cmdoptions = [
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    cmd = Command('log')
+    cmd['-r'] = args[0]
+
+    ui.status((str(cmd)), "\n")
+
+def svnrebase(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('l', 'local', None, ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    pullcmd = Command('pull')
+    pullcmd.append('default-push')
+    rebasecmd = Command('rebase')
+    rebasecmd.append('tip')
+
+    cmd = pullcmd & rebasecmd
+
+    ui.status((str(cmd)), "\n")
+
+def tag(ui, repo, *args, **kwargs):
+    cmdoptions = [
+        ('f', 'force', None, ''),
+        ('l', 'list', None, ''),
+        ('d', 'delete', None, ''),
+    ]
+    args, opts = parseoptions(ui, cmdoptions, args)
+
+    if opts.get('list'):
+        cmd = Command('tags')
+    else:
+        cmd = Command('tag')
+        cmd.append(args[0])
+        if len(args) > 1:
+            cmd['-r'] = args[1]
+
+        if opts.get('delete'):
+            cmd['--remove'] = None
+
+        if opts.get('force'):
+            cmd['-f'] = None
+
+    ui.status((str(cmd)), "\n")
+
+gitcommands = {
+    'add': add,
+    'am': am,
+    'apply': apply,
+    'bisect': bisect,
+    'blame': blame,
+    'branch': branch,
+    'checkout': checkout,
+    'cherry-pick': cherrypick,
+    'clean': clean,
+    'clone': clone,
+    'commit': commit,
+    'diff': diff,
+    'difftool': difftool,
+    'fetch': fetch,
+    'grep': grep,
+    'init': init,
+    'log': log,
+    'ls-files': lsfiles,
+    'merge': merge,
+    'merge-base': mergebase,
+    'mergetool': mergetool,
+    'mv': mv,
+    'pull': pull,
+    'push': push,
+    'rebase': rebase,
+    'reflog': reflog,
+    'reset': reset,
+    'revert': revert,
+    'rev-parse': revparse,
+    'rm': rm,
+    'show': show,
+    'stash': stash,
+    'status': status,
+    'svn': svn,
+    'tag': tag,
+    'whatchanged': deprecated,
+}
+
+gitsvncommands = {
+    'dcommit': svndcommit,
+    'fetch': svnfetch,
+    'find-rev': svnfindrev,
+    'rebase': svnrebase,
+}