1- name : Sync Template Updates
1+ name : " Sync with Az-RBSI Template "
22
33on :
4+ # cronjob trigger
45 schedule :
5- - cron : ' 0 0 * * *' # Runs daily at midnight UTC
6+ - cron : " 30 04 * * 1"
7+ # manual trigger
68 workflow_dispatch :
79
810permissions :
@@ -12,13 +14,21 @@ permissions:
1214jobs :
1315 sync-template :
1416 runs-on : ubuntu-latest
17+ env :
18+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
1519
1620 steps :
21+ # -------------------------------
22+ # Step 1: Checkout repository
23+ # -------------------------------
1724 - name : Checkout repository
1825 uses : actions/checkout@v5
1926 with :
20- fetch-depth : 0
27+ fetch-depth : 0 # Full history needed for cherry-picking
2128
29+ # -------------------------------
30+ # Step 2: Read template origin info
31+ # -------------------------------
2232 - name : Read template origin
2333 id : template
2434 run : |
@@ -28,68 +38,178 @@ jobs:
2838 exit 0
2939 fi
3040
31- template_repo=$(sed -n 's/^Template: //p' TEMPLATE_ORIGIN.txt)
32- template_branch=$(sed -n 's/^Template Branch: //p' TEMPLATE_ORIGIN.txt)
33- template_commit=$(sed -n 's/^Template Commit: //p' TEMPLATE_ORIGIN.txt)
41+ template_repo=$(sed -n 's/^Template: //p' TEMPLATE_ORIGIN.txt | tr -d '[:space:]')
42+ last_applied_commit=$(sed -n 's/^Template Commit: //p' TEMPLATE_ORIGIN.txt | tr -d '[:space:]')
3443
35- echo "Template repo: $template_repo"
36- echo "Template branch: $template_branch"
37- echo "Template commit: $template_commit"
38-
39- # Skip if placeholder values
40- if [ "$template_repo" = "none" ] || [ "$template_branch" = "none" ] || [ "$template_commit" = "none" ]; then
41- echo "Repository was not created from a template. Skipping sync."
44+ if [ "$template_repo" = "none" ] || [ "$last_applied_commit" = "none" ]; then
45+ echo "Repository is not from a template. Skipping sync."
4246 echo "skip_sync=true" >> $GITHUB_OUTPUT
4347 exit 0
4448 fi
4549
50+ # Get default branch of template repo dynamically
51+ template_branch=$(gh repo view "$template_repo" --json defaultBranchRef --jq '.defaultBranchRef.name')
4652 echo "skip_sync=false" >> $GITHUB_OUTPUT
4753 echo "template_repo=$template_repo" >> $GITHUB_OUTPUT
4854 echo "template_branch=$template_branch" >> $GITHUB_OUTPUT
49- echo "template_commit=$template_commit " >> $GITHUB_OUTPUT
55+ echo "last_applied_commit=$last_applied_commit " >> $GITHUB_OUTPUT
5056
57+ # -------------------------------
58+ # Step 3: Install GitHub CLI and jq
59+ # -------------------------------
5160 - name : Install GitHub CLI
5261 if : steps.template.outputs.skip_sync == 'false'
53- run : sudo apt-get install -y gh jq
62+ run : sudo apt-get update && sudo apt-get install -y gh jq
5463
55- - name : Fetch commits from template since last sync
64+ # -------------------------------
65+ # Step 4: Add template repo as remote and fetch commits
66+ # -------------------------------
67+ - name : Add template repo as remote and fetch
5668 if : steps.template.outputs.skip_sync == 'false'
57- id : commits
5869 run : |
59- # Get the list of commits from template repo that are not yet in this repo
60- commits=$(gh api repos/${{ steps.template.outputs.template_repo }}/commits --jq '.[] | .sha' | tac)
61- echo "$commits" > template_commits.txt
62- echo "Commits to consider:"
63- cat template_commits.txt
64- echo "commits=$(cat template_commits.txt)" >> $GITHUB_OUTPUT
65-
66- - name : Cherry-pick commits and create PR
70+ git remote add template_repo https://github.com/${{ steps.template.outputs.template_repo }}.git
71+ git fetch template_repo ${{ steps.template.outputs.template_branch }}
72+
73+ # -------------------------------
74+ # Step 5: Get new commits from template (only non-merge)
75+ # -------------------------------
76+ - name : Get new commits from template
6777 if : steps.template.outputs.skip_sync == 'false'
78+ id : commits
79+ run : |
80+ last_commit=${{ steps.template.outputs.last_applied_commit }}
81+ branch=${{ steps.template.outputs.template_branch }}
82+
83+ # List commits after last applied commit, oldest first
84+ all_commits=$(git rev-list --reverse ${last_commit}..template_repo/${branch} || true)
85+ all_commits=$(echo "$all_commits" | grep -v "^$last_commit$" || true)
86+
87+ # Filter out merge commits (those with >1 parent)
88+ non_merge_commits=""
89+ for sha in $all_commits; do
90+ parent_count=$(git rev-list --parents -n 1 "$sha" | wc -w)
91+ if [ "$parent_count" -le 2 ]; then
92+ non_merge_commits+="$sha"$'\n'
93+ fi
94+ done
95+
96+ non_merge_commits=$(echo "$non_merge_commits" | grep -v '^$' || true)
97+ commit_count=$(echo "$non_merge_commits" | grep -c . || true)
98+
99+ echo "commit_count=$commit_count" >> $GITHUB_OUTPUT
100+
101+ if [ -z "$non_merge_commits" ]; then
102+ echo "No new template commits to cherry-pick."
103+ else
104+ echo "Non-merge commits to cherry-pick:"
105+ echo "$non_merge_commits"
106+ fi
107+
108+ echo "new_commits<<EOF" >> $GITHUB_OUTPUT
109+ echo "$non_merge_commits" >> $GITHUB_OUTPUT
110+ echo "EOF" >> $GITHUB_OUTPUT
111+
112+ # -------------------------------
113+ # Step 6: Cherry-pick commits, squash, and create/update PR
114+ # -------------------------------
115+ - name : Cherry-pick commits, squash, and create/update PR
116+ if : steps.template.outputs.skip_sync == 'false' && steps.commits.outputs.new_commits != ''
68117 env :
69- GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
118+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
70119 run : |
120+ set -e
71121 git config user.name "github-actions[bot]"
72122 git config user.email "github-actions[bot]@users.noreply.github.com"
73-
74- branch_name="template-sync-$(date +%Y%m%d%H%M%S)"
75- git checkout -b "$branch_name"
76123
77- pr_message="Template Sync Commit Summary:\n\n"
124+ branch_name="template-sync"
125+ target_branch=$(gh repo view $GITHUB_REPOSITORY --json defaultBranchRef --jq '.defaultBranchRef.name')
126+ sync_date=$(date -u +"%Y-%m-%d")
127+
128+ # Ensure branch exists or create new
129+ if git ls-remote --exit-code --heads origin "$branch_name"; then
130+ git fetch origin "$branch_name"
131+ git checkout "$branch_name"
132+ git pull origin "$branch_name"
133+ else
134+ git checkout -b "$branch_name" origin/"$target_branch" || git checkout -b "$branch_name"
135+ fi
136+
137+ # Reset to target branch to avoid conflicts
138+ git fetch origin "$target_branch"
139+ git reset --hard origin/"$target_branch"
140+
141+ # Initialize commit summary
142+ commit_summary=""
143+ newest_commit=""
144+ commit_list=$(echo "${{ steps.commits.outputs.new_commits }}" | tr -d '\r')
78145
79- while read commit_sha; do
80- # Skip empty lines
146+ # Cherry-pick each commit safely
147+ while IFS= read -r commit_sha; do
81148 [ -z "$commit_sha" ] && continue
82- echo "Cherry-picking $commit_sha"
83- git cherry-pick "$commit_sha" || git cherry-pick --abort
84- pr_message="$pr_message- $commit_sha\n"
85- done < template_commits.txt
86-
87- git push origin "$branch_name"
88-
89- # Create PR
90- gh pr create \
91- --title "Sync Template Updates" \
92- --body "$pr_message" \
93- --base main \
94- --head "$branch_name" \
95- --label "template-sync"
149+
150+ short_sha=$(git rev-parse --short=7 "$commit_sha")
151+ commit_msg=$(git log -1 --pretty=%B "$commit_sha")
152+ first_line=${commit_msg%%$'\n'*}
153+
154+ # Add only once to the summary
155+ commit_summary+="- $short_sha: $first_line"$'\n'
156+
157+ # Cherry-pick without committing yet
158+ git cherry-pick --no-commit "$commit_sha"
159+ newest_commit="$commit_sha"
160+ done <<< "$commit_list"
161+
162+ # Squash all cherry-picked commits into a single commit
163+ git commit -m "Template Sync Updates"$'\n\n'"$commit_summary"
164+
165+ # Update TEMPLATE_ORIGIN.txt
166+ if [ -n "$newest_commit" ]; then
167+ echo -e "Template: ${{ steps.template.outputs.template_repo }}\nTemplate Branch: ${{ steps.template.outputs.template_branch }}\nTemplate Commit: $newest_commit" > TEMPLATE_ORIGIN.txt
168+ git add TEMPLATE_ORIGIN.txt
169+ git commit --amend --no-edit
170+ fi
171+
172+ # Push changes
173+ git push origin "$branch_name" -f
174+
175+ # Ensure template-sync label exists
176+ if ! gh label list | grep -q "^template-sync"; then
177+ gh label create template-sync --color BC8F8F --description "Updates synced from template repository"
178+ fi
179+
180+ # Build PR title with date and non-merge commit count
181+ pr_count="${{ steps.commits.outputs.commit_count }}"
182+ [ -z "$pr_count" ] && pr_count=0
183+ plural="s"
184+ [ "$pr_count" -eq 1 ] && plural=""
185+ pr_title="Sync Template Updates ($pr_count commit$plural, $sync_date)"
186+
187+ # Build PR body with summary + source info
188+ pr_body=$(printf "Template Sync Commit Summary:\n\n%s\n_Synced from:_ [%s](https://github.com/%s/tree/%s) at commit \`%s\`" \
189+ "$commit_summary" \
190+ "${{ steps.template.outputs.template_repo }}" \
191+ "${{ steps.template.outputs.template_repo }}" \
192+ "${{ steps.template.outputs.template_branch }}" \
193+ "$newest_commit")
194+
195+ existing_pr=$(gh pr list --head "$branch_name" --state open --json number --jq '.[0].number')
196+
197+ if [ -n "$existing_pr" ]; then
198+ echo "Updating existing PR #$existing_pr"
199+ printf "%s" "$pr_body" | gh pr edit "$existing_pr" --title "$pr_title" --body-file - --add-label "template-sync"
200+ else
201+ echo "Creating a new PR"
202+ printf "%s" "$pr_body" | gh pr create \
203+ --title "$pr_title" \
204+ --body-file - \
205+ --base "$target_branch" \
206+ --head "$branch_name" \
207+ --label "template-sync"
208+ fi
209+
210+ # -------------------------------
211+ # Step 7: No new commits
212+ # -------------------------------
213+ - name : No new commits
214+ if : steps.template.outputs.skip_sync == 'false' && steps.commits.outputs.new_commits == ''
215+ run : echo "No new template commits to sync. Exiting."
0 commit comments