view hgext/githelp.py @ 45095:8e04607023e5

procutil: ensure that procutil.std{out,err}.write() writes all bytes Python 3 offers different kind of streams and it’s not guaranteed for all of them that calling write() writes all bytes. When Python is started in unbuffered mode, sys.std{out,err}.buffer are instances of io.FileIO, whose write() can write less bytes for platform-specific reasons (e.g. Linux has a 0x7ffff000 bytes maximum and could write less if interrupted by a signal; when writing to Windows consoles, it’s limited to 32767 bytes to avoid the "not enough space" error). This can lead to silent loss of data, both when using sys.std{out,err}.buffer (which may in fact not be a buffered stream) and when using the text streams sys.std{out,err} (I’ve created a CPython bug report for that: https://bugs.python.org/issue41221). Python may fix the problem at some point. For now, we implement our own wrapper for procutil.std{out,err} that calls the raw stream’s write() method until all bytes have been written. We don’t use sys.std{out,err} for larger writes, so I think it’s not worth the effort to patch them.
author Manuel Jacob <me@manueljacob.de>
date Fri, 10 Jul 2020 12:27:58 +0200
parents 94f227baa76f
children 89a2afe31e82
line wrap: on
line source

# 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 (
    encoding,
    error,
    fancyopts,
    pycompat,
    registrar,
    scmutil,
)
from mercurial.utils import procutil

# 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 = b'ships-with-hg-core'

cmdtable = {}
command = registrar.command(cmdtable)


def convert(s):
    if s.startswith(b"origin/"):
        return s[7:]
    if b'HEAD' in s:
        s = s.replace(b'HEAD', b'.')
    # HEAD~ in git is .~1 in mercurial
    s = re.sub(b'~$', b'~1', s)
    return s


