{
    "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\"\"\"Interface result handling functions\n\n\"\"\"\n\n__docformat__ = 'restructuredtext'\n\nimport logging\n\nfrom os.path import (\n    isabs,\n    isdir,\n    join as opj,\n    normpath,\n    relpath,\n)\n\nfrom datalad.utils import (\n    ensure_list,\n    path_is_subpath,\n    PurePosixPath,\n)\nfrom datalad.support.path import robust_abspath\n\nfrom datalad.distribution.dataset import Dataset\n\n\nlgr = logging.getLogger('datalad.interface.results')\nlgr.log(5, \"Importing datalad.interface.results\")\n\n# which status is a success , which is failure\nsuccess_status_map = {\n    'ok': 'success',\n    'notneeded': 'success',\n    'impossible': 'failure',\n    'error': 'failure',\n}\n\n\ndef get_status_dict(action=None, ds=None, path=None, type=None, logger=None,\n                    refds=None, status=None, message=None, exception=None,\n                    error_message=None, **kwargs):\n    # `type` is intentionally not `type_` or something else, as a mismatch\n    # with the dict key 'type' causes too much pain all over the place\n    # just for not shadowing the builtin `type` in this function\n    \"\"\"Helper to create a result dictionary.\n\n    Most arguments match their key in the resulting dict, and their given\n    values are simply assigned to the result record under these keys.  Only\n    exceptions are listed here.\n\n    Parameters\n    ----------\n    ds : Dataset instance\n      If given, the `path` and `type` values are populated with the path of the\n      datasets and 'dataset' as the type. Giving additional values for both\n      keys will overwrite these pre-populated values.\n    exception : CapturedException\n      Exceptions that occurred while generating a result should be captured\n      by immediately instantiating a CapturedException. This instance can\n      be passed here to yield more comprehensive error reporting, including\n      an auto-generated traceback (added to the result record under an\n      'exception_traceback' key).\n\n    Returns\n    -------\n    dict\n    \"\"\"\n\n    d = {}\n    if action is not None:\n        d['action'] = action\n    if ds:\n        d['path'] = ds.path\n        d['type'] = 'dataset'\n    # now overwrite automatic\n    if path is not None:\n        d['path'] = path\n    if type:\n        d['type'] = type\n    if logger:\n        d['logger'] = logger\n    if refds:\n        d['refds'] = refds\n    if status is not None:\n        # TODO check for known status label\n        d['status'] = status\n    if message is not None:\n        d['message'] = message\n    if error_message is not None:\n        d['error_message'] = error_message\n    if exception is not None:\n        d['exception'] = exception\n        d['exception_traceback'] = \\\n            exception.format_oneline_tb(limit=1000, include_str=False)\n    if kwargs:\n        d.update(kwargs)\n    return d\n\n\ndef results_from_paths(paths, action=None, type=None, logger=None, refds=None,\n                       status=None, message=None, exception=None):\n    \"\"\"\n    Helper to yield analog result dicts for each path in a sequence.\n\n    Parameters\n    ----------\n    message: str\n      A result message. May contain `%s` which will be replaced by the\n      respective `path`.\n\n    Returns\n    -------\n    generator\n\n    \"\"\"\n    for p in ensure_list(paths):\n        yield get_status_dict(\n            action, path=p, type=type, logger=logger, refds=refds,\n            status=status, message=(message, p) if '%s' in message else message,\n            exception=exception\n        )\n\n\ndef is_ok_dataset(r):\n    \"\"\"Convenience test for a non-failure dataset-related result dict\"\"\"\n    return r.get('status', None) == 'ok' and r.get('type', None) == 'dataset'\n\n\nclass ResultXFM(object):\n    \"\"\"Abstract definition of the result transformer API\"\"\"\n    def __call__(self, res):\n        \"\"\"This is called with one result dict at a time\"\"\"\n        raise NotImplementedError\n\n\nclass YieldDatasets(ResultXFM):\n    \"\"\"Result transformer to return a Dataset instance from matching result.\n\n    If the `success_only` flag is given only dataset with 'ok' or 'notneeded'\n    status are returned'.\n\n    `None` is returned for any other result.\n    \"\"\"\n    def __init__(self, success_only=False):\n        self.success_only = success_only\n\n    def __call__(self, res):\n        if res.get('type', None) == 'dataset':\n            if not self.success_only or \\\n                    res.get('status', None) in ('ok', 'notneeded'):\n                return Dataset(res['path'])\n        else:\n            lgr.debug('rejected by return value configuration: %s', res)\n\n\nclass YieldRelativePaths(ResultXFM):\n    \"\"\"Result transformer to return relative paths for a result\n\n    Relative paths are determined from the 'refds' value in the result. If\n    no such value is found, `None` is returned.\n    \"\"\"\n    def __call__(self, res):\n        refpath = res.get('refds', None)\n        if refpath:\n            return relpath(res['path'], start=refpath)\n\n\nclass YieldField(ResultXFM):\n    \"\"\"Result transformer to return an arbitrary value from a result dict\"\"\"\n    def __init__(self, field):\n        \"\"\"\n        Parameters\n        ----------\n        field : str\n          Key of the field to return.\n        \"\"\"\n        self.field = field\n\n    def __call__(self, res):\n        if self.field in res:\n            return res[self.field]\n        else:\n            lgr.debug('rejected by return value configuration: %s', res)\n\n\n# a bunch of convenience labels for common result transformers\n# the API `result_xfm` argument understand any of these labels and\n# applied the corresponding callable\nknown_result_xfms = {\n    'datasets': YieldDatasets(),\n    'successdatasets-or-none': YieldDatasets(success_only=True),\n    'paths': YieldField('path'),\n    'relpaths': YieldRelativePaths(),\n    'metadata': YieldField('metadata'),\n}\n\ntranslate_annex_notes = {\n    '(Use --force to override this check, or adjust numcopies.)':\n        'configured minimum number of copies not found',\n}\n\n\ndef annexjson2result(d, ds, **kwargs):\n    \"\"\"Helper to convert an annex JSON result to a datalad result dict\n\n    Info from annex is rather heterogeneous, partly because some of it\n    our support functions are faking.\n\n    This helper should be extended with all needed special cases to\n    homogenize the information.\n\n    Parameters\n    ----------\n    d : dict\n      Annex info dict.\n    ds : Dataset instance\n      Used to determine absolute paths for `file` results. This dataset\n      is not used to set `refds` in the result, pass this as a separate\n      kwarg if needed.\n    **kwargs\n      Passes as-is to `get_status_dict`. Must not contain `refds`.\n    \"\"\"\n    lgr.debug('received JSON result from annex: %s', d)\n    messages = []\n    res = get_status_dict(**kwargs)\n    res['status'] = 'ok' if d.get('success', False) is True else 'error'\n    # we cannot rely on any of these to be available as the feed from\n    # git annex (or its wrapper) is not always homogeneous\n    if d.get('file'):\n        res['path'] = str(ds.pathobj / PurePosixPath(d['file']))\n    if 'command' in d:\n        res['action'] = d['command']\n    if 'key' in d:\n        res['annexkey'] = d['key']\n    if 'fields' in d:\n        # this is annex metadata, filter out timestamps\n        res['metadata'] = {k: v[0] if isinstance(v, list) and len(v) == 1 else v\n                           for k, v in d['fields'].items()\n                           if not k.endswith('lastchanged')}\n    if d.get('error-messages', None):\n        res['error_message'] = '\\n'.join(m.strip() for m in d['error-messages'])\n    # avoid meaningless standard messages, and collision with actual error\n    # messages\n    elif 'note' in d:\n        note = \"; \".join(ln for ln in d['note'].splitlines()\n                         if ln != 'checksum...'\n                         and not ln.startswith('checking file'))\n        if note:\n            messages.append(translate_annex_notes.get(note, note))\n    if messages:\n        res['message'] = '\\n'.join(m.strip() for m in messages)\n    return res\n\n\ndef count_results(res, **kwargs):\n    \"\"\"Return number if results that match all property values in kwargs\"\"\"\n    return sum(\n        all(k in r and r[k] == v for k, v in kwargs.items()) for r in res)\n\n\ndef only_matching_paths(res, **kwargs):\n    # TODO handle relative paths by using a contained 'refds' value\n    paths = ensure_list(kwargs.get('path', []))\n    respath = res.get('path', None)\n    return respath in paths\n\n\n# needs decorator, as it will otherwise bind to the command classes that use it\n@staticmethod\ndef is_result_matching_pathsource_argument(res, **kwargs):\n    # we either have any non-zero number of \"paths\" (that could be anything), or\n    # we have one path and one source\n    # we don't do any error checking here, done by the command itself\n    if res.get('action', None) not in ('install', 'get'):\n        # this filter is only used in install, reject anything that comes\n        # in that could not possibly be a 'install'-like result\n        # e.g. a sibling being added in the process\n        return False\n    source = kwargs.get('source', None)\n    if source is not None:\n        # we want to be able to deal with Dataset instances given as 'source':\n        if isinstance(source, Dataset):\n            source = source.path\n        # if there was a source, it needs to be recorded in the result\n        # otherwise this is not what we are looking for\n        return source == res.get('source_url', None)\n    # the only thing left is a potentially heterogeneous list of paths/URLs\n    paths = ensure_list(kwargs.get('path', []))\n    # three cases left:\n    # 1. input arg was an absolute path -> must match 'path' property\n    # 2. input arg was relative to a dataset -> must match refds/relpath\n    # 3. something nifti with a relative input path that uses PWD as the\n    #    reference\n    respath = res.get('path', None)\n    if respath in paths:\n        # absolute match, pretty sure we want this\n        return True\n    elif isinstance(kwargs.get('dataset', None), Dataset) and \\\n            YieldRelativePaths()(res) in paths:\n        # command was called with a reference dataset, and a relative\n        # path of a result matches in input argument -- not 100% exhaustive\n        # test, but could be good enough\n        return True\n    elif any(robust_abspath(p) == respath for p in paths):\n        # one absolutified input path matches the result path\n        # I'd say: got for it!\n        return True\n    elif any(p == res.get('source_url', None) for p in paths):\n        # this was installed from a URL that was given, we'll take that too\n        return True\n    else:\n        return False\n\n\ndef results_from_annex_noinfo(ds, requested_paths, respath_by_status, dir_fail_msg,\n                              noinfo_dir_msg, noinfo_file_msg, noinfo_status='notneeded',\n                              **kwargs):\n    \"\"\"Helper to yield results based on what information git annex did no give us.\n\n    The helper assumes that the annex command returned without an error code,\n    and interprets which of the requested paths we have heard nothing about,\n    and assumes that git annex was happy with their current state.\n\n    Parameters\n    ==========\n    ds : Dataset\n      All results have to be concerning this single dataset (used to resolve\n      relpaths).\n    requested_paths : list\n      List of path arguments sent to `git annex`\n    respath_by_status : dict\n      Mapping of 'success' or 'failure' labels to lists of result paths\n      reported by `git annex`. Everything that is not in here, we assume\n      that `git annex` was happy about.\n    dir_fail_msg : str\n      Message template to inject into the result for a requested directory where\n      a failure was reported for some of its content. The template contains two\n      string placeholders that will be expanded with 1) the path of the\n      directory, and 2) the content failure paths for that directory\n    noinfo_dir_msg : str\n      Message template to inject into the result for a requested directory that\n      `git annex` was silent about (incl. any content). There must be one string\n      placeholder that is expanded with the path of that directory.\n    noinfo_file_msg : str\n      Message to inject into the result for a requested file that `git\n      annex` was silent about.\n    noinfo_status : str\n      Status to report when annex provides no information\n    **kwargs\n      Any further kwargs are included in the yielded result dictionary.\n    \"\"\"\n    for p in requested_paths:\n        # any relpath is relative to the currently processed dataset\n        # not the global reference dataset\n        p = p if isabs(p) else normpath(opj(ds.path, p))\n        if any(p in ps for ps in respath_by_status.values()):\n            # we have a report for this path already\n            continue\n        common_report = dict(path=p, **kwargs)\n        if isdir(p):\n            # `annex` itself will not report on directories, but if a\n            # directory was requested, we want to say something about\n            # it in the results.  we are inside a single, existing\n            # repo, hence all directories are already present, if not\n            # we had an error\n            # do we have any failures in a subdir of the requested dir?\n            failure_results = [\n                fp for fp in respath_by_status.get('failure', [])\n                if path_is_subpath(fp, p)]\n            if failure_results:\n                # we were not able to process all requested_paths, let's label\n                # this 'impossible' to get a warning-type report\n                # after all we have the directory itself, but not\n                # (some) of its requested_paths\n                yield get_status_dict(\n                    status='impossible', type='directory',\n                    message=(dir_fail_msg, p, failure_results),\n                    **common_report)\n            else:\n                # otherwise cool, but how cool?\n                success_results = [\n                    fp for fp in respath_by_status.get('success', [])\n                    if path_is_subpath(fp, p)]\n                yield get_status_dict(\n                    status='ok' if success_results else noinfo_status,\n                    message=None if success_results else (noinfo_dir_msg, p),\n                    type='directory', **common_report)\n            continue\n        else:\n            # not a directory, and we have had no word from `git annex`,\n            # yet no exception, hence the file was most probably\n            # already in the desired state\n            yield get_status_dict(\n                status=noinfo_status, type='file',\n                message=noinfo_file_msg,\n                **common_report)\n\n\nlgr.log(5, \"Done importing datalad.interface.results\")\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\"\"\"Interface result handling functions\n\n\"\"\"\n\n__docformat__ = 'restructuredtext'\n\nimport logging\n\nfrom os.path import (\n    isabs,\n    isdir,\n    join as opj,\n    normpath,\n    relpath,\n)\n\nfrom datalad.utils import (\n    ensure_list,\n    path_is_subpath,\n    PurePosixPath,\n)\nfrom datalad.support.exceptions import CommandError\nfrom datalad.support.path import robust_abspath\nfrom datalad.support.exceptions import (\n    format_oneline_tb,\n    CapturedException,\n)\nfrom datalad.distribution.dataset import Dataset\n\n\nlgr = logging.getLogger('datalad.interface.results')\nlgr.log(5, \"Importing datalad.interface.results\")\n\n# which status is a success , which is failure\nsuccess_status_map = {\n    'ok': 'success',\n    'notneeded': 'success',\n    'impossible': 'failure',\n    'error': 'failure',\n}\n\n\ndef get_status_dict(action=None, ds=None, path=None, type=None, logger=None,\n                    refds=None, status=None, message=None, exception=None,\n                    error_message=None, **kwargs):\n    # `type` is intentionally not `type_` or something else, as a mismatch\n    # with the dict key 'type' causes too much pain all over the place\n    # just for not shadowing the builtin `type` in this function\n    \"\"\"Helper to create a result dictionary.\n\n    Most arguments match their key in the resulting dict, and their given\n    values are simply assigned to the result record under these keys.  Only\n    exceptions are listed here.\n\n    Parameters\n    ----------\n    ds : Dataset instance\n      If given, the `path` and `type` values are populated with the path of the\n      datasets and 'dataset' as the type. Giving additional values for both\n      keys will overwrite these pre-populated values.\n    exception : Exception\n      Exceptions that occurred while generating a result should be captured\n      by immediately instantiating a CapturedException. This instance can\n      be passed here to yield more comprehensive error reporting, including\n      an auto-generated traceback (added to the result record under an\n      'exception_traceback' key). Exceptions of other types are also supported.\n\n    Returns\n    -------\n    dict\n    \"\"\"\n\n    d = {}\n    if action is not None:\n        d['action'] = action\n    if ds:\n        d['path'] = ds.path\n        d['type'] = 'dataset'\n    # now overwrite automatic\n    if path is not None:\n        d['path'] = path\n    if type:\n        d['type'] = type\n    if logger:\n        d['logger'] = logger\n    if refds:\n        d['refds'] = refds\n    if status is not None:\n        # TODO check for known status label\n        d['status'] = status\n    if message is not None:\n        d['message'] = message\n    if error_message is not None:\n        d['error_message'] = error_message\n    if exception is not None:\n        d['exception'] = exception\n        d['exception_traceback'] = exception.format_oneline_tb(\n            include_str=False) \\\n            if isinstance(exception, CapturedException) \\\n            else format_oneline_tb(\n                exception, include_str=False)\n        if isinstance(exception, CommandError):\n            d['exit_code'] = exception.code\n    if kwargs:\n        d.update(kwargs)\n    return d\n\n\ndef results_from_paths(paths, action=None, type=None, logger=None, refds=None,\n                       status=None, message=None, exception=None):\n    \"\"\"\n    Helper to yield analog result dicts for each path in a sequence.\n\n    Parameters\n    ----------\n    message: str\n      A result message. May contain `%s` which will be replaced by the\n      respective `path`.\n\n    Returns\n    -------\n    generator\n\n    \"\"\"\n    for p in ensure_list(paths):\n        yield get_status_dict(\n            action, path=p, type=type, logger=logger, refds=refds,\n            status=status, message=(message, p) if '%s' in message else message,\n            exception=exception\n        )\n\n\ndef is_ok_dataset(r):\n    \"\"\"Convenience test for a non-failure dataset-related result dict\"\"\"\n    return r.get('status', None) == 'ok' and r.get('type', None) == 'dataset'\n\n\nclass ResultXFM(object):\n    \"\"\"Abstract definition of the result transformer API\"\"\"\n    def __call__(self, res):\n        \"\"\"This is called with one result dict at a time\"\"\"\n        raise NotImplementedError\n\n\nclass YieldDatasets(ResultXFM):\n    \"\"\"Result transformer to return a Dataset instance from matching result.\n\n    If the `success_only` flag is given only dataset with 'ok' or 'notneeded'\n    status are returned'.\n\n    `None` is returned for any other result.\n    \"\"\"\n    def __init__(self, success_only=False):\n        self.success_only = success_only\n\n    def __call__(self, res):\n        if res.get('type', None) == 'dataset':\n            if not self.success_only or \\\n                    res.get('status', None) in ('ok', 'notneeded'):\n                return Dataset(res['path'])\n        else:\n            lgr.debug('rejected by return value configuration: %s', res)\n\n\nclass YieldRelativePaths(ResultXFM):\n    \"\"\"Result transformer to return relative paths for a result\n\n    Relative paths are determined from the 'refds' value in the result. If\n    no such value is found, `None` is returned.\n    \"\"\"\n    def __call__(self, res):\n        refpath = res.get('refds', None)\n        if refpath:\n            return relpath(res['path'], start=refpath)\n\n\nclass YieldField(ResultXFM):\n    \"\"\"Result transformer to return an arbitrary value from a result dict\"\"\"\n    def __init__(self, field):\n        \"\"\"\n        Parameters\n        ----------\n        field : str\n          Key of the field to return.\n        \"\"\"\n        self.field = field\n\n    def __call__(self, res):\n        if self.field in res:\n            return res[self.field]\n        else:\n            lgr.debug('rejected by return value configuration: %s', res)\n\n\n# a bunch of convenience labels for common result transformers\n# the API `result_xfm` argument understand any of these labels and\n# applied the corresponding callable\nknown_result_xfms = {\n    'datasets': YieldDatasets(),\n    'successdatasets-or-none': YieldDatasets(success_only=True),\n    'paths': YieldField('path'),\n    'relpaths': YieldRelativePaths(),\n    'metadata': YieldField('metadata'),\n}\n\ntranslate_annex_notes = {\n    '(Use --force to override this check, or adjust numcopies.)':\n        'configured minimum number of copies not found',\n}\n\n\ndef annexjson2result(d, ds, **kwargs):\n    \"\"\"Helper to convert an annex JSON result to a datalad result dict\n\n    Info from annex is rather heterogeneous, partly because some of it\n    our support functions are faking.\n\n    This helper should be extended with all needed special cases to\n    homogenize the information.\n\n    Parameters\n    ----------\n    d : dict\n      Annex info dict.\n    ds : Dataset instance\n      Used to determine absolute paths for `file` results. This dataset\n      is not used to set `refds` in the result, pass this as a separate\n      kwarg if needed.\n    **kwargs\n      Passes as-is to `get_status_dict`. Must not contain `refds`.\n    \"\"\"\n    lgr.debug('received JSON result from annex: %s', d)\n    messages = []\n    res = get_status_dict(**kwargs)\n    res['status'] = 'ok' if d.get('success', False) is True else 'error'\n    # we cannot rely on any of these to be available as the feed from\n    # git annex (or its wrapper) is not always homogeneous\n    if d.get('file'):\n        res['path'] = str(ds.pathobj / PurePosixPath(d['file']))\n    if 'command' in d:\n        res['action'] = d['command']\n    if 'key' in d:\n        res['annexkey'] = d['key']\n    if 'fields' in d:\n        # this is annex metadata, filter out timestamps\n        res['metadata'] = {k: v[0] if isinstance(v, list) and len(v) == 1 else v\n                           for k, v in d['fields'].items()\n                           if not k.endswith('lastchanged')}\n    if d.get('error-messages', None):\n        res['error_message'] = '\\n'.join(m.strip() for m in d['error-messages'])\n    # avoid meaningless standard messages, and collision with actual error\n    # messages\n    elif 'note' in d:\n        note = \"; \".join(ln for ln in d['note'].splitlines()\n                         if ln != 'checksum...'\n                         and not ln.startswith('checking file'))\n        if note:\n            messages.append(translate_annex_notes.get(note, note))\n    if messages:\n        res['message'] = '\\n'.join(m.strip() for m in messages)\n    return res\n\n\ndef count_results(res, **kwargs):\n    \"\"\"Return number if results that match all property values in kwargs\"\"\"\n    return sum(\n        all(k in r and r[k] == v for k, v in kwargs.items()) for r in res)\n\n\ndef only_matching_paths(res, **kwargs):\n    # TODO handle relative paths by using a contained 'refds' value\n    paths = ensure_list(kwargs.get('path', []))\n    respath = res.get('path', None)\n    return respath in paths\n\n\n# needs decorator, as it will otherwise bind to the command classes that use it\n@staticmethod\ndef is_result_matching_pathsource_argument(res, **kwargs):\n    # we either have any non-zero number of \"paths\" (that could be anything), or\n    # we have one path and one source\n    # we don't do any error checking here, done by the command itself\n    if res.get('action', None) not in ('install', 'get'):\n        # this filter is only used in install, reject anything that comes\n        # in that could not possibly be a 'install'-like result\n        # e.g. a sibling being added in the process\n        return False\n    source = kwargs.get('source', None)\n    if source is not None:\n        # we want to be able to deal with Dataset instances given as 'source':\n        if isinstance(source, Dataset):\n            source = source.path\n        # if there was a source, it needs to be recorded in the result\n        # otherwise this is not what we are looking for\n        return source == res.get('source_url', None)\n    # the only thing left is a potentially heterogeneous list of paths/URLs\n    paths = ensure_list(kwargs.get('path', []))\n    # three cases left:\n    # 1. input arg was an absolute path -> must match 'path' property\n    # 2. input arg was relative to a dataset -> must match refds/relpath\n    # 3. something nifti with a relative input path that uses PWD as the\n    #    reference\n    respath = res.get('path', None)\n    if respath in paths:\n        # absolute match, pretty sure we want this\n        return True\n    elif isinstance(kwargs.get('dataset', None), Dataset) and \\\n            YieldRelativePaths()(res) in paths:\n        # command was called with a reference dataset, and a relative\n        # path of a result matches in input argument -- not 100% exhaustive\n        # test, but could be good enough\n        return True\n    elif any(robust_abspath(p) == respath for p in paths):\n        # one absolutified input path matches the result path\n        # I'd say: got for it!\n        return True\n    elif any(p == res.get('source_url', None) for p in paths):\n        # this was installed from a URL that was given, we'll take that too\n        return True\n    else:\n        return False\n\n\ndef results_from_annex_noinfo(ds, requested_paths, respath_by_status, dir_fail_msg,\n                              noinfo_dir_msg, noinfo_file_msg, noinfo_status='notneeded',\n                              **kwargs):\n    \"\"\"Helper to yield results based on what information git annex did no give us.\n\n    The helper assumes that the annex command returned without an error code,\n    and interprets which of the requested paths we have heard nothing about,\n    and assumes that git annex was happy with their current state.\n\n    Parameters\n    ==========\n    ds : Dataset\n      All results have to be concerning this single dataset (used to resolve\n      relpaths).\n    requested_paths : list\n      List of path arguments sent to `git annex`\n    respath_by_status : dict\n      Mapping of 'success' or 'failure' labels to lists of result paths\n      reported by `git annex`. Everything that is not in here, we assume\n      that `git annex` was happy about.\n    dir_fail_msg : str\n      Message template to inject into the result for a requested directory where\n      a failure was reported for some of its content. The template contains two\n      string placeholders that will be expanded with 1) the path of the\n      directory, and 2) the content failure paths for that directory\n    noinfo_dir_msg : str\n      Message template to inject into the result for a requested directory that\n      `git annex` was silent about (incl. any content). There must be one string\n      placeholder that is expanded with the path of that directory.\n    noinfo_file_msg : str\n      Message to inject into the result for a requested file that `git\n      annex` was silent about.\n    noinfo_status : str\n      Status to report when annex provides no information\n    **kwargs\n      Any further kwargs are included in the yielded result dictionary.\n    \"\"\"\n    for p in requested_paths:\n        # any relpath is relative to the currently processed dataset\n        # not the global reference dataset\n        p = p if isabs(p) else normpath(opj(ds.path, p))\n        if any(p in ps for ps in respath_by_status.values()):\n            # we have a report for this path already\n            continue\n        common_report = dict(path=p, **kwargs)\n        if isdir(p):\n            # `annex` itself will not report on directories, but if a\n            # directory was requested, we want to say something about\n            # it in the results.  we are inside a single, existing\n            # repo, hence all directories are already present, if not\n            # we had an error\n            # do we have any failures in a subdir of the requested dir?\n            failure_results = [\n                fp for fp in respath_by_status.get('failure', [])\n                if path_is_subpath(fp, p)]\n            if failure_results:\n                # we were not able to process all requested_paths, let's label\n                # this 'impossible' to get a warning-type report\n                # after all we have the directory itself, but not\n                # (some) of its requested_paths\n                yield get_status_dict(\n                    status='impossible', type='directory',\n                    message=(dir_fail_msg, p, failure_results),\n                    **common_report)\n            else:\n                # otherwise cool, but how cool?\n                success_results = [\n                    fp for fp in respath_by_status.get('success', [])\n                    if path_is_subpath(fp, p)]\n                yield get_status_dict(\n                    status='ok' if success_results else noinfo_status,\n                    message=None if success_results else (noinfo_dir_msg, p),\n                    type='directory', **common_report)\n            continue\n        else:\n            # not a directory, and we have had no word from `git annex`,\n            # yet no exception, hence the file was most probably\n            # already in the desired state\n            yield get_status_dict(\n                status=noinfo_status, type='file',\n                message=noinfo_file_msg,\n                **common_report)\n\n\nlgr.log(5, \"Done importing datalad.interface.results\")\n",
            "header": {
                "file": {
                    "path": "datalad/interface/results.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "259d80ab7",
                "new_commit_hash": "f4afe1c27",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/datalad/interface/results.py"
            },
            "plus_file": {
                "path": "b/datalad/interface/results.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 27,
                            "length": 8
                        },
                        "new_range": {
                            "start": 27,
                            "length": 12
                        },
                        "section": "from datalad.utils import ("
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    path_is_subpath,",
                                "type": "context"
                            },
                            {
                                "content": "    PurePosixPath,",
                                "type": "context"
                            },
                            {
                                "content": ")",
                                "type": "context"
                            },
                            {
                                "content": "from datalad.support.exceptions import CommandError",
                                "type": "added"
                            },
                            {
                                "content": "from datalad.support.path import robust_abspath",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "deleted"
                            },
                            {
                                "content": "from datalad.support.exceptions import (",
                                "type": "added"
                            },
                            {
                                "content": "    format_oneline_tb,",
                                "type": "added"
                            },
                            {
                                "content": "    CapturedException,",
                                "type": "added"
                            },
                            {
                                "content": ")",
                                "type": "added"
                            },
                            {
                                "content": "from datalad.distribution.dataset import Dataset",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "datalad/interface/results.py",
                            "type": "statement",
                            "name": "",
                            "content": "from datalad.support.exceptions import (\n    format_oneline_tb,\n    CapturedException,\n)",
                            "ast_type_str": "ImportFrom",
                            "start_lineno": 32,
                            "end_lineno": 35,
                            "parent": null
                        },
                        {
                            "file_name": "datalad/interface/results.py",
                            "type": "statement",
                            "name": "",
                            "content": "from datalad.support.exceptions import CommandError",
                            "ast_type_str": "ImportFrom",
                            "start_lineno": 30,
                            "end_lineno": 30,
                            "parent": null
                        }
                    ],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 62,
                            "length": 12
                        },
                        "new_range": {
                            "start": 66,
                            "length": 12
                        },
                        "section": "def get_status_dict(action=None, ds=None, path=None, type=None, logger=None,"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "      If given, the `path` and `type` values are populated with the path of the",
                                "type": "context"
                            },
                            {
                                "content": "      datasets and 'dataset' as the type. Giving additional values for both",
                                "type": "context"
                            },
                            {
                                "content": "      keys will overwrite these pre-populated values.",
                                "type": "context"
                            },
                            {
                                "content": "    exception : CapturedException",
                                "type": "deleted"
                            },
                            {
                                "content": "    exception : Exception",
                                "type": "added"
                            },
                            {
                                "content": "      Exceptions that occurred while generating a result should be captured",
                                "type": "context"
                            },
                            {
                                "content": "      by immediately instantiating a CapturedException. This instance can",
                                "type": "context"
                            },
                            {
                                "content": "      be passed here to yield more comprehensive error reporting, including",
                                "type": "context"
                            },
                            {
                                "content": "      an auto-generated traceback (added to the result record under an",
                                "type": "context"
                            },
                            {
                                "content": "      'exception_traceback' key).",
                                "type": "deleted"
                            },
                            {
                                "content": "      'exception_traceback' key). Exceptions of other types are also supported.",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    Returns",
                                "type": "context"
                            },
                            {
                                "content": "    -------",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/interface/results.py",
                            "type": "function",
                            "name": "get_status_dict",
                            "content": "def get_status_dict(action=None, ds=None, path=None, type=None, logger=None,\n                    refds=None, status=None, message=None, exception=None,\n                    error_message=None, **kwargs):\n    # `type` is intentionally not `type_` or something else, as a mismatch\n    # with the dict key 'type' causes too much pain all over the place\n    # just for not shadowing the builtin `type` in this function\n    \"\"\"Helper to create a result dictionary.\n\n    Most arguments match their key in the resulting dict, and their given\n    values are simply assigned to the result record under these keys.  Only\n    exceptions are listed here.\n\n    Parameters\n    ----------\n    ds : Dataset instance\n      If given, the `path` and `type` values are populated with the path of the\n      datasets and 'dataset' as the type. Giving additional values for both\n      keys will overwrite these pre-populated values.\n    exception : CapturedException\n      Exceptions that occurred while generating a result should be captured\n      by immediately instantiating a CapturedException. This instance can\n      be passed here to yield more comprehensive error reporting, including\n      an auto-generated traceback (added to the result record under an\n      'exception_traceback' key).\n\n    Returns\n    -------\n    dict\n    \"\"\"\n\n    d = {}\n    if action is not None:\n        d['action'] = action\n    if ds:\n        d['path'] = ds.path\n        d['type'] = 'dataset'\n    # now overwrite automatic\n    if path is not None:\n        d['path'] = path\n    if type:\n        d['type'] = type\n    if logger:\n        d['logger'] = logger\n    if refds:\n        d['refds'] = refds\n    if status is not None:\n        # TODO check for known status label\n        d['status'] = status\n    if message is not None:\n        d['message'] = message\n    if error_message is not None:\n        d['error_message'] = error_message\n    if exception is not None:\n        d['exception'] = exception\n        d['exception_traceback'] = \\\n            exception.format_oneline_tb(limit=1000, include_str=False)\n    if kwargs:\n        d.update(kwargs)\n    return d",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 47,
                            "end_lineno": 105,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 98,
                            "length": 8
                        },
                        "new_range": {
                            "start": 102,
                            "length": 13
                        },
                        "section": "def get_status_dict(action=None, ds=None, path=None, type=None, logger=None,"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        d['error_message'] = error_message",
                                "type": "context"
                            },
                            {
                                "content": "    if exception is not None:",
                                "type": "context"
                            },
                            {
                                "content": "        d['exception'] = exception",
                                "type": "context"
                            },
                            {
                                "content": "        d['exception_traceback'] = \\",
                                "type": "deleted"
                            },
                            {
                                "content": "            exception.format_oneline_tb(limit=1000, include_str=False)",
                                "type": "deleted"
                            },
                            {
                                "content": "        d['exception_traceback'] = exception.format_oneline_tb(",
                                "type": "added"
                            },
                            {
                                "content": "            include_str=False) \\",
                                "type": "added"
                            },
                            {
                                "content": "            if isinstance(exception, CapturedException) \\",
                                "type": "added"
                            },
                            {
                                "content": "            else format_oneline_tb(",
                                "type": "added"
                            },
                            {
                                "content": "                exception, include_str=False)",
                                "type": "added"
                            },
                            {
                                "content": "        if isinstance(exception, CommandError):",
                                "type": "added"
                            },
                            {
                                "content": "            d['exit_code'] = exception.code",
                                "type": "added"
                            },
                            {
                                "content": "    if kwargs:",
                                "type": "context"
                            },
                            {
                                "content": "        d.update(kwargs)",
                                "type": "context"
                            },
                            {
                                "content": "    return d",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/interface/results.py",
                            "type": "function",
                            "name": "get_status_dict",
                            "content": "def get_status_dict(action=None, ds=None, path=None, type=None, logger=None,\n                    refds=None, status=None, message=None, exception=None,\n                    error_message=None, **kwargs):\n    # `type` is intentionally not `type_` or something else, as a mismatch\n    # with the dict key 'type' causes too much pain all over the place\n    # just for not shadowing the builtin `type` in this function\n    \"\"\"Helper to create a result dictionary.\n\n    Most arguments match their key in the resulting dict, and their given\n    values are simply assigned to the result record under these keys.  Only\n    exceptions are listed here.\n\n    Parameters\n    ----------\n    ds : Dataset instance\n      If given, the `path` and `type` values are populated with the path of the\n      datasets and 'dataset' as the type. Giving additional values for both\n      keys will overwrite these pre-populated values.\n    exception : CapturedException\n      Exceptions that occurred while generating a result should be captured\n      by immediately instantiating a CapturedException. This instance can\n      be passed here to yield more comprehensive error reporting, including\n      an auto-generated traceback (added to the result record under an\n      'exception_traceback' key).\n\n    Returns\n    -------\n    dict\n    \"\"\"\n\n    d = {}\n    if action is not None:\n        d['action'] = action\n    if ds:\n        d['path'] = ds.path\n        d['type'] = 'dataset'\n    # now overwrite automatic\n    if path is not None:\n        d['path'] = path\n    if type:\n        d['type'] = type\n    if logger:\n        d['logger'] = logger\n    if refds:\n        d['refds'] = refds\n    if status is not None:\n        # TODO check for known status label\n        d['status'] = status\n    if message is not None:\n        d['message'] = message\n    if error_message is not None:\n        d['error_message'] = error_message\n    if exception is not None:\n        d['exception'] = exception\n        d['exception_traceback'] = \\\n            exception.format_oneline_tb(limit=1000, include_str=False)\n    if kwargs:\n        d.update(kwargs)\n    return d",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 47,
                            "end_lineno": 105,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "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\"\"\"Result utility tests\n\n\"\"\"\n\nfrom datalad.interface.results import (\n    annexjson2result,\n)\nfrom datalad.distribution.dataset import Dataset\n\nfrom datalad.tests.utils import (\n    eq_,\n    with_tempfile,\n)\n\n\n@ with_tempfile\ndef test_annexjson2result(dspath):\n    # no explicit success means 'error'\n    eq_(annexjson2result(dict(), None),\n        dict(status='error'))\n    # unrecognized -> error\n    eq_(annexjson2result(dict(success='random'), None),\n        dict(status='error'))\n    # success is possible ;-)\n    eq_(annexjson2result(dict(success=True), None),\n        dict(status='ok'))\n\n    # path handling\n    # needs a dataset\n    ds = Dataset(dspath)\n    eq_(annexjson2result(dict(file='file1'), ds),\n        dict(status='error',\n             path=str(ds.pathobj / 'file1')))\n    # on all platforms, paths are reported in platform conventions\n    # although git-annex reports in posix\n    eq_(annexjson2result(dict(file='dir1/file1'), ds),\n        dict(status='error',\n             path=str(ds.pathobj / 'dir1' / 'file1')))\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\"\"\"Result utility tests\n\n\"\"\"\n\nfrom datalad.interface.results import (\n    annexjson2result,\n    get_status_dict,\n)\nfrom datalad.distribution.dataset import Dataset\nfrom datalad.runner import CommandError\nfrom datalad.tests.utils import (\n    eq_,\n    with_tempfile,\n)\n\n\n@ with_tempfile\ndef test_annexjson2result(dspath):\n    # no explicit success means 'error'\n    eq_(annexjson2result(dict(), None),\n        dict(status='error'))\n    # unrecognized -> error\n    eq_(annexjson2result(dict(success='random'), None),\n        dict(status='error'))\n    # success is possible ;-)\n    eq_(annexjson2result(dict(success=True), None),\n        dict(status='ok'))\n\n    # path handling\n    # needs a dataset\n    ds = Dataset(dspath)\n    eq_(annexjson2result(dict(file='file1'), ds),\n        dict(status='error',\n             path=str(ds.pathobj / 'file1')))\n    # on all platforms, paths are reported in platform conventions\n    # although git-annex reports in posix\n    eq_(annexjson2result(dict(file='dir1/file1'), ds),\n        dict(status='error',\n             path=str(ds.pathobj / 'dir1' / 'file1')))\n\n\ndef tests_status_dict_exit_code():\n    d = get_status_dict(exception=CommandError(code=105))\n    eq_(d['exit_code'], 105)\n",
            "header": {
                "file": {
                    "path": "datalad/interface/tests/test_results.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "d573f34e0",
                "new_commit_hash": "3b5dacf0f",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/datalad/interface/tests/test_results.py"
            },
            "plus_file": {
                "path": "b/datalad/interface/tests/test_results.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 12,
                            "length": 9
                        },
                        "new_range": {
                            "start": 12,
                            "length": 10
                        },
                        "section": ""
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "from datalad.interface.results import (",
                                "type": "context"
                            },
                            {
                                "content": "    annexjson2result,",
                                "type": "context"
                            },
                            {
                                "content": "    get_status_dict,",
                                "type": "added"
                            },
                            {
                                "content": ")",
                                "type": "context"
                            },
                            {
                                "content": "from datalad.distribution.dataset import Dataset",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "deleted"
                            },
                            {
                                "content": "from datalad.runner import CommandError",
                                "type": "added"
                            },
                            {
                                "content": "from datalad.tests.utils import (",
                                "type": "context"
                            },
                            {
                                "content": "    eq_,",
                                "type": "context"
                            },
                            {
                                "content": "    with_tempfile,",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "datalad/interface/tests/test_results.py",
                            "type": "statement",
                            "name": "",
                            "content": "from datalad.runner import CommandError",
                            "ast_type_str": "ImportFrom",
                            "start_lineno": 18,
                            "end_lineno": 18,
                            "parent": null
                        },
                        {
                            "file_name": "datalad/interface/tests/test_results.py",
                            "type": "statement",
                            "name": "",
                            "content": "from datalad.interface.results import (\n    annexjson2result,\n    get_status_dict,\n)",
                            "ast_type_str": "ImportFrom",
                            "start_lineno": 13,
                            "end_lineno": 16,
                            "parent": null
                        }
                    ],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 44,
                            "length": 3
                        },
                        "new_range": {
                            "start": 45,
                            "length": 8
                        },
                        "section": "def test_annexjson2result(dspath):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    eq_(annexjson2result(dict(file='dir1/file1'), ds),",
                                "type": "context"
                            },
                            {
                                "content": "        dict(status='error',",
                                "type": "context"
                            },
                            {
                                "content": "             path=str(ds.pathobj / 'dir1' / 'file1')))",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "def tests_status_dict_exit_code():",
                                "type": "added"
                            },
                            {
                                "content": "    d = get_status_dict(exception=CommandError(code=105))",
                                "type": "added"
                            },
                            {
                                "content": "    eq_(d['exit_code'], 105)",
                                "type": "added"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "datalad/interface/tests/test_results.py",
                            "type": "function",
                            "name": "tests_status_dict_exit_code",
                            "content": "def tests_status_dict_exit_code():\n    d = get_status_dict(exception=CommandError(code=105))\n    eq_(d['exit_code'], 105)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 50,
                            "end_lineno": 52,
                            "parent": null
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        },
        {
            "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\"\"\" datalad exceptions\n\"\"\"\n\nimport logging\nimport re\nimport traceback\nfrom os import linesep\nfrom pathlib import Path\nfrom pprint import pformat\n\nfrom datalad.runner.exception import CommandError\n\nlgr = logging.getLogger('datalad.support.exceptions')\n\n\nclass CapturedException(object):\n    \"\"\"This class represents information about an occurred exception (including\n    its traceback), while not holding any references to the actual exception\n    object or its traceback, frame references, etc.\n\n    Just keep the textual information for logging or whatever other kind of\n    reporting.\n    \"\"\"\n\n    def __init__(self, exc, limit=None, capture_locals=False,\n                 level=8, logger=None):\n        \"\"\"Capture an exception and its traceback for logging.\n\n        Clears the exception's traceback frame references afterwards.\n\n        Parameters\n        ----------\n        exc: Exception\n        limit: int\n          Note, that this is limiting the capturing of the exception's\n          traceback depth. Formatting for output comes with it's own limit.\n        capture_locals: bool\n          Whether or not to capture the local context of traceback frames.\n        \"\"\"\n        # Note, that with lookup_lines=False the lookup is deferred,\n        # not disabled. Unclear to me ATM, whether that means to keep frame\n        # references around, but prob. not. TODO: Test that.\n        self.tb = traceback.TracebackException.from_exception(\n            exc,\n            limit=limit,\n            lookup_lines=True,\n            capture_locals=capture_locals\n        )\n        traceback.clear_frames(exc.__traceback__)\n\n        # log the captured exception\n        logger = logger or lgr\n        logger.log(level, \"%r\", self)\n\n    def format_oneline_tb(self, limit=None, include_str=True):\n        \"\"\"Format an exception traceback as a one-line summary\n\n        Returns a string of the form [filename:contextname:linenumber, ...].\n        If include_str is True (default), this is prepended with the string\n        representation of the exception.\n        \"\"\"\n\n        # Note: No import at module level, since ConfigManager imports\n        # dochelpers -> circular import when creating datalad.cfg instance at\n        # startup.\n        from datalad import cfg\n\n        if include_str:\n            # try exc message else exception type\n            leading = self.message or self.name\n            out = \"{} \".format(leading)\n        else:\n            out = \"\"\n\n        entries = []\n        entries.extend(self.tb.stack)\n        if self.tb.__cause__:\n            entries.extend(self.tb.__cause__.stack)\n        elif self.tb.__context__ and not self.tb.__suppress_context__:\n            entries.extend(self.tb.__context__.stack)\n\n        if limit is None:\n            limit = int(cfg.obtain('datalad.exc.str.tblimit',\n                                   default=len(entries)))\n        if entries:\n            tb_str = \"[%s]\" % (','.join(\n                \"{}:{}:{}\".format(\n                    Path(frame_summary.filename).name,\n                    frame_summary.name,\n                    frame_summary.lineno)\n                for frame_summary in entries[-limit:])\n            )\n            out += \"{}\".format(tb_str)\n\n        return out\n\n    def format_standard(self):\n        \"\"\"Returns python's standard formatted traceback output\n\n        Returns\n        -------\n        str\n        \"\"\"\n        # TODO: Intended for introducing a decent debug mode later when this\n        #       can be used fromm within log formatter / result renderer.\n        #       For now: a one-liner is free\n        return ''.join(self.tb.format())\n\n    def format_short(self):\n        \"\"\"Returns a short representation of the original exception\n\n        Form: ExceptionName(exception message)\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return self.name + '(' + self.message + ')'\n\n    @property\n    def message(self):\n        \"\"\"Returns only the message of the original exception\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return str(self.tb)\n\n    @property\n    def name(self):\n        \"\"\"Returns the class name of the original exception\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return self.tb.exc_type.__qualname__\n\n    def __str__(self):\n        return self.format_short()\n\n    def __repr__(self):\n        return self.format_oneline_tb(limit=None, include_str=True)\n\n\nclass MissingExternalDependency(RuntimeError):\n    \"\"\"External dependency is missing error\"\"\"\n\n    def __init__(self, name, ver=None, msg=\"\"):\n        super(MissingExternalDependency, self).__init__()\n        self.name = name\n        self.ver = ver\n        self.msg = msg\n\n    def __str__(self):\n        to_str = 'No working {} installation'.format(self.name)\n        if self.ver:\n            to_str += \" of version >= %s\" % self.ver\n        to_str += \".\"\n        if self.msg:\n            to_str += \" %s\" % self.msg\n        return to_str\n\n\nclass BrokenExternalDependency(RuntimeError):\n    \"\"\"Some particular functionality is broken with this dependency.\"\"\"\n\n\nclass DeprecatedError(RuntimeError):\n    \"\"\"To raise whenever a deprecated entirely feature is used\"\"\"\n    def __init__(self, new=None, version=None, msg=''):\n        \"\"\"\n\n        Parameters\n        ----------\n        new : str, optional\n          What new construct to use\n        version : str, optional\n          Since which version is deprecated\n        kwargs\n        \"\"\"\n        super(DeprecatedError, self).__init__()\n        self.version = version\n        self.new = new\n        self.msg = msg\n\n    def __str__(self):\n        s = self.msg if self.msg else ''\n        if self.version:\n            s += (\" is deprecated\" if s else \"Deprecated\") + \" since version %s.\" % self.version\n        if self.new:\n            s += \" Use %s instead.\" % self.new\n        return s\n\n\nclass OutdatedExternalDependency(MissingExternalDependency):\n    \"\"\"External dependency is present but outdated\"\"\"\n\n    def __init__(self, name, ver=None, ver_present=None, msg=\"\"):\n        super(OutdatedExternalDependency, self).__init__(name, ver=ver, msg=msg)\n        self.ver_present = ver_present\n\n    def __str__(self):\n        to_str = super(OutdatedExternalDependency, self).__str__()\n        # MissingExternalDependency ends with a period unless msg is\n        # given, in which case it's up to the msg and no callers in\n        # our code base currently give a msg ending with a period.\n        to_str += \".\" if self.msg else \"\"\n        to_str += \" You have version %s\" % self.ver_present \\\n            if self.ver_present else \\\n            \" Some unknown version of dependency found.\"\n        return to_str\n\n\nclass AnnexBatchCommandError(CommandError):\n    \"\"\"Thrown if a batched command to annex fails\n\n    \"\"\"\n    pass\n\n\nclass CommandNotAvailableError(CommandError):\n    \"\"\"Thrown if a command is not available due to certain circumstances.\n    \"\"\"\n    pass\n\n\nclass FileNotInAnnexError(IOError, CommandError):\n    \"\"\"Thrown if a file is not under control of git-annex.\n    \"\"\"\n    def __init__(self, cmd=\"\", msg=\"\", code=None, filename=\"\"):\n        CommandError.__init__(self, cmd=cmd, msg=msg, code=code)\n        IOError.__init__(self, code, \"%s: %s\" % (cmd, msg), filename)\n\n    def to_str(self, include_output=True):\n        return \"%s\\n%s\" % (\n            CommandError.to_str(self, include_output=include_output),\n            IOError.__str__(self))\n\n\nclass FileInGitError(FileNotInAnnexError):\n    \"\"\"Thrown if a file is not under control of git-annex, but git itself.\n    \"\"\"\n    pass\n\n\nclass FileNotInRepositoryError(FileNotInAnnexError):\n    \"\"\"Thrown if a file is not under control of the repository at all.\n    \"\"\"\n    pass\n\n\nclass InvalidGitReferenceError(ValueError):\n    \"\"\"Thrown if provided git reference is invalid\n    \"\"\"\n    def __init__(self, ref, *args, **kwargs):\n        super(InvalidGitReferenceError, self).__init__(*args, **kwargs)\n        self.ref = ref\n\n    def __str__(self):\n        return u\"Git reference '{}' invalid\".format(self.ref)\n\n\nclass GitIgnoreError(CommandError):\n    \"\"\"Thrown if a path was ignored by a git command due to .gitignore file\n\n    Note, that this might be thrown to indicate what was ignored, while the\n    actual operation was partially successful (regarding paths, not in .gitignore)\n\n    Note/Todo:\n    in case of a directory being ignored, git returns that directory as the\n    ignored path, even if a path within that directory was passed to the command.\n    That means, that in such cases the returned path might not match an item you\n    passed!\n    \"\"\"\n\n    pattern = \\\n        re.compile(r'ignored by one of your .gitignore files:\\s*(.*)'\n                   r'^(?:hint: )?Use -f.*$',\n                   flags=re.MULTILINE | re.DOTALL)\n\n    def __init__(self, cmd=\"\", msg=\"\", code=None, stdout=\"\", stderr=\"\",\n                 paths=None):\n        super(GitIgnoreError, self).__init__(\n            cmd=cmd, msg=msg, code=code, stdout=stdout, stderr=stderr)\n        self.paths = paths\n\n    def to_str(self, include_output=True):\n        # Override CommandError.to_str(), ignoring include_output.\n        return self.msg\n\n\nclass PathOutsideRepositoryError(Exception):\n    \"\"\"Thrown if a path points outside the repository that was requested to\n    deal with that path.\"\"\"\n\n    # TODO: use it in GitRepo/AnnexRepo!\n    def __init__(self, file_, repo):\n        self.file_ = file_\n        self.repo = repo\n\n    def __str__(self):\n        return \"path {0} not within repository {1}\".format(self.file_, self.repo)\n\n\nclass PathKnownToRepositoryError(Exception):\n    \"\"\"Thrown if file/path is under Git control, and attempted operation\n    must not be ran\"\"\"\n    pass\n\n\nclass GitError(Exception):\n    \"\"\" Base class for all package exceptions \"\"\"\n\n\nclass NoSuchPathError(GitError, OSError):\n    \"\"\" Thrown if a path could not be access by the system. \"\"\"\n\n\nclass MissingBranchError(Exception):\n    \"\"\"Thrown if accessing a repository's branch, that is not available\"\"\"\n\n    def __init__(self, repo, branch, available_branches=None, msg=None):\n        self.repo = repo\n        self.branch = branch\n        self.branches = available_branches\n        if msg is None:\n            self.msg = \"branch '{0}' missing in {1}.\" \\\n                       \"\".format(self.branch, self.repo)\n            if self.branches:\n                self.msg += \" Available branches: {0}\".format(self.branches)\n        else:\n            self.msg = msg\n\n    def __str__(self):\n        return self.msg\n\n\nclass InsufficientArgumentsError(ValueError):\n    \"\"\"To be raise instead of `ValueError` when use help output is desired\"\"\"\n    pass\n\n\nclass NoDatasetArgumentFound(InsufficientArgumentsError):\n    \"\"\"To be raised when expecting having a dataset but none was provided\"\"\"\n    pass\n\n\nclass NoDatasetFound(NoDatasetArgumentFound):\n    \"\"\"Raised whenever a dataset is required, but none could be determined\"\"\"\n    pass\n\n\nclass OutOfSpaceError(CommandError):\n    \"\"\"To be raised whenever a command fails if we have no sufficient space\n\n    Example is  annex get command\n    \"\"\"\n\n    def __init__(self, sizemore_msg=None, **kwargs):\n        super(OutOfSpaceError, self).__init__(**kwargs)\n        self.sizemore_msg = sizemore_msg\n\n    def to_str(self, include_output=True):\n        super_str = super().to_str(\n            include_output=include_output).rstrip(linesep + '.')\n        return \"%s needs %s more\" % (super_str, self.sizemore_msg)\n\n\nclass RemoteNotAvailableError(CommandError):\n    \"\"\"To be raised whenever a required remote is not available\n\n    Example is \"annex get somefile --from=MyRemote\",\n    where 'MyRemote' doesn't exist.\n    \"\"\"\n\n    def __init__(self, remote, **kwargs):\n        \"\"\"\n\n        Parameters\n        ----------\n        remote: str\n          name of the remote\n        kwargs:\n          arguments from CommandError\n        \"\"\"\n        super(RemoteNotAvailableError, self).__init__(**kwargs)\n        self.remote = remote\n\n    def to_str(self, include_output=True):\n        super_str = super().to_str(include_output=include_output)\n        return \"Remote '{0}' is not available. Command failed:{1}{2}\" \\\n               \"\".format(self.remote, linesep, super_str)\n\n\nclass InvalidInstanceRequestError(RuntimeError):\n    \"\"\"Thrown if a request to create a (flyweight) instance is invalid\"\"\"\n\n    def __init__(self, id_, msg=None):\n        super(InvalidInstanceRequestError, self).__init__(msg)\n        self.id = id_\n        self.msg = msg\n\n\nclass InvalidGitRepositoryError(GitError):\n    \"\"\" Thrown if the given repository appears to have an invalid format.  \"\"\"\n\n\nclass InvalidAnnexRepositoryError(RuntimeError):\n    \"\"\"Thrown if AnnexRepo was instantiated on a non-annex and\n    without init=True\"\"\"\n\n\nclass DirectModeNoLongerSupportedError(NotImplementedError):\n    \"\"\"direct mode is no longer supported\"\"\"\n\n    def __init__(self, repo, msg=None):\n        super(DirectModeNoLongerSupportedError, self).__init__(\n            ((\" \" + msg + \", but \") if msg else '')\n            +\n             \"direct mode of operation is being deprecated in git-annex and \"\n             \"no longer supported by DataLad. \"\n             \"Please use 'git annex upgrade' under %s to upgrade your direct \"\n             \"mode repository to annex v6 (or later).\" % repo.path\n            )\n        self.repo = repo  # might come handy\n\n\nclass IncompleteResultsError(RuntimeError):\n    \"\"\"Exception to be raised whenever results are incomplete.\n\n    Any results produced nevertheless are to be passed as `results`,\n    and become available via the `results` attribute.\n    \"\"\"\n    # TODO passing completed results doesn't fit in a generator paradigm\n    # such results have been yielded already at the time this exception is\n    # raised, little point in collecting them just for the sake of a possible\n    # exception\n    # MIH+YOH: AnnexRepo.copy_to and @eval_results are the last\n    # remaining user of this functionality.\n    # General use (as in AnnexRepo) of it discouraged but use in @eval_results\n    # is warranted\n    def __init__(self, results=None, failed=None, msg=None):\n        super(IncompleteResultsError, self).__init__(msg)\n        self.results = results\n        self.failed = failed\n\n    def __str__(self):\n        super_str = super(IncompleteResultsError, self).__str__()\n        return \"{}{}{}\".format(\n            super_str,\n            \". {} result(s)\".format(len(self.results)) if self.results else \"\",\n            \". {} failed:{}{}\".format(\n                len(self.failed),\n                linesep,\n                pformat(self.failed)) if self.failed else \"\")\n\n\nclass InstallFailedError(CommandError):\n    \"\"\"Generic exception to raise whenever `install` command fails\"\"\"\n    pass\n\n\nclass ConnectionOpenFailedError(CommandError):\n    \"\"\"Exception to raise whenever opening a network connection fails\"\"\"\n    pass\n#\n# Downloaders\n#\n\n\nclass DownloadError(Exception):\n    pass\n\n\nclass IncompleteDownloadError(DownloadError):\n    pass\n\n\nclass UnaccountedDownloadError(IncompleteDownloadError):\n    pass\n\n\nclass TargetFileAbsent(DownloadError):\n    pass\n\n\nclass AccessDeniedError(DownloadError):\n    def __init__(self, msg=None, supported_types=None, **kwargs):\n        super(AccessDeniedError, self).__init__(msg, **kwargs)\n        self.supported_types = supported_types\n\n\nclass AnonymousAccessDeniedError(AccessDeniedError):\n    pass\n\n\nclass AccessPermissionExpiredError(AccessDeniedError):\n    \"\"\"To raise when there is a belief that it is due to expiration of a credential\n\n    which we might possibly be able to refresh, like in the case of CompositeCredential\n    \"\"\"\n    pass\n\n\nclass AccessFailedError(DownloadError):\n    pass\n\n\nclass UnhandledRedirectError(DownloadError):\n    def __init__(self, msg=None, url=None, **kwargs):\n        super(UnhandledRedirectError, self).__init__(msg, **kwargs)\n        self.url = url\n\n#\n# Crawler\n#\n\n\nclass CrawlerError(Exception):\n    pass\n\n\nclass PipelineNotSpecifiedError(CrawlerError):\n    pass\n\n\n#\n# Warnings\n#\n\nclass DataLadWarning(Warning):\n    pass\n\n\n# We have an exception OutdatedExternalDependency, but it is intended for\n# an instance being raised.  `warnings` module requires a class to be provided\n# as a category, so here is a dedicated Warning class\nclass OutdatedExternalDependencyWarning(DataLadWarning):\n    \"\"\"Warning \"category\" to use to report about outdated\"\"\"\n    pass\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\"\"\" datalad exceptions\n\"\"\"\n\nimport logging\nimport re\nimport traceback\nfrom os import linesep\nfrom pathlib import Path\nfrom pprint import pformat\n\nfrom datalad.runner.exception import CommandError\n\nlgr = logging.getLogger('datalad.support.exceptions')\n\n\nclass CapturedException(object):\n    \"\"\"This class represents information about an occurred exception (including\n    its traceback), while not holding any references to the actual exception\n    object or its traceback, frame references, etc.\n\n    Just keep the textual information for logging or whatever other kind of\n    reporting.\n    \"\"\"\n\n    def __init__(self, exc, limit=None, capture_locals=False,\n                 level=8, logger=None):\n        \"\"\"Capture an exception and its traceback for logging.\n\n        Clears the exception's traceback frame references afterwards.\n\n        Parameters\n        ----------\n        exc: Exception\n        limit: int\n          Note, that this is limiting the capturing of the exception's\n          traceback depth. Formatting for output comes with it's own limit.\n        capture_locals: bool\n          Whether or not to capture the local context of traceback frames.\n        \"\"\"\n        # Note, that with lookup_lines=False the lookup is deferred,\n        # not disabled. Unclear to me ATM, whether that means to keep frame\n        # references around, but prob. not. TODO: Test that.\n        self.tb = traceback.TracebackException.from_exception(\n            exc,\n            limit=limit,\n            lookup_lines=True,\n            capture_locals=capture_locals\n        )\n        traceback.clear_frames(exc.__traceback__)\n\n        # log the captured exception\n        logger = logger or lgr\n        logger.log(level, \"%r\", self)\n\n    def format_oneline_tb(self, limit=None, include_str=True):\n        \"\"\"Format an exception traceback as a one-line summary\n\n        Returns a string of the form [filename:contextname:linenumber, ...].\n        If include_str is True (default), this is prepended with the string\n        representation of the exception.\n        \"\"\"\n        return format_oneline_tb(\n            self, self.tb, limit=limit, include_str=include_str)\n\n    def format_standard(self):\n        \"\"\"Returns python's standard formatted traceback output\n\n        Returns\n        -------\n        str\n        \"\"\"\n        # TODO: Intended for introducing a decent debug mode later when this\n        #       can be used fromm within log formatter / result renderer.\n        #       For now: a one-liner is free\n        return ''.join(self.tb.format())\n\n    def format_short(self):\n        \"\"\"Returns a short representation of the original exception\n\n        Form: ExceptionName(exception message)\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return self.name + '(' + self.message + ')'\n\n    @property\n    def message(self):\n        \"\"\"Returns only the message of the original exception\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return str(self.tb)\n\n    @property\n    def name(self):\n        \"\"\"Returns the class name of the original exception\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return self.tb.exc_type.__qualname__\n\n    def __str__(self):\n        return self.format_short()\n\n    def __repr__(self):\n        return self.format_oneline_tb(limit=None, include_str=True)\n\n\ndef format_oneline_tb(exc, tb=None, limit=None, include_str=True):\n    \"\"\"Format an exception traceback as a one-line summary\n\n    Parameters\n    ----------\n    exc: Exception\n    tb: TracebackException, optional\n      If not given, it is generated from the given exception.\n    limit: int, optional\n      Traceback depth limit. If not given, the config setting\n      'datalad.exc.str.tblimit' will be used, or all entries\n      are reported.\n    include_str: bool\n      If set, is True (default), the return value is prepended with a string\n    representation of the exception.\n\n    Returns\n    -------\n    str\n      Of format [filename:contextname:linenumber, ...].\n    \"\"\"\n\n    # Note: No import at module level, since ConfigManager imports\n    # dochelpers -> circular import when creating datalad.cfg instance at\n    # startup.\n    from datalad import cfg\n\n    if include_str:\n        # try exc message else exception type\n        leading = exc.message or exc.name\n        out = \"{} \".format(leading)\n    else:\n        out = \"\"\n\n    if tb is None:\n        tb = traceback.TracebackException.from_exception(\n            exc,\n            limit=limit,\n            lookup_lines=True,\n            capture_locals=False,\n        )\n\n    entries = []\n    entries.extend(tb.stack)\n    if tb.__cause__:\n        entries.extend(tb.__cause__.stack)\n    elif tb.__context__ and not tb.__suppress_context__:\n        entries.extend(tb.__context__.stack)\n\n    if limit is None:\n        limit = int(cfg.obtain('datalad.exc.str.tblimit',\n                               default=len(entries)))\n    if entries:\n        tb_str = \"[%s]\" % (','.join(\n            \"{}:{}:{}\".format(\n                Path(frame_summary.filename).name,\n                frame_summary.name,\n                frame_summary.lineno)\n            for frame_summary in entries[-limit:])\n        )\n        out += \"{}\".format(tb_str)\n\n    return out\n\n\nclass MissingExternalDependency(RuntimeError):\n    \"\"\"External dependency is missing error\"\"\"\n\n    def __init__(self, name, ver=None, msg=\"\"):\n        super(MissingExternalDependency, self).__init__()\n        self.name = name\n        self.ver = ver\n        self.msg = msg\n\n    def __str__(self):\n        to_str = 'No working {} installation'.format(self.name)\n        if self.ver:\n            to_str += \" of version >= %s\" % self.ver\n        to_str += \".\"\n        if self.msg:\n            to_str += \" %s\" % self.msg\n        return to_str\n\n\nclass BrokenExternalDependency(RuntimeError):\n    \"\"\"Some particular functionality is broken with this dependency.\"\"\"\n\n\nclass DeprecatedError(RuntimeError):\n    \"\"\"To raise whenever a deprecated entirely feature is used\"\"\"\n    def __init__(self, new=None, version=None, msg=''):\n        \"\"\"\n\n        Parameters\n        ----------\n        new : str, optional\n          What new construct to use\n        version : str, optional\n          Since which version is deprecated\n        kwargs\n        \"\"\"\n        super(DeprecatedError, self).__init__()\n        self.version = version\n        self.new = new\n        self.msg = msg\n\n    def __str__(self):\n        s = self.msg if self.msg else ''\n        if self.version:\n            s += (\" is deprecated\" if s else \"Deprecated\") + \" since version %s.\" % self.version\n        if self.new:\n            s += \" Use %s instead.\" % self.new\n        return s\n\n\nclass OutdatedExternalDependency(MissingExternalDependency):\n    \"\"\"External dependency is present but outdated\"\"\"\n\n    def __init__(self, name, ver=None, ver_present=None, msg=\"\"):\n        super(OutdatedExternalDependency, self).__init__(name, ver=ver, msg=msg)\n        self.ver_present = ver_present\n\n    def __str__(self):\n        to_str = super(OutdatedExternalDependency, self).__str__()\n        # MissingExternalDependency ends with a period unless msg is\n        # given, in which case it's up to the msg and no callers in\n        # our code base currently give a msg ending with a period.\n        to_str += \".\" if self.msg else \"\"\n        to_str += \" You have version %s\" % self.ver_present \\\n            if self.ver_present else \\\n            \" Some unknown version of dependency found.\"\n        return to_str\n\n\nclass AnnexBatchCommandError(CommandError):\n    \"\"\"Thrown if a batched command to annex fails\n\n    \"\"\"\n    pass\n\n\nclass CommandNotAvailableError(CommandError):\n    \"\"\"Thrown if a command is not available due to certain circumstances.\n    \"\"\"\n    pass\n\n\nclass FileNotInAnnexError(IOError, CommandError):\n    \"\"\"Thrown if a file is not under control of git-annex.\n    \"\"\"\n    def __init__(self, cmd=\"\", msg=\"\", code=None, filename=\"\"):\n        CommandError.__init__(self, cmd=cmd, msg=msg, code=code)\n        IOError.__init__(self, code, \"%s: %s\" % (cmd, msg), filename)\n\n    def to_str(self, include_output=True):\n        return \"%s\\n%s\" % (\n            CommandError.to_str(self, include_output=include_output),\n            IOError.__str__(self))\n\n\nclass FileInGitError(FileNotInAnnexError):\n    \"\"\"Thrown if a file is not under control of git-annex, but git itself.\n    \"\"\"\n    pass\n\n\nclass FileNotInRepositoryError(FileNotInAnnexError):\n    \"\"\"Thrown if a file is not under control of the repository at all.\n    \"\"\"\n    pass\n\n\nclass InvalidGitReferenceError(ValueError):\n    \"\"\"Thrown if provided git reference is invalid\n    \"\"\"\n    def __init__(self, ref, *args, **kwargs):\n        super(InvalidGitReferenceError, self).__init__(*args, **kwargs)\n        self.ref = ref\n\n    def __str__(self):\n        return u\"Git reference '{}' invalid\".format(self.ref)\n\n\nclass GitIgnoreError(CommandError):\n    \"\"\"Thrown if a path was ignored by a git command due to .gitignore file\n\n    Note, that this might be thrown to indicate what was ignored, while the\n    actual operation was partially successful (regarding paths, not in .gitignore)\n\n    Note/Todo:\n    in case of a directory being ignored, git returns that directory as the\n    ignored path, even if a path within that directory was passed to the command.\n    That means, that in such cases the returned path might not match an item you\n    passed!\n    \"\"\"\n\n    pattern = \\\n        re.compile(r'ignored by one of your .gitignore files:\\s*(.*)'\n                   r'^(?:hint: )?Use -f.*$',\n                   flags=re.MULTILINE | re.DOTALL)\n\n    def __init__(self, cmd=\"\", msg=\"\", code=None, stdout=\"\", stderr=\"\",\n                 paths=None):\n        super(GitIgnoreError, self).__init__(\n            cmd=cmd, msg=msg, code=code, stdout=stdout, stderr=stderr)\n        self.paths = paths\n\n    def to_str(self, include_output=True):\n        # Override CommandError.to_str(), ignoring include_output.\n        return self.msg\n\n\nclass PathOutsideRepositoryError(Exception):\n    \"\"\"Thrown if a path points outside the repository that was requested to\n    deal with that path.\"\"\"\n\n    # TODO: use it in GitRepo/AnnexRepo!\n    def __init__(self, file_, repo):\n        self.file_ = file_\n        self.repo = repo\n\n    def __str__(self):\n        return \"path {0} not within repository {1}\".format(self.file_, self.repo)\n\n\nclass PathKnownToRepositoryError(Exception):\n    \"\"\"Thrown if file/path is under Git control, and attempted operation\n    must not be ran\"\"\"\n    pass\n\n\nclass GitError(Exception):\n    \"\"\" Base class for all package exceptions \"\"\"\n\n\nclass NoSuchPathError(GitError, OSError):\n    \"\"\" Thrown if a path could not be access by the system. \"\"\"\n\n\nclass MissingBranchError(Exception):\n    \"\"\"Thrown if accessing a repository's branch, that is not available\"\"\"\n\n    def __init__(self, repo, branch, available_branches=None, msg=None):\n        self.repo = repo\n        self.branch = branch\n        self.branches = available_branches\n        if msg is None:\n            self.msg = \"branch '{0}' missing in {1}.\" \\\n                       \"\".format(self.branch, self.repo)\n            if self.branches:\n                self.msg += \" Available branches: {0}\".format(self.branches)\n        else:\n            self.msg = msg\n\n    def __str__(self):\n        return self.msg\n\n\nclass InsufficientArgumentsError(ValueError):\n    \"\"\"To be raise instead of `ValueError` when use help output is desired\"\"\"\n    pass\n\n\nclass NoDatasetArgumentFound(InsufficientArgumentsError):\n    \"\"\"To be raised when expecting having a dataset but none was provided\"\"\"\n    pass\n\n\nclass NoDatasetFound(NoDatasetArgumentFound):\n    \"\"\"Raised whenever a dataset is required, but none could be determined\"\"\"\n    pass\n\n\nclass OutOfSpaceError(CommandError):\n    \"\"\"To be raised whenever a command fails if we have no sufficient space\n\n    Example is  annex get command\n    \"\"\"\n\n    def __init__(self, sizemore_msg=None, **kwargs):\n        super(OutOfSpaceError, self).__init__(**kwargs)\n        self.sizemore_msg = sizemore_msg\n\n    def to_str(self, include_output=True):\n        super_str = super().to_str(\n            include_output=include_output).rstrip(linesep + '.')\n        return \"%s needs %s more\" % (super_str, self.sizemore_msg)\n\n\nclass RemoteNotAvailableError(CommandError):\n    \"\"\"To be raised whenever a required remote is not available\n\n    Example is \"annex get somefile --from=MyRemote\",\n    where 'MyRemote' doesn't exist.\n    \"\"\"\n\n    def __init__(self, remote, **kwargs):\n        \"\"\"\n\n        Parameters\n        ----------\n        remote: str\n          name of the remote\n        kwargs:\n          arguments from CommandError\n        \"\"\"\n        super(RemoteNotAvailableError, self).__init__(**kwargs)\n        self.remote = remote\n\n    def to_str(self, include_output=True):\n        super_str = super().to_str(include_output=include_output)\n        return \"Remote '{0}' is not available. Command failed:{1}{2}\" \\\n               \"\".format(self.remote, linesep, super_str)\n\n\nclass InvalidInstanceRequestError(RuntimeError):\n    \"\"\"Thrown if a request to create a (flyweight) instance is invalid\"\"\"\n\n    def __init__(self, id_, msg=None):\n        super(InvalidInstanceRequestError, self).__init__(msg)\n        self.id = id_\n        self.msg = msg\n\n\nclass InvalidGitRepositoryError(GitError):\n    \"\"\" Thrown if the given repository appears to have an invalid format.  \"\"\"\n\n\nclass InvalidAnnexRepositoryError(RuntimeError):\n    \"\"\"Thrown if AnnexRepo was instantiated on a non-annex and\n    without init=True\"\"\"\n\n\nclass DirectModeNoLongerSupportedError(NotImplementedError):\n    \"\"\"direct mode is no longer supported\"\"\"\n\n    def __init__(self, repo, msg=None):\n        super(DirectModeNoLongerSupportedError, self).__init__(\n            ((\" \" + msg + \", but \") if msg else '')\n            +\n             \"direct mode of operation is being deprecated in git-annex and \"\n             \"no longer supported by DataLad. \"\n             \"Please use 'git annex upgrade' under %s to upgrade your direct \"\n             \"mode repository to annex v6 (or later).\" % repo.path\n            )\n        self.repo = repo  # might come handy\n\n\nclass IncompleteResultsError(RuntimeError):\n    \"\"\"Exception to be raised whenever results are incomplete.\n\n    Any results produced nevertheless are to be passed as `results`,\n    and become available via the `results` attribute.\n    \"\"\"\n    # TODO passing completed results doesn't fit in a generator paradigm\n    # such results have been yielded already at the time this exception is\n    # raised, little point in collecting them just for the sake of a possible\n    # exception\n    # MIH+YOH: AnnexRepo.copy_to and @eval_results are the last\n    # remaining user of this functionality.\n    # General use (as in AnnexRepo) of it discouraged but use in @eval_results\n    # is warranted\n    def __init__(self, results=None, failed=None, msg=None):\n        super(IncompleteResultsError, self).__init__(msg)\n        self.results = results\n        self.failed = failed\n\n    def __str__(self):\n        super_str = super(IncompleteResultsError, self).__str__()\n        return \"{}{}{}\".format(\n            super_str,\n            \". {} result(s)\".format(len(self.results)) if self.results else \"\",\n            \". {} failed:{}{}\".format(\n                len(self.failed),\n                linesep,\n                pformat(self.failed)) if self.failed else \"\")\n\n\nclass InstallFailedError(CommandError):\n    \"\"\"Generic exception to raise whenever `install` command fails\"\"\"\n    pass\n\n\nclass ConnectionOpenFailedError(CommandError):\n    \"\"\"Exception to raise whenever opening a network connection fails\"\"\"\n    pass\n#\n# Downloaders\n#\n\n\nclass DownloadError(Exception):\n    pass\n\n\nclass IncompleteDownloadError(DownloadError):\n    pass\n\n\nclass UnaccountedDownloadError(IncompleteDownloadError):\n    pass\n\n\nclass TargetFileAbsent(DownloadError):\n    pass\n\n\nclass AccessDeniedError(DownloadError):\n    def __init__(self, msg=None, supported_types=None, **kwargs):\n        super(AccessDeniedError, self).__init__(msg, **kwargs)\n        self.supported_types = supported_types\n\n\nclass AnonymousAccessDeniedError(AccessDeniedError):\n    pass\n\n\nclass AccessPermissionExpiredError(AccessDeniedError):\n    \"\"\"To raise when there is a belief that it is due to expiration of a credential\n\n    which we might possibly be able to refresh, like in the case of CompositeCredential\n    \"\"\"\n    pass\n\n\nclass AccessFailedError(DownloadError):\n    pass\n\n\nclass UnhandledRedirectError(DownloadError):\n    def __init__(self, msg=None, url=None, **kwargs):\n        super(UnhandledRedirectError, self).__init__(msg, **kwargs)\n        self.url = url\n\n#\n# Crawler\n#\n\n\nclass CrawlerError(Exception):\n    pass\n\n\nclass PipelineNotSpecifiedError(CrawlerError):\n    pass\n\n\n#\n# Warnings\n#\n\nclass DataLadWarning(Warning):\n    pass\n\n\n# We have an exception OutdatedExternalDependency, but it is intended for\n# an instance being raised.  `warnings` module requires a class to be provided\n# as a category, so here is a dedicated Warning class\nclass OutdatedExternalDependencyWarning(DataLadWarning):\n    \"\"\"Warning \"category\" to use to report about outdated\"\"\"\n    pass\n",
            "header": {
                "file": {
                    "path": "datalad/support/exceptions.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "8218627a7",
                "new_commit_hash": "9cd5d11a6",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/datalad/support/exceptions.py"
            },
            "plus_file": {
                "path": "b/datalad/support/exceptions.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 67,
                            "length": 40
                        },
                        "new_range": {
                            "start": 67,
                            "length": 8
                        },
                        "section": "class CapturedException(object):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        If include_str is True (default), this is prepended with the string",
                                "type": "context"
                            },
                            {
                                "content": "        representation of the exception.",
                                "type": "context"
                            },
                            {
                                "content": "        \"\"\"",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "deleted"
                            },
                            {
                                "content": "        # Note: No import at module level, since ConfigManager imports",
                                "type": "deleted"
                            },
                            {
                                "content": "        # dochelpers -> circular import when creating datalad.cfg instance at",
                                "type": "deleted"
                            },
                            {
                                "content": "        # startup.",
                                "type": "deleted"
                            },
                            {
                                "content": "        from datalad import cfg",
                                "type": "deleted"
                            },
                            {
                                "content": "",
                                "type": "deleted"
                            },
                            {
                                "content": "        if include_str:",
                                "type": "deleted"
                            },
                            {
                                "content": "            # try exc message else exception type",
                                "type": "deleted"
                            },
                            {
                                "content": "            leading = self.message or self.name",
                                "type": "deleted"
                            },
                            {
                                "content": "            out = \"{} \".format(leading)",
                                "type": "deleted"
                            },
                            {
                                "content": "        else:",
                                "type": "deleted"
                            },
                            {
                                "content": "            out = \"\"",
                                "type": "deleted"
                            },
                            {
                                "content": "",
                                "type": "deleted"
                            },
                            {
                                "content": "        entries = []",
                                "type": "deleted"
                            },
                            {
                                "content": "        entries.extend(self.tb.stack)",
                                "type": "deleted"
                            },
                            {
                                "content": "        if self.tb.__cause__:",
                                "type": "deleted"
                            },
                            {
                                "content": "            entries.extend(self.tb.__cause__.stack)",
                                "type": "deleted"
                            },
                            {
                                "content": "        elif self.tb.__context__ and not self.tb.__suppress_context__:",
                                "type": "deleted"
                            },
                            {
                                "content": "            entries.extend(self.tb.__context__.stack)",
                                "type": "deleted"
                            },
                            {
                                "content": "",
                                "type": "deleted"
                            },
                            {
                                "content": "        if limit is None:",
                                "type": "deleted"
                            },
                            {
                                "content": "            limit = int(cfg.obtain('datalad.exc.str.tblimit',",
                                "type": "deleted"
                            },
                            {
                                "content": "                                   default=len(entries)))",
                                "type": "deleted"
                            },
                            {
                                "content": "        if entries:",
                                "type": "deleted"
                            },
                            {
                                "content": "            tb_str = \"[%s]\" % (','.join(",
                                "type": "deleted"
                            },
                            {
                                "content": "                \"{}:{}:{}\".format(",
                                "type": "deleted"
                            },
                            {
                                "content": "                    Path(frame_summary.filename).name,",
                                "type": "deleted"
                            },
                            {
                                "content": "                    frame_summary.name,",
                                "type": "deleted"
                            },
                            {
                                "content": "                    frame_summary.lineno)",
                                "type": "deleted"
                            },
                            {
                                "content": "                for frame_summary in entries[-limit:])",
                                "type": "deleted"
                            },
                            {
                                "content": "            )",
                                "type": "deleted"
                            },
                            {
                                "content": "            out += \"{}\".format(tb_str)",
                                "type": "deleted"
                            },
                            {
                                "content": "",
                                "type": "deleted"
                            },
                            {
                                "content": "        return out",
                                "type": "deleted"
                            },
                            {
                                "content": "        return format_oneline_tb(",
                                "type": "added"
                            },
                            {
                                "content": "            self, self.tb, limit=limit, include_str=include_str)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def format_standard(self):",
                                "type": "context"
                            },
                            {
                                "content": "        \"\"\"Returns python's standard formatted traceback output",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/support/exceptions.py",
                            "type": "class",
                            "name": "CapturedException",
                            "content": "class CapturedException(object):\n    \"\"\"This class represents information about an occurred exception (including\n    its traceback), while not holding any references to the actual exception\n    object or its traceback, frame references, etc.\n\n    Just keep the textual information for logging or whatever other kind of\n    reporting.\n    \"\"\"\n\n    def __init__(self, exc, limit=None, capture_locals=False,\n                 level=8, logger=None):\n        \"\"\"Capture an exception and its traceback for logging.\n\n        Clears the exception's traceback frame references afterwards.\n\n        Parameters\n        ----------\n        exc: Exception\n        limit: int\n          Note, that this is limiting the capturing of the exception's\n          traceback depth. Formatting for output comes with it's own limit.\n        capture_locals: bool\n          Whether or not to capture the local context of traceback frames.\n        \"\"\"\n        # Note, that with lookup_lines=False the lookup is deferred,\n        # not disabled. Unclear to me ATM, whether that means to keep frame\n        # references around, but prob. not. TODO: Test that.\n        self.tb = traceback.TracebackException.from_exception(\n            exc,\n            limit=limit,\n            lookup_lines=True,\n            capture_locals=capture_locals\n        )\n        traceback.clear_frames(exc.__traceback__)\n\n        # log the captured exception\n        logger = logger or lgr\n        logger.log(level, \"%r\", self)\n\n    def format_oneline_tb(self, limit=None, include_str=True):\n        \"\"\"Format an exception traceback as a one-line summary\n\n        Returns a string of the form [filename:contextname:linenumber, ...].\n        If include_str is True (default), this is prepended with the string\n        representation of the exception.\n        \"\"\"\n\n        # Note: No import at module level, since ConfigManager imports\n        # dochelpers -> circular import when creating datalad.cfg instance at\n        # startup.\n        from datalad import cfg\n\n        if include_str:\n            # try exc message else exception type\n            leading = self.message or self.name\n            out = \"{} \".format(leading)\n        else:\n            out = \"\"\n\n        entries = []\n        entries.extend(self.tb.stack)\n        if self.tb.__cause__:\n            entries.extend(self.tb.__cause__.stack)\n        elif self.tb.__context__ and not self.tb.__suppress_context__:\n            entries.extend(self.tb.__context__.stack)\n\n        if limit is None:\n            limit = int(cfg.obtain('datalad.exc.str.tblimit',\n                                   default=len(entries)))\n        if entries:\n            tb_str = \"[%s]\" % (','.join(\n                \"{}:{}:{}\".format(\n                    Path(frame_summary.filename).name,\n                    frame_summary.name,\n                    frame_summary.lineno)\n                for frame_summary in entries[-limit:])\n            )\n            out += \"{}\".format(tb_str)\n\n        return out\n\n    def format_standard(self):\n        \"\"\"Returns python's standard formatted traceback output\n\n        Returns\n        -------\n        str\n        \"\"\"\n        # TODO: Intended for introducing a decent debug mode later when this\n        #       can be used fromm within log formatter / result renderer.\n        #       For now: a one-liner is free\n        return ''.join(self.tb.format())\n\n    def format_short(self):\n        \"\"\"Returns a short representation of the original exception\n\n        Form: ExceptionName(exception message)\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return self.name + '(' + self.message + ')'\n\n    @property\n    def message(self):\n        \"\"\"Returns only the message of the original exception\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return str(self.tb)\n\n    @property\n    def name(self):\n        \"\"\"Returns the class name of the original exception\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return self.tb.exc_type.__qualname__\n\n    def __str__(self):\n        return self.format_short()\n\n    def __repr__(self):\n        return self.format_oneline_tb(limit=None, include_str=True)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 24,
                            "end_lineno": 152,
                            "parent": null
                        },
                        {
                            "file_name": "datalad/support/exceptions.py",
                            "type": "method",
                            "name": "CapturedException.format_oneline_tb",
                            "content": "class CapturedException(object):\n    \"\"\"This class represents information about an occurred exception (including\n    its traceback), while not holding any references to the actual exception\n    object or its traceback, frame references, etc.\n\n    Just keep the textual information for logging or whatever other kind of\n    reporting.\n    \"\"\"\n\n    def __init__(self, exc, limit=None, capture_locals=False,\n                 level=8, logger=None):\n        \"\"\"Capture an exception and its traceback for logging.\n\n        Clears the exception's traceback frame references afterwards.\n\n        Parameters\n        ----------\n        exc: Exception\n        limit: int\n          Note, that this is limiting the capturing of the exception's\n          traceback depth. Formatting for output comes with it's own limit.\n        capture_locals: bool\n          Whether or not to capture the local context of traceback frames.\n        \"\"\"\n        # Note, that with lookup_lines=False the lookup is deferred,\n        # not disabled. Unclear to me ATM, whether that means to keep frame\n        # references around, but prob. not. TODO: Test that.\n        self.tb = traceback.TracebackException.from_exception(\n            exc,\n            limit=limit,\n            lookup_lines=True,\n            capture_locals=capture_locals\n        )\n        traceback.clear_frames(exc.__traceback__)\n\n        # log the captured exception\n        logger = logger or lgr\n        logger.log(level, \"%r\", self)\n\n    def format_oneline_tb(self, limit=None, include_str=True):\n        \"\"\"Format an exception traceback as a one-line summary\n\n        Returns a string of the form [filename:contextname:linenumber, ...].\n        If include_str is True (default), this is prepended with the string\n        representation of the exception.\n        \"\"\"\n\n        # Note: No import at module level, since ConfigManager imports\n        # dochelpers -> circular import when creating datalad.cfg instance at\n        # startup.\n        from datalad import cfg\n\n        if include_str:\n            # try exc message else exception type\n            leading = self.message or self.name\n            out = \"{} \".format(leading)\n        else:\n            out = \"\"\n\n        entries = []\n        entries.extend(self.tb.stack)\n        if self.tb.__cause__:\n            entries.extend(self.tb.__cause__.stack)\n        elif self.tb.__context__ and not self.tb.__suppress_context__:\n            entries.extend(self.tb.__context__.stack)\n\n        if limit is None:\n            limit = int(cfg.obtain('datalad.exc.str.tblimit',\n                                   default=len(entries)))\n        if entries:\n            tb_str = \"[%s]\" % (','.join(\n                \"{}:{}:{}\".format(\n                    Path(frame_summary.filename).name,\n                    frame_summary.name,\n                    frame_summary.lineno)\n                for frame_summary in entries[-limit:])\n            )\n            out += \"{}\".format(tb_str)\n\n        return out\n\n    def format_standard(self):\n        \"\"\"Returns python's standard formatted traceback output\n\n        Returns\n        -------\n        str\n        \"\"\"\n        # TODO: Intended for introducing a decent debug mode later when this\n        #       can be used fromm within log formatter / result renderer.\n        #       For now: a one-liner is free\n        return ''.join(self.tb.format())\n\n    def format_short(self):\n        \"\"\"Returns a short representation of the original exception\n\n        Form: ExceptionName(exception message)\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return self.name + '(' + self.message + ')'\n\n    @property\n    def message(self):\n        \"\"\"Returns only the message of the original exception\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return str(self.tb)\n\n    @property\n    def name(self):\n        \"\"\"Returns the class name of the original exception\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return self.tb.exc_type.__qualname__\n\n    def __str__(self):\n        return self.format_short()\n\n    def __repr__(self):\n        return self.format_oneline_tb(limit=None, include_str=True)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 63,
                            "end_lineno": 103,
                            "parent": {
                                "file_name": "datalad/support/exceptions.py",
                                "type": "class",
                                "name": "CapturedException",
                                "content": "class CapturedException(object):\n    \"\"\"This class represents information about an occurred exception (including\n    its traceback), while not holding any references to the actual exception\n    object or its traceback, frame references, etc.\n\n    Just keep the textual information for logging or whatever other kind of\n    reporting.\n    \"\"\"\n\n    def __init__(self, exc, limit=None, capture_locals=False,\n                 level=8, logger=None):\n        \"\"\"Capture an exception and its traceback for logging.\n\n        Clears the exception's traceback frame references afterwards.\n\n        Parameters\n        ----------\n        exc: Exception\n        limit: int\n          Note, that this is limiting the capturing of the exception's\n          traceback depth. Formatting for output comes with it's own limit.\n        capture_locals: bool\n          Whether or not to capture the local context of traceback frames.\n        \"\"\"\n        # Note, that with lookup_lines=False the lookup is deferred,\n        # not disabled. Unclear to me ATM, whether that means to keep frame\n        # references around, but prob. not. TODO: Test that.\n        self.tb = traceback.TracebackException.from_exception(\n            exc,\n            limit=limit,\n            lookup_lines=True,\n            capture_locals=capture_locals\n        )\n        traceback.clear_frames(exc.__traceback__)\n\n        # log the captured exception\n        logger = logger or lgr\n        logger.log(level, \"%r\", self)\n\n    def format_oneline_tb(self, limit=None, include_str=True):\n        \"\"\"Format an exception traceback as a one-line summary\n\n        Returns a string of the form [filename:contextname:linenumber, ...].\n        If include_str is True (default), this is prepended with the string\n        representation of the exception.\n        \"\"\"\n\n        # Note: No import at module level, since ConfigManager imports\n        # dochelpers -> circular import when creating datalad.cfg instance at\n        # startup.\n        from datalad import cfg\n\n        if include_str:\n            # try exc message else exception type\n            leading = self.message or self.name\n            out = \"{} \".format(leading)\n        else:\n            out = \"\"\n\n        entries = []\n        entries.extend(self.tb.stack)\n        if self.tb.__cause__:\n            entries.extend(self.tb.__cause__.stack)\n        elif self.tb.__context__ and not self.tb.__suppress_context__:\n            entries.extend(self.tb.__context__.stack)\n\n        if limit is None:\n            limit = int(cfg.obtain('datalad.exc.str.tblimit',\n                                   default=len(entries)))\n        if entries:\n            tb_str = \"[%s]\" % (','.join(\n                \"{}:{}:{}\".format(\n                    Path(frame_summary.filename).name,\n                    frame_summary.name,\n                    frame_summary.lineno)\n                for frame_summary in entries[-limit:])\n            )\n            out += \"{}\".format(tb_str)\n\n        return out\n\n    def format_standard(self):\n        \"\"\"Returns python's standard formatted traceback output\n\n        Returns\n        -------\n        str\n        \"\"\"\n        # TODO: Intended for introducing a decent debug mode later when this\n        #       can be used fromm within log formatter / result renderer.\n        #       For now: a one-liner is free\n        return ''.join(self.tb.format())\n\n    def format_short(self):\n        \"\"\"Returns a short representation of the original exception\n\n        Form: ExceptionName(exception message)\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return self.name + '(' + self.message + ')'\n\n    @property\n    def message(self):\n        \"\"\"Returns only the message of the original exception\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return str(self.tb)\n\n    @property\n    def name(self):\n        \"\"\"Returns the class name of the original exception\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return self.tb.exc_type.__qualname__\n\n    def __str__(self):\n        return self.format_short()\n\n    def __repr__(self):\n        return self.format_oneline_tb(limit=None, include_str=True)",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 24,
                                "end_lineno": 152,
                                "parent": null
                            }
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 152,
                            "length": 6
                        },
                        "new_range": {
                            "start": 120,
                            "length": 71
                        },
                        "section": "class CapturedException(object):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        return self.format_oneline_tb(limit=None, include_str=True)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "def format_oneline_tb(exc, tb=None, limit=None, include_str=True):",
                                "type": "added"
                            },
                            {
                                "content": "    \"\"\"Format an exception traceback as a one-line summary",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    Parameters",
                                "type": "added"
                            },
                            {
                                "content": "    ----------",
                                "type": "added"
                            },
                            {
                                "content": "    exc: Exception",
                                "type": "added"
                            },
                            {
                                "content": "    tb: TracebackException, optional",
                                "type": "added"
                            },
                            {
                                "content": "      If not given, it is generated from the given exception.",
                                "type": "added"
                            },
                            {
                                "content": "    limit: int, optional",
                                "type": "added"
                            },
                            {
                                "content": "      Traceback depth limit. If not given, the config setting",
                                "type": "added"
                            },
                            {
                                "content": "      'datalad.exc.str.tblimit' will be used, or all entries",
                                "type": "added"
                            },
                            {
                                "content": "      are reported.",
                                "type": "added"
                            },
                            {
                                "content": "    include_str: bool",
                                "type": "added"
                            },
                            {
                                "content": "      If set, is True (default), the return value is prepended with a string",
                                "type": "added"
                            },
                            {
                                "content": "    representation of the exception.",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    Returns",
                                "type": "added"
                            },
                            {
                                "content": "    -------",
                                "type": "added"
                            },
                            {
                                "content": "    str",
                                "type": "added"
                            },
                            {
                                "content": "      Of format [filename:contextname:linenumber, ...].",
                                "type": "added"
                            },
                            {
                                "content": "    \"\"\"",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    # Note: No import at module level, since ConfigManager imports",
                                "type": "added"
                            },
                            {
                                "content": "    # dochelpers -> circular import when creating datalad.cfg instance at",
                                "type": "added"
                            },
                            {
                                "content": "    # startup.",
                                "type": "added"
                            },
                            {
                                "content": "    from datalad import cfg",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    if include_str:",
                                "type": "added"
                            },
                            {
                                "content": "        # try exc message else exception type",
                                "type": "added"
                            },
                            {
                                "content": "        leading = exc.message or exc.name",
                                "type": "added"
                            },
                            {
                                "content": "        out = \"{} \".format(leading)",
                                "type": "added"
                            },
                            {
                                "content": "    else:",
                                "type": "added"
                            },
                            {
                                "content": "        out = \"\"",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    if tb is None:",
                                "type": "added"
                            },
                            {
                                "content": "        tb = traceback.TracebackException.from_exception(",
                                "type": "added"
                            },
                            {
                                "content": "            exc,",
                                "type": "added"
                            },
                            {
                                "content": "            limit=limit,",
                                "type": "added"
                            },
                            {
                                "content": "            lookup_lines=True,",
                                "type": "added"
                            },
                            {
                                "content": "            capture_locals=False,",
                                "type": "added"
                            },
                            {
                                "content": "        )",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    entries = []",
                                "type": "added"
                            },
                            {
                                "content": "    entries.extend(tb.stack)",
                                "type": "added"
                            },
                            {
                                "content": "    if tb.__cause__:",
                                "type": "added"
                            },
                            {
                                "content": "        entries.extend(tb.__cause__.stack)",
                                "type": "added"
                            },
                            {
                                "content": "    elif tb.__context__ and not tb.__suppress_context__:",
                                "type": "added"
                            },
                            {
                                "content": "        entries.extend(tb.__context__.stack)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    if limit is None:",
                                "type": "added"
                            },
                            {
                                "content": "        limit = int(cfg.obtain('datalad.exc.str.tblimit',",
                                "type": "added"
                            },
                            {
                                "content": "                               default=len(entries)))",
                                "type": "added"
                            },
                            {
                                "content": "    if entries:",
                                "type": "added"
                            },
                            {
                                "content": "        tb_str = \"[%s]\" % (','.join(",
                                "type": "added"
                            },
                            {
                                "content": "            \"{}:{}:{}\".format(",
                                "type": "added"
                            },
                            {
                                "content": "                Path(frame_summary.filename).name,",
                                "type": "added"
                            },
                            {
                                "content": "                frame_summary.name,",
                                "type": "added"
                            },
                            {
                                "content": "                frame_summary.lineno)",
                                "type": "added"
                            },
                            {
                                "content": "            for frame_summary in entries[-limit:])",
                                "type": "added"
                            },
                            {
                                "content": "        )",
                                "type": "added"
                            },
                            {
                                "content": "        out += \"{}\".format(tb_str)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    return out",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "class MissingExternalDependency(RuntimeError):",
                                "type": "context"
                            },
                            {
                                "content": "    \"\"\"External dependency is missing error\"\"\"",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "datalad/support/exceptions.py",
                            "type": "function",
                            "name": "format_oneline_tb",
                            "content": "def format_oneline_tb(exc, tb=None, limit=None, include_str=True):\n    \"\"\"Format an exception traceback as a one-line summary\n\n    Parameters\n    ----------\n    exc: Exception\n    tb: TracebackException, optional\n      If not given, it is generated from the given exception.\n    limit: int, optional\n      Traceback depth limit. If not given, the config setting\n      'datalad.exc.str.tblimit' will be used, or all entries\n      are reported.\n    include_str: bool\n      If set, is True (default), the return value is prepended with a string\n    representation of the exception.\n\n    Returns\n    -------\n    str\n      Of format [filename:contextname:linenumber, ...].\n    \"\"\"\n\n    # Note: No import at module level, since ConfigManager imports\n    # dochelpers -> circular import when creating datalad.cfg instance at\n    # startup.\n    from datalad import cfg\n\n    if include_str:\n        # try exc message else exception type\n        leading = exc.message or exc.name\n        out = \"{} \".format(leading)\n    else:\n        out = \"\"\n\n    if tb is None:\n        tb = traceback.TracebackException.from_exception(\n            exc,\n            limit=limit,\n            lookup_lines=True,\n            capture_locals=False,\n        )\n\n    entries = []\n    entries.extend(tb.stack)\n    if tb.__cause__:\n        entries.extend(tb.__cause__.stack)\n    elif tb.__context__ and not tb.__suppress_context__:\n        entries.extend(tb.__context__.stack)\n\n    if limit is None:\n        limit = int(cfg.obtain('datalad.exc.str.tblimit',\n                               default=len(entries)))\n    if entries:\n        tb_str = \"[%s]\" % (','.join(\n            \"{}:{}:{}\".format(\n                Path(frame_summary.filename).name,\n                frame_summary.name,\n                frame_summary.lineno)\n            for frame_summary in entries[-limit:])\n        )\n        out += \"{}\".format(tb_str)\n\n    return out",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 123,
                            "end_lineno": 185,
                            "parent": null
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "615910dbe77da5573a05456e81b01a67da2a987a^",
    "new_commit_hash": "615910dbe77da5573a05456e81b01a67da2a987a",
    "commit_message": "Merge pull request #5642 from yarikoptic/enh-exc-exit-code\n\nENH: get_status_dict - add exit_code if CommandError",
    "commit_date": "2021-11-15T15:39:38+01:00",
    "metadata": {}
}
{
    "repo_name": "datalad",
    "new_commit_hash": "615910dbe77da5573a05456e81b01a67da2a987a",
    "test_file_codes": [
        "# 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\"\"\"Result utility tests\n\n\"\"\"\n\nfrom datalad.interface.results import (\n    annexjson2result,\n    get_status_dict,\n)\nfrom datalad.distribution.dataset import Dataset\nfrom datalad.runner import CommandError\nfrom datalad.tests.utils import (\n    eq_,\n    with_tempfile,\n)\n\n\n@ with_tempfile\ndef test_annexjson2result(dspath):\n    # no explicit success means 'error'\n    eq_(annexjson2result(dict(), None),\n        dict(status='error'))\n    # unrecognized -> error\n    eq_(annexjson2result(dict(success='random'), None),\n        dict(status='error'))\n    # success is possible ;-)\n    eq_(annexjson2result(dict(success=True), None),\n        dict(status='ok'))\n\n    # path handling\n    # needs a dataset\n    ds = Dataset(dspath)\n    eq_(annexjson2result(dict(file='file1'), ds),\n        dict(status='error',\n             path=str(ds.pathobj / 'file1')))\n    # on all platforms, paths are reported in platform conventions\n    # although git-annex reports in posix\n    eq_(annexjson2result(dict(file='dir1/file1'), ds),\n        dict(status='error',\n             path=str(ds.pathobj / 'dir1' / 'file1')))\n\n\ndef tests_status_dict_exit_code():\n    d = get_status_dict(exception=CommandError(code=105))\n    eq_(d['exit_code'], 105)\n",
        "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...\nDatalad version: 0.15.3+274.g615910dbe\n✅ Installation successful!\nSuccessfully installed Datalad using Python 3.9\n",
    "setup_res_stderr": "Using CPython 3.9.21\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 25 packages in 109ms\nInstalled 25 packages in 5.13s\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\nResolved 61 packages in 4.59s\nPrepared 1 package in 4.19s\nInstalled 55 packages in 166ms\n + annexremote==1.6.6\n + appdirs==1.4.4\n + argcomplete==3.5.3\n + backports-tarfile==1.2.0\n + beautifulsoup4==4.12.3\n + boto==2.49.0\n + certifi==2024.12.14\n + cffi==1.17.1\n + chardet==5.2.0\n + charset-normalizer==3.4.1\n + citeproc-py==0.6.0\n + cryptography==44.0.0\n + datalad==0.15.3+274.g615910dbe (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_615910dbe77da5573a05456e81b01a67da2a987a)\n + distro==1.9.0\n + duecredit==0.10.2\n + exifread==3.0.0\n + fasteners==0.19\n + httpretty==1.1.4\n + humanize==4.11.0\n + idna==3.10\n + importlib-metadata==8.5.0\n + iso8601==2.1.0\n + jaraco-classes==3.4.0\n + jaraco-context==6.0.1\n + jaraco-functools==4.1.0\n + jeepney==0.8.0\n + keyring==25.6.0\n + keyrings-alt==5.0.2\n + looseversion==1.3.0\n + lxml==5.3.0\n + more-itertools==10.5.0\n + msgpack==1.1.0\n + multidict==6.1.0\n + mutagen==1.47.0\n + nose==1.3.7\n + patool==1.15.0\n + pillow==11.0.0\n + propcache==0.2.1\n + pycparser==2.22\n + pyperclip==1.9.0\n + python-gitlab==5.3.0\n + python-xmp-toolkit==2.0.2\n + pyyaml==6.0.2\n + requests==2.32.3\n + requests-ftp==0.3.1\n + requests-toolbelt==1.0.0\n + secretstorage==3.3.3\n + simplejson==3.19.3\n + soupsieve==2.6\n + tqdm==4.67.1\n + urllib3==1.26.20\n + vcrpy==7.0.0\n + whoosh==2.7.4\n + wrapt==1.17.0\n + yarl==1.18.3\nResolved 95 packages in 209ms\nInstalled 32 packages in 555ms\n + alabaster==0.7.16\n + asv==0.6.4\n + asv-runner==0.2.1\n + babel==2.16.0\n + build==1.2.2.post1\n + distlib==0.3.9\n + docutils==0.21.2\n + filelock==3.16.1\n + gprof2dot==2024.6.6\n + imagesize==1.4.1\n + jinja2==3.1.5\n + json5==0.10.0\n + markupsafe==3.0.2\n + nose-timer==1.0.1\n + platformdirs==4.3.6\n + psutil==6.1.1\n + pygments==2.18.0\n + pympler==1.1\n + pypandoc==1.14\n + pyproject-hooks==1.2.0\n + snowballstemmer==2.2.0\n + sphinx==7.4.7\n + sphinx-rtd-theme==3.0.2\n + sphinxcontrib-applehelp==2.0.0\n + sphinxcontrib-devhelp==2.0.0\n + sphinxcontrib-htmlhelp==2.1.0\n + sphinxcontrib-jquery==4.1\n + sphinxcontrib-jsmath==1.0.1\n + sphinxcontrib-qthelp==2.0.0\n + sphinxcontrib-serializinghtml==2.0.0\n + tabulate==0.9.0\n + virtualenv==20.28.0\n",
    "new_commit_res_code": 1,
    "new_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.9.21, pytest-8.3.4, pluggy-1.5.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_615910dbe77da5573a05456e81b01a67da2a987a\nconfigfile: pyproject.toml\nplugins: cov-6.0.0\ncollected 2 items\n\nr2e_tests/test_1.py E.                                                   [100%]\n\n==================================== ERRORS ====================================\n___________________ ERROR at setup of test_annexjson2result ____________________\nfile /home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_615910dbe77da5573a05456e81b01a67da2a987a/r2e_tests/test_1.py, line 25\n  @ with_tempfile\n  def test_annexjson2result(dspath):\nE       fixture 'dspath' not found\n>       available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, cov, doctest_namespace, monkeypatch, no_cover, path, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, setup_git_config, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory\n>       use 'pytest --fixtures [testpath]' for help on them.\n\n/home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_615910dbe77da5573a05456e81b01a67da2a987a/r2e_tests/test_1.py:25\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::tests_status_dict_exit_code\nERROR r2e_tests/test_1.py::test_annexjson2result\n========================== 1 passed, 1 error in 1.91s ==========================\n",
    "new_commit_res_stderr": "",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.9.21, pytest-8.3.4, pluggy-1.5.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_615910dbe77da5573a05456e81b01a67da2a987a\nconfigfile: pyproject.toml\nplugins: cov-6.0.0\ncollected 2 items\n\nr2e_tests/test_1.py EF                                                   [100%]\n\n==================================== ERRORS ====================================\n___________________ ERROR at setup of test_annexjson2result ____________________\nfile /home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_615910dbe77da5573a05456e81b01a67da2a987a/r2e_tests/test_1.py, line 25\n  @ with_tempfile\n  def test_annexjson2result(dspath):\nE       fixture 'dspath' not found\n>       available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, cov, doctest_namespace, monkeypatch, no_cover, path, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, setup_git_config, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory\n>       use 'pytest --fixtures [testpath]' for help on them.\n\n/home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_615910dbe77da5573a05456e81b01a67da2a987a/r2e_tests/test_1.py:25\n=================================== FAILURES ===================================\n_________________________ tests_status_dict_exit_code __________________________\n\n    def tests_status_dict_exit_code():\n>       d = get_status_dict(exception=CommandError(code=105))\n\nr2e_tests/test_1.py:51: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\naction = None, ds = None, path = None, type = None, logger = None, refds = None\nstatus = None, message = None, exception = CommandError('')\nerror_message = None, kwargs = {}, d = {'exception': CommandError('')}\n\n    def get_status_dict(action=None, ds=None, path=None, type=None, logger=None,\n                        refds=None, status=None, message=None, exception=None,\n                        error_message=None, **kwargs):\n        # `type` is intentionally not `type_` or something else, as a mismatch\n        # with the dict key 'type' causes too much pain all over the place\n        # just for not shadowing the builtin `type` in this function\n        \"\"\"Helper to create a result dictionary.\n    \n        Most arguments match their key in the resulting dict, and their given\n        values are simply assigned to the result record under these keys.  Only\n        exceptions are listed here.\n    \n        Parameters\n        ----------\n        ds : Dataset instance\n          If given, the `path` and `type` values are populated with the path of the\n          datasets and 'dataset' as the type. Giving additional values for both\n          keys will overwrite these pre-populated values.\n        exception : CapturedException\n          Exceptions that occurred while generating a result should be captured\n          by immediately instantiating a CapturedException. This instance can\n          be passed here to yield more comprehensive error reporting, including\n          an auto-generated traceback (added to the result record under an\n          'exception_traceback' key).\n    \n        Returns\n        -------\n        dict\n        \"\"\"\n    \n        d = {}\n        if action is not None:\n            d['action'] = action\n        if ds:\n            d['path'] = ds.path\n            d['type'] = 'dataset'\n        # now overwrite automatic\n        if path is not None:\n            d['path'] = path\n        if type:\n            d['type'] = type\n        if logger:\n            d['logger'] = logger\n        if refds:\n            d['refds'] = refds\n        if status is not None:\n            # TODO check for known status label\n            d['status'] = status\n        if message is not None:\n            d['message'] = message\n        if error_message is not None:\n            d['error_message'] = error_message\n        if exception is not None:\n            d['exception'] = exception\n            d['exception_traceback'] = \\\n>               exception.format_oneline_tb(limit=1000, include_str=False)\nE           AttributeError: 'CommandError' object has no attribute 'format_oneline_tb'\n\ndatalad/interface/results.py:102: AttributeError\n=========================== short test summary info ============================\nERROR r2e_tests/test_1.py::test_annexjson2result\nFAILED r2e_tests/test_1.py::tests_status_dict_exit_code - AttributeError: 'Co...\n========================== 1 failed, 1 error in 0.58s ==========================\n",
    "old_commit_res_stderr": ""
}
datalad/interface/results.py datalad/interface/tests/test_results.py datalad/support/exceptions.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** AttributeError When Handling `CommandError` in `get_status_dict`

**Description:**
When a `CommandError` exception is passed to the `get_status_dict` function, it raises an `AttributeError` because the `CommandError` object does not have the `format_oneline_tb` method. This prevents the function from correctly adding the `exit_code` to the status dictionary.

**Example Code:**
```python
from datalad.interface.results import get_status_dict
from datalad.runner import CommandError

# Attempt to create a status dictionary with a CommandError
d = get_status_dict(exception=CommandError(code=105))
print(d['exit_code'])
```

**Expected Behavior:**
The `get_status_dict` function should successfully add the `exit_code` key to the returned dictionary with the value `105`.

**Actual Behavior:**
An `AttributeError` is raised:
```
AttributeError: 'CommandError' object has no attribute 'format_oneline_tb'
```
This error occurs because the function attempts to call `format_oneline_tb` on the `CommandError` instance, which does not have this method.

[/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/interface/results.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.
#
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""Interface result handling functions

"""

__docformat__ = 'restructuredtext'

import logging

from os.path import (
    isabs,
    isdir,
    join as opj,
    normpath,
    relpath,
)

from datalad.utils import (
    ensure_list,
    path_is_subpath,
    PurePosixPath,
)
from datalad.support.path import robust_abspath

from datalad.distribution.dataset import Dataset


lgr = logging.getLogger('datalad.interface.results')
lgr.log(5, "Importing datalad.interface.results")

# which status is a success , which is failure
success_status_map = {
    'ok': 'success',
    'notneeded': 'success',
    'impossible': 'failure',
    'error': 'failure',
}


def get_status_dict(action=None, ds=None, path=None, type=None, logger=None,
                    refds=None, status=None, message=None, exception=None,
                    error_message=None, **kwargs):
    # `type` is intentionally not `type_` or something else, as a mismatch
    # with the dict key 'type' causes too much pain all over the place
    # just for not shadowing the builtin `type` in this function
    """Helper to create a result dictionary.

    Most arguments match their key in the resulting dict, and their given
    values are simply assigned to the result record under these keys.  Only
    exceptions are listed here.

    Parameters
    ----------
    ds : Dataset instance
      If given, the `path` and `type` values are populated with the path of the
      datasets and 'dataset' as the type. Giving additional values for both
      keys will overwrite these pre-populated values.
    exception : CapturedException
      Exceptions that occurred while generating a result should be captured
      by immediately instantiating a CapturedException. This instance can
      be passed here to yield more comprehensive error reporting, including
      an auto-generated traceback (added to the result record under an
      'exception_traceback' key).

    Returns
    -------
    dict
    """

    d = {}
    if action is not None:
        d['action'] = action
    if ds:
        d['path'] = ds.path
        d['type'] = 'dataset'
    # now overwrite automatic
    if path is not None:
        d['path'] = path
    if type:
        d['type'] = type
    if logger:
        d['logger'] = logger
    if refds:
        d['refds'] = refds
    if status is not None:
        # TODO check for known status label
        d['status'] = status
    if message is not None:
        d['message'] = message
    if error_message is not None:
        d['error_message'] = error_message
    if exception is not None:
        d['exception'] = exception
        d['exception_traceback'] = \
            exception.format_oneline_tb(limit=1000, include_str=False)
    if kwargs:
        d.update(kwargs)
    return d


def results_from_paths(paths, action=None, type=None, logger=None, refds=None,
                       status=None, message=None, exception=None):
    """
    Helper to yield analog result dicts for each path in a sequence.

    Parameters
    ----------
    message: str
      A result message. May contain `%s` which will be replaced by the
      respective `path`.

    Returns
    -------
    generator

    """
    for p in ensure_list(paths):
        yield get_status_dict(
            action, path=p, type=type, logger=logger, refds=refds,
            status=status, message=(message, p) if '%s' in message else message,
            exception=exception
        )


def is_ok_dataset(r):
    """Convenience test for a non-failure dataset-related result dict"""
    return r.get('status', None) == 'ok' and r.get('type', None) == 'dataset'


class ResultXFM(object):
    """Abstract definition of the result transformer API"""
    def __call__(self, res):
        """This is called with one result dict at a time"""
        raise NotImplementedError


class YieldDatasets(ResultXFM):
    """Result transformer to return a Dataset instance from matching result.

    If the `success_only` flag is given only dataset with 'ok' or 'notneeded'
    status are returned'.

    `None` is returned for any other result.
    """
    def __init__(self, success_only=False):
        self.success_only = success_only

    def __call__(self, res):
        if res.get('type', None) == 'dataset':
            if not self.success_only or \
                    res.get('status', None) in ('ok', 'notneeded'):
                return Dataset(res['path'])
        else:
            lgr.debug('rejected by return value configuration: %s', res)


class YieldRelativePaths(ResultXFM):
    """Result transformer to return relative paths for a result

    Relative paths are determined from the 'refds' value in the result. If
    no such value is found, `None` is returned.
    """
    def __call__(self, res):
        refpath = res.get('refds', None)
        if refpath:
            return relpath(res['path'], start=refpath)


class YieldField(ResultXFM):
    """Result transformer to return an arbitrary value from a result dict"""
    def __init__(self, field):
        """
        Parameters
        ----------
        field : str
          Key of the field to return.
        """
        self.field = field

    def __call__(self, res):
        if self.field in res:
            return res[self.field]
        else:
            lgr.debug('rejected by return value configuration: %s', res)


# a bunch of convenience labels for common result transformers
# the API `result_xfm` argument understand any of these labels and
# applied the corresponding callable
known_result_xfms = {
    'datasets': YieldDatasets(),
    'successdatasets-or-none': YieldDatasets(success_only=True),
    'paths': YieldField('path'),
    'relpaths': YieldRelativePaths(),
    'metadata': YieldField('metadata'),
}

translate_annex_notes = {
    '(Use --force to override this check, or adjust numcopies.)':
        'configured minimum number of copies not found',
}


def annexjson2result(d, ds, **kwargs):
    """Helper to convert an annex JSON result to a datalad result dict

    Info from annex is rather heterogeneous, partly because some of it
    our support functions are faking.

    This helper should be extended with all needed special cases to
    homogenize the information.

    Parameters
    ----------
    d : dict
      Annex info dict.
    ds : Dataset instance
      Used to determine absolute paths for `file` results. This dataset
      is not used to set `refds` in the result, pass this as a separate
      kwarg if needed.
    **kwargs
      Passes as-is to `get_status_dict`. Must not contain `refds`.
    """
    lgr.debug('received JSON result from annex: %s', d)
    messages = []
    res = get_status_dict(**kwargs)
    res['status'] = 'ok' if d.get('success', False) is True else 'error'
    # we cannot rely on any of these to be available as the feed from
    # git annex (or its wrapper) is not always homogeneous
    if d.get('file'):
        res['path'] = str(ds.pathobj / PurePosixPath(d['file']))
    if 'command' in d:
        res['action'] = d['command']
    if 'key' in d:
        res['annexkey'] = d['key']
    if 'fields' in d:
        # this is annex metadata, filter out timestamps
        res['metadata'] = {k: v[0] if isinstance(v, list) and len(v) == 1 else v
                           for k, v in d['fields'].items()
                           if not k.endswith('lastchanged')}
    if d.get('error-messages', None):
        res['error_message'] = '\n'.join(m.strip() for m in d['error-messages'])
    # avoid meaningless standard messages, and collision with actual error
    # messages
    elif 'note' in d:
        note = "; ".join(ln for ln in d['note'].splitlines()
                         if ln != 'checksum...'
                         and not ln.startswith('checking file'))
        if note:
            messages.append(translate_annex_notes.get(note, note))
    if messages:
        res['message'] = '\n'.join(m.strip() for m in messages)
    return res


def count_results(res, **kwargs):
    """Return number if results that match all property values in kwargs"""
    return sum(
        all(k in r and r[k] == v for k, v in kwargs.items()) for r in res)


def only_matching_paths(res, **kwargs):
    # TODO handle relative paths by using a contained 'refds' value
    paths = ensure_list(kwargs.get('path', []))
    respath = res.get('path', None)
    return respath in paths


# needs decorator, as it will otherwise bind to the command classes that use it
@staticmethod
def is_result_matching_pathsource_argument(res, **kwargs):
    # we either have any non-zero number of "paths" (that could be anything), or
    # we have one path and one source
    # we don't do any error checking here, done by the command itself
    if res.get('action', None) not in ('install', 'get'):
        # this filter is only used in install, reject anything that comes
        # in that could not possibly be a 'install'-like result
        # e.g. a sibling being added in the process
        return False
    source = kwargs.get('source', None)
    if source is not None:
        # we want to be able to deal with Dataset instances given as 'source':
        if isinstance(source, Dataset):
            source = source.path
        # if there was a source, it needs to be recorded in the result
        # otherwise this is not what we are looking for
        return source == res.get('source_url', None)
    # the only thing left is a potentially heterogeneous list of paths/URLs
    paths = ensure_list(kwargs.get('path', []))
    # three cases left:
    # 1. input arg was an absolute path -> must match 'path' property
    # 2. input arg was relative to a dataset -> must match refds/relpath
    # 3. something nifti with a relative input path that uses PWD as the
    #    reference
    respath = res.get('path', None)
    if respath in paths:
        # absolute match, pretty sure we want this
        return True
    elif isinstance(kwargs.get('dataset', None), Dataset) and \
            YieldRelativePaths()(res) in paths:
        # command was called with a reference dataset, and a relative
        # path of a result matches in input argument -- not 100% exhaustive
        # test, but could be good enough
        return True
    elif any(robust_abspath(p) == respath for p in paths):
        # one absolutified input path matches the result path
        # I'd say: got for it!
        return True
    elif any(p == res.get('source_url', None) for p in paths):
        # this was installed from a URL that was given, we'll take that too
        return True
    else:
        return False


def results_from_annex_noinfo(ds, requested_paths, respath_by_status, dir_fail_msg,
                              noinfo_dir_msg, noinfo_file_msg, noinfo_status='notneeded',
                              **kwargs):
    """Helper to yield results based on what information git annex did no give us.

    The helper assumes that the annex command returned without an error code,
    and interprets which of the requested paths we have heard nothing about,
    and assumes that git annex was happy with their current state.

    Parameters
    ==========
    ds : Dataset
      All results have to be concerning this single dataset (used to resolve
      relpaths).
    requested_paths : list
      List of path arguments sent to `git annex`
    respath_by_status : dict
      Mapping of 'success' or 'failure' labels to lists of result paths
      reported by `git annex`. Everything that is not in here, we assume
      that `git annex` was happy about.
    dir_fail_msg : str
      Message template to inject into the result for a requested directory where
      a failure was reported for some of its content. The template contains two
      string placeholders that will be expanded with 1) the path of the
      directory, and 2) the content failure paths for that directory
    noinfo_dir_msg : str
      Message template to inject into the result for a requested directory that
      `git annex` was silent about (incl. any content). There must be one string
      placeholder that is expanded with the path of that directory.
    noinfo_file_msg : str
      Message to inject into the result for a requested file that `git
      annex` was silent about.
    noinfo_status : str
      Status to report when annex provides no information
    **kwargs
      Any further kwargs are included in the yielded result dictionary.
    """
    for p in requested_paths:
        # any relpath is relative to the currently processed dataset
        # not the global reference dataset
        p = p if isabs(p) else normpath(opj(ds.path, p))
        if any(p in ps for ps in respath_by_status.values()):
            # we have a report for this path already
            continue
        common_report = dict(path=p, **kwargs)
        if isdir(p):
            # `annex` itself will not report on directories, but if a
            # directory was requested, we want to say something about
            # it in the results.  we are inside a single, existing
            # repo, hence all directories are already present, if not
            # we had an error
            # do we have any failures in a subdir of the requested dir?
            failure_results = [
                fp for fp in respath_by_status.get('failure', [])
                if path_is_subpath(fp, p)]
            if failure_results:
                # we were not able to process all requested_paths, let's label
                # this 'impossible' to get a warning-type report
                # after all we have the directory itself, but not
                # (some) of its requested_paths
                yield get_status_dict(
                    status='impossible', type='directory',
                    message=(dir_fail_msg, p, failure_results),
                    **common_report)
            else:
                # otherwise cool, but how cool?
                success_results = [
                    fp for fp in respath_by_status.get('success', [])
                    if path_is_subpath(fp, p)]
                yield get_status_dict(
                    status='ok' if success_results else noinfo_status,
                    message=None if success_results else (noinfo_dir_msg, p),
                    type='directory', **common_report)
            continue
        else:
            # not a directory, and we have had no word from `git annex`,
            # yet no exception, hence the file was most probably
            # already in the desired state
            yield get_status_dict(
                status=noinfo_status, type='file',
                message=noinfo_file_msg,
                **common_report)


lgr.log(5, "Done importing datalad.interface.results")


### datalad/support/exceptions.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.
#
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
""" datalad exceptions
"""

import logging
import re
import traceback
from os import linesep
from pathlib import Path
from pprint import pformat

from datalad.runner.exception import CommandError

lgr = logging.getLogger('datalad.support.exceptions')


class CapturedException(object):
    """This class represents information about an occurred exception (including
    its traceback), while not holding any references to the actual exception
    object or its traceback, frame references, etc.

    Just keep the textual information for logging or whatever other kind of
    reporting.
    """

    def __init__(self, exc, limit=None, capture_locals=False,
                 level=8, logger=None):
        """Capture an exception and its traceback for logging.

        Clears the exception's traceback frame references afterwards.

        Parameters
        ----------
        exc: Exception
        limit: int
          Note, that this is limiting the capturing of the exception's
          traceback depth. Formatting for output comes with it's own limit.
        capture_locals: bool
          Whether or not to capture the local context of traceback frames.
        """
        # Note, that with lookup_lines=False the lookup is deferred,
        # not disabled. Unclear to me ATM, whether that means to keep frame
        # references around, but prob. not. TODO: Test that.
        self.tb = traceback.TracebackException.from_exception(
            exc,
            limit=limit,
            lookup_lines=True,
            capture_locals=capture_locals
        )
        traceback.clear_frames(exc.__traceback__)

        # log the captured exception
        logger = logger or lgr
        logger.log(level, "%r", self)

    def format_oneline_tb(self, limit=None, include_str=True):
        """Format an exception traceback as a one-line summary

        Returns a string of the form [filename:contextname:linenumber, ...].
        If include_str is True (default), this is prepended with the string
        representation of the exception.
        """

        # Note: No import at module level, since ConfigManager imports
        # dochelpers -> circular import when creating datalad.cfg instance at
        # startup.
        from datalad import cfg

        if include_str:
            # try exc message else exception type
            leading = self.message or self.name
            out = "{} ".format(leading)
        else:
            out = ""

        entries = []
        entries.extend(self.tb.stack)
        if self.tb.__cause__:
            entries.extend(self.tb.__cause__.stack)
        elif self.tb.__context__ and not self.tb.__suppress_context__:
            entries.extend(self.tb.__context__.stack)

        if limit is None:
            limit = int(cfg.obtain('datalad.exc.str.tblimit',
                                   default=len(entries)))
        if entries:
            tb_str = "[%s]" % (','.join(
                "{}:{}:{}".format(
                    Path(frame_summary.filename).name,
                    frame_summary.name,
                    frame_summary.lineno)
                for frame_summary in entries[-limit:])
            )
            out += "{}".format(tb_str)

        return out

    def format_standard(self):
        """Returns python's standard formatted traceback output

        Returns
        -------
        str
        """
        # TODO: Intended for introducing a decent debug mode later when this
        #       can be used fromm within log formatter / result renderer.
        #       For now: a one-liner is free
        return ''.join(self.tb.format())

    def format_short(self):
        """Returns a short representation of the original exception

        Form: ExceptionName(exception message)

        Returns
        -------
        str
        """
        return self.name + '(' + self.message + ')'

    @property
    def message(self):
        """Returns only the message of the original exception

        Returns
        -------
        str
        """
        return str(self.tb)

    @property
    def name(self):
        """Returns the class name of the original exception

        Returns
        -------
        str
        """
        return self.tb.exc_type.__qualname__

    def __str__(self):
        return self.format_short()

    def __repr__(self):
        return self.format_oneline_tb(limit=None, include_str=True)


class MissingExternalDependency(RuntimeError):
    """External dependency is missing error"""

    def __init__(self, name, ver=None, msg=""):
        super(MissingExternalDependency, self).__init__()
        self.name = name
        self.ver = ver
        self.msg = msg

    def __str__(self):
        to_str = 'No working {} installation'.format(self.name)
        if self.ver:
            to_str += " of version >= %s" % self.ver
        to_str += "."
        if self.msg:
            to_str += " %s" % self.msg
        return to_str


class BrokenExternalDependency(RuntimeError):
    """Some particular functionality is broken with this dependency."""


class DeprecatedError(RuntimeError):
    """To raise whenever a deprecated entirely feature is used"""
    def __init__(self, new=None, version=None, msg=''):
        """

        Parameters
        ----------
        new : str, optional
          What new construct to use
        version : str, optional
          Since which version is deprecated
        kwargs
        """
        super(DeprecatedError, self).__init__()
        self.version = version
        self.new = new
        self.msg = msg

    def __str__(self):
        s = self.msg if self.msg else ''
        if self.version:
            s += (" is deprecated" if s else "Deprecated") + " since version %s." % self.version
        if self.new:
            s += " Use %s instead." % self.new
        return s


class OutdatedExternalDependency(MissingExternalDependency):
    """External dependency is present but outdated"""

    def __init__(self, name, ver=None, ver_present=None, msg=""):
        super(OutdatedExternalDependency, self).__init__(name, ver=ver, msg=msg)
        self.ver_present = ver_present

    def __str__(self):
        to_str = super(OutdatedExternalDependency, self).__str__()
        # MissingExternalDependency ends with a period unless msg is
        # given, in which case it's up to the msg and no callers in
        # our code base currently give a msg ending with a period.
        to_str += "." if self.msg else ""
        to_str += " You have version %s" % self.ver_present \
            if self.ver_present else \
            " Some unknown version of dependency found."
        return to_str


class AnnexBatchCommandError(CommandError):
    """Thrown if a batched command to annex fails

    """
    pass


class CommandNotAvailableError(CommandError):
    """Thrown if a command is not available due to certain circumstances.
    """
    pass


class FileNotInAnnexError(IOError, CommandError):
    """Thrown if a file is not under control of git-annex.
    """
    def __init__(self, cmd="", msg="", code=None, filename=""):
        CommandError.__init__(self, cmd=cmd, msg=msg, code=code)
        IOError.__init__(self, code, "%s: %s" % (cmd, msg), filename)

    def to_str(self, include_output=True):
        return "%s\n%s" % (
            CommandError.to_str(self, include_output=include_output),
            IOError.__str__(self))


class FileInGitError(FileNotInAnnexError):
    """Thrown if a file is not under control of git-annex, but git itself.
    """
    pass


class FileNotInRepositoryError(FileNotInAnnexError):
    """Thrown if a file is not under control of the repository at all.
    """
    pass


class InvalidGitReferenceError(ValueError):
    """Thrown if provided git reference is invalid
    """
    def __init__(self, ref, *args, **kwargs):
        super(InvalidGitReferenceError, self).__init__(*args, **kwargs)
        self.ref = ref

    def __str__(self):
        return u"Git reference '{}' invalid".format(self.ref)


class GitIgnoreError(CommandError):
    """Thrown if a path was ignored by a git command due to .gitignore file

    Note, that this might be thrown to indicate what was ignored, while the
    actual operation was partially successful (regarding paths, not in .gitignore)

    Note/Todo:
    in case of a directory being ignored, git returns that directory as the
    ignored path, even if a path within that directory was passed to the command.
    That means, that in such cases the returned path might not match an item you
    passed!
    """

    pattern = \
        re.compile(r'ignored by one of your .gitignore files:\s*(.*)'
                   r'^(?:hint: )?Use -f.*$',
                   flags=re.MULTILINE | re.DOTALL)

    def __init__(self, cmd="", msg="", code=None, stdout="", stderr="",
                 paths=None):
        super(GitIgnoreError, self).__init__(
            cmd=cmd, msg=msg, code=code, stdout=stdout, stderr=stderr)
        self.paths = paths

    def to_str(self, include_output=True):
        # Override CommandError.to_str(), ignoring include_output.
        return self.msg


class PathOutsideRepositoryError(Exception):
    """Thrown if a path points outside the repository that was requested to
    deal with that path."""

    # TODO: use it in GitRepo/AnnexRepo!
    def __init__(self, file_, repo):
        self.file_ = file_
        self.repo = repo

    def __str__(self):
        return "path {0} not within repository {1}".format(self.file_, self.repo)


class PathKnownToRepositoryError(Exception):
    """Thrown if file/path is under Git control, and attempted operation
    must not be ran"""
    pass


class GitError(Exception):
    """ Base class for all package exceptions """


class NoSuchPathError(GitError, OSError):
    """ Thrown if a path could not be access by the system. """


class MissingBranchError(Exception):
    """Thrown if accessing a repository's branch, that is not available"""

    def __init__(self, repo, branch, available_branches=None, msg=None):
        self.repo = repo
        self.branch = branch
        self.branches = available_branches
        if msg is None:
            self.msg = "branch '{0}' missing in {1}." \
                       "".format(self.branch, self.repo)
            if self.branches:
                self.msg += " Available branches: {0}".format(self.branches)
        else:
            self.msg = msg

    def __str__(self):
        return self.msg


