diff --git a/.github/workflows/lab-archive.yml b/.github/workflows/lab-archive.yml new file mode 100644 index 0000000000..d29aeb2bf0 --- /dev/null +++ b/.github/workflows/lab-archive.yml @@ -0,0 +1,60 @@ +name: Archive Labs on Commit + +on: + push: + branches: + - 'main' + +permissions: + contents: write + +jobs: + archive-labs: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Install dependencies + run: pip install pyyaml + + - name: Run archive script + run: python3 gen-zip.py config.yaml + + - name: Push archives to lab-archives branch + if: success() + run: | + if [ -z "$(ls -A *.zip 2>/dev/null)" ]; then + echo "No archives generated — nothing to push." + exit 0 + fi + + # Create or switch to lab-archives branch + if git ls-remote --exit-code origin lab-archives; then + git fetch origin lab-archives + git checkout lab-archives + else + git checkout --orphan lab-archives + git rm -rf . + fi + + # Copy new zips into branch root + git add *.zip + + # Only commit if there are changes + if ! git diff --cached --quiet; then + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git commit -m "Update lab archives for commit $GITHUB_SHA" + git push origin lab-archives + else + echo "No changes to commit." + fi diff --git a/gen-zip.py b/gen-zip.py new file mode 100644 index 0000000000..a456f804a6 --- /dev/null +++ b/gen-zip.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: BSD-3-Clause + +import yaml +import sys +import re +import subprocess +import os +import zipfile +from pathlib import Path + + +def load_yaml(yaml_path): + with open(yaml_path, "r") as f: + return yaml.safe_load(f) + + +def extract_task_info_by_lab(data): + """ + Return a dict: + lab_title -> { + 'tasks': [ 'tasks/memory-access.md', ... ], + 'basenames': [ 'memory-access.md', ... ], + 'stems': [ 'memory-access', ... ] + } + """ + labs = data.get("lab_structure", []) + lab_info = {} + + for lab in labs: + title = lab.get("title", "Untitled Lab") + content = lab.get("content", []) + task_files = [c for c in content if c.startswith("tasks/")] + basenames = [Path(t).name for t in task_files] + stems = [Path(t).stem for t in task_files] + + lab_info[title] = {"tasks": task_files, "basenames": basenames, "stems": stems} + + return lab_info + + +def get_changed_files_last_commit(): + """Return a list of file paths changed in the last commit.""" + cmd = ["git", "diff", "--name-only", "HEAD~1", "HEAD"] + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return [line.strip() for line in result.stdout.splitlines() if line.strip()] + + +def find_directories_by_name(dirname): + """ + Return a list of directories in the repo that match the given base name. + Example: dirname='memory-access' matches any '.../memory-access' dir. + """ + matches = [] + for root, dirs, _ in os.walk("."): + for d in dirs: + if d == dirname: + matches.append(os.path.join(root, d)) + return matches + + +def create_zip_archive(lab_title, directories): + """ + Create a zip archive named after the lab title containing all directories. + """ + safe_name = re.sub(r"[^a-zA-Z0-9_]+", "_", lab_title) + zip_filename = f"{safe_name}.zip" + + with zipfile.ZipFile(zip_filename, "w", zipfile.ZIP_DEFLATED) as zipf: + for d in directories: + for folder, _, files in os.walk(d): + for file in files: + file_path = os.path.join(folder, file) + arcname = os.path.relpath(file_path, d) + zipf.write(file_path, os.path.join(os.path.basename(d), arcname)) + + return zip_filename + + +def main(): + if len(sys.argv) != 2: + sys.exit(1) + + yaml_path = sys.argv[1] + + # 1. Parse YAML and extract task info + data = load_yaml(yaml_path) + lab_info = extract_task_info_by_lab(data) + + # 2. Get changed files from last commit + changed_files = get_changed_files_last_commit() + changed_basenames = [Path(f).name for f in changed_files] + + # 3. Identify labs to archive + for lab_title, info in lab_info.items(): + # Check if any changed filename matches one of the task basenames + stems = info["stems"] + lab_changed = any( + any(stem.lower() in changed_path.lower() for stem in stems) + for changed_path in changed_files + ) + + if not lab_changed: + continue + + # Collect all directories for all task stems in this lab + dirs_to_zip = [] + for stem in info["stems"]: + found_dirs = find_directories_by_name(stem) + if found_dirs: + dirs_to_zip.extend(found_dirs) + + if dirs_to_zip: + create_zip_archive(lab_title, dirs_to_zip) + + +if __name__ == "__main__": + main()