-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Description
Describe the bug
Sphinx has a number of places where it uses Path.resolve()
to normalize paths. I assume this is because pathlib.Path()
doesn't have any method of normalizing ..
out of paths without resolving symlinks, but this can lead to problems when linking to a document section, when the target document is a symlink.
I have not seen this matter for RST-based sites, but for Markdown sites using MyST-Parser, it can lead to an issue with making explicit links between documents. For example, I might have an intro.md
that links to the repository README.md
. If I want to refer to a heading, I might use [Heading title](intro.md#heading-title)
. When that happens, I will get a warning from myst:
WARNING: Unknown source document '/path/to/project/README' [myst.xref_missing]
And the link will fail to render. I have tracked down the issue in executablebooks/MyST-Parser#1055, and the crux is that sphinx.environment.BuildEnvironment.relfn2path
that they call resolves symlinks.
The following patch resolves the problem:
diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py
index 79fa62785..dfdf8bdad 100644
--- a/sphinx/environment/__init__.py
+++ b/sphinx/environment/__init__.py
@@ -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 = os.path.normpath(self.srcdir.joinpath(*file_name.parts[1:]))
else:
if not docname:
if self.docname:
@@ -447,10 +447,10 @@ 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 = os.path.normpath(self.srcdir.joinpath(doc_dir, file_name))
rel_fn = _relative_path(abs_fn, self.srcdir)
- return rel_fn.as_posix(), os.fspath(abs_fn)
+ return rel_fn.as_posix(), abs_fn
@property
def found_docs(self) -> set[str]:
diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py
index 807db899a..f4138cf4b 100644
--- a/sphinx/util/osutil.py
+++ b/sphinx/util/osutil.py
@@ -178,10 +178,11 @@ def _relative_path(path: Path, root: Path, /) -> Path:
which may happen on Windows.
"""
# Path.relative_to() requires fully-resolved paths (no '..').
+ # Use normpath to avoid resolving symlinks. There is no pathlib equivalent.
if '..' in path.parts:
- path = path.resolve()
+ path = Path(os.path.normpath(path))
if '..' in root.parts:
- root = root.resolve()
+ root = Path(os.path.normpath(root))
if path.anchor != root.anchor or '..' in root.parts:
# If the drives are different, no relative path exists.
Now, this case does not present in RST because RST does not have equivalent link form:
`link text <relpath.rst#section>`_
I would be happy to submit this patch (or another that fits Sphinx's conventions better) if you'd be interested.
How to Reproduce
mkdir docs
cat <<EOF > README.md
# Header
## Section
Text
EOF
uv init --bare --name=reproduction --no-workspace
uv add sphinx myst-parser
uv run sphinx-quickstart --project="Reproduction" --author="Author Name" \
--no-sep -r '' -l en --extensions myst_parser \
docs/
ln -s ../README.md docs/description.md
cat <<EOF >> docs/conf.py
myst_heading_anchors = 2
EOF
cat <<EOF > docs/document.md
# Document Title
Explicitly linking to [Section](description.md#section).
EOF
cat <<EOF >> docs/index.rst
description.md
document.md
EOF
uv run sphinx-build -b html docs docs/_build/html
Environment Information
Platform: linux; (Linux-6.16.7-200.fc42.x86_64-x86_64-with-glibc2.41)
Python version: 3.13.7 (main, Sep 2 2025, 14:21:46) [Clang 20.1.4 ])
Python implementation: CPython
Sphinx version: 8.2.3
Docutils version: 0.21.2
Jinja2 version: 3.1.6
Pygments version: 2.19.2
Sphinx extensions
myst-parser