class InsufficientArgumentsError(ValueError):
    """To be raise instead of `ValueError` when use help output is desired"""
    pass


class NoDatasetArgumentFound(InsufficientArgumentsError):
    """To be raised when expecting having a dataset but none was provided"""
    pass


class NoDatasetFound(NoDatasetArgumentFound):
    """Raised whenever a dataset is required, but none could be determined"""
    pass


class OutOfSpaceError(CommandError):
    """To be raised whenever a command fails if we have no sufficient space

    Example is  annex get command
    """

    def __init__(self, sizemore_msg=None, **kwargs):
        super(OutOfSpaceError, self).__init__(**kwargs)
        self.sizemore_msg = sizemore_msg

    def to_str(self, include_output=True):
        super_str = super().to_str(
            include_output=include_output).rstrip(linesep + '.')
        return "%s needs %s more" % (super_str, self.sizemore_msg)


class RemoteNotAvailableError(CommandError):
    """To be raised whenever a required remote is not available

    Example is "annex get somefile --from=MyRemote",
    where 'MyRemote' doesn't exist.
    """

    def __init__(self, remote, **kwargs):
        """

        Parameters
        ----------
        remote: str
          name of the remote
        kwargs:
          arguments from CommandError
        """
        super(RemoteNotAvailableError, self).__init__(**kwargs)
        self.remote = remote

    def to_str(self, include_output=True):
        super_str = super().to_str(include_output=include_output)
        return "Remote '{0}' is not available. Command failed:{1}{2}" \
               "".format(self.remote, linesep, super_str)


