From c05e9fa92fb1afc4bcaaeb123e635c24004c4ed9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 11 Aug 2026 22:30:47 +0000 Subject: [PATCH 1/4] Initial plan From 7f0e4861423baa20a4885618d4ff0b98d2cb1095 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 11 Aug 2026 22:36:14 +0000 Subject: [PATCH 2/4] Use merge-base for link-check diffs Co-authored-by: psiddh <2467117+psiddh@users.noreply.github.com> --- .../tests/test_link_check_diff_selection.py | 111 ++++++++++++++++++ scripts/lint_urls.sh | 4 +- scripts/lint_xrefs.sh | 4 +- 3 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 .ci/scripts/tests/test_link_check_diff_selection.py diff --git a/.ci/scripts/tests/test_link_check_diff_selection.py b/.ci/scripts/tests/test_link_check_diff_selection.py new file mode 100644 index 00000000000..5a8927c9f9c --- /dev/null +++ b/.ci/scripts/tests/test_link_check_diff_selection.py @@ -0,0 +1,111 @@ +import os +import stat +import subprocess +import sys +import tempfile +import textwrap +import unittest +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[3] + + +@unittest.skipUnless(sys.platform == "linux", "The scripts under test run on Linux runners only") +class TestLinkCheckDiffSelection(unittest.TestCase): + def setUp(self) -> None: + self._tmpdir = tempfile.TemporaryDirectory() + self.repo = Path(self._tmpdir.name) + self.run_cmd("git init -b main") + self.run_cmd('git config user.name "Test User"') + self.run_cmd('git config user.email "test@example.com"') + self.write("README.md", "base\n") + self.write("docs/feature-target.md", "feature target\n") + self.write("docs/main-target.md", "main target\n") + self.run_cmd("git add README.md docs/feature-target.md docs/main-target.md") + self.run_cmd('git commit -m "base"') + self.run_cmd("git checkout -b feature") + + def tearDown(self) -> None: + self._tmpdir.cleanup() + + def run_cmd( + self, command: str, env: dict[str, str] | None = None + ) -> subprocess.CompletedProcess[str]: + return subprocess.run( + command, + shell=True, + cwd=self.repo, + env=env, + check=True, + text=True, + capture_output=True, + ) + + def write(self, relative_path: str, content: str) -> None: + path = self.repo / relative_path + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content) + + def create_diverged_history( + self, feature_path: str, feature_content: str, main_path: str, main_content: str + ) -> tuple[str, str]: + self.write(feature_path, feature_content) + self.run_cmd(f"git add {feature_path}") + self.run_cmd('git commit -m "feature change"') + head_sha = self.run_cmd("git rev-parse HEAD").stdout.strip() + + self.run_cmd("git checkout main") + self.write(main_path, main_content) + self.run_cmd(f"git add {main_path}") + self.run_cmd('git commit -m "main change"') + base_sha = self.run_cmd("git rev-parse HEAD").stdout.strip() + return base_sha, head_sha + + def make_fake_curl(self) -> dict[str, str]: + bin_dir = self.repo / "bin" + bin_dir.mkdir() + curl = bin_dir / "curl" + curl.write_text("#!/bin/sh\nprintf '200'\n") + curl.chmod(curl.stat().st_mode | stat.S_IEXEC) + env = os.environ.copy() + env["PATH"] = f"{bin_dir}:{env['PATH']}" + return env + + def test_lint_urls_uses_merge_base_for_changed_lines(self) -> None: + base_sha, head_sha = self.create_diverged_history( + "feature.md", + "https://example.com/feature\n", + "main.md", + "https://example.com/main\n", + ) + + result = self.run_cmd( + f"bash {REPO_ROOT / 'scripts' / 'lint_urls.sh'} {base_sha} {head_sha}", + env=self.make_fake_curl(), + ) + + self.assertIn("feature.md", result.stdout) + self.assertNotIn("main.md", result.stdout) + + def test_lint_xrefs_uses_merge_base_for_changed_lines(self) -> None: + base_sha, head_sha = self.create_diverged_history( + "feature.md", + textwrap.dedent( + """\ + [feature](docs/feature-target.md) + """ + ), + "main.md", + textwrap.dedent( + """\ + [main](docs/main-target.md) + """ + ), + ) + + result = self.run_cmd( + f"bash {REPO_ROOT / 'scripts' / 'lint_xrefs.sh'} {base_sha} {head_sha}" + ) + + self.assertIn("feature.md", result.stdout) + self.assertNotIn("main.md", result.stdout) diff --git a/scripts/lint_urls.sh b/scripts/lint_urls.sh index 92b57171bc1..0ca791bc167 100755 --- a/scripts/lint_urls.sh +++ b/scripts/lint_urls.sh @@ -70,8 +70,8 @@ done < <( ':(exclude,glob)**/third_party/**' ) if [ $# -eq 2 ]; then - for filename in $(git diff --name-only --unified=0 "$1..$2"); do - git diff --unified=0 "$1..$2" -- "$filename" "${excludes[@]}" \ + for filename in $(git diff --name-only --unified=0 "$1...$2"); do + git diff --unified=0 "$1...$2" -- "$filename" "${excludes[@]}" \ | grep -E '^\+' \ | grep -Ev '^\+\+\+' \ | perl -nle 'print for m#'"$pattern"'#g' \ diff --git a/scripts/lint_xrefs.sh b/scripts/lint_xrefs.sh index 54917b26d8e..3b836df038f 100755 --- a/scripts/lint_xrefs.sh +++ b/scripts/lint_xrefs.sh @@ -35,8 +35,8 @@ done < <( ':(exclude,glob)**/third_party/**' ) if [ $# -eq 2 ]; then - for filename in $(git diff --name-only --unified=0 "$1..$2"); do - git diff --unified=0 "$1..$2" -- "$filename" "${excludes[@]}" \ + for filename in $(git diff --name-only --unified=0 "$1...$2"); do + git diff --unified=0 "$1...$2" -- "$filename" "${excludes[@]}" \ | grep -E '^\+' \ | grep -Ev '^\+\+\+' \ | perl -nle 'print for m#'"$pattern"'#g' \ From 2d58ad71aa9d8e14e132252cf9bb45ea482812b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 11 Aug 2026 23:05:35 +0000 Subject: [PATCH 3/4] Fix xref lint workflow base fetch depth Co-authored-by: psiddh <2467117+psiddh@users.noreply.github.com> --- .github/workflows/_link_check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_link_check.yml b/.github/workflows/_link_check.yml index a713de1c0dc..6fe188861b2 100644 --- a/.github/workflows/_link_check.yml +++ b/.github/workflows/_link_check.yml @@ -29,7 +29,7 @@ jobs: args=() # A push creating a tag or branch reports an all zero SHA no remote can serve. if [ -n "$BASE_REF" ] && [ "$BASE_REF" != "0000000000000000000000000000000000000000" ]; then - git fetch --no-tags --depth=1 origin "$BASE_REF" + git fetch --no-tags origin "$BASE_REF" args=("$BASE_REF" "$HEAD_REF") fi ./scripts/lint_urls.sh "${args[@]}" || { From ed0c8cd4455ad5c90cd82dbd64ca319ab23201cc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 11 Aug 2026 23:06:17 +0000 Subject: [PATCH 4/4] Use two-dot diff in lint_urls Co-authored-by: psiddh <2467117+psiddh@users.noreply.github.com> --- scripts/lint_urls.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/lint_urls.sh b/scripts/lint_urls.sh index 0ca791bc167..92b57171bc1 100755 --- a/scripts/lint_urls.sh +++ b/scripts/lint_urls.sh @@ -70,8 +70,8 @@ done < <( ':(exclude,glob)**/third_party/**' ) if [ $# -eq 2 ]; then - for filename in $(git diff --name-only --unified=0 "$1...$2"); do - git diff --unified=0 "$1...$2" -- "$filename" "${excludes[@]}" \ + for filename in $(git diff --name-only --unified=0 "$1..$2"); do + git diff --unified=0 "$1..$2" -- "$filename" "${excludes[@]}" \ | grep -E '^\+' \ | grep -Ev '^\+\+\+' \ | perl -nle 'print for m#'"$pattern"'#g' \