#!/usr/bin/env python3 import os import sys from pathlib import Path def stat_follow(p: Path): try: return p.stat() # follows symlinks except FileNotFoundError: return None def lstat_nofollow(p: Path): try: return p.lstat() # does not follow symlinks except FileNotFoundError: return None def utime_nofollow(p: Path, t: float): try: os.utime(p, (t, t), follow_symlinks=False) except (NotImplementedError, PermissionError, FileNotFoundError): pass def update_dir_mtime(path: Path, visited: set[tuple[int, int]]) -> float: st = stat_follow(path) if st is None: return 0.0 key = (st.st_dev, st.st_ino) if key in visited: return st.st_mtime visited.add(key) if not path.is_dir(): return st.st_mtime newest = 0.0 for child in path.iterdir(): child_st = stat_follow(child) if child_st is None: continue # broken symlink or vanished entry -> ignore if child.is_dir(): # true also for symlink->dir when target exists child_mtime = update_dir_mtime(child, visited) else: child_mtime = child_st.st_mtime # NEW: if child is a symlink and target exists, update the symlink entry too if child.is_symlink(): link_st = lstat_nofollow(child) if link_st is not None and child_mtime > link_st.st_mtime: utime_nofollow(child, child_mtime) if child_mtime > newest: newest = child_mtime # Update this directory to reflect newest reachable content if newest > 0.0 and newest > st.st_mtime: os.utime(path, (newest, newest)) st = stat_follow(path) or st return max(newest, st.st_mtime) if __name__ == "__main__": if len(sys.argv) != 2: print("Usage: fix_dir_mtime.py ") raise SystemExit(1) root = Path(sys.argv[1]).resolve() update_dir_mtime(root, visited=set())