diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index 79fa6278549..9d4790f023d 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -29,7 +29,7 @@ from sphinx.util.docutils import LoggingReporter from sphinx.util.i18n import CatalogRepository, docname_to_domain from sphinx.util.nodes import is_translatable -from sphinx.util.osutil import _last_modified_time, _relative_path +from sphinx.util.osutil import _last_modified_time, _relative_path, normalize_path if TYPE_CHECKING: from collections.abc import Callable, Iterable, Iterator, Mapping @@ -438,7 +438,7 @@ def relfn2path( """ file_name = Path(filename) if file_name.parts[:1] in {('/',), ('\\',)}: - abs_fn = self.srcdir.joinpath(*file_name.parts[1:]).resolve() + abs_fn = normalize_path(self.srcdir.joinpath(*file_name.parts[1:])) else: if not docname: if self.docname: @@ -447,7 +447,7 @@ def relfn2path( msg = 'docname' raise KeyError(msg) doc_dir = self.doc2path(docname, base=False).parent - abs_fn = self.srcdir.joinpath(doc_dir, file_name).resolve() + abs_fn = normalize_path(self.srcdir.joinpath(doc_dir, file_name)) rel_fn = _relative_path(abs_fn, self.srcdir) return rel_fn.as_posix(), os.fspath(abs_fn) diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index 807db899af9..b1b4b679abb 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -43,6 +43,14 @@ def path_stabilize(filepath: str | os.PathLike[str], /) -> str: return unicodedata.normalize('NFC', new_path) +def normalize_path(filepath: Path, /) -> Path: + """Normalize path by removing redundant separators and up-level references. + + This *does not* resolve symbolic links. + """ + return filepath.__class__(os.path.normpath(filepath)) + + def relative_uri(base: str, to: str) -> str: """Return a relative URL from ``base`` to ``to``.""" if to.startswith(SEP): @@ -179,9 +187,9 @@ def _relative_path(path: Path, root: Path, /) -> Path: """ # Path.relative_to() requires fully-resolved paths (no '..'). if '..' in path.parts: - path = path.resolve() + path = normalize_path(path) if '..' in root.parts: - root = root.resolve() + root = normalize_path(root) if path.anchor != root.anchor or '..' in root.parts: # If the drives are different, no relative path exists.