Restore the ability to watch files through relative symlinks

Restore the ability to watch files through relative symlinks
(i.e. symlinks that have a non-absolute path as the target)

This was not fully fixed in #2427.

Switch from os.path.realpath to pathlib.Path.resolve
This commit is contained in:
Oleh Prypin
2021-06-04 16:42:38 +02:00
committed by GitHub
parent 769a926691
commit b221df94fa
2 changed files with 30 additions and 11 deletions

View File

@@ -101,29 +101,26 @@ class LiveReloadServer(socketserver.ThreadingMixIn, wsgiref.simple_server.WSGISe
def schedule(path):
seen.add(path)
if os.path.isfile(path):
if path.is_file():
# Watchdog doesn't support watching files, so watch its directory and filter by path
handler = watchdog.events.FileSystemEventHandler()
handler.on_any_event = lambda event: callback(event, allowed_path=path)
handler.on_any_event = lambda event: callback(event, allowed_path=os.fspath(path))
parent = os.path.dirname(path)
parent = path.parent
log.debug(f"Watching file '{path}' through directory '{parent}'")
self.observer.schedule(handler, parent)
else:
log.debug(f"Watching directory '{path}'")
self.observer.schedule(dir_handler, path, recursive=recursive)
schedule(os.path.realpath(path))
schedule(pathlib.Path(path).resolve())
def watch_symlink_targets(path_obj): # path is os.DirEntry or pathlib.Path
if path_obj.is_symlink():
# The extra `readlink` is needed due to https://bugs.python.org/issue9949
target = os.path.realpath(os.readlink(os.fspath(path_obj)))
if target in seen or not os.path.exists(target):
path_obj = pathlib.Path(path_obj).resolve()
if path_obj in seen or not path_obj.exists():
return
schedule(target)
path_obj = pathlib.Path(target)
schedule(path_obj)
if path_obj.is_dir() and recursive:
with os.scandir(os.fspath(path_obj)) as scan:

View File

@@ -3,6 +3,7 @@
import contextlib
import email
import io
import os
import sys
import threading
import time
@@ -496,6 +497,28 @@ class BuildTests(unittest.TestCase):
Path(tmp_dir, "file_dest_unused.md").write_text("edited")
self.assertFalse(started_building.wait(timeout=0.2))
@tempdir(prefix="site_dir")
@tempdir(["docs/unused.md", "README.md"], prefix="origin_dir")
def test_watches_through_relative_symlinks(self, origin_dir, site_dir):
docs_dir = Path(origin_dir, "docs")
old_cwd = os.getcwd()
os.chdir(docs_dir)
try:
Path(docs_dir, "README.md").symlink_to(Path("..", "README.md"))
except NotImplementedError: # PyPy on Windows
self.skipTest("Creating symlinks not supported")
finally:
os.chdir(old_cwd)
started_building = threading.Event()
with testing_server(docs_dir, started_building.set) as server:
server.watch(docs_dir)
time.sleep(0.01)
Path(origin_dir, "README.md").write_text("edited")
self.assertTrue(started_building.wait(timeout=10))
@tempdir()
def test_watch_with_broken_symlinks(self, docs_dir):
Path(docs_dir, "subdir").mkdir()
@@ -503,7 +526,6 @@ class BuildTests(unittest.TestCase):
try:
if sys.platform != "win32":
Path(docs_dir, "subdir", "circular").symlink_to(Path(docs_dir))
Path(docs_dir, "self_link").symlink_to(Path(docs_dir, "self_link"))
Path(docs_dir, "broken_1").symlink_to(Path(docs_dir, "oh no"))
Path(docs_dir, "broken_2").symlink_to(Path(docs_dir, "oh no"), target_is_directory=True)