CodeCraftLab / hf-sync.yml
S-Dreamer's picture
Upload 4 files
b9ed97d verified
name: HF ↔ GitHub Sync
# Trigger on every push to main (GitHub β†’ HF direction)
# and hourly to pull any HF-side changes back (HF β†’ GitHub direction)
on:
push:
branches: [main]
schedule:
- cron: '0 * * * *' # hourly HF pull-check
workflow_dispatch:
inputs:
force_direction:
description: 'Force sync direction (hf-wins | gh-wins | auto)'
required: false
default: 'auto'
env:
HF_REPO_TYPE: space # model | dataset | space
HF_REPO: ${{ vars.HF_REPO }} # e.g. your-org/codecraftlab
jobs:
sync:
name: Sync HuggingFace ↔ GitHub
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout (full history)
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Configure git identity
run: |
git config user.email "sync-bot@codecraftlab.noreply"
git config user.name "CodeCraftLab Sync Bot"
- name: Install git-lfs
run: |
sudo apt-get install -y git-lfs
git lfs install
- name: Add HuggingFace remote
run: |
git remote add hf \
"https://user:${{ secrets.HF_TOKEN }}@huggingface.co/${HF_REPO_TYPE}s/${HF_REPO}"
git fetch hf --prune
- name: Detect divergence and resolve
id: sync
env:
FORCE_DIRECTION: ${{ github.event.inputs.force_direction || 'auto' }}
run: |
set -euo pipefail
HF_HEAD=$(git rev-parse hf/main 2>/dev/null || echo "NONE")
GH_HEAD=$(git rev-parse HEAD)
if [ "$HF_HEAD" = "NONE" ]; then
echo "action=push-to-hf" >> "$GITHUB_OUTPUT"
echo "reason=HF remote has no main branch β€” initial push"
exit 0
fi
BASE=$(git merge-base HEAD hf/main 2>/dev/null || echo "NONE")
if [ "$FORCE_DIRECTION" = "hf-wins" ]; then
echo "action=hf-wins" >> "$GITHUB_OUTPUT"
echo "reason=Forced HF-wins override"
elif [ "$FORCE_DIRECTION" = "gh-wins" ]; then
echo "action=push-to-hf" >> "$GITHUB_OUTPUT"
echo "reason=Forced GH-wins override"
elif [ "$HF_HEAD" = "$GH_HEAD" ]; then
echo "action=in-sync" >> "$GITHUB_OUTPUT"
echo "reason=Already in sync"
elif [ "$BASE" = "$GH_HEAD" ]; then
# HF is ahead β€” pull HF β†’ GitHub
echo "action=hf-wins" >> "$GITHUB_OUTPUT"
echo "reason=GitHub is behind HF β€” fast-forward"
elif [ "$BASE" = "$HF_HEAD" ]; then
# GitHub is ahead β€” push GitHub β†’ HF
echo "action=push-to-hf" >> "$GITHUB_OUTPUT"
echo "reason=HF is behind GitHub β€” pushing"
else
# Both diverged β€” HF is source of truth
echo "action=hf-wins" >> "$GITHUB_OUTPUT"
echo "reason=CONFLICT: both diverged β€” HF wins (source of truth)"
fi
- name: "[In-sync] Nothing to do"
if: steps.sync.outputs.action == 'in-sync'
run: echo "βœ… HF and GitHub are in sync β€” no action required."
- name: "[Push] GitHub β†’ HuggingFace"
if: steps.sync.outputs.action == 'push-to-hf'
run: |
echo "πŸ“€ Pushing GitHub β†’ HuggingFace"
git push hf main
- name: "[HF Wins] HuggingFace β†’ GitHub"
if: steps.sync.outputs.action == 'hf-wins'
run: |
echo "πŸ“₯ HuggingFace wins β€” overwriting GitHub main"
git reset --hard hf/main
git push origin main --force-with-lease || git push origin main --force
- name: Summary
if: always()
run: |
echo "### Sync Result" >> "$GITHUB_STEP_SUMMARY"
echo "- **Action:** ${{ steps.sync.outputs.action }}" >> "$GITHUB_STEP_SUMMARY"
echo "- **Trigger:** ${{ github.event_name }}" >> "$GITHUB_STEP_SUMMARY"
echo "- **Branch:** main" >> "$GITHUB_STEP_SUMMARY"
# ------------------------------------------------------------------
# Validate HF Space config on every push
# ------------------------------------------------------------------
validate-space-config:
name: Validate HF Space README config
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check README frontmatter
run: |
python3 - <<'EOF'
import re, sys
with open("README.md") as f:
content = f.read()
match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
if not match:
print("❌ README is missing HF Space YAML frontmatter")
sys.exit(1)
frontmatter = match.group(1)
required_keys = ["title", "sdk", "app_port", "license"]
missing = [k for k in required_keys if k + ":" not in frontmatter]
if missing:
print(f"❌ Missing frontmatter keys: {missing}")
sys.exit(1)
if "sdk: streamlit" in frontmatter:
print("❌ sdk is still 'streamlit' β€” should be 'docker' for FastAPI")
sys.exit(1)
print("βœ… HF Space frontmatter is valid")
EOF