{
    "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 et:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"High-level interface for dropping dataset content\n\n\"\"\"\n\n__docformat__ = 'restructuredtext'\n\nimport logging\nfrom itertools import chain\nimport warnings\n\nfrom datalad.core.local.status import get_paths_by_ds\nfrom datalad.distribution.dataset import (\n    Dataset,\n    EnsureDataset,\n    datasetmethod,\n    require_dataset,\n)\nfrom datalad.interface.base import (\n    Interface,\n    build_doc,\n    eval_results,\n)\nfrom datalad.interface.common_opts import (\n    jobs_opt,\n    recursion_flag,\n    recursion_limit,\n)\nfrom datalad.interface.results import (\n    annexjson2result,\n    results_from_annex_noinfo,\n    success_status_map,\n)\nfrom datalad.runner.exception import CommandError\nfrom datalad.support.annexrepo import AnnexRepo\nfrom datalad.support.constraints import (\n    EnsureChoice,\n    EnsureNone,\n    EnsureStr,\n)\nfrom datalad.support.exceptions import CapturedException\nfrom datalad.support.param import Parameter\nfrom datalad.utils import (\n    ensure_list,\n    rmtree,\n)\n\nlgr = logging.getLogger('datalad.distributed.drop')\n\n\n@build_doc\nclass Drop(Interface):\n    \"\"\"Drop content of individual files or entire (sub)datasets\n\n    This command is the antagonist of 'get'. It can undo the retrieval of file\n    content, and the installation of subdatasets.\n\n    Dropping is a safe-by-default operation. Before dropping any information,\n    the command confirms the continued availability of file-content (see e.g.,\n    configuration 'annex.numcopies'), and the state of all dataset branches\n    from at least one known dataset sibling. Moreover, prior removal of an\n    entire dataset annex, that it is confirmed that it is no longer marked\n    as existing in the network of dataset siblings.\n\n    Importantly, all checks regarding version history availability and local\n    annex availability are performed using the current state of remote\n    siblings as known to the local dataset. This is done for performance\n    reasons and for resilience in case of absent network connectivity. To\n    ensure decision making based on up-to-date information, it is advised to\n    execute a dataset update before dropping dataset components.\n    \"\"\"\n    _examples_ = [\n        {'text': \"Drop single file content\",\n         'code_py': \"drop('path/to/file')\",\n         'code_cmd': \"datalad drop <path/to/file>\"},\n        {'text': \"Drop all file content in the current dataset\",\n         'code_py': \"drop('.')\",\n         'code_cmd': \"datalad drop\"},\n        {'text': \"Drop all file content in a dataset and all its subdatasets\",\n         'code_py': \"drop(dataset='.', recursive=True)\",\n         'code_cmd': \"datalad drop -d <path/to/dataset> -r\"},\n        {'text': \"Disable check to ensure the configured minimum number of \"\n                 \"remote sources for dropped data\",\n         'code_py': \"drop(path='path/to/content', reckless='availability')\",\n         'code_cmd': \"datalad drop <path/to/content> --reckless availability\"},\n        {'text': 'Drop (uninstall) an entire dataset '\n                 '(will fail with subdatasets present)',\n         'code_py': \"drop(what='all')\",\n         'code_cmd': \"datalad drop --what all\"},\n        {'text': 'Kill a dataset recklessly with any existing subdatasets too'\n                 '(this will be fast, but will disable any and all safety '\n                 'checks)',\n         'code_py': \"drop(what='all', reckless='kill', recursive=True)\",\n         'code_cmd': \"datalad drop --what all, --reckless kill --recursive\"},\n    ]\n\n    _params_ = dict(\n        dataset=Parameter(\n            args=(\"-d\", \"--dataset\"),\n            metavar=\"DATASET\",\n            doc=\"\"\"specify the dataset to perform drop from.\n            If no dataset is given, the current working directory is used\n            as operation context\"\"\",\n            constraints=EnsureDataset() | EnsureNone()),\n        path=Parameter(\n            args=(\"path\",),\n            metavar=\"PATH\",\n            doc=\"path of a dataset or dataset component to be dropped\",\n            nargs=\"*\",\n            constraints=EnsureStr() | EnsureNone()),\n        reckless=Parameter(\n            args=(\"--reckless\",),\n            doc=\"\"\"disable individual or all data safety measures that would\n            normally prevent potentially irreversible data-loss.\n            With 'modification', unsaved modifications in a dataset will not be\n            detected. This improves performance at the cost of permitting\n            potential loss of unsaved or untracked dataset components.\n            With 'availability', detection of dataset/branch-states that are\n            only available in the local dataset, and detection of an\n            insufficient number of file-content copies will be disabled.\n            Especially the latter is a potentially expensive check which might\n            involve numerous network transactions.\n            With 'undead', detection of whether a to-be-removed local annex is\n            still known to exist in the network of dataset-clones is disabled.\n            This could cause zombie-records of invalid file availability.\n            With 'kill', all safety-checks are disabled.\"\"\",\n            constraints=EnsureChoice(\n                'modification', 'availability', 'undead', 'kill', None)),\n        what=Parameter(\n            args=(\"--what\",),\n            doc=\"\"\"select what type of items shall be dropped.\n            With 'filecontent', only the file content (git-annex keys) of files\n            in a dataset's worktree will be dropped.\n            With 'allkeys', content of any version of any file in any branch\n            (including, but not limited to the worktree) will be dropped. This\n            effectively empties the annex of a local dataset.\n            With 'datasets', only complete datasets will be dropped (implies\n            'allkeys' mode for each such dataset), but no filecontent will be\n            dropped for any files in datasets that are not dropped entirely.\n            With 'all', content for any matching file or dataset will be dropped\n            entirely.\n            \"\"\",\n            # TODO add 'unwanted'\n            constraints=EnsureChoice('filecontent', 'allkeys', 'datasets', 'all')),\n        recursive=recursion_flag,\n        recursion_limit=recursion_limit,\n        jobs=jobs_opt,\n        check=Parameter(\n            args=(\"--nocheck\",),\n            doc=\"\"\"DEPRECATED: use '--reckless availability'\"\"\",\n            action=\"store_false\",\n            dest='check'),\n        if_dirty=Parameter(\n            args=(\"--if-dirty\",),\n            doc=\"\"\"DEPRECATED and IGNORED: use --reckless instead\"\"\",),\n    )\n\n    @staticmethod\n    @datasetmethod(name='drop')\n    @eval_results\n    def __call__(\n            path=None,\n            *,\n            what='filecontent',\n            reckless=None,\n            dataset=None,\n            recursive=False,\n            recursion_limit=None,\n            jobs=None,\n            # deprecated\n            check=None,\n            if_dirty=None):\n\n        # TODO if reckless is None, initialize from a potential config setting\n        # left behind by a reckless clone\n\n        # proper spelling of mode switches is critical for implementation\n        # below. double-check, also in Python API usage\n        # TODO consider making a generic helper\n        for label, value in (('what', what), ('reckless', reckless)):\n            try:\n                Drop._params_[label].constraints(value)\n            except ValueError as e:\n                raise ValueError(\n                    f\"Invalid '{label}' parameter value of: \"\n                    f\"{repr(value)} [{str(e)}]\") from e\n\n        if check is not None:\n            warnings.warn(\n                \"The `check` argument of `datalad drop` is deprecated, \"\n                \"use the `reckless` argument instead.\",\n                DeprecationWarning)\n        if if_dirty is not None:\n            warnings.warn(\n                \"The `if_dirty` argument of `datalad drop` is ignored, \"\n                \"it can be removed for a safe-by-default behavior. For \"\n                \"other cases consider the `reckless` argument.\",\n                DeprecationWarning)\n\n        if check is False:\n            if reckless is not None:\n                raise ValueError(\n                    'Must not use deprecated `check` argument, and new '\n                    '`reckless` argument together with `datalad drop`.')\n            reckless = 'availability'\n\n        if what in ('all', 'datasets') and reckless == 'kill' and not recursive:\n            raise ValueError(\n                'A reckless kill is requested but no recursion flag is set. '\n                \"With 'kill' no checks for subdatasets will be made, \"\n                'acknowledge by setting the recursive flag')\n\n        # we cannot test for what=='allkeys' and path==None here,\n        # on per each dataset. otherwise we will not be able to drop\n        # from a subdataset, by given its path -- desirable MIH thinks\n\n        ds = require_dataset(dataset, check_installed=True, purpose='drop')\n\n        res_props = dict(\n            logger=lgr,\n            refds=ds.path,\n        )\n        # if not paths are given, there will still be a single dataset record\n        # with paths==None\n        paths_by_ds, errors = get_paths_by_ds(\n            ds,\n            dataset,\n            ensure_list(path),\n            # XXX this needs more thought!! Maybe this is what the mode should be\n            # in general?!\n            # when we want to drop entire datasets, it is much more useful\n            # to have subdatasets be their own record\n            subdsroot_mode='sub'\n            if what in ('all', 'datasets')\n            else 'rsync',\n        )\n        for e in errors:\n            yield dict(\n                action='drop',\n                path=str(e),\n                status='error',\n                message=('path not underneath the reference dataset %s', ds),\n                **res_props)\n            # we are not returning, a caller could decide on failure mode\n\n        if what in ('all', 'datasets') and _paths_atunder_dir(\n                paths_by_ds, ds.pathobj.cwd()):\n            raise RuntimeError(\n                'refuse to perform actions that would remove the current '\n                'working directory')\n\n        lgr.debug('Discovered %i datasets to drop (from)', len(paths_by_ds))\n\n        # a dataset paths are normalized and guaranteed to be under the same\n        # root dataset\n        # we want to start from the leave datasets, such that all discovered\n        # dataset can be processed independently\n        for dpath in sorted(paths_by_ds.keys(), reverse=True):\n            d = Dataset(dpath)\n            lgr.debug('Starting to drop %s at %s', what, d)\n            for res in _drop_dataset(\n                    d,\n                    paths_by_ds[dpath],\n                    what=what,\n                    reckless=reckless,\n                    # recursion from any of the given paths!\n                    recursive=recursive,\n                    recursion_limit=recursion_limit,\n                    jobs=jobs):\n                yield dict(res, **res_props)\n            lgr.debug('Finished dropping %s at %s', what, d)\n        return\n\n\ndef _paths_atunder_dir(pbd, dirpath):\n    \"\"\"Whether any of the paths is at or under a reference path\n\n    Parameters\n    ----------\n    pbd: dict\n      Dataset path dict is produced by get_paths_by_ds()\n    dirpath: Path\n      Reference path\n\n    Returns\n    -------\n    bool\n    \"\"\"\n    for dpath, paths in pbd.items():\n        for p in ([dpath] if paths is None else paths):\n            if p == dirpath or p in dirpath.parents:\n                return True\n    return False\n\n\ndef _drop_dataset(ds, paths, what, reckless, recursive, recursion_limit, jobs):\n    lgr.debug('Start dropping for %s', ds)\n    # we know that any given path is part of `ds` and not any of its\n    # subdatasets!\n\n    # by-passing this completely with reckless=kill\n    if recursive and not reckless == 'kill':\n        # process subdatasets first with recursion\n        for sub in ds.subdatasets(\n                # must be resolved!\n                path=paths or None,\n                # nothing to drop with unavailable subdatasets\n                state='present',\n                # we can use the full recursion depth, only the first layer\n                # of calls to _drop_dataset() must/can have recursive=True\n                recursive=recursive,\n                recursion_limit=recursion_limit,\n                # start reporting with the leaves\n                bottomup=True,\n                result_xfm='datasets',\n                on_failure='ignore',\n                return_type='generator',\n                result_renderer='disabled'):\n            yield from _drop_dataset(\n                ds=sub,\n                # everything, the entire subdataset is matching a given path\n                paths=None,\n                what=what,\n                reckless=reckless,\n                recursive=False,\n                recursion_limit=None,\n                jobs=jobs)\n\n    if not ds.pathobj.exists():\n        # basic protection against something having wiped it out already.\n        # should not happen, but better than a crash, if it does\n        yield dict(\n            action='drop',\n            path=ds.path,\n            status='notneeded',\n            message=\"does not exist\",\n            type='dataset',\n        )\n        return\n\n    if paths is not None and paths != [ds.pathobj] and what == 'all':\n        # so we have paths constraints that prevent dropping the full dataset\n        lgr.debug('Only dropping file content for given paths in %s, '\n                  'allthough instruction was to drop %s', ds, what)\n        what = 'filecontent'\n\n    repo = ds.repo\n    is_annex = isinstance(repo, AnnexRepo)\n\n    # first critical checks that might prevent further operation\n    had_fatality = False\n    for res in _fatal_pre_drop_checks(\n            ds, repo, paths, what, reckless, is_annex):\n        had_fatality = True\n        yield res\n    if had_fatality:\n        return\n\n    # next check must come AFTER the modification checks above, otherwise\n    # remove() could not rely on the modification detection above\n    if paths is not None and paths != [ds.pathobj] and what == 'datasets':\n        # so we have paths constraints that prevent dropping the full dataset\n        # there is nothing to do here, but to drop keys, which we must not\n        # done\n        return\n\n    # now conditional/informative checks\n    yield from _pre_drop_checks(ds, repo, paths, what, reckless, is_annex)\n\n    if is_annex and what == 'filecontent':\n        yield from _drop_files(\n            ds,\n            repo,\n            # give paths or '.' with no constraint\n            paths=[str(p.relative_to(ds.pathobj))\n                   for p in paths] if paths else '.',\n            force=reckless in ('availability', 'kill'),\n            jobs=jobs,\n        )\n        # end it here for safety, the rest of the function deals with\n        # dropping more and more fundamentally\n        return\n\n    drop_all_errored = False\n    if is_annex and what in ('allkeys', 'datasets', 'all') \\\n            and not reckless == 'kill':\n        for r in _drop_allkeys(\n                ds,\n                repo,\n                force=reckless in ('availability',),\n                jobs=jobs):\n            res = dict(\n                action='drop',\n                type='key',\n                # use the path of the containing dataset\n                # using the location of the key does not add any\n                # practical value, and is expensive to obtain\n                path=ds.path,\n                status='ok' if r.get('success') else 'error',\n                key=r.get('key'),\n            )\n            # pull any note, and rename recommended parameter to\n            # avoid confusion\n            message = r.get('note', '').replace(\n                '--force',\n                '--reckless availability')\n            if message:\n                res['message'] = message\n            error_messages = r.get('error-messages')\n            if error_messages:\n                res['error_message'] = '\\n'.join(\n                    m.strip() for m in error_messages\n                )\n            # play safe, if there is no status, assume error\n            if res.get('status', 'error') != 'ok':\n                drop_all_errored = True\n            yield res\n\n    if drop_all_errored:\n        # end it here, if there is any indication that wiping out the\n        # repo is unsafe\n        return\n\n    if what in ('all', 'datasets'):\n        yield from _kill_dataset(ds)\n    lgr.debug('Done dropping for %s', ds)\n    return\n\n\ndef _fatal_pre_drop_checks(ds, repo, paths, what, reckless, is_annex):\n    if what == 'allkeys' and paths is not None \\\n            and paths != [ds.pathobj]:\n        yield dict(\n            action='drop',\n            path=ds.path,\n            type='dataset',\n            status='impossible',\n            message=(\n                'cannot drop %s, with path constraints given: %s',\n                what, paths),\n        )\n        return\n\n    if what in ('all', 'datasets') and not reckless == 'kill':\n        # we must not have subdatasets anymore\n        # if we do, --recursive was forgotton\n        subdatasets = ds.subdatasets(\n            path=paths,\n            # we only care about the present ones\n            state='present',\n            # first-level is enough, if that has none, there will be none\n            recursive=False,\n            result_xfm='paths',\n            result_renderer='disabled')\n        if subdatasets:\n            yield dict(\n                action='uninstall',\n                path=ds.path,\n                type='dataset',\n                status='error',\n                message=('cannot drop dataset, subdataset(s) still present '\n                         '(forgot --recursive?): %s', subdatasets)\n            )\n            # this is fatal\n            return\n\n    if what in ('all', 'datasets') \\\n            and reckless not in ('availability', 'kill') \\\n            and (paths is None or paths == [ds.pathobj]):\n        unpushed = _detect_unpushed_revs(repo, is_annex)\n        if unpushed:\n            yield dict(\n                action='uninstall',\n                path=ds.path,\n                type='dataset',\n                status='error',\n                message=(\n                    \"to-be-dropped dataset has revisions that \"\n                    \"are not available at any known sibling. Use \"\n                    \"`datalad push --to ...` to push \"\n                    \"these before dropping the local dataset, \"\n                    \"or ignore via `--reckless availability`. \"\n                    \"Unique revisions: %s\",\n                    unpushed)\n            )\n            # this is fatal\n            return\n\n    if is_annex and what in ('all', 'datasets') \\\n            and reckless not in ('undead', 'kill'):\n        # this annex is about to die, test if it is still considered\n        # not-dead. if so, complain to avoid generation of zombies\n        # (annexed that are floating around, but are actually dead).\n        # if repo.uuid is None, git annex init never ran, and we can skip this\n        remotes_that_know_this_annex = None if repo.uuid is None else [\n            r\n            for r in _detect_nondead_annex_at_remotes(repo, repo.uuid)\n            # filter out \"here\"\n            if r is not None\n        ]\n        if remotes_that_know_this_annex:\n            yield dict(\n                action='uninstall',\n                path=ds.path,\n                type='dataset',\n                status='error',\n                message=(\n                    \"to-be-deleted local annex not declared 'dead' at the \"\n                    \"following siblings. Announce death \"\n                    \"(`git annex dead here` + `datalad push --to ...`), \"\n                    \"or ignore via `--reckless undead`: %s\",\n                    remotes_that_know_this_annex)\n            )\n            # this is fatal\n            return\n\n    if reckless not in ('modification', 'kill'):\n        # do a cheaper status run to discover any kind of modification and\n        # generate results based on the `what` mode of operation\n        for res in ds.status(\n                path=paths,\n                # untracked content will not be reported on further down\n                # must catch it here\n                untracked='normal',\n                # downstream code can handle non-annex/annex distinction\n                # avoid expensive evaluation here\n                annex=False,\n                # we only need to inspect a subdataset's state in case\n                # we want to drop it completely\n                eval_subdataset_state='commit'\n                if what in ('all', 'datasets') else 'no',\n                # recursion is handled outside this function\n                recursive=False,\n                result_renderer='disabled',\n                return_type='generator',\n                on_failure='ignore'):\n            state = res.get('state')\n            if state == 'clean':\n                # nothing to worry about, nothing to communicate\n                continue\n            elif state in ('modified', 'untracked'):\n                yield dict(\n                    res,\n                    status='impossible',\n                    action='drop',\n                    message=f'cannot drop {state} content, save first',\n                )\n                # we are not aborting nevetheless. in normal conditions\n                # the above result will stop processing, but if desired\n                # we could go on\n            else:\n                lgr.debug(\n                    'Status record not considered for drop '\n                    'state inspection: %s', res)\n\n\ndef _pre_drop_checks(ds, repo, paths, what, reckless, is_annex):\n    if not is_annex and reckless not in ('datasets', 'kill'):\n        # we cannot drop content in non-annex repos, issue same\n        # 'notneeded' as for git-file in annex repo\n        for p in paths or [ds.path]:\n            yield dict(\n                action='drop',\n                path=str(p),\n                status='notneeded',\n                message=\"no annex'ed content\",\n            )\n        # continue, this is nothing fatal\n\n    if not is_annex and what in ('allkeys', 'unwanted') \\\n            and not reckless == 'kill':\n        # these drop modes are meaningless without an annex\n        yield dict(\n            action='drop',\n            path=ds.path,\n            status='notneeded',\n            message=\"dataset with no annex\",\n            type='dataset',\n        )\n        # continue, this is nothing fatal\n\n\ndef _detect_unpushed_revs(repo, consider_managed_branches):\n    \"\"\"Check if all local branch states (and HEAD) are available at a remote\n\n    There need not be a 1:1 correspondence. What is tested is whether\n    each commit corresponding to a local branch tip (or HEAD), is also an\n    ancestor of any remote branch. It is not required that there is a single\n    remote that has all commits.\n\n    This only uses the state of remotes known to the local remote state.\n    No remote synchronization is performed.\n\n    Parameters\n    ----------\n    repo: GitRepo\n      Repository to evaluated\n    consider_managed_branches: bool\n      Whether to enable handling of managed branches.\n\n    Returns\n    -------\n    list\n      Names of local states/refs that are no available at a remote.\n    \"\"\"\n    if consider_managed_branches:\n        # consolidate corresponding branches to get reliable detection\n        repo.localsync(managed_only=True)\n    # we do not want to check this for any managed branches\n    # that are not meant to be pushed without consolidation\n    # or even at all (incl. git-annex, it can behave in complex ways)\n    local_refs = [\n        lb for lb in repo.get_branches()\n        if not (not consider_managed_branches\n            or lb == 'git-annex' or repo.is_managed_branch(lb))]\n    if not repo.get_active_branch():\n        # check for HEAD, in case we are on a detached HEAD\n        local_refs.append('HEAD')\n    # extend to tags?\n    remote_refs = repo.get_remote_branches()\n\n    unpushed_refs = [\n        local_ref\n        for local_ref in local_refs\n        if not any(repo.is_ancestor(local_ref, remote_ref)\n                   for remote_ref in remote_refs)\n    ]\n    return unpushed_refs\n\n\ndef _detect_nondead_annex_at_remotes(repo, annex_uuid):\n    \"\"\"Return list of remote names that know about a given (not-dead) annex\n\n    This only uses the state of remotes known to the local remote state.\n    No remote synchronization is performed.\n\n    Parameters\n    ----------\n    repo: AnnexRepo or GitRepo\n      Repository to evaluated\n    annex_uuid: str\n      UUID string of a particular annex\n\n    Returns\n    -------\n    list\n      Names of any matching remote, the local repository is indicated using\n      a `None` label.\n    \"\"\"\n    # build the refs for all remotes and local\n    remotes_w_registration = []\n    for remote in chain([''], repo.get_remotes()):\n        refprefix = '{}{}git-annex:'.format(\n            remote,\n            '/' if remote else '',\n        )\n        uuid_known = False\n        try:\n            for line in repo.call_git_items_(\n                    ['cat-file', '-p', refprefix + 'uuid.log']):\n                if line.startswith(annex_uuid):\n                    # use None to label the local repo\n                    uuid_known = True\n                    break\n        except CommandError as e:\n            CapturedException(e)\n            # this is not a problem per-se, logged above, just continue\n            continue\n        if not uuid_known:\n            # if an annex id is not even in the uuid.log, we can stop here\n            # (for this remote)\n            continue\n\n        # annex is known, but maybe is declared dead already, must check\n        # trust.log in addition\n        try:\n            for line in repo.call_git_items_(\n                    ['cat-file', '-p', refprefix + 'trust.log']):\n                columns = line.split()\n                if columns[0] == annex_uuid:\n                    # not known if dead\n                    uuid_known = False if columns[1] == 'X' else True\n                    break\n        except CommandError as e:\n            CapturedException(e)\n            # this is not a problem per-se, logged above, just continue\n            continue\n        finally:\n            if uuid_known:\n                remotes_w_registration.append(remote or None)\n    return(remotes_w_registration)\n\n\ndef _kill_dataset(ds):\n    \"\"\"This is a harsh internal helper: it will wipe out a dataset, no checks\n    \"\"\"\n    # figure out whether we should be nice to a superdataset later on\n    has_super = ds.get_superdataset(topmost=False, registered_only=True)\n    # Close any possibly associated process etc with underlying repo.\n    # Otherwise - rmtree could fail to remove e.g. under NFS which would\n    # still have some files opened by them (thus having .nfs00000xxxx\n    # files) forbidding rmdir to work in rmtree\n    ds.close()\n    rmtree(ds.path)\n    # invalidate loaded ConfigManager -- datasets are singletons!!\n    ds._cfg = None\n    if has_super:\n        # recreate an empty mountpoint to make Git happier\n        ds.pathobj.mkdir(exist_ok=True)\n    yield dict(\n        # keep uninstall to please the gods of a distant past\n        #action='drop',\n        action='uninstall',\n        path=ds.path,\n        type='dataset',\n        status='ok',\n    )\n\n\ndef _drop_allkeys(ds, repo, force=False, jobs=None):\n    \"\"\"\n    \"\"\"\n    assert not (repo.dot_git / 'annex').is_symlink(), \\\n        \"Dropping from a symlinked annex is unsupported to prevent data-loss\"\n\n    cmd = ['drop', '--all']\n    if force:\n        cmd.append('--force')\n    if jobs:\n        cmd.extend(['--jobs', str(jobs)])\n\n    try:\n        yield from repo._call_annex_records_items_(cmd)\n    except CommandError as e:\n        # pick up the results captured so far and yield them\n        # the error will be amongst them\n        yield from e.kwargs.get('stdout_json', [])\n\n\ndef _drop_files(ds, repo, paths, force=False, jobs=None):\n    \"\"\"Helper to drop content in datasets.\n\n    Parameters\n    ----------\n    repo : AnnexRepo\n    paths : list\n      for which files to drop content\n    check : bool\n      whether to instruct annex to perform minimum copy availability\n      checks\n\n    Yields\n    ------\n    dict\n    \"\"\"\n    assert not (repo.dot_git / 'annex').is_symlink(), \\\n        \"Dropping from a symlinked annex is unsupported to prevent data-loss\"\n    cmd = ['drop']\n    if force:\n        cmd.append('--force')\n    if jobs:\n        cmd.extend(['--jobs', str(jobs)])\n\n    respath_by_status = {}\n    try:\n        yield from (\n            _postproc_annexdrop_result(res, respath_by_status, ds)\n            for res in repo._call_annex_records_items_(cmd, files=paths)\n        )\n    except CommandError as e:\n        # pick up the results captured so far and yield them\n        # the error will be amongst them\n        yield from (\n            _postproc_annexdrop_result(res, respath_by_status, ds)\n            for res in e.kwargs.get('stdout_json', [])\n        )\n    # report on things requested that annex was silent about\n    for r in results_from_annex_noinfo(\n            ds, paths, respath_by_status,\n            dir_fail_msg='could not drop some content in %s %s',\n            noinfo_dir_msg='nothing to drop from %s',\n            noinfo_file_msg=\"no annex'ed content\"):\n        r['action'] = 'drop'\n        yield r\n\n\ndef _postproc_annexdrop_result(res, respath_by_status, ds, **kwargs):\n    res = annexjson2result(\n        # annex reports are always about files\n        res, ds, type='file', **kwargs)\n    success = success_status_map[res['status']]\n    respath_by_status[success] = \\\n        respath_by_status.get(success, []) + [res['path']]\n    if res[\"status\"] == \"error\" and res[\"action\"] == \"drop\":\n        msg = res.get(\"message\", None)\n        if isinstance(msg, str) and \"Use --force to\" in msg:\n            # Avoid confusing datalad-drop callers with git-annex-drop's\n            # suggestion to use --force.\n            # Just mention reckless itself, do not go into the details\n            # of which mode. This is likely changing over time and\n            # adjusting this replacement will be forgotten.\n            res[\"message\"] = msg.replace(\n                \"--force\",\n                \"--reckless availability\")\n    return res\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 et:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"High-level interface for dropping dataset content\n\n\"\"\"\n\n__docformat__ = 'restructuredtext'\n\nimport logging\nfrom itertools import chain\nimport warnings\n\nfrom datalad.core.local.status import get_paths_by_ds\nfrom datalad.distribution.dataset import (\n    Dataset,\n    EnsureDataset,\n    datasetmethod,\n    require_dataset,\n)\nfrom datalad.interface.base import (\n    Interface,\n    build_doc,\n    eval_results,\n)\nfrom datalad.interface.common_opts import (\n    jobs_opt,\n    recursion_flag,\n    recursion_limit,\n)\nfrom datalad.interface.results import (\n    annexjson2result,\n    results_from_annex_noinfo,\n    success_status_map,\n)\nfrom datalad.runner.exception import CommandError\nfrom datalad.support.annexrepo import AnnexRepo\nfrom datalad.support.constraints import (\n    EnsureChoice,\n    EnsureNone,\n    EnsureStr,\n)\nfrom datalad.support.exceptions import CapturedException\nfrom datalad.support.param import Parameter\nfrom datalad.utils import (\n    ensure_list,\n    rmtree,\n)\n\nlgr = logging.getLogger('datalad.distributed.drop')\n\n\n@build_doc\nclass Drop(Interface):\n    \"\"\"Drop content of individual files or entire (sub)datasets\n\n    This command is the antagonist of 'get'. It can undo the retrieval of file\n    content, and the installation of subdatasets.\n\n    Dropping is a safe-by-default operation. Before dropping any information,\n    the command confirms the continued availability of file-content (see e.g.,\n    configuration 'annex.numcopies'), and the state of all dataset branches\n    from at least one known dataset sibling. Moreover, prior removal of an\n    entire dataset annex, that it is confirmed that it is no longer marked\n    as existing in the network of dataset siblings.\n\n    Importantly, all checks regarding version history availability and local\n    annex availability are performed using the current state of remote\n    siblings as known to the local dataset. This is done for performance\n    reasons and for resilience in case of absent network connectivity. To\n    ensure decision making based on up-to-date information, it is advised to\n    execute a dataset update before dropping dataset components.\n    \"\"\"\n    _examples_ = [\n        {'text': \"Drop single file content\",\n         'code_py': \"drop('path/to/file')\",\n         'code_cmd': \"datalad drop <path/to/file>\"},\n        {'text': \"Drop all file content in the current dataset\",\n         'code_py': \"drop('.')\",\n         'code_cmd': \"datalad drop\"},\n        {'text': \"Drop all file content in a dataset and all its subdatasets\",\n         'code_py': \"drop(dataset='.', recursive=True)\",\n         'code_cmd': \"datalad drop -d <path/to/dataset> -r\"},\n        {'text': \"Disable check to ensure the configured minimum number of \"\n                 \"remote sources for dropped data\",\n         'code_py': \"drop(path='path/to/content', reckless='availability')\",\n         'code_cmd': \"datalad drop <path/to/content> --reckless availability\"},\n        {'text': 'Drop (uninstall) an entire dataset '\n                 '(will fail with subdatasets present)',\n         'code_py': \"drop(what='all')\",\n         'code_cmd': \"datalad drop --what all\"},\n        {'text': 'Kill a dataset recklessly with any existing subdatasets too'\n                 '(this will be fast, but will disable any and all safety '\n                 'checks)',\n         'code_py': \"drop(what='all', reckless='kill', recursive=True)\",\n         'code_cmd': \"datalad drop --what all, --reckless kill --recursive\"},\n    ]\n\n    _params_ = dict(\n        dataset=Parameter(\n            args=(\"-d\", \"--dataset\"),\n            metavar=\"DATASET\",\n            doc=\"\"\"specify the dataset to perform drop from.\n            If no dataset is given, the current working directory is used\n            as operation context\"\"\",\n            constraints=EnsureDataset() | EnsureNone()),\n        path=Parameter(\n            args=(\"path\",),\n            metavar=\"PATH\",\n            doc=\"path of a dataset or dataset component to be dropped\",\n            nargs=\"*\",\n            constraints=EnsureStr() | EnsureNone()),\n        reckless=Parameter(\n            args=(\"--reckless\",),\n            doc=\"\"\"disable individual or all data safety measures that would\n            normally prevent potentially irreversible data-loss.\n            With 'modification', unsaved modifications in a dataset will not be\n            detected. This improves performance at the cost of permitting\n            potential loss of unsaved or untracked dataset components.\n            With 'availability', detection of dataset/branch-states that are\n            only available in the local dataset, and detection of an\n            insufficient number of file-content copies will be disabled.\n            Especially the latter is a potentially expensive check which might\n            involve numerous network transactions.\n            With 'undead', detection of whether a to-be-removed local annex is\n            still known to exist in the network of dataset-clones is disabled.\n            This could cause zombie-records of invalid file availability.\n            With 'kill', all safety-checks are disabled.\"\"\",\n            constraints=EnsureChoice(\n                'modification', 'availability', 'undead', 'kill', None)),\n        what=Parameter(\n            args=(\"--what\",),\n            doc=\"\"\"select what type of items shall be dropped.\n            With 'filecontent', only the file content (git-annex keys) of files\n            in a dataset's worktree will be dropped.\n            With 'allkeys', content of any version of any file in any branch\n            (including, but not limited to the worktree) will be dropped. This\n            effectively empties the annex of a local dataset.\n            With 'datasets', only complete datasets will be dropped (implies\n            'allkeys' mode for each such dataset), but no filecontent will be\n            dropped for any files in datasets that are not dropped entirely.\n            With 'all', content for any matching file or dataset will be dropped\n            entirely.\n            \"\"\",\n            # TODO add 'unwanted'\n            constraints=EnsureChoice('filecontent', 'allkeys', 'datasets', 'all')),\n        recursive=recursion_flag,\n        recursion_limit=recursion_limit,\n        jobs=jobs_opt,\n        check=Parameter(\n            args=(\"--nocheck\",),\n            doc=\"\"\"DEPRECATED: use '--reckless availability'\"\"\",\n            action=\"store_false\",\n            dest='check'),\n        if_dirty=Parameter(\n            args=(\"--if-dirty\",),\n            doc=\"\"\"DEPRECATED and IGNORED: use --reckless instead\"\"\",),\n    )\n\n    @staticmethod\n    @datasetmethod(name='drop')\n    @eval_results\n    def __call__(\n            path=None,\n            *,\n            what='filecontent',\n            reckless=None,\n            dataset=None,\n            recursive=False,\n            recursion_limit=None,\n            jobs=None,\n            # deprecated\n            check=None,\n            if_dirty=None):\n\n        # TODO if reckless is None, initialize from a potential config setting\n        # left behind by a reckless clone\n\n        # proper spelling of mode switches is critical for implementation\n        # below. double-check, also in Python API usage\n        # TODO consider making a generic helper\n        for label, value in (('what', what), ('reckless', reckless)):\n            try:\n                Drop._params_[label].constraints(value)\n            except ValueError as e:\n                raise ValueError(\n                    f\"Invalid '{label}' parameter value of: \"\n                    f\"{repr(value)} [{str(e)}]\") from e\n\n        if check is not None:\n            warnings.warn(\n                \"The `check` argument of `datalad drop` is deprecated, \"\n                \"use the `reckless` argument instead.\",\n                DeprecationWarning)\n        if if_dirty is not None:\n            warnings.warn(\n                \"The `if_dirty` argument of `datalad drop` is ignored, \"\n                \"it can be removed for a safe-by-default behavior. For \"\n                \"other cases consider the `reckless` argument.\",\n                DeprecationWarning)\n\n        if check is False:\n            if reckless is not None:\n                raise ValueError(\n                    'Must not use deprecated `check` argument, and new '\n                    '`reckless` argument together with `datalad drop`.')\n            reckless = 'availability'\n\n        if what in ('all', 'datasets') and reckless == 'kill' and not recursive:\n            raise ValueError(\n                'A reckless kill is requested but no recursion flag is set. '\n                \"With 'kill' no checks for subdatasets will be made, \"\n                'acknowledge by setting the recursive flag')\n\n        # we cannot test for what=='allkeys' and path==None here,\n        # on per each dataset. otherwise we will not be able to drop\n        # from a subdataset, by given its path -- desirable MIH thinks\n\n        ds = require_dataset(dataset, check_installed=True, purpose='drop')\n\n        res_props = dict(\n            logger=lgr,\n            refds=ds.path,\n        )\n        # if not paths are given, there will still be a single dataset record\n        # with paths==None\n        paths_by_ds, errors = get_paths_by_ds(\n            ds,\n            dataset,\n            ensure_list(path),\n            # XXX this needs more thought!! Maybe this is what the mode should be\n            # in general?!\n            # when we want to drop entire datasets, it is much more useful\n            # to have subdatasets be their own record\n            subdsroot_mode='sub'\n            if what in ('all', 'datasets')\n            else 'rsync',\n        )\n        for e in errors:\n            yield dict(\n                action='drop',\n                path=str(e),\n                status='error',\n                message=('path not underneath the reference dataset %s', ds),\n                **res_props)\n            # we are not returning, a caller could decide on failure mode\n\n        if what in ('all', 'datasets') and _paths_atunder_dir(\n                paths_by_ds, ds.pathobj.cwd()):\n            raise RuntimeError(\n                'refuse to perform actions that would remove the current '\n                'working directory')\n\n        lgr.debug('Discovered %i datasets to drop (from)', len(paths_by_ds))\n\n        # a dataset paths are normalized and guaranteed to be under the same\n        # root dataset\n        # we want to start from the leave datasets, such that all discovered\n        # dataset can be processed independently\n        for dpath in sorted(paths_by_ds.keys(), reverse=True):\n            d = Dataset(dpath)\n            lgr.debug('Starting to drop %s at %s', what, d)\n            for res in _drop_dataset(\n                    d,\n                    paths_by_ds[dpath],\n                    what=what,\n                    reckless=reckless,\n                    # recursion from any of the given paths!\n                    recursive=recursive,\n                    recursion_limit=recursion_limit,\n                    jobs=jobs):\n                yield dict(res, **res_props)\n            lgr.debug('Finished dropping %s at %s', what, d)\n        return\n\n\ndef _paths_atunder_dir(pbd, dirpath):\n    \"\"\"Whether any of the paths is at or under a reference path\n\n    Parameters\n    ----------\n    pbd: dict\n      Dataset path dict is produced by get_paths_by_ds()\n    dirpath: Path\n      Reference path\n\n    Returns\n    -------\n    bool\n    \"\"\"\n    for dpath, paths in pbd.items():\n        for p in ([dpath] if paths is None else paths):\n            if p == dirpath or p in dirpath.parents:\n                return True\n    return False\n\n\ndef _drop_dataset(ds, paths, what, reckless, recursive, recursion_limit, jobs):\n    lgr.debug('Start dropping for %s', ds)\n    # we know that any given path is part of `ds` and not any of its\n    # subdatasets!\n\n    # by-passing this completely with reckless=kill\n    if recursive and not reckless == 'kill':\n        # process subdatasets first with recursion\n        for sub in ds.subdatasets(\n                # must be resolved!\n                path=paths or None,\n                # nothing to drop with unavailable subdatasets\n                state='present',\n                # we can use the full recursion depth, only the first layer\n                # of calls to _drop_dataset() must/can have recursive=True\n                recursive=recursive,\n                recursion_limit=recursion_limit,\n                # start reporting with the leaves\n                bottomup=True,\n                result_xfm='datasets',\n                on_failure='ignore',\n                return_type='generator',\n                result_renderer='disabled'):\n            yield from _drop_dataset(\n                ds=sub,\n                # everything, the entire subdataset is matching a given path\n                paths=None,\n                what=what,\n                reckless=reckless,\n                recursive=False,\n                recursion_limit=None,\n                jobs=jobs)\n\n    if not ds.pathobj.exists():\n        # basic protection against something having wiped it out already.\n        # should not happen, but better than a crash, if it does\n        yield dict(\n            action='drop',\n            path=ds.path,\n            status='notneeded',\n            message=\"does not exist\",\n            type='dataset',\n        )\n        return\n\n    if paths is not None and paths != [ds.pathobj] and what == 'all':\n        if recursive and reckless == 'kill':\n            # check if any paths contains a subdataset, and if so, drop it to\n            # ensure its not left behind\n            for sub in ds.subdatasets(\n                    # just check for subds at the provided path\n                    path=paths,\n                    state='present',\n                    recursive=recursive,\n                    recursion_limit=recursion_limit,\n                    result_xfm='datasets',\n                    on_failure='ignore',\n                    return_type='generator',\n                    result_renderer='disabled'):\n                if sub is not None:\n                    # there is a subdataset underneath the given path\n                    yield from _drop_dataset(\n                        ds=sub,\n                        # everything underneath the subds can go\n                        paths=None,\n                        what=what,\n                        reckless=reckless,\n                        recursive=False,\n                        recursion_limit=None,\n                        jobs=jobs)\n        # so we have paths constraints that prevent dropping the full dataset\n        lgr.debug('Only dropping file content for given paths in %s, '\n                  'allthough instruction was to drop %s', ds, what)\n        what = 'filecontent'\n\n    repo = ds.repo\n    is_annex = isinstance(repo, AnnexRepo)\n\n    # first critical checks that might prevent further operation\n    had_fatality = False\n    for res in _fatal_pre_drop_checks(\n            ds, repo, paths, what, reckless, is_annex):\n        had_fatality = True\n        yield res\n    if had_fatality:\n        return\n\n    # next check must come AFTER the modification checks above, otherwise\n    # remove() could not rely on the modification detection above\n    if paths is not None and paths != [ds.pathobj] and what == 'datasets':\n        # so we have paths constraints that prevent dropping the full dataset\n        # there is nothing to do here, but to drop keys, which we must not\n        # done\n        return\n\n    # now conditional/informative checks\n    yield from _pre_drop_checks(ds, repo, paths, what, reckless, is_annex)\n\n    if is_annex and what == 'filecontent':\n        yield from _drop_files(\n            ds,\n            repo,\n            # give paths or '.' with no constraint\n            paths=[str(p.relative_to(ds.pathobj))\n                   for p in paths] if paths else '.',\n            force=reckless in ('availability', 'kill'),\n            jobs=jobs,\n        )\n        # end it here for safety, the rest of the function deals with\n        # dropping more and more fundamentally\n        return\n\n    drop_all_errored = False\n    if is_annex and what in ('allkeys', 'datasets', 'all') \\\n            and not reckless == 'kill':\n        for r in _drop_allkeys(\n                ds,\n                repo,\n                force=reckless in ('availability',),\n                jobs=jobs):\n            res = dict(\n                action='drop',\n                type='key',\n                # use the path of the containing dataset\n                # using the location of the key does not add any\n                # practical value, and is expensive to obtain\n                path=ds.path,\n                status='ok' if r.get('success') else 'error',\n                key=r.get('key'),\n            )\n            # pull any note, and rename recommended parameter to\n            # avoid confusion\n            message = r.get('note', '').replace(\n                '--force',\n                '--reckless availability')\n            if message:\n                res['message'] = message\n            error_messages = r.get('error-messages')\n            if error_messages:\n                res['error_message'] = '\\n'.join(\n                    m.strip() for m in error_messages\n                )\n            # play safe, if there is no status, assume error\n            if res.get('status', 'error') != 'ok':\n                drop_all_errored = True\n            yield res\n\n    if drop_all_errored:\n        # end it here, if there is any indication that wiping out the\n        # repo is unsafe\n        return\n\n    if what in ('all', 'datasets'):\n        yield from _kill_dataset(ds)\n    lgr.debug('Done dropping for %s', ds)\n    return\n\n\ndef _fatal_pre_drop_checks(ds, repo, paths, what, reckless, is_annex):\n    if what == 'allkeys' and paths is not None \\\n            and paths != [ds.pathobj]:\n        yield dict(\n            action='drop',\n            path=ds.path,\n            type='dataset',\n            status='impossible',\n            message=(\n                'cannot drop %s, with path constraints given: %s',\n                what, paths),\n        )\n        return\n\n    if what in ('all', 'datasets') and not reckless == 'kill':\n        # we must not have subdatasets anymore\n        # if we do, --recursive was forgotton\n        subdatasets = ds.subdatasets(\n            path=paths,\n            # we only care about the present ones\n            state='present',\n            # first-level is enough, if that has none, there will be none\n            recursive=False,\n            result_xfm='paths',\n            result_renderer='disabled')\n        if subdatasets:\n            yield dict(\n                action='uninstall',\n                path=ds.path,\n                type='dataset',\n                status='error',\n                message=('cannot drop dataset, subdataset(s) still present '\n                         '(forgot --recursive?): %s', subdatasets)\n            )\n            # this is fatal\n            return\n\n    if what in ('all', 'datasets') \\\n            and reckless not in ('availability', 'kill') \\\n            and (paths is None or paths == [ds.pathobj]):\n        unpushed = _detect_unpushed_revs(repo, is_annex)\n        if unpushed:\n            yield dict(\n                action='uninstall',\n                path=ds.path,\n                type='dataset',\n                status='error',\n                message=(\n                    \"to-be-dropped dataset has revisions that \"\n                    \"are not available at any known sibling. Use \"\n                    \"`datalad push --to ...` to push \"\n                    \"these before dropping the local dataset, \"\n                    \"or ignore via `--reckless availability`. \"\n                    \"Unique revisions: %s\",\n                    unpushed)\n            )\n            # this is fatal\n            return\n\n    if is_annex and what in ('all', 'datasets') \\\n            and reckless not in ('undead', 'kill'):\n        # this annex is about to die, test if it is still considered\n        # not-dead. if so, complain to avoid generation of zombies\n        # (annexed that are floating around, but are actually dead).\n        # if repo.uuid is None, git annex init never ran, and we can skip this\n        remotes_that_know_this_annex = None if repo.uuid is None else [\n            r\n            for r in _detect_nondead_annex_at_remotes(repo, repo.uuid)\n            # filter out \"here\"\n            if r is not None\n        ]\n        if remotes_that_know_this_annex:\n            yield dict(\n                action='uninstall',\n                path=ds.path,\n                type='dataset',\n                status='error',\n                message=(\n                    \"to-be-deleted local annex not declared 'dead' at the \"\n                    \"following siblings. Announce death \"\n                    \"(`git annex dead here` + `datalad push --to ...`), \"\n                    \"or ignore via `--reckless undead`: %s\",\n                    remotes_that_know_this_annex)\n            )\n            # this is fatal\n            return\n\n    if reckless not in ('modification', 'kill'):\n        # do a cheaper status run to discover any kind of modification and\n        # generate results based on the `what` mode of operation\n        for res in ds.status(\n                path=paths,\n                # untracked content will not be reported on further down\n                # must catch it here\n                untracked='normal',\n                # downstream code can handle non-annex/annex distinction\n                # avoid expensive evaluation here\n                annex=False,\n                # we only need to inspect a subdataset's state in case\n                # we want to drop it completely\n                eval_subdataset_state='commit'\n                if what in ('all', 'datasets') else 'no',\n                # recursion is handled outside this function\n                recursive=False,\n                result_renderer='disabled',\n                return_type='generator',\n                on_failure='ignore'):\n            state = res.get('state')\n            if state == 'clean':\n                # nothing to worry about, nothing to communicate\n                continue\n            elif state in ('modified', 'untracked'):\n                yield dict(\n                    res,\n                    status='impossible',\n                    action='drop',\n                    message=f'cannot drop {state} content, save first',\n                )\n                # we are not aborting nevetheless. in normal conditions\n                # the above result will stop processing, but if desired\n                # we could go on\n            else:\n                lgr.debug(\n                    'Status record not considered for drop '\n                    'state inspection: %s', res)\n\n\ndef _pre_drop_checks(ds, repo, paths, what, reckless, is_annex):\n    if not is_annex and reckless not in ('datasets', 'kill'):\n        # we cannot drop content in non-annex repos, issue same\n        # 'notneeded' as for git-file in annex repo\n        for p in paths or [ds.path]:\n            yield dict(\n                action='drop',\n                path=str(p),\n                status='notneeded',\n                message=\"no annex'ed content\",\n            )\n        # continue, this is nothing fatal\n\n    if not is_annex and what in ('allkeys', 'unwanted') \\\n            and not reckless == 'kill':\n        # these drop modes are meaningless without an annex\n        yield dict(\n            action='drop',\n            path=ds.path,\n            status='notneeded',\n            message=\"dataset with no annex\",\n            type='dataset',\n        )\n        # continue, this is nothing fatal\n\n\ndef _detect_unpushed_revs(repo, consider_managed_branches):\n    \"\"\"Check if all local branch states (and HEAD) are available at a remote\n\n    There need not be a 1:1 correspondence. What is tested is whether\n    each commit corresponding to a local branch tip (or HEAD), is also an\n    ancestor of any remote branch. It is not required that there is a single\n    remote that has all commits.\n\n    This only uses the state of remotes known to the local remote state.\n    No remote synchronization is performed.\n\n    Parameters\n    ----------\n    repo: GitRepo\n      Repository to evaluated\n    consider_managed_branches: bool\n      Whether to enable handling of managed branches.\n\n    Returns\n    -------\n    list\n      Names of local states/refs that are no available at a remote.\n    \"\"\"\n    if consider_managed_branches:\n        # consolidate corresponding branches to get reliable detection\n        repo.localsync(managed_only=True)\n    # we do not want to check this for any managed branches\n    # that are not meant to be pushed without consolidation\n    # or even at all (incl. git-annex, it can behave in complex ways)\n    local_refs = [\n        lb for lb in repo.get_branches()\n        if not (not consider_managed_branches\n            or lb == 'git-annex' or repo.is_managed_branch(lb))]\n    if not repo.get_active_branch():\n        # check for HEAD, in case we are on a detached HEAD\n        local_refs.append('HEAD')\n    # extend to tags?\n    remote_refs = repo.get_remote_branches()\n\n    unpushed_refs = [\n        local_ref\n        for local_ref in local_refs\n        if not any(repo.is_ancestor(local_ref, remote_ref)\n                   for remote_ref in remote_refs)\n    ]\n    return unpushed_refs\n\n\ndef _detect_nondead_annex_at_remotes(repo, annex_uuid):\n    \"\"\"Return list of remote names that know about a given (not-dead) annex\n\n    This only uses the state of remotes known to the local remote state.\n    No remote synchronization is performed.\n\n    Parameters\n    ----------\n    repo: AnnexRepo or GitRepo\n      Repository to evaluated\n    annex_uuid: str\n      UUID string of a particular annex\n\n    Returns\n    -------\n    list\n      Names of any matching remote, the local repository is indicated using\n      a `None` label.\n    \"\"\"\n    # build the refs for all remotes and local\n    remotes_w_registration = []\n    for remote in chain([''], repo.get_remotes()):\n        refprefix = '{}{}git-annex:'.format(\n            remote,\n            '/' if remote else '',\n        )\n        uuid_known = False\n        try:\n            for line in repo.call_git_items_(\n                    ['cat-file', '-p', refprefix + 'uuid.log']):\n                if line.startswith(annex_uuid):\n                    # use None to label the local repo\n                    uuid_known = True\n                    break\n        except CommandError as e:\n            CapturedException(e)\n            # this is not a problem per-se, logged above, just continue\n            continue\n        if not uuid_known:\n            # if an annex id is not even in the uuid.log, we can stop here\n            # (for this remote)\n            continue\n\n        # annex is known, but maybe is declared dead already, must check\n        # trust.log in addition\n        try:\n            for line in repo.call_git_items_(\n                    ['cat-file', '-p', refprefix + 'trust.log']):\n                columns = line.split()\n                if columns[0] == annex_uuid:\n                    # not known if dead\n                    uuid_known = False if columns[1] == 'X' else True\n                    break\n        except CommandError as e:\n            CapturedException(e)\n            # this is not a problem per-se, logged above, just continue\n            continue\n        finally:\n            if uuid_known:\n                remotes_w_registration.append(remote or None)\n    return(remotes_w_registration)\n\n\ndef _kill_dataset(ds):\n    \"\"\"This is a harsh internal helper: it will wipe out a dataset, no checks\n    \"\"\"\n    # figure out whether we should be nice to a superdataset later on\n    has_super = ds.get_superdataset(topmost=False, registered_only=True)\n    # Close any possibly associated process etc with underlying repo.\n    # Otherwise - rmtree could fail to remove e.g. under NFS which would\n    # still have some files opened by them (thus having .nfs00000xxxx\n    # files) forbidding rmdir to work in rmtree\n    ds.close()\n    rmtree(ds.path)\n    # invalidate loaded ConfigManager -- datasets are singletons!!\n    ds._cfg = None\n    if has_super:\n        # recreate an empty mountpoint to make Git happier\n        ds.pathobj.mkdir(exist_ok=True)\n    yield dict(\n        # keep uninstall to please the gods of a distant past\n        #action='drop',\n        action='uninstall',\n        path=ds.path,\n        type='dataset',\n        status='ok',\n    )\n\n\ndef _drop_allkeys(ds, repo, force=False, jobs=None):\n    \"\"\"\n    \"\"\"\n    assert not (repo.dot_git / 'annex').is_symlink(), \\\n        \"Dropping from a symlinked annex is unsupported to prevent data-loss\"\n\n    cmd = ['drop', '--all']\n    if force:\n        cmd.append('--force')\n    if jobs:\n        cmd.extend(['--jobs', str(jobs)])\n\n    try:\n        yield from repo._call_annex_records_items_(cmd)\n    except CommandError as e:\n        # pick up the results captured so far and yield them\n        # the error will be amongst them\n        yield from e.kwargs.get('stdout_json', [])\n\n\ndef _drop_files(ds, repo, paths, force=False, jobs=None):\n    \"\"\"Helper to drop content in datasets.\n\n    Parameters\n    ----------\n    repo : AnnexRepo\n    paths : list\n      for which files to drop content\n    check : bool\n      whether to instruct annex to perform minimum copy availability\n      checks\n\n    Yields\n    ------\n    dict\n    \"\"\"\n    assert not (repo.dot_git / 'annex').is_symlink(), \\\n        \"Dropping from a symlinked annex is unsupported to prevent data-loss\"\n    cmd = ['drop']\n    if force:\n        cmd.append('--force')\n    if jobs:\n        cmd.extend(['--jobs', str(jobs)])\n\n    respath_by_status = {}\n    try:\n        yield from (\n            _postproc_annexdrop_result(res, respath_by_status, ds)\n            for res in repo._call_annex_records_items_(cmd, files=paths)\n        )\n    except CommandError as e:\n        # pick up the results captured so far and yield them\n        # the error will be amongst them\n        yield from (\n            _postproc_annexdrop_result(res, respath_by_status, ds)\n            for res in e.kwargs.get('stdout_json', [])\n        )\n    # report on things requested that annex was silent about\n    for r in results_from_annex_noinfo(\n            ds, paths, respath_by_status,\n            dir_fail_msg='could not drop some content in %s %s',\n            noinfo_dir_msg='nothing to drop from %s',\n            noinfo_file_msg=\"no annex'ed content\"):\n        r['action'] = 'drop'\n        yield r\n\n\ndef _postproc_annexdrop_result(res, respath_by_status, ds, **kwargs):\n    res = annexjson2result(\n        # annex reports are always about files\n        res, ds, type='file', **kwargs)\n    success = success_status_map[res['status']]\n    respath_by_status[success] = \\\n        respath_by_status.get(success, []) + [res['path']]\n    if res[\"status\"] == \"error\" and res[\"action\"] == \"drop\":\n        msg = res.get(\"message\", None)\n        if isinstance(msg, str) and \"Use --force to\" in msg:\n            # Avoid confusing datalad-drop callers with git-annex-drop's\n            # suggestion to use --force.\n            # Just mention reckless itself, do not go into the details\n            # of which mode. This is likely changing over time and\n            # adjusting this replacement will be forgotten.\n            res[\"message\"] = msg.replace(\n                \"--force\",\n                \"--reckless availability\")\n    return res\n",
            "header": {
                "file": {
                    "path": "datalad/distributed/drop.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "5141148e2",
                "new_commit_hash": "ce2d40f5d",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/datalad/distributed/drop.py"
            },
            "plus_file": {
                "path": "b/datalad/distributed/drop.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 346,
                            "length": 6
                        },
                        "new_range": {
                            "start": 346,
                            "length": 30
                        },
                        "section": "def _drop_dataset(ds, paths, what, reckless, recursive, recursion_limit, jobs):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        return",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    if paths is not None and paths != [ds.pathobj] and what == 'all':",
                                "type": "context"
                            },
                            {
                                "content": "        if recursive and reckless == 'kill':",
                                "type": "added"
                            },
                            {
                                "content": "            # check if any paths contains a subdataset, and if so, drop it to",
                                "type": "added"
                            },
                            {
                                "content": "            # ensure its not left behind",
                                "type": "added"
                            },
                            {
                                "content": "            for sub in ds.subdatasets(",
                                "type": "added"
                            },
                            {
                                "content": "                    # just check for subds at the provided path",
                                "type": "added"
                            },
                            {
                                "content": "                    path=paths,",
                                "type": "added"
                            },
                            {
                                "content": "                    state='present',",
                                "type": "added"
                            },
                            {
                                "content": "                    recursive=recursive,",
                                "type": "added"
                            },
                            {
                                "content": "                    recursion_limit=recursion_limit,",
                                "type": "added"
                            },
                            {
                                "content": "                    result_xfm='datasets',",
                                "type": "added"
                            },
                            {
                                "content": "                    on_failure='ignore',",
                                "type": "added"
                            },
                            {
                                "content": "                    return_type='generator',",
                                "type": "added"
                            },
                            {
                                "content": "                    result_renderer='disabled'):",
                                "type": "added"
                            },
                            {
                                "content": "                if sub is not None:",
                                "type": "added"
                            },
                            {
                                "content": "                    # there is a subdataset underneath the given path",
                                "type": "added"
                            },
                            {
                                "content": "                    yield from _drop_dataset(",
                                "type": "added"
                            },
                            {
                                "content": "                        ds=sub,",
                                "type": "added"
                            },
                            {
                                "content": "                        # everything underneath the subds can go",
                                "type": "added"
                            },
                            {
                                "content": "                        paths=None,",
                                "type": "added"
                            },
                            {
                                "content": "                        what=what,",
                                "type": "added"
                            },
                            {
                                "content": "                        reckless=reckless,",
                                "type": "added"
                            },
                            {
                                "content": "                        recursive=False,",
                                "type": "added"
                            },
                            {
                                "content": "                        recursion_limit=None,",
                                "type": "added"
                            },
                            {
                                "content": "                        jobs=jobs)",
                                "type": "added"
                            },
                            {
                                "content": "        # so we have paths constraints that prevent dropping the full dataset",
                                "type": "context"
                            },
                            {
                                "content": "        lgr.debug('Only dropping file content for given paths in %s, '",
                                "type": "context"
                            },
                            {
                                "content": "                  'allthough instruction was to drop %s', ds, what)",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/distributed/drop.py",
                            "type": "function",
                            "name": "_drop_dataset",
                            "content": "def _drop_dataset(ds, paths, what, reckless, recursive, recursion_limit, jobs):\n    lgr.debug('Start dropping for %s', ds)\n    # we know that any given path is part of `ds` and not any of its\n    # subdatasets!\n\n    # by-passing this completely with reckless=kill\n    if recursive and not reckless == 'kill':\n        # process subdatasets first with recursion\n        for sub in ds.subdatasets(\n                # must be resolved!\n                path=paths or None,\n                # nothing to drop with unavailable subdatasets\n                state='present',\n                # we can use the full recursion depth, only the first layer\n                # of calls to _drop_dataset() must/can have recursive=True\n                recursive=recursive,\n                recursion_limit=recursion_limit,\n                # start reporting with the leaves\n                bottomup=True,\n                result_xfm='datasets',\n                on_failure='ignore',\n                return_type='generator',\n                result_renderer='disabled'):\n            yield from _drop_dataset(\n                ds=sub,\n                # everything, the entire subdataset is matching a given path\n                paths=None,\n                what=what,\n                reckless=reckless,\n                recursive=False,\n                recursion_limit=None,\n                jobs=jobs)\n\n    if not ds.pathobj.exists():\n        # basic protection against something having wiped it out already.\n        # should not happen, but better than a crash, if it does\n        yield dict(\n            action='drop',\n            path=ds.path,\n            status='notneeded',\n            message=\"does not exist\",\n            type='dataset',\n        )\n        return\n\n    if paths is not None and paths != [ds.pathobj] and what == 'all':\n        if recursive and reckless == 'kill':\n            # check if any paths contains a subdataset, and if so, drop it to\n            # ensure its not left behind\n            for sub in ds.subdatasets(\n                    # just check for subds at the provided path\n                    path=paths,\n                    state='present',\n                    recursive=recursive,\n                    recursion_limit=recursion_limit,\n                    result_xfm='datasets',\n                    on_failure='ignore',\n                    return_type='generator',\n                    result_renderer='disabled'):\n                if sub is not None:\n                    # there is a subdataset underneath the given path\n                    yield from _drop_dataset(\n                        ds=sub,\n                        # everything underneath the subds can go\n                        paths=None,\n                        what=what,\n                        reckless=reckless,\n                        recursive=False,\n                        recursion_limit=None,\n                        jobs=jobs)\n        # so we have paths constraints that prevent dropping the full dataset\n        lgr.debug('Only dropping file content for given paths in %s, '\n                  'allthough instruction was to drop %s', ds, what)\n        what = 'filecontent'\n\n    repo = ds.repo\n    is_annex = isinstance(repo, AnnexRepo)\n\n    # first critical checks that might prevent further operation\n    had_fatality = False\n    for res in _fatal_pre_drop_checks(\n            ds, repo, paths, what, reckless, is_annex):\n        had_fatality = True\n        yield res\n    if had_fatality:\n        return\n\n    # next check must come AFTER the modification checks above, otherwise\n    # remove() could not rely on the modification detection above\n    if paths is not None and paths != [ds.pathobj] and what == 'datasets':\n        # so we have paths constraints that prevent dropping the full dataset\n        # there is nothing to do here, but to drop keys, which we must not\n        # done\n        return\n\n    # now conditional/informative checks\n    yield from _pre_drop_checks(ds, repo, paths, what, reckless, is_annex)\n\n    if is_annex and what == 'filecontent':\n        yield from _drop_files(\n            ds,\n            repo,\n            # give paths or '.' with no constraint\n            paths=[str(p.relative_to(ds.pathobj))\n                   for p in paths] if paths else '.',\n            force=reckless in ('availability', 'kill'),\n            jobs=jobs,\n        )\n        # end it here for safety, the rest of the function deals with\n        # dropping more and more fundamentally\n        return\n\n    drop_all_errored = False\n    if is_annex and what in ('allkeys', 'datasets', 'all') \\\n            and not reckless == 'kill':\n        for r in _drop_allkeys(\n                ds,\n                repo,\n                force=reckless in ('availability',),\n                jobs=jobs):\n            res = dict(\n                action='drop',\n                type='key',\n                # use the path of the containing dataset\n                # using the location of the key does not add any\n                # practical value, and is expensive to obtain\n                path=ds.path,\n                status='ok' if r.get('success') else 'error',\n                key=r.get('key'),\n            )\n            # pull any note, and rename recommended parameter to\n            # avoid confusion\n            message = r.get('note', '').replace(\n                '--force',\n                '--reckless availability')\n            if message:\n                res['message'] = message\n            error_messages = r.get('error-messages')\n            if error_messages:\n                res['error_message'] = '\\n'.join(\n                    m.strip() for m in error_messages\n                )\n            # play safe, if there is no status, assume error\n            if res.get('status', 'error') != 'ok':\n                drop_all_errored = True\n            yield res\n\n    if drop_all_errored:\n        # end it here, if there is any indication that wiping out the\n        # repo is unsafe\n        return\n\n    if what in ('all', 'datasets'):\n        yield from _kill_dataset(ds)\n    lgr.debug('Done dropping for %s', ds)\n    return",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 303,
                            "end_lineno": 458,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "# ex: set sts=4 ts=4 sw=4 et:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Test drop command\"\"\"\n\nimport os\nimport os.path as op\nfrom pathlib import Path\nfrom unittest.mock import patch\n\nfrom datalad.api import (\n    Dataset,\n    clone,\n    create,\n    drop,\n)\nfrom datalad.distributed.drop import (\n    _detect_nondead_annex_at_remotes,\n    _detect_unpushed_revs,\n)\nfrom datalad.support.exceptions import (\n    IncompleteResultsError,\n    NoDatasetFound,\n)\nfrom datalad.support.gitrepo import GitRepo\nfrom datalad.tests.utils_pytest import (\n    DEFAULT_BRANCH,\n    DEFAULT_REMOTE,\n    OBSCURE_FILENAME,\n    assert_in,\n    assert_in_results,\n    assert_raises,\n    assert_repo_status,\n    assert_result_count,\n    assert_status,\n    eq_,\n    get_deeply_nested_structure,\n    known_failure_githubci_win,\n    nok_,\n    ok_,\n    with_tempfile,\n)\nfrom datalad.utils import chpwd\n\n\nckwa = dict(result_renderer='disabled')\n\n\n@with_tempfile\n@with_tempfile\ndef test_drop_file_content(path=None, outside_path=None):\n    # see docstring for test data structure\n    ds = get_deeply_nested_structure(path)\n    axfile_rootds = op.join(\"subdir\", \"annexed_file.txt\")\n    axfile_subds = op.join(\"subds_modified\", \"subdir\", \"annexed_file.txt\")\n    gitfile = op.join(\"subdir\", \"git_file.txt\")\n\n    # refuse to operate on non-ds paths\n    assert_in_results(\n        ds.drop(outside_path, on_failure='ignore'),\n        status='error',\n        message=('path not underneath the reference dataset %s', ds)\n    )\n    # we only have a single copy of annexed files right now\n    # check that it is not dropped by default\n    with assert_raises(IncompleteResultsError) as cme:\n        ds.drop(axfile_rootds)\n    # The --force suggestion from git-annex-drop is translated to --reckless.\n    assert_in(\"--reckless\", str(cme.value))\n\n    # error on non-existing paths\n    non_existant_relpaths = ['funky', op.join('subds_modified', 'subfunky')]\n    res = ds.drop(non_existant_relpaths, on_failure='ignore')\n    # only two results, one per file\n    assert_result_count(res, len(non_existant_relpaths))\n    for rp in non_existant_relpaths:\n        assert_in_results(\n            res,\n            type='file',\n            status='error',\n            action='drop',\n            error_message='File unknown to git',\n            path=str(ds.pathobj / rp),\n            refds=ds.path,\n        )\n\n    # drop multiple files from different datasets\n    ok_(ds.repo.file_has_content(axfile_rootds))\n    res = ds.drop(\n        [axfile_rootds, axfile_subds],\n        reckless='availability',\n        jobs=2,\n        on_failure='ignore')\n    nok_(ds.repo.file_has_content(axfile_rootds))\n    assert_result_count(res, 2)\n    for rp in [axfile_rootds, axfile_subds]:\n        assert_in_results(\n            res,\n            type='file',\n            status='ok',\n            action='drop',\n            path=str(ds.pathobj / rp),\n            refds=ds.path,\n        )\n\n    # dropping file content for files in git\n    res = ds.drop(gitfile, on_failure='ignore')\n    assert_result_count(res, 1)\n    assert_in_results(\n        res,\n        type='file',\n        # why is this 'notneeded' and not 'impossible'\n        # if the latter, any operation on any dataset with a\n        # single file in git would fail\n        status='notneeded',\n        action='drop',\n        message=\"no annex'ed content\",\n        path=str(ds.pathobj / gitfile),\n        refds=ds.path,\n    )\n\n    # modified files, we cannot drop their content\n    modfile = ds.pathobj / axfile_rootds\n    modfile.unlink()\n    modfile.write_text('new content')\n    res = ds.drop(modfile, on_failure='ignore')\n    assert_in_results(\n        res,\n        status='impossible',\n        action='drop',\n        message=\"cannot drop modified content, save first\",\n        path=str(modfile),\n        refds=ds.path,\n    )\n\n    # detection of untracked content\n    untrackeddir = ds.pathobj / 'subds_modified' / 'subds_lvl1_modified' / \\\n        f'{OBSCURE_FILENAME}_directory_untracked'\n    res = ds.drop(untrackeddir, on_failure='ignore')\n    assert_in_results(\n        res,\n        status='impossible',\n        action='drop',\n        message=\"cannot drop untracked content, save first\",\n        path=str(untrackeddir),\n        type='directory',\n        refds=ds.path,\n    )\n\n    # and lastly, recursive drop\n    res = ds.drop(recursive=True, on_failure='ignore')\n    # there is not much to test here (we already dropped the only\n    # annexed files above). however, we should see results from the top\n    # ds, and the most-bottom ds\n    # subdatasets\n    for p in [ds.pathobj / 'subdir' / 'file_modified',\n              untrackeddir]:\n        assert_in_results(res, path=str(p))\n\n\n@with_tempfile\n@with_tempfile\ndef test_drop_allkeys(origpath=None, clonepath=None):\n    # create a dataset with two keys, belonging to two files,\n    # in two different branches\n    ds = Dataset(origpath).create()\n    repo = ds.repo\n    repo.call_git(['checkout', '-b', 'otherbranch'])\n    (ds.pathobj / 'file1').write_text('file1')\n    ds.save()\n    repo.call_git(['checkout', DEFAULT_BRANCH])\n    (ds.pathobj / 'file2').write_text('file2')\n    ds.save()\n\n    # confirm we have two keys\n    eq_(2, repo.call_annex_records(['info'])[0]['local annex keys'])\n\n    # do it wrong first\n    assert_in_results(\n        ds.drop('some', what='allkeys', on_failure='ignore'),\n        status='impossible',\n        type='dataset',\n        action='drop',\n        message=(\n            'cannot drop %s, with path constraints given: %s',\n            'allkeys', [ds.pathobj / 'some']),\n    )\n    # confirm we still have two keys\n    eq_(2, repo.call_annex_records(['info'])[0]['local annex keys'])\n\n    # clone the beast and get all keys into the clone\n    dsclone = clone(ds.path, clonepath)\n    dsclone.repo.call_annex(['get', '--all'])\n    # confirm we have two keys in the clone\n    eq_(2, dsclone.repo.call_annex_records(['info'])[0]['local annex keys'])\n\n    # now cripple availability by dropping the \"hidden\" key at origin\n    repo.call_annex(['drop', '--branch', 'otherbranch', '--force'])\n    # confirm one key left\n    eq_(1, repo.call_annex_records(['info'])[0]['local annex keys'])\n\n    # and now drop all keys from the clone, one is redundant and can be\n    # dropped, the other is not and must fail\n    res = dsclone.drop(what='allkeys', jobs=2, on_failure='ignore')\n    # confirm one key gone, one left\n    eq_(1, dsclone.repo.call_annex_records(['info'])[0]['local annex keys'])\n    assert_result_count(res, 1, action='drop', status='error', type='key')\n    assert_result_count(res, 1, action='drop', status='ok', type='key')\n    # now force it\n    res = dsclone.drop(what='allkeys', reckless='availability',\n                       on_failure='ignore')\n    assert_result_count(res, 1)\n    assert_result_count(res, 1, action='drop', status='ok', type='key')\n    # all gone\n    eq_(0, dsclone.repo.call_annex_records(['info'])[0]['local annex keys'])\n\n\n@with_tempfile\n@with_tempfile\n@with_tempfile\ndef test_undead_annex_detection(gitpath=None, origpath=None, clonepath=None):\n    gitds = Dataset(gitpath).create(annex=False)\n    # a gitrepo can be inspected too, it might just not know anything\n    eq_([], _detect_nondead_annex_at_remotes(gitds.repo, 'someid'))\n\n    origds = Dataset(origpath).create()\n    origrepo = origds.repo\n    # only the local repo knows about its own annex\n    eq_([None], _detect_nondead_annex_at_remotes(origrepo, origrepo.uuid))\n\n    # works with clones\n    cloneds = clone(origds, clonepath)\n    clonerepo = cloneds.repo\n    # the clone now know two locations, itself and origin\n    eq_([None, DEFAULT_REMOTE],\n        _detect_nondead_annex_at_remotes(clonerepo, origrepo.uuid))\n    # just from cloning the original repo location does not learn\n    # about the new annex in the clone\n    eq_([], _detect_nondead_annex_at_remotes(origrepo, clonerepo.uuid))\n    # it will know after a push\n    cloneds.push()\n    eq_([None], _detect_nondead_annex_at_remotes(origrepo, clonerepo.uuid))\n    # we can declare an annex dead (here done at original location)\n    origrepo.call_annex(['dead', clonerepo.uuid])\n    eq_([], _detect_nondead_annex_at_remotes(origrepo, clonerepo.uuid))\n    # again not automatically communicated to clones\n    eq_([None, DEFAULT_REMOTE],\n        _detect_nondead_annex_at_remotes(clonerepo, clonerepo.uuid))\n    # but a fetch will make the death known\n    clonerepo.call_git(['fetch'])\n    eq_([None],\n        _detect_nondead_annex_at_remotes(clonerepo, clonerepo.uuid))\n    # after a local git-annex branch synchronization, it is completely\n    # \"gone\"\n    clonerepo.localsync()\n    eq_([],\n        _detect_nondead_annex_at_remotes(clonerepo, clonerepo.uuid))\n\n\n@with_tempfile\ndef test_uninstall_recursive(path=None):\n    ds = Dataset(path)\n    assert_raises(ValueError, ds.drop)\n\n    ds = ds.create()\n    subds = ds.create('sub')\n\n    # fail to uninstall with subdatasets present\n    res = ds.drop(\n        what='all', reckless='availability', on_failure='ignore')\n    assert_in_results(\n        res,\n        action='uninstall',\n        path=ds.path,\n        type='dataset',\n        status='error',\n        message=('cannot drop dataset, subdataset(s) still present '\n                 '(forgot --recursive?): %s', [subds.path]),\n    )\n    res = ds.drop(\n        what='all', reckless='availability', recursive=True,\n        on_failure='ignore')\n    # both datasets gone\n    assert_result_count(res, 2)\n    assert_result_count(res, 2, type='dataset', status='ok')\n    # the subdataset is reported first\n    eq_([subds.path, ds.path],\n        [r.get('path') for r in res])\n    # no dataset installed anymore\n    eq_(ds.is_installed(), False)\n    # not even a trace\n    eq_(ds.pathobj.exists(), False)\n\n\n@with_tempfile\n@with_tempfile\ndef test_unpushed_state_detection(origpath=None, clonepath=None):\n    origds = Dataset(origpath).create()\n    # always test in annex mode\n    tester = lambda x: _detect_unpushed_revs(x, True)\n\n    origrepo = origds.repo\n    # this is still a unique repo, all payload branches are\n    # unpushed\n    eq_([DEFAULT_BRANCH], tester(origrepo))\n    origrepo.call_git(['checkout', '-b', 'otherbranch'])\n    eq_([DEFAULT_BRANCH, 'otherbranch'],\n        tester(origrepo))\n    # let's advance the state by one\n    (origds.pathobj / 'file1').write_text('some text')\n    origds.save()\n    # same picture\n    eq_([DEFAULT_BRANCH, 'otherbranch'],\n        tester(origrepo))\n    # back to original branch\n    origrepo.call_git(['checkout', DEFAULT_BRANCH])\n\n    # now lets clone\n    cloneds = clone(origds, clonepath)\n    clonerepo = cloneds.repo\n    # right after the clone there will be no unpushed changes\n    eq_([], tester(clonerepo))\n    # even with more than one branch in the clone\n    clonerepo.call_git(['checkout', '-t', f'{DEFAULT_REMOTE}/otherbranch'])\n    eq_([], tester(clonerepo))\n\n    # let's advance the local state now\n    (cloneds.pathobj / 'file2').write_text('some other text')\n    cloneds.save()\n    # only the modified branch is detected\n    eq_(['otherbranch'], tester(clonerepo))\n    # a push will bring things into the clear\n    cloneds.push(to=DEFAULT_REMOTE)\n    eq_([], tester(clonerepo))\n\n    # now detach HEAD, should work and not somehow require a branch\n    cloneds.repo.call_git(['reset', '--hard', 'HEAD~1'])\n    # we have this state\n    eq_([], tester(clonerepo))\n\n\n@with_tempfile(mkdir=True)\n@with_tempfile\n@with_tempfile\ndef test_safetynet(otherpath=None, origpath=None, clonepath=None):\n    # we start with a dataset that is hosted somewhere\n    origds = Dataset(origpath).create()\n    # a clone is made to work on the dataset\n    cloneds = clone(origds, clonepath)\n    # checkout a different branch at origin to simplify testing below\n    origds.repo.call_git(['checkout', '-b', 'otherbranch'])\n\n    # an untracked file is added to simulate some work\n    (cloneds.pathobj / 'file1').write_text('some text')\n    # now we try to drop the entire dataset in a variety of ways\n    # to check that it does not happen too quickly\n\n    # cannot use invalid mode switch values\n    assert_raises(ValueError, drop, what='bananas')\n    assert_raises(ValueError, drop, reckless='rubberducky')\n\n    # cannot simple run drop somewhere and give a path to a dataset\n    # to drop\n    with chpwd(otherpath):\n        assert_raises(NoDatasetFound, drop, clonepath, what='all')\n    ok_(cloneds.is_installed())\n\n    # refuse to remove the CWD\n    with chpwd(clonepath):\n        assert_raises(RuntimeError, drop, what='all')\n    ok_(cloneds.is_installed())\n\n    assert_in_results(\n        cloneds.drop(what='all', on_failure='ignore'),\n        message='cannot drop untracked content, save first',\n        status='impossible')\n    ok_(cloneds.is_installed())\n\n    # so let's save...\n    cloneds.save()\n    # - branch is progressed\n    # - a new key is only available here\n    res = cloneds.drop(what='all', on_failure='ignore')\n    assert_in_results(res, action=\"uninstall\", status=\"error\")\n    ok_(res[0]['message'][0].startswith(\n        \"to-be-dropped dataset has revisions \"\n        \"that are not available at any known sibling\"))\n    ok_(cloneds.is_installed())\n\n    # so let's push -- git only\n    # we cannot use git-push directly, it would not handle\n    # managed branches properly\n    cloneds.push(data='nothing')\n\n    res = cloneds.drop(what='all', on_failure='ignore')\n    assert_in_results(res, action=\"uninstall\", status=\"error\")\n    ok_(res[0]['message'][0].startswith(\n        \"to-be-deleted local annex not declared 'dead'\"))\n    # some windows test setup is not very robust, explicitly\n    # include the default name \"origin\" in the test success\n    # conditions to make this more robust\n    eq_(res[0]['message'][1], [DEFAULT_REMOTE])\n    ok_(cloneds.is_installed())\n\n    # announce dead\n    cloneds.repo.call_annex(['dead', 'here'])\n    # but just a local declaration is not good enough\n    assert_in_results(\n        cloneds.drop(what='all', on_failure='ignore'),\n        status='error')\n    ok_(cloneds.is_installed())\n\n    # so let's push that announcement also\n    cloneds.push(data='nothing')\n\n    res = cloneds.drop(what='all', on_failure='ignore')\n    assert_in_results(res, action=\"drop\", status=\"error\")\n    ok_(res[0]['message'].startswith(\n        \"unsafe\\nCould only verify the existence of \"\n        \"0 out of 1 necessary\"),\n        msg=f\"Results were {res}\")\n    ok_(cloneds.is_installed())\n\n    # so let's push all\n    cloneds.push()\n\n    # and kill the beast!\n    res = cloneds.drop(what='all', on_failure='ignore')\n    # only now we also drop the key!\n    assert_result_count(res, 2)\n    assert_in_results(\n        res, action='drop', type='key', status='ok', path=cloneds.path)\n    assert_in_results(\n        res, action='uninstall', type='dataset', status='ok', path=cloneds.path)\n\n\n@with_tempfile\ndef test_kill(path=None):\n    # create a complicated and dirty mess\n    ds = get_deeply_nested_structure(path)\n    # cannot use kill without recursion enabled, because there will be no\n    # checks for subdatasets, hence we cannot make the impression that\n    # this would be a surgical operation\n    assert_raises(ValueError, ds.drop, what='all', reckless='kill')\n    # wipe it out\n    res = ds.drop(what='all', reckless='kill', recursive=True)\n    assert_result_count(res, 1)\n    assert_in_results(\n        res,\n        status='ok',\n        path=ds.path,\n        type='dataset',\n        action='uninstall',\n    )\n    eq_(False, ds.is_installed())\n    eq_(False, ds.pathobj.exists())\n\n\n@with_tempfile\ndef test_kill_7013(path=None):\n    \"\"\"check that a recursive kill does not silently skip subds\"\"\"\n    ds = Dataset(path).create()\n    (ds.pathobj / 'subdir').mkdir()\n    ds.create('subdir/subds')\n    ds.create('subds')\n    res = ds.drop(path=['subds', 'subdir'], what='all',\n                  reckless='kill', recursive=True)\n    # ensure that both subdatasets were killed\n    subs = ds.subdatasets(state='present')\n    assert len(subs) == 0\n\n\n@with_tempfile()\ndef test_refuse_to_drop_cwd(path=None):\n    ds = Dataset(path).create()\n    (ds.pathobj / 'deep' / 'down').mkdir(parents=True)\n    for p in (ds.pathobj, ds.pathobj / 'deep', ds.pathobj / 'deep' / 'down'):\n        with chpwd(str(p)):\n            # will never remove PWD, or anything outside the dataset\n            for target in (ds.pathobj, os.curdir, os.pardir, op.join(os.pardir, os.pardir)):\n                assert_raises(RuntimeError, drop, path=target, what='all')\n    sub = ds.create('sub')\n    subsub = sub.create('subsub')\n    for p in (sub.path, subsub.path):\n        with chpwd(p):\n            assert_raises(RuntimeError, drop, what='all')\n\n\n@with_tempfile()\ndef test_careless_subdataset_uninstall(path=None):\n    # nested datasets\n    ds = Dataset(path).create()\n    subds1 = ds.create('deep1')\n    ds.create('deep2')\n    eq_(sorted(ds.subdatasets(result_xfm='relpaths')), ['deep1', 'deep2'])\n    assert_repo_status(ds.path)\n    # now we kill the sub without the parent knowing\n    subds1.drop(what='all', reckless='kill', recursive=True)\n    ok_(not subds1.is_installed())\n    # mountpoint exists\n    ok_(subds1.pathobj.exists())\n    assert_repo_status(ds.path)\n    # parent still knows the sub\n    eq_(sorted(ds.subdatasets(result_xfm='relpaths')), ['deep1', 'deep2'])\n\n\n@with_tempfile(mkdir=True)\ndef test_drop_nocrash_absent_subds(path=None):\n    parent = Dataset(path).create()\n    parent.create('sub')\n    parent.drop('sub', reckless='availability')\n    assert_repo_status(parent.path)\n    with chpwd(path):\n        assert_status('notneeded', drop('.', recursive=True))\n\n\n@with_tempfile(mkdir=True)\ndef test_uninstall_without_super(path=None):\n    # a parent dataset with a proper subdataset, and another dataset that\n    # is just placed underneath the parent, but not an actual subdataset\n    parent = Dataset(path).create()\n    sub = parent.create('sub')\n    assert_repo_status(parent.path)\n    nosub = create(op.join(parent.path, 'nosub'))\n    assert_repo_status(nosub.path)\n    subreport = parent.subdatasets()\n    assert_result_count(subreport, 1, path=sub.path)\n    assert_result_count(subreport, 0, path=nosub.path)\n    # it should be possible to uninstall the proper subdataset, even without\n    # explicitly calling the uninstall methods of the parent -- things should\n    # be figured out by datalad\n    with chpwd(parent.path):\n        # check False because revisions are not pushed\n        drop(sub.path, what='all', reckless='availability')\n    nok_(sub.is_installed())\n    # no present subdatasets anymore\n    subreport = parent.subdatasets()\n    assert_result_count(subreport, 1)\n    assert_result_count(subreport, 1, path=sub.path, state='absent')\n    assert_result_count(subreport, 0, path=nosub.path)\n    # but we should fail on an attempt to uninstall the non-subdataset\n    with chpwd(nosub.path):\n        assert_raises(RuntimeError, drop, what='all')\n\n\n@with_tempfile\ndef test_drop_from_git(path=None):\n    ds = Dataset(path).create(annex=False)\n    res = ds.drop()\n    assert_in_results(res, action='drop', status='notneeded')\n    (ds.pathobj / 'file').write_text('some')\n    ds.save()\n    assert_status('notneeded', ds.drop('file'))\n    assert_status('notneeded', ds.drop(what='allkeys'))\n\n\n# https://github.com/datalad/datalad/issues/6180\n@with_tempfile\n@with_tempfile\ndef test_drop_uninit_annexrepo(origpath=None, path=None):\n    Dataset(origpath).create()\n    # just git-clone to bypass `git annex init`\n    GitRepo.clone(origpath, path)\n    ds = Dataset(path)\n    assert_status('ok', ds.drop(what='datasets'))\n    nok_(ds.is_installed())\n\n\n# https://github.com/datalad/datalad/issues/6577\n@with_tempfile\ndef test_drop_allkeys_result_contains_annex_error_messages(path=None):\n    # when calling drop with allkeys, expect git-annex error\n    # message(s) to be present in the result record error_message\n    ds = Dataset(path).create()\n    # mock annex call always yielding error-messages in this dataset\n    with patch.object(ds.repo, '_call_annex_records_items_') as mock_call:\n        mock_call.return_value = iter([{\n            'command': 'drop',\n            'success': False,\n            'error-messages': ['git-annex error message here']}])\n        assert_in_results(\n            ds.drop(what='allkeys', on_failure='ignore'),\n            error_message='git-annex error message here',\n        )\n\n\n# https://github.com/datalad/datalad/issues/6948\n@known_failure_githubci_win  # recent git-annex, https://github.com/datalad/datalad/issues/7197\n@with_tempfile\n@with_tempfile\ndef test_nodrop_symlinked_annex(origpath=None, clonepath=None):\n    # create a dataset with a key\n    ds = Dataset(origpath).create(**ckwa)\n    testfile = ds.pathobj / 'file1'\n    testcontent = 'precious'\n    testfile.write_text(testcontent)\n    ds.save(**ckwa)\n    rec = ds.status(testfile, annex='availability',\n                    return_type='item-or-list', **ckwa)\n    eq_(testcontent, Path(rec['objloc']).read_text())\n\n    def _droptest(_ds):\n        # drop refuses to drop from a symlinked annex\n        if (_ds.repo.dot_git / 'annex').is_symlink():\n            assert_raises(AssertionError, _ds.drop, **ckwa)\n            for what in ('all', 'allkeys', 'filecontent', 'datasets'):\n                assert_raises(AssertionError, _ds.drop, what=what, **ckwa)\n        # but a reckless kill works without crashing\n        _ds.drop(what='all', recursive=True, reckless='kill', **ckwa)\n        nok_(_ds.is_installed())\n        # nothing has made the original file content vanish\n        _rec = ds.status(\n            testfile, annex='availability',\n            return_type='item-or-list', **ckwa)\n        eq_(testcontent, Path(_rec['objloc']).read_text())\n\n    # test on a clone that does not know it has key access\n    dsclone1 = clone(ds.path, clonepath, reckless='ephemeral', **ckwa)\n    _droptest(dsclone1)\n\n    # test again on a clone that does think it has a key copy\n    dsclone2 = clone(ds.path, clonepath, reckless='ephemeral', **ckwa)\n    if not dsclone2.repo.is_managed_branch():\n        # it really should not matter, but 'origin' is set to annex.ignore=true\n        # on crippledFS\n        # https://github.com/datalad/datalad/issues/6960\n        dsclone2.get('.')\n    _droptest(dsclone2)\n",
            "new_file_content": "# ex: set sts=4 ts=4 sw=4 et:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Test drop command\"\"\"\n\nimport os\nimport os.path as op\nfrom pathlib import Path\nfrom unittest.mock import patch\n\nfrom datalad.api import (\n    Dataset,\n    clone,\n    create,\n    drop,\n)\nfrom datalad.distributed.drop import (\n    _detect_nondead_annex_at_remotes,\n    _detect_unpushed_revs,\n)\nfrom datalad.support.exceptions import (\n    IncompleteResultsError,\n    NoDatasetFound,\n)\nfrom datalad.support.gitrepo import GitRepo\nfrom datalad.tests.utils_pytest import (\n    DEFAULT_BRANCH,\n    DEFAULT_REMOTE,\n    OBSCURE_FILENAME,\n    assert_in,\n    assert_in_results,\n    assert_raises,\n    assert_repo_status,\n    assert_result_count,\n    assert_status,\n    eq_,\n    get_deeply_nested_structure,\n    known_failure_githubci_win,\n    nok_,\n    ok_,\n    with_tempfile,\n)\nfrom datalad.utils import chpwd\n\n\nckwa = dict(result_renderer='disabled')\n\n\n@with_tempfile\n@with_tempfile\ndef test_drop_file_content(path=None, outside_path=None):\n    # see docstring for test data structure\n    ds = get_deeply_nested_structure(path)\n    axfile_rootds = op.join(\"subdir\", \"annexed_file.txt\")\n    axfile_subds = op.join(\"subds_modified\", \"subdir\", \"annexed_file.txt\")\n    gitfile = op.join(\"subdir\", \"git_file.txt\")\n\n    # refuse to operate on non-ds paths\n    assert_in_results(\n        ds.drop(outside_path, on_failure='ignore'),\n        status='error',\n        message=('path not underneath the reference dataset %s', ds)\n    )\n    # we only have a single copy of annexed files right now\n    # check that it is not dropped by default\n    with assert_raises(IncompleteResultsError) as cme:\n        ds.drop(axfile_rootds)\n    # The --force suggestion from git-annex-drop is translated to --reckless.\n    assert_in(\"--reckless\", str(cme.value))\n\n    # error on non-existing paths\n    non_existant_relpaths = ['funky', op.join('subds_modified', 'subfunky')]\n    res = ds.drop(non_existant_relpaths, on_failure='ignore')\n    # only two results, one per file\n    assert_result_count(res, len(non_existant_relpaths))\n    for rp in non_existant_relpaths:\n        assert_in_results(\n            res,\n            type='file',\n            status='error',\n            action='drop',\n            error_message='File unknown to git',\n            path=str(ds.pathobj / rp),\n            refds=ds.path,\n        )\n\n    # drop multiple files from different datasets\n    ok_(ds.repo.file_has_content(axfile_rootds))\n    res = ds.drop(\n        [axfile_rootds, axfile_subds],\n        reckless='availability',\n        jobs=2,\n        on_failure='ignore')\n    nok_(ds.repo.file_has_content(axfile_rootds))\n    assert_result_count(res, 2)\n    for rp in [axfile_rootds, axfile_subds]:\n        assert_in_results(\n            res,\n            type='file',\n            status='ok',\n            action='drop',\n            path=str(ds.pathobj / rp),\n            refds=ds.path,\n        )\n\n    # dropping file content for files in git\n    res = ds.drop(gitfile, on_failure='ignore')\n    assert_result_count(res, 1)\n    assert_in_results(\n        res,\n        type='file',\n        # why is this 'notneeded' and not 'impossible'\n        # if the latter, any operation on any dataset with a\n        # single file in git would fail\n        status='notneeded',\n        action='drop',\n        message=\"no annex'ed content\",\n        path=str(ds.pathobj / gitfile),\n        refds=ds.path,\n    )\n\n    # modified files, we cannot drop their content\n    modfile = ds.pathobj / axfile_rootds\n    modfile.unlink()\n    modfile.write_text('new content')\n    res = ds.drop(modfile, on_failure='ignore')\n    assert_in_results(\n        res,\n        status='impossible',\n        action='drop',\n        message=\"cannot drop modified content, save first\",\n        path=str(modfile),\n        refds=ds.path,\n    )\n\n    # detection of untracked content\n    untrackeddir = ds.pathobj / 'subds_modified' / 'subds_lvl1_modified' / \\\n        f'{OBSCURE_FILENAME}_directory_untracked'\n    res = ds.drop(untrackeddir, on_failure='ignore')\n    assert_in_results(\n        res,\n        status='impossible',\n        action='drop',\n        message=\"cannot drop untracked content, save first\",\n        path=str(untrackeddir),\n        type='directory',\n        refds=ds.path,\n    )\n\n    # and lastly, recursive drop\n    res = ds.drop(recursive=True, on_failure='ignore')\n    # there is not much to test here (we already dropped the only\n    # annexed files above). however, we should see results from the top\n    # ds, and the most-bottom ds\n    # subdatasets\n    for p in [ds.pathobj / 'subdir' / 'file_modified',\n              untrackeddir]:\n        assert_in_results(res, path=str(p))\n\n\n@with_tempfile\n@with_tempfile\ndef test_drop_allkeys(origpath=None, clonepath=None):\n    # create a dataset with two keys, belonging to two files,\n    # in two different branches\n    ds = Dataset(origpath).create()\n    repo = ds.repo\n    repo.call_git(['checkout', '-b', 'otherbranch'])\n    (ds.pathobj / 'file1').write_text('file1')\n    ds.save()\n    repo.call_git(['checkout', DEFAULT_BRANCH])\n    (ds.pathobj / 'file2').write_text('file2')\n    ds.save()\n\n    # confirm we have two keys\n    eq_(2, repo.call_annex_records(['info'])[0]['local annex keys'])\n\n    # do it wrong first\n    assert_in_results(\n        ds.drop('some', what='allkeys', on_failure='ignore'),\n        status='impossible',\n        type='dataset',\n        action='drop',\n        message=(\n            'cannot drop %s, with path constraints given: %s',\n            'allkeys', [ds.pathobj / 'some']),\n    )\n    # confirm we still have two keys\n    eq_(2, repo.call_annex_records(['info'])[0]['local annex keys'])\n\n    # clone the beast and get all keys into the clone\n    dsclone = clone(ds.path, clonepath)\n    dsclone.repo.call_annex(['get', '--all'])\n    # confirm we have two keys in the clone\n    eq_(2, dsclone.repo.call_annex_records(['info'])[0]['local annex keys'])\n\n    # now cripple availability by dropping the \"hidden\" key at origin\n    repo.call_annex(['drop', '--branch', 'otherbranch', '--force'])\n    # confirm one key left\n    eq_(1, repo.call_annex_records(['info'])[0]['local annex keys'])\n\n    # and now drop all keys from the clone, one is redundant and can be\n    # dropped, the other is not and must fail\n    res = dsclone.drop(what='allkeys', jobs=2, on_failure='ignore')\n    # confirm one key gone, one left\n    eq_(1, dsclone.repo.call_annex_records(['info'])[0]['local annex keys'])\n    assert_result_count(res, 1, action='drop', status='error', type='key')\n    assert_result_count(res, 1, action='drop', status='ok', type='key')\n    # now force it\n    res = dsclone.drop(what='allkeys', reckless='availability',\n                       on_failure='ignore')\n    assert_result_count(res, 1)\n    assert_result_count(res, 1, action='drop', status='ok', type='key')\n    # all gone\n    eq_(0, dsclone.repo.call_annex_records(['info'])[0]['local annex keys'])\n\n\n@with_tempfile\n@with_tempfile\n@with_tempfile\ndef test_undead_annex_detection(gitpath=None, origpath=None, clonepath=None):\n    gitds = Dataset(gitpath).create(annex=False)\n    # a gitrepo can be inspected too, it might just not know anything\n    eq_([], _detect_nondead_annex_at_remotes(gitds.repo, 'someid'))\n\n    origds = Dataset(origpath).create()\n    origrepo = origds.repo\n    # only the local repo knows about its own annex\n    eq_([None], _detect_nondead_annex_at_remotes(origrepo, origrepo.uuid))\n\n    # works with clones\n    cloneds = clone(origds, clonepath)\n    clonerepo = cloneds.repo\n    # the clone now know two locations, itself and origin\n    eq_([None, DEFAULT_REMOTE],\n        _detect_nondead_annex_at_remotes(clonerepo, origrepo.uuid))\n    # just from cloning the original repo location does not learn\n    # about the new annex in the clone\n    eq_([], _detect_nondead_annex_at_remotes(origrepo, clonerepo.uuid))\n    # it will know after a push\n    cloneds.push()\n    eq_([None], _detect_nondead_annex_at_remotes(origrepo, clonerepo.uuid))\n    # we can declare an annex dead (here done at original location)\n    origrepo.call_annex(['dead', clonerepo.uuid])\n    eq_([], _detect_nondead_annex_at_remotes(origrepo, clonerepo.uuid))\n    # again not automatically communicated to clones\n    eq_([None, DEFAULT_REMOTE],\n        _detect_nondead_annex_at_remotes(clonerepo, clonerepo.uuid))\n    # but a fetch will make the death known\n    clonerepo.call_git(['fetch'])\n    eq_([None],\n        _detect_nondead_annex_at_remotes(clonerepo, clonerepo.uuid))\n    # after a local git-annex branch synchronization, it is completely\n    # \"gone\"\n    clonerepo.localsync()\n    eq_([],\n        _detect_nondead_annex_at_remotes(clonerepo, clonerepo.uuid))\n\n\n@with_tempfile\ndef test_uninstall_recursive(path=None):\n    ds = Dataset(path)\n    assert_raises(ValueError, ds.drop)\n\n    ds = ds.create()\n    subds = ds.create('sub')\n\n    # fail to uninstall with subdatasets present\n    res = ds.drop(\n        what='all', reckless='availability', on_failure='ignore')\n    assert_in_results(\n        res,\n        action='uninstall',\n        path=ds.path,\n        type='dataset',\n        status='error',\n        message=('cannot drop dataset, subdataset(s) still present '\n                 '(forgot --recursive?): %s', [subds.path]),\n    )\n    res = ds.drop(\n        what='all', reckless='availability', recursive=True,\n        on_failure='ignore')\n    # both datasets gone\n    assert_result_count(res, 2)\n    assert_result_count(res, 2, type='dataset', status='ok')\n    # the subdataset is reported first\n    eq_([subds.path, ds.path],\n        [r.get('path') for r in res])\n    # no dataset installed anymore\n    eq_(ds.is_installed(), False)\n    # not even a trace\n    eq_(ds.pathobj.exists(), False)\n\n\n@with_tempfile\n@with_tempfile\ndef test_unpushed_state_detection(origpath=None, clonepath=None):\n    origds = Dataset(origpath).create()\n    # always test in annex mode\n    tester = lambda x: _detect_unpushed_revs(x, True)\n\n    origrepo = origds.repo\n    # this is still a unique repo, all payload branches are\n    # unpushed\n    eq_([DEFAULT_BRANCH], tester(origrepo))\n    origrepo.call_git(['checkout', '-b', 'otherbranch'])\n    eq_([DEFAULT_BRANCH, 'otherbranch'],\n        tester(origrepo))\n    # let's advance the state by one\n    (origds.pathobj / 'file1').write_text('some text')\n    origds.save()\n    # same picture\n    eq_([DEFAULT_BRANCH, 'otherbranch'],\n        tester(origrepo))\n    # back to original branch\n    origrepo.call_git(['checkout', DEFAULT_BRANCH])\n\n    # now lets clone\n    cloneds = clone(origds, clonepath)\n    clonerepo = cloneds.repo\n    # right after the clone there will be no unpushed changes\n    eq_([], tester(clonerepo))\n    # even with more than one branch in the clone\n    clonerepo.call_git(['checkout', '-t', f'{DEFAULT_REMOTE}/otherbranch'])\n    eq_([], tester(clonerepo))\n\n    # let's advance the local state now\n    (cloneds.pathobj / 'file2').write_text('some other text')\n    cloneds.save()\n    # only the modified branch is detected\n    eq_(['otherbranch'], tester(clonerepo))\n    # a push will bring things into the clear\n    cloneds.push(to=DEFAULT_REMOTE)\n    eq_([], tester(clonerepo))\n\n    # now detach HEAD, should work and not somehow require a branch\n    cloneds.repo.call_git(['reset', '--hard', 'HEAD~1'])\n    # we have this state\n    eq_([], tester(clonerepo))\n\n\n@with_tempfile(mkdir=True)\n@with_tempfile\n@with_tempfile\ndef test_safetynet(otherpath=None, origpath=None, clonepath=None):\n    # we start with a dataset that is hosted somewhere\n    origds = Dataset(origpath).create()\n    # a clone is made to work on the dataset\n    cloneds = clone(origds, clonepath)\n    # checkout a different branch at origin to simplify testing below\n    origds.repo.call_git(['checkout', '-b', 'otherbranch'])\n\n    # an untracked file is added to simulate some work\n    (cloneds.pathobj / 'file1').write_text('some text')\n    # now we try to drop the entire dataset in a variety of ways\n    # to check that it does not happen too quickly\n\n    # cannot use invalid mode switch values\n    assert_raises(ValueError, drop, what='bananas')\n    assert_raises(ValueError, drop, reckless='rubberducky')\n\n    # cannot simple run drop somewhere and give a path to a dataset\n    # to drop\n    with chpwd(otherpath):\n        assert_raises(NoDatasetFound, drop, clonepath, what='all')\n    ok_(cloneds.is_installed())\n\n    # refuse to remove the CWD\n    with chpwd(clonepath):\n        assert_raises(RuntimeError, drop, what='all')\n    ok_(cloneds.is_installed())\n\n    assert_in_results(\n        cloneds.drop(what='all', on_failure='ignore'),\n        message='cannot drop untracked content, save first',\n        status='impossible')\n    ok_(cloneds.is_installed())\n\n    # so let's save...\n    cloneds.save()\n    # - branch is progressed\n    # - a new key is only available here\n    res = cloneds.drop(what='all', on_failure='ignore')\n    assert_in_results(res, action=\"uninstall\", status=\"error\")\n    ok_(res[0]['message'][0].startswith(\n        \"to-be-dropped dataset has revisions \"\n        \"that are not available at any known sibling\"))\n    ok_(cloneds.is_installed())\n\n    # so let's push -- git only\n    # we cannot use git-push directly, it would not handle\n    # managed branches properly\n    cloneds.push(data='nothing')\n\n    res = cloneds.drop(what='all', on_failure='ignore')\n    assert_in_results(res, action=\"uninstall\", status=\"error\")\n    ok_(res[0]['message'][0].startswith(\n        \"to-be-deleted local annex not declared 'dead'\"))\n    # some windows test setup is not very robust, explicitly\n    # include the default name \"origin\" in the test success\n    # conditions to make this more robust\n    eq_(res[0]['message'][1], [DEFAULT_REMOTE])\n    ok_(cloneds.is_installed())\n\n    # announce dead\n    cloneds.repo.call_annex(['dead', 'here'])\n    # but just a local declaration is not good enough\n    assert_in_results(\n        cloneds.drop(what='all', on_failure='ignore'),\n        status='error')\n    ok_(cloneds.is_installed())\n\n    # so let's push that announcement also\n    cloneds.push(data='nothing')\n\n    res = cloneds.drop(what='all', on_failure='ignore')\n    assert_in_results(res, action=\"drop\", status=\"error\")\n    ok_(res[0]['message'].startswith(\n        \"unsafe\\nCould only verify the existence of \"\n        \"0 out of 1 necessary\"),\n        msg=f\"Results were {res}\")\n    ok_(cloneds.is_installed())\n\n    # so let's push all\n    cloneds.push()\n\n    # and kill the beast!\n    res = cloneds.drop(what='all', on_failure='ignore')\n    # only now we also drop the key!\n    assert_result_count(res, 2)\n    assert_in_results(\n        res, action='drop', type='key', status='ok', path=cloneds.path)\n    assert_in_results(\n        res, action='uninstall', type='dataset', status='ok', path=cloneds.path)\n\n\n@with_tempfile\ndef test_kill(path=None):\n    # create a complicated and dirty mess\n    ds = get_deeply_nested_structure(path)\n    # cannot use kill without recursion enabled, because there will be no\n    # checks for subdatasets, hence we cannot make the impression that\n    # this would be a surgical operation\n    assert_raises(ValueError, ds.drop, what='all', reckless='kill')\n    # wipe it out\n    res = ds.drop(what='all', reckless='kill', recursive=True)\n    assert_result_count(res, 1)\n    assert_in_results(\n        res,\n        status='ok',\n        path=ds.path,\n        type='dataset',\n        action='uninstall',\n    )\n    eq_(False, ds.is_installed())\n    eq_(False, ds.pathobj.exists())\n\n\n@with_tempfile\ndef test_kill_7013(path=None):\n    \"\"\"check that a recursive kill does not silently skip subdatasets\n     contained in subdirectory: github.com/datalad/datalad/issues/7013\"\"\"\n    ds = Dataset(path).create()\n    (ds.pathobj / 'subdir').mkdir()\n    ds.create('subdir/subds')\n    ds.create('subds')\n    res = ds.drop(path=['subds', 'subdir'], what='all',\n                  reckless='kill', recursive=True)\n    # ensure that both subdatasets were killed\n    subs = ds.subdatasets(state='present')\n    assert len(subs) == 0\n\n\n@with_tempfile()\ndef test_refuse_to_drop_cwd(path=None):\n    ds = Dataset(path).create()\n    (ds.pathobj / 'deep' / 'down').mkdir(parents=True)\n    for p in (ds.pathobj, ds.pathobj / 'deep', ds.pathobj / 'deep' / 'down'):\n        with chpwd(str(p)):\n            # will never remove PWD, or anything outside the dataset\n            for target in (ds.pathobj, os.curdir, os.pardir, op.join(os.pardir, os.pardir)):\n                assert_raises(RuntimeError, drop, path=target, what='all')\n    sub = ds.create('sub')\n    subsub = sub.create('subsub')\n    for p in (sub.path, subsub.path):\n        with chpwd(p):\n            assert_raises(RuntimeError, drop, what='all')\n\n\n@with_tempfile()\ndef test_careless_subdataset_uninstall(path=None):\n    # nested datasets\n    ds = Dataset(path).create()\n    subds1 = ds.create('deep1')\n    ds.create('deep2')\n    eq_(sorted(ds.subdatasets(result_xfm='relpaths')), ['deep1', 'deep2'])\n    assert_repo_status(ds.path)\n    # now we kill the sub without the parent knowing\n    subds1.drop(what='all', reckless='kill', recursive=True)\n    ok_(not subds1.is_installed())\n    # mountpoint exists\n    ok_(subds1.pathobj.exists())\n    assert_repo_status(ds.path)\n    # parent still knows the sub\n    eq_(sorted(ds.subdatasets(result_xfm='relpaths')), ['deep1', 'deep2'])\n\n\n@with_tempfile(mkdir=True)\ndef test_drop_nocrash_absent_subds(path=None):\n    parent = Dataset(path).create()\n    parent.create('sub')\n    parent.drop('sub', reckless='availability')\n    assert_repo_status(parent.path)\n    with chpwd(path):\n        assert_status('notneeded', drop('.', recursive=True))\n\n\n@with_tempfile(mkdir=True)\ndef test_uninstall_without_super(path=None):\n    # a parent dataset with a proper subdataset, and another dataset that\n    # is just placed underneath the parent, but not an actual subdataset\n    parent = Dataset(path).create()\n    sub = parent.create('sub')\n    assert_repo_status(parent.path)\n    nosub = create(op.join(parent.path, 'nosub'))\n    assert_repo_status(nosub.path)\n    subreport = parent.subdatasets()\n    assert_result_count(subreport, 1, path=sub.path)\n    assert_result_count(subreport, 0, path=nosub.path)\n    # it should be possible to uninstall the proper subdataset, even without\n    # explicitly calling the uninstall methods of the parent -- things should\n    # be figured out by datalad\n    with chpwd(parent.path):\n        # check False because revisions are not pushed\n        drop(sub.path, what='all', reckless='availability')\n    nok_(sub.is_installed())\n    # no present subdatasets anymore\n    subreport = parent.subdatasets()\n    assert_result_count(subreport, 1)\n    assert_result_count(subreport, 1, path=sub.path, state='absent')\n    assert_result_count(subreport, 0, path=nosub.path)\n    # but we should fail on an attempt to uninstall the non-subdataset\n    with chpwd(nosub.path):\n        assert_raises(RuntimeError, drop, what='all')\n\n\n@with_tempfile\ndef test_drop_from_git(path=None):\n    ds = Dataset(path).create(annex=False)\n    res = ds.drop()\n    assert_in_results(res, action='drop', status='notneeded')\n    (ds.pathobj / 'file').write_text('some')\n    ds.save()\n    assert_status('notneeded', ds.drop('file'))\n    assert_status('notneeded', ds.drop(what='allkeys'))\n\n\n# https://github.com/datalad/datalad/issues/6180\n@with_tempfile\n@with_tempfile\ndef test_drop_uninit_annexrepo(origpath=None, path=None):\n    Dataset(origpath).create()\n    # just git-clone to bypass `git annex init`\n    GitRepo.clone(origpath, path)\n    ds = Dataset(path)\n    assert_status('ok', ds.drop(what='datasets'))\n    nok_(ds.is_installed())\n\n\n# https://github.com/datalad/datalad/issues/6577\n@with_tempfile\ndef test_drop_allkeys_result_contains_annex_error_messages(path=None):\n    # when calling drop with allkeys, expect git-annex error\n    # message(s) to be present in the result record error_message\n    ds = Dataset(path).create()\n    # mock annex call always yielding error-messages in this dataset\n    with patch.object(ds.repo, '_call_annex_records_items_') as mock_call:\n        mock_call.return_value = iter([{\n            'command': 'drop',\n            'success': False,\n            'error-messages': ['git-annex error message here']}])\n        assert_in_results(\n            ds.drop(what='allkeys', on_failure='ignore'),\n            error_message='git-annex error message here',\n        )\n\n\n# https://github.com/datalad/datalad/issues/6948\n@known_failure_githubci_win  # recent git-annex, https://github.com/datalad/datalad/issues/7197\n@with_tempfile\n@with_tempfile\ndef test_nodrop_symlinked_annex(origpath=None, clonepath=None):\n    # create a dataset with a key\n    ds = Dataset(origpath).create(**ckwa)\n    testfile = ds.pathobj / 'file1'\n    testcontent = 'precious'\n    testfile.write_text(testcontent)\n    ds.save(**ckwa)\n    rec = ds.status(testfile, annex='availability',\n                    return_type='item-or-list', **ckwa)\n    eq_(testcontent, Path(rec['objloc']).read_text())\n\n    def _droptest(_ds):\n        # drop refuses to drop from a symlinked annex\n        if (_ds.repo.dot_git / 'annex').is_symlink():\n            assert_raises(AssertionError, _ds.drop, **ckwa)\n            for what in ('all', 'allkeys', 'filecontent', 'datasets'):\n                assert_raises(AssertionError, _ds.drop, what=what, **ckwa)\n        # but a reckless kill works without crashing\n        _ds.drop(what='all', recursive=True, reckless='kill', **ckwa)\n        nok_(_ds.is_installed())\n        # nothing has made the original file content vanish\n        _rec = ds.status(\n            testfile, annex='availability',\n            return_type='item-or-list', **ckwa)\n        eq_(testcontent, Path(_rec['objloc']).read_text())\n\n    # test on a clone that does not know it has key access\n    dsclone1 = clone(ds.path, clonepath, reckless='ephemeral', **ckwa)\n    _droptest(dsclone1)\n\n    # test again on a clone that does think it has a key copy\n    dsclone2 = clone(ds.path, clonepath, reckless='ephemeral', **ckwa)\n    if not dsclone2.repo.is_managed_branch():\n        # it really should not matter, but 'origin' is set to annex.ignore=true\n        # on crippledFS\n        # https://github.com/datalad/datalad/issues/6960\n        dsclone2.get('.')\n    _droptest(dsclone2)\n",
            "header": {
                "file": {
                    "path": "datalad/distributed/tests/test_drop.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "39c40eab7",
                "new_commit_hash": "5cee7dd97",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/datalad/distributed/tests/test_drop.py"
            },
            "plus_file": {
                "path": "b/datalad/distributed/tests/test_drop.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 462,
                            "length": 7
                        },
                        "new_range": {
                            "start": 462,
                            "length": 8
                        },
                        "section": "def test_kill(path=None):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "@with_tempfile",
                                "type": "context"
                            },
                            {
                                "content": "def test_kill_7013(path=None):",
                                "type": "context"
                            },
                            {
                                "content": "    \"\"\"check that a recursive kill does not silently skip subds\"\"\"",
                                "type": "deleted"
                            },
                            {
                                "content": "    \"\"\"check that a recursive kill does not silently skip subdatasets",
                                "type": "added"
                            },
                            {
                                "content": "     contained in subdirectory: github.com/datalad/datalad/issues/7013\"\"\"",
                                "type": "added"
                            },
                            {
                                "content": "    ds = Dataset(path).create()",
                                "type": "context"
                            },
                            {
                                "content": "    (ds.pathobj / 'subdir').mkdir()",
                                "type": "context"
                            },
                            {
                                "content": "    ds.create('subdir/subds')",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/distributed/tests/test_drop.py",
                            "type": "function",
                            "name": "test_kill_7013",
                            "content": "def test_kill_7013(path=None):\n    \"\"\"check that a recursive kill does not silently skip subds\"\"\"\n    ds = Dataset(path).create()\n    (ds.pathobj / 'subdir').mkdir()\n    ds.create('subdir/subds')\n    ds.create('subds')\n    res = ds.drop(path=['subds', 'subdir'], what='all',\n                  reckless='kill', recursive=True)\n    # ensure that both subdatasets were killed\n    subs = ds.subdatasets(state='present')\n    assert len(subs) == 0",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 464,
                            "end_lineno": 474,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "8d606e1ed9765cec669981b696049033af9b9dc9^",
    "new_commit_hash": "8d606e1ed9765cec669981b696049033af9b9dc9",
    "commit_message": "BF: Don't skip subds underneath paths in reckless & recursive drop\n\nThis fixes #7013.\nIf a drop --recursive --what all --reckless kill was provided with paths\nto directories, it silently skipped killing the contained subdatasets.\nThe problem arises when the internal resolving of paths to datasets\ndetermines that the provided directory path belongs to the superdataset.\nInstead of then recursing into the subdirectory's potential subdatasets,\ndrop restrained the scope of what was to be dropped to only\n'filecontent' in order to limit the drop from the determined\nsuperdataset to the provided path as an internal safety mechanism:\n\nif paths is not None and paths != [ds.pathobj] and what == 'all':\n    # so we have paths constraints that prevent dropping the full dataset\n    lgr.debug('Only dropping file content for given paths in %s, '\n              'allthough instruction was to drop %s', ds, what)\n    what = 'filecontent'\n\nThis change lifts this safety mechanism partially: Whenever reckless is\nset to 'kill', and the provided path does not match the currently\ninvestigated dataset, it adds a subdataset call constrained to the path\nin question. If this returns a dataset, we know that this dataset is\nmeant to be dropped as well, as recursive (which has to be set whenever\nreckless==kill) must be set, too.",
    "commit_date": "2023-03-02T09:23:09+01:00",
    "metadata": {}
}
{
    "repo_name": "datalad",
    "new_commit_hash": "8d606e1ed9765cec669981b696049033af9b9dc9",
    "test_file_codes": [
        "# ex: set sts=4 ts=4 sw=4 et:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Test drop command\"\"\"\n\nimport os\nimport os.path as op\nfrom pathlib import Path\nfrom unittest.mock import patch\n\nfrom datalad.api import (\n    Dataset,\n    clone,\n    create,\n    drop,\n)\nfrom datalad.distributed.drop import (\n    _detect_nondead_annex_at_remotes,\n    _detect_unpushed_revs,\n)\nfrom datalad.support.exceptions import (\n    IncompleteResultsError,\n    NoDatasetFound,\n)\nfrom datalad.support.gitrepo import GitRepo\nfrom datalad.tests.utils_pytest import (\n    DEFAULT_BRANCH,\n    DEFAULT_REMOTE,\n    OBSCURE_FILENAME,\n    assert_in,\n    assert_in_results,\n    assert_raises,\n    assert_repo_status,\n    assert_result_count,\n    assert_status,\n    eq_,\n    get_deeply_nested_structure,\n    known_failure_githubci_win,\n    nok_,\n    ok_,\n    with_tempfile,\n)\nfrom datalad.utils import chpwd\n\n\nckwa = dict(result_renderer='disabled')\n\n\n@with_tempfile\n@with_tempfile\ndef test_drop_file_content(path=None, outside_path=None):\n    # see docstring for test data structure\n    ds = get_deeply_nested_structure(path)\n    axfile_rootds = op.join(\"subdir\", \"annexed_file.txt\")\n    axfile_subds = op.join(\"subds_modified\", \"subdir\", \"annexed_file.txt\")\n    gitfile = op.join(\"subdir\", \"git_file.txt\")\n\n    # refuse to operate on non-ds paths\n    assert_in_results(\n        ds.drop(outside_path, on_failure='ignore'),\n        status='error',\n        message=('path not underneath the reference dataset %s', ds)\n    )\n    # we only have a single copy of annexed files right now\n    # check that it is not dropped by default\n    with assert_raises(IncompleteResultsError) as cme:\n        ds.drop(axfile_rootds)\n    # The --force suggestion from git-annex-drop is translated to --reckless.\n    assert_in(\"--reckless\", str(cme.value))\n\n    # error on non-existing paths\n    non_existant_relpaths = ['funky', op.join('subds_modified', 'subfunky')]\n    res = ds.drop(non_existant_relpaths, on_failure='ignore')\n    # only two results, one per file\n    assert_result_count(res, len(non_existant_relpaths))\n    for rp in non_existant_relpaths:\n        assert_in_results(\n            res,\n            type='file',\n            status='error',\n            action='drop',\n            error_message='File unknown to git',\n            path=str(ds.pathobj / rp),\n            refds=ds.path,\n        )\n\n    # drop multiple files from different datasets\n    ok_(ds.repo.file_has_content(axfile_rootds))\n    res = ds.drop(\n        [axfile_rootds, axfile_subds],\n        reckless='availability',\n        jobs=2,\n        on_failure='ignore')\n    nok_(ds.repo.file_has_content(axfile_rootds))\n    assert_result_count(res, 2)\n    for rp in [axfile_rootds, axfile_subds]:\n        assert_in_results(\n            res,\n            type='file',\n            status='ok',\n            action='drop',\n            path=str(ds.pathobj / rp),\n            refds=ds.path,\n        )\n\n    # dropping file content for files in git\n    res = ds.drop(gitfile, on_failure='ignore')\n    assert_result_count(res, 1)\n    assert_in_results(\n        res,\n        type='file',\n        # why is this 'notneeded' and not 'impossible'\n        # if the latter, any operation on any dataset with a\n        # single file in git would fail\n        status='notneeded',\n        action='drop',\n        message=\"no annex'ed content\",\n        path=str(ds.pathobj / gitfile),\n        refds=ds.path,\n    )\n\n    # modified files, we cannot drop their content\n    modfile = ds.pathobj / axfile_rootds\n    modfile.unlink()\n    modfile.write_text('new content')\n    res = ds.drop(modfile, on_failure='ignore')\n    assert_in_results(\n        res,\n        status='impossible',\n        action='drop',\n        message=\"cannot drop modified content, save first\",\n        path=str(modfile),\n        refds=ds.path,\n    )\n\n    # detection of untracked content\n    untrackeddir = ds.pathobj / 'subds_modified' / 'subds_lvl1_modified' / \\\n        f'{OBSCURE_FILENAME}_directory_untracked'\n    res = ds.drop(untrackeddir, on_failure='ignore')\n    assert_in_results(\n        res,\n        status='impossible',\n        action='drop',\n        message=\"cannot drop untracked content, save first\",\n        path=str(untrackeddir),\n        type='directory',\n        refds=ds.path,\n    )\n\n    # and lastly, recursive drop\n    res = ds.drop(recursive=True, on_failure='ignore')\n    # there is not much to test here (we already dropped the only\n    # annexed files above). however, we should see results from the top\n    # ds, and the most-bottom ds\n    # subdatasets\n    for p in [ds.pathobj / 'subdir' / 'file_modified',\n              untrackeddir]:\n        assert_in_results(res, path=str(p))\n\n\n@with_tempfile\n@with_tempfile\ndef test_drop_allkeys(origpath=None, clonepath=None):\n    # create a dataset with two keys, belonging to two files,\n    # in two different branches\n    ds = Dataset(origpath).create()\n    repo = ds.repo\n    repo.call_git(['checkout', '-b', 'otherbranch'])\n    (ds.pathobj / 'file1').write_text('file1')\n    ds.save()\n    repo.call_git(['checkout', DEFAULT_BRANCH])\n    (ds.pathobj / 'file2').write_text('file2')\n    ds.save()\n\n    # confirm we have two keys\n    eq_(2, repo.call_annex_records(['info'])[0]['local annex keys'])\n\n    # do it wrong first\n    assert_in_results(\n        ds.drop('some', what='allkeys', on_failure='ignore'),\n        status='impossible',\n        type='dataset',\n        action='drop',\n        message=(\n            'cannot drop %s, with path constraints given: %s',\n            'allkeys', [ds.pathobj / 'some']),\n    )\n    # confirm we still have two keys\n    eq_(2, repo.call_annex_records(['info'])[0]['local annex keys'])\n\n    # clone the beast and get all keys into the clone\n    dsclone = clone(ds.path, clonepath)\n    dsclone.repo.call_annex(['get', '--all'])\n    # confirm we have two keys in the clone\n    eq_(2, dsclone.repo.call_annex_records(['info'])[0]['local annex keys'])\n\n    # now cripple availability by dropping the \"hidden\" key at origin\n    repo.call_annex(['drop', '--branch', 'otherbranch', '--force'])\n    # confirm one key left\n    eq_(1, repo.call_annex_records(['info'])[0]['local annex keys'])\n\n    # and now drop all keys from the clone, one is redundant and can be\n    # dropped, the other is not and must fail\n    res = dsclone.drop(what='allkeys', jobs=2, on_failure='ignore')\n    # confirm one key gone, one left\n    eq_(1, dsclone.repo.call_annex_records(['info'])[0]['local annex keys'])\n    assert_result_count(res, 1, action='drop', status='error', type='key')\n    assert_result_count(res, 1, action='drop', status='ok', type='key')\n    # now force it\n    res = dsclone.drop(what='allkeys', reckless='availability',\n                       on_failure='ignore')\n    assert_result_count(res, 1)\n    assert_result_count(res, 1, action='drop', status='ok', type='key')\n    # all gone\n    eq_(0, dsclone.repo.call_annex_records(['info'])[0]['local annex keys'])\n\n\n@with_tempfile\n@with_tempfile\n@with_tempfile\ndef test_undead_annex_detection(gitpath=None, origpath=None, clonepath=None):\n    gitds = Dataset(gitpath).create(annex=False)\n    # a gitrepo can be inspected too, it might just not know anything\n    eq_([], _detect_nondead_annex_at_remotes(gitds.repo, 'someid'))\n\n    origds = Dataset(origpath).create()\n    origrepo = origds.repo\n    # only the local repo knows about its own annex\n    eq_([None], _detect_nondead_annex_at_remotes(origrepo, origrepo.uuid))\n\n    # works with clones\n    cloneds = clone(origds, clonepath)\n    clonerepo = cloneds.repo\n    # the clone now know two locations, itself and origin\n    eq_([None, DEFAULT_REMOTE],\n        _detect_nondead_annex_at_remotes(clonerepo, origrepo.uuid))\n    # just from cloning the original repo location does not learn\n    # about the new annex in the clone\n    eq_([], _detect_nondead_annex_at_remotes(origrepo, clonerepo.uuid))\n    # it will know after a push\n    cloneds.push()\n    eq_([None], _detect_nondead_annex_at_remotes(origrepo, clonerepo.uuid))\n    # we can declare an annex dead (here done at original location)\n    origrepo.call_annex(['dead', clonerepo.uuid])\n    eq_([], _detect_nondead_annex_at_remotes(origrepo, clonerepo.uuid))\n    # again not automatically communicated to clones\n    eq_([None, DEFAULT_REMOTE],\n        _detect_nondead_annex_at_remotes(clonerepo, clonerepo.uuid))\n    # but a fetch will make the death known\n    clonerepo.call_git(['fetch'])\n    eq_([None],\n        _detect_nondead_annex_at_remotes(clonerepo, clonerepo.uuid))\n    # after a local git-annex branch synchronization, it is completely\n    # \"gone\"\n    clonerepo.localsync()\n    eq_([],\n        _detect_nondead_annex_at_remotes(clonerepo, clonerepo.uuid))\n\n\n@with_tempfile\ndef test_uninstall_recursive(path=None):\n    ds = Dataset(path)\n    assert_raises(ValueError, ds.drop)\n\n    ds = ds.create()\n    subds = ds.create('sub')\n\n    # fail to uninstall with subdatasets present\n    res = ds.drop(\n        what='all', reckless='availability', on_failure='ignore')\n    assert_in_results(\n        res,\n        action='uninstall',\n        path=ds.path,\n        type='dataset',\n        status='error',\n        message=('cannot drop dataset, subdataset(s) still present '\n                 '(forgot --recursive?): %s', [subds.path]),\n    )\n    res = ds.drop(\n        what='all', reckless='availability', recursive=True,\n        on_failure='ignore')\n    # both datasets gone\n    assert_result_count(res, 2)\n    assert_result_count(res, 2, type='dataset', status='ok')\n    # the subdataset is reported first\n    eq_([subds.path, ds.path],\n        [r.get('path') for r in res])\n    # no dataset installed anymore\n    eq_(ds.is_installed(), False)\n    # not even a trace\n    eq_(ds.pathobj.exists(), False)\n\n\n@with_tempfile\n@with_tempfile\ndef test_unpushed_state_detection(origpath=None, clonepath=None):\n    origds = Dataset(origpath).create()\n    # always test in annex mode\n    tester = lambda x: _detect_unpushed_revs(x, True)\n\n    origrepo = origds.repo\n    # this is still a unique repo, all payload branches are\n    # unpushed\n    eq_([DEFAULT_BRANCH], tester(origrepo))\n    origrepo.call_git(['checkout', '-b', 'otherbranch'])\n    eq_([DEFAULT_BRANCH, 'otherbranch'],\n        tester(origrepo))\n    # let's advance the state by one\n    (origds.pathobj / 'file1').write_text('some text')\n    origds.save()\n    # same picture\n    eq_([DEFAULT_BRANCH, 'otherbranch'],\n        tester(origrepo))\n    # back to original branch\n    origrepo.call_git(['checkout', DEFAULT_BRANCH])\n\n    # now lets clone\n    cloneds = clone(origds, clonepath)\n    clonerepo = cloneds.repo\n    # right after the clone there will be no unpushed changes\n    eq_([], tester(clonerepo))\n    # even with more than one branch in the clone\n    clonerepo.call_git(['checkout', '-t', f'{DEFAULT_REMOTE}/otherbranch'])\n    eq_([], tester(clonerepo))\n\n    # let's advance the local state now\n    (cloneds.pathobj / 'file2').write_text('some other text')\n    cloneds.save()\n    # only the modified branch is detected\n    eq_(['otherbranch'], tester(clonerepo))\n    # a push will bring things into the clear\n    cloneds.push(to=DEFAULT_REMOTE)\n    eq_([], tester(clonerepo))\n\n    # now detach HEAD, should work and not somehow require a branch\n    cloneds.repo.call_git(['reset', '--hard', 'HEAD~1'])\n    # we have this state\n    eq_([], tester(clonerepo))\n\n\n@with_tempfile(mkdir=True)\n@with_tempfile\n@with_tempfile\ndef test_safetynet(otherpath=None, origpath=None, clonepath=None):\n    # we start with a dataset that is hosted somewhere\n    origds = Dataset(origpath).create()\n    # a clone is made to work on the dataset\n    cloneds = clone(origds, clonepath)\n    # checkout a different branch at origin to simplify testing below\n    origds.repo.call_git(['checkout', '-b', 'otherbranch'])\n\n    # an untracked file is added to simulate some work\n    (cloneds.pathobj / 'file1').write_text('some text')\n    # now we try to drop the entire dataset in a variety of ways\n    # to check that it does not happen too quickly\n\n    # cannot use invalid mode switch values\n    assert_raises(ValueError, drop, what='bananas')\n    assert_raises(ValueError, drop, reckless='rubberducky')\n\n    # cannot simple run drop somewhere and give a path to a dataset\n    # to drop\n    with chpwd(otherpath):\n        assert_raises(NoDatasetFound, drop, clonepath, what='all')\n    ok_(cloneds.is_installed())\n\n    # refuse to remove the CWD\n    with chpwd(clonepath):\n        assert_raises(RuntimeError, drop, what='all')\n    ok_(cloneds.is_installed())\n\n    assert_in_results(\n        cloneds.drop(what='all', on_failure='ignore'),\n        message='cannot drop untracked content, save first',\n        status='impossible')\n    ok_(cloneds.is_installed())\n\n    # so let's save...\n    cloneds.save()\n    # - branch is progressed\n    # - a new key is only available here\n    res = cloneds.drop(what='all', on_failure='ignore')\n    assert_in_results(res, action=\"uninstall\", status=\"error\")\n    ok_(res[0]['message'][0].startswith(\n        \"to-be-dropped dataset has revisions \"\n        \"that are not available at any known sibling\"))\n    ok_(cloneds.is_installed())\n\n    # so let's push -- git only\n    # we cannot use git-push directly, it would not handle\n    # managed branches properly\n    cloneds.push(data='nothing')\n\n    res = cloneds.drop(what='all', on_failure='ignore')\n    assert_in_results(res, action=\"uninstall\", status=\"error\")\n    ok_(res[0]['message'][0].startswith(\n        \"to-be-deleted local annex not declared 'dead'\"))\n    # some windows test setup is not very robust, explicitly\n    # include the default name \"origin\" in the test success\n    # conditions to make this more robust\n    eq_(res[0]['message'][1], [DEFAULT_REMOTE])\n    ok_(cloneds.is_installed())\n\n    # announce dead\n    cloneds.repo.call_annex(['dead', 'here'])\n    # but just a local declaration is not good enough\n    assert_in_results(\n        cloneds.drop(what='all', on_failure='ignore'),\n        status='error')\n    ok_(cloneds.is_installed())\n\n    # so let's push that announcement also\n    cloneds.push(data='nothing')\n\n    res = cloneds.drop(what='all', on_failure='ignore')\n    assert_in_results(res, action=\"drop\", status=\"error\")\n    ok_(res[0]['message'].startswith(\n        \"unsafe\\nCould only verify the existence of \"\n        \"0 out of 1 necessary\"),\n        msg=f\"Results were {res}\")\n    ok_(cloneds.is_installed())\n\n    # so let's push all\n    cloneds.push()\n\n    # and kill the beast!\n    res = cloneds.drop(what='all', on_failure='ignore')\n    # only now we also drop the key!\n    assert_result_count(res, 2)\n    assert_in_results(\n        res, action='drop', type='key', status='ok', path=cloneds.path)\n    assert_in_results(\n        res, action='uninstall', type='dataset', status='ok', path=cloneds.path)\n\n\n@with_tempfile\ndef test_kill(path=None):\n    # create a complicated and dirty mess\n    ds = get_deeply_nested_structure(path)\n    # cannot use kill without recursion enabled, because there will be no\n    # checks for subdatasets, hence we cannot make the impression that\n    # this would be a surgical operation\n    assert_raises(ValueError, ds.drop, what='all', reckless='kill')\n    # wipe it out\n    res = ds.drop(what='all', reckless='kill', recursive=True)\n    assert_result_count(res, 1)\n    assert_in_results(\n        res,\n        status='ok',\n        path=ds.path,\n        type='dataset',\n        action='uninstall',\n    )\n    eq_(False, ds.is_installed())\n    eq_(False, ds.pathobj.exists())\n\n\n@with_tempfile\ndef test_kill_7013(path=None):\n    \"\"\"check that a recursive kill does not silently skip subdatasets\n     contained in subdirectory: github.com/datalad/datalad/issues/7013\"\"\"\n    ds = Dataset(path).create()\n    (ds.pathobj / 'subdir').mkdir()\n    ds.create('subdir/subds')\n    ds.create('subds')\n    res = ds.drop(path=['subds', 'subdir'], what='all',\n                  reckless='kill', recursive=True)\n    # ensure that both subdatasets were killed\n    subs = ds.subdatasets(state='present')\n    assert len(subs) == 0\n\n\n@with_tempfile()\ndef test_refuse_to_drop_cwd(path=None):\n    ds = Dataset(path).create()\n    (ds.pathobj / 'deep' / 'down').mkdir(parents=True)\n    for p in (ds.pathobj, ds.pathobj / 'deep', ds.pathobj / 'deep' / 'down'):\n        with chpwd(str(p)):\n            # will never remove PWD, or anything outside the dataset\n            for target in (ds.pathobj, os.curdir, os.pardir, op.join(os.pardir, os.pardir)):\n                assert_raises(RuntimeError, drop, path=target, what='all')\n    sub = ds.create('sub')\n    subsub = sub.create('subsub')\n    for p in (sub.path, subsub.path):\n        with chpwd(p):\n            assert_raises(RuntimeError, drop, what='all')\n\n\n@with_tempfile()\ndef test_careless_subdataset_uninstall(path=None):\n    # nested datasets\n    ds = Dataset(path).create()\n    subds1 = ds.create('deep1')\n    ds.create('deep2')\n    eq_(sorted(ds.subdatasets(result_xfm='relpaths')), ['deep1', 'deep2'])\n    assert_repo_status(ds.path)\n    # now we kill the sub without the parent knowing\n    subds1.drop(what='all', reckless='kill', recursive=True)\n    ok_(not subds1.is_installed())\n    # mountpoint exists\n    ok_(subds1.pathobj.exists())\n    assert_repo_status(ds.path)\n    # parent still knows the sub\n    eq_(sorted(ds.subdatasets(result_xfm='relpaths')), ['deep1', 'deep2'])\n\n\n@with_tempfile(mkdir=True)\ndef test_drop_nocrash_absent_subds(path=None):\n    parent = Dataset(path).create()\n    parent.create('sub')\n    parent.drop('sub', reckless='availability')\n    assert_repo_status(parent.path)\n    with chpwd(path):\n        assert_status('notneeded', drop('.', recursive=True))\n\n\n@with_tempfile(mkdir=True)\ndef test_uninstall_without_super(path=None):\n    # a parent dataset with a proper subdataset, and another dataset that\n    # is just placed underneath the parent, but not an actual subdataset\n    parent = Dataset(path).create()\n    sub = parent.create('sub')\n    assert_repo_status(parent.path)\n    nosub = create(op.join(parent.path, 'nosub'))\n    assert_repo_status(nosub.path)\n    subreport = parent.subdatasets()\n    assert_result_count(subreport, 1, path=sub.path)\n    assert_result_count(subreport, 0, path=nosub.path)\n    # it should be possible to uninstall the proper subdataset, even without\n    # explicitly calling the uninstall methods of the parent -- things should\n    # be figured out by datalad\n    with chpwd(parent.path):\n        # check False because revisions are not pushed\n        drop(sub.path, what='all', reckless='availability')\n    nok_(sub.is_installed())\n    # no present subdatasets anymore\n    subreport = parent.subdatasets()\n    assert_result_count(subreport, 1)\n    assert_result_count(subreport, 1, path=sub.path, state='absent')\n    assert_result_count(subreport, 0, path=nosub.path)\n    # but we should fail on an attempt to uninstall the non-subdataset\n    with chpwd(nosub.path):\n        assert_raises(RuntimeError, drop, what='all')\n\n\n@with_tempfile\ndef test_drop_from_git(path=None):\n    ds = Dataset(path).create(annex=False)\n    res = ds.drop()\n    assert_in_results(res, action='drop', status='notneeded')\n    (ds.pathobj / 'file').write_text('some')\n    ds.save()\n    assert_status('notneeded', ds.drop('file'))\n    assert_status('notneeded', ds.drop(what='allkeys'))\n\n\n# https://github.com/datalad/datalad/issues/6180\n@with_tempfile\n@with_tempfile\ndef test_drop_uninit_annexrepo(origpath=None, path=None):\n    Dataset(origpath).create()\n    # just git-clone to bypass `git annex init`\n    GitRepo.clone(origpath, path)\n    ds = Dataset(path)\n    assert_status('ok', ds.drop(what='datasets'))\n    nok_(ds.is_installed())\n\n\n# https://github.com/datalad/datalad/issues/6577\n@with_tempfile\ndef test_drop_allkeys_result_contains_annex_error_messages(path=None):\n    # when calling drop with allkeys, expect git-annex error\n    # message(s) to be present in the result record error_message\n    ds = Dataset(path).create()\n    # mock annex call always yielding error-messages in this dataset\n    with patch.object(ds.repo, '_call_annex_records_items_') as mock_call:\n        mock_call.return_value = iter([{\n            'command': 'drop',\n            'success': False,\n            'error-messages': ['git-annex error message here']}])\n        assert_in_results(\n            ds.drop(what='allkeys', on_failure='ignore'),\n            error_message='git-annex error message here',\n        )\n\n\n# https://github.com/datalad/datalad/issues/6948\n@known_failure_githubci_win  # recent git-annex, https://github.com/datalad/datalad/issues/7197\n@with_tempfile\n@with_tempfile\ndef test_nodrop_symlinked_annex(origpath=None, clonepath=None):\n    # create a dataset with a key\n    ds = Dataset(origpath).create(**ckwa)\n    testfile = ds.pathobj / 'file1'\n    testcontent = 'precious'\n    testfile.write_text(testcontent)\n    ds.save(**ckwa)\n    rec = ds.status(testfile, annex='availability',\n                    return_type='item-or-list', **ckwa)\n    eq_(testcontent, Path(rec['objloc']).read_text())\n\n    def _droptest(_ds):\n        # drop refuses to drop from a symlinked annex\n        if (_ds.repo.dot_git / 'annex').is_symlink():\n            assert_raises(AssertionError, _ds.drop, **ckwa)\n            for what in ('all', 'allkeys', 'filecontent', 'datasets'):\n                assert_raises(AssertionError, _ds.drop, what=what, **ckwa)\n        # but a reckless kill works without crashing\n        _ds.drop(what='all', recursive=True, reckless='kill', **ckwa)\n        nok_(_ds.is_installed())\n        # nothing has made the original file content vanish\n        _rec = ds.status(\n            testfile, annex='availability',\n            return_type='item-or-list', **ckwa)\n        eq_(testcontent, Path(_rec['objloc']).read_text())\n\n    # test on a clone that does not know it has key access\n    dsclone1 = clone(ds.path, clonepath, reckless='ephemeral', **ckwa)\n    _droptest(dsclone1)\n\n    # test again on a clone that does think it has a key copy\n    dsclone2 = clone(ds.path, clonepath, reckless='ephemeral', **ckwa)\n    if not dsclone2.repo.is_managed_branch():\n        # it really should not matter, but 'origin' is set to annex.ignore=true\n        # on crippledFS\n        # https://github.com/datalad/datalad/issues/6960\n        dsclone2.get('.')\n    _droptest(dsclone2)\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.18.0+7.g8d606e1ed\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 65ms\nInstalled 25 packages in 3.18s\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 65 packages in 4.31s\nPrepared 1 package in 4.72s\nInstalled 53 packages in 1.00s\n + annexremote==1.6.6\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==4.0.0\n + charset-normalizer==3.4.1\n + citeproc-py==0.6.0\n + cryptography==44.0.0\n + datalad==0.18.0+7.g8d606e1ed (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_8d606e1ed9765cec669981b696049033af9b9dc9)\n + distro==1.9.0\n + duecredit==0.10.2\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 + mypy==0.991\n + mypy-extensions==1.0.0\n + patool==1.15.0\n + platformdirs==4.3.6\n + propcache==0.2.1\n + pycparser==2.22\n + pyperclip==1.9.0\n + pytest-fail-slow==0.6.0\n + python-gitlab==5.3.0\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 + soupsieve==2.6\n + tqdm==4.67.1\n + types-python-dateutil==2.9.0.20241206\n + types-requests==2.32.0.20241016\n + urllib3==2.3.0\n + vcrpy==4.3.0\n + wrapt==1.17.0\n + yarl==1.18.3\nResolved 97 packages in 247ms\nInstalled 32 packages in 530ms\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 + execnet==2.1.1\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 + psutil==6.1.1\n + pygments==2.18.0\n + pympler==1.1\n + pypandoc==1.14\n + pyproject-hooks==1.2.0\n + pytest-xdist==3.6.1\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_8d606e1ed9765cec669981b696049033af9b9dc9\nconfigfile: tox.ini\nplugins: fail-slow-0.6.0, xdist-3.6.1, cov-6.0.0\ncollected 16 items\n\nr2e_tests/test_1.py .FF.FF..........                                     [100%]\n\n=================================== FAILURES ===================================\n______________________________ test_drop_allkeys _______________________________\n\norigpath = '/tmp/datalad_temp_test_drop_allkeysule0s15j'\nclonepath = '/tmp/datalad_temp_test_drop_allkeyszczd_a23'\n\n    @with_tempfile\n    @with_tempfile\n    def test_drop_allkeys(origpath=None, clonepath=None):\n        # create a dataset with two keys, belonging to two files,\n        # in two different branches\n        ds = Dataset(origpath).create()\n        repo = ds.repo\n        repo.call_git(['checkout', '-b', 'otherbranch'])\n        (ds.pathobj / 'file1').write_text('file1')\n        ds.save()\n>       repo.call_git(['checkout', DEFAULT_BRANCH])\n\nr2e_tests/test_1.py:175: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \ndatalad/dataset/gitrepo.py:453: in call_git\n    return \"\".join(\ndatalad/dataset/gitrepo.py:517: in call_git_items_\n    for file_no, line in self._generator_call_git(\ndatalad/dataset/gitrepo.py:355: in _generator_call_git\n    for file_no, content in generator:\n../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/_collections_abc.py:330: in __next__\n    return self.send(None)\ndatalad/runner/nonasyncrunner.py:97: in send\n    return self._locked_send(message)\ndatalad/runner/nonasyncrunner.py:122: in _locked_send\n    self._check_result()\ndatalad/runner/nonasyncrunner.py:93: in _check_result\n    self.runner._check_result()\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <datalad.runner.nonasyncrunner.ThreadedRunner object at 0x7dc9dfdb53a0>\n\n    def _check_result(self):\n        if self.exception_on_error is True:\n            if self.return_code not in (0, None):\n                protocol = self.protocol\n                decoded_output = {\n                    source: protocol.fd_infos[fileno][1].decode(protocol.encoding)\n                    for source, fileno in (\n                        (\"stdout\", protocol.stdout_fileno),\n                        (\"stderr\", protocol.stderr_fileno))\n                    if protocol.fd_infos[fileno][1] is not None\n                }\n>               raise CommandError(\n                    cmd=self.cmd,\n                    code=self.return_code,\n                    stdout=decoded_output.get(\"stdout\", None),\n                    stderr=decoded_output.get(\"stderr\", None)\n                )\nE               datalad.runner.exception.CommandError: CommandError: 'git -c diff.ignoreSubmodules=none checkout dl-test-branch' failed with exitcode 1 [err: 'error: pathspec 'dl-test-branch' did not match any file(s) known to git']\n\ndatalad/runner/nonasyncrunner.py:295: CommandError\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nadd(ok): file1 (file)\nsave(ok): . (dataset)\naction summary:\n  add (ok: 1)\n  save (ok: 1)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_drop_allkeysule0s15j\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n_________________________ test_undead_annex_detection __________________________\n\ngitpath = '/tmp/datalad_temp_test_undead_annex_detectionmyscpyxp'\norigpath = '/tmp/datalad_temp_test_undead_annex_detectionp0yx4rf6'\nclonepath = '/tmp/datalad_temp_test_undead_annex_detectionlhf_z2w8'\n\n    @with_tempfile\n    @with_tempfile\n    @with_tempfile\n    def test_undead_annex_detection(gitpath=None, origpath=None, clonepath=None):\n        gitds = Dataset(gitpath).create(annex=False)\n        # a gitrepo can be inspected too, it might just not know anything\n        eq_([], _detect_nondead_annex_at_remotes(gitds.repo, 'someid'))\n    \n        origds = Dataset(origpath).create()\n        origrepo = origds.repo\n        # only the local repo knows about its own annex\n        eq_([None], _detect_nondead_annex_at_remotes(origrepo, origrepo.uuid))\n    \n        # works with clones\n        cloneds = clone(origds, clonepath)\n        clonerepo = cloneds.repo\n        # the clone now know two locations, itself and origin\n>       eq_([None, DEFAULT_REMOTE],\n            _detect_nondead_annex_at_remotes(clonerepo, origrepo.uuid))\n\nr2e_tests/test_1.py:239: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nfirst = [None, 'dl-test-remote'], second = [None, 'origin'], msg = None\n\n    def assert_equal(first, second, msg=None):\n        if msg is None:\n>           assert first == second\nE           AssertionError: assert [None, 'dl-test-remote'] == [None, 'origin']\nE             \nE             At index 1 diff: 'dl-test-remote' != 'origin'\nE             Use -v to get more diff\n\ndatalad/tests/utils_pytest.py:73: AssertionError\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\ncreate(ok): . (dataset)\ninstall(ok): /tmp/datalad_temp_test_undead_annex_detectionlhf_z2w8 (dataset)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] Attempting a clone into /tmp/datalad_temp_test_undead_annex_detectionlhf_z2w8 \n[INFO] Attempting to clone from /tmp/datalad_temp_test_undead_annex_detectionp0yx4rf6 to /tmp/datalad_temp_test_undead_annex_detectionlhf_z2w8 \n[INFO] Completed clone attempts for Dataset(/tmp/datalad_temp_test_undead_annex_detectionlhf_z2w8) \n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.distributed.clone:log.py:432 Attempting a clone into /tmp/datalad_temp_test_undead_annex_detectionlhf_z2w8\nINFO     datalad.core.distributed.clone:log.py:432 Attempting to clone from /tmp/datalad_temp_test_undead_annex_detectionp0yx4rf6 to /tmp/datalad_temp_test_undead_annex_detectionlhf_z2w8\nINFO     datalad.core.distributed.clone:log.py:432 Completed clone attempts for Dataset(/tmp/datalad_temp_test_undead_annex_detectionlhf_z2w8)\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n________________________ test_unpushed_state_detection _________________________\n\norigpath = '/tmp/datalad_temp_test_unpushed_state_detectionbxcwtdnw'\nclonepath = '/tmp/datalad_temp_test_unpushed_state_detectionks1pge68'\n\n    @with_tempfile\n    @with_tempfile\n    def test_unpushed_state_detection(origpath=None, clonepath=None):\n        origds = Dataset(origpath).create()\n        # always test in annex mode\n        tester = lambda x: _detect_unpushed_revs(x, True)\n    \n        origrepo = origds.repo\n        # this is still a unique repo, all payload branches are\n        # unpushed\n>       eq_([DEFAULT_BRANCH], tester(origrepo))\n\nr2e_tests/test_1.py:309: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nfirst = ['dl-test-branch'], second = ['master'], msg = None\n\n    def assert_equal(first, second, msg=None):\n        if msg is None:\n>           assert first == second\nE           AssertionError: assert ['dl-test-branch'] == ['master']\nE             \nE             At index 0 diff: 'dl-test-branch' != 'master'\nE             Use -v to get more diff\n\ndatalad/tests/utils_pytest.py:73: AssertionError\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n________________________________ test_safetynet ________________________________\n\notherpath = '/tmp/datalad_temp_test_safetynet86o7x5iz'\norigpath = '/tmp/datalad_temp_test_safetynetc0sgcio4'\nclonepath = '/tmp/datalad_temp_test_safetynet2ucg2zj5'\n\n    @with_tempfile(mkdir=True)\n    @with_tempfile\n    @with_tempfile\n    def test_safetynet(otherpath=None, origpath=None, clonepath=None):\n        # we start with a dataset that is hosted somewhere\n        origds = Dataset(origpath).create()\n        # a clone is made to work on the dataset\n        cloneds = clone(origds, clonepath)\n        # checkout a different branch at origin to simplify testing below\n        origds.repo.call_git(['checkout', '-b', 'otherbranch'])\n    \n        # an untracked file is added to simulate some work\n        (cloneds.pathobj / 'file1').write_text('some text')\n        # now we try to drop the entire dataset in a variety of ways\n        # to check that it does not happen too quickly\n    \n        # cannot use invalid mode switch values\n        assert_raises(ValueError, drop, what='bananas')\n        assert_raises(ValueError, drop, reckless='rubberducky')\n    \n        # cannot simple run drop somewhere and give a path to a dataset\n        # to drop\n        with chpwd(otherpath):\n            assert_raises(NoDatasetFound, drop, clonepath, what='all')\n        ok_(cloneds.is_installed())\n    \n        # refuse to remove the CWD\n        with chpwd(clonepath):\n            assert_raises(RuntimeError, drop, what='all')\n        ok_(cloneds.is_installed())\n    \n        assert_in_results(\n            cloneds.drop(what='all', on_failure='ignore'),\n            message='cannot drop untracked content, save first',\n            status='impossible')\n        ok_(cloneds.is_installed())\n    \n        # so let's save...\n        cloneds.save()\n        # - branch is progressed\n        # - a new key is only available here\n        res = cloneds.drop(what='all', on_failure='ignore')\n        assert_in_results(res, action=\"uninstall\", status=\"error\")\n        ok_(res[0]['message'][0].startswith(\n            \"to-be-dropped dataset has revisions \"\n            \"that are not available at any known sibling\"))\n        ok_(cloneds.is_installed())\n    \n        # so let's push -- git only\n        # we cannot use git-push directly, it would not handle\n        # managed branches properly\n        cloneds.push(data='nothing')\n    \n        res = cloneds.drop(what='all', on_failure='ignore')\n        assert_in_results(res, action=\"uninstall\", status=\"error\")\n        ok_(res[0]['message'][0].startswith(\n            \"to-be-deleted local annex not declared 'dead'\"))\n        # some windows test setup is not very robust, explicitly\n        # include the default name \"origin\" in the test success\n        # conditions to make this more robust\n>       eq_(res[0]['message'][1], [DEFAULT_REMOTE])\n\nr2e_tests/test_1.py:406: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nfirst = ['origin'], second = ['dl-test-remote'], msg = None\n\n    def assert_equal(first, second, msg=None):\n        if msg is None:\n>           assert first == second\nE           AssertionError: assert ['origin'] == ['dl-test-remote']\nE             \nE             At index 0 diff: 'origin' != 'dl-test-remote'\nE             Use -v to get more diff\n\ndatalad/tests/utils_pytest.py:73: AssertionError\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\ninstall(ok): /tmp/datalad_temp_test_safetynet2ucg2zj5 (dataset)\ndrop(impossible): file1 (file) [cannot drop untracked content, save first]\nadd(ok): file1 (file)\nsave(ok): . (dataset)\naction summary:\n  add (ok: 1)\n  save (ok: 1)\nuninstall(error): . (dataset) [to-be-dropped dataset has revisions that are not available at any known sibling. Use `datalad push --to ...` to push these before dropping the local dataset, or ignore via `--reckless availability`. Unique revisions: ['master']]\npublish(ok): . (dataset) [refs/heads/git-annex->origin:refs/heads/git-annex 972d4e6..80c3417]\npublish(ok): . (dataset) [refs/heads/master->origin:refs/heads/master b5ade60..afd9a0f]\naction summary:\n  publish (ok: 2)\nuninstall(error): . (dataset) [to-be-deleted local annex not declared 'dead' at the following siblings. Announce death (`git annex dead here` + `datalad push --to ...`), or ignore via `--reckless undead`: ['origin']]\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] Attempting a clone into /tmp/datalad_temp_test_safetynet2ucg2zj5 \n[INFO] Attempting to clone from /tmp/datalad_temp_test_safetynetc0sgcio4 to /tmp/datalad_temp_test_safetynet2ucg2zj5 \n[INFO] Completed clone attempts for Dataset(/tmp/datalad_temp_test_safetynet2ucg2zj5) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] Determine push target \n[INFO] Push refspecs \n[INFO] Update availability information \n[INFO] Start enumerating objects \n[INFO] Start counting objects \n[INFO] Start compressing objects \n[INFO] Start writing objects \n[INFO] Finished push of Dataset(/tmp/datalad_temp_test_safetynet2ucg2zj5) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.distributed.clone:log.py:432 Attempting a clone into /tmp/datalad_temp_test_safetynet2ucg2zj5\nINFO     datalad.core.distributed.clone:log.py:432 Attempting to clone from /tmp/datalad_temp_test_safetynetc0sgcio4 to /tmp/datalad_temp_test_safetynet2ucg2zj5\nINFO     datalad.core.distributed.clone:log.py:432 Completed clone attempts for Dataset(/tmp/datalad_temp_test_safetynet2ucg2zj5)\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_safetynet2ucg2zj5\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.distributed.push:log.py:432 Determine push target\nINFO     datalad.core.distributed.push:log.py:432 Push refspecs\nINFO     datalad.core.distributed.push:log.py:432 Update availability information\nINFO     datalad.gitrepo:log.py:432 Start enumerating objects\nINFO     datalad.gitrepo:log.py:432 Finished enumerating objects\nINFO     datalad.gitrepo:log.py:432 Start counting objects\nINFO     datalad.gitrepo:log.py:432 Counting objects:   7% (1/14)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  14% (2/14)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  21% (3/14)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  28% (4/14)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  35% (5/14)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  42% (6/14)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  50% (7/14)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  57% (8/14)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  64% (9/14)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  71% (10/14)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  78% (11/14)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  85% (12/14)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  92% (13/14)\nINFO     datalad.gitrepo:log.py:432 Counting objects: 100% (14/14)\nINFO     datalad.gitrepo:log.py:432 Counting objects: 100% (14/14), done.\nINFO     datalad.gitrepo:log.py:432 Finished counting objects\nINFO     datalad.gitrepo:log.py:432 Start compressing objects\nINFO     datalad.gitrepo:log.py:432 Compressing objects:  11% (1/9)\nINFO     datalad.gitrepo:log.py:432 Compressing objects:  22% (2/9)\nINFO     datalad.gitrepo:log.py:432 Compressing objects:  33% (3/9)\nINFO     datalad.gitrepo:log.py:432 Compressing objects:  44% (4/9)\nINFO     datalad.gitrepo:log.py:432 Compressing objects:  55% (5/9)\nINFO     datalad.gitrepo:log.py:432 Compressing objects:  66% (6/9)\nINFO     datalad.gitrepo:log.py:432 Compressing objects:  77% (7/9)\nINFO     datalad.gitrepo:log.py:432 Compressing objects:  88% (8/9)\nINFO     datalad.gitrepo:log.py:432 Compressing objects: 100% (9/9)\nINFO     datalad.gitrepo:log.py:432 Compressing objects: 100% (9/9), done.\nINFO     datalad.gitrepo:log.py:432 Finished compressing objects\nINFO     datalad.gitrepo:log.py:432 Start writing objects\nINFO     datalad.gitrepo:log.py:432 Writing objects:   9% (1/11)\nINFO     datalad.gitrepo:log.py:432 Writing objects:  18% (2/11)\nINFO     datalad.gitrepo:log.py:432 Writing objects:  27% (3/11)\nINFO     datalad.gitrepo:log.py:432 Writing objects:  36% (4/11)\nINFO     datalad.gitrepo:log.py:432 Writing objects:  45% (5/11)\nINFO     datalad.gitrepo:log.py:432 Writing objects:  54% (6/11)\nINFO     datalad.gitrepo:log.py:432 Writing objects:  63% (7/11)\nINFO     datalad.gitrepo:log.py:432 Writing objects:  72% (8/11)\nINFO     datalad.gitrepo:log.py:432 Writing objects:  81% (9/11)\nINFO     datalad.gitrepo:log.py:432 Writing objects:  90% (10/11)\nINFO     datalad.gitrepo:log.py:432 Writing objects: 100% (11/11)\nINFO     datalad.gitrepo:log.py:432 Writing objects: 100% (11/11), 1.17 KiB | 1.17 MiB/s, done.\nINFO     datalad.gitrepo:log.py:432 Finished writing objects\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.distributed.push:log.py:432 Finished push of Dataset(/tmp/datalad_temp_test_safetynet2ucg2zj5)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n==================================== PASSES ====================================\n____________________________ test_drop_file_content ____________________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nadd(ok): subdir/annexed_file.txt (file)\nsave(ok): . (dataset)\naction summary:\n  add (ok: 1)\n  save (ok: 1)\nadd(ok): subdir/git_file.txt (file)\nsave(ok): . (dataset)\naction summary:\n  add (ok: 1)\n  save (ok: 1)\nadd(ok): subds_modified (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): subds_modified (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nadd(ok): subds_lvl1_modified (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): subds_modified (dataset)\nadd(ok): subds_modified (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): subds_modified/subds_lvl1_modified (dataset)\naction summary:\n  add (ok: 4)\n  create (ok: 1)\n  save (ok: 2)\nadd(ok): subdir/annexed_file.txt (file)\nsave(ok): . (dataset)\naction summary:\n  add (ok: 1)\n  save (ok: 1)\ndrop(error): ../datalad_temp_test_drop_file_contentgtdaaszn [path not underneath the reference dataset Dataset(/tmp/datalad_temp_test_drop_file_contentu_zqsz53)]\ndrop(error): subdir/annexed_file.txt (file) [unsafe; Could only verify the existence of 0 out of 1 necessary copies; (Use --reckless availability to override this check, or adjust numcopies.)]\ndrop(error): subds_modified/subfunky (file) [File unknown to git]\ndrop(error): funky (file) [File unknown to git]\naction summary:\n  drop (error: 2)\ndrop(ok): subds_modified/subdir/annexed_file.txt (file)\ndrop(ok): subdir/annexed_file.txt (file)\naction summary:\n  drop (ok: 2)\ndrop(impossible): subdir/annexed_file.txt (symlink) [cannot drop modified content, save first]\ndrop(impossible): subds_modified/subds_lvl1_modified/ |;&%b5{}'\"<>ΔЙקم๗あ .datc _directory_untracked (directory) [cannot drop untracked content, save first]\ndrop(impossible): subds_modified/subds_lvl1_modified/ |;&%b5{}'\"<>ΔЙקم๗あ .datc _directory_untracked (directory) [cannot drop untracked content, save first]\ndrop(impossible): subds_modified/link2superdsdir (symlink) [cannot drop untracked content, save first]\ndrop(impossible):  |;&%b5{}'\"<>ΔЙקم๗あ .datc file_modified_ (file) [cannot drop untracked content, save first]\ndrop(impossible): directory_untracked (directory) [cannot drop untracked content, save first]\ndrop(impossible): link2dir (symlink) [cannot drop untracked content, save first]\ndrop(impossible): link2subdsdir (symlink) [cannot drop untracked content, save first]\ndrop(impossible): link2subdsroot (symlink) [cannot drop untracked content, save first]\ndrop(impossible): subdir/file_modified (file) [cannot drop untracked content, save first]\ndrop(impossible): subdir/link2annex_files.txt (symlink) [cannot drop untracked content, save first]\ndrop(impossible): subdir/annexed_file.txt (symlink) [cannot drop modified content, save first]\naction summary:\n  drop (impossible: 10)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_drop_file_contentu_zqsz53\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_drop_file_contentu_zqsz53\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_drop_file_contentu_zqsz53\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_drop_file_contentu_zqsz53/subds_modified\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_drop_file_contentu_zqsz53\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_drop_file_contentu_zqsz53/subds_modified\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n___________________________ test_uninstall_recursive ___________________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nadd(ok): sub (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): sub (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nuninstall(error): . (dataset) [cannot drop dataset, subdataset(s) still present (forgot --recursive?): ['/tmp/datalad_temp_test_uninstall_recursiveljui773j/sub']]\nuninstall(ok): sub (dataset)\nuninstall(ok): . (dataset)\naction summary:\n  uninstall (ok: 2)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_uninstall_recursiveljui773j\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n__________________________________ test_kill ___________________________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nadd(ok): subdir/annexed_file.txt (file)\nsave(ok): . (dataset)\naction summary:\n  add (ok: 1)\n  save (ok: 1)\nadd(ok): subdir/git_file.txt (file)\nsave(ok): . (dataset)\naction summary:\n  add (ok: 1)\n  save (ok: 1)\nadd(ok): subds_modified (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): subds_modified (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nadd(ok): subds_lvl1_modified (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): subds_modified (dataset)\nadd(ok): subds_modified (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): subds_modified/subds_lvl1_modified (dataset)\naction summary:\n  add (ok: 4)\n  create (ok: 1)\n  save (ok: 2)\nadd(ok): subdir/annexed_file.txt (file)\nsave(ok): . (dataset)\naction summary:\n  add (ok: 1)\n  save (ok: 1)\nuninstall(ok): . (dataset)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_kill07r57o41\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_kill07r57o41\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_kill07r57o41\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_kill07r57o41/subds_modified\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_kill07r57o41\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_kill07r57o41/subds_modified\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n________________________________ test_kill_7013 ________________________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nadd(ok): subdir/subds (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): subdir/subds (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nadd(ok): subds (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): subds (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nuninstall(ok): subds (dataset)\nuninstall(ok): subdir/subds (dataset)\naction summary:\n  drop (notneeded: 1)\n  uninstall (ok: 2)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_kill_7013s47gzq5d\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_kill_7013s47gzq5d\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n___________________________ test_refuse_to_drop_cwd ____________________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\ndrop(error): .. [path not underneath the reference dataset Dataset(/tmp/datalad_temp_test_refuse_to_drop_cwddepz2tz9)]\ndrop(error): ../.. [path not underneath the reference dataset Dataset(/tmp/datalad_temp_test_refuse_to_drop_cwddepz2tz9)]\ndrop(error): .. [path not underneath the reference dataset Dataset(/tmp/datalad_temp_test_refuse_to_drop_cwddepz2tz9)]\nadd(ok): sub (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): sub (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nadd(ok): subsub (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): subsub (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_refuse_to_drop_cwddepz2tz9\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_refuse_to_drop_cwddepz2tz9/sub\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n______________________ test_careless_subdataset_uninstall ______________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nadd(ok): deep1 (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): deep1 (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nadd(ok): deep2 (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): deep2 (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nsubdataset(ok): deep1 (dataset)\nsubdataset(ok): deep2 (dataset)\nuninstall(ok): . (dataset)\nsubdataset(ok): deep1 (dataset)\nsubdataset(ok): deep2 (dataset)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_careless_subdataset_uninstallk_3p6bsy\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_careless_subdataset_uninstallk_3p6bsy\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n________________________ test_drop_nocrash_absent_subds ________________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nadd(ok): sub (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): sub (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\naction summary:\n  drop (notneeded: 2)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_drop_nocrash_absent_subdszfmyezuz\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n_________________________ test_uninstall_without_super _________________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nadd(ok): sub (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): sub (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\ncreate(ok): /tmp/datalad_temp_test_uninstall_without_superzaq_atma/nosub (dataset)\nsubdataset(ok): sub (dataset)\nuninstall(ok): sub (dataset)\nsubdataset(ok): sub (dataset)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_uninstall_without_superzaq_atma\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n______________________________ test_drop_from_git ______________________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nadd(ok): file (file)\nsave(ok): . (dataset)\naction summary:\n  add (ok: 1)\n  save (ok: 1)\naction summary:\n  drop (notneeded: 2)\n------------------------------ Captured log call -------------------------------\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_drop_from_git9neh_pui\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n__________________________ test_drop_uninit_annexrepo __________________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nuninstall(ok): . (dataset)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n____________ test_drop_allkeys_result_contains_annex_error_messages ____________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\ndrop(error): . (key) [git-annex error message here]\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n_________________________ test_nodrop_symlinked_annex __________________________\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] Attempting a clone into /tmp/datalad_temp_test_nodrop_symlinked_annex1axd2huq \n[INFO] Attempting to clone from /tmp/datalad_temp_test_nodrop_symlinked_annexk9vz3jg5 to /tmp/datalad_temp_test_nodrop_symlinked_annex1axd2huq \n[INFO] Completed clone attempts for Dataset(/tmp/datalad_temp_test_nodrop_symlinked_annex1axd2huq) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] Attempting a clone into /tmp/datalad_temp_test_nodrop_symlinked_annex1axd2huq \n[INFO] Attempting to clone from /tmp/datalad_temp_test_nodrop_symlinked_annexk9vz3jg5 to /tmp/datalad_temp_test_nodrop_symlinked_annex1axd2huq \n[INFO] Completed clone attempts for Dataset(/tmp/datalad_temp_test_nodrop_symlinked_annex1axd2huq) \n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_nodrop_symlinked_annexk9vz3jg5\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad.core.distributed.clone:log.py:432 Attempting a clone into /tmp/datalad_temp_test_nodrop_symlinked_annex1axd2huq\nINFO     datalad.core.distributed.clone:log.py:432 Attempting to clone from /tmp/datalad_temp_test_nodrop_symlinked_annexk9vz3jg5 to /tmp/datalad_temp_test_nodrop_symlinked_annex1axd2huq\nINFO     datalad.core.distributed.clone:log.py:432 Completed clone attempts for Dataset(/tmp/datalad_temp_test_nodrop_symlinked_annex1axd2huq)\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.distributed.clone:log.py:432 Attempting a clone into /tmp/datalad_temp_test_nodrop_symlinked_annex1axd2huq\nINFO     datalad.core.distributed.clone:log.py:432 Attempting to clone from /tmp/datalad_temp_test_nodrop_symlinked_annexk9vz3jg5 to /tmp/datalad_temp_test_nodrop_symlinked_annex1axd2huq\nINFO     datalad.core.distributed.clone:log.py:432 Completed clone attempts for Dataset(/tmp/datalad_temp_test_nodrop_symlinked_annex1axd2huq)\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::test_drop_file_content\nPASSED r2e_tests/test_1.py::test_uninstall_recursive\nPASSED r2e_tests/test_1.py::test_kill\nPASSED r2e_tests/test_1.py::test_kill_7013\nPASSED r2e_tests/test_1.py::test_refuse_to_drop_cwd\nPASSED r2e_tests/test_1.py::test_careless_subdataset_uninstall\nPASSED r2e_tests/test_1.py::test_drop_nocrash_absent_subds\nPASSED r2e_tests/test_1.py::test_uninstall_without_super\nPASSED r2e_tests/test_1.py::test_drop_from_git\nPASSED r2e_tests/test_1.py::test_drop_uninit_annexrepo\nPASSED r2e_tests/test_1.py::test_drop_allkeys_result_contains_annex_error_messages\nPASSED r2e_tests/test_1.py::test_nodrop_symlinked_annex\nFAILED r2e_tests/test_1.py::test_drop_allkeys - datalad.runner.exception.Comm...\nFAILED r2e_tests/test_1.py::test_undead_annex_detection - AssertionError: ass...\nFAILED r2e_tests/test_1.py::test_unpushed_state_detection - AssertionError: a...\nFAILED r2e_tests/test_1.py::test_safetynet - AssertionError: assert ['origin'...\n======================== 4 failed, 12 passed in 25.99s =========================\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_8d606e1ed9765cec669981b696049033af9b9dc9\nconfigfile: tox.ini\nplugins: fail-slow-0.6.0, xdist-3.6.1, cov-6.0.0\ncollected 16 items\n\nr2e_tests/test_1.py .FF.FF.F........                                     [100%]\n\n=================================== FAILURES ===================================\n______________________________ test_drop_allkeys _______________________________\n\norigpath = '/tmp/datalad_temp_test_drop_allkeys55s_uc90'\nclonepath = '/tmp/datalad_temp_test_drop_allkeysvxps8e46'\n\n    @with_tempfile\n    @with_tempfile\n    def test_drop_allkeys(origpath=None, clonepath=None):\n        # create a dataset with two keys, belonging to two files,\n        # in two different branches\n        ds = Dataset(origpath).create()\n        repo = ds.repo\n        repo.call_git(['checkout', '-b', 'otherbranch'])\n        (ds.pathobj / 'file1').write_text('file1')\n        ds.save()\n>       repo.call_git(['checkout', DEFAULT_BRANCH])\n\nr2e_tests/test_1.py:175: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \ndatalad/dataset/gitrepo.py:453: in call_git\n    return \"\".join(\ndatalad/dataset/gitrepo.py:517: in call_git_items_\n    for file_no, line in self._generator_call_git(\ndatalad/dataset/gitrepo.py:355: in _generator_call_git\n    for file_no, content in generator:\n../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/_collections_abc.py:330: in __next__\n    return self.send(None)\ndatalad/runner/nonasyncrunner.py:97: in send\n    return self._locked_send(message)\ndatalad/runner/nonasyncrunner.py:122: in _locked_send\n    self._check_result()\ndatalad/runner/nonasyncrunner.py:93: in _check_result\n    self.runner._check_result()\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <datalad.runner.nonasyncrunner.ThreadedRunner object at 0x718971977fa0>\n\n    def _check_result(self):\n        if self.exception_on_error is True:\n            if self.return_code not in (0, None):\n                protocol = self.protocol\n                decoded_output = {\n                    source: protocol.fd_infos[fileno][1].decode(protocol.encoding)\n                    for source, fileno in (\n                        (\"stdout\", protocol.stdout_fileno),\n                        (\"stderr\", protocol.stderr_fileno))\n                    if protocol.fd_infos[fileno][1] is not None\n                }\n>               raise CommandError(\n                    cmd=self.cmd,\n                    code=self.return_code,\n                    stdout=decoded_output.get(\"stdout\", None),\n                    stderr=decoded_output.get(\"stderr\", None)\n                )\nE               datalad.runner.exception.CommandError: CommandError: 'git -c diff.ignoreSubmodules=none checkout dl-test-branch' failed with exitcode 1 [err: 'error: pathspec 'dl-test-branch' did not match any file(s) known to git']\n\ndatalad/runner/nonasyncrunner.py:295: CommandError\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nadd(ok): file1 (file)\nsave(ok): . (dataset)\naction summary:\n  add (ok: 1)\n  save (ok: 1)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_drop_allkeys55s_uc90\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n_________________________ test_undead_annex_detection __________________________\n\ngitpath = '/tmp/datalad_temp_test_undead_annex_detectiond0ys6vq_'\norigpath = '/tmp/datalad_temp_test_undead_annex_detectionb7pktqsc'\nclonepath = '/tmp/datalad_temp_test_undead_annex_detection_q0qyhq2'\n\n    @with_tempfile\n    @with_tempfile\n    @with_tempfile\n    def test_undead_annex_detection(gitpath=None, origpath=None, clonepath=None):\n        gitds = Dataset(gitpath).create(annex=False)\n        # a gitrepo can be inspected too, it might just not know anything\n        eq_([], _detect_nondead_annex_at_remotes(gitds.repo, 'someid'))\n    \n        origds = Dataset(origpath).create()\n        origrepo = origds.repo\n        # only the local repo knows about its own annex\n        eq_([None], _detect_nondead_annex_at_remotes(origrepo, origrepo.uuid))\n    \n        # works with clones\n        cloneds = clone(origds, clonepath)\n        clonerepo = cloneds.repo\n        # the clone now know two locations, itself and origin\n>       eq_([None, DEFAULT_REMOTE],\n            _detect_nondead_annex_at_remotes(clonerepo, origrepo.uuid))\n\nr2e_tests/test_1.py:239: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nfirst = [None, 'dl-test-remote'], second = [None, 'origin'], msg = None\n\n    def assert_equal(first, second, msg=None):\n        if msg is None:\n>           assert first == second\nE           AssertionError: assert [None, 'dl-test-remote'] == [None, 'origin']\nE             \nE             At index 1 diff: 'dl-test-remote' != 'origin'\nE             Use -v to get more diff\n\ndatalad/tests/utils_pytest.py:73: AssertionError\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\ncreate(ok): . (dataset)\ninstall(ok): /tmp/datalad_temp_test_undead_annex_detection_q0qyhq2 (dataset)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] Attempting a clone into /tmp/datalad_temp_test_undead_annex_detection_q0qyhq2 \n[INFO] Attempting to clone from /tmp/datalad_temp_test_undead_annex_detectionb7pktqsc to /tmp/datalad_temp_test_undead_annex_detection_q0qyhq2 \n[INFO] Completed clone attempts for Dataset(/tmp/datalad_temp_test_undead_annex_detection_q0qyhq2) \n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.distributed.clone:log.py:432 Attempting a clone into /tmp/datalad_temp_test_undead_annex_detection_q0qyhq2\nINFO     datalad.core.distributed.clone:log.py:432 Attempting to clone from /tmp/datalad_temp_test_undead_annex_detectionb7pktqsc to /tmp/datalad_temp_test_undead_annex_detection_q0qyhq2\nINFO     datalad.core.distributed.clone:log.py:432 Completed clone attempts for Dataset(/tmp/datalad_temp_test_undead_annex_detection_q0qyhq2)\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n________________________ test_unpushed_state_detection _________________________\n\norigpath = '/tmp/datalad_temp_test_unpushed_state_detection3z27xt61'\nclonepath = '/tmp/datalad_temp_test_unpushed_state_detectionjw4fom_h'\n\n    @with_tempfile\n    @with_tempfile\n    def test_unpushed_state_detection(origpath=None, clonepath=None):\n        origds = Dataset(origpath).create()\n        # always test in annex mode\n        tester = lambda x: _detect_unpushed_revs(x, True)\n    \n        origrepo = origds.repo\n        # this is still a unique repo, all payload branches are\n        # unpushed\n>       eq_([DEFAULT_BRANCH], tester(origrepo))\n\nr2e_tests/test_1.py:309: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nfirst = ['dl-test-branch'], second = ['master'], msg = None\n\n    def assert_equal(first, second, msg=None):\n        if msg is None:\n>           assert first == second\nE           AssertionError: assert ['dl-test-branch'] == ['master']\nE             \nE             At index 0 diff: 'dl-test-branch' != 'master'\nE             Use -v to get more diff\n\ndatalad/tests/utils_pytest.py:73: AssertionError\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n________________________________ test_safetynet ________________________________\n\notherpath = '/tmp/datalad_temp_test_safetynetythoyf2h'\norigpath = '/tmp/datalad_temp_test_safetynet2fducn0j'\nclonepath = '/tmp/datalad_temp_test_safetynet1o3e_o1a'\n\n    @with_tempfile(mkdir=True)\n    @with_tempfile\n    @with_tempfile\n    def test_safetynet(otherpath=None, origpath=None, clonepath=None):\n        # we start with a dataset that is hosted somewhere\n        origds = Dataset(origpath).create()\n        # a clone is made to work on the dataset\n        cloneds = clone(origds, clonepath)\n        # checkout a different branch at origin to simplify testing below\n        origds.repo.call_git(['checkout', '-b', 'otherbranch'])\n    \n        # an untracked file is added to simulate some work\n        (cloneds.pathobj / 'file1').write_text('some text')\n        # now we try to drop the entire dataset in a variety of ways\n        # to check that it does not happen too quickly\n    \n        # cannot use invalid mode switch values\n        assert_raises(ValueError, drop, what='bananas')\n        assert_raises(ValueError, drop, reckless='rubberducky')\n    \n        # cannot simple run drop somewhere and give a path to a dataset\n        # to drop\n        with chpwd(otherpath):\n            assert_raises(NoDatasetFound, drop, clonepath, what='all')\n        ok_(cloneds.is_installed())\n    \n        # refuse to remove the CWD\n        with chpwd(clonepath):\n            assert_raises(RuntimeError, drop, what='all')\n        ok_(cloneds.is_installed())\n    \n        assert_in_results(\n            cloneds.drop(what='all', on_failure='ignore'),\n            message='cannot drop untracked content, save first',\n            status='impossible')\n        ok_(cloneds.is_installed())\n    \n        # so let's save...\n        cloneds.save()\n        # - branch is progressed\n        # - a new key is only available here\n        res = cloneds.drop(what='all', on_failure='ignore')\n        assert_in_results(res, action=\"uninstall\", status=\"error\")\n        ok_(res[0]['message'][0].startswith(\n            \"to-be-dropped dataset has revisions \"\n            \"that are not available at any known sibling\"))\n        ok_(cloneds.is_installed())\n    \n        # so let's push -- git only\n        # we cannot use git-push directly, it would not handle\n        # managed branches properly\n        cloneds.push(data='nothing')\n    \n        res = cloneds.drop(what='all', on_failure='ignore')\n        assert_in_results(res, action=\"uninstall\", status=\"error\")\n        ok_(res[0]['message'][0].startswith(\n            \"to-be-deleted local annex not declared 'dead'\"))\n        # some windows test setup is not very robust, explicitly\n        # include the default name \"origin\" in the test success\n        # conditions to make this more robust\n>       eq_(res[0]['message'][1], [DEFAULT_REMOTE])\n\nr2e_tests/test_1.py:406: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nfirst = ['origin'], second = ['dl-test-remote'], msg = None\n\n    def assert_equal(first, second, msg=None):\n        if msg is None:\n>           assert first == second\nE           AssertionError: assert ['origin'] == ['dl-test-remote']\nE             \nE             At index 0 diff: 'origin' != 'dl-test-remote'\nE             Use -v to get more diff\n\ndatalad/tests/utils_pytest.py:73: AssertionError\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\ninstall(ok): /tmp/datalad_temp_test_safetynet1o3e_o1a (dataset)\ndrop(impossible): file1 (file) [cannot drop untracked content, save first]\nadd(ok): file1 (file)\nsave(ok): . (dataset)\naction summary:\n  add (ok: 1)\n  save (ok: 1)\nuninstall(error): . (dataset) [to-be-dropped dataset has revisions that are not available at any known sibling. Use `datalad push --to ...` to push these before dropping the local dataset, or ignore via `--reckless availability`. Unique revisions: ['master']]\npublish(ok): . (dataset) [refs/heads/git-annex->origin:refs/heads/git-annex 8eab0e3..d442247]\npublish(ok): . (dataset) [refs/heads/master->origin:refs/heads/master 21a3ab3..deafce9]\naction summary:\n  publish (ok: 2)\nuninstall(error): . (dataset) [to-be-deleted local annex not declared 'dead' at the following siblings. Announce death (`git annex dead here` + `datalad push --to ...`), or ignore via `--reckless undead`: ['origin']]\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] Attempting a clone into /tmp/datalad_temp_test_safetynet1o3e_o1a \n[INFO] Attempting to clone from /tmp/datalad_temp_test_safetynet2fducn0j to /tmp/datalad_temp_test_safetynet1o3e_o1a \n[INFO] Completed clone attempts for Dataset(/tmp/datalad_temp_test_safetynet1o3e_o1a) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] Determine push target \n[INFO] Push refspecs \n[INFO] Update availability information \n[INFO] Start enumerating objects \n[INFO] Start counting objects \n[INFO] Start compressing objects \n[INFO] Start writing objects \n[INFO] Finished push of Dataset(/tmp/datalad_temp_test_safetynet1o3e_o1a) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.distributed.clone:log.py:432 Attempting a clone into /tmp/datalad_temp_test_safetynet1o3e_o1a\nINFO     datalad.core.distributed.clone:log.py:432 Attempting to clone from /tmp/datalad_temp_test_safetynet2fducn0j to /tmp/datalad_temp_test_safetynet1o3e_o1a\nINFO     datalad.core.distributed.clone:log.py:432 Completed clone attempts for Dataset(/tmp/datalad_temp_test_safetynet1o3e_o1a)\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_safetynet1o3e_o1a\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.distributed.push:log.py:432 Determine push target\nINFO     datalad.core.distributed.push:log.py:432 Push refspecs\nINFO     datalad.core.distributed.push:log.py:432 Update availability information\nINFO     datalad.gitrepo:log.py:432 Start enumerating objects\nINFO     datalad.gitrepo:log.py:432 Finished enumerating objects\nINFO     datalad.gitrepo:log.py:432 Start counting objects\nINFO     datalad.gitrepo:log.py:432 Counting objects:   5% (1/17)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  11% (2/17)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  17% (3/17)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  23% (4/17)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  29% (5/17)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  35% (6/17)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  41% (7/17)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  47% (8/17)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  52% (9/17)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  58% (10/17)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  64% (11/17)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  70% (12/17)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  76% (13/17)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  82% (14/17)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  88% (15/17)\nINFO     datalad.gitrepo:log.py:432 Counting objects:  94% (16/17)\nINFO     datalad.gitrepo:log.py:432 Counting objects: 100% (17/17)\nINFO     datalad.gitrepo:log.py:432 Counting objects: 100% (17/17), done.\nINFO     datalad.gitrepo:log.py:432 Finished counting objects\nINFO     datalad.gitrepo:log.py:432 Start compressing objects\nINFO     datalad.gitrepo:log.py:432 Compressing objects:   9% (1/11)\nINFO     datalad.gitrepo:log.py:432 Compressing objects:  18% (2/11)\nINFO     datalad.gitrepo:log.py:432 Compressing objects:  27% (3/11)\nINFO     datalad.gitrepo:log.py:432 Compressing objects:  36% (4/11)\nINFO     datalad.gitrepo:log.py:432 Compressing objects:  45% (5/11)\nINFO     datalad.gitrepo:log.py:432 Compressing objects:  54% (6/11)\nINFO     datalad.gitrepo:log.py:432 Compressing objects:  63% (7/11)\nINFO     datalad.gitrepo:log.py:432 Compressing objects:  72% (8/11)\nINFO     datalad.gitrepo:log.py:432 Compressing objects:  81% (9/11)\nINFO     datalad.gitrepo:log.py:432 Compressing objects:  90% (10/11)\nINFO     datalad.gitrepo:log.py:432 Compressing objects: 100% (11/11)\nINFO     datalad.gitrepo:log.py:432 Compressing objects: 100% (11/11), done.\nINFO     datalad.gitrepo:log.py:432 Finished compressing objects\nINFO     datalad.gitrepo:log.py:432 Start writing objects\nINFO     datalad.gitrepo:log.py:432 Writing objects:   7% (1/14)\nINFO     datalad.gitrepo:log.py:432 Writing objects:  14% (2/14)\nINFO     datalad.gitrepo:log.py:432 Writing objects:  21% (3/14)\nINFO     datalad.gitrepo:log.py:432 Writing objects:  28% (4/14)\nINFO     datalad.gitrepo:log.py:432 Writing objects:  35% (5/14)\nINFO     datalad.gitrepo:log.py:432 Writing objects:  42% (6/14)\nINFO     datalad.gitrepo:log.py:432 Writing objects:  50% (7/14)\nINFO     datalad.gitrepo:log.py:432 Writing objects:  57% (8/14)\nINFO     datalad.gitrepo:log.py:432 Writing objects:  64% (9/14)\nINFO     datalad.gitrepo:log.py:432 Writing objects:  71% (10/14)\nINFO     datalad.gitrepo:log.py:432 Writing objects:  78% (11/14)\nINFO     datalad.gitrepo:log.py:432 Writing objects:  85% (12/14)\nINFO     datalad.gitrepo:log.py:432 Writing objects:  92% (13/14)\nINFO     datalad.gitrepo:log.py:432 Writing objects: 100% (14/14)\nINFO     datalad.gitrepo:log.py:432 Writing objects: 100% (14/14), 1.45 KiB | 1.46 MiB/s, done.\nINFO     datalad.gitrepo:log.py:432 Finished writing objects\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.distributed.push:log.py:432 Finished push of Dataset(/tmp/datalad_temp_test_safetynet1o3e_o1a)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n________________________________ test_kill_7013 ________________________________\n\npath = '/tmp/datalad_temp_test_kill_70138j686od0'\n\n    @with_tempfile\n    def test_kill_7013(path=None):\n        \"\"\"check that a recursive kill does not silently skip subdatasets\n         contained in subdirectory: github.com/datalad/datalad/issues/7013\"\"\"\n        ds = Dataset(path).create()\n        (ds.pathobj / 'subdir').mkdir()\n        ds.create('subdir/subds')\n        ds.create('subds')\n        res = ds.drop(path=['subds', 'subdir'], what='all',\n                      reckless='kill', recursive=True)\n        # ensure that both subdatasets were killed\n        subs = ds.subdatasets(state='present')\n>       assert len(subs) == 0\nE       AssertionError: assert 1 == 0\nE        +  where 1 = len([{'action': 'subdataset', 'gitmodule_datalad-id': 'cce2216a-f26f-43e6-91a2-7cb64328fe89', 'gitmodule_name': 'subdir/subds', 'gitmodule_url': './subdir/subds', ...}])\n\nr2e_tests/test_1.py:475: AssertionError\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nadd(ok): subdir/subds (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): subdir/subds (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nadd(ok): subds (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): subds (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nuninstall(ok): subds (dataset)\naction summary:\n  drop (notneeded: 1)\n  uninstall (ok: 1)\nsubdataset(ok): subdir/subds (dataset)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_kill_70138j686od0\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_kill_70138j686od0\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n==================================== PASSES ====================================\n____________________________ test_drop_file_content ____________________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nadd(ok): subdir/annexed_file.txt (file)\nsave(ok): . (dataset)\naction summary:\n  add (ok: 1)\n  save (ok: 1)\nadd(ok): subdir/git_file.txt (file)\nsave(ok): . (dataset)\naction summary:\n  add (ok: 1)\n  save (ok: 1)\nadd(ok): subds_modified (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): subds_modified (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nadd(ok): subds_lvl1_modified (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): subds_modified (dataset)\nadd(ok): subds_modified (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): subds_modified/subds_lvl1_modified (dataset)\naction summary:\n  add (ok: 4)\n  create (ok: 1)\n  save (ok: 2)\nadd(ok): subdir/annexed_file.txt (file)\nsave(ok): . (dataset)\naction summary:\n  add (ok: 1)\n  save (ok: 1)\ndrop(error): ../datalad_temp_test_drop_file_contentuq1exg5q [path not underneath the reference dataset Dataset(/tmp/datalad_temp_test_drop_file_content5y9i2brd)]\ndrop(error): subdir/annexed_file.txt (file) [unsafe; Could only verify the existence of 0 out of 1 necessary copies; (Use --reckless availability to override this check, or adjust numcopies.)]\ndrop(error): subds_modified/subfunky (file) [File unknown to git]\ndrop(error): funky (file) [File unknown to git]\naction summary:\n  drop (error: 2)\ndrop(ok): subds_modified/subdir/annexed_file.txt (file)\ndrop(ok): subdir/annexed_file.txt (file)\naction summary:\n  drop (ok: 2)\ndrop(impossible): subdir/annexed_file.txt (symlink) [cannot drop modified content, save first]\ndrop(impossible): subds_modified/subds_lvl1_modified/ |;&%b5{}'\"<>ΔЙקم๗あ .datc _directory_untracked (directory) [cannot drop untracked content, save first]\ndrop(impossible): subds_modified/subds_lvl1_modified/ |;&%b5{}'\"<>ΔЙקم๗あ .datc _directory_untracked (directory) [cannot drop untracked content, save first]\ndrop(impossible): subds_modified/link2superdsdir (symlink) [cannot drop untracked content, save first]\ndrop(impossible):  |;&%b5{}'\"<>ΔЙקم๗あ .datc file_modified_ (file) [cannot drop untracked content, save first]\ndrop(impossible): directory_untracked (directory) [cannot drop untracked content, save first]\ndrop(impossible): link2dir (symlink) [cannot drop untracked content, save first]\ndrop(impossible): link2subdsdir (symlink) [cannot drop untracked content, save first]\ndrop(impossible): link2subdsroot (symlink) [cannot drop untracked content, save first]\ndrop(impossible): subdir/file_modified (file) [cannot drop untracked content, save first]\ndrop(impossible): subdir/link2annex_files.txt (symlink) [cannot drop untracked content, save first]\ndrop(impossible): subdir/annexed_file.txt (symlink) [cannot drop modified content, save first]\naction summary:\n  drop (impossible: 10)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_drop_file_content5y9i2brd\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_drop_file_content5y9i2brd\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_drop_file_content5y9i2brd\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_drop_file_content5y9i2brd/subds_modified\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_drop_file_content5y9i2brd\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_drop_file_content5y9i2brd/subds_modified\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n___________________________ test_uninstall_recursive ___________________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nadd(ok): sub (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): sub (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nuninstall(error): . (dataset) [cannot drop dataset, subdataset(s) still present (forgot --recursive?): ['/tmp/datalad_temp_test_uninstall_recursivefllvmrfy/sub']]\nuninstall(ok): sub (dataset)\nuninstall(ok): . (dataset)\naction summary:\n  uninstall (ok: 2)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_uninstall_recursivefllvmrfy\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n__________________________________ test_kill ___________________________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nadd(ok): subdir/annexed_file.txt (file)\nsave(ok): . (dataset)\naction summary:\n  add (ok: 1)\n  save (ok: 1)\nadd(ok): subdir/git_file.txt (file)\nsave(ok): . (dataset)\naction summary:\n  add (ok: 1)\n  save (ok: 1)\nadd(ok): subds_modified (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): subds_modified (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nadd(ok): subds_lvl1_modified (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): subds_modified (dataset)\nadd(ok): subds_modified (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): subds_modified/subds_lvl1_modified (dataset)\naction summary:\n  add (ok: 4)\n  create (ok: 1)\n  save (ok: 2)\nadd(ok): subdir/annexed_file.txt (file)\nsave(ok): . (dataset)\naction summary:\n  add (ok: 1)\n  save (ok: 1)\nuninstall(ok): . (dataset)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_killoxy9h1um\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_killoxy9h1um\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_killoxy9h1um\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_killoxy9h1um/subds_modified\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_killoxy9h1um\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_killoxy9h1um/subds_modified\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n___________________________ test_refuse_to_drop_cwd ____________________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\ndrop(error): .. [path not underneath the reference dataset Dataset(/tmp/datalad_temp_test_refuse_to_drop_cwd5ueuicbh)]\ndrop(error): ../.. [path not underneath the reference dataset Dataset(/tmp/datalad_temp_test_refuse_to_drop_cwd5ueuicbh)]\ndrop(error): .. [path not underneath the reference dataset Dataset(/tmp/datalad_temp_test_refuse_to_drop_cwd5ueuicbh)]\nadd(ok): sub (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): sub (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nadd(ok): subsub (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): subsub (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_refuse_to_drop_cwd5ueuicbh\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_refuse_to_drop_cwd5ueuicbh/sub\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n______________________ test_careless_subdataset_uninstall ______________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nadd(ok): deep1 (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): deep1 (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nadd(ok): deep2 (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): deep2 (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nsubdataset(ok): deep1 (dataset)\nsubdataset(ok): deep2 (dataset)\nuninstall(ok): . (dataset)\nsubdataset(ok): deep1 (dataset)\nsubdataset(ok): deep2 (dataset)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_careless_subdataset_uninstally91fd9dz\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_careless_subdataset_uninstally91fd9dz\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n________________________ test_drop_nocrash_absent_subds ________________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nadd(ok): sub (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): sub (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\naction summary:\n  drop (notneeded: 2)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_drop_nocrash_absent_subdstmmymlkg\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n_________________________ test_uninstall_without_super _________________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nadd(ok): sub (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): sub (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\ncreate(ok): /tmp/datalad_temp_test_uninstall_without_supermbq8lkli/nosub (dataset)\nsubdataset(ok): sub (dataset)\nuninstall(ok): sub (dataset)\nsubdataset(ok): sub (dataset)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_uninstall_without_supermbq8lkli\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n______________________________ test_drop_from_git ______________________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nadd(ok): file (file)\nsave(ok): . (dataset)\naction summary:\n  add (ok: 1)\n  save (ok: 1)\naction summary:\n  drop (notneeded: 2)\n------------------------------ Captured log call -------------------------------\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_drop_from_gitxj36gonf\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n__________________________ test_drop_uninit_annexrepo __________________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nuninstall(ok): . (dataset)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n____________ test_drop_allkeys_result_contains_annex_error_messages ____________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\ndrop(error): . (key) [git-annex error message here]\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n_________________________ test_nodrop_symlinked_annex __________________________\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] Attempting a clone into /tmp/datalad_temp_test_nodrop_symlinked_annexyee5hqa1 \n[INFO] Attempting to clone from /tmp/datalad_temp_test_nodrop_symlinked_annexp39zm2vs to /tmp/datalad_temp_test_nodrop_symlinked_annexyee5hqa1 \n[INFO] Completed clone attempts for Dataset(/tmp/datalad_temp_test_nodrop_symlinked_annexyee5hqa1) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] Attempting a clone into /tmp/datalad_temp_test_nodrop_symlinked_annexyee5hqa1 \n[INFO] Attempting to clone from /tmp/datalad_temp_test_nodrop_symlinked_annexp39zm2vs to /tmp/datalad_temp_test_nodrop_symlinked_annexyee5hqa1 \n[INFO] Completed clone attempts for Dataset(/tmp/datalad_temp_test_nodrop_symlinked_annexyee5hqa1) \n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_nodrop_symlinked_annexp39zm2vs\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad.core.distributed.clone:log.py:432 Attempting a clone into /tmp/datalad_temp_test_nodrop_symlinked_annexyee5hqa1\nINFO     datalad.core.distributed.clone:log.py:432 Attempting to clone from /tmp/datalad_temp_test_nodrop_symlinked_annexp39zm2vs to /tmp/datalad_temp_test_nodrop_symlinked_annexyee5hqa1\nINFO     datalad.core.distributed.clone:log.py:432 Completed clone attempts for Dataset(/tmp/datalad_temp_test_nodrop_symlinked_annexyee5hqa1)\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\nINFO     datalad.core.distributed.clone:log.py:432 Attempting a clone into /tmp/datalad_temp_test_nodrop_symlinked_annexyee5hqa1\nINFO     datalad.core.distributed.clone:log.py:432 Attempting to clone from /tmp/datalad_temp_test_nodrop_symlinked_annexp39zm2vs to /tmp/datalad_temp_test_nodrop_symlinked_annexyee5hqa1\nINFO     datalad.core.distributed.clone:log.py:432 Completed clone attempts for Dataset(/tmp/datalad_temp_test_nodrop_symlinked_annexyee5hqa1)\nINFO     datalad.annex:annexrepo.py:3906 scanning for unlocked files (this may take some time)\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::test_drop_file_content\nPASSED r2e_tests/test_1.py::test_uninstall_recursive\nPASSED r2e_tests/test_1.py::test_kill\nPASSED r2e_tests/test_1.py::test_refuse_to_drop_cwd\nPASSED r2e_tests/test_1.py::test_careless_subdataset_uninstall\nPASSED r2e_tests/test_1.py::test_drop_nocrash_absent_subds\nPASSED r2e_tests/test_1.py::test_uninstall_without_super\nPASSED r2e_tests/test_1.py::test_drop_from_git\nPASSED r2e_tests/test_1.py::test_drop_uninit_annexrepo\nPASSED r2e_tests/test_1.py::test_drop_allkeys_result_contains_annex_error_messages\nPASSED r2e_tests/test_1.py::test_nodrop_symlinked_annex\nFAILED r2e_tests/test_1.py::test_drop_allkeys - datalad.runner.exception.Comm...\nFAILED r2e_tests/test_1.py::test_undead_annex_detection - AssertionError: ass...\nFAILED r2e_tests/test_1.py::test_unpushed_state_detection - AssertionError: a...\nFAILED r2e_tests/test_1.py::test_safetynet - AssertionError: assert ['origin'...\nFAILED r2e_tests/test_1.py::test_kill_7013 - AssertionError: assert 1 == 0\n======================== 5 failed, 11 passed in 20.74s =========================\n",
    "old_commit_res_stderr": ""
}
datalad/distributed/drop.py datalad/distributed/tests/test_drop.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** Recursive Drop with `--reckless kill` Skips Subdatasets Under Specified Paths

**Description:**
When using the `drop` command with the `--recursive`, `--what all`, and `--reckless kill` options along with specified directory paths, subdatasets within those directories are not being removed as expected. This behavior leaves subdatasets lingering, which can lead to inconsistencies in the dataset state.

**Example Code:**
```python
from datalad import Dataset

def drop_subdatasets():
    ds = Dataset('/path/to/dataset').create()
    ds.create('subdir/subds')
    ds.create('subds')
    ds.drop(path=['subds', 'subdir'], what='all', reckless='kill', recursive=True)
    subs = ds.subdatasets(state='present')
    print(f"Remaining subdatasets: {len(subs)}")
```
**Expected Behavior:**
All specified paths and their contained subdatasets should be completely removed when performing a recursive drop with `--reckless kill`. After the drop operation, there should be no remaining subdatasets under the provided paths.

**Actual Behavior:**
Subdatasets within the specified directories are not being dropped. In the example above, after executing the drop command, one subdataset remains, resulting in `Remaining subdatasets: 1` instead of the expected `Remaining subdatasets: 0`.

[/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/distributed/drop.py
# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-
# ex: set sts=4 ts=4 sw=4 et:
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
#   See COPYING file distributed along with the datalad package for the
#   copyright and license terms.
#
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""High-level interface for dropping dataset content

"""

__docformat__ = 'restructuredtext'

import logging
from itertools import chain
import warnings

from datalad.core.local.status import get_paths_by_ds
from datalad.distribution.dataset import (
    Dataset,
    EnsureDataset,
    datasetmethod,
    require_dataset,
)
from datalad.interface.base import (
    Interface,
    build_doc,
    eval_results,
)
from datalad.interface.common_opts import (
    jobs_opt,
    recursion_flag,
    recursion_limit,
)
from datalad.interface.results import (
    annexjson2result,
    results_from_annex_noinfo,
    success_status_map,
)
from datalad.runner.exception import CommandError
from datalad.support.annexrepo import AnnexRepo
from datalad.support.constraints import (
    EnsureChoice,
    EnsureNone,
    EnsureStr,
)
from datalad.support.exceptions import CapturedException
from datalad.support.param import Parameter
from datalad.utils import (
    ensure_list,
    rmtree,
)

lgr = logging.getLogger('datalad.distributed.drop')


@build_doc
class Drop(Interface):
    """Drop content of individual files or entire (sub)datasets

    This command is the antagonist of 'get'. It can undo the retrieval of file
    content, and the installation of subdatasets.

    Dropping is a safe-by-default operation. Before dropping any information,
    the command confirms the continued availability of file-content (see e.g.,
    configuration 'annex.numcopies'), and the state of all dataset branches
    from at least one known dataset sibling. Moreover, prior removal of an
    entire dataset annex, that it is confirmed that it is no longer marked
    as existing in the network of dataset siblings.

    Importantly, all checks regarding version history availability and local
    annex availability are performed using the current state of remote
    siblings as known to the local dataset. This is done for performance
    reasons and for resilience in case of absent network connectivity. To
    ensure decision making based on up-to-date information, it is advised to
    execute a dataset update before dropping dataset components.
    """
    _examples_ = [
        {'text': "Drop single file content",
         'code_py': "drop('path/to/file')",
         'code_cmd': "datalad drop <path/to/file>"},
        {'text': "Drop all file content in the current dataset",
         'code_py': "drop('.')",
         'code_cmd': "datalad drop"},
        {'text': "Drop all file content in a dataset and all its subdatasets",
         'code_py': "drop(dataset='.', recursive=True)",
         'code_cmd': "datalad drop -d <path/to/dataset> -r"},
        {'text': "Disable check to ensure the configured minimum number of "
                 "remote sources for dropped data",
         'code_py': "drop(path='path/to/content', reckless='availability')",
         'code_cmd': "datalad drop <path/to/content> --reckless availability"},
        {'text': 'Drop (uninstall) an entire dataset '
                 '(will fail with subdatasets present)',
         'code_py': "drop(what='all')",
         'code_cmd': "datalad drop --what all"},
        {'text': 'Kill a dataset recklessly with any existing subdatasets too'
                 '(this will be fast, but will disable any and all safety '
                 'checks)',
         'code_py': "drop(what='all', reckless='kill', recursive=True)",
         'code_cmd': "datalad drop --what all, --reckless kill --recursive"},
    ]

    _params_ = dict(
        dataset=Parameter(
            args=("-d", "--dataset"),
            metavar="DATASET",
            doc="""specify the dataset to perform drop from.
            If no dataset is given, the current working directory is used
            as operation context""",
            constraints=EnsureDataset() | EnsureNone()),
        path=Parameter(
            args=("path",),
            metavar="PATH",
            doc="path of a dataset or dataset component to be dropped",
            nargs="*",
            constraints=EnsureStr() | EnsureNone()),
        reckless=Parameter(
            args=("--reckless",),
            doc="""disable individual or all data safety measures that would
            normally prevent potentially irreversible data-loss.
            With 'modification', unsaved modifications in a dataset will not be
            detected. This improves performance at the cost of permitting
            potential loss of unsaved or untracked dataset components.
            With 'availability', detection of dataset/branch-states that are
            only available in the local dataset, and detection of an
            insufficient number of file-content copies will be disabled.
            Especially the latter is a potentially expensive check which might
            involve numerous network transactions.
            With 'undead', detection of whether a to-be-removed local annex is
            still known to exist in the network of dataset-clones is disabled.
            This could cause zombie-records of invalid file availability.
            With 'kill', all safety-checks are disabled.""",
            constraints=EnsureChoice(
                'modification', 'availability', 'undead', 'kill', None)),
        what=Parameter(
            args=("--what",),
            doc="""select what type of items shall be dropped.
            With 'filecontent', only the file content (git-annex keys) of files
            in a dataset's worktree will be dropped.
            With 'allkeys', content of any version of any file in any branch
            (including, but not limited to the worktree) will be dropped. This
            effectively empties the annex of a local dataset.
            With 'datasets', only complete datasets will be dropped (implies
            'allkeys' mode for each such dataset), but no filecontent will be
            dropped for any files in datasets that are not dropped entirely.
            With 'all', content for any matching file or dataset will be dropped
            entirely.
            """,
            # TODO add 'unwanted'
            constraints=EnsureChoice('filecontent', 'allkeys', 'datasets', 'all')),
        recursive=recursion_flag,
        recursion_limit=recursion_limit,
        jobs=jobs_opt,
        check=Parameter(
            args=("--nocheck",),
            doc="""DEPRECATED: use '--reckless availability'""",
            action="store_false",
            dest='check'),
        if_dirty=Parameter(
            args=("--if-dirty",),
            doc="""DEPRECATED and IGNORED: use --reckless instead""",),
    )

    @staticmethod
    @datasetmethod(name='drop')
    @eval_results
    def __call__(
            path=None,
            *,
            what='filecontent',
            reckless=None,
            dataset=None,
            recursive=False,
            recursion_limit=None,
            jobs=None,
            # deprecated
            check=None,
            if_dirty=None):

        # TODO if reckless is None, initialize from a potential config setting
        # left behind by a reckless clone

        # proper spelling of mode switches is critical for implementation
        # below. double-check, also in Python API usage
        # TODO consider making a generic helper
        for label, value in (('what', what), ('reckless', reckless)):
            try:
                Drop._params_[label].constraints(value)
            except ValueError as e:
                raise ValueError(
                    f"Invalid '{label}' parameter value of: "
                    f"{repr(value)} [{str(e)}]") from e

        if check is not None:
            warnings.warn(
                "The `check` argument of `datalad drop` is deprecated, "
                "use the `reckless` argument instead.",
                DeprecationWarning)
        if if_dirty is not None:
            warnings.warn(
                "The `if_dirty` argument of `datalad drop` is ignored, "
                "it can be removed for a safe-by-default behavior. For "
                "other cases consider the `reckless` argument.",
                DeprecationWarning)

        if check is False:
            if reckless is not None:
                raise ValueError(
                    'Must not use deprecated `check` argument, and new '
                    '`reckless` argument together with `datalad drop`.')
            reckless = 'availability'

        if what in ('all', 'datasets') and reckless == 'kill' and not recursive:
            raise ValueError(
                'A reckless kill is requested but no recursion flag is set. '
                "With 'kill' no checks for subdatasets will be made, "
                'acknowledge by setting the recursive flag')

        # we cannot test for what=='allkeys' and path==None here,
        # on per each dataset. otherwise we will not be able to drop
        # from a subdataset, by given its path -- desirable MIH thinks

        ds = require_dataset(dataset, check_installed=True, purpose='drop')

        res_props = dict(
            logger=lgr,
            refds=ds.path,
        )
        # if not paths are given, there will still be a single dataset record
        # with paths==None
        paths_by_ds, errors = get_paths_by_ds(
            ds,
            dataset,
            ensure_list(path),
            # XXX this needs more thought!! Maybe this is what the mode should be
            # in general?!
            # when we want to drop entire datasets, it is much more useful
            # to have subdatasets be their own record
            subdsroot_mode='sub'
            if what in ('all', 'datasets')
            else 'rsync',
        )
        for e in errors:
            yield dict(
                action='drop',
                path=str(e),
                status='error',
                message=('path not underneath the reference dataset %s', ds),
                **res_props)
            # we are not returning, a caller could decide on failure mode

        if what in ('all', 'datasets') and _paths_atunder_dir(
                paths_by_ds, ds.pathobj.cwd()):
            raise RuntimeError(
                'refuse to perform actions that would remove the current '
                'working directory')

        lgr.debug('Discovered %i datasets to drop (from)', len(paths_by_ds))

        # a dataset paths are normalized and guaranteed to be under the same
        # root dataset
        # we want to start from the leave datasets, such that all discovered
        # dataset can be processed independently
        for dpath in sorted(paths_by_ds.keys(), reverse=True):
            d = Dataset(dpath)
            lgr.debug('Starting to drop %s at %s', what, d)
            for res in _drop_dataset(
                    d,
                    paths_by_ds[dpath],
                    what=what,
                    reckless=reckless,
                    # recursion from any of the given paths!
                    recursive=recursive,
                    recursion_limit=recursion_limit,
                    jobs=jobs):
                yield dict(res, **res_props)
            lgr.debug('Finished dropping %s at %s', what, d)
        return


def _paths_atunder_dir(pbd, dirpath):
    """Whether any of the paths is at or under a reference path

    Parameters
    ----------
    pbd: dict
      Dataset path dict is produced by get_paths_by_ds()
    dirpath: Path
      Reference path

    Returns
    -------
    bool
    """
    for dpath, paths in pbd.items():
        for p in ([dpath] if paths is None else paths):
            if p == dirpath or p in dirpath.parents:
                return True
    return False


def _drop_dataset(ds, paths, what, reckless, recursive, recursion_limit, jobs):
    lgr.debug('Start dropping for %s', ds)
    # we know that any given path is part of `ds` and not any of its
    # subdatasets!

    # by-passing this completely with reckless=kill
    if recursive and not reckless == 'kill':
        # process subdatasets first with recursion
        for sub in ds.subdatasets(
                # must be resolved!
                path=paths or None,
                # nothing to drop with unavailable subdatasets
                state='present',
                # we can use the full recursion depth, only the first layer
                # of calls to _drop_dataset() must/can have recursive=True
                recursive=recursive,
                recursion_limit=recursion_limit,
                # start reporting with the leaves
                bottomup=True,
                result_xfm='datasets',
                on_failure='ignore',
                return_type='generator',
                result_renderer='disabled'):
            yield from _drop_dataset(
                ds=sub,
                # everything, the entire subdataset is matching a given path
                paths=None,
                what=what,
                reckless=reckless,
                recursive=False,
                recursion_limit=None,
                jobs=jobs)

    if not ds.pathobj.exists():
        # basic protection against something having wiped it out already.
        # should not happen, but better than a crash, if it does
        yield dict(
            action='drop',
            path=ds.path,
            status='notneeded',
            message="does not exist",
            type='dataset',
        )
        return

    if paths is not None and paths != [ds.pathobj] and what == 'all':
        # so we have paths constraints that prevent dropping the full dataset
        lgr.debug('Only dropping file content for given paths in %s, '
                  'allthough instruction was to drop %s', ds, what)
        what = 'filecontent'

    repo = ds.repo
    is_annex = isinstance(repo, AnnexRepo)

    # first critical checks that might prevent further operation
    had_fatality = False
    for res in _fatal_pre_drop_checks(
            ds, repo, paths, what, reckless, is_annex):
        had_fatality = True
        yield res
    if had_fatality:
        return

    # next check must come AFTER the modification checks above, otherwise
    # remove() could not rely on the modification detection above
    if paths is not None and paths != [ds.pathobj] and what == 'datasets':
        # so we have paths constraints that prevent dropping the full dataset
        # there is nothing to do here, but to drop keys, which we must not
        # done
        return

    # now conditional/informative checks
    yield from _pre_drop_checks(ds, repo, paths, what, reckless, is_annex)

    if is_annex and what == 'filecontent':
        yield from _drop_files(
            ds,
            repo,
            # give paths or '.' with no constraint
            paths=[str(p.relative_to(ds.pathobj))
                   for p in paths] if paths else '.',
            force=reckless in ('availability', 'kill'),
            jobs=jobs,
        )
        # end it here for safety, the rest of the function deals with
        # dropping more and more fundamentally
        return

    drop_all_errored = False
    if is_annex and what in ('allkeys', 'datasets', 'all') \
            and not reckless == 'kill':
        for r in _drop_allkeys(
                ds,
                repo,
                force=reckless in ('availability',),
                jobs=jobs):
            res = dict(
                action='drop',
                type='key',
                # use the path of the containing dataset
                # using the location of the key does not add any
                # practical value, and is expensive to obtain
                path=ds.path,
                status='ok' if r.get('success') else 'error',
                key=r.get('key'),
            )
            # pull any note, and rename recommended parameter to
            # avoid confusion
            message = r.get('note', '').replace(
                '--force',
                '--reckless availability')
            if message:
                res['message'] = message
            error_messages = r.get('error-messages')
            if error_messages:
                res['error_message'] = '\n'.join(
                    m.strip() for m in error_messages
                )
            # play safe, if there is no status, assume error
            if res.get('status', 'error') != 'ok':
                drop_all_errored = True
            yield res

    if drop_all_errored:
        # end it here, if there is any indication that wiping out the
        # repo is unsafe
        return

    if what in ('all', 'datasets'):
        yield from _kill_dataset(ds)
    lgr.debug('Done dropping for %s', ds)
    return


def _fatal_pre_drop_checks(ds, repo, paths, what, reckless, is_annex):
    if what == 'allkeys' and paths is not None \
            and paths != [ds.pathobj]:
        yield dict(
            action='drop',
            path=ds.path,
            type='dataset',
            status='impossible',
            message=(
                'cannot drop %s, with path constraints given: %s',
                what, paths),
        )
        return

    if what in ('all', 'datasets') and not reckless == 'kill':
        # we must not have subdatasets anymore
        # if we do, --recursive was forgotton
        subdatasets = ds.subdatasets(
            path=paths,
            # we only care about the present ones
            state='present',
            # first-level is enough, if that has none, there will be none
            recursive=False,
            result_xfm='paths',
            result_renderer='disabled')
        if subdatasets:
            yield dict(
                action='uninstall',
                path=ds.path,
                type='dataset',
                status='error',
                message=('cannot drop dataset, subdataset(s) still present '
                         '(forgot --recursive?): %s', subdatasets)
            )
            # this is fatal
            return

    if what in ('all', 'datasets') \
            and reckless not in ('availability', 'kill') \
            and (paths is None or paths == [ds.pathobj]):
        unpushed = _detect_unpushed_revs(repo, is_annex)
        if unpushed:
            yield dict(
                action='uninstall',
                path=ds.path,
                type='dataset',
                status='error',
                message=(
                    "to-be-dropped dataset has revisions that "
                    "are not available at any known sibling. Use "
                    "`datalad push --to ...` to push "
                    "these before dropping the local dataset, "
                    "or ignore via `--reckless availability`. "
                    "Unique revisions: %s",
                    unpushed)
            )
            # this is fatal
            return

    if is_annex and what in ('all', 'datasets') \
            and reckless not in ('undead', 'kill'):
        # this annex is about to die, test if it is still considered
        # not-dead. if so, complain to avoid generation of zombies
        # (annexed that are floating around, but are actually dead).
        # if repo.uuid is None, git annex init never ran, and we can skip this
        remotes_that_know_this_annex = None if repo.uuid is None else [
            r
            for r in _detect_nondead_annex_at_remotes(repo, repo.uuid)
            # filter out "here"
            if r is not None
        ]
        if remotes_that_know_this_annex:
            yield dict(
                action='uninstall',
                path=ds.path,
                type='dataset',
                status='error',
                message=(
                    "to-be-deleted local annex not declared 'dead' at the "
                    "following siblings. Announce death "
                    "(`git annex dead here` + `datalad push --to ...`), "
                    "or ignore via `--reckless undead`: %s",
                    remotes_that_know_this_annex)
            )
            # this is fatal
            return

    if reckless not in ('modification', 'kill'):
        # do a cheaper status run to discover any kind of modification and
        # generate results based on the `what` mode of operation
        for res in ds.status(
                path=paths,
                # untracked content will not be reported on further down
                # must catch it here
                untracked='normal',
                # downstream code can handle non-annex/annex distinction
                # avoid expensive evaluation here
                annex=False,
                # we only need to inspect a subdataset's state in case
                # we want to drop it completely
                eval_subdataset_state='commit'
                if what in ('all', 'datasets') else 'no',
                # recursion is handled outside this function
                recursive=False,
                result_renderer='disabled',
                return_type='generator',
                on_failure='ignore'):
            state = res.get('state')
            if state == 'clean':
                # nothing to worry about, nothing to communicate
                continue
            elif state in ('modified', 'untracked'):
                yield dict(
                    res,
                    status='impossible',
                    action='drop',
                    message=f'cannot drop {state} content, save first',
                )
                # we are not aborting nevetheless. in normal conditions
                # the above result will stop processing, but if desired
                # we could go on
            else:
                lgr.debug(
                    'Status record not considered for drop '
                    'state inspection: %s', res)


def _pre_drop_checks(ds, repo, paths, what, reckless, is_annex):
    if not is_annex and reckless not in ('datasets', 'kill'):
        # we cannot drop content in non-annex repos, issue same
        # 'notneeded' as for git-file in annex repo
        for p in paths or [ds.path]:
            yield dict(
                action='drop',
                path=str(p),
                status='notneeded',
                message="no annex'ed content",
            )
        # continue, this is nothing fatal

    if not is_annex and what in ('allkeys', 'unwanted') \
            and not reckless == 'kill':
        # these drop modes are meaningless without an annex
        yield dict(
            action='drop',
            path=ds.path,
            status='notneeded',
            message="dataset with no annex",
            type='dataset',
        )
        # continue, this is nothing fatal


def _detect_unpushed_revs(repo, consider_managed_branches):
    """Check if all local branch states (and HEAD) are available at a remote

    There need not be a 1:1 correspondence. What is tested is whether
    each commit corresponding to a local branch tip (or HEAD), is also an
    ancestor of any remote branch. It is not required that there is a single
    remote that has all commits.

    This only uses the state of remotes known to the local remote state.
    No remote synchronization is performed.

    Parameters
    ----------
    repo: GitRepo
      Repository to evaluated
    consider_managed_branches: bool
      Whether to enable handling of managed branches.

    Returns
    -------
    list
      Names of local states/refs that are no available at a remote.
    """
    if consider_managed_branches:
        # consolidate corresponding branches to get reliable detection
        repo.localsync(managed_only=True)
    # we do not want to check this for any managed branches
    # that are not meant to be pushed without consolidation
    # or even at all (incl. git-annex, it can behave in complex ways)
    local_refs = [
        lb for lb in repo.get_branches()
        if not (not consider_managed_branches
            or lb == 'git-annex' or repo.is_managed_branch(lb))]
    if not repo.get_active_branch():
        # check for HEAD, in case we are on a detached HEAD
        local_refs.append('HEAD')
    # extend to tags?
    remote_refs = repo.get_remote_branches()

    unpushed_refs = [
        local_ref
        for local_ref in local_refs
        if not any(repo.is_ancestor(local_ref, remote_ref)
                   for remote_ref in remote_refs)
    ]
    return unpushed_refs


def _detect_nondead_annex_at_remotes(repo, annex_uuid):
    """Return list of remote names that know about a given (not-dead) annex

    This only uses the state of remotes known to the local remote state.
    No remote synchronization is performed.

    Parameters
    ----------
    repo: AnnexRepo or GitRepo
      Repository to evaluated
    annex_uuid: str
      UUID string of a particular annex

    Returns
    -------
    list
      Names of any matching remote, the local repository is indicated using
      a `None` label.
    """
    # build the refs for all remotes and local
    remotes_w_registration = []
    for remote in chain([''], repo.get_remotes()):
        refprefix = '{}{}git-annex:'.format(
            remote,
            '/' if remote else '',
        )
        uuid_known = False
        try:
            for line in repo.call_git_items_(
                    ['cat-file', '-p', refprefix + 'uuid.log']):
                if line.startswith(annex_uuid):
                    # use None to label the local repo
                    uuid_known = True
                    break
        except CommandError as e:
            CapturedException(e)
            # this is not a problem per-se, logged above, just continue
            continue
        if not uuid_known:
            # if an annex id is not even in the uuid.log, we can stop here
            # (for this remote)
            continue

        # annex is known, but maybe is declared dead already, must check
        # trust.log in addition
        try:
            for line in repo.call_git_items_(
                    ['cat-file', '-p', refprefix + 'trust.log']):
                columns = line.split()
                if columns[0] == annex_uuid:
                    # not known if dead
                    uuid_known = False if columns[1] == 'X' else True
                    break
        except CommandError as e:
            CapturedException(e)
            # this is not a problem per-se, logged above, just continue
            continue
        finally:
            if uuid_known:
                remotes_w_registration.append(remote or None)
    return(remotes_w_registration)


def _kill_dataset(ds):
    """This is a harsh internal helper: it will wipe out a dataset, no checks
    """
    # figure out whether we should be nice to a superdataset later on
    has_super = ds.get_superdataset(topmost=False, registered_only=True)
    # Close any possibly associated process etc with underlying repo.
    # Otherwise - rmtree could fail to remove e.g. under NFS which would
    # still have some files opened by them (thus having .nfs00000xxxx
    # files) forbidding rmdir to work in rmtree
    ds.close()
    rmtree(ds.path)
    # invalidate loaded ConfigManager -- datasets are singletons!!
    ds._cfg = None
    if has_super:
        # recreate an empty mountpoint to make Git happier
        ds.pathobj.mkdir(exist_ok=True)
    yield dict(
        # keep uninstall to please the gods of a distant past
        #action='drop',
        action='uninstall',
        path=ds.path,
        type='dataset',
        status='ok',
    )


def _drop_allkeys(ds, repo, force=False, jobs=None):
    """
    """
    assert not (repo.dot_git / 'annex').is_symlink(), \
        "Dropping from a symlinked annex is unsupported to prevent data-loss"

    cmd = ['drop', '--all']
    if force:
        cmd.append('--force')
    if jobs:
        cmd.extend(['--jobs', str(jobs)])

    try:
        yield from repo._call_annex_records_items_(cmd)
    except CommandError as e:
        # pick up the results captured so far and yield them
        # the error will be amongst them
        yield from e.kwargs.get('stdout_json', [])


def _drop_files(ds, repo, paths, force=False, jobs=None):
    """Helper to drop content in datasets.

    Parameters
    ----------
    repo : AnnexRepo
    paths : list
      for which files to drop content
    check : bool
      whether to instruct annex to perform minimum copy availability
      checks

    Yields
    ------
    dict
    """
    assert not (repo.dot_git / 'annex').is_symlink(), \
        "Dropping from a symlinked annex is unsupported to prevent data-loss"
    cmd = ['drop']
    if force:
        cmd.append('--force')
    if jobs:
        cmd.extend(['--jobs', str(jobs)])

    respath_by_status = {}
    try:
        yield from (
            _postproc_annexdrop_result(res, respath_by_status, ds)
            for res in repo._call_annex_records_items_(cmd, files=paths)
        )
    except CommandError as e:
        # pick up the results captured so far and yield them
        # the error will be amongst them
        yield from (
            _postproc_annexdrop_result(res, respath_by_status, ds)
            for res in e.kwargs.get('stdout_json', [])
        )
    # report on things requested that annex was silent about
    for r in results_from_annex_noinfo(
            ds, paths, respath_by_status,
            dir_fail_msg='could not drop some content in %s %s',
            noinfo_dir_msg='nothing to drop from %s',
            noinfo_file_msg="no annex'ed content"):
        r['action'] = 'drop'
        yield r


def _postproc_annexdrop_result(res, respath_by_status, ds, **kwargs):
    res = annexjson2result(
        # annex reports are always about files
        res, ds, type='file', **kwargs)
    success = success_status_map[res['status']]
    respath_by_status[success] = \
        respath_by_status.get(success, []) + [res['path']]
    if res["status"] == "error" and res["action"] == "drop":
        msg = res.get("message", None)
        if isinstance(msg, str) and "Use --force to" in msg:
            # Avoid confusing datalad-drop callers with git-annex-drop's
            # suggestion to use --force.
            # Just mention reckless itself, do not go into the details
            # of which mode. This is likely changing over time and
            # adjusting this replacement will be forgotten.
            res["message"] = msg.replace(
                "--force",
                "--reckless availability")
    return res

```
--- 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:** Recursive Drop with `--reckless kill` Skips Subdatasets Under Specified Paths

**Description:**
When using the `drop` command with the `--recursive`, `--what all`, and `--reckless kill` options along with specified directory paths, subdatasets within those directories are not being removed as expected. This behavior leaves subdatasets lingering, which can lead to inconsistencies in the dataset state.

**Example Code:**
```python
from datalad import Dataset

def drop_subdatasets():
    ds = Dataset('/path/to/dataset').create()
    ds.create('subdir/subds')
    ds.create('subds')
    ds.drop(path=['subds', 'subdir'], what='all', reckless='kill', recursive=True)
    subs = ds.subdatasets(state='present')
    print(f"Remaining subdatasets: {len(subs)}")
```
**Expected Behavior:**
All specified paths and their contained subdatasets should be completely removed when performing a recursive drop with `--reckless kill`. After the drop operation, there should be no remaining subdatasets under the provided paths.

**Actual Behavior:**
Subdatasets within the specified directories are not being dropped. In the example above, after executing the drop command, one subdataset remains, resulting in `Remaining subdatasets: 1` instead of the expected `Remaining subdatasets: 0`.

[/ISSUE]
{
    "test_drop_file_content": "PASSED",
    "test_uninstall_recursive": "PASSED",
    "test_kill": "PASSED",
    "test_kill_7013": "PASSED",
    "test_refuse_to_drop_cwd": "PASSED",
    "test_careless_subdataset_uninstall": "PASSED",
    "test_drop_nocrash_absent_subds": "PASSED",
    "test_uninstall_without_super": "PASSED",
    "test_drop_from_git": "PASSED",
    "test_drop_uninit_annexrepo": "PASSED",
    "test_drop_allkeys_result_contains_annex_error_messages": "PASSED",
    "test_nodrop_symlinked_annex": "PASSED",
    "test_drop_allkeys": "FAILED",
    "test_undead_annex_detection": "FAILED",
    "test_unpushed_state_detection": "FAILED",
    "test_safetynet": "FAILED"
}
diff --git a/datalad/distributed/drop.py b/datalad/distributed/drop.py
index 5141148e2..ce2d40f5d 100644
--- a/datalad/distributed/drop.py
+++ b/datalad/distributed/drop.py
@@ -346,6 +346,30 @@ def _drop_dataset(ds, paths, what, reckless, recursive, recursion_limit, jobs):
         return
 
     if paths is not None and paths != [ds.pathobj] and what == 'all':
+        if recursive and reckless == 'kill':
+            # check if any paths contains a subdataset, and if so, drop it to
+            # ensure its not left behind
+            for sub in ds.subdatasets(
+                    # just check for subds at the provided path
+                    path=paths,
+                    state='present',
+                    recursive=recursive,
+                    recursion_limit=recursion_limit,
+                    result_xfm='datasets',
+                    on_failure='ignore',
+                    return_type='generator',
+                    result_renderer='disabled'):
+                if sub is not None:
+                    # there is a subdataset underneath the given path
+                    yield from _drop_dataset(
+                        ds=sub,
+                        # everything underneath the subds can go
+                        paths=None,
+                        what=what,
+                        reckless=reckless,
+                        recursive=False,
+                        recursion_limit=None,
+                        jobs=jobs)
         # so we have paths constraints that prevent dropping the full dataset
         lgr.debug('Only dropping file content for given paths in %s, '
                   'allthough instruction was to drop %s', ds, what)

{"datalad/distributed/drop.py": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 et:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"High-level interface for dropping dataset content\n\n\"\"\"\n\n__docformat__ = 'restructuredtext'\n\nimport logging\nfrom itertools import chain\nimport warnings\n\nfrom datalad.core.local.status import get_paths_by_ds\nfrom datalad.distribution.dataset import (\n    Dataset,\n    EnsureDataset,\n    datasetmethod,\n    require_dataset,\n)\nfrom datalad.interface.base import (\n    Interface,\n    build_doc,\n    eval_results,\n)\nfrom datalad.interface.common_opts import (\n    jobs_opt,\n    recursion_flag,\n    recursion_limit,\n)\nfrom datalad.interface.results import (\n    annexjson2result,\n    results_from_annex_noinfo,\n    success_status_map,\n)\nfrom datalad.runner.exception import CommandError\nfrom datalad.support.annexrepo import AnnexRepo\nfrom datalad.support.constraints import (\n    EnsureChoice,\n    EnsureNone,\n    EnsureStr,\n)\nfrom datalad.support.exceptions import CapturedException\nfrom datalad.support.param import Parameter\nfrom datalad.utils import (\n    ensure_list,\n    rmtree,\n)\n\nlgr = logging.getLogger('datalad.distributed.drop')\n\n\n@build_doc\nclass Drop(Interface):\n    \"\"\"Drop content of individual files or entire (sub)datasets\n\n    This command is the antagonist of 'get'. It can undo the retrieval of file\n    content, and the installation of subdatasets.\n\n    Dropping is a safe-by-default operation. Before dropping any information,\n    the command confirms the continued availability of file-content (see e.g.,\n    configuration 'annex.numcopies'), and the state of all dataset branches\n    from at least one known dataset sibling. Moreover, prior removal of an\n    entire dataset annex, that it is confirmed that it is no longer marked\n    as existing in the network of dataset siblings.\n\n    Importantly, all checks regarding version history availability and local\n    annex availability are performed using the current state of remote\n    siblings as known to the local dataset. This is done for performance\n    reasons and for resilience in case of absent network connectivity. To\n    ensure decision making based on up-to-date information, it is advised to\n    execute a dataset update before dropping dataset components.\n    \"\"\"\n    _examples_ = [\n        {'text': \"Drop single file content\",\n         'code_py': \"drop('path/to/file')\",\n         'code_cmd': \"datalad drop <path/to/file>\"},\n        {'text': \"Drop all file content in the current dataset\",\n         'code_py': \"drop('.')\",\n         'code_cmd': \"datalad drop\"},\n        {'text': \"Drop all file content in a dataset and all its subdatasets\",\n         'code_py': \"drop(dataset='.', recursive=True)\",\n         'code_cmd': \"datalad drop -d <path/to/dataset> -r\"},\n        {'text': \"Disable check to ensure the configured minimum number of \"\n                 \"remote sources for dropped data\",\n         'code_py': \"drop(path='path/to/content', reckless='availability')\",\n         'code_cmd': \"datalad drop <path/to/content> --reckless availability\"},\n        {'text': 'Drop (uninstall) an entire dataset '\n                 '(will fail with subdatasets present)',\n         'code_py': \"drop(what='all')\",\n         'code_cmd': \"datalad drop --what all\"},\n        {'text': 'Kill a dataset recklessly with any existing subdatasets too'\n                 '(this will be fast, but will disable any and all safety '\n                 'checks)',\n         'code_py': \"drop(what='all', reckless='kill', recursive=True)\",\n         'code_cmd': \"datalad drop --what all, --reckless kill --recursive\"},\n    ]\n\n    _params_ = dict(\n        dataset=Parameter(\n            args=(\"-d\", \"--dataset\"),\n            metavar=\"DATASET\",\n            doc=\"\"\"specify the dataset to perform drop from.\n            If no dataset is given, the current working directory is used\n            as operation context\"\"\",\n            constraints=EnsureDataset() | EnsureNone()),\n        path=Parameter(\n            args=(\"path\",),\n            metavar=\"PATH\",\n            doc=\"path of a dataset or dataset component to be dropped\",\n            nargs=\"*\",\n            constraints=EnsureStr() | EnsureNone()),\n        reckless=Parameter(\n            args=(\"--reckless\",),\n            doc=\"\"\"disable individual or all data safety measures that would\n            normally prevent potentially irreversible data-loss.\n            With 'modification', unsaved modifications in a dataset will not be\n            detected. This improves performance at the cost of permitting\n            potential loss of unsaved or untracked dataset components.\n            With 'availability', detection of dataset/branch-states that are\n            only available in the local dataset, and detection of an\n            insufficient number of file-content copies will be disabled.\n            Especially the latter is a potentially expensive check which might\n            involve numerous network transactions.\n            With 'undead', detection of whether a to-be-removed local annex is\n            still known to exist in the network of dataset-clones is disabled.\n            This could cause zombie-records of invalid file availability.\n            With 'kill', all safety-checks are disabled.\"\"\",\n            constraints=EnsureChoice(\n                'modification', 'availability', 'undead', 'kill', None)),\n        what=Parameter(\n            args=(\"--what\",),\n            doc=\"\"\"select what type of items shall be dropped.\n            With 'filecontent', only the file content (git-annex keys) of files\n            in a dataset's worktree will be dropped.\n            With 'allkeys', content of any version of any file in any branch\n            (including, but not limited to the worktree) will be dropped. This\n            effectively empties the annex of a local dataset.\n            With 'datasets', only complete datasets will be dropped (implies\n            'allkeys' mode for each such dataset), but no filecontent will be\n            dropped for any files in datasets that are not dropped entirely.\n            With 'all', content for any matching file or dataset will be dropped\n            entirely.\n            \"\"\",\n            # TODO add 'unwanted'\n            constraints=EnsureChoice('filecontent', 'allkeys', 'datasets', 'all')),\n        recursive=recursion_flag,\n        recursion_limit=recursion_limit,\n        jobs=jobs_opt,\n        check=Parameter(\n            args=(\"--nocheck\",),\n            doc=\"\"\"DEPRECATED: use '--reckless availability'\"\"\",\n            action=\"store_false\",\n            dest='check'),\n        if_dirty=Parameter(\n            args=(\"--if-dirty\",),\n            doc=\"\"\"DEPRECATED and IGNORED: use --reckless instead\"\"\",),\n    )\n\n    @staticmethod\n    @datasetmethod(name='drop')\n    @eval_results\n    def __call__(\n            path=None,\n            *,\n            what='filecontent',\n            reckless=None,\n            dataset=None,\n            recursive=False,\n            recursion_limit=None,\n            jobs=None,\n            # deprecated\n            check=None,\n            if_dirty=None):\n\n        # TODO if reckless is None, initialize from a potential config setting\n        # left behind by a reckless clone\n\n        # proper spelling of mode switches is critical for implementation\n        # below. double-check, also in Python API usage\n        # TODO consider making a generic helper\n        for label, value in (('what', what), ('reckless', reckless)):\n            try:\n                Drop._params_[label].constraints(value)\n            except ValueError as e:\n                raise ValueError(\n                    f\"Invalid '{label}' parameter value of: \"\n                    f\"{repr(value)} [{str(e)}]\") from e\n\n        if check is not None:\n            warnings.warn(\n                \"The `check` argument of `datalad drop` is deprecated, \"\n                \"use the `reckless` argument instead.\",\n                DeprecationWarning)\n        if if_dirty is not None:\n            warnings.warn(\n                \"The `if_dirty` argument of `datalad drop` is ignored, \"\n                \"it can be removed for a safe-by-default behavior. For \"\n                \"other cases consider the `reckless` argument.\",\n                DeprecationWarning)\n\n        if check is False:\n            if reckless is not None:\n                raise ValueError(\n                    'Must not use deprecated `check` argument, and new '\n                    '`reckless` argument together with `datalad drop`.')\n            reckless = 'availability'\n\n        if what in ('all', 'datasets') and reckless == 'kill' and not recursive:\n            raise ValueError(\n                'A reckless kill is requested but no recursion flag is set. '\n                \"With 'kill' no checks for subdatasets will be made, \"\n                'acknowledge by setting the recursive flag')\n\n        # we cannot test for what=='allkeys' and path==None here,\n        # on per each dataset. otherwise we will not be able to drop\n        # from a subdataset, by given its path -- desirable MIH thinks\n\n        ds = require_dataset(dataset, check_installed=True, purpose='drop')\n\n        res_props = dict(\n            logger=lgr,\n            refds=ds.path,\n        )\n        # if not paths are given, there will still be a single dataset record\n        # with paths==None\n        paths_by_ds, errors = get_paths_by_ds(\n            ds,\n            dataset,\n            ensure_list(path),\n            # XXX this needs more thought!! Maybe this is what the mode should be\n            # in general?!\n            # when we want to drop entire datasets, it is much more useful\n            # to have subdatasets be their own record\n            subdsroot_mode='sub'\n            if what in ('all', 'datasets')\n            else 'rsync',\n        )\n        for e in errors:\n            yield dict(\n                action='drop',\n                path=str(e),\n                status='error',\n                message=('path not underneath the reference dataset %s', ds),\n                **res_props)\n            # we are not returning, a caller could decide on failure mode\n\n        if what in ('all', 'datasets') and _paths_atunder_dir(\n                paths_by_ds, ds.pathobj.cwd()):\n            raise RuntimeError(\n                'refuse to perform actions that would remove the current '\n                'working directory')\n\n        lgr.debug('Discovered %i datasets to drop (from)', len(paths_by_ds))\n\n        # a dataset paths are normalized and guaranteed to be under the same\n        # root dataset\n        # we want to start from the leave datasets, such that all discovered\n        # dataset can be processed independently\n        for dpath in sorted(paths_by_ds.keys(), reverse=True):\n            d = Dataset(dpath)\n            lgr.debug('Starting to drop %s at %s', what, d)\n            for res in _drop_dataset(\n                    d,\n                    paths_by_ds[dpath],\n                    what=what,\n                    reckless=reckless,\n                    # recursion from any of the given paths!\n                    recursive=recursive,\n                    recursion_limit=recursion_limit,\n                    jobs=jobs):\n                yield dict(res, **res_props)\n            lgr.debug('Finished dropping %s at %s', what, d)\n        return\n\n\ndef _paths_atunder_dir(pbd, dirpath):\n    \"\"\"Whether any of the paths is at or under a reference path\n\n    Parameters\n    ----------\n    pbd: dict\n      Dataset path dict is produced by get_paths_by_ds()\n    dirpath: Path\n      Reference path\n\n    Returns\n    -------\n    bool\n    \"\"\"\n    for dpath, paths in pbd.items():\n        for p in ([dpath] if paths is None else paths):\n            if p == dirpath or p in dirpath.parents:\n                return True\n    return False\n\n\ndef _drop_dataset(ds, paths, what, reckless, recursive, recursion_limit, jobs):\n    lgr.debug('Start dropping for %s', ds)\n    # we know that any given path is part of `ds` and not any of its\n    # subdatasets!\n\n    # by-passing this completely with reckless=kill\n    if recursive and not reckless == 'kill':\n        # process subdatasets first with recursion\n        for sub in ds.subdatasets(\n                # must be resolved!\n                path=paths or None,\n                # nothing to drop with unavailable subdatasets\n                state='present',\n                # we can use the full recursion depth, only the first layer\n                # of calls to _drop_dataset() must/can have recursive=True\n                recursive=recursive,\n                recursion_limit=recursion_limit,\n                # start reporting with the leaves\n                bottomup=True,\n                result_xfm='datasets',\n                on_failure='ignore',\n                return_type='generator',\n                result_renderer='disabled'):\n            yield from _drop_dataset(\n                ds=sub,\n                # everything, the entire subdataset is matching a given path\n                paths=None,\n                what=what,\n                reckless=reckless,\n                recursive=False,\n                recursion_limit=None,\n                jobs=jobs)\n\n    if not ds.pathobj.exists():\n        # basic protection against something having wiped it out already.\n        # should not happen, but better than a crash, if it does\n        yield dict(\n            action='drop',\n            path=ds.path,\n            status='notneeded',\n            message=\"does not exist\",\n            type='dataset',\n        )\n        return\n\n    if paths is not None and paths != [ds.pathobj] and what == 'all':\n        # so we have paths constraints that prevent dropping the full dataset\n        lgr.debug('Only dropping file content for given paths in %s, '\n                  'allthough instruction was to drop %s', ds, what)\n        what = 'filecontent'\n\n    repo = ds.repo\n    is_annex = isinstance(repo, AnnexRepo)\n\n    # first critical checks that might prevent further operation\n    had_fatality = False\n    for res in _fatal_pre_drop_checks(\n            ds, repo, paths, what, reckless, is_annex):\n        had_fatality = True\n        yield res\n    if had_fatality:\n        return\n\n    # next check must come AFTER the modification checks above, otherwise\n    # remove() could not rely on the modification detection above\n    if paths is not None and paths != [ds.pathobj] and what == 'datasets':\n        # so we have paths constraints that prevent dropping the full dataset\n        # there is nothing to do here, but to drop keys, which we must not\n        # done\n        return\n\n    # now conditional/informative checks\n    yield from _pre_drop_checks(ds, repo, paths, what, reckless, is_annex)\n\n    if is_annex and what == 'filecontent':\n        yield from _drop_files(\n            ds,\n            repo,\n            # give paths or '.' with no constraint\n            paths=[str(p.relative_to(ds.pathobj))\n                   for p in paths] if paths else '.',\n            force=reckless in ('availability', 'kill'),\n            jobs=jobs,\n        )\n        # end it here for safety, the rest of the function deals with\n        # dropping more and more fundamentally\n        return\n\n    drop_all_errored = False\n    if is_annex and what in ('allkeys', 'datasets', 'all') \\\n            and not reckless == 'kill':\n        for r in _drop_allkeys(\n                ds,\n                repo,\n                force=reckless in ('availability',),\n                jobs=jobs):\n            res = dict(\n                action='drop',\n                type='key',\n                # use the path of the containing dataset\n                # using the location of the key does not add any\n                # practical value, and is expensive to obtain\n                path=ds.path,\n                status='ok' if r.get('success') else 'error',\n                key=r.get('key'),\n            )\n            # pull any note, and rename recommended parameter to\n            # avoid confusion\n            message = r.get('note', '').replace(\n                '--force',\n                '--reckless availability')\n            if message:\n                res['message'] = message\n            error_messages = r.get('error-messages')\n            if error_messages:\n                res['error_message'] = '\\n'.join(\n                    m.strip() for m in error_messages\n                )\n            # play safe, if there is no status, assume error\n            if res.get('status', 'error') != 'ok':\n                drop_all_errored = True\n            yield res\n\n    if drop_all_errored:\n        # end it here, if there is any indication that wiping out the\n        # repo is unsafe\n        return\n\n    if what in ('all', 'datasets'):\n        yield from _kill_dataset(ds)\n    lgr.debug('Done dropping for %s', ds)\n    return\n\n\ndef _fatal_pre_drop_checks(ds, repo, paths, what, reckless, is_annex):\n    if what == 'allkeys' and paths is not None \\\n            and paths != [ds.pathobj]:\n        yield dict(\n            action='drop',\n            path=ds.path,\n            type='dataset',\n            status='impossible',\n            message=(\n                'cannot drop %s, with path constraints given: %s',\n                what, paths),\n        )\n        return\n\n    if what in ('all', 'datasets') and not reckless == 'kill':\n        # we must not have subdatasets anymore\n        # if we do, --recursive was forgotton\n        subdatasets = ds.subdatasets(\n            path=paths,\n            # we only care about the present ones\n            state='present',\n            # first-level is enough, if that has none, there will be none\n            recursive=False,\n            result_xfm='paths',\n            result_renderer='disabled')\n        if subdatasets:\n            yield dict(\n                action='uninstall',\n                path=ds.path,\n                type='dataset',\n                status='error',\n                message=('cannot drop dataset, subdataset(s) still present '\n                         '(forgot --recursive?): %s', subdatasets)\n            )\n            # this is fatal\n            return\n\n    if what in ('all', 'datasets') \\\n            and reckless not in ('availability', 'kill') \\\n            and (paths is None or paths == [ds.pathobj]):\n        unpushed = _detect_unpushed_revs(repo, is_annex)\n        if unpushed:\n            yield dict(\n                action='uninstall',\n                path=ds.path,\n                type='dataset',\n                status='error',\n                message=(\n                    \"to-be-dropped dataset has revisions that \"\n                    \"are not available at any known sibling. Use \"\n                    \"`datalad push --to ...` to push \"\n                    \"these before dropping the local dataset, \"\n                    \"or ignore via `--reckless availability`. \"\n                    \"Unique revisions: %s\",\n                    unpushed)\n            )\n            # this is fatal\n            return\n\n    if is_annex and what in ('all', 'datasets') \\\n            and reckless not in ('undead', 'kill'):\n        # this annex is about to die, test if it is still considered\n        # not-dead. if so, complain to avoid generation of zombies\n        # (annexed that are floating around, but are actually dead).\n        # if repo.uuid is None, git annex init never ran, and we can skip this\n        remotes_that_know_this_annex = None if repo.uuid is None else [\n            r\n            for r in _detect_nondead_annex_at_remotes(repo, repo.uuid)\n            # filter out \"here\"\n            if r is not None\n        ]\n        if remotes_that_know_this_annex:\n            yield dict(\n                action='uninstall',\n                path=ds.path,\n                type='dataset',\n                status='error',\n                message=(\n                    \"to-be-deleted local annex not declared 'dead' at the \"\n                    \"following siblings. Announce death \"\n                    \"(`git annex dead here` + `datalad push --to ...`), \"\n                    \"or ignore via `--reckless undead`: %s\",\n                    remotes_that_know_this_annex)\n            )\n            # this is fatal\n            return\n\n    if reckless not in ('modification', 'kill'):\n        # do a cheaper status run to discover any kind of modification and\n        # generate results based on the `what` mode of operation\n        for res in ds.status(\n                path=paths,\n                # untracked content will not be reported on further down\n                # must catch it here\n                untracked='normal',\n                # downstream code can handle non-annex/annex distinction\n                # avoid expensive evaluation here\n                annex=False,\n                # we only need to inspect a subdataset's state in case\n                # we want to drop it completely\n                eval_subdataset_state='commit'\n                if what in ('all', 'datasets') else 'no',\n                # recursion is handled outside this function\n                recursive=False,\n                result_renderer='disabled',\n                return_type='generator',\n                on_failure='ignore'):\n            state = res.get('state')\n            if state == 'clean':\n                # nothing to worry about, nothing to communicate\n                continue\n            elif state in ('modified', 'untracked'):\n                yield dict(\n                    res,\n                    status='impossible',\n                    action='drop',\n                    message=f'cannot drop {state} content, save first',\n                )\n                # we are not aborting nevetheless. in normal conditions\n                # the above result will stop processing, but if desired\n                # we could go on\n            else:\n                lgr.debug(\n                    'Status record not considered for drop '\n                    'state inspection: %s', res)\n\n\ndef _pre_drop_checks(ds, repo, paths, what, reckless, is_annex):\n    if not is_annex and reckless not in ('datasets', 'kill'):\n        # we cannot drop content in non-annex repos, issue same\n        # 'notneeded' as for git-file in annex repo\n        for p in paths or [ds.path]:\n            yield dict(\n                action='drop',\n                path=str(p),\n                status='notneeded',\n                message=\"no annex'ed content\",\n            )\n        # continue, this is nothing fatal\n\n    if not is_annex and what in ('allkeys', 'unwanted') \\\n            and not reckless == 'kill':\n        # these drop modes are meaningless without an annex\n        yield dict(\n            action='drop',\n            path=ds.path,\n            status='notneeded',\n            message=\"dataset with no annex\",\n            type='dataset',\n        )\n        # continue, this is nothing fatal\n\n\ndef _detect_unpushed_revs(repo, consider_managed_branches):\n    \"\"\"Check if all local branch states (and HEAD) are available at a remote\n\n    There need not be a 1:1 correspondence. What is tested is whether\n    each commit corresponding to a local branch tip (or HEAD), is also an\n    ancestor of any remote branch. It is not required that there is a single\n    remote that has all commits.\n\n    This only uses the state of remotes known to the local remote state.\n    No remote synchronization is performed.\n\n    Parameters\n    ----------\n    repo: GitRepo\n      Repository to evaluated\n    consider_managed_branches: bool\n      Whether to enable handling of managed branches.\n\n    Returns\n    -------\n    list\n      Names of local states/refs that are no available at a remote.\n    \"\"\"\n    if consider_managed_branches:\n        # consolidate corresponding branches to get reliable detection\n        repo.localsync(managed_only=True)\n    # we do not want to check this for any managed branches\n    # that are not meant to be pushed without consolidation\n    # or even at all (incl. git-annex, it can behave in complex ways)\n    local_refs = [\n        lb for lb in repo.get_branches()\n        if not (not consider_managed_branches\n            or lb == 'git-annex' or repo.is_managed_branch(lb))]\n    if not repo.get_active_branch():\n        # check for HEAD, in case we are on a detached HEAD\n        local_refs.append('HEAD')\n    # extend to tags?\n    remote_refs = repo.get_remote_branches()\n\n    unpushed_refs = [\n        local_ref\n        for local_ref in local_refs\n        if not any(repo.is_ancestor(local_ref, remote_ref)\n                   for remote_ref in remote_refs)\n    ]\n    return unpushed_refs\n\n\ndef _detect_nondead_annex_at_remotes(repo, annex_uuid):\n    \"\"\"Return list of remote names that know about a given (not-dead) annex\n\n    This only uses the state of remotes known to the local remote state.\n    No remote synchronization is performed.\n\n    Parameters\n    ----------\n    repo: AnnexRepo or GitRepo\n      Repository to evaluated\n    annex_uuid: str\n      UUID string of a particular annex\n\n    Returns\n    -------\n    list\n      Names of any matching remote, the local repository is indicated using\n      a `None` label.\n    \"\"\"\n    # build the refs for all remotes and local\n    remotes_w_registration = []\n    for remote in chain([''], repo.get_remotes()):\n        refprefix = '{}{}git-annex:'.format(\n            remote,\n            '/' if remote else '',\n        )\n        uuid_known = False\n        try:\n            for line in repo.call_git_items_(\n                    ['cat-file', '-p', refprefix + 'uuid.log']):\n                if line.startswith(annex_uuid):\n                    # use None to label the local repo\n                    uuid_known = True\n                    break\n        except CommandError as e:\n            CapturedException(e)\n            # this is not a problem per-se, logged above, just continue\n            continue\n        if not uuid_known:\n            # if an annex id is not even in the uuid.log, we can stop here\n            # (for this remote)\n            continue\n\n        # annex is known, but maybe is declared dead already, must check\n        # trust.log in addition\n        try:\n            for line in repo.call_git_items_(\n                    ['cat-file', '-p', refprefix + 'trust.log']):\n                columns = line.split()\n                if columns[0] == annex_uuid:\n                    # not known if dead\n                    uuid_known = False if columns[1] == 'X' else True\n                    break\n        except CommandError as e:\n            CapturedException(e)\n            # this is not a problem per-se, logged above, just continue\n            continue\n        finally:\n            if uuid_known:\n                remotes_w_registration.append(remote or None)\n    return(remotes_w_registration)\n\n\ndef _kill_dataset(ds):\n    \"\"\"This is a harsh internal helper: it will wipe out a dataset, no checks\n    \"\"\"\n    # figure out whether we should be nice to a superdataset later on\n    has_super = ds.get_superdataset(topmost=False, registered_only=True)\n    # Close any possibly associated process etc with underlying repo.\n    # Otherwise - rmtree could fail to remove e.g. under NFS which would\n    # still have some files opened by them (thus having .nfs00000xxxx\n    # files) forbidding rmdir to work in rmtree\n    ds.close()\n    rmtree(ds.path)\n    # invalidate loaded ConfigManager -- datasets are singletons!!\n    ds._cfg = None\n    if has_super:\n        # recreate an empty mountpoint to make Git happier\n        ds.pathobj.mkdir(exist_ok=True)\n    yield dict(\n        # keep uninstall to please the gods of a distant past\n        #action='drop',\n        action='uninstall',\n        path=ds.path,\n        type='dataset',\n        status='ok',\n    )\n\n\ndef _drop_allkeys(ds, repo, force=False, jobs=None):\n    \"\"\"\n    \"\"\"\n    assert not (repo.dot_git / 'annex').is_symlink(), \\\n        \"Dropping from a symlinked annex is unsupported to prevent data-loss\"\n\n    cmd = ['drop', '--all']\n    if force:\n        cmd.append('--force')\n    if jobs:\n        cmd.extend(['--jobs', str(jobs)])\n\n    try:\n        yield from repo._call_annex_records_items_(cmd)\n    except CommandError as e:\n        # pick up the results captured so far and yield them\n        # the error will be amongst them\n        yield from e.kwargs.get('stdout_json', [])\n\n\ndef _drop_files(ds, repo, paths, force=False, jobs=None):\n    \"\"\"Helper to drop content in datasets.\n\n    Parameters\n    ----------\n    repo : AnnexRepo\n    paths : list\n      for which files to drop content\n    check : bool\n      whether to instruct annex to perform minimum copy availability\n      checks\n\n    Yields\n    ------\n    dict\n    \"\"\"\n    assert not (repo.dot_git / 'annex').is_symlink(), \\\n        \"Dropping from a symlinked annex is unsupported to prevent data-loss\"\n    cmd = ['drop']\n    if force:\n        cmd.append('--force')\n    if jobs:\n        cmd.extend(['--jobs', str(jobs)])\n\n    respath_by_status = {}\n    try:\n        yield from (\n            _postproc_annexdrop_result(res, respath_by_status, ds)\n            for res in repo._call_annex_records_items_(cmd, files=paths)\n        )\n    except CommandError as e:\n        # pick up the results captured so far and yield them\n        # the error will be amongst them\n        yield from (\n            _postproc_annexdrop_result(res, respath_by_status, ds)\n            for res in e.kwargs.get('stdout_json', [])\n        )\n    # report on things requested that annex was silent about\n    for r in results_from_annex_noinfo(\n            ds, paths, respath_by_status,\n            dir_fail_msg='could not drop some content in %s %s',\n            noinfo_dir_msg='nothing to drop from %s',\n            noinfo_file_msg=\"no annex'ed content\"):\n        r['action'] = 'drop'\n        yield r\n\n\ndef _postproc_annexdrop_result(res, respath_by_status, ds, **kwargs):\n    res = annexjson2result(\n        # annex reports are always about files\n        res, ds, type='file', **kwargs)\n    success = success_status_map[res['status']]\n    respath_by_status[success] = \\\n        respath_by_status.get(success, []) + [res['path']]\n    if res[\"status\"] == \"error\" and res[\"action\"] == \"drop\":\n        msg = res.get(\"message\", None)\n        if isinstance(msg, str) and \"Use --force to\" in msg:\n            # Avoid confusing datalad-drop callers with git-annex-drop's\n            # suggestion to use --force.\n            # Just mention reckless itself, do not go into the details\n            # of which mode. This is likely changing over time and\n            # adjusting this replacement will be forgotten.\n            res[\"message\"] = msg.replace(\n                \"--force\",\n                \"--reckless availability\")\n    return res\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:** Recursive Drop with `--reckless kill` Skips Subdatasets Under Specified Paths

**Description:**
When using the `drop` command with the `--recursive`, `--what all`, and `--reckless kill` options along with specified directory paths, subdatasets within those directories are not being removed as expected. This behavior leaves subdatasets lingering, which can lead to inconsistencies in the dataset state.

**Example Code:**
```python
from datalad import Dataset

def drop_subdatasets():
    ds = Dataset('/path/to/dataset').create()
    ds.create('subdir/subds')
    ds.create('subds')
    ds.drop(path=['subds', 'subdir'], what='all', reckless='kill', recursive=True)
    subs = ds.subdatasets(state='present')
    print(f"Remaining subdatasets: {len(subs)}")
```
**Expected Behavior:**
All specified paths and their contained subdatasets should be completely removed when performing a recursive drop with `--reckless kill`. After the drop operation, there should be no remaining subdatasets under the provided paths.

**Actual Behavior:**
Subdatasets within the specified directories are not being dropped. In the example above, after executing the drop command, one subdataset remains, resulting in `Remaining subdatasets: 1` instead of the expected `Remaining subdatasets: 0`.

[/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/distributed/drop.py
# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-
# ex: set sts=4 ts=4 sw=4 et:
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
#   See COPYING file distributed along with the datalad package for the
#   copyright and license terms.
#
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""High-level interface for dropping dataset content

"""

__docformat__ = 'restructuredtext'

import logging
from itertools import chain
import warnings

from datalad.core.local.status import get_paths_by_ds
from datalad.distribution.dataset import (
    Dataset,
    EnsureDataset,
    datasetmethod,
    require_dataset,
)
from datalad.interface.base import (
    Interface,
    build_doc,
    eval_results,
)
from datalad.interface.common_opts import (
    jobs_opt,
    recursion_flag,
    recursion_limit,
)
from datalad.interface.results import (
    annexjson2result,
    results_from_annex_noinfo,
    success_status_map,
)
from datalad.runner.exception import CommandError
from datalad.support.annexrepo import AnnexRepo
from datalad.support.constraints import (
    EnsureChoice,
    EnsureNone,
    EnsureStr,
)
from datalad.support.exceptions import CapturedException
from datalad.support.param import Parameter
from datalad.utils import (
    ensure_list,
    rmtree,
)

lgr = logging.getLogger('datalad.distributed.drop')


@build_doc
class Drop(Interface):
    """Drop content of individual files or entire (sub)datasets

    This command is the antagonist of 'get'. It can undo the retrieval of file
    content, and the installation of subdatasets.

    Dropping is a safe-by-default operation. Before dropping any information,
    the command confirms the continued availability of file-content (see e.g.,
    configuration 'annex.numcopies'), and the state of all dataset branches
    from at least one known dataset sibling. Moreover, prior removal of an
    entire dataset annex, that it is confirmed that it is no longer marked
    as existing in the network of dataset siblings.

    Importantly, all checks regarding version history availability and local
    annex availability are performed using the current state of remote
    siblings as known to the local dataset. This is done for performance
    reasons and for resilience in case of absent network connectivity. To
    ensure decision making based on up-to-date information, it is advised to
    execute a dataset update before dropping dataset components.
    """
    _examples_ = [
        {'text': "Drop single file content",
         'code_py': "drop('path/to/file')",
         'code_cmd': "datalad drop <path/to/file>"},
        {'text': "Drop all file content in the current dataset",
         'code_py': "drop('.')",
         'code_cmd': "datalad drop"},
        {'text': "Drop all file content in a dataset and all its subdatasets",
         'code_py': "drop(dataset='.', recursive=True)",
         'code_cmd': "datalad drop -d <path/to/dataset> -r"},
        {'text': "Disable check to ensure the configured minimum number of "
                 "remote sources for dropped data",
         'code_py': "drop(path='path/to/content', reckless='availability')",
         'code_cmd': "datalad drop <path/to/content> --reckless availability"},
        {'text': 'Drop (uninstall) an entire dataset '
                 '(will fail with subdatasets present)',
         'code_py': "drop(what='all')",
         'code_cmd': "datalad drop --what all"},
        {'text': 'Kill a dataset recklessly with any existing subdatasets too'
                 '(this will be fast, but will disable any and all safety '
                 'checks)',
         'code_py': "drop(what='all', reckless='kill', recursive=True)",
         'code_cmd': "datalad drop --what all, --reckless kill --recursive"},
    ]

    _params_ = dict(
        dataset=Parameter(
            args=("-d", "--dataset"),
            metavar="DATASET",
            doc="""specify the dataset to perform drop from.
            If no dataset is given, the current working directory is used
            as operation context""",
            constraints=EnsureDataset() | EnsureNone()),
        path=Parameter(
            args=("path",),
            metavar="PATH",
            doc="path of a dataset or dataset component to be dropped",
            nargs="*",
            constraints=EnsureStr() | EnsureNone()),
        reckless=Parameter(
            args=("--reckless",),
            doc="""disable individual or all data safety measures that would
            normally prevent potentially irreversible data-loss.
            With 'modification', unsaved modifications in a dataset will not be
            detected. This improves performance at the cost of permitting
            potential loss of unsaved or untracked dataset components.
            With 'availability', detection of dataset/branch-states that are
            only available in the local dataset, and detection of an
            insufficient number of file-content copies will be disabled.
            Especially the latter is a potentially expensive check which might
            involve numerous network transactions.
            With 'undead', detection of whether a to-be-removed local annex is
            still known to exist in the network of dataset-clones is disabled.
            This could cause zombie-records of invalid file availability.
            With 'kill', all safety-checks are disabled.""",
            constraints=EnsureChoice(
                'modification', 'availability', 'undead', 'kill', None)),
        what=Parameter(
            args=("--what",),
            doc="""select what type of items shall be dropped.
            With 'filecontent', only the file content (git-annex keys) of files
            in a dataset's worktree will be dropped.
            With 'allkeys', content of any version of any file in any branch
            (including, but not limited to the worktree) will be dropped. This
            effectively empties the annex of a local dataset.
            With 'datasets', only complete datasets will be dropped (implies
            'allkeys' mode for each such dataset), but no filecontent will be
            dropped for any files in datasets that are not dropped entirely.
            With 'all', content for any matching file or dataset will be dropped
            entirely.
            """,
            # TODO add 'unwanted'
            constraints=EnsureChoice('filecontent', 'allkeys', 'datasets', 'all')),
        recursive=recursion_flag,
        recursion_limit=recursion_limit,
        jobs=jobs_opt,
        check=Parameter(
            args=("--nocheck",),
            doc="""DEPRECATED: use '--reckless availability'""",
            action="store_false",
            dest='check'),
        if_dirty=Parameter(
            args=("--if-dirty",),
            doc="""DEPRECATED and IGNORED: use --reckless instead""",),
    )

    @staticmethod
    @datasetmethod(name='drop')
    @eval_results
    def __call__(
            path=None,
            *,
            what='filecontent',
            reckless=None,
            dataset=None,
            recursive=False,
            recursion_limit=None,
            jobs=None,
            # deprecated
            check=None,
            if_dirty=None):

        # TODO if reckless is None, initialize from a potential config setting
        # left behind by a reckless clone

        # proper spelling of mode switches is critical for implementation
        # below. double-check, also in Python API usage
        # TODO consider making a generic helper
        for label, value in (('what', what), ('reckless', reckless)):
            try:
                Drop._params_[label].constraints(value)
            except ValueError as e:
                raise ValueError(
                    f"Invalid '{label}' parameter value of: "
                    f"{repr(value)} [{str(e)}]") from e

        if check is not None:
            warnings.warn(
                "The `check` argument of `datalad drop` is deprecated, "
                "use the `reckless` argument instead.",
                DeprecationWarning)
        if if_dirty is not None:
            warnings.warn(
                "The `if_dirty` argument of `datalad drop` is ignored, "
                "it can be removed for a safe-by-default behavior. For "
                "other cases consider the `reckless` argument.",
                DeprecationWarning)

        if check is False:
            if reckless is not None:
                raise ValueError(
                    'Must not use deprecated `check` argument, and new '
                    '`reckless` argument together with `datalad drop`.')
            reckless = 'availability'

        if what in ('all', 'datasets') and reckless == 'kill' and not recursive:
            raise ValueError(
                'A reckless kill is requested but no recursion flag is set. '
                "With 'kill' no checks for subdatasets will be made, "
                'acknowledge by setting the recursive flag')

        # we cannot test for what=='allkeys' and path==None here,
        # on per each dataset. otherwise we will not be able to drop
        # from a subdataset, by given its path -- desirable MIH thinks

        ds = require_dataset(dataset, check_installed=True, purpose='drop')

        res_props = dict(
            logger=lgr,
            refds=ds.path,
        )
        # if not paths are given, there will still be a single dataset record
        # with paths==None
        paths_by_ds, errors = get_paths_by_ds(
            ds,
            dataset,
            ensure_list(path),
            # XXX this needs more thought!! Maybe this is what the mode should be
            # in general?!
            # when we want to drop entire datasets, it is much more useful
            # to have subdatasets be their own record
            subdsroot_mode='sub'
            if what in ('all', 'datasets')
            else 'rsync',
        )
        for e in errors:
            yield dict(
                action='drop',
                path=str(e),
                status='error',
                message=('path not underneath the reference dataset %s', ds),
                **res_props)
            # we are not returning, a caller could decide on failure mode

        if what in ('all', 'datasets') and _paths_atunder_dir(
                paths_by_ds, ds.pathobj.cwd()):
            raise RuntimeError(
                'refuse to perform actions that would remove the current '
                'working directory')

        lgr.debug('Discovered %i datasets to drop (from)', len(paths_by_ds))

        # a dataset paths are normalized and guaranteed to be under the same
        # root dataset
        # we want to start from the leave datasets, such that all discovered
        # dataset can be processed independently
        for dpath in sorted(paths_by_ds.keys(), reverse=True):
            d = Dataset(dpath)
            lgr.debug('Starting to drop %s at %s', what, d)
            for res in _drop_dataset(
                    d,
                    paths_by_ds[dpath],
                    what=what,
                    reckless=reckless,
                    # recursion from any of the given paths!
                    recursive=recursive,
                    recursion_limit=recursion_limit,
                    jobs=jobs):
                yield dict(res, **res_props)
            lgr.debug('Finished dropping %s at %s', what, d)
        return


def _paths_atunder_dir(pbd, dirpath):
    """Whether any of the paths is at or under a reference path

    Parameters
    ----------
    pbd: dict
      Dataset path dict is produced by get_paths_by_ds()
    dirpath: Path
      Reference path

    Returns
    -------
    bool
    """
    for dpath, paths in pbd.items():
        for p in ([dpath] if paths is None else paths):
            if p == dirpath or p in dirpath.parents:
                return True
    return False


def _drop_dataset(ds, paths, what, reckless, recursive, recursion_limit, jobs):
    lgr.debug('Start dropping for %s', ds)
    # we know that any given path is part of `ds` and not any of its
    # subdatasets!

    # by-passing this completely with reckless=kill
    if recursive and not reckless == 'kill':
        # process subdatasets first with recursion
        for sub in ds.subdatasets(
                # must be resolved!
                path=paths or None,
                # nothing to drop with unavailable subdatasets
                state='present',
                # we can use the full recursion depth, only the first layer
                # of calls to _drop_dataset() must/can have recursive=True
                recursive=recursive,
                recursion_limit=recursion_limit,
                # start reporting with the leaves
                bottomup=True,
                result_xfm='datasets',
                on_failure='ignore',
                return_type='generator',
                result_renderer='disabled'):
            yield from _drop_dataset(
                ds=sub,
                # everything, the entire subdataset is matching a given path
                paths=None,
                what=what,
                reckless=reckless,
                recursive=False,
                recursion_limit=None,
                jobs=jobs)

    if not ds.pathobj.exists():
        # basic protection against something having wiped it out already.
        # should not happen, but better than a crash, if it does
        yield dict(
            action='drop',
            path=ds.path,
            status='notneeded',
            message="does not exist",
            type='dataset',
        )
        return

    if paths is not None and paths != [ds.pathobj] and what == 'all':
        # so we have paths constraints that prevent dropping the full dataset
        lgr.debug('Only dropping file content for given paths in %s, '
                  'allthough instruction was to drop %s', ds, what)
        what = 'filecontent'

    repo = ds.repo
    is_annex = isinstance(repo, AnnexRepo)

    # first critical checks that might prevent further operation
    had_fatality = False
    for res in _fatal_pre_drop_checks(
            ds, repo, paths, what, reckless, is_annex):
        had_fatality = True
        yield res
    if had_fatality:
        return

    # next check must come AFTER the modification checks above, otherwise
    # remove() could not rely on the modification detection above
    if paths is not None and paths != [ds.pathobj] and what == 'datasets':
        # so we have paths constraints that prevent dropping the full dataset
        # there is nothing to do here, but to drop keys, which we must not
        # done
        return

    # now conditional/informative checks
    yield from _pre_drop_checks(ds, repo, paths, what, reckless, is_annex)

    if is_annex and what == 'filecontent':
        yield from _drop_files(
            ds,
            repo,
            # give paths or '.' with no constraint
            paths=[str(p.relative_to(ds.pathobj))
                   for p in paths] if paths else '.',
            force=reckless in ('availability', 'kill'),
            jobs=jobs,
        )
        # end it here for safety, the rest of the function deals with
        # dropping more and more fundamentally
        return

    drop_all_errored = False
    if is_annex and what in ('allkeys', 'datasets', 'all') \
            and not reckless == 'kill':
        for r in _drop_allkeys(
                ds,
                repo,
                force=reckless in ('availability',),
                jobs=jobs):
            res = dict(
                action='drop',
                type='key',
                # use the path of the containing dataset
                # using the location of the key does not add any
                # practical value, and is expensive to obtain
                path=ds.path,
                status='ok' if r.get('success') else 'error',
                key=r.get('key'),
            )
            # pull any note, and rename recommended parameter to
            # avoid confusion
            message = r.get('note', '').replace(
                '--force',
                '--reckless availability')
            if message:
                res['message'] = message
            error_messages = r.get('error-messages')
            if error_messages:
                res['error_message'] = '\n'.join(
                    m.strip() for m in error_messages
                )
            # play safe, if there is no status, assume error
            if res.get('status', 'error') != 'ok':
                drop_all_errored = True
            yield res

    if drop_all_errored:
        # end it here, if there is any indication that wiping out the
        # repo is unsafe
        return

    if what in ('all', 'datasets'):
        yield from _kill_dataset(ds)
    lgr.debug('Done dropping for %s', ds)
    return


def _fatal_pre_drop_checks(ds, repo, paths, what, reckless, is_annex):
    if what == 'allkeys' and paths is not None \
            and paths != [ds.pathobj]:
        yield dict(
            action='drop',
            path=ds.path,
            type='dataset',
            status='impossible',
            message=(
                'cannot drop %s, with path constraints given: %s',
                what, paths),
        )
        return

    if what in ('all', 'datasets') and not reckless == 'kill':
        # we must not have subdatasets anymore
        # if we do, --recursive was forgotton
        subdatasets = ds.subdatasets(
            path=paths,
            # we only care about the present ones
            state='present',
            # first-level is enough, if that has none, there will be none
            recursive=False,
            result_xfm='paths',
            result_renderer='disabled')
        if subdatasets:
            yield dict(
                action='uninstall',
                path=ds.path,
                type='dataset',
                status='error',
                message=('cannot drop dataset, subdataset(s) still present '
                         '(forgot --recursive?): %s', subdatasets)
            )
            # this is fatal
            return

    if what in ('all', 'datasets') \
            and reckless not in ('availability', 'kill') \
            and (paths is None or paths == [ds.pathobj]):
        unpushed = _detect_unpushed_revs(repo, is_annex)
        if unpushed:
            yield dict(
                action='uninstall',
                path=ds.path,
                type='dataset',
                status='error',
                message=(
                    "to-be-dropped dataset has revisions that "
                    "are not available at any known sibling. Use "
                    "`datalad push --to ...` to push "
                    "these before dropping the local dataset, "
                    "or ignore via `--reckless availability`. "
                    "Unique revisions: %s",
                    unpushed)
            )
            # this is fatal
            return

    if is_annex and what in ('all', 'datasets') \
            and reckless not in ('undead', 'kill'):
        # this annex is about to die, test if it is still considered
        # not-dead. if so, complain to avoid generation of zombies
        # (annexed that are floating around, but are actually dead).
        # if repo.uuid is None, git annex init never ran, and we can skip this
        remotes_that_know_this_annex = None if repo.uuid is None else [
            r
            for r in _detect_nondead_annex_at_remotes(repo, repo.uuid)
            # filter out "here"
            if r is not None
        ]
        if remotes_that_know_this_annex:
            yield dict(
                action='uninstall',
                path=ds.path,
                type='dataset',
                status='error',
                message=(
                    "to-be-deleted local annex not declared 'dead' at the "
                    "following siblings. Announce death "
                    "(`git annex dead here` + `datalad push --to ...`), "
                    "or ignore via `--reckless undead`: %s",
                    remotes_that_know_this_annex)
            )
            # this is fatal
            return

    if reckless not in ('modification', 'kill'):
        # do a cheaper status run to discover any kind of modification and
        # generate results based on the `what` mode of operation
        for res in ds.status(
                path=paths,
                # untracked content will not be reported on further down
                # must catch it here
                untracked='normal',
                # downstream code can handle non-annex/annex distinction
                # avoid expensive evaluation here
                annex=False,
                # we only need to inspect a subdataset's state in case
                # we want to drop it completely
                eval_subdataset_state='commit'
                if what in ('all', 'datasets') else 'no',
                # recursion is handled outside this function
                recursive=False,
                result_renderer='disabled',
                return_type='generator',
                on_failure='ignore'):
            state = res.get('state')
            if state == 'clean':
                # nothing to worry about, nothing to communicate
                continue
            elif state in ('modified', 'untracked'):
                yield dict(
                    res,
                    status='impossible',
                    action='drop',
                    message=f'cannot drop {state} content, save first',
                )
                # we are not aborting nevetheless. in normal conditions
                # the above result will stop processing, but if desired
                # we could go on
            else:
                lgr.debug(
                    'Status record not considered for drop '
                    'state inspection: %s', res)


def _pre_drop_checks(ds, repo, paths, what, reckless, is_annex):
    if not is_annex and reckless not in ('datasets', 'kill'):
        # we cannot drop content in non-annex repos, issue same
        # 'notneeded' as for git-file in annex repo
        for p in paths or [ds.path]:
            yield dict(
                action='drop',
                path=str(p),
                status='notneeded',
                message="no annex'ed content",
            )
        # continue, this is nothing fatal

    if not is_annex and what in ('allkeys', 'unwanted') \
            and not reckless == 'kill':
        # these drop modes are meaningless without an annex
        yield dict(
            action='drop',
            path=ds.path,
            status='notneeded',
            message="dataset with no annex",
            type='dataset',
        )
        # continue, this is nothing fatal


def _detect_unpushed_revs(repo, consider_managed_branches):
    """Check if all local branch states (and HEAD) are available at a remote

    There need not be a 1:1 correspondence. What is tested is whether
    each commit corresponding to a local branch tip (or HEAD), is also an
    ancestor of any remote branch. It is not required that there is a single
    remote that has all commits.

    This only uses the state of remotes known to the local remote state.
    No remote synchronization is performed.

    Parameters
    ----------
    repo: GitRepo
      Repository to evaluated
    consider_managed_branches: bool
      Whether to enable handling of managed branches.

    Returns
    -------
    list
      Names of local states/refs that are no available at a remote.
    """
    if consider_managed_branches:
        # consolidate corresponding branches to get reliable detection
        repo.localsync(managed_only=True)
    # we do not want to check this for any managed branches
    # that are not meant to be pushed without consolidation
    # or even at all (incl. git-annex, it can behave in complex ways)
    local_refs = [
        lb for lb in repo.get_branches()
        if not (not consider_managed_branches
            or lb == 'git-annex' or repo.is_managed_branch(lb))]
    if not repo.get_active_branch():
        # check for HEAD, in case we are on a detached HEAD
        local_refs.append('HEAD')
    # extend to tags?
    remote_refs = repo.get_remote_branches()

    unpushed_refs = [
        local_ref
        for local_ref in local_refs
        if not any(repo.is_ancestor(local_ref, remote_ref)
                   for remote_ref in remote_refs)
    ]
    return unpushed_refs


def _detect_nondead_annex_at_remotes(repo, annex_uuid):
    """Return list of remote names that know about a given (not-dead) annex

    This only uses the state of remotes known to the local remote state.
    No remote synchronization is performed.

    Parameters
    ----------
    repo: AnnexRepo or GitRepo
      Repository to evaluated
    annex_uuid: str
      UUID string of a particular annex

    Returns
    -------
    list
      Names of any matching remote, the local repository is indicated using
      a `None` label.
    """
    # build the refs for all remotes and local
    remotes_w_registration = []
    for remote in chain([''], repo.get_remotes()):
        refprefix = '{}{}git-annex:'.format(
            remote,
            '/' if remote else '',
        )
        uuid_known = False
        try:
            for line in repo.call_git_items_(
                    ['cat-file', '-p', refprefix + 'uuid.log']):
                if line.startswith(annex_uuid):
                    # use None to label the local repo
                    uuid_known = True
                    break
        except CommandError as e:
            CapturedException(e)
            # this is not a problem per-se, logged above, just continue
            continue
        if not uuid_known:
            # if an annex id is not even in the uuid.log, we can stop here
            # (for this remote)
            continue

        # annex is known, but maybe is declared dead already, must check
        # trust.log in addition
        try:
            for line in repo.call_git_items_(
                    ['cat-file', '-p', refprefix + 'trust.log']):
                columns = line.split()
                if columns[0] == annex_uuid:
                    # not known if dead
                    uuid_known = False if columns[1] == 'X' else True
                    break
        except CommandError as e:
            CapturedException(e)
            # this is not a problem per-se, logged above, just continue
            continue
        finally:
            if uuid_known:
                remotes_w_registration.append(remote or None)
    return(remotes_w_registration)


def _kill_dataset(ds):
    """This is a harsh internal helper: it will wipe out a dataset, no checks
    """
    # figure out whether we should be nice to a superdataset later on
    has_super = ds.get_superdataset(topmost=False, registered_only=True)
    # Close any possibly associated process etc with underlying repo.
    # Otherwise - rmtree could fail to remove e.g. under NFS which would
    # still have some files opened by them (thus having .nfs00000xxxx
    # files) forbidding rmdir to work in rmtree
    ds.close()
    rmtree(ds.path)
    # invalidate loaded ConfigManager -- datasets are singletons!!
    ds._cfg = None
    if has_super:
        # recreate an empty mountpoint to make Git happier
        ds.pathobj.mkdir(exist_ok=True)
    yield dict(
        # keep uninstall to please the gods of a distant past
        #action='drop',
        action='uninstall',
        path=ds.path,
        type='dataset',
        status='ok',
    )


def _drop_allkeys(ds, repo, force=False, jobs=None):
    """
    """
    assert not (repo.dot_git / 'annex').is_symlink(), \
        "Dropping from a symlinked annex is unsupported to prevent data-loss"

    cmd = ['drop', '--all']
    if force:
        cmd.append('--force')
    if jobs:
        cmd.extend(['--jobs', str(jobs)])

    try:
        yield from repo._call_annex_records_items_(cmd)
    except CommandError as e:
        # pick up the results captured so far and yield them
        # the error will be amongst them
        yield from e.kwargs.get('stdout_json', [])


def _drop_files(ds, repo, paths, force=False, jobs=None):
    """Helper to drop content in datasets.

    Parameters
    ----------
    repo : AnnexRepo
    paths : list
      for which files to drop content
    check : bool
      whether to instruct annex to perform minimum copy availability
      checks

    Yields
    ------
    dict
    """
    assert not (repo.dot_git / 'annex').is_symlink(), \
        "Dropping from a symlinked annex is unsupported to prevent data-loss"
    cmd = ['drop']
    if force:
        cmd.append('--force')
    if jobs:
        cmd.extend(['--jobs', str(jobs)])

    respath_by_status = {}
    try:
        yield from (
            _postproc_annexdrop_result(res, respath_by_status, ds)
            for res in repo._call_annex_records_items_(cmd, files=paths)
        )
    except CommandError as e:
        # pick up the results captured so far and yield them
        # the error will be amongst them
        yield from (
            _postproc_annexdrop_result(res, respath_by_status, ds)
            for res in e.kwargs.get('stdout_json', [])
        )
    # report on things requested that annex was silent about
    for r in results_from_annex_noinfo(
            ds, paths, respath_by_status,
            dir_fail_msg='could not drop some content in %s %s',
            noinfo_dir_msg='nothing to drop from %s',
            noinfo_file_msg="no annex'ed content"):
        r['action'] = 'drop'
        yield r


def _postproc_annexdrop_result(res, respath_by_status, ds, **kwargs):
    res = annexjson2result(
        # annex reports are always about files
        res, ds, type='file', **kwargs)
    success = success_status_map[res['status']]
    respath_by_status[success] = \
        respath_by_status.get(success, []) + [res['path']]
    if res["status"] == "error" and res["action"] == "drop":
        msg = res.get("message", None)
        if isinstance(msg, str) and "Use --force to" in msg:
            # Avoid confusing datalad-drop callers with git-annex-drop's
            # suggestion to use --force.
            # Just mention reckless itself, do not go into the details
            # of which mode. This is likely changing over time and
            # adjusting this replacement will be forgotten.
            res["message"] = msg.replace(
                "--force",
                "--reckless availability")
    return res

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