diff --git a/Makefile b/Makefile index 6c4e4fd..e19721a 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help run build preview aggregate aggregate-repo test-aggregate-local clean clean-projects clean-aggregated-git test test-unit test-integration check spelling linkcheck woke +.PHONY: help run build preview aggregate aggregate-repo aggregate-update-repo aggregate-repo-single aggregate-update-repo-single test-aggregate-local clean clean-projects clean-aggregated-git test test-unit test-integration check spelling linkcheck woke help: @echo "Garden Linux Documentation Hub - Available targets:" @@ -23,8 +23,10 @@ help: @echo " aggregate-local - Aggregate from local repos (file:// URLs in repos-config.local.json)" @echo " aggregate - Aggregate from locked commits (repos-config.json)" @echo " aggregate-update - Fetch latest from remotes and update commit locks" - @echo " aggregate-repo - Aggregate single repo (usage: make aggregate-repo REPO=gardenlinux)" - @echo " aggregate-update-repo - Update single repo to latest (usage: make aggregate-update-repo REPO=gardenlinux)" + @echo " aggregate-repo - Aggregate all repos, overrides scoped to REPO (usage: make aggregate-repo REPO=gardenlinux [REF=branch] [COMMIT=hash])" + @echo " aggregate-update-repo - Same as aggregate-repo plus update commit locks" + @echo " aggregate-repo-single - Aggregate only REPO (usage: make aggregate-repo-single REPO=gardenlinux [REF=branch] [COMMIT=hash])" + @echo " aggregate-update-repo-single - Aggregate only REPO and update its commit lock (same optional REF/COMMIT)" @echo "" @echo " Utilities:" @echo " clean - Clean aggregated docs and build artifacts" @@ -90,20 +92,46 @@ aggregate-update: aggregate-repo: @if [ -z "$(REPO)" ]; then \ echo "Error: REPO variable not set"; \ - echo "Usage: make aggregate-repo REPO=gardenlinux"; \ + echo "Usage: make aggregate-repo REPO=gardenlinux [REF=branch] [COMMIT=hash]"; \ exit 1; \ fi - @echo "Aggregating documentation for locked repository: $(REPO)" - python3 src/aggregate.py --repo $(REPO) + @echo "Aggregating all repositories with overrides scoped to: $(REPO)" + python3 src/aggregate.py --repo $(REPO) \ + $(if $(REF),--override-ref $(REF)) \ + $(if $(COMMIT),--override-commit $(COMMIT)) aggregate-update-repo: @if [ -z "$(REPO)" ]; then \ echo "Error: REPO variable not set"; \ - echo "Usage: make aggregate-update-repo REPO=gardenlinux"; \ + echo "Usage: make aggregate-update-repo REPO=gardenlinux [REF=branch] [COMMIT=hash]"; \ exit 1; \ fi - @echo "Aggregating documentation for locked repository: $(REPO)" - python3 src/aggregate.py --update-locks --repo $(REPO) + @echo "Aggregating all repositories with overrides scoped to: $(REPO) (updating locks)" + python3 src/aggregate.py --update-locks --repo $(REPO) \ + $(if $(REF),--override-ref $(REF)) \ + $(if $(COMMIT),--override-commit $(COMMIT)) + +aggregate-repo-single: + @if [ -z "$(REPO)" ]; then \ + echo "Error: REPO variable not set"; \ + echo "Usage: make aggregate-repo-single REPO=gardenlinux [REF=branch] [COMMIT=hash]"; \ + exit 1; \ + fi + @echo "Aggregating single repository: $(REPO)" + python3 src/aggregate.py --repo $(REPO) --single \ + $(if $(REF),--override-ref $(REF)) \ + $(if $(COMMIT),--override-commit $(COMMIT)) + +aggregate-update-repo-single: + @if [ -z "$(REPO)" ]; then \ + echo "Error: REPO variable not set"; \ + echo "Usage: make aggregate-update-repo-single REPO=gardenlinux [REF=branch] [COMMIT=hash]"; \ + exit 1; \ + fi + @echo "Aggregating single repository: $(REPO) (updating lock)" + python3 src/aggregate.py --repo $(REPO) --single --update-locks \ + $(if $(REF),--override-ref $(REF)) \ + $(if $(COMMIT),--override-commit $(COMMIT)) # Utilities clean: diff --git a/docs/contributing/documentation/adding-repos.md b/docs/contributing/documentation/adding-repos.md index 1c17cb4..72d3045 100644 --- a/docs/contributing/documentation/adding-repos.md +++ b/docs/contributing/documentation/adding-repos.md @@ -152,8 +152,8 @@ ls -la docs/how-to/ For production, lock to a specific commit: ```bash -# This fetches the latest and updates repos-config.json -make aggregate-update-repo REPO=new-repo +# This fetches only the new repo and updates repos-config.json with its commit hash +make aggregate-update-repo-single REPO=new-repo ``` Or manually add the commit hash: diff --git a/docs/contributing/documentation/working-locally.md b/docs/contributing/documentation/working-locally.md index 75eb8df..f6cb6fc 100644 --- a/docs/contributing/documentation/working-locally.md +++ b/docs/contributing/documentation/working-locally.md @@ -102,8 +102,24 @@ This creates a production build in `docs/.vitepress/dist/`. ### Updating a Single Repository +To re-aggregate only one repository without re-fetching all others: + +```bash +make aggregate-repo-single REPO=gardenlinux +``` + +To re-aggregate a single repository at a specific ref or commit: + +```bash +make aggregate-repo-single REPO=gardenlinux REF=feature/my-branch +make aggregate-repo-single REPO=gardenlinux REF=feature/my-branch COMMIT=abc123def +``` + +To apply a ref or commit override to one repository while re-aggregating all +repos (for example, to test a feature branch across the full docs tree): + ```bash -make aggregate-repo REPO=gardenlinux +make aggregate-repo REPO=gardenlinux REF=feature/my-branch ``` ### Updating Lock Files diff --git a/src/aggregate.py b/src/aggregate.py index e998278..48b8a40 100755 --- a/src/aggregate.py +++ b/src/aggregate.py @@ -105,20 +105,23 @@ def main() -> int: formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: - # Aggregate all repositories + # Aggregate all repositories (default) %(prog)s - # Aggregate with local config (file:// URLs, no git) - %(prog)s --config repos-config.local.json + # Aggregate all repositories (explicit) + %(prog)s --all - # Aggregate specific repository - %(prog)s --repo gardenlinux - - # Aggregate with ref/commit override (for CI from external repos) + # Aggregate all repos, applying overrides to one repo (used in CI) %(prog)s --repo gardenlinux --override-ref feature/my-docs --override-commit abc123def - # Update commit locks (fetch and update config with resolved commit hashes) + # Aggregate only a single repository + %(prog)s --repo gardenlinux --single + + # Update commit locks for all repos %(prog)s --update-locks + + # Update commit lock for a single repo only + %(prog)s --repo gardenlinux --single --update-locks """, ) @@ -134,7 +137,7 @@ def main() -> int: ) parser.add_argument( "--repo", - help="Only aggregate specific repository", + help="Scope overrides to this repository; required when --single is used", ) parser.add_argument( "--update-locks", @@ -150,8 +153,26 @@ def main() -> int: help="Override commit for the repo specified by --repo", ) + mode_group = parser.add_mutually_exclusive_group() + mode_group.add_argument( + "--all", + action="store_true", + help="Aggregate all repositories (default behaviour; explicit alias)", + ) + mode_group.add_argument( + "--single", + action="store_true", + help="Aggregate only the repository specified by --repo", + ) + args = parser.parse_args() + # Validate argument combinations + if args.single and not args.repo: + parser.error("--single requires --repo NAME") + if (args.override_ref or args.override_commit) and not args.repo: + parser.error("--override-ref/--override-commit require --repo NAME") + # Determine script directory script_dir = Path(__file__).parent.resolve() project_root = script_dir.parent @@ -174,8 +195,10 @@ def main() -> int: print(f"{'='*60}\n") print(f"Configuration: {config_path}") print(f"Docs directory: {docs_dir}") - if args.repo: - print(f"Repository filter: {args.repo}") + if args.single and args.repo: + print(f"Single-repo mode: {args.repo}") + elif args.repo: + print(f"Override scoped to: {args.repo}") if args.update_locks: print("Update commit locks: ENABLED") print() @@ -194,7 +217,21 @@ def main() -> int: print(f"Override commit for {repo.name}: {args.override_commit}") break else: - print(f"WARNING: Repository '{args.repo}' not found for override") + parser.error( + f"Repository '{args.repo}' not found in config; " + "cannot apply --override-ref/--override-commit" + ) + elif args.repo: + # --repo without overrides: validate the name exists when --single is used + # (filter will silently aggregate nothing if the name is wrong) + repo_names = {repo.name for repo in repos} + if args.repo not in repo_names: + if args.single: + parser.error( + f"Repository '{args.repo}' not found in config" + ) + else: + print(f"WARNING: Repository '{args.repo}' not found in config") # Create temporary directory for fetched docs with tempfile.TemporaryDirectory() as temp_dir_str: @@ -211,8 +248,8 @@ def main() -> int: # Aggregate each repository for repo in repos: - # Filter by repo if specified - if args.repo and repo.name != args.repo: + # Filter to a single repo when --single is set + if args.single and repo.name != args.repo: continue success, resolved_commit = aggregate_repo(