class InvalidInstanceRequestError(RuntimeError):
    """Thrown if a request to create a (flyweight) instance is invalid"""

    def __init__(self, id_, msg=None):
        super(InvalidInstanceRequestError, self).__init__(msg)
        self.id = id_
        self.msg = msg


class InvalidGitRepositoryError(GitError):
    """ Thrown if the given repository appears to have an invalid format.  """


class InvalidAnnexRepositoryError(RuntimeError):
    """Thrown if AnnexRepo was instantiated on a non-annex and
    without init=True"""


class DirectModeNoLongerSupportedError(NotImplementedError):
    """direct mode is no longer supported"""

    def __init__(self, repo, msg=None):
        super(DirectModeNoLongerSupportedError, self).__init__(
            ((" " + msg + ", but ") if msg else '')
            +
             "direct mode of operation is being deprecated in git-annex and "
             "no longer supported by DataLad. "
             "Please use 'git annex upgrade' under %s to upgrade your direct "
             "mode repository to annex v6 (or later)." % repo.path
            )
        self.repo = repo  # might come handy


class IncompleteResultsError(RuntimeError):
    """Exception to be raised whenever results are incomplete.

    Any results produced nevertheless are to be passed as `results`,
    and become available via the `results` attribute.
    """
    # TODO passing completed results doesn't fit in a generator paradigm
    # such results have been yielded already at the time this exception is
    # raised, little point in collecting them just for the sake of a possible
    # exception
    # MIH+YOH: AnnexRepo.copy_to and @eval_results are the last
    # remaining user of this functionality.
    # General use (as in AnnexRepo) of it discouraged but use in @eval_results
    # is warranted
    def __init__(self, results=None, failed=None, msg=None):
        super(IncompleteResultsError, self).__init__(msg)
        self.results = results
        self.failed = failed

    def __str__(self):
        super_str = super(IncompleteResultsError, self).__str__()
        return "{}{}{}".format(
            super_str,
            ". {} result(s)".format(len(self.results)) if self.results else "",
            ". {} failed:{}{}".format(
                len(self.failed),
                linesep,
                pformat(self.failed)) if self.failed else "")


