{
    "file_diffs": [
        {
            "old_file_content": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 noet:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"\"\"\"\nfrom datalad.ui.utils import get_console_width\n\n__docformat__ = 'restructuredtext'\n\nimport logging\nlgr = logging.getLogger('datalad.cmdline')\n\nlgr.log(5, \"Importing cmdline.main\")\n\nimport argparse\nimport sys\nimport textwrap\nfrom importlib import import_module\nimport os\n\nfrom six import text_type\n\nimport datalad\n\nfrom datalad.cmdline import helpers\nfrom datalad.plugin import _get_plugins\nfrom datalad.plugin import _load_plugin\nfrom datalad.support.exceptions import InsufficientArgumentsError\nfrom datalad.support.exceptions import IncompleteResultsError\nfrom datalad.support.exceptions import CommandError\nfrom .helpers import strip_arg_from_argv\nfrom ..utils import setup_exceptionhook, chpwd\nfrom ..utils import assure_unicode\nfrom ..utils import on_msys_tainted_paths\nfrom ..dochelpers import exc_str\n\n\ndef _license_info():\n    return \"\"\"\\\nCopyright (c) 2013-2018 DataLad developers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\"\"\"\n\n\nclass ArgumentParserDisableAbbrev(argparse.ArgumentParser):\n    # Don't accept abbreviations for long options. With py3.5 and above, we\n    # could just use allow_abbrev=False.\n    #\n    # See https://bugs.python.org/issue14910#msg204678\n    def _get_option_tuples(self, option_string):\n        return []\n\n\n# TODO:  OPT look into making setup_parser smarter to become faster\n# Now it seems to take up to 200ms to do all the parser setup\n# even though it might not be necessary to know about all the commands etc.\n# I wondered if it could somehow decide on what commands to worry about etc\n# by going through sys.args first\ndef setup_parser(\n        cmdlineargs,\n        formatter_class=argparse.RawDescriptionHelpFormatter,\n        return_subparsers=False):\n    lgr.log(5, \"Starting to setup_parser\")\n    # delay since it can be a heavy import\n    from ..interface.base import dedent_docstring, get_interface_groups, \\\n        get_cmdline_command_name, alter_interface_docs_for_cmdline\n    # setup cmdline args parser\n    parts = {}\n    # main parser\n    parser = ArgumentParserDisableAbbrev(\n        # cannot use '@' because we need to input JSON-LD properties (which might come wit @ prefix)\n        # MH: question, do we need this at all?\n        fromfile_prefix_chars=':',\n        # usage=\"%(prog)s ...\",\n        description=dedent_docstring(\"\"\"\\\n            Comprehensive data management solution\n\n            DataLad provides a unified data distribution system built on the Git\n            and Git-annex. DataLad command line tools allow to manipulate (obtain,\n            create, update, publish, etc.) datasets and provide a comprehensive\n            toolbox for joint management of data and code. Compared to Git/annex\n            it primarly extends their functionality to transparently and\n            simultaneously work with multiple inter-related repositories.\"\"\"),\n        epilog='\"Be happy!\"',\n        formatter_class=formatter_class,\n        add_help=False)\n    # common options\n    helpers.parser_add_common_opt(parser, 'log_level')\n    helpers.parser_add_common_opt(parser, 'pbs_runner')\n    helpers.parser_add_common_opt(parser, 'change_path')\n    helpers.parser_add_common_opt(\n        parser,\n        'version',\n        version='datalad %s\\n\\n%s' % (datalad.__version__, _license_info()))\n    if __debug__:\n        parser.add_argument(\n            '--dbg', action='store_true', dest='common_debug',\n            help=\"enter Python debugger when uncaught exception happens\")\n        parser.add_argument(\n            '--idbg', action='store_true', dest='common_idebug',\n            help=\"enter IPython debugger when uncaught exception happens\")\n    parser.add_argument(\n        '-c', action='append', dest='cfg_overrides', metavar='KEY=VALUE',\n        help=\"\"\"configuration variable setting. Overrides any configuration\n        read from a file, but is potentially overridden itself by configuration\n        variables in the process environment.\"\"\")\n    parser.add_argument(\n        '-f', '--output-format', dest='common_output_format',\n        default='default',\n        type=assure_unicode,\n        metavar=\"{default,json,json_pp,tailored,'<template>'}\",\n        help=\"\"\"select format for returned command results. 'default' give one line\n        per result reporting action, status, path and an optional message;\n        'json' renders a JSON object with all properties for each result (one per\n        line); 'json_pp' pretty-prints JSON spanning multiple lines; 'tailored'\n        enables a command-specific rendering style that is typically\n        tailored to human consumption (no result output otherwise),\n        '<template>' reports any value(s) of any result properties in any format\n        indicated by the template (e.g. '{path}'; compare with JSON\n        output for all key-value choices). The template syntax follows the Python\n        \"format() language\". It is possible to report individual\n        dictionary values, e.g. '{metadata[name]}'. If a 2nd-level key contains\n        a colon, e.g. 'music:Genre', ':' must be substituted by '#' in the template,\n        like so: '{metadata[music#Genre]}'.\"\"\")\n    parser.add_argument(\n        '--report-status', dest='common_report_status',\n        choices=['success', 'failure', 'ok', 'notneeded', 'impossible', 'error'],\n        help=\"\"\"constrain command result report to records matching the given\n        status. 'success' is a synonym for 'ok' OR 'notneeded', 'failure' stands\n        for 'impossible' OR 'error'.\"\"\")\n    parser.add_argument(\n        '--report-type', dest='common_report_type',\n        choices=['dataset', 'file'],\n        action='append',\n        help=\"\"\"constrain command result report to records matching the given\n        type. Can be given more than once to match multiple types.\"\"\")\n    parser.add_argument(\n        '--on-failure', dest='common_on_failure',\n        choices=['ignore', 'continue', 'stop'],\n        # no default: better be configure per-command\n        help=\"\"\"when an operation fails: 'ignore' and continue with remaining\n        operations, the error is logged but does not lead to a non-zero exit code\n        of the command; 'continue' works like 'ignore', but an error causes a\n        non-zero exit code; 'stop' halts on first failure and yields non-zero exit\n        code. A failure is any result with status 'impossible' or 'error'.\"\"\")\n    parser.add_argument(\n        '--proc-pre', dest='common_proc_pre',\n        nargs='+',\n        action='append',\n        metavar=('<PROCEDURE NAME>', 'ARGS'),\n        help=\"\"\"Dataset procedure to run before the main command (see run-procedure\n        command for details). This option can be given more than once to run\n        multiple procedures in the order in which they were given.\n        It is important to specify the target dataset via the --dataset argument\n        of the main command.\"\"\"),\n    parser.add_argument(\n        '--proc-post', dest='common_proc_post',\n        nargs='+',\n        action='append',\n        metavar=('<PROCEDURE NAME>', 'ARGS'),\n        help=\"\"\"Like --proc-pre, but procedures are executed after the main command\n        has finished.\"\"\"),\n    parser.add_argument(\n        '--cmd', dest='_', action='store_true',\n        help=\"\"\"syntactical helper that can be used to end the list of global\n        command line options before the subcommand label. Options like\n        --proc-pre can take an arbitrary number of arguments and may require\n        to be followed by a single --cmd in order to enable identification\n        of the subcommand.\"\"\")\n\n    # yoh: atm we only dump to console.  Might adopt the same separation later on\n    #      and for consistency will call it --verbose-level as well for now\n    # log-level is set via common_opts ATM\n    # parser.add_argument('--log-level',\n    #                     choices=('critical', 'error', 'warning', 'info', 'debug'),\n    #                     dest='common_log_level',\n    #                     help=\"\"\"level of verbosity in log files. By default\n    #                          everything, including debug messages is logged.\"\"\")\n    #parser.add_argument('-l', '--verbose-level',\n    #                    choices=('critical', 'error', 'warning', 'info', 'debug'),\n    #                    dest='common_verbose_level',\n    #                    help=\"\"\"level of verbosity of console output. By default\n    #                         only warnings and errors are printed.\"\"\")\n\n    # Before doing anything additional and possibly expensive see may be that\n    # we have got the command already\n    need_single_subparser = False if return_subparsers else None\n    fail_handler = (lambda *a, **kw: True) \\\n        if return_subparsers else fail_with_short_help\n    try:\n        parsed_args, unparsed_args = parser._parse_known_args(\n            cmdlineargs[1:], argparse.Namespace())\n        if not unparsed_args:\n            fail_handler(parser, msg=\"too few arguments\", exit_code=2)\n        lgr.debug(\"Command line args 1st pass. Parsed: %s Unparsed: %s\",\n                  parsed_args, unparsed_args)\n    except Exception as exc:\n        lgr.debug(\"Early parsing failed with %s\", exc_str(exc))\n        need_single_subparser = False\n        unparsed_args = cmdlineargs[1:]  # referenced before assignment otherwise\n\n    interface_groups = get_interface_groups()\n    # TODO: see if we could retain \"generator\" for plugins\n    # ATM we need to make it explicit so we could check the command(s) below\n    # It could at least follow the same destiny as extensions so we would\n    # just do more iterative \"load ups\"\n    interface_groups.append(('plugins', 'Plugins', list(_get_plugins())))\n\n    # First unparsed could be either unknown option to top level \"datalad\"\n    # or a command. Among unknown could be --help/--help-np which would\n    # need to be dealt with\n    unparsed_arg = unparsed_args[0] if unparsed_args else None\n    if need_single_subparser is not None \\\n            or unparsed_arg in ('--help', '--help-np', '-h'):\n        need_single_subparser = False\n        add_entrypoints_to_interface_groups(interface_groups)\n    elif unparsed_arg.startswith('-'):  # unknown option\n        fail_with_short_help(parser,\n                             msg=\"unrecognized argument %s\" % unparsed_arg,\n                             exit_code=2)\n        # if we could get a list of options known to parser,\n        # we could suggest them\n        # known=get_all_options(parser), provided=unparsed_arg)\n    else:  # the command to handle\n        known_commands = get_commands_from_groups(interface_groups)\n        if unparsed_arg not in known_commands:\n            # need to load all the extensions and try again\n            add_entrypoints_to_interface_groups(interface_groups)\n            known_commands = get_commands_from_groups(interface_groups)\n\n        if unparsed_arg not in known_commands:\n            # check if might be coming from known extensions\n            from ..interface import _known_extension_commands\n            extension_commands = {\n                c: e\n                for e, commands in _known_extension_commands.items()\n                for c in commands\n            }\n            hint = None\n            if unparsed_arg in extension_commands:\n                hint = \"Command %s is provided by (not installed) extension %s.\" \\\n                      % (unparsed_arg, extension_commands[unparsed_arg])\n            fail_with_short_help(\n                parser,\n                hint=hint,\n                provided=unparsed_arg,\n                known=list(known_commands.keys()) + list(extension_commands.keys())\n            )\n        if need_single_subparser is None:\n            need_single_subparser = unparsed_arg\n\n    # --help specification was delayed since it causes immediate printout of\n    # --help output before we setup --help for each command\n    helpers.parser_add_common_opt(parser, 'help')\n\n    grp_short_descriptions = []\n    # create subparser, use module suffix as cmd name\n    subparsers = parser.add_subparsers()\n    for _, _, _interfaces \\\n            in sorted(interface_groups, key=lambda x: x[1]):\n        # for all subcommand modules it can find\n        cmd_short_descriptions = []\n\n        for _intfspec in _interfaces:\n            cmd_name = get_cmdline_command_name(_intfspec)\n            if need_single_subparser and cmd_name != need_single_subparser:\n                continue\n            if isinstance(_intfspec[1], dict):\n                # plugin\n                _intf = _load_plugin(_intfspec[1]['file'], fail=False)\n                if _intf is None:\n                    # TODO:  add doc why we could skip this one... makes this\n                    # loop harder to extract into a dedicated function\n                    continue\n            else:\n                # turn the interface spec into an instance\n                lgr.log(5, \"Importing module %s \" % _intfspec[0])\n                try:\n                    _mod = import_module(_intfspec[0], package='datalad')\n                except Exception as e:\n                    lgr.error(\"Internal error, cannot import interface '%s': %s\",\n                              _intfspec[0], exc_str(e))\n                    continue\n                _intf = getattr(_mod, _intfspec[1])\n            # deal with optional parser args\n            if hasattr(_intf, 'parser_args'):\n                parser_args = _intf.parser_args\n            else:\n                parser_args = dict(formatter_class=formatter_class)\n                # use class description, if no explicit description is available\n                intf_doc = '' if _intf.__doc__ is None else _intf.__doc__.strip()\n                if hasattr(_intf, '_docs_'):\n                    # expand docs\n                    intf_doc = intf_doc.format(**_intf._docs_)\n                parser_args['description'] = alter_interface_docs_for_cmdline(\n                    intf_doc)\n            subparser = subparsers.add_parser(cmd_name, add_help=False, **parser_args)\n            # our own custom help for all commands\n            helpers.parser_add_common_opt(subparser, 'help')\n            # let module configure the parser\n            _intf.setup_parser(subparser)\n            # logger for command\n\n            # configure 'run' function for this command\n            plumbing_args = dict(\n                func=_intf.call_from_parser,\n                logger=logging.getLogger(_intf.__module__),\n                subparser=subparser)\n            if hasattr(_intf, 'result_renderer_cmdline'):\n                plumbing_args['result_renderer'] = _intf.result_renderer_cmdline\n            subparser.set_defaults(**plumbing_args)\n            parts[cmd_name] = subparser\n            # store short description for later\n            sdescr = getattr(_intf, 'short_description',\n                             parser_args['description'].split('\\n')[0])\n            cmd_short_descriptions.append((cmd_name, sdescr))\n        grp_short_descriptions.append(cmd_short_descriptions)\n\n    # create command summary\n    if '--help' in cmdlineargs or '--help-np' in cmdlineargs:\n        parser.description = get_description_with_cmd_summary(\n            grp_short_descriptions,\n            interface_groups,\n            parser.description)\n\n    parts['datalad'] = parser\n    lgr.log(5, \"Finished setup_parser\")\n    if return_subparsers:\n        return parts\n    else:\n        return parser\n\n\ndef fail_with_short_help(parser=None,\n                         msg=None,\n                         known=None, provided=None,\n                         hint=None,\n                         exit_code=1,\n                         what=\"command\",\n                         out=None):\n    \"\"\"Generic helper to fail\n    with short help possibly hinting on what was intended if `known`\n    were provided\n    \"\"\"\n    out = out or sys.stderr\n    if msg:\n        out.write(\"error: %s\\n\" % msg)\n    if not known:\n        if parser:\n            # just to appear in print_usage also consistent with --help output\n            parser.add_argument(\"command [command-opts]\")\n            parser.print_usage(file=out)\n    else:\n        out.write(\n            \"datalad: Unknown %s %r.  See 'datalad --help'.\\n\\n\"\n            % (what, provided,))\n        if provided not in known:\n            import difflib\n            suggestions = difflib.get_close_matches(provided, known)\n            if suggestions:\n                out.write(\n                    \"Did you mean one of these?\\n        %s\\n\"\n                    % \"\\n        \".join(suggestions))\n        # Too noisy\n        # sys.stderr.write(\" All known %ss: %s\\n\"\n        #                   % (what, \", \".join(sorted(known))))\n    if hint:\n        out.write(\"Hint: %s\\n\" % hint)\n    raise SystemExit(exit_code)\n\n\ndef get_description_with_cmd_summary(grp_short_descriptions, interface_groups,\n                                     parser_description):\n    from ..interface.base import dedent_docstring\n    lgr.debug(\"Generating detailed description for the parser\")\n    cmd_summary = []\n    console_width = get_console_width()\n    for i, grp in enumerate(\n            sorted(interface_groups, key=lambda x: x[1])):\n        grp_descr = grp[1]\n        grp_cmds = grp_short_descriptions[i]\n\n        cmd_summary.append('\\n*%s*\\n' % (grp_descr,))\n        for cd in grp_cmds:\n            cmd_summary.append('  %s\\n%s'\n                               % ((cd[0],\n                                   textwrap.fill(\n                                       cd[1].rstrip(' .'),\n                                       console_width - 5,\n                                       initial_indent=' ' * 6,\n                                       subsequent_indent=' ' * 6))))\n    # we need one last formal section to not have the trailed be\n    # confused with the last command group\n    cmd_summary.append('\\n*General information*\\n')\n    detailed_description = '%s\\n%s\\n\\n%s' \\\n                           % (parser_description,\n                              '\\n'.join(cmd_summary),\n                              textwrap.fill(dedent_docstring(\"\"\"\\\n    Detailed usage information for individual commands is\n    available via command-specific --help, i.e.:\n    datalad <command> --help\"\"\"),\n                                            console_width - 5,\n                                            initial_indent='',\n                                            subsequent_indent=''))\n    return detailed_description\n\n\ndef get_commands_from_groups(groups):\n    \"\"\"Get a dictionary of command: interface_spec\"\"\"\n    from ..interface.base import get_cmdline_command_name\n    return {\n        get_cmdline_command_name(_intfspec): _intfspec\n        for _, _, _interfaces in groups\n        for _intfspec in _interfaces\n    }\n\n\ndef add_entrypoints_to_interface_groups(interface_groups):\n    lgr.debug(\"Loading entrypoints\")\n    from pkg_resources import iter_entry_points  # delay expensive import\n    for ep in iter_entry_points('datalad.extensions'):\n        lgr.debug(\n            'Loading entrypoint %s from datalad.extensions for docs building',\n            ep.name)\n        try:\n            spec = ep.load()\n            interface_groups.append((ep.name, spec[0], spec[1]))\n            lgr.debug('Loaded entrypoint %s', ep.name)\n        except Exception as e:\n            lgr.warning('Failed to load entrypoint %s: %s', ep.name, exc_str(e))\n            continue\n\ndef _fix_datalad_ri(s):\n    \"\"\"Fixup argument if it was a DataLadRI and had leading / removed\n\n    See gh-2643\n    \"\"\"\n    if s.startswith('//') and (len(s) == 2 or (len(s) > 2 and s[2] != '/')):\n        lgr.info(\n            \"Changing %s back to /%s as it was probably changed by MINGW/MSYS, \"\n            \"see http://www.mingw.org/wiki/Posix_path_conversion\", s, s)\n        return \"/\" + s\n    return s\n\n\ndef main(args=None):\n    lgr.log(5, \"Starting main(%r)\", args)\n    args = args or sys.argv\n    if on_msys_tainted_paths:\n        # Possibly present DataLadRIs were stripped of a leading /\n        args = [_fix_datalad_ri(s) for s in args]\n    # PYTHON_ARGCOMPLETE_OK\n    parser = setup_parser(args)\n    try:\n        import argcomplete\n        argcomplete.autocomplete(parser)\n    except ImportError:\n        pass\n\n    # parse cmd args\n    lgr.debug(\"Parsing known args among %s\", repr(args))\n    cmdlineargs, unparsed_args = parser.parse_known_args(args[1:])\n    has_func = hasattr(cmdlineargs, 'func') and cmdlineargs.func is not None\n    if unparsed_args:\n        if has_func and cmdlineargs.func.__self__.__name__ != 'Export':\n            lgr.error('unknown argument{}: {}'.format(\n                's' if len(unparsed_args) > 1 else '',\n                unparsed_args if len(unparsed_args) > 1 else unparsed_args[0]))\n            cmdlineargs.subparser.print_usage()\n            sys.exit(1)\n        else:\n            # store all unparsed arguments\n            cmdlineargs.datalad_unparsed_args = unparsed_args\n\n    # to possibly be passed into PBS scheduled call\n    args_ = args or sys.argv\n\n    if cmdlineargs.cfg_overrides is not None:\n        overrides = dict([\n            (o.split('=')[0], '='.join(o.split('=')[1:]))\n            for o in cmdlineargs.cfg_overrides])\n        datalad.cfg.overrides.update(overrides)\n\n    # enable overrides\n    datalad.cfg.reload(force=True)\n\n    if cmdlineargs.change_path is not None:\n        from .common_args import change_path as change_path_opt\n        for path in cmdlineargs.change_path:\n            chpwd(path)\n            args_ = strip_arg_from_argv(args_, path, change_path_opt[1])\n\n    ret = None\n    if cmdlineargs.pbs_runner:\n        from .helpers import run_via_pbs\n        from .common_args import pbs_runner as pbs_runner_opt\n        args_ = strip_arg_from_argv(args_, cmdlineargs.pbs_runner, pbs_runner_opt[1])\n        # run the function associated with the selected command\n        run_via_pbs(args_, cmdlineargs.pbs_runner)\n    elif has_func:\n        if cmdlineargs.common_debug or cmdlineargs.common_idebug:\n            # so we could see/stop clearly at the point of failure\n            setup_exceptionhook(ipython=cmdlineargs.common_idebug)\n            from datalad.interface.base import Interface\n            Interface._interrupted_exit_code = None\n            ret = cmdlineargs.func(cmdlineargs)\n        else:\n            # otherwise - guard and only log the summary. Postmortem is not\n            # as convenient if being caught in this ultimate except\n            try:\n                ret = cmdlineargs.func(cmdlineargs)\n            except InsufficientArgumentsError as exc:\n                # if the func reports inappropriate usage, give help output\n                lgr.error('%s (%s)' % (exc_str(exc), exc.__class__.__name__))\n                cmdlineargs.subparser.print_usage(sys.stderr)\n                sys.exit(2)\n            except IncompleteResultsError as exc:\n                # rendering for almost all commands now happens 'online'\n                # hence we are no longer attempting to render the actual\n                # results in an IncompleteResultsError, ubt rather trust that\n                # this happened before\n\n                # in general we do not want to see the error again, but\n                # present in debug output\n                lgr.debug('could not perform all requested actions: %s',\n                          exc_str(exc))\n                sys.exit(1)\n            except CommandError as exc:\n                # behave as if the command ran directly, importantly pass\n                # exit code as is\n                if exc.msg:\n                    msg = exc.msg.encode() if isinstance(exc.msg, text_type) else exc.msg\n                    os.write(2, msg + b\"\\n\")\n                if exc.stdout:\n                    os.write(1, exc.stdout.encode() if isinstance(exc.stdout, text_type) else exc.stdout)\n                if exc.stderr:\n                    os.write(2, exc.stderr.encode() if isinstance(exc.stderr, text_type) else exc.stderr)\n                # We must not exit with 0 code if any exception got here but\n                # had no code defined\n                sys.exit(exc.code if exc.code is not None else 1)\n            except Exception as exc:\n                lgr.error('%s (%s)' % (exc_str(exc), exc.__class__.__name__))\n                sys.exit(1)\n    else:\n        # just let argparser spit out its error, since there is smth wrong\n        parser.parse_args(args)\n        # if that one didn't puke -- we should\n        parser.print_usage()\n        lgr.error(\"Please specify the command\")\n        sys.exit(2)\n\n    try:\n        if hasattr(cmdlineargs, 'result_renderer'):\n            cmdlineargs.result_renderer(ret, cmdlineargs)\n    except Exception as exc:\n        lgr.error(\"Failed to render results due to %s\", exc_str(exc))\n        sys.exit(1)\n\nlgr.log(5, \"Done importing cmdline.main\")\n",
            "new_file_content": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 noet:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"\"\"\"\nfrom datalad.ui.utils import get_console_width\n\n__docformat__ = 'restructuredtext'\n\nimport logging\nlgr = logging.getLogger('datalad.cmdline')\n\nlgr.log(5, \"Importing cmdline.main\")\n\nimport argparse\nimport sys\nimport textwrap\nfrom importlib import import_module\nimport os\n\nfrom six import text_type\n\nimport datalad\n\nfrom datalad.cmdline import helpers\nfrom datalad.plugin import _get_plugins\nfrom datalad.plugin import _load_plugin\nfrom datalad.support.exceptions import InsufficientArgumentsError\nfrom datalad.support.exceptions import IncompleteResultsError\nfrom datalad.support.exceptions import CommandError\nfrom .helpers import strip_arg_from_argv\nfrom ..utils import setup_exceptionhook, chpwd\nfrom ..utils import assure_unicode\nfrom ..utils import on_msys_tainted_paths\nfrom ..dochelpers import exc_str\n\n\ndef _license_info():\n    return \"\"\"\\\nCopyright (c) 2013-2018 DataLad developers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\"\"\"\n\n\nclass ArgumentParserDisableAbbrev(argparse.ArgumentParser):\n    # Don't accept abbreviations for long options. With py3.5 and above, we\n    # could just use allow_abbrev=False.\n    #\n    # Modified from the solution posted at\n    # https://bugs.python.org/issue14910#msg204678\n    def _get_option_tuples(self, option_string):\n        chars = self.prefix_chars\n        if option_string[0] in chars and option_string[1] in chars:\n            # option_string is a long flag. Disable abbreviation.\n            return []\n        return super(ArgumentParserDisableAbbrev, self)._get_option_tuples(\n            option_string)\n\n\n# TODO:  OPT look into making setup_parser smarter to become faster\n# Now it seems to take up to 200ms to do all the parser setup\n# even though it might not be necessary to know about all the commands etc.\n# I wondered if it could somehow decide on what commands to worry about etc\n# by going through sys.args first\ndef setup_parser(\n        cmdlineargs,\n        formatter_class=argparse.RawDescriptionHelpFormatter,\n        return_subparsers=False):\n    lgr.log(5, \"Starting to setup_parser\")\n    # delay since it can be a heavy import\n    from ..interface.base import dedent_docstring, get_interface_groups, \\\n        get_cmdline_command_name, alter_interface_docs_for_cmdline\n    # setup cmdline args parser\n    parts = {}\n    # main parser\n    parser = ArgumentParserDisableAbbrev(\n        # cannot use '@' because we need to input JSON-LD properties (which might come wit @ prefix)\n        # MH: question, do we need this at all?\n        fromfile_prefix_chars=':',\n        # usage=\"%(prog)s ...\",\n        description=dedent_docstring(\"\"\"\\\n            Comprehensive data management solution\n\n            DataLad provides a unified data distribution system built on the Git\n            and Git-annex. DataLad command line tools allow to manipulate (obtain,\n            create, update, publish, etc.) datasets and provide a comprehensive\n            toolbox for joint management of data and code. Compared to Git/annex\n            it primarly extends their functionality to transparently and\n            simultaneously work with multiple inter-related repositories.\"\"\"),\n        epilog='\"Be happy!\"',\n        formatter_class=formatter_class,\n        add_help=False)\n    # common options\n    helpers.parser_add_common_opt(parser, 'log_level')\n    helpers.parser_add_common_opt(parser, 'pbs_runner')\n    helpers.parser_add_common_opt(parser, 'change_path')\n    helpers.parser_add_common_opt(\n        parser,\n        'version',\n        version='datalad %s\\n\\n%s' % (datalad.__version__, _license_info()))\n    if __debug__:\n        parser.add_argument(\n            '--dbg', action='store_true', dest='common_debug',\n            help=\"enter Python debugger when uncaught exception happens\")\n        parser.add_argument(\n            '--idbg', action='store_true', dest='common_idebug',\n            help=\"enter IPython debugger when uncaught exception happens\")\n    parser.add_argument(\n        '-c', action='append', dest='cfg_overrides', metavar='KEY=VALUE',\n        help=\"\"\"configuration variable setting. Overrides any configuration\n        read from a file, but is potentially overridden itself by configuration\n        variables in the process environment.\"\"\")\n    parser.add_argument(\n        '-f', '--output-format', dest='common_output_format',\n        default='default',\n        type=assure_unicode,\n        metavar=\"{default,json,json_pp,tailored,'<template>'}\",\n        help=\"\"\"select format for returned command results. 'default' give one line\n        per result reporting action, status, path and an optional message;\n        'json' renders a JSON object with all properties for each result (one per\n        line); 'json_pp' pretty-prints JSON spanning multiple lines; 'tailored'\n        enables a command-specific rendering style that is typically\n        tailored to human consumption (no result output otherwise),\n        '<template>' reports any value(s) of any result properties in any format\n        indicated by the template (e.g. '{path}'; compare with JSON\n        output for all key-value choices). The template syntax follows the Python\n        \"format() language\". It is possible to report individual\n        dictionary values, e.g. '{metadata[name]}'. If a 2nd-level key contains\n        a colon, e.g. 'music:Genre', ':' must be substituted by '#' in the template,\n        like so: '{metadata[music#Genre]}'.\"\"\")\n    parser.add_argument(\n        '--report-status', dest='common_report_status',\n        choices=['success', 'failure', 'ok', 'notneeded', 'impossible', 'error'],\n        help=\"\"\"constrain command result report to records matching the given\n        status. 'success' is a synonym for 'ok' OR 'notneeded', 'failure' stands\n        for 'impossible' OR 'error'.\"\"\")\n    parser.add_argument(\n        '--report-type', dest='common_report_type',\n        choices=['dataset', 'file'],\n        action='append',\n        help=\"\"\"constrain command result report to records matching the given\n        type. Can be given more than once to match multiple types.\"\"\")\n    parser.add_argument(\n        '--on-failure', dest='common_on_failure',\n        choices=['ignore', 'continue', 'stop'],\n        # no default: better be configure per-command\n        help=\"\"\"when an operation fails: 'ignore' and continue with remaining\n        operations, the error is logged but does not lead to a non-zero exit code\n        of the command; 'continue' works like 'ignore', but an error causes a\n        non-zero exit code; 'stop' halts on first failure and yields non-zero exit\n        code. A failure is any result with status 'impossible' or 'error'.\"\"\")\n    parser.add_argument(\n        '--proc-pre', dest='common_proc_pre',\n        nargs='+',\n        action='append',\n        metavar=('<PROCEDURE NAME>', 'ARGS'),\n        help=\"\"\"Dataset procedure to run before the main command (see run-procedure\n        command for details). This option can be given more than once to run\n        multiple procedures in the order in which they were given.\n        It is important to specify the target dataset via the --dataset argument\n        of the main command.\"\"\"),\n    parser.add_argument(\n        '--proc-post', dest='common_proc_post',\n        nargs='+',\n        action='append',\n        metavar=('<PROCEDURE NAME>', 'ARGS'),\n        help=\"\"\"Like --proc-pre, but procedures are executed after the main command\n        has finished.\"\"\"),\n    parser.add_argument(\n        '--cmd', dest='_', action='store_true',\n        help=\"\"\"syntactical helper that can be used to end the list of global\n        command line options before the subcommand label. Options like\n        --proc-pre can take an arbitrary number of arguments and may require\n        to be followed by a single --cmd in order to enable identification\n        of the subcommand.\"\"\")\n\n    # yoh: atm we only dump to console.  Might adopt the same separation later on\n    #      and for consistency will call it --verbose-level as well for now\n    # log-level is set via common_opts ATM\n    # parser.add_argument('--log-level',\n    #                     choices=('critical', 'error', 'warning', 'info', 'debug'),\n    #                     dest='common_log_level',\n    #                     help=\"\"\"level of verbosity in log files. By default\n    #                          everything, including debug messages is logged.\"\"\")\n    #parser.add_argument('-l', '--verbose-level',\n    #                    choices=('critical', 'error', 'warning', 'info', 'debug'),\n    #                    dest='common_verbose_level',\n    #                    help=\"\"\"level of verbosity of console output. By default\n    #                         only warnings and errors are printed.\"\"\")\n\n    # Before doing anything additional and possibly expensive see may be that\n    # we have got the command already\n    need_single_subparser = False if return_subparsers else None\n    fail_handler = (lambda *a, **kw: True) \\\n        if return_subparsers else fail_with_short_help\n    try:\n        parsed_args, unparsed_args = parser._parse_known_args(\n            cmdlineargs[1:], argparse.Namespace())\n        if not unparsed_args:\n            fail_handler(parser, msg=\"too few arguments\", exit_code=2)\n        lgr.debug(\"Command line args 1st pass. Parsed: %s Unparsed: %s\",\n                  parsed_args, unparsed_args)\n    except Exception as exc:\n        lgr.debug(\"Early parsing failed with %s\", exc_str(exc))\n        need_single_subparser = False\n        unparsed_args = cmdlineargs[1:]  # referenced before assignment otherwise\n\n    interface_groups = get_interface_groups()\n    # TODO: see if we could retain \"generator\" for plugins\n    # ATM we need to make it explicit so we could check the command(s) below\n    # It could at least follow the same destiny as extensions so we would\n    # just do more iterative \"load ups\"\n    interface_groups.append(('plugins', 'Plugins', list(_get_plugins())))\n\n    # First unparsed could be either unknown option to top level \"datalad\"\n    # or a command. Among unknown could be --help/--help-np which would\n    # need to be dealt with\n    unparsed_arg = unparsed_args[0] if unparsed_args else None\n    if need_single_subparser is not None \\\n            or unparsed_arg in ('--help', '--help-np', '-h'):\n        need_single_subparser = False\n        add_entrypoints_to_interface_groups(interface_groups)\n    elif unparsed_arg.startswith('-'):  # unknown option\n        fail_with_short_help(parser,\n                             msg=\"unrecognized argument %s\" % unparsed_arg,\n                             exit_code=2)\n        # if we could get a list of options known to parser,\n        # we could suggest them\n        # known=get_all_options(parser), provided=unparsed_arg)\n    else:  # the command to handle\n        known_commands = get_commands_from_groups(interface_groups)\n        if unparsed_arg not in known_commands:\n            # need to load all the extensions and try again\n            add_entrypoints_to_interface_groups(interface_groups)\n            known_commands = get_commands_from_groups(interface_groups)\n\n        if unparsed_arg not in known_commands:\n            # check if might be coming from known extensions\n            from ..interface import _known_extension_commands\n            extension_commands = {\n                c: e\n                for e, commands in _known_extension_commands.items()\n                for c in commands\n            }\n            hint = None\n            if unparsed_arg in extension_commands:\n                hint = \"Command %s is provided by (not installed) extension %s.\" \\\n                      % (unparsed_arg, extension_commands[unparsed_arg])\n            fail_with_short_help(\n                parser,\n                hint=hint,\n                provided=unparsed_arg,\n                known=list(known_commands.keys()) + list(extension_commands.keys())\n            )\n        if need_single_subparser is None:\n            need_single_subparser = unparsed_arg\n\n    # --help specification was delayed since it causes immediate printout of\n    # --help output before we setup --help for each command\n    helpers.parser_add_common_opt(parser, 'help')\n\n    grp_short_descriptions = []\n    # create subparser, use module suffix as cmd name\n    subparsers = parser.add_subparsers()\n    for _, _, _interfaces \\\n            in sorted(interface_groups, key=lambda x: x[1]):\n        # for all subcommand modules it can find\n        cmd_short_descriptions = []\n\n        for _intfspec in _interfaces:\n            cmd_name = get_cmdline_command_name(_intfspec)\n            if need_single_subparser and cmd_name != need_single_subparser:\n                continue\n            if isinstance(_intfspec[1], dict):\n                # plugin\n                _intf = _load_plugin(_intfspec[1]['file'], fail=False)\n                if _intf is None:\n                    # TODO:  add doc why we could skip this one... makes this\n                    # loop harder to extract into a dedicated function\n                    continue\n            else:\n                # turn the interface spec into an instance\n                lgr.log(5, \"Importing module %s \" % _intfspec[0])\n                try:\n                    _mod = import_module(_intfspec[0], package='datalad')\n                except Exception as e:\n                    lgr.error(\"Internal error, cannot import interface '%s': %s\",\n                              _intfspec[0], exc_str(e))\n                    continue\n                _intf = getattr(_mod, _intfspec[1])\n            # deal with optional parser args\n            if hasattr(_intf, 'parser_args'):\n                parser_args = _intf.parser_args\n            else:\n                parser_args = dict(formatter_class=formatter_class)\n                # use class description, if no explicit description is available\n                intf_doc = '' if _intf.__doc__ is None else _intf.__doc__.strip()\n                if hasattr(_intf, '_docs_'):\n                    # expand docs\n                    intf_doc = intf_doc.format(**_intf._docs_)\n                parser_args['description'] = alter_interface_docs_for_cmdline(\n                    intf_doc)\n            subparser = subparsers.add_parser(cmd_name, add_help=False, **parser_args)\n            # our own custom help for all commands\n            helpers.parser_add_common_opt(subparser, 'help')\n            # let module configure the parser\n            _intf.setup_parser(subparser)\n            # logger for command\n\n            # configure 'run' function for this command\n            plumbing_args = dict(\n                func=_intf.call_from_parser,\n                logger=logging.getLogger(_intf.__module__),\n                subparser=subparser)\n            if hasattr(_intf, 'result_renderer_cmdline'):\n                plumbing_args['result_renderer'] = _intf.result_renderer_cmdline\n            subparser.set_defaults(**plumbing_args)\n            parts[cmd_name] = subparser\n            # store short description for later\n            sdescr = getattr(_intf, 'short_description',\n                             parser_args['description'].split('\\n')[0])\n            cmd_short_descriptions.append((cmd_name, sdescr))\n        grp_short_descriptions.append(cmd_short_descriptions)\n\n    # create command summary\n    if '--help' in cmdlineargs or '--help-np' in cmdlineargs:\n        parser.description = get_description_with_cmd_summary(\n            grp_short_descriptions,\n            interface_groups,\n            parser.description)\n\n    parts['datalad'] = parser\n    lgr.log(5, \"Finished setup_parser\")\n    if return_subparsers:\n        return parts\n    else:\n        return parser\n\n\ndef fail_with_short_help(parser=None,\n                         msg=None,\n                         known=None, provided=None,\n                         hint=None,\n                         exit_code=1,\n                         what=\"command\",\n                         out=None):\n    \"\"\"Generic helper to fail\n    with short help possibly hinting on what was intended if `known`\n    were provided\n    \"\"\"\n    out = out or sys.stderr\n    if msg:\n        out.write(\"error: %s\\n\" % msg)\n    if not known:\n        if parser:\n            # just to appear in print_usage also consistent with --help output\n            parser.add_argument(\"command [command-opts]\")\n            parser.print_usage(file=out)\n    else:\n        out.write(\n            \"datalad: Unknown %s %r.  See 'datalad --help'.\\n\\n\"\n            % (what, provided,))\n        if provided not in known:\n            import difflib\n            suggestions = difflib.get_close_matches(provided, known)\n            if suggestions:\n                out.write(\n                    \"Did you mean one of these?\\n        %s\\n\"\n                    % \"\\n        \".join(suggestions))\n        # Too noisy\n        # sys.stderr.write(\" All known %ss: %s\\n\"\n        #                   % (what, \", \".join(sorted(known))))\n    if hint:\n        out.write(\"Hint: %s\\n\" % hint)\n    raise SystemExit(exit_code)\n\n\ndef get_description_with_cmd_summary(grp_short_descriptions, interface_groups,\n                                     parser_description):\n    from ..interface.base import dedent_docstring\n    lgr.debug(\"Generating detailed description for the parser\")\n    cmd_summary = []\n    console_width = get_console_width()\n    for i, grp in enumerate(\n            sorted(interface_groups, key=lambda x: x[1])):\n        grp_descr = grp[1]\n        grp_cmds = grp_short_descriptions[i]\n\n        cmd_summary.append('\\n*%s*\\n' % (grp_descr,))\n        for cd in grp_cmds:\n            cmd_summary.append('  %s\\n%s'\n                               % ((cd[0],\n                                   textwrap.fill(\n                                       cd[1].rstrip(' .'),\n                                       console_width - 5,\n                                       initial_indent=' ' * 6,\n                                       subsequent_indent=' ' * 6))))\n    # we need one last formal section to not have the trailed be\n    # confused with the last command group\n    cmd_summary.append('\\n*General information*\\n')\n    detailed_description = '%s\\n%s\\n\\n%s' \\\n                           % (parser_description,\n                              '\\n'.join(cmd_summary),\n                              textwrap.fill(dedent_docstring(\"\"\"\\\n    Detailed usage information for individual commands is\n    available via command-specific --help, i.e.:\n    datalad <command> --help\"\"\"),\n                                            console_width - 5,\n                                            initial_indent='',\n                                            subsequent_indent=''))\n    return detailed_description\n\n\ndef get_commands_from_groups(groups):\n    \"\"\"Get a dictionary of command: interface_spec\"\"\"\n    from ..interface.base import get_cmdline_command_name\n    return {\n        get_cmdline_command_name(_intfspec): _intfspec\n        for _, _, _interfaces in groups\n        for _intfspec in _interfaces\n    }\n\n\ndef add_entrypoints_to_interface_groups(interface_groups):\n    lgr.debug(\"Loading entrypoints\")\n    from pkg_resources import iter_entry_points  # delay expensive import\n    for ep in iter_entry_points('datalad.extensions'):\n        lgr.debug(\n            'Loading entrypoint %s from datalad.extensions for docs building',\n            ep.name)\n        try:\n            spec = ep.load()\n            interface_groups.append((ep.name, spec[0], spec[1]))\n            lgr.debug('Loaded entrypoint %s', ep.name)\n        except Exception as e:\n            lgr.warning('Failed to load entrypoint %s: %s', ep.name, exc_str(e))\n            continue\n\ndef _fix_datalad_ri(s):\n    \"\"\"Fixup argument if it was a DataLadRI and had leading / removed\n\n    See gh-2643\n    \"\"\"\n    if s.startswith('//') and (len(s) == 2 or (len(s) > 2 and s[2] != '/')):\n        lgr.info(\n            \"Changing %s back to /%s as it was probably changed by MINGW/MSYS, \"\n            \"see http://www.mingw.org/wiki/Posix_path_conversion\", s, s)\n        return \"/\" + s\n    return s\n\n\ndef main(args=None):\n    lgr.log(5, \"Starting main(%r)\", args)\n    args = args or sys.argv\n    if on_msys_tainted_paths:\n        # Possibly present DataLadRIs were stripped of a leading /\n        args = [_fix_datalad_ri(s) for s in args]\n    # PYTHON_ARGCOMPLETE_OK\n    parser = setup_parser(args)\n    try:\n        import argcomplete\n        argcomplete.autocomplete(parser)\n    except ImportError:\n        pass\n\n    # parse cmd args\n    lgr.debug(\"Parsing known args among %s\", repr(args))\n    cmdlineargs, unparsed_args = parser.parse_known_args(args[1:])\n    has_func = hasattr(cmdlineargs, 'func') and cmdlineargs.func is not None\n    if unparsed_args:\n        if has_func and cmdlineargs.func.__self__.__name__ != 'Export':\n            lgr.error('unknown argument{}: {}'.format(\n                's' if len(unparsed_args) > 1 else '',\n                unparsed_args if len(unparsed_args) > 1 else unparsed_args[0]))\n            cmdlineargs.subparser.print_usage()\n            sys.exit(1)\n        else:\n            # store all unparsed arguments\n            cmdlineargs.datalad_unparsed_args = unparsed_args\n\n    # to possibly be passed into PBS scheduled call\n    args_ = args or sys.argv\n\n    if cmdlineargs.cfg_overrides is not None:\n        overrides = dict([\n            (o.split('=')[0], '='.join(o.split('=')[1:]))\n            for o in cmdlineargs.cfg_overrides])\n        datalad.cfg.overrides.update(overrides)\n\n    # enable overrides\n    datalad.cfg.reload(force=True)\n\n    if cmdlineargs.change_path is not None:\n        from .common_args import change_path as change_path_opt\n        for path in cmdlineargs.change_path:\n            chpwd(path)\n            args_ = strip_arg_from_argv(args_, path, change_path_opt[1])\n\n    ret = None\n    if cmdlineargs.pbs_runner:\n        from .helpers import run_via_pbs\n        from .common_args import pbs_runner as pbs_runner_opt\n        args_ = strip_arg_from_argv(args_, cmdlineargs.pbs_runner, pbs_runner_opt[1])\n        # run the function associated with the selected command\n        run_via_pbs(args_, cmdlineargs.pbs_runner)\n    elif has_func:\n        if cmdlineargs.common_debug or cmdlineargs.common_idebug:\n            # so we could see/stop clearly at the point of failure\n            setup_exceptionhook(ipython=cmdlineargs.common_idebug)\n            from datalad.interface.base import Interface\n            Interface._interrupted_exit_code = None\n            ret = cmdlineargs.func(cmdlineargs)\n        else:\n            # otherwise - guard and only log the summary. Postmortem is not\n            # as convenient if being caught in this ultimate except\n            try:\n                ret = cmdlineargs.func(cmdlineargs)\n            except InsufficientArgumentsError as exc:\n                # if the func reports inappropriate usage, give help output\n                lgr.error('%s (%s)' % (exc_str(exc), exc.__class__.__name__))\n                cmdlineargs.subparser.print_usage(sys.stderr)\n                sys.exit(2)\n            except IncompleteResultsError as exc:\n                # rendering for almost all commands now happens 'online'\n                # hence we are no longer attempting to render the actual\n                # results in an IncompleteResultsError, ubt rather trust that\n                # this happened before\n\n                # in general we do not want to see the error again, but\n                # present in debug output\n                lgr.debug('could not perform all requested actions: %s',\n                          exc_str(exc))\n                sys.exit(1)\n            except CommandError as exc:\n                # behave as if the command ran directly, importantly pass\n                # exit code as is\n                if exc.msg:\n                    msg = exc.msg.encode() if isinstance(exc.msg, text_type) else exc.msg\n                    os.write(2, msg + b\"\\n\")\n                if exc.stdout:\n                    os.write(1, exc.stdout.encode() if isinstance(exc.stdout, text_type) else exc.stdout)\n                if exc.stderr:\n                    os.write(2, exc.stderr.encode() if isinstance(exc.stderr, text_type) else exc.stderr)\n                # We must not exit with 0 code if any exception got here but\n                # had no code defined\n                sys.exit(exc.code if exc.code is not None else 1)\n            except Exception as exc:\n                lgr.error('%s (%s)' % (exc_str(exc), exc.__class__.__name__))\n                sys.exit(1)\n    else:\n        # just let argparser spit out its error, since there is smth wrong\n        parser.parse_args(args)\n        # if that one didn't puke -- we should\n        parser.print_usage()\n        lgr.error(\"Please specify the command\")\n        sys.exit(2)\n\n    try:\n        if hasattr(cmdlineargs, 'result_renderer'):\n            cmdlineargs.result_renderer(ret, cmdlineargs)\n    except Exception as exc:\n        lgr.error(\"Failed to render results due to %s\", exc_str(exc))\n        sys.exit(1)\n\nlgr.log(5, \"Done importing cmdline.main\")\n",
            "header": {
                "file": {
                    "path": "datalad/cmdline/main.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "cf45c3830",
                "new_commit_hash": "bbb7d492b",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/datalad/cmdline/main.py"
            },
            "plus_file": {
                "path": "b/datalad/cmdline/main.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 67,
                            "length": 9
                        },
                        "new_range": {
                            "start": 67,
                            "length": 15
                        },
                        "section": "class ArgumentParserDisableAbbrev(argparse.ArgumentParser):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    # Don't accept abbreviations for long options. With py3.5 and above, we",
                                "type": "context"
                            },
                            {
                                "content": "    # could just use allow_abbrev=False.",
                                "type": "context"
                            },
                            {
                                "content": "    #",
                                "type": "context"
                            },
                            {
                                "content": "    # See https://bugs.python.org/issue14910#msg204678",
                                "type": "deleted"
                            },
                            {
                                "content": "    # Modified from the solution posted at",
                                "type": "added"
                            },
                            {
                                "content": "    # https://bugs.python.org/issue14910#msg204678",
                                "type": "added"
                            },
                            {
                                "content": "    def _get_option_tuples(self, option_string):",
                                "type": "context"
                            },
                            {
                                "content": "        return []",
                                "type": "deleted"
                            },
                            {
                                "content": "        chars = self.prefix_chars",
                                "type": "added"
                            },
                            {
                                "content": "        if option_string[0] in chars and option_string[1] in chars:",
                                "type": "added"
                            },
                            {
                                "content": "            # option_string is a long flag. Disable abbreviation.",
                                "type": "added"
                            },
                            {
                                "content": "            return []",
                                "type": "added"
                            },
                            {
                                "content": "        return super(ArgumentParserDisableAbbrev, self)._get_option_tuples(",
                                "type": "added"
                            },
                            {
                                "content": "            option_string)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "# TODO:  OPT look into making setup_parser smarter to become faster",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/cmdline/main.py",
                            "type": "class",
                            "name": "ArgumentParserDisableAbbrev",
                            "content": "class ArgumentParserDisableAbbrev(argparse.ArgumentParser):\n    # Don't accept abbreviations for long options. With py3.5 and above, we\n    # could just use allow_abbrev=False.\n    #\n    # See https://bugs.python.org/issue14910#msg204678\n    def _get_option_tuples(self, option_string):\n        return []",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 66,
                            "end_lineno": 72,
                            "parent": null
                        },
                        {
                            "file_name": "datalad/cmdline/main.py",
                            "type": "method",
                            "name": "ArgumentParserDisableAbbrev._get_option_tuples",
                            "content": "class ArgumentParserDisableAbbrev(argparse.ArgumentParser):\n    # Don't accept abbreviations for long options. With py3.5 and above, we\n    # could just use allow_abbrev=False.\n    #\n    # See https://bugs.python.org/issue14910#msg204678\n    def _get_option_tuples(self, option_string):\n        return []",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 71,
                            "end_lineno": 72,
                            "parent": {
                                "file_name": "datalad/cmdline/main.py",
                                "type": "class",
                                "name": "ArgumentParserDisableAbbrev",
                                "content": "class ArgumentParserDisableAbbrev(argparse.ArgumentParser):\n    # Don't accept abbreviations for long options. With py3.5 and above, we\n    # could just use allow_abbrev=False.\n    #\n    # See https://bugs.python.org/issue14910#msg204678\n    def _get_option_tuples(self, option_string):\n        return []",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 66,
                                "end_lineno": 72,
                                "parent": null
                            }
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "# emacs: -*- mode: python-mode; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 noet:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Test functioning of the datalad main cmdline utility \"\"\"\n\nfrom datalad.tests.utils import known_failure_v6\nfrom datalad.tests.utils import known_failure_direct_mode\nfrom datalad.tests.utils import skip_if_on_windows\n\n\nimport re\nimport sys\nfrom six.moves import StringIO\nfrom mock import patch\n\nimport datalad\nfrom ..main import (\n    main,\n    fail_with_short_help,\n    _fix_datalad_ri,\n)\nfrom datalad import __version__\nfrom datalad.cmd import Runner\nfrom datalad.ui.utils import get_console_width\nfrom datalad.api import create\nfrom datalad.utils import chpwd\nfrom datalad.tests.utils import with_tempfile\nfrom datalad.tests.utils import assert_equal, assert_raises, in_, ok_startswith\nfrom datalad.tests.utils import assert_in\nfrom datalad.tests.utils import assert_re_in\nfrom datalad.tests.utils import assert_not_in\nfrom datalad.tests.utils import slow\n\n\ndef run_main(args, exit_code=0, expect_stderr=False):\n    \"\"\"Run main() of the datalad, do basic checks and provide outputs\n\n    Parameters\n    ----------\n    args : list\n        List of string cmdline arguments to pass\n    exit_code : int\n        Expected exit code. Would raise AssertionError if differs\n    expect_stderr : bool or string\n        Either to expect stderr output. If string -- match\n\n    Returns\n    -------\n    stdout, stderr  strings\n       Output produced\n    \"\"\"\n    with patch('sys.stderr', new_callable=StringIO) as cmerr:\n        with patch('sys.stdout', new_callable=StringIO) as cmout:\n            with assert_raises(SystemExit) as cm:\n                main([\"datalad\"] + list(args))\n            assert_equal(cm.exception.code, exit_code)\n            stdout = cmout.getvalue()\n            stderr = cmerr.getvalue()\n            if expect_stderr is False:\n                assert_equal(stderr, \"\")\n            elif expect_stderr is True:\n                # do nothing -- just return\n                pass\n            else:\n                # must be a string\n                assert_equal(stderr, expect_stderr)\n    return stdout, stderr\n\n\n# TODO: switch to stdout for --version output\ndef test_version():\n    stdout, stderr = run_main(['--version'], expect_stderr=True)\n\n    # and output should contain our version, copyright, license\n\n    # https://hg.python.org/cpython/file/default/Doc/whatsnew/3.4.rst#l1952\n    out = stdout if sys.version_info >= (3, 4) else stderr\n    ok_startswith(out, 'datalad %s\\n' % datalad.__version__)\n    in_(\"Copyright\", out)\n    in_(\"Permission is hereby granted\", out)\n\n\ndef test_help_np():\n    stdout, stderr = run_main(['--help-np'])\n\n    # Let's extract section titles:\n    # enough of bin/datalad and .tox/py27/bin/datalad -- guarantee consistency! ;)\n    ok_startswith(stdout, 'Usage: datalad')\n    # Sections start/end with * if ran under DATALAD_HELP2MAN mode\n    sections = [l[1:-1] for l in filter(re.compile('^\\*.*\\*$').match, stdout.split('\\n'))]\n    # but order is still not guaranteed (dict somewhere)! TODO\n    # see https://travis-ci.org/datalad/datalad/jobs/80519004\n    # thus testing sets\n    for s in {'Commands for dataset operations',\n              'Commands for metadata handling',\n              'Miscellaneous commands',\n              'General information',\n              'Global options',\n              'Plumbing commands',\n              'Plugins'}:\n        assert_in(s, sections)\n\n    # none of the lines must be longer than 80 chars\n    # TODO: decide on   create-sibling and possibly\n    # rewrite-urls\n    accepted_width = get_console_width()\n\n    long_lines = [\"%d %s\" % (len(l), l) for l in stdout.split('\\n')\n                  if len(l) > accepted_width and\n                  '{' not in l  # on nd70 summary line is unsplit\n                  ]\n    if long_lines:\n        raise AssertionError(\n            \"Following lines in --help output were longer than %s chars:\\n%s\"\n            % (accepted_width, '\\n'.join(long_lines))\n        )\n\n\ndef test_usage_on_insufficient_args():\n    stdout, stderr = run_main(['install'], exit_code=2, expect_stderr=True)\n    ok_startswith(stderr, 'usage:')\n\n\ndef test_subcmd_usage_on_unknown_args():\n    stdout, stderr = run_main(['get', '--murks'], exit_code=1, expect_stderr=True)\n    in_('get', stdout)\n\n\ndef check_incorrect_option(opts, err_str):\n    # The first line used to be:\n    # stdout, stderr = run_main((sys.argv[0],) + opts, expect_stderr=True, exit_code=2)\n    # But: what do we expect to be in sys.argv[0] here?\n    # It depends on how we invoke the test.\n    # - nosetests -s -v datalad/cmdline/tests/test_main.py would result in:\n    #   sys.argv[0}=='nosetests'\n    # - python -m nose -s -v datalad/cmdline/tests/test_main.py would result in:\n    #   sys.argv[0}=='python -m nose'\n    # - python -c \"import nose; nose.main()\" -s -v datalad/cmdline/tests/test_main.py would result in:\n    #   sys.argv[0]=='-c'\n    # This led to failure in case sys.argv[0] contained an option, that was\n    # defined to be a datalad option too, therefore was a 'known_arg' and was\n    # checked to meet its constraints.\n    # But sys.argv[0] actually isn't used by main at all. It simply doesn't\n    # matter what's in there. The only thing important to pass here is `opts`.\n    stdout, stderr = run_main(opts, expect_stderr=True, exit_code=2)\n    out = stdout + stderr\n    assert_in(\"usage: \", out)\n    assert_re_in(err_str, out, match=False)\n\n\ndef test_incorrect_options():\n    # apparently a bit different if following a good one so let's do both\n    err_invalid = \"error: (invalid|too few arguments|unrecognized argument)\"\n    yield check_incorrect_option, ('--buga',), err_invalid\n    yield check_incorrect_option, ('--dbg', '--buga'), err_invalid\n\n    err_insufficient = err_invalid  # \"specify\"\n    yield check_incorrect_option, ('--dbg',), err_insufficient\n    yield check_incorrect_option, tuple(), err_insufficient\n\n\n@skip_if_on_windows\ndef test_script_shims():\n    runner = Runner()\n    for script in [\n        'datalad',\n        'git-annex-remote-datalad-archives',\n        'git-annex-remote-datalad']:\n        # those must be available for execution, and should not contain\n        which, _ = runner(['which', script])\n        # test if there is no easy install shim in there\n        with open(which.rstrip()) as f:\n            content = f.read()\n        assert_not_in('EASY', content) # NOTHING easy should be there\n        assert_not_in('pkg_resources', content)\n\n        # and let's check that it is our script\n        out, err = runner([script, '--version'])\n        version = (out + err).splitlines()[0].split(' ', 1)[1]\n        # we can get git and non git .dev version... so for now\n        # relax\n        get_numeric_portion = lambda v: [x for x in v.split('.') if x.isdigit()]\n        # extract numeric portion\n        assert get_numeric_portion(version) # that my lambda is correctish\n        assert_equal(get_numeric_portion(__version__),\n                     get_numeric_portion(version))\n\n\n@slow  # 11.2591s\n@with_tempfile(mkdir=True)\ndef test_cfg_override(path):\n    with chpwd(path):\n        # control\n        out, err = Runner()('datalad wtf -s some', shell=True)\n        assert_not_in('datalad.dummy: this', out)\n        # ensure that this is not a dataset's cfg manager\n        assert_not_in('datalad.dataset.id', out)\n        # env var\n        from datalad.utils import on_windows\n        if on_windows:\n            cmd_str = 'set DATALAD_DUMMY=this&& datalad wtf -s some'\n        else:\n            cmd_str = 'DATALAD_DUMMY=this datalad wtf -s some'\n        out, err = Runner()(cmd_str, shell=True)\n        assert_in('datalad.dummy: this', out)\n        # cmdline arg\n        out, err = Runner()('datalad -c datalad.dummy=this wtf -s some', shell=True)\n        assert_in('datalad.dummy: this', out)\n\n        # now create a dataset in the path. the wtf plugin will switch to\n        # using the dataset's config manager, which must inherit the overrides\n        create(dataset=path)\n        # control\n        out, err = Runner()('datalad wtf -s some', shell=True)\n        assert_not_in('datalad.dummy: this', out)\n        # ensure that this is a dataset's cfg manager\n        assert_in('datalad.dataset.id', out)\n        # env var\n        if on_windows:\n            cmd_str = 'set DATALAD_DUMMY=this&& datalad wtf -s some'\n        else:\n            cmd_str = 'DATALAD_DUMMY=this datalad wtf -s some'\n        out, err = Runner()(cmd_str, shell=True)\n        assert_in('datalad.dummy: this', out)\n        # cmdline arg\n        out, err = Runner()('datalad -c datalad.dummy=this wtf -s some', shell=True)\n        assert_in('datalad.dummy: this', out)\n\n\ndef test_fail_with_short_help():\n    out = StringIO()\n    with assert_raises(SystemExit) as cme:\n        fail_with_short_help(exit_code=3, out=out)\n    assert_equal(cme.exception.code, 3)\n    assert_equal(out.getvalue(), \"\")\n\n    out = StringIO()\n    with assert_raises(SystemExit) as cme:\n        fail_with_short_help(msg=\"Failed badly\", out=out)\n    assert_equal(cme.exception.code, 1)\n    assert_equal(out.getvalue(), \"error: Failed badly\\n\")\n\n    # Suggestions, hint, etc\n    out = StringIO()\n    with assert_raises(SystemExit) as cme:\n        fail_with_short_help(\n            msg=\"Failed badly\",\n            known=[\"mother\", \"mutter\", \"father\", \"son\"],\n            provided=\"muther\",\n            hint=\"You can become one\",\n            exit_code=0,  # noone forbids\n            what=\"parent\",\n            out=out)\n    assert_equal(cme.exception.code, 0)\n    assert_equal(out.getvalue(),\n                 \"error: Failed badly\\n\"\n                 \"datalad: Unknown parent 'muther'.  See 'datalad --help'.\\n\\n\"\n                 \"Did you mean one of these?\\n\"\n                 \"        mutter\\n\"\n                 \"        mother\\n\"\n                 \"        father\\n\"\n                 \"Hint: You can become one\\n\")\n\ndef test_fix_datalad_ri():\n    assert_equal(_fix_datalad_ri('/'), '/')\n    assert_equal(_fix_datalad_ri('/a/b'), '/a/b')\n    assert_equal(_fix_datalad_ri('//'), '///')\n    assert_equal(_fix_datalad_ri('///'), '///')\n    assert_equal(_fix_datalad_ri('//a'), '///a')\n    assert_equal(_fix_datalad_ri('///a'), '///a')\n    assert_equal(_fix_datalad_ri('//a/b'), '///a/b')\n    assert_equal(_fix_datalad_ri('///a/b'), '///a/b')",
            "new_file_content": "# emacs: -*- mode: python-mode; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 noet:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Test functioning of the datalad main cmdline utility \"\"\"\n\nfrom datalad.tests.utils import known_failure_v6\nfrom datalad.tests.utils import known_failure_direct_mode\nfrom datalad.tests.utils import skip_if_on_windows\n\n\nimport re\nimport sys\nfrom six.moves import StringIO\nfrom mock import patch\n\nimport datalad\nfrom ..main import (\n    main,\n    fail_with_short_help,\n    _fix_datalad_ri,\n)\nfrom datalad import __version__\nfrom datalad.cmd import Runner\nfrom datalad.ui.utils import get_console_width\nfrom datalad.api import create\nfrom datalad.utils import chpwd\nfrom datalad.tests.utils import with_tempfile\nfrom datalad.tests.utils import assert_equal, assert_raises, in_, ok_startswith\nfrom datalad.tests.utils import assert_in\nfrom datalad.tests.utils import assert_re_in\nfrom datalad.tests.utils import assert_not_in\nfrom datalad.tests.utils import slow\n\n\ndef run_main(args, exit_code=0, expect_stderr=False):\n    \"\"\"Run main() of the datalad, do basic checks and provide outputs\n\n    Parameters\n    ----------\n    args : list\n        List of string cmdline arguments to pass\n    exit_code : int\n        Expected exit code. Would raise AssertionError if differs\n    expect_stderr : bool or string\n        Either to expect stderr output. If string -- match\n\n    Returns\n    -------\n    stdout, stderr  strings\n       Output produced\n    \"\"\"\n    with patch('sys.stderr', new_callable=StringIO) as cmerr:\n        with patch('sys.stdout', new_callable=StringIO) as cmout:\n            with assert_raises(SystemExit) as cm:\n                main([\"datalad\"] + list(args))\n            assert_equal(cm.exception.code, exit_code)\n            stdout = cmout.getvalue()\n            stderr = cmerr.getvalue()\n            if expect_stderr is False:\n                assert_equal(stderr, \"\")\n            elif expect_stderr is True:\n                # do nothing -- just return\n                pass\n            else:\n                # must be a string\n                assert_equal(stderr, expect_stderr)\n    return stdout, stderr\n\n\n# TODO: switch to stdout for --version output\ndef test_version():\n    stdout, stderr = run_main(['--version'], expect_stderr=True)\n\n    # and output should contain our version, copyright, license\n\n    # https://hg.python.org/cpython/file/default/Doc/whatsnew/3.4.rst#l1952\n    out = stdout if sys.version_info >= (3, 4) else stderr\n    ok_startswith(out, 'datalad %s\\n' % datalad.__version__)\n    in_(\"Copyright\", out)\n    in_(\"Permission is hereby granted\", out)\n\n\ndef test_help_np():\n    stdout, stderr = run_main(['--help-np'])\n\n    # Let's extract section titles:\n    # enough of bin/datalad and .tox/py27/bin/datalad -- guarantee consistency! ;)\n    ok_startswith(stdout, 'Usage: datalad')\n    # Sections start/end with * if ran under DATALAD_HELP2MAN mode\n    sections = [l[1:-1] for l in filter(re.compile('^\\*.*\\*$').match, stdout.split('\\n'))]\n    # but order is still not guaranteed (dict somewhere)! TODO\n    # see https://travis-ci.org/datalad/datalad/jobs/80519004\n    # thus testing sets\n    for s in {'Commands for dataset operations',\n              'Commands for metadata handling',\n              'Miscellaneous commands',\n              'General information',\n              'Global options',\n              'Plumbing commands',\n              'Plugins'}:\n        assert_in(s, sections)\n\n    # none of the lines must be longer than 80 chars\n    # TODO: decide on   create-sibling and possibly\n    # rewrite-urls\n    accepted_width = get_console_width()\n\n    long_lines = [\"%d %s\" % (len(l), l) for l in stdout.split('\\n')\n                  if len(l) > accepted_width and\n                  '{' not in l  # on nd70 summary line is unsplit\n                  ]\n    if long_lines:\n        raise AssertionError(\n            \"Following lines in --help output were longer than %s chars:\\n%s\"\n            % (accepted_width, '\\n'.join(long_lines))\n        )\n\n\ndef test_usage_on_insufficient_args():\n    stdout, stderr = run_main(['install'], exit_code=2, expect_stderr=True)\n    ok_startswith(stderr, 'usage:')\n\n\ndef test_subcmd_usage_on_unknown_args():\n    stdout, stderr = run_main(['get', '--murks'], exit_code=1, expect_stderr=True)\n    in_('get', stdout)\n\n\ndef test_combined_short_option():\n    stdout, stderr = run_main(['-fjson'], exit_code=2, expect_stderr=True)\n    assert_not_in(\"unrecognized argument\", stderr)\n    assert_in(\"too few arguments\", stderr)\n\n\ndef check_incorrect_option(opts, err_str):\n    # The first line used to be:\n    # stdout, stderr = run_main((sys.argv[0],) + opts, expect_stderr=True, exit_code=2)\n    # But: what do we expect to be in sys.argv[0] here?\n    # It depends on how we invoke the test.\n    # - nosetests -s -v datalad/cmdline/tests/test_main.py would result in:\n    #   sys.argv[0}=='nosetests'\n    # - python -m nose -s -v datalad/cmdline/tests/test_main.py would result in:\n    #   sys.argv[0}=='python -m nose'\n    # - python -c \"import nose; nose.main()\" -s -v datalad/cmdline/tests/test_main.py would result in:\n    #   sys.argv[0]=='-c'\n    # This led to failure in case sys.argv[0] contained an option, that was\n    # defined to be a datalad option too, therefore was a 'known_arg' and was\n    # checked to meet its constraints.\n    # But sys.argv[0] actually isn't used by main at all. It simply doesn't\n    # matter what's in there. The only thing important to pass here is `opts`.\n    stdout, stderr = run_main(opts, expect_stderr=True, exit_code=2)\n    out = stdout + stderr\n    assert_in(\"usage: \", out)\n    assert_re_in(err_str, out, match=False)\n\n\ndef test_incorrect_options():\n    # apparently a bit different if following a good one so let's do both\n    err_invalid = \"error: (invalid|too few arguments|unrecognized argument)\"\n    yield check_incorrect_option, ('--buga',), err_invalid\n    yield check_incorrect_option, ('--dbg', '--buga'), err_invalid\n\n    err_insufficient = err_invalid  # \"specify\"\n    yield check_incorrect_option, ('--dbg',), err_insufficient\n    yield check_incorrect_option, tuple(), err_insufficient\n\n\n@skip_if_on_windows\ndef test_script_shims():\n    runner = Runner()\n    for script in [\n        'datalad',\n        'git-annex-remote-datalad-archives',\n        'git-annex-remote-datalad']:\n        # those must be available for execution, and should not contain\n        which, _ = runner(['which', script])\n        # test if there is no easy install shim in there\n        with open(which.rstrip()) as f:\n            content = f.read()\n        assert_not_in('EASY', content) # NOTHING easy should be there\n        assert_not_in('pkg_resources', content)\n\n        # and let's check that it is our script\n        out, err = runner([script, '--version'])\n        version = (out + err).splitlines()[0].split(' ', 1)[1]\n        # we can get git and non git .dev version... so for now\n        # relax\n        get_numeric_portion = lambda v: [x for x in v.split('.') if x.isdigit()]\n        # extract numeric portion\n        assert get_numeric_portion(version) # that my lambda is correctish\n        assert_equal(get_numeric_portion(__version__),\n                     get_numeric_portion(version))\n\n\n@slow  # 11.2591s\n@with_tempfile(mkdir=True)\ndef test_cfg_override(path):\n    with chpwd(path):\n        # control\n        out, err = Runner()('datalad wtf -s some', shell=True)\n        assert_not_in('datalad.dummy: this', out)\n        # ensure that this is not a dataset's cfg manager\n        assert_not_in('datalad.dataset.id', out)\n        # env var\n        from datalad.utils import on_windows\n        if on_windows:\n            cmd_str = 'set DATALAD_DUMMY=this&& datalad wtf -s some'\n        else:\n            cmd_str = 'DATALAD_DUMMY=this datalad wtf -s some'\n        out, err = Runner()(cmd_str, shell=True)\n        assert_in('datalad.dummy: this', out)\n        # cmdline arg\n        out, err = Runner()('datalad -c datalad.dummy=this wtf -s some', shell=True)\n        assert_in('datalad.dummy: this', out)\n\n        # now create a dataset in the path. the wtf plugin will switch to\n        # using the dataset's config manager, which must inherit the overrides\n        create(dataset=path)\n        # control\n        out, err = Runner()('datalad wtf -s some', shell=True)\n        assert_not_in('datalad.dummy: this', out)\n        # ensure that this is a dataset's cfg manager\n        assert_in('datalad.dataset.id', out)\n        # env var\n        if on_windows:\n            cmd_str = 'set DATALAD_DUMMY=this&& datalad wtf -s some'\n        else:\n            cmd_str = 'DATALAD_DUMMY=this datalad wtf -s some'\n        out, err = Runner()(cmd_str, shell=True)\n        assert_in('datalad.dummy: this', out)\n        # cmdline arg\n        out, err = Runner()('datalad -c datalad.dummy=this wtf -s some', shell=True)\n        assert_in('datalad.dummy: this', out)\n\n\ndef test_fail_with_short_help():\n    out = StringIO()\n    with assert_raises(SystemExit) as cme:\n        fail_with_short_help(exit_code=3, out=out)\n    assert_equal(cme.exception.code, 3)\n    assert_equal(out.getvalue(), \"\")\n\n    out = StringIO()\n    with assert_raises(SystemExit) as cme:\n        fail_with_short_help(msg=\"Failed badly\", out=out)\n    assert_equal(cme.exception.code, 1)\n    assert_equal(out.getvalue(), \"error: Failed badly\\n\")\n\n    # Suggestions, hint, etc\n    out = StringIO()\n    with assert_raises(SystemExit) as cme:\n        fail_with_short_help(\n            msg=\"Failed badly\",\n            known=[\"mother\", \"mutter\", \"father\", \"son\"],\n            provided=\"muther\",\n            hint=\"You can become one\",\n            exit_code=0,  # noone forbids\n            what=\"parent\",\n            out=out)\n    assert_equal(cme.exception.code, 0)\n    assert_equal(out.getvalue(),\n                 \"error: Failed badly\\n\"\n                 \"datalad: Unknown parent 'muther'.  See 'datalad --help'.\\n\\n\"\n                 \"Did you mean one of these?\\n\"\n                 \"        mutter\\n\"\n                 \"        mother\\n\"\n                 \"        father\\n\"\n                 \"Hint: You can become one\\n\")\n\ndef test_fix_datalad_ri():\n    assert_equal(_fix_datalad_ri('/'), '/')\n    assert_equal(_fix_datalad_ri('/a/b'), '/a/b')\n    assert_equal(_fix_datalad_ri('//'), '///')\n    assert_equal(_fix_datalad_ri('///'), '///')\n    assert_equal(_fix_datalad_ri('//a'), '///a')\n    assert_equal(_fix_datalad_ri('///a'), '///a')\n    assert_equal(_fix_datalad_ri('//a/b'), '///a/b')\n    assert_equal(_fix_datalad_ri('///a/b'), '///a/b')",
            "header": {
                "file": {
                    "path": "datalad/cmdline/tests/test_main.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "37502fb59",
                "new_commit_hash": "d3a0ce10d",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/datalad/cmdline/tests/test_main.py"
            },
            "plus_file": {
                "path": "b/datalad/cmdline/tests/test_main.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 131,
                            "length": 6
                        },
                        "new_range": {
                            "start": 131,
                            "length": 12
                        },
                        "section": "def test_subcmd_usage_on_unknown_args():"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    in_('get', stdout)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "def test_combined_short_option():",
                                "type": "added"
                            },
                            {
                                "content": "    stdout, stderr = run_main(['-fjson'], exit_code=2, expect_stderr=True)",
                                "type": "added"
                            },
                            {
                                "content": "    assert_not_in(\"unrecognized argument\", stderr)",
                                "type": "added"
                            },
                            {
                                "content": "    assert_in(\"too few arguments\", stderr)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "def check_incorrect_option(opts, err_str):",
                                "type": "context"
                            },
                            {
                                "content": "    # The first line used to be:",
                                "type": "context"
                            },
                            {
                                "content": "    # stdout, stderr = run_main((sys.argv[0],) + opts, expect_stderr=True, exit_code=2)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "datalad/cmdline/tests/test_main.py",
                            "type": "function",
                            "name": "test_combined_short_option",
                            "content": "def test_combined_short_option():\n    stdout, stderr = run_main(['-fjson'], exit_code=2, expect_stderr=True)\n    assert_not_in(\"unrecognized argument\", stderr)\n    assert_in(\"too few arguments\", stderr)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 134,
                            "end_lineno": 137,
                            "parent": null
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "6b3f2fffe32116320cac8a6dfb43a10bc7d84e74^",
    "new_commit_hash": "6b3f2fffe32116320cac8a6dfb43a10bc7d84e74",
    "commit_message": "BF: cmdline: Restore handling of short options\n\nWe fail to parse combined short options (e.g., 'datalad -ar') and\nshort option/value pairs with no space (e.g., 'datalad -l1 ...) as of\nbe49a95dd (ENH: cmdline: Disable abbreviations for long options,\n2018-05-24).  That commit overrides _get_option_tuples so that it\nunconditionally returns an empty list.  Unfortunately, in addition to\ndisabling abbreviations, that swallows combined short options.\nInstead, perform the same check that ArgumentParser._get_option_tuples\ndoes to distinguish long flags from combined short options, and only\nreturn an empty list in the long flag case.\n\nFixes #2709.",
    "commit_date": "2018-07-24T11:51:03-04:00",
    "metadata": {}
}
{
    "repo_name": "datalad",
    "new_commit_hash": "6b3f2fffe32116320cac8a6dfb43a10bc7d84e74",
    "test_file_codes": [
        "# emacs: -*- mode: python-mode; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 noet:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Test functioning of the datalad main cmdline utility \"\"\"\n\nfrom datalad.tests.utils import known_failure_v6\nfrom datalad.tests.utils import known_failure_direct_mode\nfrom datalad.tests.utils import skip_if_on_windows\n\n\nimport re\nimport sys\nfrom six.moves import StringIO\nfrom mock import patch\n\nimport datalad\nfrom datalad.cmdline.main import (\n    main,\n    fail_with_short_help,\n    _fix_datalad_ri,\n)\nfrom datalad import __version__\nfrom datalad.cmd import Runner\nfrom datalad.ui.utils import get_console_width\nfrom datalad.api import create\nfrom datalad.utils import chpwd\nfrom datalad.tests.utils import with_tempfile\nfrom datalad.tests.utils import assert_equal, assert_raises, in_, ok_startswith\nfrom datalad.tests.utils import assert_in\nfrom datalad.tests.utils import assert_re_in\nfrom datalad.tests.utils import assert_not_in\nfrom datalad.tests.utils import slow\n\n\ndef run_main(args, exit_code=0, expect_stderr=False):\n    \"\"\"Run main() of the datalad, do basic checks and provide outputs\n\n    Parameters\n    ----------\n    args : list\n        List of string cmdline arguments to pass\n    exit_code : int\n        Expected exit code. Would raise AssertionError if differs\n    expect_stderr : bool or string\n        Either to expect stderr output. If string -- match\n\n    Returns\n    -------\n    stdout, stderr  strings\n       Output produced\n    \"\"\"\n    with patch('sys.stderr', new_callable=StringIO) as cmerr:\n        with patch('sys.stdout', new_callable=StringIO) as cmout:\n            with assert_raises(SystemExit) as cm:\n                main([\"datalad\"] + list(args))\n            assert_equal(cm.exception.code, exit_code)\n            stdout = cmout.getvalue()\n            stderr = cmerr.getvalue()\n            if expect_stderr is False:\n                assert_equal(stderr, \"\")\n            elif expect_stderr is True:\n                # do nothing -- just return\n                pass\n            else:\n                # must be a string\n                assert_equal(stderr, expect_stderr)\n    return stdout, stderr\n\n\n# TODO: switch to stdout for --version output\ndef test_version():\n    stdout, stderr = run_main(['--version'], expect_stderr=True)\n\n    # and output should contain our version, copyright, license\n\n    # https://hg.python.org/cpython/file/default/Doc/whatsnew/3.4.rst#l1952\n    out = stdout if sys.version_info >= (3, 4) else stderr\n    ok_startswith(out, 'datalad %s\\n' % datalad.__version__)\n    in_(\"Copyright\", out)\n    in_(\"Permission is hereby granted\", out)\n\n\ndef test_help_np():\n    stdout, stderr = run_main(['--help-np'])\n\n    # Let's extract section titles:\n    # enough of bin/datalad and .tox/py27/bin/datalad -- guarantee consistency! ;)\n    ok_startswith(stdout, 'Usage: datalad')\n    # Sections start/end with * if ran under DATALAD_HELP2MAN mode\n    sections = [l[1:-1] for l in filter(re.compile('^\\*.*\\*$').match, stdout.split('\\n'))]\n    # but order is still not guaranteed (dict somewhere)! TODO\n    # see https://travis-ci.org/datalad/datalad/jobs/80519004\n    # thus testing sets\n    for s in {'Commands for dataset operations',\n              'Commands for metadata handling',\n              'Miscellaneous commands',\n              'General information',\n              'Global options',\n              'Plumbing commands',\n              'Plugins'}:\n        assert_in(s, sections)\n\n    # none of the lines must be longer than 80 chars\n    # TODO: decide on   create-sibling and possibly\n    # rewrite-urls\n    accepted_width = get_console_width()\n\n    long_lines = [\"%d %s\" % (len(l), l) for l in stdout.split('\\n')\n                  if len(l) > accepted_width and\n                  '{' not in l  # on nd70 summary line is unsplit\n                  ]\n    if long_lines:\n        raise AssertionError(\n            \"Following lines in --help output were longer than %s chars:\\n%s\"\n            % (accepted_width, '\\n'.join(long_lines))\n        )\n\n\ndef test_usage_on_insufficient_args():\n    stdout, stderr = run_main(['install'], exit_code=2, expect_stderr=True)\n    ok_startswith(stderr, 'usage:')\n\n\ndef test_subcmd_usage_on_unknown_args():\n    stdout, stderr = run_main(['get', '--murks'], exit_code=1, expect_stderr=True)\n    in_('get', stdout)\n\n\ndef test_combined_short_option():\n    stdout, stderr = run_main(['-fjson'], exit_code=2, expect_stderr=True)\n    assert_not_in(\"unrecognized argument\", stderr)\n    assert_in(\"too few arguments\", stderr)\n\n\ndef check_incorrect_option(opts, err_str):\n    # The first line used to be:\n    # stdout, stderr = run_main((sys.argv[0],) + opts, expect_stderr=True, exit_code=2)\n    # But: what do we expect to be in sys.argv[0] here?\n    # It depends on how we invoke the test.\n    # - nosetests -s -v datalad/cmdline/tests/test_main.py would result in:\n    #   sys.argv[0}=='nosetests'\n    # - python -m nose -s -v datalad/cmdline/tests/test_main.py would result in:\n    #   sys.argv[0}=='python -m nose'\n    # - python -c \"import nose; nose.main()\" -s -v datalad/cmdline/tests/test_main.py would result in:\n    #   sys.argv[0]=='-c'\n    # This led to failure in case sys.argv[0] contained an option, that was\n    # defined to be a datalad option too, therefore was a 'known_arg' and was\n    # checked to meet its constraints.\n    # But sys.argv[0] actually isn't used by main at all. It simply doesn't\n    # matter what's in there. The only thing important to pass here is `opts`.\n    stdout, stderr = run_main(opts, expect_stderr=True, exit_code=2)\n    out = stdout + stderr\n    assert_in(\"usage: \", out)\n    assert_re_in(err_str, out, match=False)\n\n\ndef test_incorrect_options():\n    # apparently a bit different if following a good one so let's do both\n    err_invalid = \"error: (invalid|too few arguments|unrecognized argument)\"\n    yield check_incorrect_option, ('--buga',), err_invalid\n    yield check_incorrect_option, ('--dbg', '--buga'), err_invalid\n\n    err_insufficient = err_invalid  # \"specify\"\n    yield check_incorrect_option, ('--dbg',), err_insufficient\n    yield check_incorrect_option, tuple(), err_insufficient\n\n\n@skip_if_on_windows\ndef test_script_shims():\n    runner = Runner()\n    for script in [\n        'datalad',\n        'git-annex-remote-datalad-archives',\n        'git-annex-remote-datalad']:\n        # those must be available for execution, and should not contain\n        which, _ = runner(['which', script])\n        # test if there is no easy install shim in there\n        with open(which.rstrip()) as f:\n            content = f.read()\n        assert_not_in('EASY', content) # NOTHING easy should be there\n        assert_not_in('pkg_resources', content)\n\n        # and let's check that it is our script\n        out, err = runner([script, '--version'])\n        version = (out + err).splitlines()[0].split(' ', 1)[1]\n        # we can get git and non git .dev version... so for now\n        # relax\n        get_numeric_portion = lambda v: [x for x in v.split('.') if x.isdigit()]\n        # extract numeric portion\n        assert get_numeric_portion(version) # that my lambda is correctish\n        assert_equal(get_numeric_portion(__version__),\n                     get_numeric_portion(version))\n\n\n@slow  # 11.2591s\n@with_tempfile(mkdir=True)\ndef test_cfg_override(path):\n    with chpwd(path):\n        # control\n        out, err = Runner()('datalad wtf -s some', shell=True)\n        assert_not_in('datalad.dummy: this', out)\n        # ensure that this is not a dataset's cfg manager\n        assert_not_in('datalad.dataset.id', out)\n        # env var\n        from datalad.utils import on_windows\n        if on_windows:\n            cmd_str = 'set DATALAD_DUMMY=this&& datalad wtf -s some'\n        else:\n            cmd_str = 'DATALAD_DUMMY=this datalad wtf -s some'\n        out, err = Runner()(cmd_str, shell=True)\n        assert_in('datalad.dummy: this', out)\n        # cmdline arg\n        out, err = Runner()('datalad -c datalad.dummy=this wtf -s some', shell=True)\n        assert_in('datalad.dummy: this', out)\n\n        # now create a dataset in the path. the wtf plugin will switch to\n        # using the dataset's config manager, which must inherit the overrides\n        create(dataset=path)\n        # control\n        out, err = Runner()('datalad wtf -s some', shell=True)\n        assert_not_in('datalad.dummy: this', out)\n        # ensure that this is a dataset's cfg manager\n        assert_in('datalad.dataset.id', out)\n        # env var\n        if on_windows:\n            cmd_str = 'set DATALAD_DUMMY=this&& datalad wtf -s some'\n        else:\n            cmd_str = 'DATALAD_DUMMY=this datalad wtf -s some'\n        out, err = Runner()(cmd_str, shell=True)\n        assert_in('datalad.dummy: this', out)\n        # cmdline arg\n        out, err = Runner()('datalad -c datalad.dummy=this wtf -s some', shell=True)\n        assert_in('datalad.dummy: this', out)\n\n\ndef test_fail_with_short_help():\n    out = StringIO()\n    with assert_raises(SystemExit) as cme:\n        fail_with_short_help(exit_code=3, out=out)\n    assert_equal(cme.exception.code, 3)\n    assert_equal(out.getvalue(), \"\")\n\n    out = StringIO()\n    with assert_raises(SystemExit) as cme:\n        fail_with_short_help(msg=\"Failed badly\", out=out)\n    assert_equal(cme.exception.code, 1)\n    assert_equal(out.getvalue(), \"error: Failed badly\\n\")\n\n    # Suggestions, hint, etc\n    out = StringIO()\n    with assert_raises(SystemExit) as cme:\n        fail_with_short_help(\n            msg=\"Failed badly\",\n            known=[\"mother\", \"mutter\", \"father\", \"son\"],\n            provided=\"muther\",\n            hint=\"You can become one\",\n            exit_code=0,  # noone forbids\n            what=\"parent\",\n            out=out)\n    assert_equal(cme.exception.code, 0)\n    assert_equal(out.getvalue(),\n                 \"error: Failed badly\\n\"\n                 \"datalad: Unknown parent 'muther'.  See 'datalad --help'.\\n\\n\"\n                 \"Did you mean one of these?\\n\"\n                 \"        mutter\\n\"\n                 \"        mother\\n\"\n                 \"        father\\n\"\n                 \"Hint: You can become one\\n\")\n\ndef test_fix_datalad_ri():\n    assert_equal(_fix_datalad_ri('/'), '/')\n    assert_equal(_fix_datalad_ri('/a/b'), '/a/b')\n    assert_equal(_fix_datalad_ri('//'), '///')\n    assert_equal(_fix_datalad_ri('///'), '///')\n    assert_equal(_fix_datalad_ri('//a'), '///a')\n    assert_equal(_fix_datalad_ri('///a'), '///a')\n    assert_equal(_fix_datalad_ri('//a/b'), '///a/b')\n    assert_equal(_fix_datalad_ri('///a/b'), '///a/b')",
        "import pytest\nimport tempfile\nimport os\n\n\n@pytest.fixture\ndef path():\n    with tempfile.TemporaryDirectory() as tmpdir:\n        yield tmpdir\n\n\n@pytest.fixture(autouse=True)\ndef setup_git_config():\n    with tempfile.TemporaryDirectory() as home:\n        old_home = os.environ.get(\"HOME\")\n        os.environ[\"HOME\"] = home\n        os.system('git config --global user.name \"DataLad Tester\"')\n        os.system('git config --global user.email \"test@example.com\"')\n        yield\n        if old_home:\n            os.environ[\"HOME\"] = old_home\n"
    ],
    "test_file_names": [
        "test_1.py",
        "conftest.py"
    ],
    "setup_res_code": 0,
    "setup_res_stdout": "Starting Datalad installation attempts...\nVerifying installation...\n❌ Verification failed\nPython 3.9 installation failed, trying Python 3.7...\nVerifying installation...\nDatalad version: 0.10.2.dev30\n✅ Installation successful!\nSuccessfully installed Datalad using Python 3.7\n",
    "setup_res_stderr": "Using CPython 3.9.21\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 25 packages in 106ms\nInstalled 25 packages in 3.58s\n + coverage==7.6.10\n + docopt==0.6.2\n + exceptiongroup==1.2.2\n + grabbit==0.2.6\n + importlib-resources==6.4.5\n + iniconfig==2.0.0\n + nibabel==5.3.2\n + num2words==0.5.14\n + numpy==2.0.2\n + packaging==24.2\n + pandas==2.2.3\n + patsy==1.0.1\n + pluggy==1.5.0\n + pybids==0.6.5\n + pytest==8.3.4\n + pytest-cov==6.0.0\n + python-dateutil==2.9.0.post0\n + pytz==2024.2\n + scipy==1.13.1\n + setuptools==75.6.0\n + six==1.17.0\n + tomli==2.2.1\n + typing-extensions==4.12.2\n + tzdata==2024.2\n + zipp==3.21.0\nerror: The build backend returned an error\n  Caused by: Call to `setuptools.build_meta:__legacy__.build_editable` failed (exit status: 1)\n\n[stderr]\nTraceback (most recent call last):\n  File \"<string>\", line 14, in <module>\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmp7yYJwI/lib/python3.9/site-packages/setuptools/build_meta.py\", line 483, in get_requires_for_build_editable\n    return self.get_requires_for_build_wheel(config_settings)\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmp7yYJwI/lib/python3.9/site-packages/setuptools/build_meta.py\", line 334, in get_requires_for_build_wheel\n    return self._get_build_requires(config_settings, requirements=[])\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmp7yYJwI/lib/python3.9/site-packages/setuptools/build_meta.py\", line 304, in _get_build_requires\n    self.run_setup()\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmp7yYJwI/lib/python3.9/site-packages/setuptools/build_meta.py\", line 522, in run_setup\n    super().run_setup(setup_script=setup_script)\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmp7yYJwI/lib/python3.9/site-packages/setuptools/build_meta.py\", line 320, in run_setup\n    exec(code, locals())\n  File \"<string>\", line 50, in <module>\nAttributeError: module 'platform' has no attribute 'dist'\n\nhint: This usually indicates a problem with the package or the build environment.\nerror: The build backend returned an error\n  Caused by: Call to `setuptools.build_meta:__legacy__.build_editable` failed (exit status: 1)\n\n[stderr]\nTraceback (most recent call last):\n  File \"<string>\", line 14, in <module>\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmpYAOQ9C/lib/python3.9/site-packages/setuptools/build_meta.py\", line 483, in get_requires_for_build_editable\n    return self.get_requires_for_build_wheel(config_settings)\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmpYAOQ9C/lib/python3.9/site-packages/setuptools/build_meta.py\", line 334, in get_requires_for_build_wheel\n    return self._get_build_requires(config_settings, requirements=[])\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmpYAOQ9C/lib/python3.9/site-packages/setuptools/build_meta.py\", line 304, in _get_build_requires\n    self.run_setup()\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmpYAOQ9C/lib/python3.9/site-packages/setuptools/build_meta.py\", line 522, in run_setup\n    super().run_setup(setup_script=setup_script)\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmpYAOQ9C/lib/python3.9/site-packages/setuptools/build_meta.py\", line 320, in run_setup\n    exec(code, locals())\n  File \"<string>\", line 50, in <module>\nAttributeError: module 'platform' has no attribute 'dist'\n\nhint: This usually indicates a problem with the package or the build environment.\nTraceback (most recent call last):\n  File \"<string>\", line 1, in <module>\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_6b3f2fffe32116320cac8a6dfb43a10bc7d84e74/datalad/__init__.py\", line 39, in <module>\n    from datalad.cmd import GitRunner\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_6b3f2fffe32116320cac8a6dfb43a10bc7d84e74/datalad/cmd.py\", line 28, in <module>\n    from .support import path as op\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_6b3f2fffe32116320cac8a6dfb43a10bc7d84e74/datalad/support/path.py\", line 18, in <module>\n    from ..utils import assure_bytes\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_6b3f2fffe32116320cac8a6dfb43a10bc7d84e74/datalad/utils.py\", line 25, in <module>\n    import wrapt\nModuleNotFoundError: No module named 'wrapt'\nUsing CPython 3.7.9\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 26 packages in 295ms\nInstalled 26 packages in 2.41s\n + bids==0.0\n + coverage==7.2.7\n + docopt==0.6.2\n + exceptiongroup==1.2.2\n + fasteners==0.19\n + grabbit==0.2.6\n + importlib-metadata==6.7.0\n + iniconfig==2.0.0\n + nibabel==4.0.2\n + num2words==0.5.14\n + numpy==1.21.6\n + packaging==24.0\n + pandas==1.3.5\n + patsy==1.0.1\n + pluggy==1.2.0\n + pybids==0.6.5\n + pytest==7.4.4\n + pytest-cov==4.1.0\n + python-dateutil==2.9.0.post0\n + pytz==2024.2\n + scipy==1.7.3\n + setuptools==68.0.0\n + six==1.17.0\n + tomli==2.0.1\n + typing-extensions==4.7.1\n + zipp==3.15.0\nResolved 57 packages in 1.33s\nPrepared 1 package in 1.20s\nInstalled 50 packages in 58ms\n + appdirs==1.4.4\n + beautifulsoup4==4.12.3\n + boto==2.49.0\n + certifi==2024.12.14\n + cffi==1.15.1\n + chardet==5.2.0\n + charset-normalizer==3.4.1\n + cryptography==44.0.0\n + datalad==0.10.2 (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_6b3f2fffe32116320cac8a6dfb43a10bc7d84e74)\n + deprecated==1.2.15\n + exifread==3.0.0\n + gitdb==4.0.11\n + gitpython==3.1.43\n + httpretty==1.1.4\n + humanize==4.6.0\n + idna==3.10\n + importlib-resources==5.12.0\n + iso8601==2.1.0\n + jaraco-classes==3.2.3\n + jeepney==0.8.0\n + jsmin==3.0.1\n + keyring==24.1.1\n + keyrings-alt==4.2.0\n + mock==5.1.0\n + more-itertools==9.1.0\n + msgpack==1.0.5\n + multidict==6.0.5\n + mutagen==1.47.0\n + nose==1.3.7\n + patool==1.12\n + pillow==9.5.0\n + pycparser==2.21\n + pygithub==2.3.0\n + pyjwt==2.8.0\n + pynacl==1.5.0\n + pyperclip==1.9.0\n + python-xmp-toolkit==2.0.2\n + pyyaml==6.0.1\n + requests==2.31.0\n + requests-ftp==0.3.1\n + secretstorage==3.3.3\n + simplejson==3.19.3\n + smmap==5.0.1\n + soupsieve==2.4.1\n + tqdm==4.67.1\n + urllib3==2.0.7\n + vcrpy==4.3.0\n + whoosh==2.7.4\n + wrapt==1.16.0\n + yarl==1.9.4\nResolved 90 packages in 215ms\nInstalled 31 packages in 159ms\n + alabaster==0.7.13\n + asv==0.6.4\n + asv-runner==0.2.1\n + babel==2.14.0\n + build==1.1.1\n + distlib==0.3.9\n + docutils==0.19\n + filelock==3.12.2\n + imagesize==1.4.1\n + jinja2==3.1.5\n + json5==0.9.16\n + markupsafe==2.1.5\n + nose-timer==1.0.1\n + platformdirs==4.0.0\n + psutil==6.1.1\n + pygments==2.17.2\n + pympler==1.1\n + pypandoc==1.14\n + pyproject-hooks==1.2.0\n + snowballstemmer==2.2.0\n + sphinx==5.3.0\n + sphinx-rtd-theme==2.0.0\n + sphinxcontrib-applehelp==1.0.2\n + sphinxcontrib-devhelp==1.0.2\n + sphinxcontrib-htmlhelp==2.0.0\n + sphinxcontrib-jquery==4.1\n + sphinxcontrib-jsmath==1.0.1\n + sphinxcontrib-qthelp==1.0.3\n + sphinxcontrib-serializinghtml==1.1.5\n + tabulate==0.9.0\n + virtualenv==20.26.6\n",
    "new_commit_res_code": 1,
    "new_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.7.9, pytest-7.4.4, pluggy-1.2.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_6b3f2fffe32116320cac8a6dfb43a10bc7d84e74\nplugins: cov-4.1.0\ncollected 10 items\n\nr2e_tests/test_1.py .....xFF..                                           [100%]\n\n=================================== FAILURES ===================================\n______________________________ test_script_shims _______________________________\n\n    @skip_if_on_windows\n    def test_script_shims():\n        runner = Runner()\n        for script in [\n            'datalad',\n            'git-annex-remote-datalad-archives',\n            'git-annex-remote-datalad']:\n            # those must be available for execution, and should not contain\n            which, _ = runner(['which', script])\n            # test if there is no easy install shim in there\n            with open(which.rstrip()) as f:\n                content = f.read()\n            assert_not_in('EASY', content) # NOTHING easy should be there\n            assert_not_in('pkg_resources', content)\n    \n            # and let's check that it is our script\n>           out, err = runner([script, '--version'])\n\nr2e_tests/test_1.py:189: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \ndatalad/cmd.py:186: in __call__\n    return self.run(cmd, *args, **kwargs)\ndatalad/cmd.py:486: in run\n    stdin=stdin)\n../../../../.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib/python3.7/subprocess.py:800: in __init__\n    restore_signals, start_new_session)\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <subprocess.Popen object at 0x7c5e14e227d0>\nargs = ['datalad', '--version'], executable = b'datalad', preexec_fn = None\nclose_fds = True, pass_fds = (), cwd = None, env = None, startupinfo = None\ncreationflags = 0, shell = False, p2cread = -1, p2cwrite = -1, c2pread = 13\nc2pwrite = 15, errread = 16, errwrite = 17, restore_signals = True\nstart_new_session = False\n\n    def _execute_child(self, args, executable, preexec_fn, close_fds,\n                       pass_fds, cwd, env,\n                       startupinfo, creationflags, shell,\n                       p2cread, p2cwrite,\n                       c2pread, c2pwrite,\n                       errread, errwrite,\n                       restore_signals, start_new_session):\n        \"\"\"Execute program (POSIX version)\"\"\"\n    \n        if isinstance(args, (str, bytes)):\n            args = [args]\n        else:\n            args = list(args)\n    \n        if shell:\n            # On Android the default shell is at '/system/bin/sh'.\n            unix_shell = ('/system/bin/sh' if\n                      hasattr(sys, 'getandroidapilevel') else '/bin/sh')\n            args = [unix_shell, \"-c\"] + args\n            if executable:\n                args[0] = executable\n    \n        if executable is None:\n            executable = args[0]\n        orig_executable = executable\n    \n        # For transferring possible exec failure from child to parent.\n        # Data format: \"exception name:hex errno:description\"\n        # Pickle is not used; it is complex and involves memory allocation.\n        errpipe_read, errpipe_write = os.pipe()\n        # errpipe_write must not be in the standard io 0, 1, or 2 fd range.\n        low_fds_to_close = []\n        while errpipe_write < 3:\n            low_fds_to_close.append(errpipe_write)\n            errpipe_write = os.dup(errpipe_write)\n        for low_fd in low_fds_to_close:\n            os.close(low_fd)\n        try:\n            try:\n                # We must avoid complex work that could involve\n                # malloc or free in the child process to avoid\n                # potential deadlocks, thus we do all this here.\n                # and pass it to fork_exec()\n    \n                if env is not None:\n                    env_list = []\n                    for k, v in env.items():\n                        k = os.fsencode(k)\n                        if b'=' in k:\n                            raise ValueError(\"illegal environment variable name\")\n                        env_list.append(k + b'=' + os.fsencode(v))\n                else:\n                    env_list = None  # Use execv instead of execve.\n                executable = os.fsencode(executable)\n                if os.path.dirname(executable):\n                    executable_list = (executable,)\n                else:\n                    # This matches the behavior of os._execvpe().\n                    executable_list = tuple(\n                        os.path.join(os.fsencode(dir), executable)\n                        for dir in os.get_exec_path(env))\n                fds_to_keep = set(pass_fds)\n                fds_to_keep.add(errpipe_write)\n                self.pid = _posixsubprocess.fork_exec(\n                        args, executable_list,\n                        close_fds, tuple(sorted(map(int, fds_to_keep))),\n                        cwd, env_list,\n                        p2cread, p2cwrite, c2pread, c2pwrite,\n                        errread, errwrite,\n                        errpipe_read, errpipe_write,\n                        restore_signals, start_new_session, preexec_fn)\n                self._child_created = True\n            finally:\n                # be sure the FD is closed no matter what\n                os.close(errpipe_write)\n    \n            # self._devnull is not always defined.\n            devnull_fd = getattr(self, '_devnull', None)\n            if p2cread != -1 and p2cwrite != -1 and p2cread != devnull_fd:\n                os.close(p2cread)\n            if c2pwrite != -1 and c2pread != -1 and c2pwrite != devnull_fd:\n                os.close(c2pwrite)\n            if errwrite != -1 and errread != -1 and errwrite != devnull_fd:\n                os.close(errwrite)\n            if devnull_fd is not None:\n                os.close(devnull_fd)\n            # Prevent a double close of these fds from __init__ on error.\n            self._closed_child_pipe_fds = True\n    \n            # Wait for exec to fail or succeed; possibly raising an\n            # exception (limited in size)\n            errpipe_data = bytearray()\n            while True:\n                part = os.read(errpipe_read, 50000)\n                errpipe_data += part\n                if not part or len(errpipe_data) > 50000:\n                    break\n        finally:\n            # be sure the FD is closed no matter what\n            os.close(errpipe_read)\n    \n        if errpipe_data:\n            try:\n                pid, sts = os.waitpid(self.pid, 0)\n                if pid == self.pid:\n                    self._handle_exitstatus(sts)\n                else:\n                    self.returncode = sys.maxsize\n            except ChildProcessError:\n                pass\n    \n            try:\n                exception_name, hex_errno, err_msg = (\n                        errpipe_data.split(b':', 2))\n                # The encoding here should match the encoding\n                # written in by the subprocess implementations\n                # like _posixsubprocess\n                err_msg = err_msg.decode()\n            except ValueError:\n                exception_name = b'SubprocessError'\n                hex_errno = b'0'\n                err_msg = 'Bad exception data from child: {!r}'.format(\n                              bytes(errpipe_data))\n            child_exception_type = getattr(\n                    builtins, exception_name.decode('ascii'),\n                    SubprocessError)\n            if issubclass(child_exception_type, OSError) and hex_errno:\n                errno_num = int(hex_errno, 16)\n                child_exec_never_called = (err_msg == \"noexec\")\n                if child_exec_never_called:\n                    err_msg = \"\"\n                    # The error must be from chdir(cwd).\n                    err_filename = cwd\n                else:\n                    err_filename = orig_executable\n                if errno_num != 0:\n                    err_msg = os.strerror(errno_num)\n                    if errno_num == errno.ENOENT:\n                        err_msg += ': ' + repr(err_filename)\n>               raise child_exception_type(errno_num, err_msg, err_filename)\nE               FileNotFoundError: [Errno 2] No such file or directory: 'datalad': 'datalad'\n\n../../../../.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib/python3.7/subprocess.py:1551: FileNotFoundError\n______________________________ test_cfg_override _______________________________\n\narg = (), kw = {'path': '/tmp/tmp-page2annexj61q0l9k'}\nfilename = '/tmp/datalad_temp_test_cfg_overridejn3_c6xm'\n\n    @wraps(t)\n    def newfunc(*arg, **kw):\n        with make_tempfile(wrapped=t, **tkwargs) as filename:\n>           return t(*(arg + (filename,)), **kw)\nE           TypeError: test_cfg_override() got multiple values for argument 'path'\n\ndatalad/tests/utils.py:587: TypeError\n==================================== PASSES ====================================\n_______________________ test_usage_on_insufficient_args ________________________\n----------------------------- Captured stderr call -----------------------------\n[ERROR] Please provide at least a source or a path [install.py:__call__:167] (InsufficientArgumentsError) \n------------------------------ Captured log call -------------------------------\nERROR    datalad.cmdline:main.py:541 Please provide at least a source or a path [install.py:__call__:167] (InsufficientArgumentsError)\n______________________ test_subcmd_usage_on_unknown_args _______________________\n----------------------------- Captured stderr call -----------------------------\n[ERROR] unknown argument: --murks \n------------------------------ Captured log call -------------------------------\nERROR    datalad.cmdline:main.py:495 unknown argument: --murks\n_____________________________ test_fix_datalad_ri ______________________________\n----------------------------- Captured stderr call -----------------------------\n[INFO] Changing // back to /// as it was probably changed by MINGW/MSYS, see http://www.mingw.org/wiki/Posix_path_conversion \n[INFO] Changing //a back to ///a as it was probably changed by MINGW/MSYS, see http://www.mingw.org/wiki/Posix_path_conversion \n[INFO] Changing //a/b back to ///a/b as it was probably changed by MINGW/MSYS, see http://www.mingw.org/wiki/Posix_path_conversion \n------------------------------ Captured log call -------------------------------\nINFO     datalad.cmdline:main.py:468 Changing // back to /// as it was probably changed by MINGW/MSYS, see http://www.mingw.org/wiki/Posix_path_conversion\nINFO     datalad.cmdline:main.py:468 Changing //a back to ///a as it was probably changed by MINGW/MSYS, see http://www.mingw.org/wiki/Posix_path_conversion\nINFO     datalad.cmdline:main.py:468 Changing //a/b back to ///a/b as it was probably changed by MINGW/MSYS, see http://www.mingw.org/wiki/Posix_path_conversion\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::test_version\nPASSED r2e_tests/test_1.py::test_help_np\nPASSED r2e_tests/test_1.py::test_usage_on_insufficient_args\nPASSED r2e_tests/test_1.py::test_subcmd_usage_on_unknown_args\nPASSED r2e_tests/test_1.py::test_combined_short_option\nPASSED r2e_tests/test_1.py::test_fail_with_short_help\nPASSED r2e_tests/test_1.py::test_fix_datalad_ri\nXFAIL r2e_tests/test_1.py::test_incorrect_options - reason: [NOTRUN] yield tests were removed in pytest 4.0 - test_incorrect_options will be ignored\nFAILED r2e_tests/test_1.py::test_script_shims - FileNotFoundError: [Errno 2] ...\nFAILED r2e_tests/test_1.py::test_cfg_override - TypeError: test_cfg_override(...\n==================== 2 failed, 7 passed, 1 xfailed in 1.33s ====================\n",
    "new_commit_res_stderr": "",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.7.9, pytest-7.4.4, pluggy-1.2.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_6b3f2fffe32116320cac8a6dfb43a10bc7d84e74\nplugins: cov-4.1.0\ncollected 10 items\n\nr2e_tests/test_1.py ....FxFF..                                           [100%]\n\n=================================== FAILURES ===================================\n__________________________ test_combined_short_option __________________________\n\n    def test_combined_short_option():\n        stdout, stderr = run_main(['-fjson'], exit_code=2, expect_stderr=True)\n>       assert_not_in(\"unrecognized argument\", stderr)\n\nr2e_tests/test_1.py:136: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n../../../../.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib/python3.7/unittest/case.py:1126: in assertNotIn\n    self.fail(self._formatMessage(msg, standardMsg))\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <nose.tools.trivial.Dummy testMethod=nop>\nmsg = '\\'unrecognized argument\\' unexpectedly found in \"error: unrecognized argument -fjson\\\\nusage: __main__.py [-l LEVEL] ...\\n                   [--proc-post <PROCEDURE NAME> [ARGS ...]] [--cmd]\\\\n                   command [command-opts]\\\\n\"'\n\n    def fail(self, msg=None):\n        \"\"\"Fail immediately, with the given message.\"\"\"\n>       raise self.failureException(msg)\nE       AssertionError: 'unrecognized argument' unexpectedly found in \"error: unrecognized argument -fjson\\nusage: __main__.py [-l LEVEL] [--pbs-runner {condor}] [-C PATH] [--version]\\n                   [--dbg] [--idbg] [-c KEY=VALUE]\\n                   [-f {default,json,json_pp,tailored,'<template>'}]\\n                   [--report-status {success,failure,ok,notneeded,impossible,error}]\\n                   [--report-type {dataset,file}]\\n                   [--on-failure {ignore,continue,stop}]\\n                   [--proc-pre <PROCEDURE NAME> [ARGS ...]]\\n                   [--proc-post <PROCEDURE NAME> [ARGS ...]] [--cmd]\\n                   command [command-opts]\\n\"\n\n../../../../.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib/python3.7/unittest/case.py:693: AssertionError\n______________________________ test_script_shims _______________________________\n\n    @skip_if_on_windows\n    def test_script_shims():\n        runner = Runner()\n        for script in [\n            'datalad',\n            'git-annex-remote-datalad-archives',\n            'git-annex-remote-datalad']:\n            # those must be available for execution, and should not contain\n            which, _ = runner(['which', script])\n            # test if there is no easy install shim in there\n            with open(which.rstrip()) as f:\n                content = f.read()\n            assert_not_in('EASY', content) # NOTHING easy should be there\n            assert_not_in('pkg_resources', content)\n    \n            # and let's check that it is our script\n>           out, err = runner([script, '--version'])\n\nr2e_tests/test_1.py:189: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \ndatalad/cmd.py:186: in __call__\n    return self.run(cmd, *args, **kwargs)\ndatalad/cmd.py:486: in run\n    stdin=stdin)\n../../../../.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib/python3.7/subprocess.py:800: in __init__\n    restore_signals, start_new_session)\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <subprocess.Popen object at 0x7173120fd690>\nargs = ['datalad', '--version'], executable = b'datalad', preexec_fn = None\nclose_fds = True, pass_fds = (), cwd = None, env = None, startupinfo = None\ncreationflags = 0, shell = False, p2cread = -1, p2cwrite = -1, c2pread = 13\nc2pwrite = 15, errread = 16, errwrite = 17, restore_signals = True\nstart_new_session = False\n\n    def _execute_child(self, args, executable, preexec_fn, close_fds,\n                       pass_fds, cwd, env,\n                       startupinfo, creationflags, shell,\n                       p2cread, p2cwrite,\n                       c2pread, c2pwrite,\n                       errread, errwrite,\n                       restore_signals, start_new_session):\n        \"\"\"Execute program (POSIX version)\"\"\"\n    \n        if isinstance(args, (str, bytes)):\n            args = [args]\n        else:\n            args = list(args)\n    \n        if shell:\n            # On Android the default shell is at '/system/bin/sh'.\n            unix_shell = ('/system/bin/sh' if\n                      hasattr(sys, 'getandroidapilevel') else '/bin/sh')\n            args = [unix_shell, \"-c\"] + args\n            if executable:\n                args[0] = executable\n    \n        if executable is None:\n            executable = args[0]\n        orig_executable = executable\n    \n        # For transferring possible exec failure from child to parent.\n        # Data format: \"exception name:hex errno:description\"\n        # Pickle is not used; it is complex and involves memory allocation.\n        errpipe_read, errpipe_write = os.pipe()\n        # errpipe_write must not be in the standard io 0, 1, or 2 fd range.\n        low_fds_to_close = []\n        while errpipe_write < 3:\n            low_fds_to_close.append(errpipe_write)\n            errpipe_write = os.dup(errpipe_write)\n        for low_fd in low_fds_to_close:\n            os.close(low_fd)\n        try:\n            try:\n                # We must avoid complex work that could involve\n                # malloc or free in the child process to avoid\n                # potential deadlocks, thus we do all this here.\n                # and pass it to fork_exec()\n    \n                if env is not None:\n                    env_list = []\n                    for k, v in env.items():\n                        k = os.fsencode(k)\n                        if b'=' in k:\n                            raise ValueError(\"illegal environment variable name\")\n                        env_list.append(k + b'=' + os.fsencode(v))\n                else:\n                    env_list = None  # Use execv instead of execve.\n                executable = os.fsencode(executable)\n                if os.path.dirname(executable):\n                    executable_list = (executable,)\n                else:\n                    # This matches the behavior of os._execvpe().\n                    executable_list = tuple(\n                        os.path.join(os.fsencode(dir), executable)\n                        for dir in os.get_exec_path(env))\n                fds_to_keep = set(pass_fds)\n                fds_to_keep.add(errpipe_write)\n                self.pid = _posixsubprocess.fork_exec(\n                        args, executable_list,\n                        close_fds, tuple(sorted(map(int, fds_to_keep))),\n                        cwd, env_list,\n                        p2cread, p2cwrite, c2pread, c2pwrite,\n                        errread, errwrite,\n                        errpipe_read, errpipe_write,\n                        restore_signals, start_new_session, preexec_fn)\n                self._child_created = True\n            finally:\n                # be sure the FD is closed no matter what\n                os.close(errpipe_write)\n    \n            # self._devnull is not always defined.\n            devnull_fd = getattr(self, '_devnull', None)\n            if p2cread != -1 and p2cwrite != -1 and p2cread != devnull_fd:\n                os.close(p2cread)\n            if c2pwrite != -1 and c2pread != -1 and c2pwrite != devnull_fd:\n                os.close(c2pwrite)\n            if errwrite != -1 and errread != -1 and errwrite != devnull_fd:\n                os.close(errwrite)\n            if devnull_fd is not None:\n                os.close(devnull_fd)\n            # Prevent a double close of these fds from __init__ on error.\n            self._closed_child_pipe_fds = True\n    \n            # Wait for exec to fail or succeed; possibly raising an\n            # exception (limited in size)\n            errpipe_data = bytearray()\n            while True:\n                part = os.read(errpipe_read, 50000)\n                errpipe_data += part\n                if not part or len(errpipe_data) > 50000:\n                    break\n        finally:\n            # be sure the FD is closed no matter what\n            os.close(errpipe_read)\n    \n        if errpipe_data:\n            try:\n                pid, sts = os.waitpid(self.pid, 0)\n                if pid == self.pid:\n                    self._handle_exitstatus(sts)\n                else:\n                    self.returncode = sys.maxsize\n            except ChildProcessError:\n                pass\n    \n            try:\n                exception_name, hex_errno, err_msg = (\n                        errpipe_data.split(b':', 2))\n                # The encoding here should match the encoding\n                # written in by the subprocess implementations\n                # like _posixsubprocess\n                err_msg = err_msg.decode()\n            except ValueError:\n                exception_name = b'SubprocessError'\n                hex_errno = b'0'\n                err_msg = 'Bad exception data from child: {!r}'.format(\n                              bytes(errpipe_data))\n            child_exception_type = getattr(\n                    builtins, exception_name.decode('ascii'),\n                    SubprocessError)\n            if issubclass(child_exception_type, OSError) and hex_errno:\n                errno_num = int(hex_errno, 16)\n                child_exec_never_called = (err_msg == \"noexec\")\n                if child_exec_never_called:\n                    err_msg = \"\"\n                    # The error must be from chdir(cwd).\n                    err_filename = cwd\n                else:\n                    err_filename = orig_executable\n                if errno_num != 0:\n                    err_msg = os.strerror(errno_num)\n                    if errno_num == errno.ENOENT:\n                        err_msg += ': ' + repr(err_filename)\n>               raise child_exception_type(errno_num, err_msg, err_filename)\nE               FileNotFoundError: [Errno 2] No such file or directory: 'datalad': 'datalad'\n\n../../../../.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib/python3.7/subprocess.py:1551: FileNotFoundError\n______________________________ test_cfg_override _______________________________\n\narg = (), kw = {'path': '/tmp/tmp-page2annexcz6rpacu'}\nfilename = '/tmp/datalad_temp_test_cfg_overrideq97cjzrq'\n\n    @wraps(t)\n    def newfunc(*arg, **kw):\n        with make_tempfile(wrapped=t, **tkwargs) as filename:\n>           return t(*(arg + (filename,)), **kw)\nE           TypeError: test_cfg_override() got multiple values for argument 'path'\n\ndatalad/tests/utils.py:587: TypeError\n==================================== PASSES ====================================\n_______________________ test_usage_on_insufficient_args ________________________\n----------------------------- Captured stderr call -----------------------------\n[ERROR] Please provide at least a source or a path [install.py:__call__:167] (InsufficientArgumentsError) \n------------------------------ Captured log call -------------------------------\nERROR    datalad.cmdline:main.py:535 Please provide at least a source or a path [install.py:__call__:167] (InsufficientArgumentsError)\n______________________ test_subcmd_usage_on_unknown_args _______________________\n----------------------------- Captured stderr call -----------------------------\n[ERROR] unknown argument: --murks \n------------------------------ Captured log call -------------------------------\nERROR    datalad.cmdline:main.py:489 unknown argument: --murks\n_____________________________ test_fix_datalad_ri ______________________________\n----------------------------- Captured stderr call -----------------------------\n[INFO] Changing // back to /// as it was probably changed by MINGW/MSYS, see http://www.mingw.org/wiki/Posix_path_conversion \n[INFO] Changing //a back to ///a as it was probably changed by MINGW/MSYS, see http://www.mingw.org/wiki/Posix_path_conversion \n[INFO] Changing //a/b back to ///a/b as it was probably changed by MINGW/MSYS, see http://www.mingw.org/wiki/Posix_path_conversion \n------------------------------ Captured log call -------------------------------\nINFO     datalad.cmdline:main.py:462 Changing // back to /// as it was probably changed by MINGW/MSYS, see http://www.mingw.org/wiki/Posix_path_conversion\nINFO     datalad.cmdline:main.py:462 Changing //a back to ///a as it was probably changed by MINGW/MSYS, see http://www.mingw.org/wiki/Posix_path_conversion\nINFO     datalad.cmdline:main.py:462 Changing //a/b back to ///a/b as it was probably changed by MINGW/MSYS, see http://www.mingw.org/wiki/Posix_path_conversion\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::test_version\nPASSED r2e_tests/test_1.py::test_help_np\nPASSED r2e_tests/test_1.py::test_usage_on_insufficient_args\nPASSED r2e_tests/test_1.py::test_subcmd_usage_on_unknown_args\nPASSED r2e_tests/test_1.py::test_fail_with_short_help\nPASSED r2e_tests/test_1.py::test_fix_datalad_ri\nXFAIL r2e_tests/test_1.py::test_incorrect_options - reason: [NOTRUN] yield tests were removed in pytest 4.0 - test_incorrect_options will be ignored\nFAILED r2e_tests/test_1.py::test_combined_short_option - AssertionError: 'unr...\nFAILED r2e_tests/test_1.py::test_script_shims - FileNotFoundError: [Errno 2] ...\nFAILED r2e_tests/test_1.py::test_cfg_override - TypeError: test_cfg_override(...\n==================== 3 failed, 6 passed, 1 xfailed in 0.72s ====================\n",
    "old_commit_res_stderr": ""
}
datalad/cmdline/main.py datalad/cmdline/tests/test_main.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** Combined Short Options Trigger "Unrecognized Argument" Error

**Description:**
When using combined short options without a space, the command fails to parse them correctly. For example, executing the following command:

```bash
datalad -ar
```

produces the error:

```
error: unrecognized argument '-ar'
```

This behavior prevents the command from recognizing and handling the individual `-a` and `-r` options as intended.

**Expected Behavior:**
The command-line parser should correctly interpret combined short options by separating them into individual flags. In this case, `-ar` should be parsed as `-a` and `-r`, allowing the command to execute with both options enabled.

**Actual Behavior:**
Instead of recognizing the combined short options, the parser fails and returns an "unrecognized argument" error, preventing the command from executing the desired functionalities associated with the `-a` and `-r` options.

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### datalad/cmdline/main.py
# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-
# ex: set sts=4 ts=4 sw=4 noet:
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
#   See COPYING file distributed along with the datalad package for the
#   copyright and license terms.
#
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
""""""
from datalad.ui.utils import get_console_width

__docformat__ = 'restructuredtext'

import logging
lgr = logging.getLogger('datalad.cmdline')

lgr.log(5, "Importing cmdline.main")

import argparse
import sys
import textwrap
from importlib import import_module
import os

from six import text_type

import datalad

from datalad.cmdline import helpers
from datalad.plugin import _get_plugins
from datalad.plugin import _load_plugin
from datalad.support.exceptions import InsufficientArgumentsError
from datalad.support.exceptions import IncompleteResultsError
from datalad.support.exceptions import CommandError
from .helpers import strip_arg_from_argv
from ..utils import setup_exceptionhook, chpwd
from ..utils import assure_unicode
from ..utils import on_msys_tainted_paths
from ..dochelpers import exc_str


def _license_info():
    return """\
Copyright (c) 2013-2018 DataLad developers

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""


class ArgumentParserDisableAbbrev(argparse.ArgumentParser):
    # Don't accept abbreviations for long options. With py3.5 and above, we
    # could just use allow_abbrev=False.
    #
    # See https://bugs.python.org/issue14910#msg204678
    def _get_option_tuples(self, option_string):
        return []


# TODO:  OPT look into making setup_parser smarter to become faster
# Now it seems to take up to 200ms to do all the parser setup
# even though it might not be necessary to know about all the commands etc.
# I wondered if it could somehow decide on what commands to worry about etc
# by going through sys.args first
def setup_parser(
        cmdlineargs,
        formatter_class=argparse.RawDescriptionHelpFormatter,
        return_subparsers=False):
    lgr.log(5, "Starting to setup_parser")
    # delay since it can be a heavy import
    from ..interface.base import dedent_docstring, get_interface_groups, \
        get_cmdline_command_name, alter_interface_docs_for_cmdline
    # setup cmdline args parser
    parts = {}
    # main parser
    parser = ArgumentParserDisableAbbrev(
        # cannot use '@' because we need to input JSON-LD properties (which might come wit @ prefix)
        # MH: question, do we need this at all?
        fromfile_prefix_chars=':',
        # usage="%(prog)s ...",
        description=dedent_docstring("""\
            Comprehensive data management solution

            DataLad provides a unified data distribution system built on the Git
            and Git-annex. DataLad command line tools allow to manipulate (obtain,
            create, update, publish, etc.) datasets and provide a comprehensive
            toolbox for joint management of data and code. Compared to Git/annex
            it primarly extends their functionality to transparently and
            simultaneously work with multiple inter-related repositories."""),
        epilog='"Be happy!"',
        formatter_class=formatter_class,
        add_help=False)
    # common options
    helpers.parser_add_common_opt(parser, 'log_level')
    helpers.parser_add_common_opt(parser, 'pbs_runner')
    helpers.parser_add_common_opt(parser, 'change_path')
    helpers.parser_add_common_opt(
        parser,
        'version',
        version='datalad %s\n\n%s' % (datalad.__version__, _license_info()))
    if __debug__:
        parser.add_argument(
            '--dbg', action='store_true', dest='common_debug',
            help="enter Python debugger when uncaught exception happens")
        parser.add_argument(
            '--idbg', action='store_true', dest='common_idebug',
            help="enter IPython debugger when uncaught exception happens")
    parser.add_argument(
        '-c', action='append', dest='cfg_overrides', metavar='KEY=VALUE',
        help="""configuration variable setting. Overrides any configuration
        read from a file, but is potentially overridden itself by configuration
        variables in the process environment.""")
    parser.add_argument(
        '-f', '--output-format', dest='common_output_format',
        default='default',
        type=assure_unicode,
        metavar="{default,json,json_pp,tailored,'<template>'}",
        help="""select format for returned command results. 'default' give one line
        per result reporting action, status, path and an optional message;
        'json' renders a JSON object with all properties for each result (one per
        line); 'json_pp' pretty-prints JSON spanning multiple lines; 'tailored'
        enables a command-specific rendering style that is typically
        tailored to human consumption (no result output otherwise),
        '<template>' reports any value(s) of any result properties in any format
        indicated by the template (e.g. '{path}'; compare with JSON
        output for all key-value choices). The template syntax follows the Python
        "format() language". It is possible to report individual
        dictionary values, e.g. '{metadata[name]}'. If a 2nd-level key contains
        a colon, e.g. 'music:Genre', ':' must be substituted by '#' in the template,
        like so: '{metadata[music#Genre]}'.""")
    parser.add_argument(
        '--report-status', dest='common_report_status',
        choices=['success', 'failure', 'ok', 'notneeded', 'impossible', 'error'],
        help="""constrain command result report to records matching the given
        status. 'success' is a synonym for 'ok' OR 'notneeded', 'failure' stands
        for 'impossible' OR 'error'.""")
    parser.add_argument(
        '--report-type', dest='common_report_type',
        choices=['dataset', 'file'],
        action='append',
        help="""constrain command result report to records matching the given
        type. Can be given more than once to match multiple types.""")
    parser.add_argument(
        '--on-failure', dest='common_on_failure',
        choices=['ignore', 'continue', 'stop'],
        # no default: better be configure per-command
        help="""when an operation fails: 'ignore' and continue with remaining
        operations, the error is logged but does not lead to a non-zero exit code
        of the command; 'continue' works like 'ignore', but an error causes a
        non-zero exit code; 'stop' halts on first failure and yields non-zero exit
        code. A failure is any result with status 'impossible' or 'error'.""")
    parser.add_argument(
        '--proc-pre', dest='common_proc_pre',
        nargs='+',
        action='append',
        metavar=('<PROCEDURE NAME>', 'ARGS'),
        help="""Dataset procedure to run before the main command (see run-procedure
        command for details). This option can be given more than once to run
        multiple procedures in the order in which they were given.
        It is important to specify the target dataset via the --dataset argument
        of the main command."""),
    parser.add_argument(
        '--proc-post', dest='common_proc_post',
        nargs='+',
        action='append',
        metavar=('<PROCEDURE NAME>', 'ARGS'),
        help="""Like --proc-pre, but procedures are executed after the main command
        has finished."""),
    parser.add_argument(
        '--cmd', dest='_', action='store_true',
        help="""syntactical helper that can be used to end the list of global
        command line options before the subcommand label. Options like
        --proc-pre can take an arbitrary number of arguments and may require
        to be followed by a single --cmd in order to enable identification
        of the subcommand.""")

    # yoh: atm we only dump to console.  Might adopt the same separation later on
    #      and for consistency will call it --verbose-level as well for now
    # log-level is set via common_opts ATM
    # parser.add_argument('--log-level',
    #                     choices=('critical', 'error', 'warning', 'info', 'debug'),
    #                     dest='common_log_level',
    #                     help="""level of verbosity in log files. By default
    #                          everything, including debug messages is logged.""")
    #parser.add_argument('-l', '--verbose-level',
    #                    choices=('critical', 'error', 'warning', 'info', 'debug'),
    #                    dest='common_verbose_level',
    #                    help="""level of verbosity of console output. By default
    #                         only warnings and errors are printed.""")

    # Before doing anything additional and possibly expensive see may be that
    # we have got the command already
    need_single_subparser = False if return_subparsers else None
    fail_handler = (lambda *a, **kw: True) \
        if return_subparsers else fail_with_short_help
    try:
        parsed_args, unparsed_args = parser._parse_known_args(
            cmdlineargs[1:], argparse.Namespace())
        if not unparsed_args:
            fail_handler(parser, msg="too few arguments", exit_code=2)
        lgr.debug("Command line args 1st pass. Parsed: %s Unparsed: %s",
                  parsed_args, unparsed_args)
    except Exception as exc:
        lgr.debug("Early parsing failed with %s", exc_str(exc))
        need_single_subparser = False
        unparsed_args = cmdlineargs[1:]  # referenced before assignment otherwise

    interface_groups = get_interface_groups()
    # TODO: see if we could retain "generator" for plugins
    # ATM we need to make it explicit so we could check the command(s) below
    # It could at least follow the same destiny as extensions so we would
    # just do more iterative "load ups"
    interface_groups.append(('plugins', 'Plugins', list(_get_plugins())))

    # First unparsed could be either unknown option to top level "datalad"
    # or a command. Among unknown could be --help/--help-np which would
    # need to be dealt with
    unparsed_arg = unparsed_args[0] if unparsed_args else None
    if need_single_subparser is not None \
            or unparsed_arg in ('--help', '--help-np', '-h'):
        need_single_subparser = False
        add_entrypoints_to_interface_groups(interface_groups)
    elif unparsed_arg.startswith('-'):  # unknown option
        fail_with_short_help(parser,
                             msg="unrecognized argument %s" % unparsed_arg,
                             exit_code=2)
        # if we could get a list of options known to parser,
        # we could suggest them
        # known=get_all_options(parser), provided=unparsed_arg)
    else:  # the command to handle
        known_commands = get_commands_from_groups(interface_groups)
        if unparsed_arg not in known_commands:
            # need to load all the extensions and try again
            add_entrypoints_to_interface_groups(interface_groups)
            known_commands = get_commands_from_groups(interface_groups)

        if unparsed_arg not in known_commands:
            # check if might be coming from known extensions
            from ..interface import _known_extension_commands
            extension_commands = {
                c: e
                for e, commands in _known_extension_commands.items()
                for c in commands
            }
            hint = None
            if unparsed_arg in extension_commands:
                hint = "Command %s is provided by (not installed) extension %s." \
                      % (unparsed_arg, extension_commands[unparsed_arg])
            fail_with_short_help(
                parser,
                hint=hint,
                provided=unparsed_arg,
                known=list(known_commands.keys()) + list(extension_commands.keys())
            )
        if need_single_subparser is None:
            need_single_subparser = unparsed_arg

    # --help specification was delayed since it causes immediate printout of
    # --help output before we setup --help for each command
    helpers.parser_add_common_opt(parser, 'help')

    grp_short_descriptions = []
    # create subparser, use module suffix as cmd name
    subparsers = parser.add_subparsers()
    for _, _, _interfaces \
            in sorted(interface_groups, key=lambda x: x[1]):
        # for all subcommand modules it can find
        cmd_short_descriptions = []

        for _intfspec in _interfaces:
            cmd_name = get_cmdline_command_name(_intfspec)
            if need_single_subparser and cmd_name != need_single_subparser:
                continue
            if isinstance(_intfspec[1], dict):
                # plugin
                _intf = _load_plugin(_intfspec[1]['file'], fail=False)
                if _intf is None:
                    # TODO:  add doc why we could skip this one... makes this
                    # loop harder to extract into a dedicated function
                    continue
            else:
                # turn the interface spec into an instance
                lgr.log(5, "Importing module %s " % _intfspec[0])
                try:
                    _mod = import_module(_intfspec[0], package='datalad')
                except Exception as e:
                    lgr.error("Internal error, cannot import interface '%s': %s",
                              _intfspec[0], exc_str(e))
                    continue
                _intf = getattr(_mod, _intfspec[1])
            # deal with optional parser args
            if hasattr(_intf, 'parser_args'):
                parser_args = _intf.parser_args
            else:
                parser_args = dict(formatter_class=formatter_class)
                # use class description, if no explicit description is available
                intf_doc = '' if _intf.__doc__ is None else _intf.__doc__.strip()
                if hasattr(_intf, '_docs_'):
                    # expand docs
                    intf_doc = intf_doc.format(**_intf._docs_)
                parser_args['description'] = alter_interface_docs_for_cmdline(
                    intf_doc)
            subparser = subparsers.add_parser(cmd_name, add_help=False, **parser_args)
            # our own custom help for all commands
            helpers.parser_add_common_opt(subparser, 'help')
            # let module configure the parser
            _intf.setup_parser(subparser)
            # logger for command

            # configure 'run' function for this command
            plumbing_args = dict(
                func=_intf.call_from_parser,
                logger=logging.getLogger(_intf.__module__),
                subparser=subparser)
            if hasattr(_intf, 'result_renderer_cmdline'):
                plumbing_args['result_renderer'] = _intf.result_renderer_cmdline
            subparser.set_defaults(**plumbing_args)
            parts[cmd_name] = subparser
            # store short description for later
            sdescr = getattr(_intf, 'short_description',
                             parser_args['description'].split('\n')[0])
            cmd_short_descriptions.append((cmd_name, sdescr))
        grp_short_descriptions.append(cmd_short_descriptions)

    # create command summary
    if '--help' in cmdlineargs or '--help-np' in cmdlineargs:
        parser.description = get_description_with_cmd_summary(
            grp_short_descriptions,
            interface_groups,
            parser.description)

    parts['datalad'] = parser
    lgr.log(5, "Finished setup_parser")
    if return_subparsers:
        return parts
    else:
        return parser


def fail_with_short_help(parser=None,
                         msg=None,
                         known=None, provided=None,
                         hint=None,
                         exit_code=1,
                         what="command",
                         out=None):
    """Generic helper to fail
    with short help possibly hinting on what was intended if `known`
    were provided
    """
    out = out or sys.stderr
    if msg:
        out.write("error: %s\n" % msg)
    if not known:
        if parser:
            # just to appear in print_usage also consistent with --help output
            parser.add_argument("command [command-opts]")
            parser.print_usage(file=out)
    else:
        out.write(
            "datalad: Unknown %s %r.  See 'datalad --help'.\n\n"
            % (what, provided,))
        if provided not in known:
            import difflib
            suggestions = difflib.get_close_matches(provided, known)
            if suggestions:
                out.write(
                    "Did you mean one of these?\n        %s\n"
                    % "\n        ".join(suggestions))
        # Too noisy
        # sys.stderr.write(" All known %ss: %s\n"
        #                   % (what, ", ".join(sorted(known))))
    if hint:
        out.write("Hint: %s\n" % hint)
    raise SystemExit(exit_code)


def get_description_with_cmd_summary(grp_short_descriptions, interface_groups,
                                     parser_description):
    from ..interface.base import dedent_docstring
    lgr.debug("Generating detailed description for the parser")
    cmd_summary = []
    console_width = get_console_width()
    for i, grp in enumerate(
            sorted(interface_groups, key=lambda x: x[1])):
        grp_descr = grp[1]
        grp_cmds = grp_short_descriptions[i]

        cmd_summary.append('\n*%s*\n' % (grp_descr,))
        for cd in grp_cmds:
            cmd_summary.append('  %s\n%s'
                               % ((cd[0],
                                   textwrap.fill(
                                       cd[1].rstrip(' .'),
                                       console_width - 5,
                                       initial_indent=' ' * 6,
                                       subsequent_indent=' ' * 6))))
    # we need one last formal section to not have the trailed be
    # confused with the last command group
    cmd_summary.append('\n*General information*\n')
    detailed_description = '%s\n%s\n\n%s' \
                           % (parser_description,
                              '\n'.join(cmd_summary),
                              textwrap.fill(dedent_docstring("""\
    Detailed usage information for individual commands is
    available via command-specific --help, i.e.:
    datalad <command> --help"""),
                                            console_width - 5,
                                            initial_indent='',
                                            subsequent_indent=''))
    return detailed_description


def get_commands_from_groups(groups):
    """Get a dictionary of command: interface_spec"""
    from ..interface.base import get_cmdline_command_name
    return {
        get_cmdline_command_name(_intfspec): _intfspec
        for _, _, _interfaces in groups
        for _intfspec in _interfaces
    }


def add_entrypoints_to_interface_groups(interface_groups):
    lgr.debug("Loading entrypoints")
    from pkg_resources import iter_entry_points  # delay expensive import
    for ep in iter_entry_points('datalad.extensions'):
        lgr.debug(
            'Loading entrypoint %s from datalad.extensions for docs building',
            ep.name)
        try:
            spec = ep.load()
            interface_groups.append((ep.name, spec[0], spec[1]))
            lgr.debug('Loaded entrypoint %s', ep.name)
        except Exception as e:
            lgr.warning('Failed to load entrypoint %s: %s', ep.name, exc_str(e))
            continue

def _fix_datalad_ri(s):
    """Fixup argument if it was a DataLadRI and had leading / removed

    See gh-2643
    """
    if s.startswith('//') and (len(s) == 2 or (len(s) > 2 and s[2] != '/')):
        lgr.info(
            "Changing %s back to /%s as it was probably changed by MINGW/MSYS, "
            "see http://www.mingw.org/wiki/Posix_path_conversion", s, s)
        return "/" + s
    return s


def main(args=None):
    lgr.log(5, "Starting main(%r)", args)
    args = args or sys.argv
    if on_msys_tainted_paths:
        # Possibly present DataLadRIs were stripped of a leading /
        args = [_fix_datalad_ri(s) for s in args]
    # PYTHON_ARGCOMPLETE_OK
    parser = setup_parser(args)
    try:
        import argcomplete
        argcomplete.autocomplete(parser)
    except ImportError:
        pass

    # parse cmd args
    lgr.debug("Parsing known args among %s", repr(args))
    cmdlineargs, unparsed_args = parser.parse_known_args(args[1:])
    has_func = hasattr(cmdlineargs, 'func') and cmdlineargs.func is not None
    if unparsed_args:
        if has_func and cmdlineargs.func.__self__.__name__ != 'Export':
            lgr.error('unknown argument{}: {}'.format(
                's' if len(unparsed_args) > 1 else '',
                unparsed_args if len(unparsed_args) > 1 else unparsed_args[0]))
            cmdlineargs.subparser.print_usage()
            sys.exit(1)
        else:
            # store all unparsed arguments
            cmdlineargs.datalad_unparsed_args = unparsed_args

    # to possibly be passed into PBS scheduled call
    args_ = args or sys.argv

    if cmdlineargs.cfg_overrides is not None:
        overrides = dict([
            (o.split('=')[0], '='.join(o.split('=')[1:]))
            for o in cmdlineargs.cfg_overrides])
        datalad.cfg.overrides.update(overrides)

    # enable overrides
    datalad.cfg.reload(force=True)

    if cmdlineargs.change_path is not None:
        from .common_args import change_path as change_path_opt
        for path in cmdlineargs.change_path:
            chpwd(path)
            args_ = strip_arg_from_argv(args_, path, change_path_opt[1])

    ret = None
    if cmdlineargs.pbs_runner:
        from .helpers import run_via_pbs
        from .common_args import pbs_runner as pbs_runner_opt
        args_ = strip_arg_from_argv(args_, cmdlineargs.pbs_runner, pbs_runner_opt[1])
        # run the function associated with the selected command
        run_via_pbs(args_, cmdlineargs.pbs_runner)
    elif has_func:
        if cmdlineargs.common_debug or cmdlineargs.common_idebug:
            # so we could see/stop clearly at the point of failure
            setup_exceptionhook(ipython=cmdlineargs.common_idebug)
            from datalad.interface.base import Interface
            Interface._interrupted_exit_code = None
            ret = cmdlineargs.func(cmdlineargs)
        else:
            # otherwise - guard and only log the summary. Postmortem is not
            # as convenient if being caught in this ultimate except
            try:
                ret = cmdlineargs.func(cmdlineargs)
            except InsufficientArgumentsError as exc:
                # if the func reports inappropriate usage, give help output
                lgr.error('%s (%s)' % (exc_str(exc), exc.__class__.__name__))
                cmdlineargs.subparser.print_usage(sys.stderr)
                sys.exit(2)
            except IncompleteResultsError as exc:
                # rendering for almost all commands now happens 'online'
                # hence we are no longer attempting to render the actual
                # results in an IncompleteResultsError, ubt rather trust that
                # this happened before

                # in general we do not want to see the error again, but
                # present in debug output
                lgr.debug('could not perform all requested actions: %s',
                          exc_str(exc))
                sys.exit(1)
            except CommandError as exc:
                # behave as if the command ran directly, importantly pass
                # exit code as is
                if exc.msg:
                    msg = exc.msg.encode() if isinstance(exc.msg, text_type) else exc.msg
                    os.write(2, msg + b"\n")
                if exc.stdout:
                    os.write(1, exc.stdout.encode() if isinstance(exc.stdout, text_type) else exc.stdout)
                if exc.stderr:
                    os.write(2, exc.stderr.encode() if isinstance(exc.stderr, text_type) else exc.stderr)
                # We must not exit with 0 code if any exception got here but
                # had no code defined
                sys.exit(exc.code if exc.code is not None else 1)
            except Exception as exc:
                lgr.error('%s (%s)' % (exc_str(exc), exc.__class__.__name__))
                sys.exit(1)
    else:
        # just let argparser spit out its error, since there is smth wrong
        parser.parse_args(args)
        # if that one didn't puke -- we should
        parser.print_usage()
        lgr.error("Please specify the command")
        sys.exit(2)

    try:
        if hasattr(cmdlineargs, 'result_renderer'):
            cmdlineargs.result_renderer(ret, cmdlineargs)
    except Exception as exc:
        lgr.error("Failed to render results due to %s", exc_str(exc))
        sys.exit(1)

lgr.log(5, "Done importing cmdline.main")

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
[ISSUE]
**Title:** Combined Short Options Trigger "Unrecognized Argument" Error

**Description:**
When using combined short options without a space, the command fails to parse them correctly. For example, executing the following command:

```bash
datalad -ar
```

produces the error:

```
error: unrecognized argument '-ar'
```

This behavior prevents the command from recognizing and handling the individual `-a` and `-r` options as intended.

**Expected Behavior:**
The command-line parser should correctly interpret combined short options by separating them into individual flags. In this case, `-ar` should be parsed as `-a` and `-r`, allowing the command to execute with both options enabled.

**Actual Behavior:**
Instead of recognizing the combined short options, the parser fails and returns an "unrecognized argument" error, preventing the command from executing the desired functionalities associated with the `-a` and `-r` options.

[/ISSUE]
{
    "test_version": "PASSED",
    "test_help_np": "PASSED",
    "test_usage_on_insufficient_args": "PASSED",
    "test_subcmd_usage_on_unknown_args": "PASSED",
    "test_combined_short_option": "PASSED",
    "test_fail_with_short_help": "PASSED",
    "test_fix_datalad_ri": "PASSED",
    "test_script_shims": "FAILED",
    "test_cfg_override": "FAILED"
}
diff --git a/datalad/cmdline/main.py b/datalad/cmdline/main.py
index cf45c3830..bbb7d492b 100644
--- a/datalad/cmdline/main.py
+++ b/datalad/cmdline/main.py
@@ -67,9 +67,15 @@ class ArgumentParserDisableAbbrev(argparse.ArgumentParser):
     # Don't accept abbreviations for long options. With py3.5 and above, we
     # could just use allow_abbrev=False.
     #
-    # See https://bugs.python.org/issue14910#msg204678
+    # Modified from the solution posted at
+    # https://bugs.python.org/issue14910#msg204678
     def _get_option_tuples(self, option_string):
-        return []
+        chars = self.prefix_chars
+        if option_string[0] in chars and option_string[1] in chars:
+            # option_string is a long flag. Disable abbreviation.
+            return []
+        return super(ArgumentParserDisableAbbrev, self)._get_option_tuples(
+            option_string)
 
 
 # TODO:  OPT look into making setup_parser smarter to become faster

{"datalad/cmdline/main.py": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 noet:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"\"\"\"\nfrom datalad.ui.utils import get_console_width\n\n__docformat__ = 'restructuredtext'\n\nimport logging\nlgr = logging.getLogger('datalad.cmdline')\n\nlgr.log(5, \"Importing cmdline.main\")\n\nimport argparse\nimport sys\nimport textwrap\nfrom importlib import import_module\nimport os\n\nfrom six import text_type\n\nimport datalad\n\nfrom datalad.cmdline import helpers\nfrom datalad.plugin import _get_plugins\nfrom datalad.plugin import _load_plugin\nfrom datalad.support.exceptions import InsufficientArgumentsError\nfrom datalad.support.exceptions import IncompleteResultsError\nfrom datalad.support.exceptions import CommandError\nfrom .helpers import strip_arg_from_argv\nfrom ..utils import setup_exceptionhook, chpwd\nfrom ..utils import assure_unicode\nfrom ..utils import on_msys_tainted_paths\nfrom ..dochelpers import exc_str\n\n\ndef _license_info():\n    return \"\"\"\\\nCopyright (c) 2013-2018 DataLad developers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\"\"\"\n\n\nclass ArgumentParserDisableAbbrev(argparse.ArgumentParser):\n    # Don't accept abbreviations for long options. With py3.5 and above, we\n    # could just use allow_abbrev=False.\n    #\n    # See https://bugs.python.org/issue14910#msg204678\n    def _get_option_tuples(self, option_string):\n        return []\n\n\n# TODO:  OPT look into making setup_parser smarter to become faster\n# Now it seems to take up to 200ms to do all the parser setup\n# even though it might not be necessary to know about all the commands etc.\n# I wondered if it could somehow decide on what commands to worry about etc\n# by going through sys.args first\ndef setup_parser(\n        cmdlineargs,\n        formatter_class=argparse.RawDescriptionHelpFormatter,\n        return_subparsers=False):\n    lgr.log(5, \"Starting to setup_parser\")\n    # delay since it can be a heavy import\n    from ..interface.base import dedent_docstring, get_interface_groups, \\\n        get_cmdline_command_name, alter_interface_docs_for_cmdline\n    # setup cmdline args parser\n    parts = {}\n    # main parser\n    parser = ArgumentParserDisableAbbrev(\n        # cannot use '@' because we need to input JSON-LD properties (which might come wit @ prefix)\n        # MH: question, do we need this at all?\n        fromfile_prefix_chars=':',\n        # usage=\"%(prog)s ...\",\n        description=dedent_docstring(\"\"\"\\\n            Comprehensive data management solution\n\n            DataLad provides a unified data distribution system built on the Git\n            and Git-annex. DataLad command line tools allow to manipulate (obtain,\n            create, update, publish, etc.) datasets and provide a comprehensive\n            toolbox for joint management of data and code. Compared to Git/annex\n            it primarly extends their functionality to transparently and\n            simultaneously work with multiple inter-related repositories.\"\"\"),\n        epilog='\"Be happy!\"',\n        formatter_class=formatter_class,\n        add_help=False)\n    # common options\n    helpers.parser_add_common_opt(parser, 'log_level')\n    helpers.parser_add_common_opt(parser, 'pbs_runner')\n    helpers.parser_add_common_opt(parser, 'change_path')\n    helpers.parser_add_common_opt(\n        parser,\n        'version',\n        version='datalad %s\\n\\n%s' % (datalad.__version__, _license_info()))\n    if __debug__:\n        parser.add_argument(\n            '--dbg', action='store_true', dest='common_debug',\n            help=\"enter Python debugger when uncaught exception happens\")\n        parser.add_argument(\n            '--idbg', action='store_true', dest='common_idebug',\n            help=\"enter IPython debugger when uncaught exception happens\")\n    parser.add_argument(\n        '-c', action='append', dest='cfg_overrides', metavar='KEY=VALUE',\n        help=\"\"\"configuration variable setting. Overrides any configuration\n        read from a file, but is potentially overridden itself by configuration\n        variables in the process environment.\"\"\")\n    parser.add_argument(\n        '-f', '--output-format', dest='common_output_format',\n        default='default',\n        type=assure_unicode,\n        metavar=\"{default,json,json_pp,tailored,'<template>'}\",\n        help=\"\"\"select format for returned command results. 'default' give one line\n        per result reporting action, status, path and an optional message;\n        'json' renders a JSON object with all properties for each result (one per\n        line); 'json_pp' pretty-prints JSON spanning multiple lines; 'tailored'\n        enables a command-specific rendering style that is typically\n        tailored to human consumption (no result output otherwise),\n        '<template>' reports any value(s) of any result properties in any format\n        indicated by the template (e.g. '{path}'; compare with JSON\n        output for all key-value choices). The template syntax follows the Python\n        \"format() language\". It is possible to report individual\n        dictionary values, e.g. '{metadata[name]}'. If a 2nd-level key contains\n        a colon, e.g. 'music:Genre', ':' must be substituted by '#' in the template,\n        like so: '{metadata[music#Genre]}'.\"\"\")\n    parser.add_argument(\n        '--report-status', dest='common_report_status',\n        choices=['success', 'failure', 'ok', 'notneeded', 'impossible', 'error'],\n        help=\"\"\"constrain command result report to records matching the given\n        status. 'success' is a synonym for 'ok' OR 'notneeded', 'failure' stands\n        for 'impossible' OR 'error'.\"\"\")\n    parser.add_argument(\n        '--report-type', dest='common_report_type',\n        choices=['dataset', 'file'],\n        action='append',\n        help=\"\"\"constrain command result report to records matching the given\n        type. Can be given more than once to match multiple types.\"\"\")\n    parser.add_argument(\n        '--on-failure', dest='common_on_failure',\n        choices=['ignore', 'continue', 'stop'],\n        # no default: better be configure per-command\n        help=\"\"\"when an operation fails: 'ignore' and continue with remaining\n        operations, the error is logged but does not lead to a non-zero exit code\n        of the command; 'continue' works like 'ignore', but an error causes a\n        non-zero exit code; 'stop' halts on first failure and yields non-zero exit\n        code. A failure is any result with status 'impossible' or 'error'.\"\"\")\n    parser.add_argument(\n        '--proc-pre', dest='common_proc_pre',\n        nargs='+',\n        action='append',\n        metavar=('<PROCEDURE NAME>', 'ARGS'),\n        help=\"\"\"Dataset procedure to run before the main command (see run-procedure\n        command for details). This option can be given more than once to run\n        multiple procedures in the order in which they were given.\n        It is important to specify the target dataset via the --dataset argument\n        of the main command.\"\"\"),\n    parser.add_argument(\n        '--proc-post', dest='common_proc_post',\n        nargs='+',\n        action='append',\n        metavar=('<PROCEDURE NAME>', 'ARGS'),\n        help=\"\"\"Like --proc-pre, but procedures are executed after the main command\n        has finished.\"\"\"),\n    parser.add_argument(\n        '--cmd', dest='_', action='store_true',\n        help=\"\"\"syntactical helper that can be used to end the list of global\n        command line options before the subcommand label. Options like\n        --proc-pre can take an arbitrary number of arguments and may require\n        to be followed by a single --cmd in order to enable identification\n        of the subcommand.\"\"\")\n\n    # yoh: atm we only dump to console.  Might adopt the same separation later on\n    #      and for consistency will call it --verbose-level as well for now\n    # log-level is set via common_opts ATM\n    # parser.add_argument('--log-level',\n    #                     choices=('critical', 'error', 'warning', 'info', 'debug'),\n    #                     dest='common_log_level',\n    #                     help=\"\"\"level of verbosity in log files. By default\n    #                          everything, including debug messages is logged.\"\"\")\n    #parser.add_argument('-l', '--verbose-level',\n    #                    choices=('critical', 'error', 'warning', 'info', 'debug'),\n    #                    dest='common_verbose_level',\n    #                    help=\"\"\"level of verbosity of console output. By default\n    #                         only warnings and errors are printed.\"\"\")\n\n    # Before doing anything additional and possibly expensive see may be that\n    # we have got the command already\n    need_single_subparser = False if return_subparsers else None\n    fail_handler = (lambda *a, **kw: True) \\\n        if return_subparsers else fail_with_short_help\n    try:\n        parsed_args, unparsed_args = parser._parse_known_args(\n            cmdlineargs[1:], argparse.Namespace())\n        if not unparsed_args:\n            fail_handler(parser, msg=\"too few arguments\", exit_code=2)\n        lgr.debug(\"Command line args 1st pass. Parsed: %s Unparsed: %s\",\n                  parsed_args, unparsed_args)\n    except Exception as exc:\n        lgr.debug(\"Early parsing failed with %s\", exc_str(exc))\n        need_single_subparser = False\n        unparsed_args = cmdlineargs[1:]  # referenced before assignment otherwise\n\n    interface_groups = get_interface_groups()\n    # TODO: see if we could retain \"generator\" for plugins\n    # ATM we need to make it explicit so we could check the command(s) below\n    # It could at least follow the same destiny as extensions so we would\n    # just do more iterative \"load ups\"\n    interface_groups.append(('plugins', 'Plugins', list(_get_plugins())))\n\n    # First unparsed could be either unknown option to top level \"datalad\"\n    # or a command. Among unknown could be --help/--help-np which would\n    # need to be dealt with\n    unparsed_arg = unparsed_args[0] if unparsed_args else None\n    if need_single_subparser is not None \\\n            or unparsed_arg in ('--help', '--help-np', '-h'):\n        need_single_subparser = False\n        add_entrypoints_to_interface_groups(interface_groups)\n    elif unparsed_arg.startswith('-'):  # unknown option\n        fail_with_short_help(parser,\n                             msg=\"unrecognized argument %s\" % unparsed_arg,\n                             exit_code=2)\n        # if we could get a list of options known to parser,\n        # we could suggest them\n        # known=get_all_options(parser), provided=unparsed_arg)\n    else:  # the command to handle\n        known_commands = get_commands_from_groups(interface_groups)\n        if unparsed_arg not in known_commands:\n            # need to load all the extensions and try again\n            add_entrypoints_to_interface_groups(interface_groups)\n            known_commands = get_commands_from_groups(interface_groups)\n\n        if unparsed_arg not in known_commands:\n            # check if might be coming from known extensions\n            from ..interface import _known_extension_commands\n            extension_commands = {\n                c: e\n                for e, commands in _known_extension_commands.items()\n                for c in commands\n            }\n            hint = None\n            if unparsed_arg in extension_commands:\n                hint = \"Command %s is provided by (not installed) extension %s.\" \\\n                      % (unparsed_arg, extension_commands[unparsed_arg])\n            fail_with_short_help(\n                parser,\n                hint=hint,\n                provided=unparsed_arg,\n                known=list(known_commands.keys()) + list(extension_commands.keys())\n            )\n        if need_single_subparser is None:\n            need_single_subparser = unparsed_arg\n\n    # --help specification was delayed since it causes immediate printout of\n    # --help output before we setup --help for each command\n    helpers.parser_add_common_opt(parser, 'help')\n\n    grp_short_descriptions = []\n    # create subparser, use module suffix as cmd name\n    subparsers = parser.add_subparsers()\n    for _, _, _interfaces \\\n            in sorted(interface_groups, key=lambda x: x[1]):\n        # for all subcommand modules it can find\n        cmd_short_descriptions = []\n\n        for _intfspec in _interfaces:\n            cmd_name = get_cmdline_command_name(_intfspec)\n            if need_single_subparser and cmd_name != need_single_subparser:\n                continue\n            if isinstance(_intfspec[1], dict):\n                # plugin\n                _intf = _load_plugin(_intfspec[1]['file'], fail=False)\n                if _intf is None:\n                    # TODO:  add doc why we could skip this one... makes this\n                    # loop harder to extract into a dedicated function\n                    continue\n            else:\n                # turn the interface spec into an instance\n                lgr.log(5, \"Importing module %s \" % _intfspec[0])\n                try:\n                    _mod = import_module(_intfspec[0], package='datalad')\n                except Exception as e:\n                    lgr.error(\"Internal error, cannot import interface '%s': %s\",\n                              _intfspec[0], exc_str(e))\n                    continue\n                _intf = getattr(_mod, _intfspec[1])\n            # deal with optional parser args\n            if hasattr(_intf, 'parser_args'):\n                parser_args = _intf.parser_args\n            else:\n                parser_args = dict(formatter_class=formatter_class)\n                # use class description, if no explicit description is available\n                intf_doc = '' if _intf.__doc__ is None else _intf.__doc__.strip()\n                if hasattr(_intf, '_docs_'):\n                    # expand docs\n                    intf_doc = intf_doc.format(**_intf._docs_)\n                parser_args['description'] = alter_interface_docs_for_cmdline(\n                    intf_doc)\n            subparser = subparsers.add_parser(cmd_name, add_help=False, **parser_args)\n            # our own custom help for all commands\n            helpers.parser_add_common_opt(subparser, 'help')\n            # let module configure the parser\n            _intf.setup_parser(subparser)\n            # logger for command\n\n            # configure 'run' function for this command\n            plumbing_args = dict(\n                func=_intf.call_from_parser,\n                logger=logging.getLogger(_intf.__module__),\n                subparser=subparser)\n            if hasattr(_intf, 'result_renderer_cmdline'):\n                plumbing_args['result_renderer'] = _intf.result_renderer_cmdline\n            subparser.set_defaults(**plumbing_args)\n            parts[cmd_name] = subparser\n            # store short description for later\n            sdescr = getattr(_intf, 'short_description',\n                             parser_args['description'].split('\\n')[0])\n            cmd_short_descriptions.append((cmd_name, sdescr))\n        grp_short_descriptions.append(cmd_short_descriptions)\n\n    # create command summary\n    if '--help' in cmdlineargs or '--help-np' in cmdlineargs:\n        parser.description = get_description_with_cmd_summary(\n            grp_short_descriptions,\n            interface_groups,\n            parser.description)\n\n    parts['datalad'] = parser\n    lgr.log(5, \"Finished setup_parser\")\n    if return_subparsers:\n        return parts\n    else:\n        return parser\n\n\ndef fail_with_short_help(parser=None,\n                         msg=None,\n                         known=None, provided=None,\n                         hint=None,\n                         exit_code=1,\n                         what=\"command\",\n                         out=None):\n    \"\"\"Generic helper to fail\n    with short help possibly hinting on what was intended if `known`\n    were provided\n    \"\"\"\n    out = out or sys.stderr\n    if msg:\n        out.write(\"error: %s\\n\" % msg)\n    if not known:\n        if parser:\n            # just to appear in print_usage also consistent with --help output\n            parser.add_argument(\"command [command-opts]\")\n            parser.print_usage(file=out)\n    else:\n        out.write(\n            \"datalad: Unknown %s %r.  See 'datalad --help'.\\n\\n\"\n            % (what, provided,))\n        if provided not in known:\n            import difflib\n            suggestions = difflib.get_close_matches(provided, known)\n            if suggestions:\n                out.write(\n                    \"Did you mean one of these?\\n        %s\\n\"\n                    % \"\\n        \".join(suggestions))\n        # Too noisy\n        # sys.stderr.write(\" All known %ss: %s\\n\"\n        #                   % (what, \", \".join(sorted(known))))\n    if hint:\n        out.write(\"Hint: %s\\n\" % hint)\n    raise SystemExit(exit_code)\n\n\ndef get_description_with_cmd_summary(grp_short_descriptions, interface_groups,\n                                     parser_description):\n    from ..interface.base import dedent_docstring\n    lgr.debug(\"Generating detailed description for the parser\")\n    cmd_summary = []\n    console_width = get_console_width()\n    for i, grp in enumerate(\n            sorted(interface_groups, key=lambda x: x[1])):\n        grp_descr = grp[1]\n        grp_cmds = grp_short_descriptions[i]\n\n        cmd_summary.append('\\n*%s*\\n' % (grp_descr,))\n        for cd in grp_cmds:\n            cmd_summary.append('  %s\\n%s'\n                               % ((cd[0],\n                                   textwrap.fill(\n                                       cd[1].rstrip(' .'),\n                                       console_width - 5,\n                                       initial_indent=' ' * 6,\n                                       subsequent_indent=' ' * 6))))\n    # we need one last formal section to not have the trailed be\n    # confused with the last command group\n    cmd_summary.append('\\n*General information*\\n')\n    detailed_description = '%s\\n%s\\n\\n%s' \\\n                           % (parser_description,\n                              '\\n'.join(cmd_summary),\n                              textwrap.fill(dedent_docstring(\"\"\"\\\n    Detailed usage information for individual commands is\n    available via command-specific --help, i.e.:\n    datalad <command> --help\"\"\"),\n                                            console_width - 5,\n                                            initial_indent='',\n                                            subsequent_indent=''))\n    return detailed_description\n\n\ndef get_commands_from_groups(groups):\n    \"\"\"Get a dictionary of command: interface_spec\"\"\"\n    from ..interface.base import get_cmdline_command_name\n    return {\n        get_cmdline_command_name(_intfspec): _intfspec\n        for _, _, _interfaces in groups\n        for _intfspec in _interfaces\n    }\n\n\ndef add_entrypoints_to_interface_groups(interface_groups):\n    lgr.debug(\"Loading entrypoints\")\n    from pkg_resources import iter_entry_points  # delay expensive import\n    for ep in iter_entry_points('datalad.extensions'):\n        lgr.debug(\n            'Loading entrypoint %s from datalad.extensions for docs building',\n            ep.name)\n        try:\n            spec = ep.load()\n            interface_groups.append((ep.name, spec[0], spec[1]))\n            lgr.debug('Loaded entrypoint %s', ep.name)\n        except Exception as e:\n            lgr.warning('Failed to load entrypoint %s: %s', ep.name, exc_str(e))\n            continue\n\ndef _fix_datalad_ri(s):\n    \"\"\"Fixup argument if it was a DataLadRI and had leading / removed\n\n    See gh-2643\n    \"\"\"\n    if s.startswith('//') and (len(s) == 2 or (len(s) > 2 and s[2] != '/')):\n        lgr.info(\n            \"Changing %s back to /%s as it was probably changed by MINGW/MSYS, \"\n            \"see http://www.mingw.org/wiki/Posix_path_conversion\", s, s)\n        return \"/\" + s\n    return s\n\n\ndef main(args=None):\n    lgr.log(5, \"Starting main(%r)\", args)\n    args = args or sys.argv\n    if on_msys_tainted_paths:\n        # Possibly present DataLadRIs were stripped of a leading /\n        args = [_fix_datalad_ri(s) for s in args]\n    # PYTHON_ARGCOMPLETE_OK\n    parser = setup_parser(args)\n    try:\n        import argcomplete\n        argcomplete.autocomplete(parser)\n    except ImportError:\n        pass\n\n    # parse cmd args\n    lgr.debug(\"Parsing known args among %s\", repr(args))\n    cmdlineargs, unparsed_args = parser.parse_known_args(args[1:])\n    has_func = hasattr(cmdlineargs, 'func') and cmdlineargs.func is not None\n    if unparsed_args:\n        if has_func and cmdlineargs.func.__self__.__name__ != 'Export':\n            lgr.error('unknown argument{}: {}'.format(\n                's' if len(unparsed_args) > 1 else '',\n                unparsed_args if len(unparsed_args) > 1 else unparsed_args[0]))\n            cmdlineargs.subparser.print_usage()\n            sys.exit(1)\n        else:\n            # store all unparsed arguments\n            cmdlineargs.datalad_unparsed_args = unparsed_args\n\n    # to possibly be passed into PBS scheduled call\n    args_ = args or sys.argv\n\n    if cmdlineargs.cfg_overrides is not None:\n        overrides = dict([\n            (o.split('=')[0], '='.join(o.split('=')[1:]))\n            for o in cmdlineargs.cfg_overrides])\n        datalad.cfg.overrides.update(overrides)\n\n    # enable overrides\n    datalad.cfg.reload(force=True)\n\n    if cmdlineargs.change_path is not None:\n        from .common_args import change_path as change_path_opt\n        for path in cmdlineargs.change_path:\n            chpwd(path)\n            args_ = strip_arg_from_argv(args_, path, change_path_opt[1])\n\n    ret = None\n    if cmdlineargs.pbs_runner:\n        from .helpers import run_via_pbs\n        from .common_args import pbs_runner as pbs_runner_opt\n        args_ = strip_arg_from_argv(args_, cmdlineargs.pbs_runner, pbs_runner_opt[1])\n        # run the function associated with the selected command\n        run_via_pbs(args_, cmdlineargs.pbs_runner)\n    elif has_func:\n        if cmdlineargs.common_debug or cmdlineargs.common_idebug:\n            # so we could see/stop clearly at the point of failure\n            setup_exceptionhook(ipython=cmdlineargs.common_idebug)\n            from datalad.interface.base import Interface\n            Interface._interrupted_exit_code = None\n            ret = cmdlineargs.func(cmdlineargs)\n        else:\n            # otherwise - guard and only log the summary. Postmortem is not\n            # as convenient if being caught in this ultimate except\n            try:\n                ret = cmdlineargs.func(cmdlineargs)\n            except InsufficientArgumentsError as exc:\n                # if the func reports inappropriate usage, give help output\n                lgr.error('%s (%s)' % (exc_str(exc), exc.__class__.__name__))\n                cmdlineargs.subparser.print_usage(sys.stderr)\n                sys.exit(2)\n            except IncompleteResultsError as exc:\n                # rendering for almost all commands now happens 'online'\n                # hence we are no longer attempting to render the actual\n                # results in an IncompleteResultsError, ubt rather trust that\n                # this happened before\n\n                # in general we do not want to see the error again, but\n                # present in debug output\n                lgr.debug('could not perform all requested actions: %s',\n                          exc_str(exc))\n                sys.exit(1)\n            except CommandError as exc:\n                # behave as if the command ran directly, importantly pass\n                # exit code as is\n                if exc.msg:\n                    msg = exc.msg.encode() if isinstance(exc.msg, text_type) else exc.msg\n                    os.write(2, msg + b\"\\n\")\n                if exc.stdout:\n                    os.write(1, exc.stdout.encode() if isinstance(exc.stdout, text_type) else exc.stdout)\n                if exc.stderr:\n                    os.write(2, exc.stderr.encode() if isinstance(exc.stderr, text_type) else exc.stderr)\n                # We must not exit with 0 code if any exception got here but\n                # had no code defined\n                sys.exit(exc.code if exc.code is not None else 1)\n            except Exception as exc:\n                lgr.error('%s (%s)' % (exc_str(exc), exc.__class__.__name__))\n                sys.exit(1)\n    else:\n        # just let argparser spit out its error, since there is smth wrong\n        parser.parse_args(args)\n        # if that one didn't puke -- we should\n        parser.print_usage()\n        lgr.error(\"Please specify the command\")\n        sys.exit(2)\n\n    try:\n        if hasattr(cmdlineargs, 'result_renderer'):\n            cmdlineargs.result_renderer(ret, cmdlineargs)\n    except Exception as exc:\n        lgr.error(\"Failed to render results due to %s\", exc_str(exc))\n        sys.exit(1)\n\nlgr.log(5, \"Done importing cmdline.main\")\n"}
A user will ask you to solve a task. You should first draft your thinking process (inner monologue). Then, generate the solution.

Your response format must follow the template below:
<think>
Your thoughts or/and draft, like working through an exercise on scratch paper. Be as casual and as long as you want until you are confident to generate a correct solution.
</think>
<solution>
Final solution presented to the user.
</solution>
system
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** Combined Short Options Trigger "Unrecognized Argument" Error

**Description:**
When using combined short options without a space, the command fails to parse them correctly. For example, executing the following command:

```bash
datalad -ar
```

produces the error:

```
error: unrecognized argument '-ar'
```

This behavior prevents the command from recognizing and handling the individual `-a` and `-r` options as intended.

**Expected Behavior:**
The command-line parser should correctly interpret combined short options by separating them into individual flags. In this case, `-ar` should be parsed as `-a` and `-r`, allowing the command to execute with both options enabled.

**Actual Behavior:**
Instead of recognizing the combined short options, the parser fails and returns an "unrecognized argument" error, preventing the command from executing the desired functionalities associated with the `-a` and `-r` options.

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### datalad/cmdline/main.py
# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-
# ex: set sts=4 ts=4 sw=4 noet:
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
#   See COPYING file distributed along with the datalad package for the
#   copyright and license terms.
#
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
""""""
from datalad.ui.utils import get_console_width

__docformat__ = 'restructuredtext'

import logging
lgr = logging.getLogger('datalad.cmdline')

lgr.log(5, "Importing cmdline.main")

import argparse
import sys
import textwrap
from importlib import import_module
import os

from six import text_type

import datalad

from datalad.cmdline import helpers
from datalad.plugin import _get_plugins
from datalad.plugin import _load_plugin
from datalad.support.exceptions import InsufficientArgumentsError
from datalad.support.exceptions import IncompleteResultsError
from datalad.support.exceptions import CommandError
from .helpers import strip_arg_from_argv
from ..utils import setup_exceptionhook, chpwd
from ..utils import assure_unicode
from ..utils import on_msys_tainted_paths
from ..dochelpers import exc_str


def _license_info():
    return """\
Copyright (c) 2013-2018 DataLad developers

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""


class ArgumentParserDisableAbbrev(argparse.ArgumentParser):
    # Don't accept abbreviations for long options. With py3.5 and above, we
    # could just use allow_abbrev=False.
    #
    # See https://bugs.python.org/issue14910#msg204678
    def _get_option_tuples(self, option_string):
        return []


# TODO:  OPT look into making setup_parser smarter to become faster
# Now it seems to take up to 200ms to do all the parser setup
# even though it might not be necessary to know about all the commands etc.
# I wondered if it could somehow decide on what commands to worry about etc
# by going through sys.args first
def setup_parser(
        cmdlineargs,
        formatter_class=argparse.RawDescriptionHelpFormatter,
        return_subparsers=False):
    lgr.log(5, "Starting to setup_parser")
    # delay since it can be a heavy import
    from ..interface.base import dedent_docstring, get_interface_groups, \
        get_cmdline_command_name, alter_interface_docs_for_cmdline
    # setup cmdline args parser
    parts = {}
    # main parser
    parser = ArgumentParserDisableAbbrev(
        # cannot use '@' because we need to input JSON-LD properties (which might come wit @ prefix)
        # MH: question, do we need this at all?
        fromfile_prefix_chars=':',
        # usage="%(prog)s ...",
        description=dedent_docstring("""\
            Comprehensive data management solution

            DataLad provides a unified data distribution system built on the Git
            and Git-annex. DataLad command line tools allow to manipulate (obtain,
            create, update, publish, etc.) datasets and provide a comprehensive
            toolbox for joint management of data and code. Compared to Git/annex
            it primarly extends their functionality to transparently and
            simultaneously work with multiple inter-related repositories."""),
        epilog='"Be happy!"',
        formatter_class=formatter_class,
        add_help=False)
    # common options
    helpers.parser_add_common_opt(parser, 'log_level')
    helpers.parser_add_common_opt(parser, 'pbs_runner')
    helpers.parser_add_common_opt(parser, 'change_path')
    helpers.parser_add_common_opt(
        parser,
        'version',
        version='datalad %s\n\n%s' % (datalad.__version__, _license_info()))
    if __debug__:
        parser.add_argument(
            '--dbg', action='store_true', dest='common_debug',
            help="enter Python debugger when uncaught exception happens")
        parser.add_argument(
            '--idbg', action='store_true', dest='common_idebug',
            help="enter IPython debugger when uncaught exception happens")
    parser.add_argument(
        '-c', action='append', dest='cfg_overrides', metavar='KEY=VALUE',
        help="""configuration variable setting. Overrides any configuration
        read from a file, but is potentially overridden itself by configuration
        variables in the process environment.""")
    parser.add_argument(
        '-f', '--output-format', dest='common_output_format',
        default='default',
        type=assure_unicode,
        metavar="{default,json,json_pp,tailored,'<template>'}",
        help="""select format for returned command results. 'default' give one line
        per result reporting action, status, path and an optional message;
        'json' renders a JSON object with all properties for each result (one per
        line); 'json_pp' pretty-prints JSON spanning multiple lines; 'tailored'
        enables a command-specific rendering style that is typically
        tailored to human consumption (no result output otherwise),
        '<template>' reports any value(s) of any result properties in any format
        indicated by the template (e.g. '{path}'; compare with JSON
        output for all key-value choices). The template syntax follows the Python
        "format() language". It is possible to report individual
        dictionary values, e.g. '{metadata[name]}'. If a 2nd-level key contains
        a colon, e.g. 'music:Genre', ':' must be substituted by '#' in the template,
        like so: '{metadata[music#Genre]}'.""")
    parser.add_argument(
        '--report-status', dest='common_report_status',
        choices=['success', 'failure', 'ok', 'notneeded', 'impossible', 'error'],
        help="""constrain command result report to records matching the given
        status. 'success' is a synonym for 'ok' OR 'notneeded', 'failure' stands
        for 'impossible' OR 'error'.""")
    parser.add_argument(
        '--report-type', dest='common_report_type',
        choices=['dataset', 'file'],
        action='append',
        help="""constrain command result report to records matching the given
        type. Can be given more than once to match multiple types.""")
    parser.add_argument(
        '--on-failure', dest='common_on_failure',
        choices=['ignore', 'continue', 'stop'],
        # no default: better be configure per-command
        help="""when an operation fails: 'ignore' and continue with remaining
        operations, the error is logged but does not lead to a non-zero exit code
        of the command; 'continue' works like 'ignore', but an error causes a
        non-zero exit code; 'stop' halts on first failure and yields non-zero exit
        code. A failure is any result with status 'impossible' or 'error'.""")
    parser.add_argument(
        '--proc-pre', dest='common_proc_pre',
        nargs='+',
        action='append',
        metavar=('<PROCEDURE NAME>', 'ARGS'),
        help="""Dataset procedure to run before the main command (see run-procedure
        command for details). This option can be given more than once to run
        multiple procedures in the order in which they were given.
        It is important to specify the target dataset via the --dataset argument
        of the main command."""),
    parser.add_argument(
        '--proc-post', dest='common_proc_post',
        nargs='+',
        action='append',
        metavar=('<PROCEDURE NAME>', 'ARGS'),
        help="""Like --proc-pre, but procedures are executed after the main command
        has finished."""),
    parser.add_argument(
        '--cmd', dest='_', action='store_true',
        help="""syntactical helper that can be used to end the list of global
        command line options before the subcommand label. Options like
        --proc-pre can take an arbitrary number of arguments and may require
        to be followed by a single --cmd in order to enable identification
        of the subcommand.""")

    # yoh: atm we only dump to console.  Might adopt the same separation later on
    #      and for consistency will call it --verbose-level as well for now
    # log-level is set via common_opts ATM
    # parser.add_argument('--log-level',
    #                     choices=('critical', 'error', 'warning', 'info', 'debug'),
    #                     dest='common_log_level',
    #                     help="""level of verbosity in log files. By default
    #                          everything, including debug messages is logged.""")
    #parser.add_argument('-l', '--verbose-level',
    #                    choices=('critical', 'error', 'warning', 'info', 'debug'),
    #                    dest='common_verbose_level',
    #                    help="""level of verbosity of console output. By default
    #                         only warnings and errors are printed.""")

    # Before doing anything additional and possibly expensive see may be that
    # we have got the command already
    need_single_subparser = False if return_subparsers else None
    fail_handler = (lambda *a, **kw: True) \
        if return_subparsers else fail_with_short_help
    try:
        parsed_args, unparsed_args = parser._parse_known_args(
            cmdlineargs[1:], argparse.Namespace())
        if not unparsed_args:
            fail_handler(parser, msg="too few arguments", exit_code=2)
        lgr.debug("Command line args 1st pass. Parsed: %s Unparsed: %s",
                  parsed_args, unparsed_args)
    except Exception as exc:
        lgr.debug("Early parsing failed with %s", exc_str(exc))
        need_single_subparser = False
        unparsed_args = cmdlineargs[1:]  # referenced before assignment otherwise

    interface_groups = get_interface_groups()
    # TODO: see if we could retain "generator" for plugins
    # ATM we need to make it explicit so we could check the command(s) below
    # It could at least follow the same destiny as extensions so we would
    # just do more iterative "load ups"
    interface_groups.append(('plugins', 'Plugins', list(_get_plugins())))

    # First unparsed could be either unknown option to top level "datalad"
    # or a command. Among unknown could be --help/--help-np which would
    # need to be dealt with
    unparsed_arg = unparsed_args[0] if unparsed_args else None
    if need_single_subparser is not None \
            or unparsed_arg in ('--help', '--help-np', '-h'):
        need_single_subparser = False
        add_entrypoints_to_interface_groups(interface_groups)
    elif unparsed_arg.startswith('-'):  # unknown option
        fail_with_short_help(parser,
                             msg="unrecognized argument %s" % unparsed_arg,
                             exit_code=2)
        # if we could get a list of options known to parser,
        # we could suggest them
        # known=get_all_options(parser), provided=unparsed_arg)
    else:  # the command to handle
        known_commands = get_commands_from_groups(interface_groups)
        if unparsed_arg not in known_commands:
            # need to load all the extensions and try again
            add_entrypoints_to_interface_groups(interface_groups)
            known_commands = get_commands_from_groups(interface_groups)

        if unparsed_arg not in known_commands:
            # check if might be coming from known extensions
            from ..interface import _known_extension_commands
            extension_commands = {
                c: e
                for e, commands in _known_extension_commands.items()
                for c in commands
            }
            hint = None
            if unparsed_arg in extension_commands:
                hint = "Command %s is provided by (not installed) extension %s." \
                      % (unparsed_arg, extension_commands[unparsed_arg])
            fail_with_short_help(
                parser,
                hint=hint,
                provided=unparsed_arg,
                known=list(known_commands.keys()) + list(extension_commands.keys())
            )
        if need_single_subparser is None:
            need_single_subparser = unparsed_arg

    # --help specification was delayed since it causes immediate printout of
    # --help output before we setup --help for each command
    helpers.parser_add_common_opt(parser, 'help')

    grp_short_descriptions = []
    # create subparser, use module suffix as cmd name
    subparsers = parser.add_subparsers()
    for _, _, _interfaces \
            in sorted(interface_groups, key=lambda x: x[1]):
        # for all subcommand modules it can find
        cmd_short_descriptions = []

        for _intfspec in _interfaces:
            cmd_name = get_cmdline_command_name(_intfspec)
            if need_single_subparser and cmd_name != need_single_subparser:
                continue
            if isinstance(_intfspec[1], dict):
                # plugin
                _intf = _load_plugin(_intfspec[1]['file'], fail=False)
                if _intf is None:
                    # TODO:  add doc why we could skip this one... makes this
                    # loop harder to extract into a dedicated function
                    continue
            else:
                # turn the interface spec into an instance
                lgr.log(5, "Importing module %s " % _intfspec[0])
                try:
                    _mod = import_module(_intfspec[0], package='datalad')
                except Exception as e:
                    lgr.error("Internal error, cannot import interface '%s': %s",
                              _intfspec[0], exc_str(e))
                    continue
                _intf = getattr(_mod, _intfspec[1])
            # deal with optional parser args
            if hasattr(_intf, 'parser_args'):
                parser_args = _intf.parser_args
            else:
                parser_args = dict(formatter_class=formatter_class)
                # use class description, if no explicit description is available
                intf_doc = '' if _intf.__doc__ is None else _intf.__doc__.strip()
                if hasattr(_intf, '_docs_'):
                    # expand docs
                    intf_doc = intf_doc.format(**_intf._docs_)
                parser_args['description'] = alter_interface_docs_for_cmdline(
                    intf_doc)
            subparser = subparsers.add_parser(cmd_name, add_help=False, **parser_args)
            # our own custom help for all commands
            helpers.parser_add_common_opt(subparser, 'help')
            # let module configure the parser
            _intf.setup_parser(subparser)
            # logger for command

            # configure 'run' function for this command
            plumbing_args = dict(
                func=_intf.call_from_parser,
                logger=logging.getLogger(_intf.__module__),
                subparser=subparser)
            if hasattr(_intf, 'result_renderer_cmdline'):
                plumbing_args['result_renderer'] = _intf.result_renderer_cmdline
            subparser.set_defaults(**plumbing_args)
            parts[cmd_name] = subparser
            # store short description for later
            sdescr = getattr(_intf, 'short_description',
                             parser_args['description'].split('\n')[0])
            cmd_short_descriptions.append((cmd_name, sdescr))
        grp_short_descriptions.append(cmd_short_descriptions)

    # create command summary
    if '--help' in cmdlineargs or '--help-np' in cmdlineargs:
        parser.description = get_description_with_cmd_summary(
            grp_short_descriptions,
            interface_groups,
            parser.description)

    parts['datalad'] = parser
    lgr.log(5, "Finished setup_parser")
    if return_subparsers:
        return parts
    else:
        return parser


def fail_with_short_help(parser=None,
                         msg=None,
                         known=None, provided=None,
                         hint=None,
                         exit_code=1,
                         what="command",
                         out=None):
    """Generic helper to fail
    with short help possibly hinting on what was intended if `known`
    were provided
    """
    out = out or sys.stderr
    if msg:
        out.write("error: %s\n" % msg)
    if not known:
        if parser:
            # just to appear in print_usage also consistent with --help output
            parser.add_argument("command [command-opts]")
            parser.print_usage(file=out)
    else:
        out.write(
            "datalad: Unknown %s %r.  See 'datalad --help'.\n\n"
            % (what, provided,))
        if provided not in known:
            import difflib
            suggestions = difflib.get_close_matches(provided, known)
            if suggestions:
                out.write(
                    "Did you mean one of these?\n        %s\n"
                    % "\n        ".join(suggestions))
        # Too noisy
        # sys.stderr.write(" All known %ss: %s\n"
        #                   % (what, ", ".join(sorted(known))))
    if hint:
        out.write("Hint: %s\n" % hint)
    raise SystemExit(exit_code)


def get_description_with_cmd_summary(grp_short_descriptions, interface_groups,
                                     parser_description):
    from ..interface.base import dedent_docstring
    lgr.debug("Generating detailed description for the parser")
    cmd_summary = []
    console_width = get_console_width()
    for i, grp in enumerate(
            sorted(interface_groups, key=lambda x: x[1])):
        grp_descr = grp[1]
        grp_cmds = grp_short_descriptions[i]

        cmd_summary.append('\n*%s*\n' % (grp_descr,))
        for cd in grp_cmds:
            cmd_summary.append('  %s\n%s'
                               % ((cd[0],
                                   textwrap.fill(
                                       cd[1].rstrip(' .'),
                                       console_width - 5,
                                       initial_indent=' ' * 6,
                                       subsequent_indent=' ' * 6))))
    # we need one last formal section to not have the trailed be
    # confused with the last command group
    cmd_summary.append('\n*General information*\n')
    detailed_description = '%s\n%s\n\n%s' \
                           % (parser_description,
                              '\n'.join(cmd_summary),
                              textwrap.fill(dedent_docstring("""\
    Detailed usage information for individual commands is
    available via command-specific --help, i.e.:
    datalad <command> --help"""),
                                            console_width - 5,
                                            initial_indent='',
                                            subsequent_indent=''))
    return detailed_description


def get_commands_from_groups(groups):
    """Get a dictionary of command: interface_spec"""
    from ..interface.base import get_cmdline_command_name
    return {
        get_cmdline_command_name(_intfspec): _intfspec
        for _, _, _interfaces in groups
        for _intfspec in _interfaces
    }


def add_entrypoints_to_interface_groups(interface_groups):
    lgr.debug("Loading entrypoints")
    from pkg_resources import iter_entry_points  # delay expensive import
    for ep in iter_entry_points('datalad.extensions'):
        lgr.debug(
            'Loading entrypoint %s from datalad.extensions for docs building',
            ep.name)
        try:
            spec = ep.load()
            interface_groups.append((ep.name, spec[0], spec[1]))
            lgr.debug('Loaded entrypoint %s', ep.name)
        except Exception as e:
            lgr.warning('Failed to load entrypoint %s: %s', ep.name, exc_str(e))
            continue

def _fix_datalad_ri(s):
    """Fixup argument if it was a DataLadRI and had leading / removed

    See gh-2643
    """
    if s.startswith('//') and (len(s) == 2 or (len(s) > 2 and s[2] != '/')):
        lgr.info(
            "Changing %s back to /%s as it was probably changed by MINGW/MSYS, "
            "see http://www.mingw.org/wiki/Posix_path_conversion", s, s)
        return "/" + s
    return s


def main(args=None):
    lgr.log(5, "Starting main(%r)", args)
    args = args or sys.argv
    if on_msys_tainted_paths:
        # Possibly present DataLadRIs were stripped of a leading /
        args = [_fix_datalad_ri(s) for s in args]
    # PYTHON_ARGCOMPLETE_OK
    parser = setup_parser(args)
    try:
        import argcomplete
        argcomplete.autocomplete(parser)
    except ImportError:
        pass

    # parse cmd args
    lgr.debug("Parsing known args among %s", repr(args))
    cmdlineargs, unparsed_args = parser.parse_known_args(args[1:])
    has_func = hasattr(cmdlineargs, 'func') and cmdlineargs.func is not None
    if unparsed_args:
        if has_func and cmdlineargs.func.__self__.__name__ != 'Export':
            lgr.error('unknown argument{}: {}'.format(
                's' if len(unparsed_args) > 1 else '',
                unparsed_args if len(unparsed_args) > 1 else unparsed_args[0]))
            cmdlineargs.subparser.print_usage()
            sys.exit(1)
        else:
            # store all unparsed arguments
            cmdlineargs.datalad_unparsed_args = unparsed_args

    # to possibly be passed into PBS scheduled call
    args_ = args or sys.argv

    if cmdlineargs.cfg_overrides is not None:
        overrides = dict([
            (o.split('=')[0], '='.join(o.split('=')[1:]))
            for o in cmdlineargs.cfg_overrides])
        datalad.cfg.overrides.update(overrides)

    # enable overrides
    datalad.cfg.reload(force=True)

    if cmdlineargs.change_path is not None:
        from .common_args import change_path as change_path_opt
        for path in cmdlineargs.change_path:
            chpwd(path)
            args_ = strip_arg_from_argv(args_, path, change_path_opt[1])

    ret = None
    if cmdlineargs.pbs_runner:
        from .helpers import run_via_pbs
        from .common_args import pbs_runner as pbs_runner_opt
        args_ = strip_arg_from_argv(args_, cmdlineargs.pbs_runner, pbs_runner_opt[1])
        # run the function associated with the selected command
        run_via_pbs(args_, cmdlineargs.pbs_runner)
    elif has_func:
        if cmdlineargs.common_debug or cmdlineargs.common_idebug:
            # so we could see/stop clearly at the point of failure
            setup_exceptionhook(ipython=cmdlineargs.common_idebug)
            from datalad.interface.base import Interface
            Interface._interrupted_exit_code = None
            ret = cmdlineargs.func(cmdlineargs)
        else:
            # otherwise - guard and only log the summary. Postmortem is not
            # as convenient if being caught in this ultimate except
            try:
                ret = cmdlineargs.func(cmdlineargs)
            except InsufficientArgumentsError as exc:
                # if the func reports inappropriate usage, give help output
                lgr.error('%s (%s)' % (exc_str(exc), exc.__class__.__name__))
                cmdlineargs.subparser.print_usage(sys.stderr)
                sys.exit(2)
            except IncompleteResultsError as exc:
                # rendering for almost all commands now happens 'online'
                # hence we are no longer attempting to render the actual
                # results in an IncompleteResultsError, ubt rather trust that
                # this happened before

                # in general we do not want to see the error again, but
                # present in debug output
                lgr.debug('could not perform all requested actions: %s',
                          exc_str(exc))
                sys.exit(1)
            except CommandError as exc:
                # behave as if the command ran directly, importantly pass
                # exit code as is
                if exc.msg:
                    msg = exc.msg.encode() if isinstance(exc.msg, text_type) else exc.msg
                    os.write(2, msg + b"\n")
                if exc.stdout:
                    os.write(1, exc.stdout.encode() if isinstance(exc.stdout, text_type) else exc.stdout)
                if exc.stderr:
                    os.write(2, exc.stderr.encode() if isinstance(exc.stderr, text_type) else exc.stderr)
                # We must not exit with 0 code if any exception got here but
                # had no code defined
                sys.exit(exc.code if exc.code is not None else 1)
            except Exception as exc:
                lgr.error('%s (%s)' % (exc_str(exc), exc.__class__.__name__))
                sys.exit(1)
    else:
        # just let argparser spit out its error, since there is smth wrong
        parser.parse_args(args)
        # if that one didn't puke -- we should
        parser.print_usage()
        lgr.error("Please specify the command")
        sys.exit(2)

    try:
        if hasattr(cmdlineargs, 'result_renderer'):
            cmdlineargs.result_renderer(ret, cmdlineargs)
    except Exception as exc:
        lgr.error("Failed to render results due to %s", exc_str(exc))
        sys.exit(1)

lgr.log(5, "Done importing cmdline.main")

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
user