3232 across different working directories.
3333"""
3434
35+ import os
3536import re
36- from pathlib import Path
37+ import shutil
38+ import subprocess
39+ import sys
3740from contextlib import suppress
38- import os , sys , shutil , subprocess
39- import stat
41+ from pathlib import Path
42+
4043
4144def _is_windows_junction (p : Path ) -> bool :
42- """Return True if path is a directory junction (reparse point mount point)."""
45+ """Return True if path is a directory junction."""
46+ if not sys .platform .startswith ("win" ):
47+ return False
48+
4349 try :
44- st = p .lstat ()
45- return getattr (st , "st_reparse_tag" , 0 ) == stat .IO_REPARSE_TAG_MOUNT_POINT
46- except (OSError , AttributeError ):
50+ # On Windows, check if it's a directory that's not a symlink
51+ # Junctions appear as directories but resolve to different paths
52+ return p .exists () and p .is_dir () and not p .is_symlink () and p .resolve () != p
53+ except (OSError , RuntimeError ):
54+ # Handle cases where path operations fail
4755 return False
4856
57+
4958def _safe_remove_path (p : Path ) -> None :
5059 """Remove file/dir/symlink/junction at p without following links."""
5160 if not os .path .lexists (str (p )):
@@ -54,12 +63,14 @@ def _safe_remove_path(p: Path) -> None:
5463 if p .is_symlink ():
5564 p .unlink ()
5665 elif _is_windows_junction (p ):
57- os .rmdir (p )
66+ # Use rmdir for Windows junctions
67+ p .rmdir ()
5868 elif p .is_dir ():
5969 shutil .rmtree (p )
6070 else :
6171 p .unlink ()
6272
73+
6374def _make_latest_windows (latest : Path , target : Path ) -> None :
6475 # Clean previous latest (symlink/junction/dir/file)
6576 _safe_remove_path (latest )
@@ -69,15 +80,20 @@ def _make_latest_windows(latest: Path, target: Path) -> None:
6980
7081 # Try junction first (no admin needed), fallback to copy
7182 try :
72- subprocess .run (
73- ["cmd" , "/c" , "mklink" , "/J" , str (tmp ), str (target .resolve ())],
83+ # nosec B603, B607: Windows-specific command for creating junction
84+ subprocess .run ( # noqa: S603
85+ ["cmd" , "/c" , "mklink" , "/J" , str (tmp ), str (target .resolve ())], # noqa: S607
7486 check = True ,
7587 capture_output = True ,
7688 )
77- except Exception :
89+ except ( subprocess . CalledProcessError , OSError , FileNotFoundError ) :
7890 shutil .copytree (target , tmp )
91+ else :
92+ tmp .replace (latest )
93+ return
94+
95+ tmp .replace (latest )
7996
80- os .replace (tmp , latest )
8197
8298def create_versioned_dir (root_dir : str | Path ) -> Path :
8399 """Create a new version directory and update the ``latest`` symbolic link.
0 commit comments