{
    "file_diffs": [
        {
            "old_file_content": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 noet:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"DataLad aims to expose (scientific) data available online as a unified data\ndistribution with the convenience of git-annex repositories as a backend.\"\"\"\n\n\n# Other imports are interspersed with lgr.debug to ease troubleshooting startup\n# delays etc.\n\n# If there is a bundled git, make sure GitPython uses it too:\nfrom datalad.cmd import GitRunner\nGitRunner._check_git_path()\nif GitRunner._GIT_PATH:\n    import os\n    os.environ['GIT_PYTHON_GIT_EXECUTABLE'] = \\\n        os.path.join(GitRunner._GIT_PATH, 'git')\n\nfrom .config import ConfigManager\ncfg = ConfigManager()\n\nfrom .log import lgr\nlgr.log(5, \"Instantiating ssh manager\")\nfrom .support.sshconnector import SSHManager\nssh_manager = SSHManager()\n\nimport atexit\natexit.register(ssh_manager.close, allow_fail=False)\natexit.register(lgr.log, 5, \"Exiting\")\n\nfrom .version import __version__\n\n\ndef test(package='datalad', **kwargs):\n    \"\"\"A helper to run datalad's tests.  Requires numpy and nose\n\n    See numpy.testing.Tester -- **kwargs are passed into the\n    Tester().test call\n    \"\"\"\n    try:\n        from numpy.testing import Tester\n        Tester(package=package).test(**kwargs)\n        # we don't have any benchmarks atm\n        # bench = Tester().bench\n    except ImportError:\n        raise RuntimeError('Need numpy >= 1.2 for datalad.tests().  Nothing is done')\ntest.__test__ = False\n\n# Following fixtures are necessary at the top level __init__ for fixtures which\n# would cover all **/tests and not just datalad/tests/\n\n# To store settings which setup_package changes and teardown_package should return\n_test_states = {\n    'loglevel': None,\n    'DATALAD_LOG_LEVEL': None,\n}\n\n\ndef setup_package():\n    import os\n\n    # To overcome pybuild overriding HOME but us possibly wanting our\n    # own HOME where we pre-setup git for testing (name, email)\n    if 'GIT_HOME' in os.environ:\n        os.environ['HOME'] = os.environ['GIT_HOME']\n\n    # To overcome pybuild by default defining http{,s}_proxy we would need\n    # to define them to e.g. empty value so it wouldn't bother touching them.\n    # But then haskell libraries do not digest empty value nicely, so we just\n    # pop them out from the environment\n    for ev in ('http_proxy', 'https_proxy'):\n        if ev in os.environ and not (os.environ[ev]):\n            lgr.debug(\"Removing %s from the environment since it is empty\", ev)\n            os.environ.pop(ev)\n\n    DATALAD_LOG_LEVEL = os.environ.get('DATALAD_LOG_LEVEL', None)\n    if DATALAD_LOG_LEVEL is None:\n        # very very silent.  Tests introspecting logs should use\n        # swallow_logs(new_level=...)\n        _test_states['loglevel'] = lgr.getEffectiveLevel()\n        lgr.setLevel(100)\n\n        # And we should also set it within environ so underlying commands also stay silent\n        _test_states['DATALAD_LOG_LEVEL'] = DATALAD_LOG_LEVEL\n        os.environ['DATALAD_LOG_LEVEL'] = '100'\n    else:\n        # We are not overriding them, since explicitly were asked to have some log level\n        _test_states['loglevel'] = None\n\n    # Set to non-interactive UI\n    from datalad.ui import ui\n    _test_states['ui_backend'] = ui.backend\n    # obtain() since that one consults for the default value\n    ui.set_backend(cfg.obtain('datalad.tests.ui.backend'))\n\n\ndef teardown_package():\n    import os\n    if os.environ.get('DATALAD_TESTS_NOTEARDOWN'):\n        return\n    from datalad.ui import ui\n    ui.set_backend(_test_states['ui_backend'])\n    if _test_states['loglevel'] is not None:\n        lgr.setLevel(_test_states['loglevel'])\n        if _test_states['DATALAD_LOG_LEVEL'] is None:\n            os.environ.pop('DATALAD_LOG_LEVEL')\n        else:\n            os.environ['DATALAD_LOG_LEVEL'] = _test_states['DATALAD_LOG_LEVEL']\n\n    from datalad.tests import _TEMP_PATHS_GENERATED\n    from datalad.tests.utils import rmtemp\n    if len(_TEMP_PATHS_GENERATED):\n        msg = \"Removing %d dirs/files: %s\" % (len(_TEMP_PATHS_GENERATED), ', '.join(_TEMP_PATHS_GENERATED))\n    else:\n        msg = \"Nothing to remove\"\n    lgr.debug(\"Teardown tests. \" + msg)\n    for path in _TEMP_PATHS_GENERATED:\n        rmtemp(path, ignore_errors=True)\n\n    lgr.debug(\"Printing versioning information collected so far\")\n    from datalad.support.external_versions import external_versions as ev\n    # request versioning for few others which we do not check at runtime\n    for m in ('git',):\n        try:  # Let's make sure to not blow up when we are almost done\n            ev[m]\n        except Exception:\n            pass\n    print(ev.dumps(query=True))\n\nlgr.log(5, \"Done importing main __init__\")\n",
            "new_file_content": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 noet:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"DataLad aims to expose (scientific) data available online as a unified data\ndistribution with the convenience of git-annex repositories as a backend.\"\"\"\n\n\n# Other imports are interspersed with lgr.debug to ease troubleshooting startup\n# delays etc.\n\n# If there is a bundled git, make sure GitPython uses it too:\nfrom datalad.cmd import GitRunner\nGitRunner._check_git_path()\nif GitRunner._GIT_PATH:\n    import os\n    os.environ['GIT_PYTHON_GIT_EXECUTABLE'] = \\\n        os.path.join(GitRunner._GIT_PATH, 'git')\n\nfrom .config import ConfigManager\ncfg = ConfigManager()\n\nfrom .log import lgr\nlgr.log(5, \"Instantiating ssh manager\")\nfrom .support.sshconnector import SSHManager\nssh_manager = SSHManager()\n\nimport atexit\natexit.register(ssh_manager.close, allow_fail=False)\natexit.register(lgr.log, 5, \"Exiting\")\n\nfrom .version import __version__\n\n\ndef test(package='datalad', **kwargs):\n    \"\"\"A helper to run datalad's tests.  Requires numpy and nose\n\n    See numpy.testing.Tester -- **kwargs are passed into the\n    Tester().test call\n    \"\"\"\n    try:\n        from numpy.testing import Tester\n        Tester(package=package).test(**kwargs)\n        # we don't have any benchmarks atm\n        # bench = Tester().bench\n    except ImportError:\n        raise RuntimeError('Need numpy >= 1.2 for datalad.tests().  Nothing is done')\ntest.__test__ = False\n\n# Following fixtures are necessary at the top level __init__ for fixtures which\n# would cover all **/tests and not just datalad/tests/\n\n# To store settings which setup_package changes and teardown_package should return\n_test_states = {\n    'loglevel': None,\n    'DATALAD_LOG_LEVEL': None,\n}\n\n\ndef setup_package():\n    import os\n\n    # To overcome pybuild overriding HOME but us possibly wanting our\n    # own HOME where we pre-setup git for testing (name, email)\n    if 'GIT_HOME' in os.environ:\n        os.environ['HOME'] = os.environ['GIT_HOME']\n\n    # To overcome pybuild by default defining http{,s}_proxy we would need\n    # to define them to e.g. empty value so it wouldn't bother touching them.\n    # But then haskell libraries do not digest empty value nicely, so we just\n    # pop them out from the environment\n    for ev in ('http_proxy', 'https_proxy'):\n        if ev in os.environ and not (os.environ[ev]):\n            lgr.debug(\"Removing %s from the environment since it is empty\", ev)\n            os.environ.pop(ev)\n\n    DATALAD_LOG_LEVEL = os.environ.get('DATALAD_LOG_LEVEL', None)\n    if DATALAD_LOG_LEVEL is None:\n        # very very silent.  Tests introspecting logs should use\n        # swallow_logs(new_level=...)\n        _test_states['loglevel'] = lgr.getEffectiveLevel()\n        lgr.setLevel(100)\n\n        # And we should also set it within environ so underlying commands also stay silent\n        _test_states['DATALAD_LOG_LEVEL'] = DATALAD_LOG_LEVEL\n        os.environ['DATALAD_LOG_LEVEL'] = '100'\n    else:\n        # We are not overriding them, since explicitly were asked to have some log level\n        _test_states['loglevel'] = None\n\n    # Set to non-interactive UI\n    from datalad.ui import ui\n    _test_states['ui_backend'] = ui.backend\n    # obtain() since that one consults for the default value\n    ui.set_backend(cfg.obtain('datalad.tests.ui.backend'))\n\n\ndef teardown_package():\n    import os\n    if os.environ.get('DATALAD_TESTS_NOTEARDOWN'):\n        return\n    from datalad.ui import ui\n    ui.set_backend(_test_states['ui_backend'])\n    if _test_states['loglevel'] is not None:\n        lgr.setLevel(_test_states['loglevel'])\n        if _test_states['DATALAD_LOG_LEVEL'] is None:\n            os.environ.pop('DATALAD_LOG_LEVEL')\n        else:\n            os.environ['DATALAD_LOG_LEVEL'] = _test_states['DATALAD_LOG_LEVEL']\n\n    from datalad.tests import _TEMP_PATHS_GENERATED\n    from datalad.tests.utils import rmtemp\n    if len(_TEMP_PATHS_GENERATED):\n        msg = \"Removing %d dirs/files: %s\" % (len(_TEMP_PATHS_GENERATED), ', '.join(_TEMP_PATHS_GENERATED))\n    else:\n        msg = \"Nothing to remove\"\n    lgr.debug(\"Teardown tests. \" + msg)\n    for path in _TEMP_PATHS_GENERATED:\n        rmtemp(path, ignore_errors=True)\n\n    lgr.debug(\"Printing versioning information collected so far\")\n    from datalad.support.external_versions import external_versions as ev\n    # request versioning for few others which we do not check at runtime\n    for m in ('git', 'system-ssh'):\n        try:  # Let's make sure to not blow up when we are almost done\n            ev[m]\n        except Exception:\n            pass\n    print(ev.dumps(query=True))\n\nlgr.log(5, \"Done importing main __init__\")\n",
            "header": {
                "file": {
                    "path": "datalad/__init__.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "7ff46eb71",
                "new_commit_hash": "ed6b2f8bd",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/datalad/__init__.py"
            },
            "plus_file": {
                "path": "b/datalad/__init__.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 125,
                            "length": 7
                        },
                        "new_range": {
                            "start": 125,
                            "length": 7
                        },
                        "section": "def teardown_package():"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    lgr.debug(\"Printing versioning information collected so far\")",
                                "type": "context"
                            },
                            {
                                "content": "    from datalad.support.external_versions import external_versions as ev",
                                "type": "context"
                            },
                            {
                                "content": "    # request versioning for few others which we do not check at runtime",
                                "type": "context"
                            },
                            {
                                "content": "    for m in ('git',):",
                                "type": "deleted"
                            },
                            {
                                "content": "    for m in ('git', 'system-ssh'):",
                                "type": "added"
                            },
                            {
                                "content": "        try:  # Let's make sure to not blow up when we are almost done",
                                "type": "context"
                            },
                            {
                                "content": "            ev[m]",
                                "type": "context"
                            },
                            {
                                "content": "        except Exception:",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/__init__.py",
                            "type": "function",
                            "name": "teardown_package",
                            "content": "def teardown_package():\n    import os\n    if os.environ.get('DATALAD_TESTS_NOTEARDOWN'):\n        return\n    from datalad.ui import ui\n    ui.set_backend(_test_states['ui_backend'])\n    if _test_states['loglevel'] is not None:\n        lgr.setLevel(_test_states['loglevel'])\n        if _test_states['DATALAD_LOG_LEVEL'] is None:\n            os.environ.pop('DATALAD_LOG_LEVEL')\n        else:\n            os.environ['DATALAD_LOG_LEVEL'] = _test_states['DATALAD_LOG_LEVEL']\n\n    from datalad.tests import _TEMP_PATHS_GENERATED\n    from datalad.tests.utils import rmtemp\n    if len(_TEMP_PATHS_GENERATED):\n        msg = \"Removing %d dirs/files: %s\" % (len(_TEMP_PATHS_GENERATED), ', '.join(_TEMP_PATHS_GENERATED))\n    else:\n        msg = \"Nothing to remove\"\n    lgr.debug(\"Teardown tests. \" + msg)\n    for path in _TEMP_PATHS_GENERATED:\n        rmtemp(path, ignore_errors=True)\n\n    lgr.debug(\"Printing versioning information collected so far\")\n    from datalad.support.external_versions import external_versions as ev\n    # request versioning for few others which we do not check at runtime\n    for m in ('git',):\n        try:  # Let's make sure to not blow up when we are almost done\n            ev[m]\n        except Exception:\n            pass\n    print(ev.dumps(query=True))",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 102,
                            "end_lineno": 133,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 noet:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Module to help maintain a registry of versions for external modules etc\n\"\"\"\nimport sys\nfrom os import linesep\nfrom six import string_types\nfrom six import binary_type\n\nfrom distutils.version import LooseVersion\n\nfrom datalad.dochelpers import exc_str\nfrom datalad.log import lgr\n# import version helper from config to have only one implementation\n# config needs this to avoid circular imports\nfrom datalad.config import get_git_version as __get_git_version\nfrom .exceptions import CommandError\n\n__all__ = ['UnknownVersion', 'ExternalVersions', 'external_versions']\n\n\n# To depict an unknown version, which can't be compared by mistake etc\nclass UnknownVersion:\n    \"\"\"For internal use\n    \"\"\"\n\n    def __str__(self):\n        return \"UNKNOWN\"\n\n    def __cmp__(self, other):\n        if other is self:\n            return 0\n        raise TypeError(\"UNKNOWN version is not comparable\")\n\n\n#\n# Custom handlers\n#\nfrom datalad.cmd import Runner\nfrom datalad.cmd import GitRunner\n_runner = Runner()\n_git_runner = GitRunner()\n\n\ndef _get_annex_version():\n    \"\"\"Return version of available git-annex\"\"\"\n    try:\n        return _runner.run('git annex version --raw'.split())[0]\n    except CommandError:\n        # fall back on method that could work with older installations\n        out, err = _runner.run(['git', 'annex', 'version'])\n        return out.split('\\n')[0].split(':')[1].strip()\n\n\ndef _get_git_version():\n    \"\"\"Return version of git we use (might be bundled)\"\"\"\n    return __get_git_version(_git_runner)\n\n\ndef _get_system_git_version():\n    \"\"\"Return version of git available system-wide\n\n    Might be different from the one we are using, which might be\n    bundled with git-annex\n    \"\"\"\n    return __get_git_version(_runner)\n\n\nclass ExternalVersions(object):\n    \"\"\"Helper to figure out/use versions of the externals (modules, cmdline tools, etc).\n\n    To avoid collision between names of python modules and command line tools,\n    prepend names for command line tools with `cmd:`.\n\n    It maintains a dictionary of `distuil.version.LooseVersion`s to make\n    comparisons easy. Note that even if version string conform the StrictVersion\n    \"standard\", LooseVersion will be used.  If version can't be deduced for the\n    external, `UnknownVersion()` is assigned.  If external is not present (can't\n    be imported, or custom check throws exception), None is returned without\n    storing it, so later call will re-evaluate fully.\n    \"\"\"\n\n    UNKNOWN = UnknownVersion()\n\n    CUSTOM = {\n        'cmd:annex': _get_annex_version,\n        'cmd:git': _get_git_version,\n        'cmd:system-git': _get_system_git_version,\n    }\n\n    def __init__(self):\n        self._versions = {}\n\n    @classmethod\n    def _deduce_version(klass, value):\n        version = None\n\n        # see if it is something containing a version\n        for attr in ('__version__', 'version'):\n            if hasattr(value, attr):\n                version = getattr(value, attr)\n                break\n\n        # assume that value is the version\n        if version is None:\n            version = value\n\n        # do type analysis\n        if isinstance(version, (tuple, list)):\n            #  Generate string representation\n            version = \".\".join(str(x) for x in version)\n        elif isinstance(version, binary_type):\n            version = version.decode()\n        elif isinstance(version, string_types):\n            pass\n        else:\n            version = None\n\n        if version:\n            return LooseVersion(version)\n        else:\n            return klass.UNKNOWN\n\n    def __getitem__(self, module):\n        # when ran straight in its source code -- fails to discover nipy's version.. TODO\n        #if module == 'nipy':\n        #    import pdb; pdb.set_trace()\n        if not isinstance(module, string_types):\n            modname = module.__name__\n        else:\n            modname = module\n            module = None\n\n        # Early returns None so we do not store prev result for  them\n        # and allow users to install things at run time, so later check\n        # doesn't pick it up from the _versions\n        if modname not in self._versions:\n            version = None   # by default -- not present\n            if modname in self.CUSTOM:\n                try:\n                    version = self.CUSTOM[modname]()\n                    version = self._deduce_version(version)\n                except Exception as exc:\n                    lgr.debug(\"Failed to deduce version of %s due to %s\"\n                              % (modname, exc_str(exc)))\n                    return None\n            else:\n                if module is None:\n                    if modname not in sys.modules:\n                        try:\n                            module = __import__(modname)\n                        except ImportError:\n                            lgr.debug(\"Module %s seems to be not present\" % modname)\n                            return None\n                    else:\n                        module = sys.modules[modname]\n                if module:\n                    version = self._deduce_version(module)\n            self._versions[modname] = version\n\n        return self._versions.get(modname, self.UNKNOWN)\n\n    def keys(self):\n        \"\"\"Return names of the known modules\"\"\"\n        return self._versions.keys()\n\n    def __contains__(self, item):\n        return item in self._versions\n\n    @property\n    def versions(self):\n        \"\"\"Return dictionary (copy) of versions\"\"\"\n        return self._versions.copy()\n\n    def dumps(self, indent=False, preamble=\"Versions:\", query=False):\n        \"\"\"Return listing of versions as a string\n\n        Parameters\n        ----------\n        indent: bool or str, optional\n          If set would instruct on how to indent entries (if just True, ' '\n          is used). Otherwise returned in a single line\n        preamble: str, optional\n          What preamble to the listing to use\n        query : bool, optional\n          To query for versions of all \"registered\" custom externals, so to\n          get those which weren't queried for yet\n        \"\"\"\n        if query:\n            [self[k] for k in self.CUSTOM]\n        if indent and (indent is True):\n            indent = ' '\n        items = [\"%s=%s\" % (k, self._versions[k]) for k in sorted(self._versions)]\n        out = \"%s\" % preamble\n        if indent:\n            out += (linesep + indent).join([''] + items) + linesep\n        else:\n            out += \" \" + ' '.join(items)\n        return out\n\n\nexternal_versions = ExternalVersions()\n",
            "new_file_content": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 noet:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Module to help maintain a registry of versions for external modules etc\n\"\"\"\nimport sys\nfrom os import linesep\nfrom six import string_types\nfrom six import binary_type\n\nfrom distutils.version import LooseVersion\n\nfrom datalad.dochelpers import exc_str\nfrom datalad.log import lgr\n# import version helper from config to have only one implementation\n# config needs this to avoid circular imports\nfrom datalad.config import get_git_version as __get_git_version\nfrom .exceptions import CommandError\n\n__all__ = ['UnknownVersion', 'ExternalVersions', 'external_versions']\n\n\n# To depict an unknown version, which can't be compared by mistake etc\nclass UnknownVersion:\n    \"\"\"For internal use\n    \"\"\"\n\n    def __str__(self):\n        return \"UNKNOWN\"\n\n    def __cmp__(self, other):\n        if other is self:\n            return 0\n        raise TypeError(\"UNKNOWN version is not comparable\")\n\n\n#\n# Custom handlers\n#\nfrom datalad.cmd import Runner\nfrom datalad.cmd import GitRunner\n_runner = Runner()\n_git_runner = GitRunner()\n\n\ndef _get_annex_version():\n    \"\"\"Return version of available git-annex\"\"\"\n    try:\n        return _runner.run('git annex version --raw'.split())[0]\n    except CommandError:\n        # fall back on method that could work with older installations\n        out, err = _runner.run(['git', 'annex', 'version'])\n        return out.split('\\n')[0].split(':')[1].strip()\n\n\ndef _get_git_version():\n    \"\"\"Return version of git we use (might be bundled)\"\"\"\n    return __get_git_version(_git_runner)\n\n\ndef _get_system_git_version():\n    \"\"\"Return version of git available system-wide\n\n    Might be different from the one we are using, which might be\n    bundled with git-annex\n    \"\"\"\n    return __get_git_version(_runner)\n\n\ndef _get_system_ssh_version():\n    \"\"\"Return version of ssh available system-wide\n\n    Annex prior 20170302 was using bundled version, but now would use system one\n    if installed\n    \"\"\"\n    try:\n        out, err = _runner.run('ssh -V'.split())\n        # apparently spits out to err but I wouldn't trust it blindly\n        if err.startswith('OpenSSH'):\n            out = err\n        assert out.startswith('OpenSSH')  # that is the only one we care about atm\n        return out.split(' ', 1)[0].rstrip(',.').split('_')[1]\n    except CommandError as exc:\n        lgr.warn(\"Could not determine version of ssh available: %s\", exc_str(exc))\n        return None\n\n\nclass ExternalVersions(object):\n    \"\"\"Helper to figure out/use versions of the externals (modules, cmdline tools, etc).\n\n    To avoid collision between names of python modules and command line tools,\n    prepend names for command line tools with `cmd:`.\n\n    It maintains a dictionary of `distuil.version.LooseVersion`s to make\n    comparisons easy. Note that even if version string conform the StrictVersion\n    \"standard\", LooseVersion will be used.  If version can't be deduced for the\n    external, `UnknownVersion()` is assigned.  If external is not present (can't\n    be imported, or custom check throws exception), None is returned without\n    storing it, so later call will re-evaluate fully.\n    \"\"\"\n\n    UNKNOWN = UnknownVersion()\n\n    CUSTOM = {\n        'cmd:annex': _get_annex_version,\n        'cmd:git': _get_git_version,\n        'cmd:system-git': _get_system_git_version,\n        'cmd:system-ssh': _get_system_ssh_version,\n    }\n\n    def __init__(self):\n        self._versions = {}\n\n    @classmethod\n    def _deduce_version(klass, value):\n        version = None\n\n        # see if it is something containing a version\n        for attr in ('__version__', 'version'):\n            if hasattr(value, attr):\n                version = getattr(value, attr)\n                break\n\n        # assume that value is the version\n        if version is None:\n            version = value\n\n        # do type analysis\n        if isinstance(version, (tuple, list)):\n            #  Generate string representation\n            version = \".\".join(str(x) for x in version)\n        elif isinstance(version, binary_type):\n            version = version.decode()\n        elif isinstance(version, string_types):\n            pass\n        else:\n            version = None\n\n        if version:\n            return LooseVersion(version)\n        else:\n            return klass.UNKNOWN\n\n    def __getitem__(self, module):\n        # when ran straight in its source code -- fails to discover nipy's version.. TODO\n        #if module == 'nipy':\n        #    import pdb; pdb.set_trace()\n        if not isinstance(module, string_types):\n            modname = module.__name__\n        else:\n            modname = module\n            module = None\n\n        # Early returns None so we do not store prev result for  them\n        # and allow users to install things at run time, so later check\n        # doesn't pick it up from the _versions\n        if modname not in self._versions:\n            version = None   # by default -- not present\n            if modname in self.CUSTOM:\n                try:\n                    version = self.CUSTOM[modname]()\n                    version = self._deduce_version(version)\n                except Exception as exc:\n                    lgr.debug(\"Failed to deduce version of %s due to %s\"\n                              % (modname, exc_str(exc)))\n                    return None\n            else:\n                if module is None:\n                    if modname not in sys.modules:\n                        try:\n                            module = __import__(modname)\n                        except ImportError:\n                            lgr.debug(\"Module %s seems to be not present\" % modname)\n                            return None\n                    else:\n                        module = sys.modules[modname]\n                if module:\n                    version = self._deduce_version(module)\n            self._versions[modname] = version\n\n        return self._versions.get(modname, self.UNKNOWN)\n\n    def keys(self):\n        \"\"\"Return names of the known modules\"\"\"\n        return self._versions.keys()\n\n    def __contains__(self, item):\n        return item in self._versions\n\n    @property\n    def versions(self):\n        \"\"\"Return dictionary (copy) of versions\"\"\"\n        return self._versions.copy()\n\n    def dumps(self, indent=False, preamble=\"Versions:\", query=False):\n        \"\"\"Return listing of versions as a string\n\n        Parameters\n        ----------\n        indent: bool or str, optional\n          If set would instruct on how to indent entries (if just True, ' '\n          is used). Otherwise returned in a single line\n        preamble: str, optional\n          What preamble to the listing to use\n        query : bool, optional\n          To query for versions of all \"registered\" custom externals, so to\n          get those which weren't queried for yet\n        \"\"\"\n        if query:\n            [self[k] for k in self.CUSTOM]\n        if indent and (indent is True):\n            indent = ' '\n        items = [\"%s=%s\" % (k, self._versions[k]) for k in sorted(self._versions)]\n        out = \"%s\" % preamble\n        if indent:\n            out += (linesep + indent).join([''] + items) + linesep\n        else:\n            out += \" \" + ' '.join(items)\n        return out\n\n\nexternal_versions = ExternalVersions()\n",
            "header": {
                "file": {
                    "path": "datalad/support/external_versions.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "601d8b712",
                "new_commit_hash": "b4dca7f07",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/datalad/support/external_versions.py"
            },
            "plus_file": {
                "path": "b/datalad/support/external_versions.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 72,
                            "length": 6
                        },
                        "new_range": {
                            "start": 72,
                            "length": 24
                        },
                        "section": "def _get_system_git_version():"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    return __get_git_version(_runner)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "def _get_system_ssh_version():",
                                "type": "added"
                            },
                            {
                                "content": "    \"\"\"Return version of ssh available system-wide",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    Annex prior 20170302 was using bundled version, but now would use system one",
                                "type": "added"
                            },
                            {
                                "content": "    if installed",
                                "type": "added"
                            },
                            {
                                "content": "    \"\"\"",
                                "type": "added"
                            },
                            {
                                "content": "    try:",
                                "type": "added"
                            },
                            {
                                "content": "        out, err = _runner.run('ssh -V'.split())",
                                "type": "added"
                            },
                            {
                                "content": "        # apparently spits out to err but I wouldn't trust it blindly",
                                "type": "added"
                            },
                            {
                                "content": "        if err.startswith('OpenSSH'):",
                                "type": "added"
                            },
                            {
                                "content": "            out = err",
                                "type": "added"
                            },
                            {
                                "content": "        assert out.startswith('OpenSSH')  # that is the only one we care about atm",
                                "type": "added"
                            },
                            {
                                "content": "        return out.split(' ', 1)[0].rstrip(',.').split('_')[1]",
                                "type": "added"
                            },
                            {
                                "content": "    except CommandError as exc:",
                                "type": "added"
                            },
                            {
                                "content": "        lgr.warn(\"Could not determine version of ssh available: %s\", exc_str(exc))",
                                "type": "added"
                            },
                            {
                                "content": "        return None",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "class ExternalVersions(object):",
                                "type": "context"
                            },
                            {
                                "content": "    \"\"\"Helper to figure out/use versions of the externals (modules, cmdline tools, etc).",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "datalad/support/external_versions.py",
                            "type": "function",
                            "name": "_get_system_ssh_version",
                            "content": "def _get_system_ssh_version():\n    \"\"\"Return version of ssh available system-wide\n\n    Annex prior 20170302 was using bundled version, but now would use system one\n    if installed\n    \"\"\"\n    try:\n        out, err = _runner.run('ssh -V'.split())\n        # apparently spits out to err but I wouldn't trust it blindly\n        if err.startswith('OpenSSH'):\n            out = err\n        assert out.startswith('OpenSSH')  # that is the only one we care about atm\n        return out.split(' ', 1)[0].rstrip(',.').split('_')[1]\n    except CommandError as exc:\n        lgr.warn(\"Could not determine version of ssh available: %s\", exc_str(exc))\n        return None",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 75,
                            "end_lineno": 90,
                            "parent": null
                        }
                    ],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 92,
                            "length": 6
                        },
                        "new_range": {
                            "start": 110,
                            "length": 7
                        },
                        "section": "class ExternalVersions(object):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        'cmd:annex': _get_annex_version,",
                                "type": "context"
                            },
                            {
                                "content": "        'cmd:git': _get_git_version,",
                                "type": "context"
                            },
                            {
                                "content": "        'cmd:system-git': _get_system_git_version,",
                                "type": "context"
                            },
                            {
                                "content": "        'cmd:system-ssh': _get_system_ssh_version,",
                                "type": "added"
                            },
                            {
                                "content": "    }",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def __init__(self):",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/support/external_versions.py",
                            "type": "class",
                            "name": "ExternalVersions",
                            "content": "class ExternalVersions(object):\n    \"\"\"Helper to figure out/use versions of the externals (modules, cmdline tools, etc).\n\n    To avoid collision between names of python modules and command line tools,\n    prepend names for command line tools with `cmd:`.\n\n    It maintains a dictionary of `distuil.version.LooseVersion`s to make\n    comparisons easy. Note that even if version string conform the StrictVersion\n    \"standard\", LooseVersion will be used.  If version can't be deduced for the\n    external, `UnknownVersion()` is assigned.  If external is not present (can't\n    be imported, or custom check throws exception), None is returned without\n    storing it, so later call will re-evaluate fully.\n    \"\"\"\n\n    UNKNOWN = UnknownVersion()\n\n    CUSTOM = {\n        'cmd:annex': _get_annex_version,\n        'cmd:git': _get_git_version,\n        'cmd:system-git': _get_system_git_version,\n        'cmd:system-ssh': _get_system_ssh_version,\n    }\n\n    def __init__(self):\n        self._versions = {}\n\n    @classmethod\n    def _deduce_version(klass, value):\n        version = None\n\n        # see if it is something containing a version\n        for attr in ('__version__', 'version'):\n            if hasattr(value, attr):\n                version = getattr(value, attr)\n                break\n\n        # assume that value is the version\n        if version is None:\n            version = value\n\n        # do type analysis\n        if isinstance(version, (tuple, list)):\n            #  Generate string representation\n            version = \".\".join(str(x) for x in version)\n        elif isinstance(version, binary_type):\n            version = version.decode()\n        elif isinstance(version, string_types):\n            pass\n        else:\n            version = None\n\n        if version:\n            return LooseVersion(version)\n        else:\n            return klass.UNKNOWN\n\n    def __getitem__(self, module):\n        # when ran straight in its source code -- fails to discover nipy's version.. TODO\n        #if module == 'nipy':\n        #    import pdb; pdb.set_trace()\n        if not isinstance(module, string_types):\n            modname = module.__name__\n        else:\n            modname = module\n            module = None\n\n        # Early returns None so we do not store prev result for  them\n        # and allow users to install things at run time, so later check\n        # doesn't pick it up from the _versions\n        if modname not in self._versions:\n            version = None   # by default -- not present\n            if modname in self.CUSTOM:\n                try:\n                    version = self.CUSTOM[modname]()\n                    version = self._deduce_version(version)\n                except Exception as exc:\n                    lgr.debug(\"Failed to deduce version of %s due to %s\"\n                              % (modname, exc_str(exc)))\n                    return None\n            else:\n                if module is None:\n                    if modname not in sys.modules:\n                        try:\n                            module = __import__(modname)\n                        except ImportError:\n                            lgr.debug(\"Module %s seems to be not present\" % modname)\n                            return None\n                    else:\n                        module = sys.modules[modname]\n                if module:\n                    version = self._deduce_version(module)\n            self._versions[modname] = version\n\n        return self._versions.get(modname, self.UNKNOWN)\n\n    def keys(self):\n        \"\"\"Return names of the known modules\"\"\"\n        return self._versions.keys()\n\n    def __contains__(self, item):\n        return item in self._versions\n\n    @property\n    def versions(self):\n        \"\"\"Return dictionary (copy) of versions\"\"\"\n        return self._versions.copy()\n\n    def dumps(self, indent=False, preamble=\"Versions:\", query=False):\n        \"\"\"Return listing of versions as a string\n\n        Parameters\n        ----------\n        indent: bool or str, optional\n          If set would instruct on how to indent entries (if just True, ' '\n          is used). Otherwise returned in a single line\n        preamble: str, optional\n          What preamble to the listing to use\n        query : bool, optional\n          To query for versions of all \"registered\" custom externals, so to\n          get those which weren't queried for yet\n        \"\"\"\n        if query:\n            [self[k] for k in self.CUSTOM]\n        if indent and (indent is True):\n            indent = ' '\n        items = [\"%s=%s\" % (k, self._versions[k]) for k in sorted(self._versions)]\n        out = \"%s\" % preamble\n        if indent:\n            out += (linesep + indent).join([''] + items) + linesep\n        else:\n            out += \" \" + ' '.join(items)\n        return out",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 93,
                            "end_lineno": 224,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 noet:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\nfrom os import linesep\n\nfrom ...version import __version__\nfrom ..external_versions import ExternalVersions, LooseVersion\nfrom ..exceptions import CommandError\nfrom ..exceptions import OutdatedExternalDependency\nfrom ...support.annexrepo import AnnexRepo\n\nfrom mock import patch\nfrom nose.tools import assert_true, assert_false\nfrom nose.tools import assert_equal, assert_greater_equal, assert_greater\nfrom nose.tools import assert_raises\nfrom nose import SkipTest\nfrom six import PY3\n\nif PY3:\n    # just to ease testing\n    def cmp(a, b):\n        return (a > b) - (a < b)\n\n\ndef test_external_versions_basic():\n    ev = ExternalVersions()\n    our_module = 'datalad'\n    assert_equal(ev.versions, {})\n    assert_equal(ev[our_module], __version__)\n    # and it could be compared\n    assert_greater_equal(ev[our_module], __version__)\n    assert_greater(ev[our_module], '0.1')\n    assert_equal(list(ev.keys()), [our_module])\n    assert_true(our_module in ev)\n    assert_false('unknown' in ev)\n\n    # all are LooseVersions now\n    assert_true(isinstance(ev[our_module], LooseVersion))\n    version_str = __version__\n    assert_equal(ev.dumps(), \"Versions: %s=%s\" % (our_module, version_str))\n\n    # For non-existing one we get None\n    assert_equal(ev['custom__nonexisting'], None)\n    # and nothing gets added to _versions for nonexisting\n    assert_equal(set(ev.versions.keys()), {our_module})\n\n    # but if it is a module without version, we get it set to UNKNOWN\n    assert_equal(ev['os'], ev.UNKNOWN)\n    # And get a record on that inside\n    assert_equal(ev.versions.get('os'), ev.UNKNOWN)\n    # And that thing is \"True\", i.e. present\n    assert(ev['os'])\n    # but not comparable with anything besides itself (was above)\n    assert_raises(TypeError, cmp, ev['os'], '0')\n    assert_raises(TypeError, assert_greater, ev['os'], '0')\n\n    return\n    # Code below is from original duecredit, and we don't care about\n    # testing this one\n    # And we can get versions based on modules themselves\n    from datalad.tests import mod\n    assert_equal(ev[mod], mod.__version__)\n\n    # Check that we can get a copy of the versions\n    versions_dict = ev.versions\n    versions_dict[our_module] = \"0.0.1\"\n    assert_equal(versions_dict[our_module], \"0.0.1\")\n    assert_equal(ev[our_module], __version__)\n\n\ndef test_external_versions_unknown():\n    assert_equal(str(ExternalVersions.UNKNOWN), 'UNKNOWN')\n\n\ndef _test_external(ev, modname):\n    try:\n        exec (\"import %s\" % modname, globals(), locals())\n    except ImportError:\n        raise SkipTest(\"External %s not present\" % modname)\n    except Exception as e:\n        raise SkipTest(\"External %s fails to import: %s\" % (modname, e))\n    assert (ev[modname] is not ev.UNKNOWN)\n    assert_greater(ev[modname], '0.0.1')\n    assert_greater('1000000.0', ev[modname])  # unlikely in our lifetimes\n\n\ndef test_external_versions_popular_packages():\n    ev = ExternalVersions()\n\n    for modname in ('scipy', 'numpy', 'mvpa2', 'sklearn', 'statsmodels', 'pandas',\n                    'matplotlib', 'psychopy'):\n        yield _test_external, ev, modname\n\n    # more of a smoke test\n    assert_false(linesep in ev.dumps())\n    assert_true(ev.dumps(indent=True).endswith(linesep))\n\n\ndef test_custom_versions():\n    ev = ExternalVersions()\n    assert(ev['cmd:annex'] > '6.20160101')  # annex must be present and recentish\n    assert_equal(set(ev.versions.keys()), {'cmd:annex'})\n    # some older git version don't support files to be passed to\n    # `commit` call under some conditions and this will lead to diverse\n    # errors\n    assert(ev['cmd:git'] > '2.0')  # git must be present and recentish\n    assert(isinstance(ev['cmd:git'], LooseVersion))\n    assert_equal(set(ev.versions.keys()), {'cmd:annex', 'cmd:git'})\n\n    # and there is also a version of system-wide installed git, which might\n    # differ from cmd:git but should be at least good old 1.7\n    assert(ev['cmd:system-git'] > '1.7')\n\n    ev.CUSTOM = {'bogus': lambda: 1 / 0}\n    assert_equal(ev['bogus'], None)\n    assert_equal(set(ev.versions), {'cmd:annex', 'cmd:git', 'cmd:system-git'})\n\n\ndef test_ancient_annex():\n\n    class _runner(object):\n        def run(self, cmd):\n            if '--raw' in cmd:\n                raise CommandError\n            return \"git-annex version: 0.1\", \"\"\n\n    ev = ExternalVersions()\n    with patch('datalad.support.external_versions._runner', _runner()):\n        assert_equal(ev['cmd:annex'], '0.1')\n\n\ndef _test_annex_version_comparison(v, cmp_):\n    class _runner(object):\n        def run(self, cmd):\n            return v, \"\"\n\n    ev = ExternalVersions()\n    with patch('datalad.support.external_versions._runner', _runner()), \\\n         patch('datalad.support.annexrepo.external_versions',\n               ExternalVersions()):\n        ev['cmd:annex'] < AnnexRepo.GIT_ANNEX_MIN_VERSION\n        if cmp_ in (1, 0):\n            AnnexRepo._check_git_annex_version()\n            if cmp_ == 0:\n                assert_equal(AnnexRepo.git_annex_version, v)\n        elif cmp == -1:\n            with assert_raises(OutdatedExternalDependency):\n                AnnexRepo._check_git_annex_version()\n\n\ndef test_annex_version_comparison():\n    # see https://github.com/datalad/datalad/issues/1128\n    for cmp_, base in [(-1, '6.2011'), (1, \"2100.0\")]:\n        # there could be differing versions of a version\n        #   release, snapshot, neurodebian build of a snapshot\n        for v in base, base + '-g0a34f08', base + '+gitg9f179ae-1~ndall+1':\n            # they all must be comparable to our specification of min version\n            yield _test_annex_version_comparison, v, cmp_\n    yield _test_annex_version_comparison, str(AnnexRepo.GIT_ANNEX_MIN_VERSION), 0\n\n\ndef _test_list_tuple(thing):\n    version = ExternalVersions._deduce_version(thing)\n    assert_greater(version, '0.0.1')\n    assert_greater('0.2', version)\n    assert_equal('0.1', version)\n    assert_equal(version, '0.1')\n\n\ndef test_list_tuple():\n\n    class thing_with_tuple_version:\n        __version__ = (0, 1)\n\n    class thing_with_list_version:\n        __version__ = [0, 1]\n\n    for v in thing_with_list_version, thing_with_tuple_version, '0.1', (0, 1), [0, 1]:\n        yield _test_list_tuple, v\n",
            "new_file_content": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 noet:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\nfrom os import linesep\n\nfrom ...version import __version__\nfrom ..external_versions import ExternalVersions, LooseVersion\nfrom ..exceptions import CommandError\nfrom ..exceptions import OutdatedExternalDependency\nfrom ...support.annexrepo import AnnexRepo\n\nfrom mock import patch\nfrom nose.tools import assert_true, assert_false\nfrom nose.tools import assert_equal, assert_greater_equal, assert_greater\nfrom nose.tools import assert_raises\nfrom nose import SkipTest\nfrom six import PY3\n\nif PY3:\n    # just to ease testing\n    def cmp(a, b):\n        return (a > b) - (a < b)\n\n\ndef test_external_versions_basic():\n    ev = ExternalVersions()\n    our_module = 'datalad'\n    assert_equal(ev.versions, {})\n    assert_equal(ev[our_module], __version__)\n    # and it could be compared\n    assert_greater_equal(ev[our_module], __version__)\n    assert_greater(ev[our_module], '0.1')\n    assert_equal(list(ev.keys()), [our_module])\n    assert_true(our_module in ev)\n    assert_false('unknown' in ev)\n\n    # all are LooseVersions now\n    assert_true(isinstance(ev[our_module], LooseVersion))\n    version_str = __version__\n    assert_equal(ev.dumps(), \"Versions: %s=%s\" % (our_module, version_str))\n\n    # For non-existing one we get None\n    assert_equal(ev['custom__nonexisting'], None)\n    # and nothing gets added to _versions for nonexisting\n    assert_equal(set(ev.versions.keys()), {our_module})\n\n    # but if it is a module without version, we get it set to UNKNOWN\n    assert_equal(ev['os'], ev.UNKNOWN)\n    # And get a record on that inside\n    assert_equal(ev.versions.get('os'), ev.UNKNOWN)\n    # And that thing is \"True\", i.e. present\n    assert(ev['os'])\n    # but not comparable with anything besides itself (was above)\n    assert_raises(TypeError, cmp, ev['os'], '0')\n    assert_raises(TypeError, assert_greater, ev['os'], '0')\n\n    return\n    # Code below is from original duecredit, and we don't care about\n    # testing this one\n    # And we can get versions based on modules themselves\n    from datalad.tests import mod\n    assert_equal(ev[mod], mod.__version__)\n\n    # Check that we can get a copy of the versions\n    versions_dict = ev.versions\n    versions_dict[our_module] = \"0.0.1\"\n    assert_equal(versions_dict[our_module], \"0.0.1\")\n    assert_equal(ev[our_module], __version__)\n\n\ndef test_external_versions_unknown():\n    assert_equal(str(ExternalVersions.UNKNOWN), 'UNKNOWN')\n\n\ndef _test_external(ev, modname):\n    try:\n        exec (\"import %s\" % modname, globals(), locals())\n    except ImportError:\n        raise SkipTest(\"External %s not present\" % modname)\n    except Exception as e:\n        raise SkipTest(\"External %s fails to import: %s\" % (modname, e))\n    assert (ev[modname] is not ev.UNKNOWN)\n    assert_greater(ev[modname], '0.0.1')\n    assert_greater('1000000.0', ev[modname])  # unlikely in our lifetimes\n\n\ndef test_external_versions_popular_packages():\n    ev = ExternalVersions()\n\n    for modname in ('scipy', 'numpy', 'mvpa2', 'sklearn', 'statsmodels', 'pandas',\n                    'matplotlib', 'psychopy'):\n        yield _test_external, ev, modname\n\n    # more of a smoke test\n    assert_false(linesep in ev.dumps())\n    assert_true(ev.dumps(indent=True).endswith(linesep))\n\n\ndef test_custom_versions():\n    ev = ExternalVersions()\n    assert(ev['cmd:annex'] > '6.20160101')  # annex must be present and recentish\n    assert_equal(set(ev.versions.keys()), {'cmd:annex'})\n    # some older git version don't support files to be passed to\n    # `commit` call under some conditions and this will lead to diverse\n    # errors\n    assert(ev['cmd:git'] > '2.0')  # git must be present and recentish\n    assert(isinstance(ev['cmd:git'], LooseVersion))\n    assert_equal(set(ev.versions.keys()), {'cmd:annex', 'cmd:git'})\n\n    # and there is also a version of system-wide installed git, which might\n    # differ from cmd:git but should be at least good old 1.7\n    assert(ev['cmd:system-git'] > '1.7')\n\n    ev.CUSTOM = {'bogus': lambda: 1 / 0}\n    assert_equal(ev['bogus'], None)\n    assert_equal(set(ev.versions), {'cmd:annex', 'cmd:git', 'cmd:system-git'})\n\n\ndef test_ancient_annex():\n\n    class _runner(object):\n        def run(self, cmd):\n            if '--raw' in cmd:\n                raise CommandError\n            return \"git-annex version: 0.1\", \"\"\n\n    ev = ExternalVersions()\n    with patch('datalad.support.external_versions._runner', _runner()):\n        assert_equal(ev['cmd:annex'], '0.1')\n\n\ndef _test_annex_version_comparison(v, cmp_):\n    class _runner(object):\n        def run(self, cmd):\n            return v, \"\"\n\n    ev = ExternalVersions()\n    with patch('datalad.support.external_versions._runner', _runner()), \\\n         patch('datalad.support.annexrepo.external_versions',\n               ExternalVersions()):\n        ev['cmd:annex'] < AnnexRepo.GIT_ANNEX_MIN_VERSION\n        if cmp_ in (1, 0):\n            AnnexRepo._check_git_annex_version()\n            if cmp_ == 0:\n                assert_equal(AnnexRepo.git_annex_version, v)\n        elif cmp == -1:\n            with assert_raises(OutdatedExternalDependency):\n                AnnexRepo._check_git_annex_version()\n\n\ndef test_annex_version_comparison():\n    # see https://github.com/datalad/datalad/issues/1128\n    for cmp_, base in [(-1, '6.2011'), (1, \"2100.0\")]:\n        # there could be differing versions of a version\n        #   release, snapshot, neurodebian build of a snapshot\n        for v in base, base + '-g0a34f08', base + '+gitg9f179ae-1~ndall+1':\n            # they all must be comparable to our specification of min version\n            yield _test_annex_version_comparison, v, cmp_\n    yield _test_annex_version_comparison, str(AnnexRepo.GIT_ANNEX_MIN_VERSION), 0\n\n\ndef _test_list_tuple(thing):\n    version = ExternalVersions._deduce_version(thing)\n    assert_greater(version, '0.0.1')\n    assert_greater('0.2', version)\n    assert_equal('0.1', version)\n    assert_equal(version, '0.1')\n\n\ndef test_list_tuple():\n\n    class thing_with_tuple_version:\n        __version__ = (0, 1)\n\n    class thing_with_list_version:\n        __version__ = [0, 1]\n\n    for v in thing_with_list_version, thing_with_tuple_version, '0.1', (0, 1), [0, 1]:\n        yield _test_list_tuple, v\n\n\ndef test_system_ssh_version():\n    ev = ExternalVersions()\n    assert ev['cmd:system-ssh']  # usually we have some available at boxes we test\n\n    for s, v in [\n        ('OpenSSH_7.4p1 Debian-6, OpenSSL 1.0.2k  26 Jan 2017', '7.4p1'),\n    ]:\n        ev = ExternalVersions()\n        # TODO: figure out leaner way\n        class _runner(object):\n            def run(self, cmd):\n                return \"\", s\n        with patch('datalad.support.external_versions._runner', _runner()):\n            assert_equal(ev['cmd:system-ssh'], v)\n",
            "header": {
                "file": {
                    "path": "datalad/support/tests/test_external_versions.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "974a3e2da",
                "new_commit_hash": "a31f8c071",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/datalad/support/tests/test_external_versions.py"
            },
            "plus_file": {
                "path": "b/datalad/support/tests/test_external_versions.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 183,
                            "length": 3
                        },
                        "new_range": {
                            "start": 183,
                            "length": 19
                        },
                        "section": "def test_list_tuple():"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    for v in thing_with_list_version, thing_with_tuple_version, '0.1', (0, 1), [0, 1]:",
                                "type": "context"
                            },
                            {
                                "content": "        yield _test_list_tuple, v",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "def test_system_ssh_version():",
                                "type": "added"
                            },
                            {
                                "content": "    ev = ExternalVersions()",
                                "type": "added"
                            },
                            {
                                "content": "    assert ev['cmd:system-ssh']  # usually we have some available at boxes we test",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    for s, v in [",
                                "type": "added"
                            },
                            {
                                "content": "        ('OpenSSH_7.4p1 Debian-6, OpenSSL 1.0.2k  26 Jan 2017', '7.4p1'),",
                                "type": "added"
                            },
                            {
                                "content": "    ]:",
                                "type": "added"
                            },
                            {
                                "content": "        ev = ExternalVersions()",
                                "type": "added"
                            },
                            {
                                "content": "        # TODO: figure out leaner way",
                                "type": "added"
                            },
                            {
                                "content": "        class _runner(object):",
                                "type": "added"
                            },
                            {
                                "content": "            def run(self, cmd):",
                                "type": "added"
                            },
                            {
                                "content": "                return \"\", s",
                                "type": "added"
                            },
                            {
                                "content": "        with patch('datalad.support.external_versions._runner', _runner()):",
                                "type": "added"
                            },
                            {
                                "content": "            assert_equal(ev['cmd:system-ssh'], v)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "datalad/support/tests/test_external_versions.py",
                            "type": "function",
                            "name": "test_system_ssh_version",
                            "content": "def test_system_ssh_version():\n    ev = ExternalVersions()\n    assert ev['cmd:system-ssh']  # usually we have some available at boxes we test\n\n    for s, v in [\n        ('OpenSSH_7.4p1 Debian-6, OpenSSL 1.0.2k  26 Jan 2017', '7.4p1'),\n    ]:\n        ev = ExternalVersions()\n        # TODO: figure out leaner way\n        class _runner(object):\n            def run(self, cmd):\n                return \"\", s\n        with patch('datalad.support.external_versions._runner', _runner()):\n            assert_equal(ev['cmd:system-ssh'], v)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 188,
                            "end_lineno": 201,
                            "parent": null
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "751902c5dddce92a1ae5d115c62d96f4498c730c^",
    "new_commit_hash": "751902c5dddce92a1ae5d115c62d96f4498c730c",
    "commit_message": "ENH: collect system-ssh version",
    "commit_date": "2017-04-06T12:15:11-04:00",
    "metadata": {}
}
{
    "repo_name": "datalad",
    "new_commit_hash": "751902c5dddce92a1ae5d115c62d96f4498c730c",
    "test_file_codes": [
        "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 noet:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\nfrom os import linesep\n\nfrom datalad.version import __version__\nfrom datalad.support.external_versions import ExternalVersions, LooseVersion\nfrom datalad.support.exceptions import CommandError\nfrom datalad.support.exceptions import OutdatedExternalDependency\nfrom datalad.support.annexrepo import AnnexRepo\n\nfrom mock import patch\nfrom nose.tools import assert_true, assert_false\nfrom nose.tools import assert_equal, assert_greater_equal, assert_greater\nfrom nose.tools import assert_raises\nfrom nose import SkipTest\nfrom six import PY3\n\nif PY3:\n    # just to ease testing\n    def cmp(a, b):\n        return (a > b) - (a < b)\n\n\ndef test_external_versions_basic():\n    ev = ExternalVersions()\n    our_module = 'datalad'\n    assert_equal(ev.versions, {})\n    assert_equal(ev[our_module], __version__)\n    # and it could be compared\n    assert_greater_equal(ev[our_module], __version__)\n    assert_greater(ev[our_module], '0.1')\n    assert_equal(list(ev.keys()), [our_module])\n    assert_true(our_module in ev)\n    assert_false('unknown' in ev)\n\n    # all are LooseVersions now\n    assert_true(isinstance(ev[our_module], LooseVersion))\n    version_str = __version__\n    assert_equal(ev.dumps(), \"Versions: %s=%s\" % (our_module, version_str))\n\n    # For non-existing one we get None\n    assert_equal(ev['custom__nonexisting'], None)\n    # and nothing gets added to _versions for nonexisting\n    assert_equal(set(ev.versions.keys()), {our_module})\n\n    # but if it is a module without version, we get it set to UNKNOWN\n    assert_equal(ev['os'], ev.UNKNOWN)\n    # And get a record on that inside\n    assert_equal(ev.versions.get('os'), ev.UNKNOWN)\n    # And that thing is \"True\", i.e. present\n    assert(ev['os'])\n    # but not comparable with anything besides itself (was above)\n    assert_raises(TypeError, cmp, ev['os'], '0')\n    assert_raises(TypeError, assert_greater, ev['os'], '0')\n\n    return\n    # Code below is from original duecredit, and we don't care about\n    # testing this one\n    # And we can get versions based on modules themselves\n    from datalad.tests import mod\n    assert_equal(ev[mod], mod.__version__)\n\n    # Check that we can get a copy of the versions\n    versions_dict = ev.versions\n    versions_dict[our_module] = \"0.0.1\"\n    assert_equal(versions_dict[our_module], \"0.0.1\")\n    assert_equal(ev[our_module], __version__)\n\n\ndef test_external_versions_unknown():\n    assert_equal(str(ExternalVersions.UNKNOWN), 'UNKNOWN')\n\n\ndef _test_external(ev, modname):\n    try:\n        exec (\"import %s\" % modname, globals(), locals())\n    except ImportError:\n        raise SkipTest(\"External %s not present\" % modname)\n    except Exception as e:\n        raise SkipTest(\"External %s fails to import: %s\" % (modname, e))\n    assert (ev[modname] is not ev.UNKNOWN)\n    assert_greater(ev[modname], '0.0.1')\n    assert_greater('1000000.0', ev[modname])  # unlikely in our lifetimes\n\n\ndef test_external_versions_popular_packages():\n    ev = ExternalVersions()\n\n    for modname in ('scipy', 'numpy', 'mvpa2', 'sklearn', 'statsmodels', 'pandas',\n                    'matplotlib', 'psychopy'):\n        yield _test_external, ev, modname\n\n    # more of a smoke test\n    assert_false(linesep in ev.dumps())\n    assert_true(ev.dumps(indent=True).endswith(linesep))\n\n\ndef test_custom_versions():\n    ev = ExternalVersions()\n    assert(ev['cmd:annex'] > '6.20160101')  # annex must be present and recentish\n    assert_equal(set(ev.versions.keys()), {'cmd:annex'})\n    # some older git version don't support files to be passed to\n    # `commit` call under some conditions and this will lead to diverse\n    # errors\n    assert(ev['cmd:git'] > '2.0')  # git must be present and recentish\n    assert(isinstance(ev['cmd:git'], LooseVersion))\n    assert_equal(set(ev.versions.keys()), {'cmd:annex', 'cmd:git'})\n\n    # and there is also a version of system-wide installed git, which might\n    # differ from cmd:git but should be at least good old 1.7\n    assert(ev['cmd:system-git'] > '1.7')\n\n    ev.CUSTOM = {'bogus': lambda: 1 / 0}\n    assert_equal(ev['bogus'], None)\n    assert_equal(set(ev.versions), {'cmd:annex', 'cmd:git', 'cmd:system-git'})\n\n\ndef test_ancient_annex():\n\n    class _runner(object):\n        def run(self, cmd):\n            if '--raw' in cmd:\n                raise CommandError\n            return \"git-annex version: 0.1\", \"\"\n\n    ev = ExternalVersions()\n    with patch('datalad.support.external_versions._runner', _runner()):\n        assert_equal(ev['cmd:annex'], '0.1')\n\n\ndef _test_annex_version_comparison(v, cmp_):\n    class _runner(object):\n        def run(self, cmd):\n            return v, \"\"\n\n    ev = ExternalVersions()\n    with patch('datalad.support.external_versions._runner', _runner()), \\\n         patch('datalad.support.annexrepo.external_versions',\n               ExternalVersions()):\n        ev['cmd:annex'] < AnnexRepo.GIT_ANNEX_MIN_VERSION\n        if cmp_ in (1, 0):\n            AnnexRepo._check_git_annex_version()\n            if cmp_ == 0:\n                assert_equal(AnnexRepo.git_annex_version, v)\n        elif cmp == -1:\n            with assert_raises(OutdatedExternalDependency):\n                AnnexRepo._check_git_annex_version()\n\n\ndef test_annex_version_comparison():\n    # see https://github.com/datalad/datalad/issues/1128\n    for cmp_, base in [(-1, '6.2011'), (1, \"2100.0\")]:\n        # there could be differing versions of a version\n        #   release, snapshot, neurodebian build of a snapshot\n        for v in base, base + '-g0a34f08', base + '+gitg9f179ae-1~ndall+1':\n            # they all must be comparable to our specification of min version\n            yield _test_annex_version_comparison, v, cmp_\n    yield _test_annex_version_comparison, str(AnnexRepo.GIT_ANNEX_MIN_VERSION), 0\n\n\ndef _test_list_tuple(thing):\n    version = ExternalVersions._deduce_version(thing)\n    assert_greater(version, '0.0.1')\n    assert_greater('0.2', version)\n    assert_equal('0.1', version)\n    assert_equal(version, '0.1')\n\n\ndef test_list_tuple():\n\n    class thing_with_tuple_version:\n        __version__ = (0, 1)\n\n    class thing_with_list_version:\n        __version__ = [0, 1]\n\n    for v in thing_with_list_version, thing_with_tuple_version, '0.1', (0, 1), [0, 1]:\n        yield _test_list_tuple, v\n\n\ndef test_system_ssh_version():\n    ev = ExternalVersions()\n    assert ev['cmd:system-ssh']  # usually we have some available at boxes we test\n\n    for s, v in [\n        ('OpenSSH_7.4p1 Debian-6, OpenSSL 1.0.2k  26 Jan 2017', '7.4p1'),\n    ]:\n        ev = ExternalVersions()\n        # TODO: figure out leaner way\n        class _runner(object):\n            def run(self, cmd):\n                return \"\", s\n        with patch('datalad.support.external_versions._runner', _runner()):\n            assert_equal(ev['cmd:system-ssh'], v)\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...\n❌ Verification failed\nPython 3.9 installation failed, trying Python 3.7...\nVerifying installation...\nDatalad version: 0.5.1.dev12\n✅ Installation successful!\nSuccessfully installed Datalad using Python 3.7\n",
    "setup_res_stderr": "Using CPython 3.9.21\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 25 packages in 127ms\nInstalled 25 packages in 6.75s\n + coverage==7.6.10\n + docopt==0.6.2\n + exceptiongroup==1.2.2\n + grabbit==0.2.6\n + importlib-resources==6.4.5\n + iniconfig==2.0.0\n + nibabel==5.3.2\n + num2words==0.5.14\n + numpy==2.0.2\n + packaging==24.2\n + pandas==2.2.3\n + patsy==1.0.1\n + pluggy==1.5.0\n + pybids==0.6.5\n + pytest==8.3.4\n + pytest-cov==6.0.0\n + python-dateutil==2.9.0.post0\n + pytz==2024.2\n + scipy==1.13.1\n + setuptools==75.6.0\n + six==1.17.0\n + tomli==2.2.1\n + typing-extensions==4.12.2\n + tzdata==2024.2\n + zipp==3.21.0\nerror: The build backend returned an error\n  Caused by: Call to `setuptools.build_meta:__legacy__.build_editable` failed (exit status: 1)\n\n[stderr]\nTraceback (most recent call last):\n  File \"<string>\", line 14, in <module>\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmpNxkwmJ/lib/python3.9/site-packages/setuptools/build_meta.py\", line 483, in get_requires_for_build_editable\n    return self.get_requires_for_build_wheel(config_settings)\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmpNxkwmJ/lib/python3.9/site-packages/setuptools/build_meta.py\", line 334, in get_requires_for_build_wheel\n    return self._get_build_requires(config_settings, requirements=[])\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmpNxkwmJ/lib/python3.9/site-packages/setuptools/build_meta.py\", line 304, in _get_build_requires\n    self.run_setup()\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmpNxkwmJ/lib/python3.9/site-packages/setuptools/build_meta.py\", line 522, in run_setup\n    super().run_setup(setup_script=setup_script)\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmpNxkwmJ/lib/python3.9/site-packages/setuptools/build_meta.py\", line 320, in run_setup\n    exec(code, locals())\n  File \"<string>\", line 48, in <module>\nAttributeError: module 'platform' has no attribute 'dist'\n\nhint: This usually indicates a problem with the package or the build environment.\nerror: The build backend returned an error\n  Caused by: Call to `setuptools.build_meta:__legacy__.build_editable` failed (exit status: 1)\n\n[stderr]\nTraceback (most recent call last):\n  File \"<string>\", line 14, in <module>\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmp53PXW3/lib/python3.9/site-packages/setuptools/build_meta.py\", line 483, in get_requires_for_build_editable\n    return self.get_requires_for_build_wheel(config_settings)\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmp53PXW3/lib/python3.9/site-packages/setuptools/build_meta.py\", line 334, in get_requires_for_build_wheel\n    return self._get_build_requires(config_settings, requirements=[])\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmp53PXW3/lib/python3.9/site-packages/setuptools/build_meta.py\", line 304, in _get_build_requires\n    self.run_setup()\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmp53PXW3/lib/python3.9/site-packages/setuptools/build_meta.py\", line 522, in run_setup\n    super().run_setup(setup_script=setup_script)\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmp53PXW3/lib/python3.9/site-packages/setuptools/build_meta.py\", line 320, in run_setup\n    exec(code, locals())\n  File \"<string>\", line 48, in <module>\nAttributeError: module 'platform' has no attribute 'dist'\n\nhint: This usually indicates a problem with the package or the build environment.\nTraceback (most recent call last):\n  File \"<string>\", line 1, in <module>\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_751902c5dddce92a1ae5d115c62d96f4498c730c/datalad/__init__.py\", line 30, in <module>\n    ssh_manager = SSHManager()\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_751902c5dddce92a1ae5d115c62d96f4498c730c/datalad/support/sshconnector.py\", line 333, in __init__\n    for p in listdir(self.socket_dir)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_751902c5dddce92a1ae5d115c62d96f4498c730c/datalad/support/sshconnector.py\", line 342, in socket_dir\n    self._socket_dir = opj(cfg.obtain('datalad.locations.cache'),\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_751902c5dddce92a1ae5d115c62d96f4498c730c/datalad/config.py\", line 304, in obtain\n    from datalad.interface.common_cfg import definitions as cfg_defs\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_751902c5dddce92a1ae5d115c62d96f4498c730c/datalad/interface/common_cfg.py\", line 15, in <module>\n    from appdirs import AppDirs\nModuleNotFoundError: No module named 'appdirs'\nUsing CPython 3.7.9\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 26 packages in 339ms\nInstalled 26 packages in 2.87s\n + bids==0.0\n + coverage==7.2.7\n + docopt==0.6.2\n + exceptiongroup==1.2.2\n + fasteners==0.19\n + grabbit==0.2.6\n + importlib-metadata==6.7.0\n + iniconfig==2.0.0\n + nibabel==4.0.2\n + num2words==0.5.14\n + numpy==1.21.6\n + packaging==24.0\n + pandas==1.3.5\n + patsy==1.0.1\n + pluggy==1.2.0\n + pybids==0.6.5\n + pytest==7.4.4\n + pytest-cov==4.1.0\n + python-dateutil==2.9.0.post0\n + pytz==2024.2\n + scipy==1.7.3\n + setuptools==68.0.0\n + six==1.17.0\n + tomli==2.0.1\n + typing-extensions==4.7.1\n + zipp==3.15.0\nResolved 77 packages in 1.30s\nPrepared 1 package in 1.53s\nInstalled 71 packages in 54ms\n + appdirs==1.4.4\n + attrs==24.2.0\n + automat==22.10.0\n + beautifulsoup4==4.12.3\n + boto==2.49.0\n + cachetools==5.5.0\n + certifi==2024.12.14\n + cffi==1.15.1\n + charset-normalizer==3.4.1\n + constantly==15.1.0\n + cryptography==44.0.0\n + cssselect==1.2.0\n + datalad==0.5.1.dev1 (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_751902c5dddce92a1ae5d115c62d96f4498c730c)\n + deprecated==1.2.15\n + filelock==3.12.2\n + frozendict==2.4.6\n + gitdb==4.0.12\n + gitpython==3.1.43\n + httpretty==1.1.4\n + humanize==4.6.0\n + hyperlink==21.0.0\n + idna==3.10\n + importlib-resources==5.12.0\n + incremental==22.10.0\n + iso8601==2.1.0\n + itemadapter==0.8.0\n + itemloaders==1.1.0\n + jaraco-classes==3.2.3\n + jeepney==0.8.0\n + jmespath==1.0.1\n + jsmin==3.0.1\n + keyring==24.1.1\n + keyrings-alt==4.2.0\n + lxml==5.3.0\n + mock==5.1.0\n + more-itertools==9.1.0\n + msgpack-python==0.5.6\n + multidict==6.0.5\n + nose==1.3.7\n + parsel==1.8.1\n + patool==1.12\n + protego==0.3.0\n + pyasn1==0.5.1\n + pyasn1-modules==0.3.0\n + pycparser==2.21\n + pydispatcher==2.0.7\n + pygithub==2.3.0\n + pyjwt==2.8.0\n + pyld==2.0.4\n + pynacl==1.5.0\n + pyopenssl==24.3.0\n + pyyaml==6.0.1\n + queuelib==1.6.2\n + requests==2.31.0\n + requests-file==2.1.0\n + requests-ftp==0.3.1\n + scrapy==2.9.0\n + secretstorage==3.3.3\n + service-identity==21.1.0\n + simplejson==3.19.3\n + smmap==5.0.2\n + soupsieve==2.4.1\n + tldextract==4.0.0\n + tqdm==4.67.1\n + twisted==23.8.0\n + urllib3==2.0.7\n + vcrpy==4.3.0\n + w3lib==2.1.2\n + wrapt==1.16.0\n + yarl==1.9.4\n + zope-interface==6.4.post2\nResolved 100 packages in 185ms\nInstalled 20 packages in 53ms\n + alabaster==0.7.13\n + babel==2.14.0\n + docutils==0.19\n + imagesize==1.4.1\n + jinja2==3.1.5\n + line-profiler==4.1.3\n + markupsafe==2.1.5\n + nose-timer==1.0.1\n + pygments==2.17.2\n + pypandoc==1.14\n + snowballstemmer==2.2.0\n + sphinx==5.3.0\n + sphinx-rtd-theme==2.0.0\n + sphinxcontrib-applehelp==1.0.2\n + sphinxcontrib-devhelp==1.0.2\n + sphinxcontrib-htmlhelp==2.0.0\n + sphinxcontrib-jquery==4.1\n + sphinxcontrib-jsmath==1.0.1\n + sphinxcontrib-qthelp==1.0.3\n + sphinxcontrib-serializinghtml==1.1.5\n",
    "new_commit_res_code": 0,
    "new_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.7.9, pytest-7.4.4, pluggy-1.2.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_751902c5dddce92a1ae5d115c62d96f4498c730c\nplugins: cov-4.1.0\ncollected 8 items\n\nr2e_tests/test_1.py ..x..xx.                                             [100%]\n\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::test_external_versions_basic\nPASSED r2e_tests/test_1.py::test_external_versions_unknown\nPASSED r2e_tests/test_1.py::test_custom_versions\nPASSED r2e_tests/test_1.py::test_ancient_annex\nPASSED r2e_tests/test_1.py::test_system_ssh_version\nXFAIL r2e_tests/test_1.py::test_external_versions_popular_packages - reason: [NOTRUN] yield tests were removed in pytest 4.0 - test_external_versions_popular_packages will be ignored\nXFAIL r2e_tests/test_1.py::test_annex_version_comparison - reason: [NOTRUN] yield tests were removed in pytest 4.0 - test_annex_version_comparison will be ignored\nXFAIL r2e_tests/test_1.py::test_list_tuple - reason: [NOTRUN] yield tests were removed in pytest 4.0 - test_list_tuple will be ignored\n========================= 5 passed, 3 xfailed in 0.81s =========================\n",
    "new_commit_res_stderr": "",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.7.9, pytest-7.4.4, pluggy-1.2.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_751902c5dddce92a1ae5d115c62d96f4498c730c\nplugins: cov-4.1.0\ncollected 8 items\n\nr2e_tests/test_1.py ..x..xxF                                             [100%]\n\n=================================== FAILURES ===================================\n___________________________ test_system_ssh_version ____________________________\n\n    def test_system_ssh_version():\n        ev = ExternalVersions()\n>       assert ev['cmd:system-ssh']  # usually we have some available at boxes we test\nE       assert None\n\nr2e_tests/test_1.py:190: AssertionError\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::test_external_versions_basic\nPASSED r2e_tests/test_1.py::test_external_versions_unknown\nPASSED r2e_tests/test_1.py::test_custom_versions\nPASSED r2e_tests/test_1.py::test_ancient_annex\nXFAIL r2e_tests/test_1.py::test_external_versions_popular_packages - reason: [NOTRUN] yield tests were removed in pytest 4.0 - test_external_versions_popular_packages will be ignored\nXFAIL r2e_tests/test_1.py::test_annex_version_comparison - reason: [NOTRUN] yield tests were removed in pytest 4.0 - test_annex_version_comparison will be ignored\nXFAIL r2e_tests/test_1.py::test_list_tuple - reason: [NOTRUN] yield tests were removed in pytest 4.0 - test_list_tuple will be ignored\nFAILED r2e_tests/test_1.py::test_system_ssh_version - assert None\n==================== 1 failed, 4 passed, 3 xfailed in 0.44s ====================\n",
    "old_commit_res_stderr": ""
}
datalad/__init__.py datalad/support/external_versions.py datalad/support/tests/test_external_versions.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** Unable to Retrieve System SSH Version, Returning `None`

**Description:**
When attempting to obtain the system-wide SSH version, the function fails and returns `None` instead of the actual version string. This issue affects any functionality that relies on knowing the SSH version installed on the system.

**Example Code:**
```python
ev = ExternalVersions()
ssh_version = ev['cmd:system-ssh']
print(ssh_version)  # Expected output: e.g., '7.4p1', but it prints: None
```

**Expected Behavior:**
The `ssh_version` should correctly contain the system's SSH version, such as `'7.4p1'`.

**Actual Behavior:**
The `ssh_version` is `None`, indicating that the SSH version could not be retrieved.

[/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/support/external_versions.py
# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-
# ex: set sts=4 ts=4 sw=4 noet:
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
#   See COPYING file distributed along with the datalad package for the
#   copyright and license terms.
#
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""Module to help maintain a registry of versions for external modules etc
"""
import sys
from os import linesep
from six import string_types
from six import binary_type

from distutils.version import LooseVersion

from datalad.dochelpers import exc_str
from datalad.log import lgr
# import version helper from config to have only one implementation
# config needs this to avoid circular imports
from datalad.config import get_git_version as __get_git_version
from .exceptions import CommandError

__all__ = ['UnknownVersion', 'ExternalVersions', 'external_versions']


# To depict an unknown version, which can't be compared by mistake etc
class UnknownVersion:
    """For internal use
    """

    def __str__(self):
        return "UNKNOWN"

    def __cmp__(self, other):
        if other is self:
            return 0
        raise TypeError("UNKNOWN version is not comparable")


#
# Custom handlers
#
from datalad.cmd import Runner
from datalad.cmd import GitRunner
_runner = Runner()
_git_runner = GitRunner()


def _get_annex_version():
    """Return version of available git-annex"""
    try:
        return _runner.run('git annex version --raw'.split())[0]
    except CommandError:
        # fall back on method that could work with older installations
        out, err = _runner.run(['git', 'annex', 'version'])
        return out.split('\n')[0].split(':')[1].strip()


def _get_git_version():
    """Return version of git we use (might be bundled)"""
    return __get_git_version(_git_runner)


def _get_system_git_version():
    """Return version of git available system-wide

    Might be different from the one we are using, which might be
    bundled with git-annex
    """
    return __get_git_version(_runner)


class ExternalVersions(object):
    """Helper to figure out/use versions of the externals (modules, cmdline tools, etc).

    To avoid collision between names of python modules and command line tools,
    prepend names for command line tools with `cmd:`.

    It maintains a dictionary of `distuil.version.LooseVersion`s to make
    comparisons easy. Note that even if version string conform the StrictVersion
    "standard", LooseVersion will be used.  If version can't be deduced for the
    external, `UnknownVersion()` is assigned.  If external is not present (can't
    be imported, or custom check throws exception), None is returned without
    storing it, so later call will re-evaluate fully.
    """

    UNKNOWN = UnknownVersion()

    CUSTOM = {
        'cmd:annex': _get_annex_version,
        'cmd:git': _get_git_version,
        'cmd:system-git': _get_system_git_version,
    }

    def __init__(self):
        self._versions = {}

    @classmethod
    def _deduce_version(klass, value):
        version = None

        # see if it is something containing a version
        for attr in ('__version__', 'version'):
            if hasattr(value, attr):
                version = getattr(value, attr)
                break

        # assume that value is the version
        if version is None:
            version = value

        # do type analysis
        if isinstance(version, (tuple, list)):
            #  Generate string representation
            version = ".".join(str(x) for x in version)
        elif isinstance(version, binary_type):
            version = version.decode()
        elif isinstance(version, string_types):
            pass
        else:
            version = None

        if version:
            return LooseVersion(version)
        else:
            return klass.UNKNOWN

    def __getitem__(self, module):
        # when ran straight in its source code -- fails to discover nipy's version.. TODO
        #if module == 'nipy':
        #    import pdb; pdb.set_trace()
        if not isinstance(module, string_types):
            modname = module.__name__
        else:
            modname = module
            module = None

        # Early returns None so we do not store prev result for  them
        # and allow users to install things at run time, so later check
        # doesn't pick it up from the _versions
        if modname not in self._versions:
            version = None   # by default -- not present
            if modname in self.CUSTOM:
                try:
                    version = self.CUSTOM[modname]()
                    version = self._deduce_version(version)
                except Exception as exc:
                    lgr.debug("Failed to deduce version of %s due to %s"
                              % (modname, exc_str(exc)))
                    return None
            else:
                if module is None:
                    if modname not in sys.modules:
                        try:
                            module = __import__(modname)
                        except ImportError:
                            lgr.debug("Module %s seems to be not present" % modname)
                            return None
                    else:
                        module = sys.modules[modname]
                if module:
                    version = self._deduce_version(module)
            self._versions[modname] = version

        return self._versions.get(modname, self.UNKNOWN)

    def keys(self):
        """Return names of the known modules"""
        return self._versions.keys()

    def __contains__(self, item):
        return item in self._versions

    @property
    def versions(self):
        """Return dictionary (copy) of versions"""
        return self._versions.copy()

    def dumps(self, indent=False, preamble="Versions:", query=False):
        """Return listing of versions as a string

        Parameters
        ----------
        indent: bool or str, optional
          If set would instruct on how to indent entries (if just True, ' '
          is used). Otherwise returned in a single line
        preamble: str, optional
          What preamble to the listing to use
        query : bool, optional
          To query for versions of all "registered" custom externals, so to
          get those which weren't queried for yet
        """
        if query:
            [self[k] for k in self.CUSTOM]
        if indent and (indent is True):
            indent = ' '
        items = ["%s=%s" % (k, self._versions[k]) for k in sorted(self._versions)]
        out = "%s" % preamble
        if indent:
            out += (linesep + indent).join([''] + items) + linesep
        else:
            out += " " + ' '.join(items)
        return out


external_versions = ExternalVersions()

```
--- 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:** Unable to Retrieve System SSH Version, Returning `None`

**Description:**
When attempting to obtain the system-wide SSH version, the function fails and returns `None` instead of the actual version string. This issue affects any functionality that relies on knowing the SSH version installed on the system.

**Example Code:**
```python
ev = ExternalVersions()
ssh_version = ev['cmd:system-ssh']
print(ssh_version)  # Expected output: e.g., '7.4p1', but it prints: None
```

**Expected Behavior:**
The `ssh_version` should correctly contain the system's SSH version, such as `'7.4p1'`.

**Actual Behavior:**
The `ssh_version` is `None`, indicating that the SSH version could not be retrieved.

[/ISSUE]
{
    "test_external_versions_basic": "PASSED",
    "test_external_versions_unknown": "PASSED",
    "test_custom_versions": "PASSED",
    "test_ancient_annex": "PASSED",
    "test_system_ssh_version": "PASSED"
}
diff --git a/datalad/__init__.py b/datalad/__init__.py
index 7ff46eb71..ed6b2f8bd 100644
--- a/datalad/__init__.py
+++ b/datalad/__init__.py
@@ -125,7 +125,7 @@ def teardown_package():
     lgr.debug("Printing versioning information collected so far")
     from datalad.support.external_versions import external_versions as ev
     # request versioning for few others which we do not check at runtime
-    for m in ('git',):
+    for m in ('git', 'system-ssh'):
         try:  # Let's make sure to not blow up when we are almost done
             ev[m]
         except Exception:
diff --git a/datalad/support/external_versions.py b/datalad/support/external_versions.py
index 601d8b712..b4dca7f07 100644
--- a/datalad/support/external_versions.py
+++ b/datalad/support/external_versions.py
@@ -72,6 +72,24 @@ def _get_system_git_version():
     return __get_git_version(_runner)
 
 
+def _get_system_ssh_version():
+    """Return version of ssh available system-wide
+
+    Annex prior 20170302 was using bundled version, but now would use system one
+    if installed
+    """
+    try:
+        out, err = _runner.run('ssh -V'.split())
+        # apparently spits out to err but I wouldn't trust it blindly
+        if err.startswith('OpenSSH'):
+            out = err
+        assert out.startswith('OpenSSH')  # that is the only one we care about atm
+        return out.split(' ', 1)[0].rstrip(',.').split('_')[1]
+    except CommandError as exc:
+        lgr.warn("Could not determine version of ssh available: %s", exc_str(exc))
+        return None
+
+
 class ExternalVersions(object):
     """Helper to figure out/use versions of the externals (modules, cmdline tools, etc).
 
@@ -92,6 +110,7 @@ class ExternalVersions(object):
         'cmd:annex': _get_annex_version,
         'cmd:git': _get_git_version,
         'cmd:system-git': _get_system_git_version,
+        'cmd:system-ssh': _get_system_ssh_version,
     }
 
     def __init__(self):

{"datalad/support/external_versions.py": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 noet:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Module to help maintain a registry of versions for external modules etc\n\"\"\"\nimport sys\nfrom os import linesep\nfrom six import string_types\nfrom six import binary_type\n\nfrom distutils.version import LooseVersion\n\nfrom datalad.dochelpers import exc_str\nfrom datalad.log import lgr\n# import version helper from config to have only one implementation\n# config needs this to avoid circular imports\nfrom datalad.config import get_git_version as __get_git_version\nfrom .exceptions import CommandError\n\n__all__ = ['UnknownVersion', 'ExternalVersions', 'external_versions']\n\n\n# To depict an unknown version, which can't be compared by mistake etc\nclass UnknownVersion:\n    \"\"\"For internal use\n    \"\"\"\n\n    def __str__(self):\n        return \"UNKNOWN\"\n\n    def __cmp__(self, other):\n        if other is self:\n            return 0\n        raise TypeError(\"UNKNOWN version is not comparable\")\n\n\n#\n# Custom handlers\n#\nfrom datalad.cmd import Runner\nfrom datalad.cmd import GitRunner\n_runner = Runner()\n_git_runner = GitRunner()\n\n\ndef _get_annex_version():\n    \"\"\"Return version of available git-annex\"\"\"\n    try:\n        return _runner.run('git annex version --raw'.split())[0]\n    except CommandError:\n        # fall back on method that could work with older installations\n        out, err = _runner.run(['git', 'annex', 'version'])\n        return out.split('\\n')[0].split(':')[1].strip()\n\n\ndef _get_git_version():\n    \"\"\"Return version of git we use (might be bundled)\"\"\"\n    return __get_git_version(_git_runner)\n\n\ndef _get_system_git_version():\n    \"\"\"Return version of git available system-wide\n\n    Might be different from the one we are using, which might be\n    bundled with git-annex\n    \"\"\"\n    return __get_git_version(_runner)\n\n\nclass ExternalVersions(object):\n    \"\"\"Helper to figure out/use versions of the externals (modules, cmdline tools, etc).\n\n    To avoid collision between names of python modules and command line tools,\n    prepend names for command line tools with `cmd:`.\n\n    It maintains a dictionary of `distuil.version.LooseVersion`s to make\n    comparisons easy. Note that even if version string conform the StrictVersion\n    \"standard\", LooseVersion will be used.  If version can't be deduced for the\n    external, `UnknownVersion()` is assigned.  If external is not present (can't\n    be imported, or custom check throws exception), None is returned without\n    storing it, so later call will re-evaluate fully.\n    \"\"\"\n\n    UNKNOWN = UnknownVersion()\n\n    CUSTOM = {\n        'cmd:annex': _get_annex_version,\n        'cmd:git': _get_git_version,\n        'cmd:system-git': _get_system_git_version,\n    }\n\n    def __init__(self):\n        self._versions = {}\n\n    @classmethod\n    def _deduce_version(klass, value):\n        version = None\n\n        # see if it is something containing a version\n        for attr in ('__version__', 'version'):\n            if hasattr(value, attr):\n                version = getattr(value, attr)\n                break\n\n        # assume that value is the version\n        if version is None:\n            version = value\n\n        # do type analysis\n        if isinstance(version, (tuple, list)):\n            #  Generate string representation\n            version = \".\".join(str(x) for x in version)\n        elif isinstance(version, binary_type):\n            version = version.decode()\n        elif isinstance(version, string_types):\n            pass\n        else:\n            version = None\n\n        if version:\n            return LooseVersion(version)\n        else:\n            return klass.UNKNOWN\n\n    def __getitem__(self, module):\n        # when ran straight in its source code -- fails to discover nipy's version.. TODO\n        #if module == 'nipy':\n        #    import pdb; pdb.set_trace()\n        if not isinstance(module, string_types):\n            modname = module.__name__\n        else:\n            modname = module\n            module = None\n\n        # Early returns None so we do not store prev result for  them\n        # and allow users to install things at run time, so later check\n        # doesn't pick it up from the _versions\n        if modname not in self._versions:\n            version = None   # by default -- not present\n            if modname in self.CUSTOM:\n                try:\n                    version = self.CUSTOM[modname]()\n                    version = self._deduce_version(version)\n                except Exception as exc:\n                    lgr.debug(\"Failed to deduce version of %s due to %s\"\n                              % (modname, exc_str(exc)))\n                    return None\n            else:\n                if module is None:\n                    if modname not in sys.modules:\n                        try:\n                            module = __import__(modname)\n                        except ImportError:\n                            lgr.debug(\"Module %s seems to be not present\" % modname)\n                            return None\n                    else:\n                        module = sys.modules[modname]\n                if module:\n                    version = self._deduce_version(module)\n            self._versions[modname] = version\n\n        return self._versions.get(modname, self.UNKNOWN)\n\n    def keys(self):\n        \"\"\"Return names of the known modules\"\"\"\n        return self._versions.keys()\n\n    def __contains__(self, item):\n        return item in self._versions\n\n    @property\n    def versions(self):\n        \"\"\"Return dictionary (copy) of versions\"\"\"\n        return self._versions.copy()\n\n    def dumps(self, indent=False, preamble=\"Versions:\", query=False):\n        \"\"\"Return listing of versions as a string\n\n        Parameters\n        ----------\n        indent: bool or str, optional\n          If set would instruct on how to indent entries (if just True, ' '\n          is used). Otherwise returned in a single line\n        preamble: str, optional\n          What preamble to the listing to use\n        query : bool, optional\n          To query for versions of all \"registered\" custom externals, so to\n          get those which weren't queried for yet\n        \"\"\"\n        if query:\n            [self[k] for k in self.CUSTOM]\n        if indent and (indent is True):\n            indent = ' '\n        items = [\"%s=%s\" % (k, self._versions[k]) for k in sorted(self._versions)]\n        out = \"%s\" % preamble\n        if indent:\n            out += (linesep + indent).join([''] + items) + linesep\n        else:\n            out += \" \" + ' '.join(items)\n        return out\n\n\nexternal_versions = ExternalVersions()\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:** Unable to Retrieve System SSH Version, Returning `None`

**Description:**
When attempting to obtain the system-wide SSH version, the function fails and returns `None` instead of the actual version string. This issue affects any functionality that relies on knowing the SSH version installed on the system.

**Example Code:**
```python
ev = ExternalVersions()
ssh_version = ev['cmd:system-ssh']
print(ssh_version)  # Expected output: e.g., '7.4p1', but it prints: None
```

**Expected Behavior:**
The `ssh_version` should correctly contain the system's SSH version, such as `'7.4p1'`.

**Actual Behavior:**
The `ssh_version` is `None`, indicating that the SSH version could not be retrieved.

[/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/support/external_versions.py
# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-
# ex: set sts=4 ts=4 sw=4 noet:
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
#   See COPYING file distributed along with the datalad package for the
#   copyright and license terms.
#
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""Module to help maintain a registry of versions for external modules etc
"""
import sys
from os import linesep
from six import string_types
from six import binary_type

from distutils.version import LooseVersion

from datalad.dochelpers import exc_str
from datalad.log import lgr
# import version helper from config to have only one implementation
# config needs this to avoid circular imports
from datalad.config import get_git_version as __get_git_version
from .exceptions import CommandError

__all__ = ['UnknownVersion', 'ExternalVersions', 'external_versions']


# To depict an unknown version, which can't be compared by mistake etc
class UnknownVersion:
    """For internal use
    """

    def __str__(self):
        return "UNKNOWN"

    def __cmp__(self, other):
        if other is self:
            return 0
        raise TypeError("UNKNOWN version is not comparable")


#
# Custom handlers
#
from datalad.cmd import Runner
from datalad.cmd import GitRunner
_runner = Runner()
_git_runner = GitRunner()


def _get_annex_version():
    """Return version of available git-annex"""
    try:
        return _runner.run('git annex version --raw'.split())[0]
    except CommandError:
        # fall back on method that could work with older installations
        out, err = _runner.run(['git', 'annex', 'version'])
        return out.split('\n')[0].split(':')[1].strip()


def _get_git_version():
    """Return version of git we use (might be bundled)"""
    return __get_git_version(_git_runner)


def _get_system_git_version():
    """Return version of git available system-wide

    Might be different from the one we are using, which might be
    bundled with git-annex
    """
    return __get_git_version(_runner)


class ExternalVersions(object):
    """Helper to figure out/use versions of the externals (modules, cmdline tools, etc).

    To avoid collision between names of python modules and command line tools,
    prepend names for command line tools with `cmd:`.

    It maintains a dictionary of `distuil.version.LooseVersion`s to make
    comparisons easy. Note that even if version string conform the StrictVersion
    "standard", LooseVersion will be used.  If version can't be deduced for the
    external, `UnknownVersion()` is assigned.  If external is not present (can't
    be imported, or custom check throws exception), None is returned without
    storing it, so later call will re-evaluate fully.
    """

    UNKNOWN = UnknownVersion()

    CUSTOM = {
        'cmd:annex': _get_annex_version,
        'cmd:git': _get_git_version,
        'cmd:system-git': _get_system_git_version,
    }

    def __init__(self):
        self._versions = {}

    @classmethod
    def _deduce_version(klass, value):
        version = None

        # see if it is something containing a version
        for attr in ('__version__', 'version'):
            if hasattr(value, attr):
                version = getattr(value, attr)
                break

        # assume that value is the version
        if version is None:
            version = value

        # do type analysis
        if isinstance(version, (tuple, list)):
            #  Generate string representation
            version = ".".join(str(x) for x in version)
        elif isinstance(version, binary_type):
            version = version.decode()
        elif isinstance(version, string_types):
            pass
        else:
            version = None

        if version:
            return LooseVersion(version)
        else:
            return klass.UNKNOWN

    def __getitem__(self, module):
        # when ran straight in its source code -- fails to discover nipy's version.. TODO
        #if module == 'nipy':
        #    import pdb; pdb.set_trace()
        if not isinstance(module, string_types):
            modname = module.__name__
        else:
            modname = module
            module = None

        # Early returns None so we do not store prev result for  them
        # and allow users to install things at run time, so later check
        # doesn't pick it up from the _versions
        if modname not in self._versions:
            version = None   # by default -- not present
            if modname in self.CUSTOM:
                try:
                    version = self.CUSTOM[modname]()
                    version = self._deduce_version(version)
                except Exception as exc:
                    lgr.debug("Failed to deduce version of %s due to %s"
                              % (modname, exc_str(exc)))
                    return None
            else:
                if module is None:
                    if modname not in sys.modules:
                        try:
                            module = __import__(modname)
                        except ImportError:
                            lgr.debug("Module %s seems to be not present" % modname)
                            return None
                    else:
                        module = sys.modules[modname]
                if module:
                    version = self._deduce_version(module)
            self._versions[modname] = version

        return self._versions.get(modname, self.UNKNOWN)

    def keys(self):
        """Return names of the known modules"""
        return self._versions.keys()

    def __contains__(self, item):
        return item in self._versions

    @property
    def versions(self):
        """Return dictionary (copy) of versions"""
        return self._versions.copy()

    def dumps(self, indent=False, preamble="Versions:", query=False):
        """Return listing of versions as a string

        Parameters
        ----------
        indent: bool or str, optional
          If set would instruct on how to indent entries (if just True, ' '
          is used). Otherwise returned in a single line
        preamble: str, optional
          What preamble to the listing to use
        query : bool, optional
          To query for versions of all "registered" custom externals, so to
          get those which weren't queried for yet
        """
        if query:
            [self[k] for k in self.CUSTOM]
        if indent and (indent is True):
            indent = ' '
        items = ["%s=%s" % (k, self._versions[k]) for k in sorted(self._versions)]
        out = "%s" % preamble
        if indent:
            out += (linesep + indent).join([''] + items) + linesep
        else:
            out += " " + ' '.join(items)
        return out


external_versions = ExternalVersions()

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