Skip to content

Commit c2c7b57

Browse files
committed
fix(ci): preserve URL authority and raw path semantics
Signed-off-by: Chen Cui <chcui@nvidia.com>
1 parent f3e8008 commit c2c7b57

3 files changed

Lines changed: 39 additions & 2 deletions

File tree

‎.github/workflows/link-check.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2025-2026, NVIDIA CORPORATION.
1+
# Copyright (c) 2025, NVIDIA CORPORATION.
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.

‎scripts/docs/check_repository_links.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ def repository_target(url: str, *, repository: str, branch: str) -> tuple[str, s
4646
parsed = urlsplit(url)
4747
if parsed.scheme not in {"http", "https"}:
4848
raise ValueError(f"Expected an HTTP(S) URL from Lychee, got {url!r}")
49+
# A different authority is an online destination even when hostname matches.
50+
default_port = 443 if parsed.scheme == "https" else 80
51+
if parsed.username is not None or parsed.password is not None or parsed.port not in {None, default_port}:
52+
return None
4953
parts = parsed.path.lstrip("/").split("/", 2)
5054
if len(parts) != 3 or "/".join(parts[:2]).casefold() != repository.casefold():
5155
return None
@@ -68,7 +72,9 @@ def repository_target(url: str, *, repository: str, branch: str) -> tuple[str, s
6872
for end in range(1, len(segments) + 1):
6973
candidate = unquote("/".join(segments[:end]))
7074
if candidate == ref:
71-
return kind, unquote("/".join(segments[end:])).rstrip("/")
75+
path = unquote("/".join(segments[end:]))
76+
# GitHub HTML redirects trailing slashes; raw content returns 404.
77+
return kind, path.rstrip("/") if kind == "object" else path
7278
if len(candidate) >= len(ref):
7379
break
7480
return None

‎tests/unit_tests/doc_consistency/test_repository_links.py‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
115
"""Candidate-tree link checks need no model runtime or network access."""
216

317
import importlib.util
@@ -78,13 +92,16 @@ def test_added_target_passes_before_merge_and_missing_target_fails(repo: Path) -
7892
"url",
7993
[
8094
f"{PREFIX}/blob/main/src/retired.py#L1",
95+
f"{PREFIX}/blob/main/src/retired.py/",
8196
f"{PREFIX}/tree/main/src/retired.py?plain=1#L1",
8297
f"{PREFIX}/tree/main/src/",
8398
f"{PREFIX}/blob/main/src/",
8499
f"{PREFIX}/tree/main",
85100
f"https://raw.githubusercontent.com/{REPOSITORY}/main/src/retired.py",
86101
f"https://raw.githubusercontent.com/{REPOSITORY}/refs/heads/main/src/retired.py",
87102
f"http://github.com/{REPOSITORY.lower()}/blob/main/src/retired.py",
103+
f"https://github.com:443/{REPOSITORY}/blob/main/src/retired.py",
104+
f"http://github.com:80/{REPOSITORY}/blob/main/src/retired.py",
88105
],
89106
)
90107
def test_supported_github_and_raw_urls(repo: Path, url: str) -> None:
@@ -95,6 +112,15 @@ def test_raw_url_requires_file(repo: Path) -> None:
95112
assert len(check(repo, [f"https://raw.githubusercontent.com/{REPOSITORY}/main/src"])[1]) == 1
96113

97114

115+
@pytest.mark.parametrize("suffix", ["/", "%2F"])
116+
def test_raw_file_trailing_slash_fails(repo: Path, suffix: str) -> None:
117+
url = f"https://raw.githubusercontent.com/{REPOSITORY}/main/src/retired.py{suffix}"
118+
external, errors = check(repo, [url])
119+
assert external == []
120+
assert len(errors) == 1
121+
assert "missing" in errors[0]
122+
123+
98124
@pytest.mark.parametrize(
99125
"url",
100126
[
@@ -109,6 +135,11 @@ def test_raw_url_requires_file(repo: Path) -> None:
109135
"https://github.com/another/repo/blob/main/missing.py",
110136
f"https://example.com/{REPOSITORY}/blob/main/missing.py",
111137
f"https://github.com.evil.example/{REPOSITORY}/blob/main/missing.py",
138+
f"https://github.com:8443/{REPOSITORY}/blob/main/src/retired.py",
139+
f"https://raw.githubusercontent.com:8443/{REPOSITORY}/main/src/retired.py",
140+
f"https://github.com:80/{REPOSITORY}/blob/main/src/retired.py",
141+
f"http://github.com:443/{REPOSITORY}/blob/main/src/retired.py",
142+
f"https://user@github.com/{REPOSITORY}/blob/main/src/retired.py",
112143
],
113144
)
114145
def test_explicit_versions_and_external_urls_remain_online(repo: Path, url: str) -> None:

0 commit comments

Comments
 (0)