class InstallFailedError(CommandError):
    """Generic exception to raise whenever `install` command fails"""
    pass


class ConnectionOpenFailedError(CommandError):
    """Exception to raise whenever opening a network connection fails"""
    pass
#
# Downloaders
#


class DownloadError(Exception):
    pass


class IncompleteDownloadError(DownloadError):
    pass


class UnaccountedDownloadError(IncompleteDownloadError):
    pass


class TargetFileAbsent(DownloadError):
    pass


class AccessDeniedError(DownloadError):
    def __init__(self, msg=None, supported_types=None, **kwargs):
        super(AccessDeniedError, self).__init__(msg, **kwargs)
        self.supported_types = supported_types


class AnonymousAccessDeniedError(AccessDeniedError):
    pass


class AccessPermissionExpiredError(AccessDeniedError):
    """To raise when there is a belief that it is due to expiration of a credential

    which we might possibly be able to refresh, like in the case of CompositeCredential
    """
    pass


class AccessFailedError(DownloadError):
    pass


class UnhandledRedirectError(DownloadError):
    def __init__(self, msg=None, url=None, **kwargs):
        super(UnhandledRedirectError, self).__init__(msg, **kwargs)
        self.url = url

#
# Crawler
#


class CrawlerError(Exception):
    pass


class PipelineNotSpecifiedError(CrawlerError):
    pass


