mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-01 03:09:09 +00:00
ci: split sync into orchestrator + per-branch runners, generalise for any app (#55414)
* ci: re-fetch before push to avoid force-push on translations_hotfix
If upstream/translations_hotfix moved forward while the script was
running (e.g., a concurrent run or manual push), git push would fail
with "behind remote". Re-fetch right before pushing and merge any new
remote commits with -X ours so our .po changes and the main.pot from
${HOTFIX_BRANCH} (set by the initial -X theirs merge) are preserved
without needing a force-push.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* ci: define HOTFIX_BRANCH once as job env; pass it to sync script
version-16-hotfix is now declared as env.HOTFIX_BRANCH at the job level
so the checkout ref and the script argument both derive from the same
value. Quoting the GITHUB_WORKSPACE path guards against spaces on
self-hosted runners.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* ci: define HOTFIX_BRANCH as job env and pass to sync script via env
Declares version-16-hotfix once as env.HOTFIX_BRANCH at the job level
so the checkout ref and the script both derive from the same value.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* ci: read HOTFIX_BRANCH from env instead of hardcoding in script
Removes the hardcoded branch name from the script; reads it from the
HOTFIX_BRANCH env var set by the workflow. Fails immediately with a
clear message if the var is not set.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* ci: use ls-remote to check branch existence instead of swallowing fetch errors
Replace `git fetch ... 2>/dev/null || true` + `git rev-parse --verify`
with `git ls-remote --exit-code --heads upstream translations_hotfix`.
ls-remote queries the remote directly so the check is never based on
stale local state, and real failures (auth, network) propagate through
set -e instead of being silently ignored. Applied to both the initial
branch setup and the pre-push re-fetch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* ci: rewrite orchestrator to dispatch runner per hotfix branch via matrix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* ci: add per-branch runner workflow for hotfix translation sync
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* ci: generalise sync script to use APP_NAME and GITHUB_REPOSITORY
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* ci: remove push trigger from runner workflow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* ci: rename working branch to sync_translations_{hotfix_branch}
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
62
.github/helper/sync_hotfix_translations.sh
vendored
62
.github/helper/sync_hotfix_translations.sh
vendored
@@ -1,11 +1,13 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Syncs Crowdin translations from develop to version-16-hotfix.
|
# Syncs Crowdin translations from develop to a hotfix branch.
|
||||||
# Merge logic: see merge_po_files.py.
|
# Merge logic: see merge_po_files.py.
|
||||||
# Env: GH_TOKEN, PR_REVIEWER, GITHUB_WORKSPACE (all set by Actions).
|
# Env: GH_TOKEN, PR_REVIEWER, GITHUB_WORKSPACE, APP_NAME, GITHUB_REPOSITORY
|
||||||
|
# (all set by Actions).
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
HOTFIX_BRANCH="version-16-hotfix"
|
HOTFIX_BRANCH="${HOTFIX_BRANCH:?HOTFIX_BRANCH env var is required}"
|
||||||
|
APP_NAME="${APP_NAME:?APP_NAME env var is required}"
|
||||||
|
|
||||||
cd ~ || exit
|
cd ~ || exit
|
||||||
|
|
||||||
@@ -13,32 +15,32 @@ echo "=== Setting up bench ==="
|
|||||||
pip install frappe-bench
|
pip install frappe-bench
|
||||||
bench -v init frappe-bench --skip-assets --skip-redis-config-generation --python "$(which python)"
|
bench -v init frappe-bench --skip-assets --skip-redis-config-generation --python "$(which python)"
|
||||||
cd ./frappe-bench || exit
|
cd ./frappe-bench || exit
|
||||||
bench get-app --skip-assets erpnext "${GITHUB_WORKSPACE}"
|
bench get-app --skip-assets "${APP_NAME}" "${GITHUB_WORKSPACE}"
|
||||||
|
|
||||||
echo "=== Setting up translations_hotfix branch ==="
|
echo "=== Setting up sync_translations_${HOTFIX_BRANCH} branch ==="
|
||||||
cd ./apps/erpnext || exit
|
cd "./apps/${APP_NAME}" || exit
|
||||||
git config user.email "developers@erpnext.com"
|
git config user.email "developers@erpnext.com"
|
||||||
git config user.name "frappe-pr-bot"
|
git config user.name "frappe-pr-bot"
|
||||||
git remote set-url upstream https://github.com/frappe/erpnext.git
|
git remote set-url upstream "https://github.com/${GITHUB_REPOSITORY}.git"
|
||||||
gh auth setup-git
|
gh auth setup-git
|
||||||
git fetch upstream "${HOTFIX_BRANCH}"
|
git fetch upstream "${HOTFIX_BRANCH}"
|
||||||
git fetch upstream translations_hotfix 2>/dev/null || true
|
|
||||||
|
|
||||||
if git rev-parse --verify "upstream/translations_hotfix" >/dev/null 2>&1; then
|
if git ls-remote --exit-code --heads upstream sync_translations_${HOTFIX_BRANCH} >/dev/null 2>&1; then
|
||||||
git checkout -b translations_hotfix "upstream/translations_hotfix"
|
git fetch upstream sync_translations_${HOTFIX_BRANCH}
|
||||||
|
git checkout -b sync_translations_${HOTFIX_BRANCH} "upstream/sync_translations_${HOTFIX_BRANCH}"
|
||||||
git merge -X theirs "upstream/${HOTFIX_BRANCH}" --no-edit
|
git merge -X theirs "upstream/${HOTFIX_BRANCH}" --no-edit
|
||||||
else
|
else
|
||||||
git checkout -b translations_hotfix "upstream/${HOTFIX_BRANCH}"
|
git checkout -b sync_translations_${HOTFIX_BRANCH} "upstream/${HOTFIX_BRANCH}"
|
||||||
fi
|
fi
|
||||||
cd ../.. || exit
|
cd ../.. || exit
|
||||||
|
|
||||||
echo "=== Fetching develop's .po files ==="
|
echo "=== Fetching develop's .po files ==="
|
||||||
mkdir -p /tmp/develop-po
|
mkdir -p /tmp/develop-po
|
||||||
git -C "${GITHUB_WORKSPACE}" fetch origin develop
|
git -C "${GITHUB_WORKSPACE}" fetch origin develop
|
||||||
git -C "${GITHUB_WORKSPACE}" archive origin/develop erpnext/locale/ \
|
git -C "${GITHUB_WORKSPACE}" archive origin/develop "${APP_NAME}/locale/" \
|
||||||
| tar -xf - -C /tmp/develop-po/
|
| tar -xf - -C /tmp/develop-po/
|
||||||
|
|
||||||
po_count=$(find /tmp/develop-po/erpnext/locale -name "*.po" | wc -l)
|
po_count=$(find "/tmp/develop-po/${APP_NAME}/locale" -name "*.po" | wc -l)
|
||||||
if [ "${po_count}" -eq 0 ]; then
|
if [ "${po_count}" -eq 0 ]; then
|
||||||
echo "ERROR: No .po files found in develop's archive. Aborting." >&2
|
echo "ERROR: No .po files found in develop's archive. Aborting." >&2
|
||||||
exit 1
|
exit 1
|
||||||
@@ -47,46 +49,50 @@ echo "Extracted ${po_count} .po file(s) from develop."
|
|||||||
|
|
||||||
echo "=== Merging and reconciling ==="
|
echo "=== Merging and reconciling ==="
|
||||||
env/bin/python "${GITHUB_WORKSPACE}/.github/helper/merge_po_files.py"
|
env/bin/python "${GITHUB_WORKSPACE}/.github/helper/merge_po_files.py"
|
||||||
bench update-po-files --app erpnext
|
bench update-po-files --app "${APP_NAME}"
|
||||||
|
|
||||||
cd ./apps/erpnext || exit
|
cd "./apps/${APP_NAME}" || exit
|
||||||
|
|
||||||
if git diff --quiet erpnext/locale/ && [ -z "$(git ls-files --others --exclude-standard erpnext/locale/)" ]; then
|
if git diff --quiet "${APP_NAME}/locale/" && [ -z "$(git ls-files --others --exclude-standard "${APP_NAME}/locale/")" ]; then
|
||||||
echo "Translations are already up to date. No PR needed."
|
echo "Translations are already up to date. No PR needed."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Changed files:"
|
echo "Changed files:"
|
||||||
git diff --name-only erpnext/locale/
|
git diff --name-only "${APP_NAME}/locale/"
|
||||||
git ls-files --others --exclude-standard erpnext/locale/
|
git ls-files --others --exclude-standard "${APP_NAME}/locale/"
|
||||||
|
|
||||||
echo "=== Committing ==="
|
echo "=== Committing ==="
|
||||||
while IFS= read -r file; do
|
while IFS= read -r file; do
|
||||||
git add "${file}"
|
git add "${file}"
|
||||||
lang=$(basename "${file}" .po)
|
lang=$(basename "${file}" .po)
|
||||||
git commit -m "fix: add ${lang} translation to ${HOTFIX_BRANCH}"
|
git commit -m "chore: add ${lang} translation to ${HOTFIX_BRANCH}"
|
||||||
done < <(git ls-files --others --exclude-standard erpnext/locale/ | grep '\.po$' | sort)
|
done < <(git ls-files --others --exclude-standard "${APP_NAME}/locale/" | grep '\.po$' | sort)
|
||||||
|
|
||||||
while IFS= read -r file; do
|
while IFS= read -r file; do
|
||||||
git add "${file}"
|
git add "${file}"
|
||||||
if ! git diff --staged --quiet -- "${file}"; then
|
if ! git diff --staged --quiet -- "${file}"; then
|
||||||
lang=$(basename "${file}" .po)
|
lang=$(basename "${file}" .po)
|
||||||
git commit -m "fix: sync ${lang} translation to ${HOTFIX_BRANCH}"
|
git commit -m "chore: sync ${lang} translation to ${HOTFIX_BRANCH}"
|
||||||
else
|
else
|
||||||
git restore --staged -- "${file}"
|
git restore --staged -- "${file}"
|
||||||
fi
|
fi
|
||||||
done < <(git diff --name-only erpnext/locale/ | grep '\.po$' | sort)
|
done < <(git diff --name-only "${APP_NAME}/locale/" | grep '\.po$' | sort)
|
||||||
|
|
||||||
git push -u upstream translations_hotfix
|
if git ls-remote --exit-code --heads upstream sync_translations_${HOTFIX_BRANCH} >/dev/null 2>&1; then
|
||||||
|
git fetch upstream sync_translations_${HOTFIX_BRANCH}
|
||||||
|
git merge -X ours "upstream/sync_translations_${HOTFIX_BRANCH}" --no-edit
|
||||||
|
fi
|
||||||
|
git push -u upstream sync_translations_${HOTFIX_BRANCH}
|
||||||
|
|
||||||
echo "=== Opening PR (if not already open) ==="
|
echo "=== Opening PR (if not already open) ==="
|
||||||
existing_pr=$(gh pr list \
|
existing_pr=$(gh pr list \
|
||||||
--base "${HOTFIX_BRANCH}" \
|
--base "${HOTFIX_BRANCH}" \
|
||||||
--head "translations_hotfix" \
|
--head "sync_translations_${HOTFIX_BRANCH}" \
|
||||||
--state open \
|
--state open \
|
||||||
--json number \
|
--json number \
|
||||||
--jq 'length' \
|
--jq 'length' \
|
||||||
-R frappe/erpnext)
|
-R "${GITHUB_REPOSITORY}")
|
||||||
|
|
||||||
if [ "${existing_pr}" -gt 0 ]; then
|
if [ "${existing_pr}" -gt 0 ]; then
|
||||||
echo "PR already open — branch updated in place. No new PR needed."
|
echo "PR already open — branch updated in place. No new PR needed."
|
||||||
@@ -95,8 +101,8 @@ fi
|
|||||||
|
|
||||||
gh pr create \
|
gh pr create \
|
||||||
--base "${HOTFIX_BRANCH}" \
|
--base "${HOTFIX_BRANCH}" \
|
||||||
--head "translations_hotfix" \
|
--head "sync_translations_${HOTFIX_BRANCH}" \
|
||||||
--title "fix: sync translations to ${HOTFIX_BRANCH}" \
|
--title "chore: sync translations to ${HOTFIX_BRANCH}" \
|
||||||
--body "Automated sync of Crowdin translations from \`develop\` to \`${HOTFIX_BRANCH}\`.
|
--body "Automated sync of Crowdin translations from \`develop\` to \`${HOTFIX_BRANCH}\`.
|
||||||
|
|
||||||
A 3-way merge is performed per language, then \`bench update-po-files\` reconciles each \`.po\` against hotfix's \`main.pot\`:
|
A 3-way merge is performed per language, then \`bench update-po-files\` reconciles each \`.po\` against hotfix's \`main.pot\`:
|
||||||
@@ -111,4 +117,4 @@ Generated by the \`sync-hotfix-translations\` workflow." \
|
|||||||
--label "translation" \
|
--label "translation" \
|
||||||
--label "skip-release-notes" \
|
--label "skip-release-notes" \
|
||||||
--reviewer "${PR_REVIEWER}" \
|
--reviewer "${PR_REVIEWER}" \
|
||||||
-R frappe/erpnext
|
-R "${GITHUB_REPOSITORY}"
|
||||||
|
|||||||
52
.github/workflows/run-hotfix-translation-sync.yml
vendored
Normal file
52
.github/workflows/run-hotfix-translation-sync.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Runner — maintain this file on each hotfix branch, not on develop.
|
||||||
|
#
|
||||||
|
# Fires when main.pot changes on this branch (i.e. after a POT update PR
|
||||||
|
# merges), or when dispatched by the orchestrator on develop (weekly schedule).
|
||||||
|
#
|
||||||
|
# Uses github.ref_name so the file is identical across all hotfix branches
|
||||||
|
# with no branch-specific edits required.
|
||||||
|
|
||||||
|
name: Run hotfix translation sync
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# One run at a time per branch. cancel-in-progress: false to avoid leaving
|
||||||
|
# an orphaned remote branch from a mid-flight git push + gh pr create.
|
||||||
|
concurrency:
|
||||||
|
group: sync-hotfix-translations-${{ github.ref_name }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
sync-translations:
|
||||||
|
name: Sync translations from develop into ${{ github.ref_name }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
env:
|
||||||
|
HOTFIX_BRANCH: ${{ github.ref_name }}
|
||||||
|
APP_NAME: ${{ github.event.repository.name }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout ${{ env.HOTFIX_BRANCH }}
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
ref: ${{ env.HOTFIX_BRANCH }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: "3.14"
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
|
||||||
|
- name: Run sync script
|
||||||
|
run: |
|
||||||
|
bash "${GITHUB_WORKSPACE}/.github/helper/sync_hotfix_translations.sh"
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
|
PR_REVIEWER: diptanilsaha
|
||||||
71
.github/workflows/sync-hotfix-translations.yml
vendored
71
.github/workflows/sync-hotfix-translations.yml
vendored
@@ -1,67 +1,36 @@
|
|||||||
# Syncs Crowdin translations from develop into version-16-hotfix,
|
# Orchestrator — lives on develop only.
|
||||||
# filtered to only the strings present in hotfix's main.pot.
|
|
||||||
#
|
#
|
||||||
# Trigger: fires when version-16-hotfix's main.pot is updated — i.e., when
|
# Triggers on the weekly schedule and dispatches the runner workflow on each
|
||||||
# the POT update PR from generate-pot-file.yml is merged. At that point
|
# hotfix branch listed in the matrix. To add or remove a branch, edit the
|
||||||
# hotfix's main.pot is authoritative, and this workflow fetches develop's
|
# matrix below.
|
||||||
# latest .po files (Crowdin translations) and merges them against it.
|
|
||||||
#
|
#
|
||||||
# The weekly schedule acts as a safety net to pick up any Crowdin translations
|
# POT-change triggers are handled by the runner on each hotfix branch
|
||||||
# that arrived on develop between POT update cycles.
|
# (run-hotfix-translation-sync.yml), since GitHub only evaluates a workflow
|
||||||
#
|
# from the branch that receives the push.
|
||||||
# POT file generation remains in generate-pot-file.yml (unchanged).
|
|
||||||
# Maintain this file on develop only; it always acts on version-16-hotfix.
|
|
||||||
|
|
||||||
name: Sync translations to version-16-hotfix
|
name: Sync translations to hotfix branches
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- version-16-hotfix
|
|
||||||
paths:
|
|
||||||
- "erpnext/locale/main.pot" # fires exactly when the POT update PR merges
|
|
||||||
schedule:
|
schedule:
|
||||||
# 10:00 UTC Monday — safety net for Crowdin translations that arrived
|
# 10:00 UTC Monday
|
||||||
# on develop since the last POT update PR merged to version-16-hotfix
|
|
||||||
- cron: "0 10 * * 1"
|
- cron: "0 10 * * 1"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
# Prevent concurrent runs. cancel-in-progress: false because a mid-flight
|
|
||||||
# `git push` + `gh pr create` cancellation can leave an orphaned remote branch.
|
|
||||||
concurrency:
|
|
||||||
group: sync-hotfix-translations
|
|
||||||
cancel-in-progress: false
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sync-translations:
|
trigger-runners:
|
||||||
name: Sync translations to version-16-hotfix
|
name: Trigger sync → ${{ matrix.hotfix_branch }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
strategy:
|
||||||
contents: write
|
matrix:
|
||||||
|
hotfix_branch:
|
||||||
|
- version-16-hotfix
|
||||||
|
fail-fast: false
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout version-16-hotfix
|
- name: Dispatch runner on ${{ matrix.hotfix_branch }}
|
||||||
uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
ref: version-16-hotfix
|
|
||||||
# Full history so `git fetch origin develop` inside the helper works
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v6
|
|
||||||
with:
|
|
||||||
# Match generate-pot-file.yml — bench runs under the same frappe
|
|
||||||
# stack and must use the same Python version.
|
|
||||||
python-version: "3.14"
|
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: 24
|
|
||||||
|
|
||||||
- name: Run sync script
|
|
||||||
run: |
|
run: |
|
||||||
bash ${GITHUB_WORKSPACE}/.github/helper/sync_hotfix_translations.sh
|
gh workflow run run-hotfix-translation-sync.yml \
|
||||||
|
--repo "${{ github.repository }}" \
|
||||||
|
--ref "${{ matrix.hotfix_branch }}"
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
PR_REVIEWER: barredterra # change to your GitHub username if you copied this file
|
|
||||||
|
|||||||
Reference in New Issue
Block a user