Skip to content

Commit

Permalink
Adjust handling of symlinks in sync_tree
Browse files Browse the repository at this point in the history
Adjust heuristic to guess if target of a symlink is a dir or a
file. This is important on Windows platform in order to recreate
correctly the link even if the target of the link do not already
exist at a given point in time.

it/e3-core#23
  • Loading branch information
Nikokrock committed Jan 9, 2025
1 parent 62ef874 commit 8a7832e
Showing 1 changed file with 39 additions and 5 deletions.
44 changes: 39 additions & 5 deletions src/e3/fs.py
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,7 @@ def copystat(src: FileInfo, dst: FileInfo) -> None:

logger.debug("chflags: operation not supported [EOPNOTSUPP]")

def safe_copy(src: FileInfo, dst: FileInfo, is_directory: bool = False) -> None:
def safe_copy(src: FileInfo, dst: FileInfo) -> None:
"""Copy src file into dst preserving all attributes.
:param src: the source FileInfo object
Expand All @@ -842,7 +842,43 @@ def safe_copy(src: FileInfo, dst: FileInfo, is_directory: bool = False) -> None:
# to transform Cygwin links into Win32 symlinks
if dst.stat is not None:
rm(dst.path, recursive=True, glob=False)
os.symlink(linkto, dst.path, target_is_directory=is_directory)

target_is_directory = False
if sys.platform == "win32":
# This is important to try guessing the right nature of the link
# (i.e whether it points to a directory or a file). Indeed on
# Windows system symbolic links to directory and files are distinct.
# During a call to sync_tree we are not sure in advance what will
# be created first: the link or the target of the link. Thus Python
# cannot always guess the right nature of the link (in that case
# Python defaults to a link to a file).
# In addition this function support WSL links that may be created
# by Cygwin when the nature of the target is not known. Python
# cannot read those links so in that case doing
# os.path.isdir(src.path) will always return False. That's why we
# do the check directly on the target path.
# limit recursion to 32 in order not to crash on link loops
src_linkto_path = os.path.join(os.path.dirname(src.path), linkto)
for _ in range(32):
if not os.path.exists(src_linkto_path):
break

src_linkto = FileInfo(
src_linkto_path,
os.lstat(src_linkto_path),
os.path.basename(src_linkto_path),
)

if not islink(src_linkto):
break

src_linkto_path = os.path.join(
os.path.dirname(src_linkto.path),
e3.os.fs.readlink(src_linkto.path),
)
target_is_directory = os.path.isdir(src_linkto_path)

os.symlink(linkto, dst.path, target_is_directory=target_is_directory)
copystat(src, dst)
else:
if isdir(dst):
Expand Down Expand Up @@ -1034,9 +1070,7 @@ def walk(
# the source tree.
if need_update(wf.source, wf.target):
if isfile(wf.source) or islink(wf.source):
safe_copy(
wf.source, wf.target, is_directory=os.path.isdir(wf.source.path)
)
safe_copy(wf.source, wf.target)
updated_list.append(wf.target.path)
elif isdir(wf.source):
safe_mkdir(wf.source, wf.target)
Expand Down

0 comments on commit 8a7832e

Please sign in to comment.