diff --git a/mkdocs/livereload/__init__.py b/mkdocs/livereload/__init__.py index c64b5e69..5ac36e73 100644 --- a/mkdocs/livereload/__init__.py +++ b/mkdocs/livereload/__init__.py @@ -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: diff --git a/mkdocs/tests/livereload_tests.py b/mkdocs/tests/livereload_tests.py index 576ec15e..c4d25697 100644 --- a/mkdocs/tests/livereload_tests.py +++ b/mkdocs/tests/livereload_tests.py @@ -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)