|
@@ -196,32 +196,6 @@ def hardlink(src, lnk):
|
|
|
os.link(src, lnk)
|
|
|
|
|
|
|
|
|
-@errorfix_win
|
|
|
-def hardlink_or_copy(src, lnk):
|
|
|
- def should_fallback_to_copy(exc):
|
|
|
- if WindowsError is not None and isinstance(exc, WindowsError) and exc.winerror == 1142: # too many hardlinks
|
|
|
- return True
|
|
|
- # cross-device hardlink or too many hardlinks, or some known WSL error
|
|
|
- if isinstance(exc, OSError) and exc.errno in (
|
|
|
- errno.EXDEV,
|
|
|
- errno.EMLINK,
|
|
|
- errno.EINVAL,
|
|
|
- errno.EACCES,
|
|
|
- errno.EPERM,
|
|
|
- ):
|
|
|
- return True
|
|
|
- return False
|
|
|
-
|
|
|
- try:
|
|
|
- hardlink(src, lnk)
|
|
|
- except Exception as e:
|
|
|
- logger.debug('Failed to hardlink %s to %s with error %s, will copy it', src, lnk, repr(e))
|
|
|
- if should_fallback_to_copy(e):
|
|
|
- copy2(src, lnk, follow_symlinks=False)
|
|
|
- else:
|
|
|
- raise
|
|
|
-
|
|
|
-
|
|
|
# Atomic file/directory symlink (Unix only)
|
|
|
# Dst must not exist
|
|
|
# Throws OSError
|
|
@@ -247,24 +221,57 @@ def copy2(src, lnk, follow_symlinks=True):
|
|
|
symlink(os.readlink(src), lnk)
|
|
|
|
|
|
|
|
|
+def copy2_safe(src, lnk, follow_symlinks=True):
|
|
|
+ try:
|
|
|
+ copy2(src, lnk, follow_symlinks)
|
|
|
+ except shutil.SameFileError:
|
|
|
+ pass
|
|
|
+
|
|
|
+
|
|
|
+@errorfix_win
|
|
|
+def hardlink_or_copy(src, lnk, copy_function=copy2):
|
|
|
+ def should_fallback_to_copy(exc):
|
|
|
+ if WindowsError is not None and isinstance(exc, WindowsError) and exc.winerror == 1142: # too many hardlinks
|
|
|
+ return True
|
|
|
+ # cross-device hardlink or too many hardlinks, or some known WSL error
|
|
|
+ if isinstance(exc, OSError) and exc.errno in (
|
|
|
+ errno.EXDEV,
|
|
|
+ errno.EMLINK,
|
|
|
+ errno.EINVAL,
|
|
|
+ errno.EACCES,
|
|
|
+ errno.EPERM,
|
|
|
+ ):
|
|
|
+ return True
|
|
|
+ return False
|
|
|
+
|
|
|
+ try:
|
|
|
+ hardlink(src, lnk)
|
|
|
+ except Exception as e:
|
|
|
+ logger.debug('Failed to hardlink %s to %s with error %s, will copy it', src, lnk, repr(e))
|
|
|
+ if should_fallback_to_copy(e):
|
|
|
+ copy_function(src, lnk, follow_symlinks=False)
|
|
|
+ else:
|
|
|
+ raise
|
|
|
+
|
|
|
+
|
|
|
# Recursively hardlink directory
|
|
|
# Uses plain hardlink for files
|
|
|
# Dst must not exist
|
|
|
# Non-atomic
|
|
|
# Throws OSError
|
|
|
@errorfix_win
|
|
|
-def hardlink_tree(src, dst):
|
|
|
+def hardlink_tree(src, dst, hardlink_function=hardlink, mkdir_function=os.mkdir):
|
|
|
if not os.path.exists(src):
|
|
|
raise CustomFsError(errno.ENOENT, filename=src)
|
|
|
if os.path.isfile(src):
|
|
|
- hardlink(src, dst)
|
|
|
+ hardlink_function(src, dst)
|
|
|
return
|
|
|
for dirpath, _, filenames in walk_relative(src):
|
|
|
src_dirpath = os.path.join(src, dirpath) if dirpath != '.' else src
|
|
|
dst_dirpath = os.path.join(dst, dirpath) if dirpath != '.' else dst
|
|
|
- os.mkdir(dst_dirpath)
|
|
|
+ mkdir_function(dst_dirpath)
|
|
|
for filename in filenames:
|
|
|
- hardlink(os.path.join(src_dirpath, filename), os.path.join(dst_dirpath, filename))
|
|
|
+ hardlink_function(os.path.join(src_dirpath, filename), os.path.join(dst_dirpath, filename))
|
|
|
|
|
|
|
|
|
# File copy
|