Sync with Az-RBSI Template #6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: "Sync with Az-RBSI Template" | |
| on: | |
| # cronjob trigger | |
| schedule: | |
| # Every Monday at 4:30AM | |
| - cron: "30 04 * * 1" | |
| # manual trigger | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| jobs: | |
| sync-template: | |
| runs-on: ubuntu-latest | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| steps: | |
| # ------------------------------- | |
| # Step 1: Checkout repository | |
| # ------------------------------- | |
| - name: Checkout repository | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 # Full history needed for cherry-picking | |
| # ------------------------------- | |
| # Step 2: Read template origin info | |
| # ------------------------------- | |
| - name: Read template origin | |
| id: template | |
| run: | | |
| if [ ! -f TEMPLATE_ORIGIN.txt ]; then | |
| echo "TEMPLATE_ORIGIN.txt not found. Skipping sync." | |
| echo "skip_sync=true" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| template_repo=$(sed -n 's/^Template: //p' TEMPLATE_ORIGIN.txt | tr -d '[:space:]') | |
| last_applied_commit=$(sed -n 's/^Template Commit: //p' TEMPLATE_ORIGIN.txt | tr -d '[:space:]') | |
| if [ "$template_repo" = "none" ] || [ "$last_applied_commit" = "none" ]; then | |
| echo "Repository is not from a template. Skipping sync." | |
| echo "skip_sync=true" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| # Get default branch of template repo dynamically | |
| template_branch=$(gh repo view "$template_repo" --json defaultBranchRef --jq '.defaultBranchRef.name') | |
| echo "skip_sync=false" >> $GITHUB_OUTPUT | |
| echo "template_repo=$template_repo" >> $GITHUB_OUTPUT | |
| echo "template_branch=$template_branch" >> $GITHUB_OUTPUT | |
| echo "last_applied_commit=$last_applied_commit" >> $GITHUB_OUTPUT | |
| # ------------------------------- | |
| # Step 3: Install GitHub CLI and jq | |
| # ------------------------------- | |
| - name: Install GitHub CLI | |
| if: steps.template.outputs.skip_sync == 'false' | |
| run: sudo apt-get update && sudo apt-get install -y gh jq | |
| # ------------------------------- | |
| # Step 4: Add template repo as remote and fetch commits | |
| # ------------------------------- | |
| - name: Add template repo as remote and fetch | |
| if: steps.template.outputs.skip_sync == 'false' | |
| run: | | |
| git remote add template_repo https://github.com/${{ steps.template.outputs.template_repo }}.git | |
| git fetch template_repo ${{ steps.template.outputs.template_branch }} | |
| # ------------------------------- | |
| # Step 5: Get new commits from template (only non-merge) | |
| # ------------------------------- | |
| - name: Get new commits from template | |
| if: steps.template.outputs.skip_sync == 'false' | |
| id: commits | |
| run: | | |
| last_commit=${{ steps.template.outputs.last_applied_commit }} | |
| branch=${{ steps.template.outputs.template_branch }} | |
| # List commits after last applied commit, oldest first | |
| all_commits=$(git rev-list --reverse ${last_commit}..template_repo/${branch} || true) | |
| all_commits=$(echo "$all_commits" | grep -v "^$last_commit$" || true) | |
| # Filter out merge commits (those with >1 parent) | |
| non_merge_commits="" | |
| for sha in $all_commits; do | |
| parent_count=$(git rev-list --parents -n 1 "$sha" | wc -w) | |
| if [ "$parent_count" -le 2 ]; then | |
| non_merge_commits+="$sha"$'\n' | |
| fi | |
| done | |
| non_merge_commits=$(echo "$non_merge_commits" | grep -v '^$' || true) | |
| commit_count=$(echo "$non_merge_commits" | grep -c . || true) | |
| echo "commit_count=$commit_count" >> $GITHUB_OUTPUT | |
| if [ -z "$non_merge_commits" ]; then | |
| echo "No new template commits to cherry-pick." | |
| else | |
| echo "Non-merge commits to cherry-pick:" | |
| echo "$non_merge_commits" | |
| fi | |
| echo "new_commits<<EOF" >> $GITHUB_OUTPUT | |
| echo "$non_merge_commits" >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| # ------------------------------- | |
| # Step 6: Cherry-pick commits, squash, and create/update PR | |
| # ------------------------------- | |
| - name: Cherry-pick commits, squash, and create/update PR | |
| if: steps.template.outputs.skip_sync == 'false' && steps.commits.outputs.new_commits != '' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -e | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| branch_name="template-sync" | |
| target_branch=$(gh repo view $GITHUB_REPOSITORY --json defaultBranchRef --jq '.defaultBranchRef.name') | |
| sync_date=$(date -u +"%Y-%m-%d") | |
| timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ") | |
| # Ensure branch exists or create new | |
| if git ls-remote --exit-code --heads origin "$branch_name"; then | |
| git fetch origin "$branch_name" | |
| git checkout "$branch_name" | |
| git pull origin "$branch_name" | |
| else | |
| git checkout -b "$branch_name" origin/"$target_branch" || git checkout -b "$branch_name" | |
| fi | |
| # Reset to target branch to avoid conflicts | |
| git fetch origin "$target_branch" | |
| git reset --hard origin/"$target_branch" | |
| # Initialize commit summary | |
| commit_summary="" | |
| newest_commit="" | |
| commit_list=$(echo "${{ steps.commits.outputs.new_commits }}" | tr -d '\r') | |
| # Cherry-pick each commit safely | |
| while IFS= read -r commit_sha; do | |
| [ -z "$commit_sha" ] && continue | |
| short_sha=$(git rev-parse --short=7 "$commit_sha") | |
| commit_msg=$(git log -1 --pretty=%B "$commit_sha") | |
| first_line=${commit_msg%%$'\n'*} | |
| # Add only once to the summary | |
| commit_summary+="- $short_sha: $first_line"$'\n' | |
| # Cherry-pick without committing yet | |
| git cherry-pick --no-commit "$commit_sha" | |
| newest_commit="$commit_sha" | |
| done <<< "$commit_list" | |
| # Squash all cherry-picked commits into a single commit | |
| git commit -m "Template Sync Updates"$'\n\n'"$commit_summary" | |
| # Update TEMPLATE_ORIGIN.txt (with new Recorded At) | |
| if [ -n "$newest_commit" ]; then | |
| { | |
| echo "Template: ${{ steps.template.outputs.template_repo }}" | |
| echo "Template Branch: ${{ steps.template.outputs.template_branch }}" | |
| echo "Template Commit: $newest_commit" | |
| echo "Recorded At (UTC): $timestamp" | |
| } > TEMPLATE_ORIGIN.txt | |
| git add TEMPLATE_ORIGIN.txt | |
| git commit --amend --no-edit | |
| fi | |
| # Push changes | |
| git push origin "$branch_name" -f | |
| # Ensure template-sync label exists | |
| if ! gh label list | grep -q "^template-sync"; then | |
| gh label create template-sync --color BC8F8F --description "Updates synced from template repository" | |
| fi | |
| # Build PR title with date and non-merge commit count | |
| pr_count="${{ steps.commits.outputs.commit_count }}" | |
| [ -z "$pr_count" ] && pr_count=0 | |
| plural="s" | |
| [ "$pr_count" -eq 1 ] && plural="" | |
| pr_title="Sync Template Updates ($pr_count commit$plural, $sync_date)" | |
| # Build PR body with summary + source info | |
| pr_body=$(printf "Template Sync Commit Summary:\n\n%s\n_Synced from:_ [%s](https://github.com/%s/tree/%s) at commit \`%s\`\n\n_Last recorded at (UTC): %s_" \ | |
| "$commit_summary" \ | |
| "${{ steps.template.outputs.template_repo }}" \ | |
| "${{ steps.template.outputs.template_repo }}" \ | |
| "${{ steps.template.outputs.template_branch }}" \ | |
| "$newest_commit" \ | |
| "$timestamp") | |
| existing_pr=$(gh pr list --head "$branch_name" --state open --json number --jq '.[0].number') | |
| if [ -n "$existing_pr" ]; then | |
| echo "Updating existing PR #$existing_pr" | |
| printf "%s" "$pr_body" | gh pr edit "$existing_pr" --title "$pr_title" --body-file - --add-label "template-sync" | |
| else | |
| echo "Creating a new PR" | |
| printf "%s" "$pr_body" | gh pr create \ | |
| --title "$pr_title" \ | |
| --body-file - \ | |
| --base "$target_branch" \ | |
| --head "$branch_name" \ | |
| --label "template-sync" | |
| fi | |
| # ------------------------------- | |
| # Step 7: No new commits | |
| # ------------------------------- | |
| - name: No new commits | |
| if: steps.template.outputs.skip_sync == 'false' && steps.commits.outputs.new_commits == '' | |
| run: echo "No new template commits to sync. Exiting." |