Skip to content

Commit 58e64d4

Browse files
committed
Fix _relative_path() for unresolved paths
1 parent 1a3d1de commit 58e64d4

File tree

4 files changed

+22
-2
lines changed

4 files changed

+22
-2
lines changed

CHANGES.rst

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ Bugs fixed
2525
Patch by Dave Hoese.
2626
* #13382: Napoleon: Use the right valid types for configuration values.
2727
Patch by Adam Turner.
28+
* 13376: Fix copying assets from a relative :confval:`html_static_path` entry.
29+
Patch by Adam Turner.
2830

2931
Testing
3032
-------

sphinx/util/fileutil.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ def copy_asset(
140140
return
141141

142142
for root, dirs, files in os.walk(source, followlinks=True):
143-
reldir = _relative_path(Path(root), source).as_posix()
143+
root_p = Path(root)
144+
reldir = _relative_path(root_p, source).as_posix()
144145
for dir in dirs.copy():
145146
if excluded(posixpath.join(reldir, dir)):
146147
dirs.remove(dir)

sphinx/util/osutil.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,14 @@ def _relative_path(path: Path, root: Path, /) -> Path:
177177
It returns the original path if *path* and *root* are on different drives,
178178
which may happen on Windows.
179179
"""
180+
# Path.relative_to() requires fully-resolved paths (no '..').
181+
if '..' in path.parts:
182+
path = path.resolve()
183+
if '..' in root.parts:
184+
root = root.resolve()
185+
180186
if path.anchor != root.anchor or '..' in root.parts:
181187
# If the drives are different, no relative path exists.
182-
# Path.relative_to() requires fully-resolved paths (no '..').
183188
return path
184189
if sys.version_info[:2] < (3, 12):
185190
return Path(os.path.relpath(path, root))

tests/test_util/test_util_fileutil.py

+12
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ def test_copy_asset(tmp_path):
8484
(source / '_templates' / 'sidebar.html.jinja').write_text(
8585
'sidebar: {{var2}}', encoding='utf8'
8686
)
87+
sibling = tmp_path / 'sibling'
88+
sibling.mkdir(parents=True, exist_ok=True)
89+
sibling.joinpath('spam').mkdir(parents=True, exist_ok=True)
90+
sibling.joinpath('spam', 'ham').touch()
8791

8892
# copy a single file
8993
assert not (tmp_path / 'test1').exists()
@@ -126,6 +130,14 @@ def excluded(path):
126130
assert (destdir / '_templates' / 'layout.html').exists()
127131
assert not (destdir / '_templates' / 'sidebar.html').exists()
128132

133+
# copy sibling
134+
assert not (tmp_path / 'test4').exists()
135+
copy_asset(source / '../sibling', tmp_path / 'test4')
136+
assert (tmp_path / 'test4').is_dir()
137+
assert (tmp_path / 'test4' / 'spam').is_dir()
138+
assert (tmp_path / 'test4' / 'spam' / 'ham').is_file()
139+
assert (tmp_path / 'test4' / 'spam' / 'ham').read_bytes() == b''
140+
129141

130142
@pytest.mark.sphinx('html', testroot='html_assets')
131143
def test_copy_asset_template(app: SphinxTestApp) -> None:

0 commit comments

Comments
 (0)