@command(
    b'githelp|git',
    [],
    _(b'hg githelp'),
    helpcategory=command.CATEGORY_HELP,
    helpbasic=True,
)
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] == b'git'):
        raise error.Abort(
            _(b'missing git command - usage: hg githelp -- <git command>')
        )

    if args[0] == b'git':
        args = args[1:]

    cmd = args[0]
    if not cmd in gitcommands:
        raise error.Abort(_(b"error: unknown git command %s") % cmd)

    ui.pager(b'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:
            if "requires argument" in ex.msg:
                raise
            if ('--' + ex.opt) in ex.msg:
                flag = b'--' + pycompat.bytestr(ex.opt)
            elif ('-' + ex.opt) in ex.msg:
                flag = b'-' + pycompat.bytestr(ex.opt)
            else:
                raise error.Abort(
                    _(b"unknown option %s") % pycompat.bytestr(ex.opt)
                )
            try:
                args.remove(flag)
            except Exception:
                msg = _(b"unknown option '%s' packed with other options")
                hint = _(b"please try passing the option as its own flag: -%s")
                raise error.Abort(
                    msg % pycompat.bytestr(ex.opt),
                    hint=hint % pycompat.bytestr(ex.opt),
                )

            ui.warn(_(b"ignoring unknown option %s\n") % flag)

    args = list([convert(x) for x in args])
    opts = dict(
        [
            (k, convert(v)) if isinstance(v, bytes) else (k, v)
            for k, v in pycompat.iteritems(opts)
        ]
    )

    return args, opts


class Command(object):
    def __init__(self, name):
        self.name = name
        self.args = []
        self.opts = {}

    def __bytes__(self):
        cmd = b"hg " + self.name
        if self.opts:
            for k, values in sorted(pycompat.iteritems(self.opts)):
                for v in values:
                    if v:
                        if isinstance(v, int):
                            fmt = b' %s %d'
                        else:
                            fmt = b' %s %s'

                        cmd += fmt % (k, v)
                    else:
                        cmd += b" %s" % (k,)
        if self.args:
            cmd += b" "
            cmd += b" ".join(self.args)
        return cmd

    __str__ = encoding.strmethod(__bytes__)

    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 b"%s && %s" % (self.left, self.right)

    def __and__(self, other):
        return AndCommand(self, other)


def add(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'A', b'all', None, b''),
        (b'p', b'patch', None, b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)

    if opts.get(b'patch'):
        ui.status(
            _(
                b"note: Mercurial will commit when complete, "
                b"as there is no staging area in Mercurial\n\n"
            )
        )
        cmd = Command(b'commit --interactive')
    else:
        cmd = Command(b"add")

        if not opts.get(b'all'):
            cmd.extend(args)
        else:
            ui.status(
                _(
                    b"note: use hg addremove to remove files that have "
                    b"been deleted\n\n"
                )
            )

    ui.status((bytes(cmd)), b"\n")


def am(ui, repo, *args, **kwargs):
    cmdoptions = []
    parseoptions(ui, cmdoptions, args)
    cmd = Command(b'import')
    ui.status(bytes(cmd), b"\n")


def apply(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'p', b'p', int, b''),
        (b'', b'directory', b'', b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)

    cmd = Command(b'import --no-commit')
    if opts.get(b'p'):
        cmd[b'-p'] = opts.get(b'p')
    if opts.get(b'directory'):
        cmd[b'--prefix'] = opts.get(b'directory')
    cmd.extend(args)

    ui.status((bytes(cmd)), b"\n")


def bisect(ui, repo, *args, **kwargs):
    ui.status(_(b"see 'hg help bisect' for how to use bisect\n\n"))


def blame(ui, repo, *args, **kwargs):
    cmdoptions = []
    args, opts = parseoptions(ui, cmdoptions, args)
    cmd = Command(b'annotate -udl')
    cmd.extend([convert(v) for v in args])
    ui.status((bytes(cmd)), b"\n")


def branch(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'', b'set-upstream', None, b''),
        (b'', b'set-upstream-to', b'', b''),
        (b'd', b'delete', None, b''),
        (b'D', b'delete', None, b''),
        (b'm', b'move', None, b''),
        (b'M', b'move', None, b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)

    cmd = Command(b"bookmark")

    if opts.get(b'set_upstream') or opts.get(b'set_upstream_to'):
        ui.status(_(b"Mercurial has no concept of upstream branches\n"))
        return
    elif opts.get(b'delete'):
        cmd = Command(b"strip")
        for branch in args:
            cmd[b'-B'] = branch
        else:
            cmd[b'-B'] = None
    elif opts.get(b'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 = b'`hg log -T"{activebookmark}" -r .`'
        else:
            raise error.Abort(_(b'missing newbranch argument'))
        new = args[0]
        cmd[b'-m'] = old
        cmd.append(new)
    else:
        if len(args) > 1:
            cmd[b'-r'] = args[1]
            cmd.append(args[0])
        elif len(args) == 1:
            cmd.append(args[0])
    ui.status((bytes(cmd)), b"\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 scmutil.isrevsymbol(repo, string):
        # 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 == b'':
        repopath = string
    else:
        repopath = cwd + b'/' + string

    exists = repo.wvfs.exists(repopath)
    if exists:
        return True

    manifest = repo[b'.'].manifest()

    didexist = (repopath in manifest) or manifest.hasdir(repopath)

    return didexist


def checkout(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'b', b'branch', b'', b''),
        (b'B', b'branch', b'', b''),
        (b'f', b'force', None, b''),
        (b'p', b'patch', None, b''),
    ]
    paths = []
    if b'--' in args:
        sepindex = args.index(b'--')
        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(b'update')

    if opts.get(b'force'):
        if paths or rev:
            cmd[b'-C'] = None

    if opts.get(b'patch'):
        cmd = Command(b'revert')
        cmd[b'-i'] = None

    if opts.get(b'branch'):
        if len(args) == 0:
            cmd = Command(b'bookmark')
            cmd.append(opts.get(b'branch'))
        else:
            cmd.append(args[0])
            bookcmd = Command(b'bookmark')
            bookcmd.append(opts.get(b'branch'))
            cmd = cmd & bookcmd
    # if there is any path argument supplied, use revert instead of update
    elif len(paths) > 0:
        ui.status(_(b"note: use --no-backup to avoid creating .orig files\n\n"))
        cmd = Command(b'revert')
        if opts.get(b'patch'):
            cmd[b'-i'] = None
        if rev:
            cmd[b'-r'] = rev
        cmd.extend(paths)
    elif rev:
        if opts.get(b'patch'):
            cmd[b'-r'] = rev
        else:
            cmd.append(rev)
    elif opts.get(b'force'):
        cmd = Command(b'revert')
        cmd[b'--all'] = None
    else:
        raise error.Abort(_(b"a commit must be specified"))

    ui.status((bytes(cmd)), b"\n")


def cherrypick(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'', b'continue', None, b''),
        (b'', b'abort', None, b''),
        (b'e', b'edit', None, b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)

    cmd = Command(b'graft')

    if opts.get(b'edit'):
        cmd[b'--edit'] = None
    if opts.get(b'continue'):
        cmd[b'--continue'] = None
    elif opts.get(b'abort'):
        ui.status(_(b"note: hg graft does not have --abort\n\n"))
        return
    else:
        cmd.extend(args)

    ui.status((bytes(cmd)), b"\n")


def clean(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'd', b'd', None, b''),
        (b'f', b'force', None, b''),
        (b'x', b'x', None, b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)

    cmd = Command(b'purge')
    if opts.get(b'x'):
        cmd[b'--all'] = None
    cmd.extend(args)

    ui.status((bytes(cmd)), b"\n")


def clone(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'', b'bare', None, b''),
        (b'n', b'no-checkout', None, b''),
        (b'b', b'branch', b'', b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)

    if len(args) == 0:
        raise error.Abort(_(b"a repository to clone must be specified"))

    cmd = Command(b'clone')
    cmd.append(args[0])
    if len(args) > 1:
        cmd.append(args[1])

    if opts.get(b'bare'):
        cmd[b'-U'] = None
        ui.status(
            _(
                b"note: Mercurial does not have bare clones. "
                b"-U will clone the repo without checking out a commit\n\n"
            )
        )
    elif opts.get(b'no_checkout'):
        cmd[b'-U'] = None

    if opts.get(b'branch'):
        cocmd = Command(b"update")
        cocmd.append(opts.get(b'branch'))
        cmd = cmd & cocmd

    ui.status((bytes(cmd)), b"\n")


def commit(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'a', b'all', None, b''),
        (b'm', b'message', b'', b''),
        (b'p', b'patch', None, b''),
        (b'C', b'reuse-message', b'', b''),
        (b'F', b'file', b'', b''),
        (b'', b'author', b'', b''),
        (b'', b'date', b'', b''),
        (b'', b'amend', None, b''),
        (b'', b'no-edit', None, b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)

    cmd = Command(b'commit')
    if opts.get(b'patch'):
        cmd = Command(b'commit --interactive')

    if opts.get(b'amend'):
        if opts.get(b'no_edit'):
            cmd = Command(b'amend')
        else:
            cmd[b'--amend'] = None

    if opts.get(b'reuse_message'):
        cmd[b'-M'] = opts.get(b'reuse_message')

    if opts.get(b'message'):
        cmd[b'-m'] = b"'%s'" % (opts.get(b'message'),)

    if opts.get(b'all'):
        ui.status(
            _(
                b"note: Mercurial doesn't have a staging area, "
                b"so there is no --all. -A will add and remove files "
                b"for you though.\n\n"
            )
        )

    if opts.get(b'file'):
        cmd[b'-l'] = opts.get(b'file')

    if opts.get(b'author'):
        cmd[b'-u'] = opts.get(b'author')

    if opts.get(b'date'):
        cmd[b'-d'] = opts.get(b'date')

    cmd.extend(args)

    ui.status((bytes(cmd)), b"\n")


def deprecated(ui, repo, *args, **kwargs):
    ui.warn(
        _(
            b'this command has been deprecated in the git project, '
            b'thus isn\'t supported by this tool\n\n'
        )
    )


def diff(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'a', b'all', None, b''),
        (b'', b'cached', None, b''),
        (b'R', b'reverse', None, b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)

    cmd = Command(b'diff')

    if opts.get(b'cached'):
        ui.status(
            _(
                b'note: Mercurial has no concept of a staging area, '
                b'so --cached does nothing\n\n'
            )
        )

    if opts.get(b'reverse'):
        cmd[b'--reverse'] = None

    for a in list(args):
        args.remove(a)
        try:
            repo.revs(a)
            cmd[b'-r'] = a
        except Exception:
            cmd.append(a)

    ui.status((bytes(cmd)), b"\n")


def difftool(ui, repo, *args, **kwargs):
    ui.status(
        _(
            b'Mercurial does not enable external difftool by default. You '
            b'need to enable the extdiff extension in your .hgrc file by adding\n'
            b'extdiff =\n'
            b'to the [extensions] section and then running\n\n'
            b'hg extdiff -p <program>\n\n'
            b'See \'hg help extdiff\' and \'hg help -e extdiff\' for more '
            b'information.\n'
        )
    )


def fetch(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'', b'all', None, b''),
        (b'f', b'force', None, b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)

    cmd = Command(b'pull')

    if len(args) > 0:
        cmd.append(args[0])
        if len(args) > 1:
            ui.status(
                _(
                    b"note: Mercurial doesn't have refspecs. "
                    b"-r can be used to specify which commits you want to "
                    b"pull. -B can be used to specify which bookmark you "
                    b"want to pull.\n\n"
                )
            )
            for v in args[1:]:
                if v in repo._bookmarks:
                    cmd[b'-B'] = v
                else:
                    cmd[b'-r'] = v

    ui.status((bytes(cmd)), b"\n")


def grep(ui, repo, *args, **kwargs):
    cmdoptions = []
    args, opts = parseoptions(ui, cmdoptions, args)

    cmd = Command(b'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((bytes(cmd)), b"\n")


def init(ui, repo, *args, **kwargs):
    cmdoptions = []
    args, opts = parseoptions(ui, cmdoptions, args)

    cmd = Command(b'init')

    if len(args) > 0:
        cmd.append(args[0])

    ui.status((bytes(cmd)), b"\n")


def log(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'', b'follow', None, b''),
        (b'', b'decorate', None, b''),
        (b'n', b'number', b'', b''),
        (b'1', b'1', None, b''),
        (b'', b'pretty', b'', b''),
        (b'', b'format', b'', b''),
        (b'', b'oneline', None, b''),
        (b'', b'stat', None, b''),
        (b'', b'graph', None, b''),
        (b'p', b'patch', None, b''),
        (b'G', b'grep-diff', b'', b''),
        (b'S', b'pickaxe-regex', b'', b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)
    grep_pat = opts.get(b'grep_diff') or opts.get(b'pickaxe_regex')
    if grep_pat:
        cmd = Command(b'grep')
        cmd[b'--diff'] = grep_pat
        ui.status(b'%s\n' % bytes(cmd))
        return

    ui.status(
        _(
            b'note: -v prints the entire commit message like Git does. To '
            b'print just the first line, drop the -v.\n\n'
        )
    )
    ui.status(
        _(
            b"note: see hg help revset for information on how to filter "
            b"log output\n\n"
        )
    )

    cmd = Command(b'log')
    cmd[b'-v'] = None

    if opts.get(b'number'):
        cmd[b'-l'] = opts.get(b'number')
    if opts.get(b'1'):
        cmd[b'-l'] = b'1'
    if opts.get(b'stat'):
        cmd[b'--stat'] = None
    if opts.get(b'graph'):
        cmd[b'-G'] = None
    if opts.get(b'patch'):
        cmd[b'-p'] = None

    if opts.get(b'pretty') or opts.get(b'format') or opts.get(b'oneline'):
        format = opts.get(b'format', b'')
        if b'format:' in format:
            ui.status(
                _(
                    b"note: --format format:??? equates to Mercurial's "
                    b"--template. See hg help templates for more info.\n\n"
                )
            )
            cmd[b'--template'] = b'???'
        else:
            ui.status(
                _(
                    b"note: --pretty/format/oneline equate to Mercurial's "
                    b"--style or --template. See hg help templates for "
                    b"more info.\n\n"
                )
            )
            cmd[b'--style'] = b'???'

    if len(args) > 0:
        if b'..' in args[0]:
            since, until = args[0].split(b'..')
            cmd[b'-r'] = b"'%s::%s'" % (since, until)
            del args[0]
        cmd.extend(args)

    ui.status((bytes(cmd)), b"\n")


def lsfiles(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'c', b'cached', None, b''),
        (b'd', b'deleted', None, b''),
        (b'm', b'modified', None, b''),
        (b'o', b'others', None, b''),
        (b'i', b'ignored', None, b''),
        (b's', b'stage', None, b''),
        (b'z', b'_zero', None, b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)

    if (
        opts.get(b'modified')
        or opts.get(b'deleted')
        or opts.get(b'others')
        or opts.get(b'ignored')
    ):
        cmd = Command(b'status')
        if opts.get(b'deleted'):
            cmd[b'-d'] = None
        if opts.get(b'modified'):
            cmd[b'-m'] = None
        if opts.get(b'others'):
            cmd[b'-o'] = None
        if opts.get(b'ignored'):
            cmd[b'-i'] = None
    else:
        cmd = Command(b'files')
    if opts.get(b'stage'):
        ui.status(
            _(
                b"note: Mercurial doesn't have a staging area, ignoring "
                b"--stage\n"
            )
        )
    if opts.get(b'_zero'):
        cmd[b'-0'] = None
    cmd.append(b'.')
    for include in args:
        cmd[b'-I'] = procutil.shellquote(include)

    ui.status((bytes(cmd)), b"\n")


def merge(ui, repo, *args, **kwargs):
    cmdoptions = []
    args, opts = parseoptions(ui, cmdoptions, args)

    cmd = Command(b'merge')

    if len(args) > 0:
        cmd.append(args[len(args) - 1])

    ui.status((bytes(cmd)), b"\n")


def mergebase(ui, repo, *args, **kwargs):
    cmdoptions = []
    args, opts = parseoptions(ui, cmdoptions, args)

    if len(args) != 2:
        args = [b'A', b'B']

    cmd = Command(
        b"log -T '{node}\\n' -r 'ancestor(%s,%s)'" % (args[0], args[1])
    )

    ui.status(
        _(b'note: ancestors() is part of the revset language\n'),
        _(b"(learn more about revsets with 'hg help revsets')\n\n"),
    )
    ui.status((bytes(cmd)), b"\n")


def mergetool(ui, repo, *args, **kwargs):
    cmdoptions = []
    args, opts = parseoptions(ui, cmdoptions, args)

    cmd = Command(b"resolve")

    if len(args) == 0:
        cmd[b'--all'] = None
    cmd.extend(args)
    ui.status((bytes(cmd)), b"\n")


def mv(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'f', b'force', None, b''),
        (b'n', b'dry-run', None, b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)

    cmd = Command(b'mv')
    cmd.extend(args)

    if opts.get(b'force'):
        cmd[b'-f'] = None
    if opts.get(b'dry_run'):
        cmd[b'-n'] = None

    ui.status((bytes(cmd)), b"\n")


def pull(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'', b'all', None, b''),
        (b'f', b'force', None, b''),
        (b'r', b'rebase', None, b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)

    cmd = Command(b'pull')
    cmd[b'--rebase'] = None

    if len(args) > 0:
        cmd.append(args[0])
        if len(args) > 1:
            ui.status(
                _(
                    b"note: Mercurial doesn't have refspecs. "
                    b"-r can be used to specify which commits you want to "
                    b"pull. -B can be used to specify which bookmark you "
                    b"want to pull.\n\n"
                )
            )
            for v in args[1:]:
                if v in repo._bookmarks:
                    cmd[b'-B'] = v
                else:
                    cmd[b'-r'] = v

    ui.status((bytes(cmd)), b"\n")


def push(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'', b'all', None, b''),
        (b'f', b'force', None, b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)

    cmd = Command(b'push')

    if len(args) > 0:
        cmd.append(args[0])
        if len(args) > 1:
            ui.status(
                _(
                    b"note: Mercurial doesn't have refspecs. "
                    b"-r can be used to specify which commits you want "
                    b"to push. -B can be used to specify which bookmark "
                    b"you want to push.\n\n"
                )
            )
            for v in args[1:]:
                if v in repo._bookmarks:
                    cmd[b'-B'] = v
                else:
                    cmd[b'-r'] = v

    if opts.get(b'force'):
        cmd[b'-f'] = None

    ui.status((bytes(cmd)), b"\n")


def rebase(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'', b'all', None, b''),
        (b'i', b'interactive', None, b''),
        (b'', b'onto', b'', b''),
        (b'', b'abort', None, b''),
        (b'', b'continue', None, b''),
        (b'', b'skip', None, b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)

    if opts.get(b'interactive'):
        ui.status(
            _(
                b"note: hg histedit does not perform a rebase. "
                b"It just edits history.\n\n"
            )
        )
        cmd = Command(b'histedit')
        if len(args) > 0:
            ui.status(
                _(
                    b"also note: 'hg histedit' will automatically detect"
                    b" your stack, so no second argument is necessary\n\n"
                )
            )
        ui.status((bytes(cmd)), b"\n")
        return

    if opts.get(b'skip'):
        cmd = Command(b'revert --all -r .')
        ui.status((bytes(cmd)), b"\n")

    cmd = Command(b'rebase')

    if opts.get(b'continue') or opts.get(b'skip'):
        cmd[b'--continue'] = None
    if opts.get(b'abort'):
        cmd[b'--abort'] = None

    if opts.get(b'onto'):
        ui.status(
            _(
                b"note: if you're trying to lift a commit off one branch, "
                b"try hg rebase -d <destination commit> -s <commit to be "
                b"lifted>\n\n"
            )
        )
        cmd[b'-d'] = convert(opts.get(b'onto'))
        if len(args) < 2:
            raise error.Abort(_(b"expected format: git rebase --onto X Y Z"))
        cmd[b'-s'] = b"'::%s - ::%s'" % (convert(args[1]), convert(args[0]))
    else:
        if len(args) == 1:
            cmd[b'-d'] = convert(args[0])
        elif len(args) == 2:
            cmd[b'-d'] = convert(args[0])
            cmd[b'-b'] = convert(args[1])

    ui.status((bytes(cmd)), b"\n")


def reflog(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'', b'all', None, b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)

    cmd = Command(b'journal')
    if opts.get(b'all'):
        cmd[b'--all'] = None
    if len(args) > 0:
        cmd.append(args[0])

    ui.status(bytes(cmd), b"\n\n")
    ui.status(
        _(
            b"note: in hg commits can be deleted from repo but we always"
            b" have backups\n"
        )
    )


def reset(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'', b'soft', None, b''),
        (b'', b'hard', None, b''),
        (b'', b'mixed', None, b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)

    commit = convert(args[0] if len(args) > 0 else b'.')
    hard = opts.get(b'hard')

    if opts.get(b'mixed'):
        ui.status(
            _(
                b'note: --mixed has no meaning since Mercurial has no '
                b'staging area\n\n'
            )
        )
    if opts.get(b'soft'):
        ui.status(
            _(
                b'note: --soft has no meaning since Mercurial has no '
                b'staging area\n\n'
            )
        )

    cmd = Command(b'update')
    if hard:
        cmd.append(b'--clean')

    cmd.append(commit)

    ui.status((bytes(cmd)), b"\n")


def revert(ui, repo, *args, **kwargs):
    cmdoptions = []
    args, opts = parseoptions(ui, cmdoptions, args)

    if len(args) > 1:
        ui.status(
            _(
                b"note: hg backout doesn't support multiple commits at "
                b"once\n\n"
            )
        )

    cmd = Command(b'backout')
    if args:
        cmd.append(args[0])

    ui.status((bytes(cmd)), b"\n")


def revparse(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'', b'show-cdup', None, b''),
        (b'', b'show-toplevel', None, b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)

    if opts.get(b'show_cdup') or opts.get(b'show_toplevel'):
        cmd = Command(b'root')
        if opts.get(b'show_cdup'):
            ui.status(_(b"note: hg root prints the root of the repository\n\n"))
        ui.status((bytes(cmd)), b"\n")
    else:
        ui.status(_(b"note: see hg help revset for how to refer to commits\n"))


def rm(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'f', b'force', None, b''),
        (b'n', b'dry-run', None, b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)

    cmd = Command(b'rm')
    cmd.extend(args)

    if opts.get(b'force'):
        cmd[b'-f'] = None
    if opts.get(b'dry_run'):
        cmd[b'-n'] = None

    ui.status((bytes(cmd)), b"\n")


def show(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'', b'name-status', None, b''),
        (b'', b'pretty', b'', b''),
        (b'U', b'unified', int, b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)

    if opts.get(b'name_status'):
        if opts.get(b'pretty') == b'format:':
            cmd = Command(b'status')
            cmd[b'--change'] = b'.'
        else:
            cmd = Command(b'log')
            cmd.append(b'--style status')
            cmd.append(b'-r .')
    elif len(args) > 0:
        if ispath(repo, args[0]):
            cmd = Command(b'cat')
        else:
            cmd = Command(b'export')
        cmd.extend(args)
        if opts.get(b'unified'):
            cmd.append(b'--config diff.unified=%d' % (opts[b'unified'],))
    elif opts.get(b'unified'):
        cmd = Command(b'export')
        cmd.append(b'--config diff.unified=%d' % (opts[b'unified'],))
    else:
        cmd = Command(b'export')

    ui.status((bytes(cmd)), b"\n")


def stash(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'p', b'patch', None, b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)

    cmd = Command(b'shelve')
    action = args[0] if len(args) > 0 else None

    if action == b'list':
        cmd[b'-l'] = None
        if opts.get(b'patch'):
            cmd[b'-p'] = None
    elif action == b'show':
        if opts.get(b'patch'):
            cmd[b'-p'] = None
        else:
            cmd[b'--stat'] = None
        if len(args) > 1:
            cmd.append(args[1])
    elif action == b'clear':
        cmd[b'--cleanup'] = None
    elif action == b'drop':
        cmd[b'-d'] = None
        if len(args) > 1:
            cmd.append(args[1])
        else:
            cmd.append(b'<shelve name>')
    elif action == b'pop' or action == b'apply':
        cmd = Command(b'unshelve')
        if len(args) > 1:
            cmd.append(args[1])
        if action == b'apply':
            cmd[b'--keep'] = None
    elif action == b'branch' or action == b'create':
        ui.status(
            _(
                b"note: Mercurial doesn't have equivalents to the "
                b"git stash branch or create actions\n\n"
            )
        )
        return
    else:
        if len(args) > 0:
            if args[0] != b'save':
                cmd[b'--name'] = args[0]
            elif len(args) > 1:
                cmd[b'--name'] = args[1]

    ui.status((bytes(cmd)), b"\n")


def status(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'', b'ignored', None, b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)

    cmd = Command(b'status')
    cmd.extend(args)

    if opts.get(b'ignored'):
        cmd[b'-i'] = None

    ui.status((bytes(cmd)), b"\n")


def svn(ui, repo, *args, **kwargs):
    if not args:
        raise error.Abort(_(b'missing svn command'))
    svncmd = args[0]
    if svncmd not in gitsvncommands:
        raise error.Abort(_(b'unknown git svn command "%s"') % svncmd)

    args = args[1:]
    return gitsvncommands[svncmd](ui, repo, *args, **kwargs)


def svndcommit(ui, repo, *args, **kwargs):
    cmdoptions = []
    parseoptions(ui, cmdoptions, args)

    cmd = Command(b'push')

    ui.status((bytes(cmd)), b"\n")


def svnfetch(ui, repo, *args, **kwargs):
    cmdoptions = []
    parseoptions(ui, cmdoptions, args)

    cmd = Command(b'pull')
    cmd.append(b'default-push')

    ui.status((bytes(cmd)), b"\n")


def svnfindrev(ui, repo, *args, **kwargs):
    cmdoptions = []
    args, opts = parseoptions(ui, cmdoptions, args)

    if not args:
        raise error.Abort(_(b'missing find-rev argument'))

    cmd = Command(b'log')
    cmd[b'-r'] = args[0]

    ui.status((bytes(cmd)), b"\n")


def svnrebase(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'l', b'local', None, b''),
    ]
    parseoptions(ui, cmdoptions, args)

    pullcmd = Command(b'pull')
    pullcmd.append(b'default-push')
    rebasecmd = Command(b'rebase')
    rebasecmd.append(b'tip')

    cmd = pullcmd & rebasecmd

    ui.status((bytes(cmd)), b"\n")


def tag(ui, repo, *args, **kwargs):
    cmdoptions = [
        (b'f', b'force', None, b''),
        (b'l', b'list', None, b''),
        (b'd', b'delete', None, b''),
    ]
    args, opts = parseoptions(ui, cmdoptions, args)

    if opts.get(b'list'):
        cmd = Command(b'tags')
    else:
        cmd = Command(b'tag')

        if not args:
            raise error.Abort(_(b'missing tag argument'))

        cmd.append(args[0])
        if len(args) > 1:
            cmd[b'-r'] = args[1]

        if opts.get(b'delete'):
            cmd[b'--remove'] = None

        if opts.get(b'force'):
            cmd[b'-f'] = None

    ui.status((bytes(cmd)), b"\n")


gitcommands = {
    b'add': add,
    b'am': am,
    b'apply': apply,
    b'bisect': bisect,
    b'blame': blame,
    b'branch': branch,
    b'checkout': checkout,
    b'cherry-pick': cherrypick,
    b'clean': clean,
    b'clone': clone,
    b'commit': commit,
    b'diff': diff,
    b'difftool': difftool,
    b'fetch': fetch,
    b'grep': grep,
    b'init': init,
    b'log': log,
    b'ls-files': lsfiles,
    b'merge': merge,
    b'merge-base': mergebase,
    b'mergetool': mergetool,
    b'mv': mv,
    b'pull': pull,
    b'push': push,
    b'rebase': rebase,
    b'reflog': reflog,
    b'reset': reset,
    b'revert': revert,
    b'rev-parse': revparse,
    b'rm': rm,
    b'show': show,
    b'stash': stash,
    b'status': status,
    b'svn': svn,
    b'tag': tag,
    b'whatchanged': deprecated,
}

gitsvncommands = {
    b'dcommit': svndcommit,
    b'fetch': svnfetch,
    b'find-rev': svnfindrev,
    b'rebase': svnrebase,
}