66 workflow_dispatch :
77 inputs :
88 cpython_tag :
9- description : ' CPython tag to sync against (e.g. v3 .14)'
9+ description : ' CPython tag/branch to sync against (e.g. 3 .14)'
1010 required : false
1111 default : ' 3.14'
1212
1313permissions :
1414 contents : write
1515 issues : write
1616
17+ # Shared with maintenance.yml so the two never push at the same time.
1718concurrency :
18- group : sync
19+ group : repo-writes
1920 cancel-in-progress : false
2021
2122jobs :
2223 sync :
2324 runs-on : ubuntu-latest
25+ timeout-minutes : 45
2426 env :
2527 CPYTHON_TAG : ${{ github.event.inputs.cpython_tag || '3.14' }}
2628 steps :
@@ -29,92 +31,111 @@ jobs:
2931 with :
3032 fetch-depth : 0
3133
34+ # --- Skip the expensive build if upstream hasn't moved -------------
35+ - name : Get upstream head
36+ id : upstream
37+ run : |
38+ sha=$(git ls-remote https://github.com/python/cpython \
39+ "refs/heads/$CPYTHON_TAG" "refs/tags/$CPYTHON_TAG" \
40+ | head -1 | cut -f1)
41+ echo "sha=$sha" >> "$GITHUB_OUTPUT"
42+ # Marker lives outside the repo so `git add --all` never sees it.
43+ echo "$sha" > "$RUNNER_TEMP/sync-marker"
44+
45+ - name : Check whether this upstream head was already synced
46+ id : seen
47+ # Fail open: no SHA -> no lookup -> full run.
48+ if : steps.upstream.outputs.sha != ''
49+ uses : actions/cache/restore@v4
50+ with :
51+ path : ${{ runner.temp }}/sync-marker
52+ key : synced-${{ env.CPYTHON_TAG }}-${{ steps.upstream.outputs.sha }}
53+ lookup-only : true
54+
55+ - name : Decide whether to run
56+ run : |
57+ if [ "${{ steps.seen.outputs.cache-hit }}" = "true" ] \
58+ && [ "${{ github.event_name }}" != "workflow_dispatch" ]; then
59+ echo "Upstream ${{ steps.upstream.outputs.sha }} already synced; skipping."
60+ echo "SKIP=true" >> "$GITHUB_ENV"
61+ fi
62+
63+ # --- Sync ------------------------------------------------------------
3264 - name : Set up Python
65+ if : env.SKIP != 'true'
3366 uses : actions/setup-python@v4
3467 with :
3568 python-version : ' 3.12'
3669
3770 - name : Install gettext
71+ if : env.SKIP != 'true'
3872 run : sudo apt-get install -y gettext
3973
4074 - name : Install Python dependencies
41- run : pip install sphinx sphinx-intl
75+ if : env.SKIP != 'true'
76+ # polib is used by scripts/review_report.py to build the issue body.
77+ # Consider installing sphinx from CPython's Doc/requirements.txt
78+ # instead of unpinned, so Sphinx upgrades can't change msgids on
79+ # their own.
80+ run : pip install sphinx sphinx-intl polib
4281
43- # Item 4: fetch/build/merge/validate now all live in one shared
44- # module (scripts/po_sync.py) instead of inline bash/awk here, so
45- # this workflow can't drift from update_python_version.py the way
46- # it did before (missing --no-location --no-wrap on one side).
4782 - name : Sync msgids (fetch, build gettext, merge, validate)
83+ if : env.SKIP != 'true'
4884 run : python scripts/po_sync.py sync-only "$CPYTHON_TAG" --create-new
4985
50- - name : Ensure translation label exists
51- env :
52- GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
53- run : |
54- gh label create "translation" \
55- --color "0075ca" \
56- --description "Translation review needed" \
57- --repo ${{ github.repository }} \
58- 2>/dev/null || true
59-
60- - name : Open issue for fuzzy strings
61- env :
62- GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
63- run : |
64- tmpfile=$(mktemp)
65- while IFS= read -r f; do
66- fuzzy=$(msgattrib --only-fuzzy --no-obsolete "$f" 2>/dev/null)
67- if [ -n "$fuzzy" ]; then
68- entries=$(echo "$fuzzy" | awk '
69- /^msgid / {
70- first=$0; msg=$0"\n"; lines=1; incapture=1; next
71- }
72- incapture && /^"/ {
73- msg = msg $0 "\n"; lines++; next
74- }
75- incapture && /^msgstr/ {
76- incapture=0
77- if (!(first == "msgid \"\"" && lines==1)) {
78- printf "- %s\n", msg
79- }
80- next
81- }
82- ')
83- count=$(echo "$entries" | grep -c '^- ' || echo 0)
84- if [ "$count" -gt 0 ]; then
85- echo "### $f ($count fuzzy)" >> "$tmpfile"
86- echo "$entries" | head -20 >> "$tmpfile"
87- echo "" >> "$tmpfile"
88- fi
89- fi
90- done < <(find . -name "*.po" \
91- -not -path "./.git/*" \
92- -not -path "./.cpython-src/*" \
93- -not -path "./.pot-templates/*")
94- if [ -s "$tmpfile" ]; then
95- gh issue create \
96- --title "🔍 Fuzzy strings need review after CPython sync ($(date +%Y-%m-%d))" \
97- --body "$(cat "$tmpfile")
98-
99- Triggered by sync with \`$CPYTHON_TAG\`." \
100- --label "translation" \
101- --repo ${{ github.repository }}
102- fi
103- rm -f "$tmpfile"
104-
10586 - name : Stage changes
87+ if : env.SKIP != 'true'
10688 run : git add --all
10789
10890 - name : Unstage POT-Creation-Date-only changes
91+ if : env.SKIP != 'true'
10992 run : python scripts/unstage_cosmetic.py
11093
111- - name : Commit if changed
94+ # --- Build the report from what is actually staged -------------------
95+ # New PO files, newly fuzzy strings (old -> new diff) and new
96+ # untranslated strings. Writes an empty file if there is nothing to
97+ # report, in which case no issue is opened.
98+ - name : Build review report (new files, newly fuzzy, new untranslated)
99+ if : env.SKIP != 'true'
100+ run : python scripts/review_report.py --output "$RUNNER_TEMP/report.md"
101+
102+ # --- Commit + push ---------------------------------------------------
103+ - name : Commit and push if changed
104+ id : commit
105+ if : env.SKIP != 'true'
112106 run : |
113107 git config user.name "github-actions[bot]"
114108 git config user.email "github-actions[bot]@users.noreply.github.com"
115109 if git diff --staged --quiet; then
116- echo "Nothing to commit, skipping."
110+ echo "Nothing to commit."
111+ echo "committed=false" >> "$GITHUB_OUTPUT"
117112 exit 0
118113 fi
119114 git commit -m "chore: sync msgids with CPython $CPYTHON_TAG"
115+ git pull --rebase
120116 git push
117+ echo "committed=true" >> "$GITHUB_OUTPUT"
118+
119+ # Only after a successful push, so a failed run can't create duplicates.
120+ - name : Open issue for new files / fuzzy / untranslated strings
121+ if : env.SKIP != 'true' && steps.commit.outputs.committed == 'true'
122+ env :
123+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
124+ run : |
125+ report="$RUNNER_TEMP/report.md"
126+ if [ -s "$report" ]; then
127+ echo "Triggered by sync with \`$CPYTHON_TAG\` (commit $(git rev-parse --short HEAD))." >> "$report"
128+ gh issue create \
129+ --title "🔍 Translation review needed after CPython sync ($(date +%Y-%m-%d))" \
130+ --body-file "$report" \
131+ --label "تغییرات خودکار در ترجمهها" \
132+ --repo "${{ github.repository }}"
133+ fi
134+
135+ # Record the upstream head as synced (only reached if everything above passed).
136+ - name : Remember synced upstream head
137+ if : env.SKIP != 'true' && steps.upstream.outputs.sha != ''
138+ uses : actions/cache/save@v4
139+ with :
140+ path : ${{ runner.temp }}/sync-marker
141+ key : synced-${{ env.CPYTHON_TAG }}-${{ steps.upstream.outputs.sha }}
0 commit comments