File: fix-symlinks.py

#!/usr/local/bin/python3
"""
=================================================================
Mac OS Catalina locked down the '/' root, which breaks symlinks
using absolute paths from former root-level folders, as well as 
content copiers that follow them (e.g., this site's PUBLISH.py).
Run this on a folder tree to redirect all its root symlinks.
© M.Lutz (learning-python.com) 2020, no warranties provided. 

Caveats: 
1) This does not copy modtimes or permissions as is because 
   they're irrelevant to the handful of links adjusted

2) Symlinks are notoriously platform specific; read more at:
   learning-python.com/ziptools/ziptools/_README.html#Platforms

3) Absolute symlinks won't usually work on external drives or
   other computers; use relative paths (e.g., '..') if possible

Coding notes: this cannot just loop over fileshere: os.walk()
doesn't return links to folders there; if [not followlinks],
[fileshere + subshere] works, but os.listdir() works as well.
Add an os.path.exists(oldlink) to detect/drop broken links.
=================================================================
"""
import sys, os

assert len(sys.argv) == 2, "Please pass in a folder tree's path"
folder = sys.argv[1]

# change these
LISTONLY = True            # True = show, but don't mod
OLD_ROOT = '/MY-STUFF'     # pre-catalina root-paths start
NEW_ROOT = '/Users/me'     # prepend this to old root paths

nlinks = nfixes = 0
for (thisdir, subshere, fileshere) in os.walk(folder, followlinks=False):
    for item in os.listdir(thisdir):
        path = os.path.join(thisdir, item)
        if os.path.islink(path):
            nlinks += 1
            oldlink = os.readlink(path)
            context = '%s => %s' % (path, oldlink)
            if not oldlink.startswith(OLD_ROOT):
                print('Non-root:  ', context)
            elif LISTONLY:
                print('Unchanged: ', context)
            else:
                try:
                    nfixes += 1
                    newlink = NEW_ROOT + oldlink
                    os.remove(path)
                    os.symlink(newlink, path)
                    print('Redirected:', context)
                except:
                    print('Cannot fix:', context)

print('Links found:', nlinks)
print('Links fixed:', nfixes)



"""
=================================================================
Example output:

~/MY-STUFF/Websites$ py3 fix-symlinks.py .
Redirected: ./Posts/Current/_shrinkpix-info.txt => /MY-STUFF/Websites/_admin/Top-Level-Image-Shrinks-mar1020.txt
Non-root:   ./Posts/Current/TOOLS.txt => ../../Books/Current/TOOLS.txt
...etc...
Redirected: ./OldMain/Current/_shrinkpix-info.txt => /MY-STUFF/Websites/_admin/Top-Level-Image-Shrinks-mar1020.txt
Non-root:   ./OldMain/Current/TOOLS.txt => ../../Books/Current/TOOLS.txt
Non-root:   ./OldMain/Current/Complete/_main.css => ../../../Books/Current/Complete/_main.css
Links found: 20
Links fixed: 7

~/MY-STUFF/Websites$ py3 fix-symlinks.py .
Non-root:   ./Posts/Current/_shrinkpix-info.txt => /Users/me/MY-STUFF/Websites/_admin/Top-Level-Image-Shrinks-mar1020.txt
Non-root:   ./Posts/Current/TOOLS.txt => ../../Books/Current/TOOLS.txt
..etc...
Non-root:   ./OldMain/Current/_shrinkpix-info.txt => /Users/me/MY-STUFF/Websites/_admin/Top-Level-Image-Shrinks-mar1020.txt
Non-root:   ./OldMain/Current/TOOLS.txt => ../../Books/Current/TOOLS.txt
Non-root:   ./OldMain/Current/Complete/_main.css => ../../../Books/Current/Complete/_main.css
Links found: 20
Links fixed: 0
=================================================================
"""



[Home page] Books Code Blog Python Author Train Find ©M.Lutz