Skip to content

Commit

Permalink
Restore the ability to watch files through relative symlinks
Browse files Browse the repository at this point in the history
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 mkdocs#2427.

Switch from os.path.realpath to pathlib.Path.resolve
  • Loading branch information
oprypin authored Jun 4, 2021
1 parent 769a926 commit b221df9
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 11 deletions.
17 changes: 7 additions & 10 deletions mkdocs/livereload/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,29 +101,26 @@ def callback(event, allowed_path=None):

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:
Expand Down
24 changes: 23 additions & 1 deletion mkdocs/tests/livereload_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import contextlib
import email
import io
import os
import sys
import threading
import time
Expand Down Expand Up @@ -496,14 +497,35 @@ def wait_for_build():
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()

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)
Expand Down

0 comments on commit b221df9

Please sign in to comment.