Skip to content

Commit c56f6fd

Browse files
bridadanBrian Daniels
andauthored
Add script to check that deleted slides have redirects (#3174)
Fixes #1417. This adds a script and a CI job to ensure that slides that are deleted have a matching redirect entry in `bool.toml`. If a redirect entry is not found (for example, for `src/hello-world.md`), the following is printed before exiting with a status of 1: ``` The following deleted files have missing redirect entries in book.toml: src/hello-world.md ``` Co-authored-by: Brian Daniels <briandaniels@google.com>
1 parent 7501a42 commit c56f6fd

2 files changed

Lines changed: 139 additions & 0 deletions

File tree

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Copyright 2026 Google LLC
2+
# SPDX-License-Identifier: Apache-2.0
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
"""Ensure all deleted files have a redirect entry.
16+
17+
First, the code finds all deleted ".md" files under the "src/" directory between
18+
the current working copy and the specificed commit/branch.
19+
20+
Next, for each deleted ".md" file it searches for a corresponding entry in
21+
"book.toml" under the "[output.html.redirect]" section. The ".md" extension
22+
should be swapped with a ".html" extension and a "src/" prefix add to it.
23+
Any deleted ".md" file that is not found is printed to stdout.
24+
25+
If any deleted ".md" files are missing a redirect entry, the script exits with
26+
a value of 1. Otherwise, it exits with a value of 0.
27+
28+
Usage:
29+
check-redirects.py <root directory of the repository> <commit to compare (e.g. origin/main)>
30+
"""
31+
32+
import os
33+
import subprocess
34+
import sys
35+
36+
37+
def main():
38+
if len(sys.argv) != 3:
39+
print(
40+
"Usage: check-redirects.py <root directory of the repository> <commit to compare (e.g. origin/main)>"
41+
)
42+
sys.exit(1)
43+
44+
repo_root = sys.argv[1]
45+
commit = sys.argv[2]
46+
47+
if not os.path.isdir(repo_root):
48+
print(f"Error: {repo_root} is not a directory")
49+
sys.exit(1)
50+
51+
# Change to repo root to run git commands and find book.toml
52+
os.chdir(repo_root)
53+
54+
# Get deleted .md files under src/
55+
try:
56+
diff_output = subprocess.check_output(
57+
["git", "diff", commit, "--diff-filter=D", "--name-only"],
58+
text=True)
59+
except subprocess.CalledProcessError as e:
60+
print(f"Error running git diff: {e}")
61+
sys.exit(1)
62+
63+
deleted_files = [
64+
f for f in diff_output.splitlines()
65+
if f.startswith("src/") and f.endswith(".md")
66+
]
67+
68+
if not deleted_files:
69+
sys.exit(0)
70+
71+
# Read book.toml and extract redirects
72+
book_toml_path = "book.toml"
73+
if not os.path.exists(book_toml_path):
74+
print(f"Error: {book_toml_path} not found in {repo_root}")
75+
sys.exit(1)
76+
77+
with open(book_toml_path, "r") as f:
78+
lines = f.readlines()
79+
80+
redirects = set()
81+
in_redirect_section = False
82+
for line in lines:
83+
line = line.strip()
84+
if not line or line.startswith("#"):
85+
continue
86+
87+
if line.startswith("[") and line.endswith("]"):
88+
if line == "[output.html.redirect]":
89+
in_redirect_section = True
90+
else:
91+
in_redirect_section = False
92+
continue
93+
94+
if in_redirect_section:
95+
# Entry looks like "old.html" = "new.html" or "old.html" = "/new"
96+
if "=" in line:
97+
key = line.split("=")[0].strip().strip("\"'")
98+
redirects.add(key)
99+
100+
missing_redirects = []
101+
for md_file in deleted_files:
102+
# Swap .md with .html and remove src/ prefix if it's relative to site root
103+
# Usually redirects in book.toml for mdbook are relative to the output root.
104+
# If the file is src/foo/bar.md, it becomes foo/bar.html in the output.
105+
html_file = md_file.replace("src/", "", 1).replace(".md", ".html")
106+
if html_file not in redirects:
107+
missing_redirects.append(md_file)
108+
109+
if missing_redirects:
110+
print(
111+
"The following deleted files are missing a redirect entry in book.toml:"
112+
)
113+
for f in missing_redirects:
114+
print(f)
115+
sys.exit(1)
116+
117+
sys.exit(0)
118+
119+
120+
if __name__ == "__main__":
121+
main()
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: Check that redirects are valid
2+
permissions:
3+
contents: read
4+
5+
on:
6+
pull_request:
7+
8+
jobs:
9+
check-redirects:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Checkout
13+
uses: actions/checkout@v6
14+
with:
15+
fetch-depth: 0
16+
17+
- name: Check redirects
18+
run: python3 .github/workflows/check-redirects.py . origin/main

0 commit comments

Comments
 (0)