Skip to content

Commit 2d7cf72

Browse files
Roger-luoclaude
andauthored
ci: keep gh-pages small to avoid bloating clones (#365)
## Why The `gh-pages` branch had grown to **811 commits / ~147 MB packed** because every docs deploy (`mike deploy dev` on each merge, a version dir per release, per-PR previews) commits a full copy of the built site and nothing ever reclaims that history. A default `git clone` pulls **all** branches, so every contributor was downloading ~150 MB even though only `main` (~11 MB) is needed for development. The history has already been purged out-of-band (gh-pages rebuilt as a single orphan commit; full pre-purge site archived to the [`docs-archive-2026-07-10`](https://github.com/QuEraComputing/bloqade/releases/tag/docs-archive-2026-07-10) release). **Fresh clone dropped ~152 MB → ~29 MB.** This PR adds the guards so it doesn't creep back. ## What - **`.github/workflows/squash-gh-pages.yml`** (new) — monthly + `workflow_dispatch` job that collapses `gh-pages` to a single orphan commit. Safe: the branch is a generated artifact and every version is rebuildable from its release tag. - **`.github/workflows/pub_doc.yml`** — after each release deploy, prune the live site to `dev` + the newest **3** releases (`KEEP_RELEASES`). Older versions remain rebuildable from tags and archived in the release asset. - **`.github/scripts/select_old_doc_versions.py`** (new) — small, unit-tested stdin→stdout filter that picks which versions to delete (handles semver ordering, e.g. `v0.10.0` > `v0.9.0`). - **Shared `gh-pages-write` concurrency group** across the dev deploy, release deploy, and squash job so concurrent pushes can't clobber the branch. `cancel-in-progress: false` (cancelling a mike push mid-flight could corrupt gh-pages). ## Notes / follow-ups - `cancel-in-progress` on the dev-docs deploy changes from `true` → `false`: rapid merges to `main` now queue sequential dev deploys instead of cancelling the older one. The monthly squash reclaims the extra commits. - The pr-preview workflow (`doc.yml`) is intentionally left out of the shared lock to preserve its per-PR cancel behaviour; it already races mike deploys today (pre-existing) and the monthly squash runs at a quiet time (03:00 UTC on the 1st). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 4f3b7fb commit 2d7cf72

4 files changed

Lines changed: 118 additions & 4 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/usr/bin/env python3
2+
"""Select stale mike doc versions to prune from the live site.
3+
4+
Reads `mike list -j` JSON on stdin and prints, one per line, the release
5+
versions that should be deleted so that only the newest ``KEEP_RELEASES``
6+
releases remain live (``dev`` and any non-release entries are always kept).
7+
8+
Older versions stay rebuildable from their git release tags and are captured
9+
in the ``docs-archive-*`` GitHub release asset, so pruning them from the
10+
deployed site loses nothing permanent.
11+
"""
12+
13+
import json
14+
import os
15+
import re
16+
import sys
17+
18+
RELEASE_RE = re.compile(r"^v?\d+\.\d+")
19+
20+
21+
def is_release(version: str) -> bool:
22+
return RELEASE_RE.match(version) is not None
23+
24+
25+
def sem_key(version: str):
26+
return [int(n) for n in re.findall(r"\d+", version)]
27+
28+
29+
def main() -> int:
30+
keep = int(os.environ.get("KEEP_RELEASES", "3"))
31+
entries = json.load(sys.stdin)
32+
releases = sorted(
33+
(e["version"] for e in entries if is_release(e["version"])),
34+
key=sem_key,
35+
reverse=True,
36+
)
37+
for version in releases[keep:]:
38+
print(version)
39+
return 0
40+
41+
42+
if __name__ == "__main__":
43+
raise SystemExit(main())

.github/workflows/devdoc.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ on:
44
branches:
55
- main
66

7+
# Serialize with every other workflow that writes to gh-pages (release deploy,
8+
# history squash) so concurrent pushes can't clobber the branch. Queued dev
9+
# deploys run in order rather than cancelling an in-flight gh-pages push.
710
concurrency:
8-
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
9-
cancel-in-progress: true
11+
group: gh-pages-write
12+
cancel-in-progress: false
1013

1114
jobs:
1215
documentation:

.github/workflows/pub_doc.yml

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ on:
44
tags:
55
- "v*"
66

7+
# Serialize with every other workflow that writes to gh-pages (dev deploy,
8+
# release deploy, history squash) so concurrent pushes can't clobber the branch.
9+
# cancel-in-progress must stay false here: cancelling a mike push mid-flight
10+
# could leave gh-pages in a broken state.
711
concurrency:
8-
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
9-
cancel-in-progress: true
12+
group: gh-pages-write
13+
cancel-in-progress: false
1014

1115
jobs:
1216
documentation:
@@ -55,3 +59,18 @@ jobs:
5559
run: |
5660
git fetch origin gh-pages --depth=1
5761
uv run mike deploy --update-alias --push ${TAG_VERSION} latest
62+
# Keep the live site small: retain only `dev` plus the newest N release
63+
# versions. Older versions stay rebuildable from their release tags and
64+
# are captured in the `docs-archive-*` release asset.
65+
- name: Prune old versions from the live site
66+
env:
67+
KEEP_RELEASES: "3"
68+
run: |
69+
git fetch origin gh-pages --depth=1
70+
to_delete=$(uv run mike list -j | python3 .github/scripts/select_old_doc_versions.py)
71+
if [ -n "$to_delete" ]; then
72+
echo "Deleting old versions: $to_delete"
73+
uv run mike delete --push $to_delete
74+
else
75+
echo "Nothing to prune."
76+
fi
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Keeps the gh-pages branch from re-accumulating build history.
2+
#
3+
# Every docs deploy (mike / pr-preview) adds a full commit to gh-pages, so its
4+
# history grows without bound even though only the tip is ever served. This job
5+
# periodically collapses gh-pages to a single orphan commit. The content is a
6+
# generated artifact and every version is rebuildable from its release tag, so
7+
# discarding the history is always safe.
8+
name: Compact gh-pages history
9+
10+
on:
11+
schedule:
12+
# 03:00 UTC on the 1st of each month (a quiet time, to avoid racing deploys)
13+
- cron: "0 3 1 * *"
14+
workflow_dispatch: {}
15+
16+
# Share a lock with the workflows that write to gh-pages so a squash never
17+
# races a mike/pr-preview push.
18+
concurrency:
19+
group: gh-pages-write
20+
cancel-in-progress: false
21+
22+
permissions:
23+
contents: write
24+
25+
jobs:
26+
squash:
27+
name: Squash gh-pages to a single commit
28+
runs-on: ubuntu-latest
29+
steps:
30+
- uses: actions/checkout@v6
31+
with:
32+
ref: gh-pages
33+
fetch-depth: 0
34+
35+
- name: Collapse history into one orphan commit
36+
run: |
37+
git config user.name "github-actions[bot]"
38+
git config user.email "github-actions[bot]@users.noreply.github.com"
39+
before=$(git rev-list --count HEAD)
40+
if [ "$before" -le 1 ]; then
41+
echo "gh-pages already has $before commit(s); nothing to squash."
42+
exit 0
43+
fi
44+
git checkout --orphan _squash
45+
git add -A
46+
git commit -q -m "docs: compact gh-pages (squash $before commits)"
47+
git branch -M _squash gh-pages
48+
git push --force origin gh-pages
49+
echo "Squashed gh-pages from $before commits to 1."

0 commit comments

Comments
 (0)