#
# Warnings
#

class DataLadWarning(Warning):
    pass


# We have an exception OutdatedExternalDependency, but it is intended for
# an instance being raised.  `warnings` module requires a class to be provided
# as a category, so here is a dedicated Warning class
class OutdatedExternalDependencyWarning(DataLadWarning):
    """Warning "category" to use to report about outdated"""
    pass

```
--- 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:** AttributeError When Handling `CommandError` in `get_status_dict`

**Description:**
When a `CommandError` exception is passed to the `get_status_dict` function, it raises an `AttributeError` because the `CommandError` object does not have the `format_oneline_tb` method. This prevents the function from correctly adding the `exit_code` to the status dictionary.

**Example Code:**
```python
from datalad.interface.results import get_status_dict
from datalad.runner import CommandError

# Attempt to create a status dictionary with a CommandError
d = get_status_dict(exception=CommandError(code=105))
print(d['exit_code'])
```

**Expected Behavior:**
The `get_status_dict` function should successfully add the `exit_code` key to the returned dictionary with the value `105`.

**Actual Behavior:**
An `AttributeError` is raised:
```
AttributeError: 'CommandError' object has no attribute 'format_oneline_tb'
```
This error occurs because the function attempts to call `format_oneline_tb` on the `CommandError` instance, which does not have this method.

[/ISSUE]
{
    "tests_status_dict_exit_code": "PASSED",
    "test_annexjson2result": "ERROR"
}
diff --git a/datalad/interface/results.py b/datalad/interface/results.py
index 259d80ab7..f4afe1c27 100644
--- a/datalad/interface/results.py
+++ b/datalad/interface/results.py
@@ -27,8 +27,12 @@ from datalad.utils import (
     path_is_subpath,
     PurePosixPath,
 )
+from datalad.support.exceptions import CommandError
 from datalad.support.path import robust_abspath
-
+from datalad.support.exceptions import (
+    format_oneline_tb,
+    CapturedException,
+)
 from datalad.distribution.dataset import Dataset
 
 
@@ -62,12 +66,12 @@ def get_status_dict(action=None, ds=None, path=None, type=None, logger=None,
       If given, the `path` and `type` values are populated with the path of the
       datasets and 'dataset' as the type. Giving additional values for both
       keys will overwrite these pre-populated values.
-    exception : CapturedException
+    exception : Exception
       Exceptions that occurred while generating a result should be captured
       by immediately instantiating a CapturedException. This instance can
       be passed here to yield more comprehensive error reporting, including
       an auto-generated traceback (added to the result record under an
-      'exception_traceback' key).
+      'exception_traceback' key). Exceptions of other types are also supported.
 
     Returns
     -------
@@ -98,8 +102,13 @@ def get_status_dict(action=None, ds=None, path=None, type=None, logger=None,
         d['error_message'] = error_message
     if exception is not None:
         d['exception'] = exception
-        d['exception_traceback'] = \
-            exception.format_oneline_tb(limit=1000, include_str=False)
+        d['exception_traceback'] = exception.format_oneline_tb(
+            include_str=False) \
+            if isinstance(exception, CapturedException) \
+            else format_oneline_tb(
+                exception, include_str=False)
+        if isinstance(exception, CommandError):
+            d['exit_code'] = exception.code
     if kwargs:
         d.update(kwargs)
     return d
diff --git a/datalad/support/exceptions.py b/datalad/support/exceptions.py
index 8218627a7..9cd5d11a6 100644
--- a/datalad/support/exceptions.py
+++ b/datalad/support/exceptions.py
@@ -67,40 +67,8 @@ class CapturedException(object):
         If include_str is True (default), this is prepended with the string
         representation of the exception.
         """
-
-        # Note: No import at module level, since ConfigManager imports
-        # dochelpers -> circular import when creating datalad.cfg instance at
-        # startup.
-        from datalad import cfg
-
-        if include_str:
-            # try exc message else exception type
-            leading = self.message or self.name
-            out = "{} ".format(leading)
-        else:
-            out = ""
-
-        entries = []
-        entries.extend(self.tb.stack)
-        if self.tb.__cause__:
-            entries.extend(self.tb.__cause__.stack)
-        elif self.tb.__context__ and not self.tb.__suppress_context__:
-            entries.extend(self.tb.__context__.stack)
-
-        if limit is None:
-            limit = int(cfg.obtain('datalad.exc.str.tblimit',
-                                   default=len(entries)))
-        if entries:
-            tb_str = "[%s]" % (','.join(
-                "{}:{}:{}".format(
-                    Path(frame_summary.filename).name,
-                    frame_summary.name,
-                    frame_summary.lineno)
-                for frame_summary in entries[-limit:])
-            )
-            out += "{}".format(tb_str)
-
-        return out
+        return format_oneline_tb(
+            self, self.tb, limit=limit, include_str=include_str)
 
     def format_standard(self):
         """Returns python's standard formatted traceback output
@@ -152,6 +120,71 @@ class CapturedException(object):
         return self.format_oneline_tb(limit=None, include_str=True)
 
 
+def format_oneline_tb(exc, tb=None, limit=None, include_str=True):
+    """Format an exception traceback as a one-line summary
+
+    Parameters
+    ----------
+    exc: Exception
+    tb: TracebackException, optional
+      If not given, it is generated from the given exception.
+    limit: int, optional
+      Traceback depth limit. If not given, the config setting
+      'datalad.exc.str.tblimit' will be used, or all entries
+      are reported.
+    include_str: bool
+      If set, is True (default), the return value is prepended with a string
+    representation of the exception.
+
+    Returns
+    -------
+    str
+      Of format [filename:contextname:linenumber, ...].
+    """
+
+    # Note: No import at module level, since ConfigManager imports
+    # dochelpers -> circular import when creating datalad.cfg instance at
+    # startup.
+    from datalad import cfg
+
+    if include_str:
+        # try exc message else exception type
+        leading = exc.message or exc.name
+        out = "{} ".format(leading)
+    else:
+        out = ""
+
+    if tb is None:
+        tb = traceback.TracebackException.from_exception(
+            exc,
+            limit=limit,
+            lookup_lines=True,
+            capture_locals=False,
+        )
+
+    entries = []
+    entries.extend(tb.stack)
+    if tb.__cause__:
+        entries.extend(tb.__cause__.stack)
+    elif tb.__context__ and not tb.__suppress_context__:
+        entries.extend(tb.__context__.stack)
+
+    if limit is None:
+        limit = int(cfg.obtain('datalad.exc.str.tblimit',
+                               default=len(entries)))
+    if entries:
+        tb_str = "[%s]" % (','.join(
+            "{}:{}:{}".format(
+                Path(frame_summary.filename).name,
+                frame_summary.name,
+                frame_summary.lineno)
+            for frame_summary in entries[-limit:])
+        )
+        out += "{}".format(tb_str)
+
+    return out
+
+
 class MissingExternalDependency(RuntimeError):
     """External dependency is missing error"""
 
 

{"datalad/interface/results.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\"\"\"Interface result handling functions\n\n\"\"\"\n\n__docformat__ = 'restructuredtext'\n\nimport logging\n\nfrom os.path import (\n    isabs,\n    isdir,\n    join as opj,\n    normpath,\n    relpath,\n)\n\nfrom datalad.utils import (\n    ensure_list,\n    path_is_subpath,\n    PurePosixPath,\n)\nfrom datalad.support.path import robust_abspath\n\nfrom datalad.distribution.dataset import Dataset\n\n\nlgr = logging.getLogger('datalad.interface.results')\nlgr.log(5, \"Importing datalad.interface.results\")\n\n# which status is a success , which is failure\nsuccess_status_map = {\n    'ok': 'success',\n    'notneeded': 'success',\n    'impossible': 'failure',\n    'error': 'failure',\n}\n\n\ndef get_status_dict(action=None, ds=None, path=None, type=None, logger=None,\n                    refds=None, status=None, message=None, exception=None,\n                    error_message=None, **kwargs):\n    # `type` is intentionally not `type_` or something else, as a mismatch\n    # with the dict key 'type' causes too much pain all over the place\n    # just for not shadowing the builtin `type` in this function\n    \"\"\"Helper to create a result dictionary.\n\n    Most arguments match their key in the resulting dict, and their given\n    values are simply assigned to the result record under these keys.  Only\n    exceptions are listed here.\n\n    Parameters\n    ----------\n    ds : Dataset instance\n      If given, the `path` and `type` values are populated with the path of the\n      datasets and 'dataset' as the type. Giving additional values for both\n      keys will overwrite these pre-populated values.\n    exception : CapturedException\n      Exceptions that occurred while generating a result should be captured\n      by immediately instantiating a CapturedException. This instance can\n      be passed here to yield more comprehensive error reporting, including\n      an auto-generated traceback (added to the result record under an\n      'exception_traceback' key).\n\n    Returns\n    -------\n    dict\n    \"\"\"\n\n    d = {}\n    if action is not None:\n        d['action'] = action\n    if ds:\n        d['path'] = ds.path\n        d['type'] = 'dataset'\n    # now overwrite automatic\n    if path is not None:\n        d['path'] = path\n    if type:\n        d['type'] = type\n    if logger:\n        d['logger'] = logger\n    if refds:\n        d['refds'] = refds\n    if status is not None:\n        # TODO check for known status label\n        d['status'] = status\n    if message is not None:\n        d['message'] = message\n    if error_message is not None:\n        d['error_message'] = error_message\n    if exception is not None:\n        d['exception'] = exception\n        d['exception_traceback'] = \\\n            exception.format_oneline_tb(limit=1000, include_str=False)\n    if kwargs:\n        d.update(kwargs)\n    return d\n\n\ndef results_from_paths(paths, action=None, type=None, logger=None, refds=None,\n                       status=None, message=None, exception=None):\n    \"\"\"\n    Helper to yield analog result dicts for each path in a sequence.\n\n    Parameters\n    ----------\n    message: str\n      A result message. May contain `%s` which will be replaced by the\n      respective `path`.\n\n    Returns\n    -------\n    generator\n\n    \"\"\"\n    for p in ensure_list(paths):\n        yield get_status_dict(\n            action, path=p, type=type, logger=logger, refds=refds,\n            status=status, message=(message, p) if '%s' in message else message,\n            exception=exception\n        )\n\n\ndef is_ok_dataset(r):\n    \"\"\"Convenience test for a non-failure dataset-related result dict\"\"\"\n    return r.get('status', None) == 'ok' and r.get('type', None) == 'dataset'\n\n\nclass ResultXFM(object):\n    \"\"\"Abstract definition of the result transformer API\"\"\"\n    def __call__(self, res):\n        \"\"\"This is called with one result dict at a time\"\"\"\n        raise NotImplementedError\n\n\nclass YieldDatasets(ResultXFM):\n    \"\"\"Result transformer to return a Dataset instance from matching result.\n\n    If the `success_only` flag is given only dataset with 'ok' or 'notneeded'\n    status are returned'.\n\n    `None` is returned for any other result.\n    \"\"\"\n    def __init__(self, success_only=False):\n        self.success_only = success_only\n\n    def __call__(self, res):\n        if res.get('type', None) == 'dataset':\n            if not self.success_only or \\\n                    res.get('status', None) in ('ok', 'notneeded'):\n                return Dataset(res['path'])\n        else:\n            lgr.debug('rejected by return value configuration: %s', res)\n\n\nclass YieldRelativePaths(ResultXFM):\n    \"\"\"Result transformer to return relative paths for a result\n\n    Relative paths are determined from the 'refds' value in the result. If\n    no such value is found, `None` is returned.\n    \"\"\"\n    def __call__(self, res):\n        refpath = res.get('refds', None)\n        if refpath:\n            return relpath(res['path'], start=refpath)\n\n\nclass YieldField(ResultXFM):\n    \"\"\"Result transformer to return an arbitrary value from a result dict\"\"\"\n    def __init__(self, field):\n        \"\"\"\n        Parameters\n        ----------\n        field : str\n          Key of the field to return.\n        \"\"\"\n        self.field = field\n\n    def __call__(self, res):\n        if self.field in res:\n            return res[self.field]\n        else:\n            lgr.debug('rejected by return value configuration: %s', res)\n\n\n# a bunch of convenience labels for common result transformers\n# the API `result_xfm` argument understand any of these labels and\n# applied the corresponding callable\nknown_result_xfms = {\n    'datasets': YieldDatasets(),\n    'successdatasets-or-none': YieldDatasets(success_only=True),\n    'paths': YieldField('path'),\n    'relpaths': YieldRelativePaths(),\n    'metadata': YieldField('metadata'),\n}\n\ntranslate_annex_notes = {\n    '(Use --force to override this check, or adjust numcopies.)':\n        'configured minimum number of copies not found',\n}\n\n\ndef annexjson2result(d, ds, **kwargs):\n    \"\"\"Helper to convert an annex JSON result to a datalad result dict\n\n    Info from annex is rather heterogeneous, partly because some of it\n    our support functions are faking.\n\n    This helper should be extended with all needed special cases to\n    homogenize the information.\n\n    Parameters\n    ----------\n    d : dict\n      Annex info dict.\n    ds : Dataset instance\n      Used to determine absolute paths for `file` results. This dataset\n      is not used to set `refds` in the result, pass this as a separate\n      kwarg if needed.\n    **kwargs\n      Passes as-is to `get_status_dict`. Must not contain `refds`.\n    \"\"\"\n    lgr.debug('received JSON result from annex: %s', d)\n    messages = []\n    res = get_status_dict(**kwargs)\n    res['status'] = 'ok' if d.get('success', False) is True else 'error'\n    # we cannot rely on any of these to be available as the feed from\n    # git annex (or its wrapper) is not always homogeneous\n    if d.get('file'):\n        res['path'] = str(ds.pathobj / PurePosixPath(d['file']))\n    if 'command' in d:\n        res['action'] = d['command']\n    if 'key' in d:\n        res['annexkey'] = d['key']\n    if 'fields' in d:\n        # this is annex metadata, filter out timestamps\n        res['metadata'] = {k: v[0] if isinstance(v, list) and len(v) == 1 else v\n                           for k, v in d['fields'].items()\n                           if not k.endswith('lastchanged')}\n    if d.get('error-messages', None):\n        res['error_message'] = '\\n'.join(m.strip() for m in d['error-messages'])\n    # avoid meaningless standard messages, and collision with actual error\n    # messages\n    elif 'note' in d:\n        note = \"; \".join(ln for ln in d['note'].splitlines()\n                         if ln != 'checksum...'\n                         and not ln.startswith('checking file'))\n        if note:\n            messages.append(translate_annex_notes.get(note, note))\n    if messages:\n        res['message'] = '\\n'.join(m.strip() for m in messages)\n    return res\n\n\ndef count_results(res, **kwargs):\n    \"\"\"Return number if results that match all property values in kwargs\"\"\"\n    return sum(\n        all(k in r and r[k] == v for k, v in kwargs.items()) for r in res)\n\n\ndef only_matching_paths(res, **kwargs):\n    # TODO handle relative paths by using a contained 'refds' value\n    paths = ensure_list(kwargs.get('path', []))\n    respath = res.get('path', None)\n    return respath in paths\n\n\n# needs decorator, as it will otherwise bind to the command classes that use it\n@staticmethod\ndef is_result_matching_pathsource_argument(res, **kwargs):\n    # we either have any non-zero number of \"paths\" (that could be anything), or\n    # we have one path and one source\n    # we don't do any error checking here, done by the command itself\n    if res.get('action', None) not in ('install', 'get'):\n        # this filter is only used in install, reject anything that comes\n        # in that could not possibly be a 'install'-like result\n        # e.g. a sibling being added in the process\n        return False\n    source = kwargs.get('source', None)\n    if source is not None:\n        # we want to be able to deal with Dataset instances given as 'source':\n        if isinstance(source, Dataset):\n            source = source.path\n        # if there was a source, it needs to be recorded in the result\n        # otherwise this is not what we are looking for\n        return source == res.get('source_url', None)\n    # the only thing left is a potentially heterogeneous list of paths/URLs\n    paths = ensure_list(kwargs.get('path', []))\n    # three cases left:\n    # 1. input arg was an absolute path -> must match 'path' property\n    # 2. input arg was relative to a dataset -> must match refds/relpath\n    # 3. something nifti with a relative input path that uses PWD as the\n    #    reference\n    respath = res.get('path', None)\n    if respath in paths:\n        # absolute match, pretty sure we want this\n        return True\n    elif isinstance(kwargs.get('dataset', None), Dataset) and \\\n            YieldRelativePaths()(res) in paths:\n        # command was called with a reference dataset, and a relative\n        # path of a result matches in input argument -- not 100% exhaustive\n        # test, but could be good enough\n        return True\n    elif any(robust_abspath(p) == respath for p in paths):\n        # one absolutified input path matches the result path\n        # I'd say: got for it!\n        return True\n    elif any(p == res.get('source_url', None) for p in paths):\n        # this was installed from a URL that was given, we'll take that too\n        return True\n    else:\n        return False\n\n\ndef results_from_annex_noinfo(ds, requested_paths, respath_by_status, dir_fail_msg,\n                              noinfo_dir_msg, noinfo_file_msg, noinfo_status='notneeded',\n                              **kwargs):\n    \"\"\"Helper to yield results based on what information git annex did no give us.\n\n    The helper assumes that the annex command returned without an error code,\n    and interprets which of the requested paths we have heard nothing about,\n    and assumes that git annex was happy with their current state.\n\n    Parameters\n    ==========\n    ds : Dataset\n      All results have to be concerning this single dataset (used to resolve\n      relpaths).\n    requested_paths : list\n      List of path arguments sent to `git annex`\n    respath_by_status : dict\n      Mapping of 'success' or 'failure' labels to lists of result paths\n      reported by `git annex`. Everything that is not in here, we assume\n      that `git annex` was happy about.\n    dir_fail_msg : str\n      Message template to inject into the result for a requested directory where\n      a failure was reported for some of its content. The template contains two\n      string placeholders that will be expanded with 1) the path of the\n      directory, and 2) the content failure paths for that directory\n    noinfo_dir_msg : str\n      Message template to inject into the result for a requested directory that\n      `git annex` was silent about (incl. any content). There must be one string\n      placeholder that is expanded with the path of that directory.\n    noinfo_file_msg : str\n      Message to inject into the result for a requested file that `git\n      annex` was silent about.\n    noinfo_status : str\n      Status to report when annex provides no information\n    **kwargs\n      Any further kwargs are included in the yielded result dictionary.\n    \"\"\"\n    for p in requested_paths:\n        # any relpath is relative to the currently processed dataset\n        # not the global reference dataset\n        p = p if isabs(p) else normpath(opj(ds.path, p))\n        if any(p in ps for ps in respath_by_status.values()):\n            # we have a report for this path already\n            continue\n        common_report = dict(path=p, **kwargs)\n        if isdir(p):\n            # `annex` itself will not report on directories, but if a\n            # directory was requested, we want to say something about\n            # it in the results.  we are inside a single, existing\n            # repo, hence all directories are already present, if not\n            # we had an error\n            # do we have any failures in a subdir of the requested dir?\n            failure_results = [\n                fp for fp in respath_by_status.get('failure', [])\n                if path_is_subpath(fp, p)]\n            if failure_results:\n                # we were not able to process all requested_paths, let's label\n                # this 'impossible' to get a warning-type report\n                # after all we have the directory itself, but not\n                # (some) of its requested_paths\n                yield get_status_dict(\n                    status='impossible', type='directory',\n                    message=(dir_fail_msg, p, failure_results),\n                    **common_report)\n            else:\n                # otherwise cool, but how cool?\n                success_results = [\n                    fp for fp in respath_by_status.get('success', [])\n                    if path_is_subpath(fp, p)]\n                yield get_status_dict(\n                    status='ok' if success_results else noinfo_status,\n                    message=None if success_results else (noinfo_dir_msg, p),\n                    type='directory', **common_report)\n            continue\n        else:\n            # not a directory, and we have had no word from `git annex`,\n            # yet no exception, hence the file was most probably\n            # already in the desired state\n            yield get_status_dict(\n                status=noinfo_status, type='file',\n                message=noinfo_file_msg,\n                **common_report)\n\n\nlgr.log(5, \"Done importing datalad.interface.results\")\n", "datalad/support/exceptions.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\"\"\" datalad exceptions\n\"\"\"\n\nimport logging\nimport re\nimport traceback\nfrom os import linesep\nfrom pathlib import Path\nfrom pprint import pformat\n\nfrom datalad.runner.exception import CommandError\n\nlgr = logging.getLogger('datalad.support.exceptions')\n\n\nclass CapturedException(object):\n    \"\"\"This class represents information about an occurred exception (including\n    its traceback), while not holding any references to the actual exception\n    object or its traceback, frame references, etc.\n\n    Just keep the textual information for logging or whatever other kind of\n    reporting.\n    \"\"\"\n\n    def __init__(self, exc, limit=None, capture_locals=False,\n                 level=8, logger=None):\n        \"\"\"Capture an exception and its traceback for logging.\n\n        Clears the exception's traceback frame references afterwards.\n\n        Parameters\n        ----------\n        exc: Exception\n        limit: int\n          Note, that this is limiting the capturing of the exception's\n          traceback depth. Formatting for output comes with it's own limit.\n        capture_locals: bool\n          Whether or not to capture the local context of traceback frames.\n        \"\"\"\n        # Note, that with lookup_lines=False the lookup is deferred,\n        # not disabled. Unclear to me ATM, whether that means to keep frame\n        # references around, but prob. not. TODO: Test that.\n        self.tb = traceback.TracebackException.from_exception(\n            exc,\n            limit=limit,\n            lookup_lines=True,\n            capture_locals=capture_locals\n        )\n        traceback.clear_frames(exc.__traceback__)\n\n        # log the captured exception\n        logger = logger or lgr\n        logger.log(level, \"%r\", self)\n\n    def format_oneline_tb(self, limit=None, include_str=True):\n        \"\"\"Format an exception traceback as a one-line summary\n\n        Returns a string of the form [filename:contextname:linenumber, ...].\n        If include_str is True (default), this is prepended with the string\n        representation of the exception.\n        \"\"\"\n\n        # Note: No import at module level, since ConfigManager imports\n        # dochelpers -> circular import when creating datalad.cfg instance at\n        # startup.\n        from datalad import cfg\n\n        if include_str:\n            # try exc message else exception type\n            leading = self.message or self.name\n            out = \"{} \".format(leading)\n        else:\n            out = \"\"\n\n        entries = []\n        entries.extend(self.tb.stack)\n        if self.tb.__cause__:\n            entries.extend(self.tb.__cause__.stack)\n        elif self.tb.__context__ and not self.tb.__suppress_context__:\n            entries.extend(self.tb.__context__.stack)\n\n        if limit is None:\n            limit = int(cfg.obtain('datalad.exc.str.tblimit',\n                                   default=len(entries)))\n        if entries:\n            tb_str = \"[%s]\" % (','.join(\n                \"{}:{}:{}\".format(\n                    Path(frame_summary.filename).name,\n                    frame_summary.name,\n                    frame_summary.lineno)\n                for frame_summary in entries[-limit:])\n            )\n            out += \"{}\".format(tb_str)\n\n        return out\n\n    def format_standard(self):\n        \"\"\"Returns python's standard formatted traceback output\n\n        Returns\n        -------\n        str\n        \"\"\"\n        # TODO: Intended for introducing a decent debug mode later when this\n        #       can be used fromm within log formatter / result renderer.\n        #       For now: a one-liner is free\n        return ''.join(self.tb.format())\n\n    def format_short(self):\n        \"\"\"Returns a short representation of the original exception\n\n        Form: ExceptionName(exception message)\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return self.name + '(' + self.message + ')'\n\n    @property\n    def message(self):\n        \"\"\"Returns only the message of the original exception\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return str(self.tb)\n\n    @property\n    def name(self):\n        \"\"\"Returns the class name of the original exception\n\n        Returns\n        -------\n        str\n        \"\"\"\n        return self.tb.exc_type.__qualname__\n\n    def __str__(self):\n        return self.format_short()\n\n    def __repr__(self):\n        return self.format_oneline_tb(limit=None, include_str=True)\n\n\nclass MissingExternalDependency(RuntimeError):\n    \"\"\"External dependency is missing error\"\"\"\n\n    def __init__(self, name, ver=None, msg=\"\"):\n        super(MissingExternalDependency, self).__init__()\n        self.name = name\n        self.ver = ver\n        self.msg = msg\n\n    def __str__(self):\n        to_str = 'No working {} installation'.format(self.name)\n        if self.ver:\n            to_str += \" of version >= %s\" % self.ver\n        to_str += \".\"\n        if self.msg:\n            to_str += \" %s\" % self.msg\n        return to_str\n\n\nclass BrokenExternalDependency(RuntimeError):\n    \"\"\"Some particular functionality is broken with this dependency.\"\"\"\n\n\nclass DeprecatedError(RuntimeError):\n    \"\"\"To raise whenever a deprecated entirely feature is used\"\"\"\n    def __init__(self, new=None, version=None, msg=''):\n        \"\"\"\n\n        Parameters\n        ----------\n        new : str, optional\n          What new construct to use\n        version : str, optional\n          Since which version is deprecated\n        kwargs\n        \"\"\"\n        super(DeprecatedError, self).__init__()\n        self.version = version\n        self.new = new\n        self.msg = msg\n\n    def __str__(self):\n        s = self.msg if self.msg else ''\n        if self.version:\n            s += (\" is deprecated\" if s else \"Deprecated\") + \" since version %s.\" % self.version\n        if self.new:\n            s += \" Use %s instead.\" % self.new\n        return s\n\n\nclass OutdatedExternalDependency(MissingExternalDependency):\n    \"\"\"External dependency is present but outdated\"\"\"\n\n    def __init__(self, name, ver=None, ver_present=None, msg=\"\"):\n        super(OutdatedExternalDependency, self).__init__(name, ver=ver, msg=msg)\n        self.ver_present = ver_present\n\n    def __str__(self):\n        to_str = super(OutdatedExternalDependency, self).__str__()\n        # MissingExternalDependency ends with a period unless msg is\n        # given, in which case it's up to the msg and no callers in\n        # our code base currently give a msg ending with a period.\n        to_str += \".\" if self.msg else \"\"\n        to_str += \" You have version %s\" % self.ver_present \\\n            if self.ver_present else \\\n            \" Some unknown version of dependency found.\"\n        return to_str\n\n\nclass AnnexBatchCommandError(CommandError):\n    \"\"\"Thrown if a batched command to annex fails\n\n    \"\"\"\n    pass\n\n\nclass CommandNotAvailableError(CommandError):\n    \"\"\"Thrown if a command is not available due to certain circumstances.\n    \"\"\"\n    pass\n\n\nclass FileNotInAnnexError(IOError, CommandError):\n    \"\"\"Thrown if a file is not under control of git-annex.\n    \"\"\"\n    def __init__(self, cmd=\"\", msg=\"\", code=None, filename=\"\"):\n        CommandError.__init__(self, cmd=cmd, msg=msg, code=code)\n        IOError.__init__(self, code, \"%s: %s\" % (cmd, msg), filename)\n\n    def to_str(self, include_output=True):\n        return \"%s\\n%s\" % (\n            CommandError.to_str(self, include_output=include_output),\n            IOError.__str__(self))\n\n\nclass FileInGitError(FileNotInAnnexError):\n    \"\"\"Thrown if a file is not under control of git-annex, but git itself.\n    \"\"\"\n    pass\n\n\nclass FileNotInRepositoryError(FileNotInAnnexError):\n    \"\"\"Thrown if a file is not under control of the repository at all.\n    \"\"\"\n    pass\n\n\nclass InvalidGitReferenceError(ValueError):\n    \"\"\"Thrown if provided git reference is invalid\n    \"\"\"\n    def __init__(self, ref, *args, **kwargs):\n        super(InvalidGitReferenceError, self).__init__(*args, **kwargs)\n        self.ref = ref\n\n    def __str__(self):\n        return u\"Git reference '{}' invalid\".format(self.ref)\n\n\nclass GitIgnoreError(CommandError):\n    \"\"\"Thrown if a path was ignored by a git command due to .gitignore file\n\n    Note, that this might be thrown to indicate what was ignored, while the\n    actual operation was partially successful (regarding paths, not in .gitignore)\n\n    Note/Todo:\n    in case of a directory being ignored, git returns that directory as the\n    ignored path, even if a path within that directory was passed to the command.\n    That means, that in such cases the returned path might not match an item you\n    passed!\n    \"\"\"\n\n    pattern = \\\n        re.compile(r'ignored by one of your .gitignore files:\\s*(.*)'\n                   r'^(?:hint: )?Use -f.*$',\n                   flags=re.MULTILINE | re.DOTALL)\n\n    def __init__(self, cmd=\"\", msg=\"\", code=None, stdout=\"\", stderr=\"\",\n                 paths=None):\n        super(GitIgnoreError, self).__init__(\n            cmd=cmd, msg=msg, code=code, stdout=stdout, stderr=stderr)\n        self.paths = paths\n\n    def to_str(self, include_output=True):\n        # Override CommandError.to_str(), ignoring include_output.\n        return self.msg\n\n\nclass PathOutsideRepositoryError(Exception):\n    \"\"\"Thrown if a path points outside the repository that was requested to\n    deal with that path.\"\"\"\n\n    # TODO: use it in GitRepo/AnnexRepo!\n    def __init__(self, file_, repo):\n        self.file_ = file_\n        self.repo = repo\n\n    def __str__(self):\n        return \"path {0} not within repository {1}\".format(self.file_, self.repo)\n\n\nclass PathKnownToRepositoryError(Exception):\n    \"\"\"Thrown if file/path is under Git control, and attempted operation\n    must not be ran\"\"\"\n    pass\n\n\nclass GitError(Exception):\n    \"\"\" Base class for all package exceptions \"\"\"\n\n\nclass NoSuchPathError(GitError, OSError):\n    \"\"\" Thrown if a path could not be access by the system. \"\"\"\n\n\nclass MissingBranchError(Exception):\n    \"\"\"Thrown if accessing a repository's branch, that is not available\"\"\"\n\n    def __init__(self, repo, branch, available_branches=None, msg=None):\n        self.repo = repo\n        self.branch = branch\n        self.branches = available_branches\n        if msg is None:\n            self.msg = \"branch '{0}' missing in {1}.\" \\\n                       \"\".format(self.branch, self.repo)\n            if self.branches:\n                self.msg += \" Available branches: {0}\".format(self.branches)\n        else:\n            self.msg = msg\n\n    def __str__(self):\n        return self.msg\n\n\nclass InsufficientArgumentsError(ValueError):\n    \"\"\"To be raise instead of `ValueError` when use help output is desired\"\"\"\n    pass\n\n\nclass NoDatasetArgumentFound(InsufficientArgumentsError):\n    \"\"\"To be raised when expecting having a dataset but none was provided\"\"\"\n    pass\n\n\nclass NoDatasetFound(NoDatasetArgumentFound):\n    \"\"\"Raised whenever a dataset is required, but none could be determined\"\"\"\n    pass\n\n\nclass OutOfSpaceError(CommandError):\n    \"\"\"To be raised whenever a command fails if we have no sufficient space\n\n    Example is  annex get command\n    \"\"\"\n\n    def __init__(self, sizemore_msg=None, **kwargs):\n        super(OutOfSpaceError, self).__init__(**kwargs)\n        self.sizemore_msg = sizemore_msg\n\n    def to_str(self, include_output=True):\n        super_str = super().to_str(\n            include_output=include_output).rstrip(linesep + '.')\n        return \"%s needs %s more\" % (super_str, self.sizemore_msg)\n\n\nclass RemoteNotAvailableError(CommandError):\n    \"\"\"To be raised whenever a required remote is not available\n\n    Example is \"annex get somefile --from=MyRemote\",\n    where 'MyRemote' doesn't exist.\n    \"\"\"\n\n    def __init__(self, remote, **kwargs):\n        \"\"\"\n\n        Parameters\n        ----------\n        remote: str\n          name of the remote\n        kwargs:\n          arguments from CommandError\n        \"\"\"\n        super(RemoteNotAvailableError, self).__init__(**kwargs)\n        self.remote = remote\n\n    def to_str(self, include_output=True):\n        super_str = super().to_str(include_output=include_output)\n        return \"Remote '{0}' is not available. Command failed:{1}{2}\" \\\n               \"\".format(self.remote, linesep, super_str)\n\n\nclass InvalidInstanceRequestError(RuntimeError):\n    \"\"\"Thrown if a request to create a (flyweight) instance is invalid\"\"\"\n\n    def __init__(self, id_, msg=None):\n        super(InvalidInstanceRequestError, self).__init__(msg)\n        self.id = id_\n        self.msg = msg\n\n\nclass InvalidGitRepositoryError(GitError):\n    \"\"\" Thrown if the given repository appears to have an invalid format.  \"\"\"\n\n\nclass InvalidAnnexRepositoryError(RuntimeError):\n    \"\"\"Thrown if AnnexRepo was instantiated on a non-annex and\n    without init=True\"\"\"\n\n\nclass DirectModeNoLongerSupportedError(NotImplementedError):\n    \"\"\"direct mode is no longer supported\"\"\"\n\n    def __init__(self, repo, msg=None):\n        super(DirectModeNoLongerSupportedError, self).__init__(\n            ((\" \" + msg + \", but \") if msg else '')\n            +\n             \"direct mode of operation is being deprecated in git-annex and \"\n             \"no longer supported by DataLad. \"\n             \"Please use 'git annex upgrade' under %s to upgrade your direct \"\n             \"mode repository to annex v6 (or later).\" % repo.path\n            )\n        self.repo = repo  # might come handy\n\n\nclass IncompleteResultsError(RuntimeError):\n    \"\"\"Exception to be raised whenever results are incomplete.\n\n    Any results produced nevertheless are to be passed as `results`,\n    and become available via the `results` attribute.\n    \"\"\"\n    # TODO passing completed results doesn't fit in a generator paradigm\n    # such results have been yielded already at the time this exception is\n    # raised, little point in collecting them just for the sake of a possible\n    # exception\n    # MIH+YOH: AnnexRepo.copy_to and @eval_results are the last\n    # remaining user of this functionality.\n    # General use (as in AnnexRepo) of it discouraged but use in @eval_results\n    # is warranted\n    def __init__(self, results=None, failed=None, msg=None):\n        super(IncompleteResultsError, self).__init__(msg)\n        self.results = results\n        self.failed = failed\n\n    def __str__(self):\n        super_str = super(IncompleteResultsError, self).__str__()\n        return \"{}{}{}\".format(\n            super_str,\n            \". {} result(s)\".format(len(self.results)) if self.results else \"\",\n            \". {} failed:{}{}\".format(\n                len(self.failed),\n                linesep,\n                pformat(self.failed)) if self.failed else \"\")\n\n\nclass InstallFailedError(CommandError):\n    \"\"\"Generic exception to raise whenever `install` command fails\"\"\"\n    pass\n\n\nclass ConnectionOpenFailedError(CommandError):\n    \"\"\"Exception to raise whenever opening a network connection fails\"\"\"\n    pass\n#\n# Downloaders\n#\n\n\nclass DownloadError(Exception):\n    pass\n\n\nclass IncompleteDownloadError(DownloadError):\n    pass\n\n\nclass UnaccountedDownloadError(IncompleteDownloadError):\n    pass\n\n\nclass TargetFileAbsent(DownloadError):\n    pass\n\n\nclass AccessDeniedError(DownloadError):\n    def __init__(self, msg=None, supported_types=None, **kwargs):\n        super(AccessDeniedError, self).__init__(msg, **kwargs)\n        self.supported_types = supported_types\n\n\nclass AnonymousAccessDeniedError(AccessDeniedError):\n    pass\n\n\nclass AccessPermissionExpiredError(AccessDeniedError):\n    \"\"\"To raise when there is a belief that it is due to expiration of a credential\n\n    which we might possibly be able to refresh, like in the case of CompositeCredential\n    \"\"\"\n    pass\n\n\nclass AccessFailedError(DownloadError):\n    pass\n\n\nclass UnhandledRedirectError(DownloadError):\n    def __init__(self, msg=None, url=None, **kwargs):\n        super(UnhandledRedirectError, self).__init__(msg, **kwargs)\n        self.url = url\n\n#\n# Crawler\n#\n\n\nclass CrawlerError(Exception):\n    pass\n\n\nclass PipelineNotSpecifiedError(CrawlerError):\n    pass\n\n\n#\n# Warnings\n#\n\nclass DataLadWarning(Warning):\n    pass\n\n\n# We have an exception OutdatedExternalDependency, but it is intended for\n# an instance being raised.  `warnings` module requires a class to be provided\n# as a category, so here is a dedicated Warning class\nclass OutdatedExternalDependencyWarning(DataLadWarning):\n    \"\"\"Warning \"category\" to use to report about outdated\"\"\"\n    pass\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:** AttributeError When Handling `CommandError` in `get_status_dict`

**Description:**
When a `CommandError` exception is passed to the `get_status_dict` function, it raises an `AttributeError` because the `CommandError` object does not have the `format_oneline_tb` method. This prevents the function from correctly adding the `exit_code` to the status dictionary.

**Example Code:**
```python
from datalad.interface.results import get_status_dict
from datalad.runner import CommandError

