From 874aceccf88df1da72ac5d18ef8134ba96e19407 Mon Sep 17 00:00:00 2001 From: Ruslan Kuprieiev Date: Tue, 26 Oct 2021 01:41:14 +0300 Subject: [PATCH] system: use linkat instead of link `os.link` is broken across platforms when dealing with symlinks. See https://bugs.python.org/issue41355 for more info. --- dvc/system.py | 30 +++++++++++++++++++++++++++++- setup.cfg | 2 +- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/dvc/system.py b/dvc/system.py index 43a0e4e187..893e460631 100644 --- a/dvc/system.py +++ b/dvc/system.py @@ -2,14 +2,42 @@ import logging import os import platform +import stat +import sys logger = logging.getLogger(__name__) +if os.name == "nt" and sys.version_info < (3, 8): + # NOTE: using backports for `os.path.realpath` + # See https://bugs.python.org/issue9949 for more info. + # pylint: disable=import-error, no-name-in-module + from jaraco.windows.filesystem.backports import realpath as _realpath + + def realpath(path): + return _realpath(os.fspath(path)) + + +else: + realpath = os.path.realpath + + class System: @staticmethod def hardlink(source, link_name): - os.link(source, link_name) + # NOTE: we should really be using `os.link()` here with + # `follow_symlinks=True`, but unfortunately the implementation is + # buggy across platforms, so until it is fixed, we just dereference + # the symlink ourselves here. + # + # See https://bugs.python.org/issue41355 for more info. + st = os.lstat(source) + if stat.S_ISLNK(st.st_mode): + src = realpath(source) + else: + src = source + + os.link(src, link_name) @staticmethod def symlink(source, link_name): diff --git a/setup.cfg b/setup.cfg index 15ec180797..544cdc68f6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -75,6 +75,7 @@ install_requires = fsspec[http]>=2021.10.1 aiohttp-retry>=2.4.5 diskcache>=5.2.1 + jaraco.windows>=5.7.0; python_version < '3.8' and sys_platform == 'win32' [options.extras_require] # gssapi should not be included in all_remotes, because it doesn't have wheels @@ -139,7 +140,6 @@ tests = Pygments==2.10.0 collective.checkdocs==0.2 pydocstyle==6.1.1 - jaraco.windows==5.7.0 # pylint requirements pylint==2.11.1 # we use this to suppress pytest-related false positives in our tests.