From d2ba51bb746871f12120ec7d591529bab9423466 Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Tue, 7 Jul 2026 10:43:13 +0200 Subject: [PATCH 1/2] ci: prune noise-only files from deploy PRs The scheduled deploy workflow rebuilds every documentation version nightly. Each rebuild changes files even when their content is identical, because of Sphinx build-time timestamps and mermaid diagram zoom ids (random UUIDs regenerated on every build). The old gate only decided whether to open a PR at all: as soon as a single real change existed anywhere, every noise-only file was committed alongside it (e.g. PR #15278: 1337 files, most of them timestamp-only). Prune per file before opening the PR: - revert HTML whose entire diff is noise (timestamps, mermaid zoom ids) - revert pdf/epub for version folders left with no real HTML change - recompute has_changes from the pruned tree Files with genuine content changes are kept untouched. The result is a deploy PR that carries only real content updates. Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv --- .github/workflows/deploy-docs.yml | 42 +++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index b64cdf9a212..106f9fcf839 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -178,17 +178,45 @@ jobs: # Cleanup empty directories find . -type d -empty -delete - # Check for meaningful changes, ignoring: - # - lastupdated date lines in HTML (Sphinx build-time timestamps) - # - epub/pdf binaries (they regenerate automatically alongside HTML) - meaningful_html=$(git diff HEAD -- '*.html' | grep -E '^[+-]' | grep -v '^[+-]{3}' | grep -ivE 'lastupdated|Last updated on' | wc -l) - other_changes=$(git diff --name-only HEAD | grep -cvE '\.(html|epub|pdf)$' || true) + # ---------------------------------------------------------------- + # Prune build-only noise so the PR carries exclusively real content + # changes. A rebuilt page differs every night even when its content + # is identical, because of: + # - Sphinx build-time timestamps (lastupdated / "Last updated on") + # - mermaid diagram zoom ids (random UUID regenerated each build) + # Files whose ENTIRE diff is noise are reverted to HEAD; files with a + # real change are kept untouched (noise lines ride along, harmless). + # ---------------------------------------------------------------- + NOISE='lastupdated|Last updated on|data-zoom-id' + + # 1) Revert HTML files whose only changes are noise. + git diff --name-only HEAD -- '*.html' | while IFS= read -r f; do + [ -n "$f" ] || continue + real=$(git diff HEAD -- "$f" | grep -E '^[+-]' | grep -vE '^[+-]{3}' | grep -ivE "$NOISE" | wc -l) + [ "$real" -eq 0 ] && git checkout HEAD -- "$f" + done + + # 2) PDF/ePub binaries regenerate every build with no content change. + # Keep them only for a version folder that still has a real HTML + # change after step 1; otherwise revert them too. + git diff --name-only HEAD -- '*.pdf' '*.epub' | while IFS= read -r f; do + [ -n "$f" ] || continue + case "$f" in + server/*) + vdir="server/$(printf '%s' "${f#server/}" | cut -d/ -f1)" + remaining=$(git diff --name-only HEAD -- "$vdir" | grep -vE '\.(epub|pdf)$' | wc -l) + [ "$remaining" -eq 0 ] && git checkout HEAD -- "$f" + ;; + *) git checkout HEAD -- "$f" ;; + esac + done - if [ "$meaningful_html" -gt 0 ] || [ "$other_changes" -gt 0 ]; then + # Anything real left to deploy? (tracked diffs or brand-new files) + if [ -n "$(git status --porcelain)" ]; then echo "has_changes=true" >> $GITHUB_OUTPUT else echo "has_changes=false" >> $GITHUB_OUTPUT - echo "::notice::Skipping deploy PR: only lastupdated timestamps or epub/pdf binaries changed" + echo "::notice::Skipping deploy PR: only build noise (timestamps, mermaid ids, binaries) changed" fi - name: Strip noindex from stable docs From 9501e92bb085f3f5685faf834c7004b20b5aaef7 Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Tue, 7 Jul 2026 10:56:37 +0200 Subject: [PATCH 2/2] ci: fix index.lock race in deploy prune step The prune step piped 'git diff --name-only' into a while loop that ran 'git checkout' per file. The streaming diff refreshes the index and holds .git/index.lock while the loop's checkout tries to write it, racing and failing with 'Unable to create index.lock: File exists' (exit 128). Materialise each file list fully before any checkout, set GIT_OPTIONAL_LOCKS=0 so read-only diffs never take the lock, and batch the reverts into a single xargs git checkout call. Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv --- .github/workflows/deploy-docs.yml | 36 +++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 106f9fcf839..c77888fca35 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -189,27 +189,45 @@ jobs: # ---------------------------------------------------------------- NOISE='lastupdated|Last updated on|data-zoom-id' - # 1) Revert HTML files whose only changes are noise. - git diff --name-only HEAD -- '*.html' | while IFS= read -r f; do + # Read-only diffs must not take .git/index.lock: a `git diff` that + # refreshes the index while a `git checkout` writes it races on the + # lock. GIT_OPTIONAL_LOCKS=0 suppresses the refresh write; we also + # materialise each file list fully before running any checkout, and + # batch the reverts into a single checkout call. + export GIT_OPTIONAL_LOCKS=0 + + # 1) Collect HTML files whose only changes are noise, then revert them. + : > /tmp/revert-html.txt + git diff --name-only HEAD -- '*.html' > /tmp/changed-html.txt + while IFS= read -r f; do [ -n "$f" ] || continue real=$(git diff HEAD -- "$f" | grep -E '^[+-]' | grep -vE '^[+-]{3}' | grep -ivE "$NOISE" | wc -l) - [ "$real" -eq 0 ] && git checkout HEAD -- "$f" - done + [ "$real" -eq 0 ] && printf '%s\n' "$f" >> /tmp/revert-html.txt + done < /tmp/changed-html.txt + if [ -s /tmp/revert-html.txt ]; then + xargs -a /tmp/revert-html.txt -r git checkout HEAD -- + fi # 2) PDF/ePub binaries regenerate every build with no content change. # Keep them only for a version folder that still has a real HTML - # change after step 1; otherwise revert them too. - git diff --name-only HEAD -- '*.pdf' '*.epub' | while IFS= read -r f; do + # change after step 1; otherwise revert them too. Runs after the + # HTML revert so the per-folder check sees the pruned tree. + : > /tmp/revert-bin.txt + git diff --name-only HEAD -- '*.pdf' '*.epub' > /tmp/changed-bin.txt + while IFS= read -r f; do [ -n "$f" ] || continue case "$f" in server/*) vdir="server/$(printf '%s' "${f#server/}" | cut -d/ -f1)" remaining=$(git diff --name-only HEAD -- "$vdir" | grep -vE '\.(epub|pdf)$' | wc -l) - [ "$remaining" -eq 0 ] && git checkout HEAD -- "$f" + [ "$remaining" -eq 0 ] && printf '%s\n' "$f" >> /tmp/revert-bin.txt ;; - *) git checkout HEAD -- "$f" ;; + *) printf '%s\n' "$f" >> /tmp/revert-bin.txt ;; esac - done + done < /tmp/changed-bin.txt + if [ -s /tmp/revert-bin.txt ]; then + xargs -a /tmp/revert-bin.txt -r git checkout HEAD -- + fi # Anything real left to deploy? (tracked diffs or brand-new files) if [ -n "$(git status --porcelain)" ]; then