# Attempt to create a status dictionary with a CommandError
d = get_status_dict(exception=CommandError(code=105))
print(d['exit_code'])
```

**Expected Behavior:**
The `get_status_dict` function should successfully add the `exit_code` key to the returned dictionary with the value `105`.

**Actual Behavior:**
An `AttributeError` is raised:
```
AttributeError: 'CommandError' object has no attribute 'format_oneline_tb'
```
This error occurs because the function attempts to call `format_oneline_tb` on the `CommandError` instance, which does not have this method.

[/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/interface/results.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.
#
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""Interface result handling functions

"""

__docformat__ = 'restructuredtext'

import logging

from os.path import (
    isabs,
    isdir,
    join as opj,
    normpath,
    relpath,
)

from datalad.utils import (
    ensure_list,
    path_is_subpath,
    PurePosixPath,
)
from datalad.support.path import robust_abspath

from datalad.distribution.dataset import Dataset


lgr = logging.getLogger('datalad.interface.results')
lgr.log(5, "Importing datalad.interface.results")

# which status is a success , which is failure
success_status_map = {
    'ok': 'success',
    'notneeded': 'success',
    'impossible': 'failure',
    'error': 'failure',
}


def get_status_dict(action=None, ds=None, path=None, type=None, logger=None,
                    refds=None, status=None, message=None, exception=None,
                    error_message=None, **kwargs):
    # `type` is intentionally not `type_` or something else, as a mismatch
    # with the dict key 'type' causes too much pain all over the place
    # just for not shadowing the builtin `type` in this function
    """Helper to create a result dictionary.

    Most arguments match their key in the resulting dict, and their given
    values are simply assigned to the result record under these keys.  Only
    exceptions are listed here.

    Parameters
    ----------
    ds : Dataset instance
      If given, the `path` and `type` values are populated with the path of the
      datasets and 'dataset' as the type. Giving additional values for both
      keys will overwrite these pre-populated values.
    exception : CapturedException
      Exceptions that occurred while generating a result should be captured
      by immediately instantiating a CapturedException. This instance can
      be passed here to yield more comprehensive error reporting, including
      an auto-generated traceback (added to the result record under an
      'exception_traceback' key).

    Returns
    -------
    dict
    """

    d = {}
    if action is not None:
        d['action'] = action
    if ds:
        d['path'] = ds.path
        d['type'] = 'dataset'
    # now overwrite automatic
    if path is not None:
        d['path'] = path
    if type:
        d['type'] = type
    if logger:
        d['logger'] = logger
    if refds:
        d['refds'] = refds
    if status is not None:
        # TODO check for known status label
        d['status'] = status
    if message is not None:
        d['message'] = message
    if error_message is not None:
        d['error_message'] = error_message
    if exception is not None:
        d['exception'] = exception
        d['exception_traceback'] = \
            exception.format_oneline_tb(limit=1000, include_str=False)
    if kwargs:
        d.update(kwargs)
    return d


def results_from_paths(paths, action=None, type=None, logger=None, refds=None,
                       status=None, message=None, exception=None):
    """
    Helper to yield analog result dicts for each path in a sequence.

    Parameters
    ----------
    message: str
      A result message. May contain `%s` which will be replaced by the
      respective `path`.

    Returns
    -------
    generator

    """
    for p in ensure_list(paths):
        yield get_status_dict(
            action, path=p, type=type, logger=logger, refds=refds,
            status=status, message=(message, p) if '%s' in message else message,
            exception=exception
        )


def is_ok_dataset(r):
    """Convenience test for a non-failure dataset-related result dict"""
    return r.get('status', None) == 'ok' and r.get('type', None) == 'dataset'


class ResultXFM(object):
    """Abstract definition of the result transformer API"""
    def __call__(self, res):
        """This is called with one result dict at a time"""
        raise NotImplementedError


class YieldDatasets(ResultXFM):
    """Result transformer to return a Dataset instance from matching result.

    If the `success_only` flag is given only dataset with 'ok' or 'notneeded'
    status are returned'.

    `None` is returned for any other result.
    """
    def __init__(self, success_only=False):
        self.success_only = success_only

    def __call__(self, res):
        if res.get('type', None) == 'dataset':
            if not self.success_only or \
                    res.get('status', None) in ('ok', 'notneeded'):
                return Dataset(res['path'])
        else:
            lgr.debug('rejected by return value configuration: %s', res)


class YieldRelativePaths(ResultXFM):
    """Result transformer to return relative paths for a result

    Relative paths are determined from the 'refds' value in the result. If
    no such value is found, `None` is returned.
    """
    def __call__(self, res):
        refpath = res.get('refds', None)
        if refpath:
            return relpath(res['path'], start=refpath)


class YieldField(ResultXFM):
    """Result transformer to return an arbitrary value from a result dict"""
    def __init__(self, field):
        """
        Parameters
        ----------
        field : str
          Key of the field to return.
        """
        self.field = field

    def __call__(self, res):
        if self.field in res:
            return res[self.field]
        else:
            lgr.debug('rejected by return value configuration: %s', res)


# a bunch of convenience labels for common result transformers
# the API `result_xfm` argument understand any of these labels and
# applied the corresponding callable
known_result_xfms = {
    'datasets': YieldDatasets(),
    'successdatasets-or-none': YieldDatasets(success_only=True),
    'paths': YieldField('path'),
    'relpaths': YieldRelativePaths(),
    'metadata': YieldField('metadata'),
}

translate_annex_notes = {
    '(Use --force to override this check, or adjust numcopies.)':
        'configured minimum number of copies not found',
}


def annexjson2result(d, ds, **kwargs):
    """Helper to convert an annex JSON result to a datalad result dict

    Info from annex is rather heterogeneous, partly because some of it
    our support functions are faking.

    This helper should be extended with all needed special cases to
    homogenize the information.

    Parameters
    ----------
    d : dict
      Annex info dict.
    ds : Dataset instance
      Used to determine absolute paths for `file` results. This dataset
      is not used to set `refds` in the result, pass this as a separate
      kwarg if needed.
    **kwargs
      Passes as-is to `get_status_dict`. Must not contain `refds`.
    """
    lgr.debug('received JSON result from annex: %s', d)
    messages = []
    res = get_status_dict(**kwargs)
    res['status'] = 'ok' if d.get('success', False) is True else 'error'
    # we cannot rely on any of these to be available as the feed from
    # git annex (or its wrapper) is not always homogeneous
    if d.get('file'):
        res['path'] = str(ds.pathobj / PurePosixPath(d['file']))
    if 'command' in d:
        res['action'] = d['command']
    if 'key' in d:
        res['annexkey'] = d['key']
    if 'fields' in d:
        # this is annex metadata, filter out timestamps
        res['metadata'] = {k: v[0] if isinstance(v, list) and len(v) == 1 else v
                           for k, v in d['fields'].items()
                           if not k.endswith('lastchanged')}
    if d.get('error-messages', None):
        res['error_message'] = '\n'.join(m.strip() for m in d['error-messages'])
    # avoid meaningless standard messages, and collision with actual error
    # messages
    elif 'note' in d:
        note = "; ".join(ln for ln in d['note'].splitlines()
                         if ln != 'checksum...'
                         and not ln.startswith('checking file'))
        if note:
            messages.append(translate_annex_notes.get(note, note))
    if messages:
        res['message'] = '\n'.join(m.strip() for m in messages)
    return res


def count_results(res, **kwargs):
    """Return number if results that match all property values in kwargs"""
    return sum(
        all(k in r and r[k] == v for k, v in kwargs.items()) for r in res)


def only_matching_paths(res, **kwargs):
    # TODO handle relative paths by using a contained 'refds' value
    paths = ensure_list(kwargs.get('path', []))
    respath = res.get('path', None)
    return respath in paths


# needs decorator, as it will otherwise bind to the command classes that use it
@staticmethod
def is_result_matching_pathsource_argument(res, **kwargs):
    # we either have any non-zero number of "paths" (that could be anything), or
    # we have one path and one source
    # we don't do any error checking here, done by the command itself
    if res.get('action', None) not in ('install', 'get'):
        # this filter is only used in install, reject anything that comes
        # in that could not possibly be a 'install'-like result
        # e.g. a sibling being added in the process
        return False
    source = kwargs.get('source', None)
    if source is not None:
        # we want to be able to deal with Dataset instances given as 'source':
        if isinstance(source, Dataset):
            source = source.path
        # if there was a source, it needs to be recorded in the result
        # otherwise this is not what we are looking for
        return source == res.get('source_url', None)
    # the only thing left is a potentially heterogeneous list of paths/URLs
    paths = ensure_list(kwargs.get('path', []))
    # three cases left:
    # 1. input arg was an absolute path -> must match 'path' property
    # 2. input arg was relative to a dataset -> must match refds/relpath
    # 3. something nifti with a relative input path that uses PWD as the
    #    reference
    respath = res.get('path', None)
    if respath in paths:
        # absolute match, pretty sure we want this
        return True
    elif isinstance(kwargs.get('dataset', None), Dataset) and \
            YieldRelativePaths()(res) in paths:
        # command was called with a reference dataset, and a relative
        # path of a result matches in input argument -- not 100% exhaustive
        # test, but could be good enough
        return True
    elif any(robust_abspath(p) == respath for p in paths):
        # one absolutified input path matches the result path
        # I'd say: got for it!
        return True
    elif any(p == res.get('source_url', None) for p in paths):
        # this was installed from a URL that was given, we'll take that too
        return True
    else:
        return False


def results_from_annex_noinfo(ds, requested_paths, respath_by_status, dir_fail_msg,
                              noinfo_dir_msg, noinfo_file_msg, noinfo_status='notneeded',
                              **kwargs):
    """Helper to yield results based on what information git annex did no give us.

    The helper assumes that the annex command returned without an error code,
    and interprets which of the requested paths we have heard nothing about,
    and assumes that git annex was happy with their current state.

    Parameters
    ==========
    ds : Dataset
      All results have to be concerning this single dataset (used to resolve
      relpaths).
    requested_paths : list
      List of path arguments sent to `git annex`
    respath_by_status : dict
      Mapping of 'success' or 'failure' labels to lists of result paths
      reported by `git annex`. Everything that is not in here, we assume
      that `git annex` was happy about.
    dir_fail_msg : str
      Message template to inject into the result for a requested directory where
      a failure was reported for some of its content. The template contains two
      string placeholders that will be expanded with 1) the path of the
      directory, and 2) the content failure paths for that directory
    noinfo_dir_msg : str
      Message template to inject into the result for a requested directory that
      `git annex` was silent about (incl. any content). There must be one string
      placeholder that is expanded with the path of that directory.
    noinfo_file_msg : str
      Message to inject into the result for a requested file that `git
      annex` was silent about.
    noinfo_status : str
      Status to report when annex provides no information
    **kwargs
      Any further kwargs are included in the yielded result dictionary.
    """
    for p in requested_paths:
        # any relpath is relative to the currently processed dataset
        # not the global reference dataset
        p = p if isabs(p) else normpath(opj(ds.path, p))
        if any(p in ps for ps in respath_by_status.values()):
            # we have a report for this path already
            continue
        common_report = dict(path=p, **kwargs)
        if isdir(p):
            # `annex` itself will not report on directories, but if a
            # directory was requested, we want to say something about
            # it in the results.  we are inside a single, existing
            # repo, hence all directories are already present, if not
            # we had an error
            # do we have any failures in a subdir of the requested dir?
            failure_results = [
                fp for fp in respath_by_status.get('failure', [])
                if path_is_subpath(fp, p)]
            if failure_results:
                # we were not able to process all requested_paths, let's label
                # this 'impossible' to get a warning-type report
                # after all we have the directory itself, but not
                # (some) of its requested_paths
                yield get_status_dict(
                    status='impossible', type='directory',
                    message=(dir_fail_msg, p, failure_results),
                    **common_report)
            else:
                # otherwise cool, but how cool?
                success_results = [
                    fp for fp in respath_by_status.get('success', [])
                    if path_is_subpath(fp, p)]
                yield get_status_dict(
                    status='ok' if success_results else noinfo_status,
                    message=None if success_results else (noinfo_dir_msg, p),
                    type='directory', **common_report)
            continue
        else:
            # not a directory, and we have had no word from `git annex`,
            # yet no exception, hence the file was most probably
            # already in the desired state
            yield get_status_dict(
                status=noinfo_status, type='file',
                message=noinfo_file_msg,
                **common_report)


lgr.log(5, "Done importing datalad.interface.results")


### datalad/support/exceptions.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.
#
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
""" datalad exceptions
"""

import logging
import re
import traceback
from os import linesep
from pathlib import Path
from pprint import pformat

from datalad.runner.exception import CommandError

lgr = logging.getLogger('datalad.support.exceptions')


class CapturedException(object):
    """This class represents information about an occurred exception (including
    its traceback), while not holding any references to the actual exception
    object or its traceback, frame references, etc.

    Just keep the textual information for logging or whatever other kind of
    reporting.
    """

    def __init__(self, exc, limit=None, capture_locals=False,
                 level=8, logger=None):
        """Capture an exception and its traceback for logging.

        Clears the exception's traceback frame references afterwards.

        Parameters
        ----------
        exc: Exception
        limit: int
          Note, that this is limiting the capturing of the exception's
          traceback depth. Formatting for output comes with it's own limit.
        capture_locals: bool
          Whether or not to capture the local context of traceback frames.
        """
        # Note, that with lookup_lines=False the lookup is deferred,
        # not disabled. Unclear to me ATM, whether that means to keep frame
        # references around, but prob. not. TODO: Test that.
        self.tb = traceback.TracebackException.from_exception(
            exc,
            limit=limit,
            lookup_lines=True,
            capture_locals=capture_locals
        )
        traceback.clear_frames(exc.__traceback__)

        # log the captured exception
        logger = logger or lgr
        logger.log(level, "%r", self)

    def format_oneline_tb(self, limit=None, include_str=True):
        """Format an exception traceback as a one-line summary

        Returns a string of the form [filename:contextname:linenumber, ...].
        If include_str is True (default), this is prepended with the string
        representation of the exception.
        """

        # Note: No import at module level, since ConfigManager imports
        # dochelpers -> circular import when creating datalad.cfg instance at
        # startup.
        from datalad import cfg

        if include_str:
            # try exc message else exception type
            leading = self.message or self.name
            out = "{} ".format(leading)
        else:
            out = ""

        entries = []
        entries.extend(self.tb.stack)
        if self.tb.__cause__:
            entries.extend(self.tb.__cause__.stack)
        elif self.tb.__context__ and not self.tb.__suppress_context__:
            entries.extend(self.tb.__context__.stack)

        if limit is None:
            limit = int(cfg.obtain('datalad.exc.str.tblimit',
                                   default=len(entries)))
        if entries:
            tb_str = "[%s]" % (','.join(
                "{}:{}:{}".format(
                    Path(frame_summary.filename).name,
                    frame_summary.name,
                    frame_summary.lineno)
                for frame_summary in entries[-limit:])
            )
            out += "{}".format(tb_str)

        return out

    def format_standard(self):
        """Returns python's standard formatted traceback output

        Returns
        -------
        str
        """
        # TODO: Intended for introducing a decent debug mode later when this
        #       can be used fromm within log formatter / result renderer.
        #       For now: a one-liner is free
        return ''.join(self.tb.format())

    def format_short(self):
        """Returns a short representation of the original exception

        Form: ExceptionName(exception message)

        Returns
        -------
        str
        """
        return self.name + '(' + self.message + ')'

    @property
    def message(self):
        """Returns only the message of the original exception

        Returns
        -------
        str
        """
        return str(self.tb)

    @property
    def name(self):
        """Returns the class name of the original exception

        Returns
        -------
        str
        """
        return self.tb.exc_type.__qualname__

    def __str__(self):
        return self.format_short()

    def __repr__(self):
        return self.format_oneline_tb(limit=None, include_str=True)


class MissingExternalDependency(RuntimeError):
    """External dependency is missing error"""

    def __init__(self, name, ver=None, msg=""):
        super(MissingExternalDependency, self).__init__()
        self.name = name
        self.ver = ver
        self.msg = msg

    def __str__(self):
        to_str = 'No working {} installation'.format(self.name)
        if self.ver:
            to_str += " of version >= %s" % self.ver
        to_str += "."
        if self.msg:
            to_str += " %s" % self.msg
        return to_str


class BrokenExternalDependency(RuntimeError):
    """Some particular functionality is broken with this dependency."""


class DeprecatedError(RuntimeError):
    """To raise whenever a deprecated entirely feature is used"""
    def __init__(self, new=None, version=None, msg=''):
        """

        Parameters
        ----------
        new : str, optional
          What new construct to use
        version : str, optional
          Since which version is deprecated
        kwargs
        """
        super(DeprecatedError, self).__init__()
        self.version = version
        self.new = new
        self.msg = msg

    def __str__(self):
        s = self.msg if self.msg else ''
        if self.version:
            s += (" is deprecated" if s else "Deprecated") + " since version %s." % self.version
        if self.new:
            s += " Use %s instead." % self.new
        return s


class OutdatedExternalDependency(MissingExternalDependency):
    """External dependency is present but outdated"""

    def __init__(self, name, ver=None, ver_present=None, msg=""):
        super(OutdatedExternalDependency, self).__init__(name, ver=ver, msg=msg)
        self.ver_present = ver_present

    def __str__(self):
        to_str = super(OutdatedExternalDependency, self).__str__()
        # MissingExternalDependency ends with a period unless msg is
        # given, in which case it's up to the msg and no callers in
        # our code base currently give a msg ending with a period.
        to_str += "." if self.msg else ""
        to_str += " You have version %s" % self.ver_present \
            if self.ver_present else \
            " Some unknown version of dependency found."
        return to_str


class AnnexBatchCommandError(CommandError):
    """Thrown if a batched command to annex fails

    """
    pass


class CommandNotAvailableError(CommandError):
    """Thrown if a command is not available due to certain circumstances.
    """
    pass


class FileNotInAnnexError(IOError, CommandError):
    """Thrown if a file is not under control of git-annex.
    """
    def __init__(self, cmd="", msg="", code=None, filename=""):
        CommandError.__init__(self, cmd=cmd, msg=msg, code=code)
        IOError.__init__(self, code, "%s: %s" % (cmd, msg), filename)

    def to_str(self, include_output=True):
        return "%s\n%s" % (
            CommandError.to_str(self, include_output=include_output),
            IOError.__str__(self))


class FileInGitError(FileNotInAnnexError):
    """Thrown if a file is not under control of git-annex, but git itself.
    """
    pass


class FileNotInRepositoryError(FileNotInAnnexError):
    """Thrown if a file is not under control of the repository at all.
    """
    pass


class InvalidGitReferenceError(ValueError):
    """Thrown if provided git reference is invalid
    """
    def __init__(self, ref, *args, **kwargs):
        super(InvalidGitReferenceError, self).__init__(*args, **kwargs)
        self.ref = ref

    def __str__(self):
        return u"Git reference '{}' invalid".format(self.ref)


class GitIgnoreError(CommandError):
    """Thrown if a path was ignored by a git command due to .gitignore file

    Note, that this might be thrown to indicate what was ignored, while the
    actual operation was partially successful (regarding paths, not in .gitignore)

    Note/Todo:
    in case of a directory being ignored, git returns that directory as the
    ignored path, even if a path within that directory was passed to the command.
    That means, that in such cases the returned path might not match an item you
    passed!
    """

    pattern = \
        re.compile(r'ignored by one of your .gitignore files:\s*(.*)'
                   r'^(?:hint: )?Use -f.*$',
                   flags=re.MULTILINE | re.DOTALL)

    def __init__(self, cmd="", msg="", code=None, stdout="", stderr="",
                 paths=None):
        super(GitIgnoreError, self).__init__(
            cmd=cmd, msg=msg, code=code, stdout=stdout, stderr=stderr)
        self.paths = paths

    def to_str(self, include_output=True):
        # Override CommandError.to_str(), ignoring include_output.
        return self.msg


class PathOutsideRepositoryError(Exception):
    """Thrown if a path points outside the repository that was requested to
    deal with that path."""

    # TODO: use it in GitRepo/AnnexRepo!
    def __init__(self, file_, repo):
        self.file_ = file_
        self.repo = repo

    def __str__(self):
        return "path {0} not within repository {1}".format(self.file_, self.repo)


class PathKnownToRepositoryError(Exception):
    """Thrown if file/path is under Git control, and attempted operation
    must not be ran"""
    pass


class GitError(Exception):
    """ Base class for all package exceptions """


class NoSuchPathError(GitError, OSError):
    """ Thrown if a path could not be access by the system. """


class MissingBranchError(Exception):
    """Thrown if accessing a repository's branch, that is not available"""

    def __init__(self, repo, branch, available_branches=None, msg=None):
        self.repo = repo
        self.branch = branch
        self.branches = available_branches
        if msg is None:
            self.msg = "branch '{0}' missing in {1}." \
                       "".format(self.branch, self.repo)
            if self.branches:
                self.msg += " Available branches: {0}".format(self.branches)
        else:
            self.msg = msg

    def __str__(self):
        return self.msg


class InsufficientArgumentsError(ValueError):
    """To be raise instead of `ValueError` when use help output is desired"""
    pass


class NoDatasetArgumentFound(InsufficientArgumentsError):
    """To be raised when expecting having a dataset but none was provided"""
    pass


class NoDatasetFound(NoDatasetArgumentFound):
    """Raised whenever a dataset is required, but none could be determined"""
    pass


class OutOfSpaceError(CommandError):
    """To be raised whenever a command fails if we have no sufficient space

    Example is  annex get command
    """

    def __init__(self, sizemore_msg=None, **kwargs):
        super(OutOfSpaceError, self).__init__(**kwargs)
        self.sizemore_msg = sizemore_msg

    def to_str(self, include_output=True):
        super_str = super().to_str(
            include_output=include_output).rstrip(linesep + '.')
        return "%s needs %s more" % (super_str, self.sizemore_msg)


class RemoteNotAvailableError(CommandError):
    """To be raised whenever a required remote is not available

    Example is "annex get somefile --from=MyRemote",
    where 'MyRemote' doesn't exist.
    """

    def __init__(self, remote, **kwargs):
        """

        Parameters
        ----------
        remote: str
          name of the remote
        kwargs:
          arguments from CommandError
        """
        super(RemoteNotAvailableError, self).__init__(**kwargs)
        self.remote = remote

    def to_str(self, include_output=True):
        super_str = super().to_str(include_output=include_output)
        return "Remote '{0}' is not available. Command failed:{1}{2}" \
               "".format(self.remote, linesep, super_str)


class InvalidInstanceRequestError(RuntimeError):
    """Thrown if a request to create a (flyweight) instance is invalid"""

    def __init__(self, id_, msg=None):
        super(InvalidInstanceRequestError, self).__init__(msg)
        self.id = id_
        self.msg = msg


class InvalidGitRepositoryError(GitError):
    """ Thrown if the given repository appears to have an invalid format.  """


class InvalidAnnexRepositoryError(RuntimeError):
    """Thrown if AnnexRepo was instantiated on a non-annex and
    without init=True"""


class DirectModeNoLongerSupportedError(NotImplementedError):
    """direct mode is no longer supported"""

    def __init__(self, repo, msg=None):
        super(DirectModeNoLongerSupportedError, self).__init__(
            ((" " + msg + ", but ") if msg else '')
            +
             "direct mode of operation is being deprecated in git-annex and "
             "no longer supported by DataLad. "
             "Please use 'git annex upgrade' under %s to upgrade your direct "
             "mode repository to annex v6 (or later)." % repo.path
            )
        self.repo = repo  # might come handy


class IncompleteResultsError(RuntimeError):
    """Exception to be raised whenever results are incomplete.

    Any results produced nevertheless are to be passed as `results`,
    and become available via the `results` attribute.
    """
    # TODO passing completed results doesn't fit in a generator paradigm
    # such results have been yielded already at the time this exception is
    # raised, little point in collecting them just for the sake of a possible
    # exception
    # MIH+YOH: AnnexRepo.copy_to and @eval_results are the last
    # remaining user of this functionality.
    # General use (as in AnnexRepo) of it discouraged but use in @eval_results
    # is warranted
    def __init__(self, results=None, failed=None, msg=None):
        super(IncompleteResultsError, self).__init__(msg)
        self.results = results
        self.failed = failed

    def __str__(self):
        super_str = super(IncompleteResultsError, self).__str__()
        return "{}{}{}".format(
            super_str,
            ". {} result(s)".format(len(self.results)) if self.results else "",
            ". {} failed:{}{}".format(
                len(self.failed),
                linesep,
                pformat(self.failed)) if self.failed else "")


class InstallFailedError(CommandError):
    """Generic exception to raise whenever `install` command fails"""
    pass


class ConnectionOpenFailedError(CommandError):
    """Exception to raise whenever opening a network connection fails"""
    pass
#
# Downloaders
#


class DownloadError(Exception):
    pass


class IncompleteDownloadError(DownloadError):
    pass


class UnaccountedDownloadError(IncompleteDownloadError):
    pass


class TargetFileAbsent(DownloadError):
    pass


class AccessDeniedError(DownloadError):
    def __init__(self, msg=None, supported_types=None, **kwargs):
        super(AccessDeniedError, self).__init__(msg, **kwargs)
        self.supported_types = supported_types


class AnonymousAccessDeniedError(AccessDeniedError):
    pass


class AccessPermissionExpiredError(AccessDeniedError):
    """To raise when there is a belief that it is due to expiration of a credential

    which we might possibly be able to refresh, like in the case of CompositeCredential
    """
    pass


class AccessFailedError(DownloadError):
    pass


class UnhandledRedirectError(DownloadError):
    def __init__(self, msg=None, url=None, **kwargs):
        super(UnhandledRedirectError, self).__init__(msg, **kwargs)
        self.url = url

#
# Crawler
#


class CrawlerError(Exception):
    pass


class PipelineNotSpecifiedError(CrawlerError):
    pass


#
# Warnings
#

class DataLadWarning(Warning):
    pass


# We have an exception OutdatedExternalDependency, but it is intended for
# an instance being raised.  `warnings` module requires a class to be provided
# as a category, so here is a dedicated Warning class
class OutdatedExternalDependencyWarning(DataLadWarning):
    """Warning "category" to use to report about outdated"""
    pass

```
--- 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