chore: sync cascade-dev.yml from Template-Generic [skip ci]

This commit is contained in:
2026-07-13 16:22:07 +00:00
parent b1904e57a2
commit 4d528e440d
+106 -22
View File
@@ -7,8 +7,8 @@
# INGROUP: MokoCLI.Cascade
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/cascade-dev.yml
# VERSION: 02.00.00
# BRIEF: Cascade main -> dev via PR; auto-merge only if conflict-free, else notify
# VERSION: 02.01.00
# BRIEF: Cascade main -> dev; auto-merge clean, auto-resolve VERSION-stamp-only conflicts, else notify
name: "Cascade Main -> Dev"
@@ -16,6 +16,10 @@ on:
push:
branches:
- main
# Daily safety net: catches drift even when main only received [skip ci] pushes
# (which never fire the push trigger above). Off-round minute to avoid a fleet-wide spike.
schedule:
- cron: '23 7 * * *'
workflow_dispatch:
permissions:
@@ -33,7 +37,13 @@ jobs:
name: Cascade main -> dev
runs-on: ubuntu-latest
steps:
- name: Open main -> dev PR (auto-merge if clean, else notify)
- name: Checkout (full history for merge/resolve)
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.MOKOGITEA_TOKEN }}
- name: Cascade main -> dev (auto-resolve version stamps, else notify)
env:
TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
REPO: ${{ github.repository }}
@@ -41,7 +51,7 @@ jobs:
set -uo pipefail
API="${MOKOGITEA_URL}/api/v1/repos/${REPO}"
AUTH="Authorization: token ${TOKEN}"
jqnum() { python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$1',''))" 2>/dev/null; }
jqget() { python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$1',''))" 2>/dev/null; }
# 0. dev must exist
if ! curl -sf -H "$AUTH" "${API}/branches/dev" >/dev/null 2>&1; then
@@ -61,8 +71,8 @@ jobs:
| python3 -c "import sys,json; d=json.load(sys.stdin); print(next((str(p['number']) for p in d if p.get('head',{}).get('ref')=='main'), ''))" 2>/dev/null || echo "")
if [ -z "$PR" ]; then
RESP=$(curl -s -H "$AUTH" -H "Content-Type: application/json" -X POST "${API}/pulls" \
-d '{"head":"main","base":"dev","title":"chore(sync): cascade main -> dev","body":"Automated cascade of main into dev. Auto-merges only if conflict-free; otherwise left open for manual resolution."}')
PR=$(printf '%s' "$RESP" | jqnum number)
-d '{"head":"main","base":"dev","title":"chore(sync): cascade main -> dev","body":"Automated cascade of main into dev. Auto-merges when conflict-free, auto-resolves VERSION-stamp-only conflicts, otherwise left open for manual resolution."}')
PR=$(printf '%s' "$RESP" | jqget number)
if [ -z "$PR" ]; then
echo "::warning::Could not open cascade PR: $RESP"; exit 0
fi
@@ -71,15 +81,6 @@ jobs:
echo "Reusing open cascade PR #${PR}"
fi
# 3. wait for MokoGitea to compute mergeability (conflict detection)
MERGEABLE=""
for _ in 1 2 3 4 5 6; do
MERGEABLE=$(curl -sf -H "$AUTH" "${API}/pulls/${PR}" | jqnum mergeable)
case "$MERGEABLE" in True|False) break ;; esac
sleep 3
done
echo "mergeable=${MERGEABLE}"
notify() {
curl -sS \
-H "Title: ${REPO}: dev cascade needs manual merge" \
@@ -90,17 +91,100 @@ jobs:
"${NTFY_URL}/${NTFY_TOPIC}" || true
}
# 4. auto-merge only if conflict-free; otherwise notify
# 3. wait for MokoGitea to compute mergeability (conflict detection)
MERGEABLE=""
for _ in 1 2 3 4 5 6; do
MERGEABLE=$(curl -sf -H "$AUTH" "${API}/pulls/${PR}" | jqget mergeable)
case "$MERGEABLE" in True|False) break ;; esac
sleep 3
done
echo "mergeable=${MERGEABLE}"
# 4a. conflict-free -> merge via API (existing behaviour)
if [ "$MERGEABLE" = "True" ]; then
CODE=$(curl -s -o /tmp/merge.json -w "%{http_code}" -H "$AUTH" -H "Content-Type: application/json" \
-X POST "${API}/pulls/${PR}/merge" -d '{"Do":"merge","merge_when_checks_succeed":true}')
if [ "$CODE" -ge 200 ] && [ "$CODE" -lt 300 ]; then
echo "Cascade PR #${PR} merged (or scheduled to merge when checks pass)."
else
echo "::warning::Auto-merge returned HTTP ${CODE}: $(cat /tmp/merge.json)"
notify "could not be auto-merged (HTTP ${CODE})."
exit 0
fi
else
echo "::warning::Cascade PR #${PR} has conflicts (mergeable=${MERGEABLE}); sending notification."
notify "has conflicts and cannot be merged automatically."
echo "::warning::Auto-merge returned HTTP ${CODE}: $(cat /tmp/merge.json)"
notify "could not be auto-merged (HTTP ${CODE})."
exit 0
fi
# 4b. conflicts -> try to auto-resolve if they are ONLY VERSION-stamp lines.
echo "PR not cleanly mergeable; checking whether conflicts are VERSION-stamp-only..."
git config user.name "MokoGitea Cascade"
git config user.email "actions@mokoconsulting.tech"
git fetch --quiet origin main dev
git checkout -B dev origin/dev
if git merge --no-ff --no-commit origin/main >/dev/null 2>&1; then
# Became clean at git level (e.g. mergeability was still computing) -> commit + push.
git commit -m "chore(sync): cascade main -> dev [skip ci]" >/dev/null
git push origin dev
echo "Cascade merged cleanly at git level and pushed to dev."
exit 0
fi
CONFLICTS=$(git diff --name-only --diff-filter=U)
echo "Conflicted files:"; echo "${CONFLICTS}"
# A conflict is "stamp-only" when every line inside every conflict block matches
# a version-stamp pattern (VERSION: header, <version> element, or CHANGELOG title).
is_stamp_only() {
awk '
/^<<<<<<< / { inc=1; next }
inc && /^=======$/ { next }
/^>>>>>>> / { inc=0; next }
inc { if ($0 !~ /(VERSION:|<version>|# Changelog)/) { bad=1 } }
END { exit(bad ? 1 : 0) }
' "$1"
}
# Resolve a stamp-only file by keeping dev (ours) for the conflicting lines,
# preserving all auto-merged content around them.
keep_ours() {
awk '
/^<<<<<<< / { inc=1; side="ours"; next }
inc && /^=======$/ { side="theirs"; next }
/^>>>>>>> / { inc=0; next }
{ if (!inc) { print; next } if (side=="ours") print }
' "$1" > "$1.resolved" && mv "$1.resolved" "$1"
}
ALL_STAMP=1
for f in ${CONFLICTS}; do
if ! is_stamp_only "$f"; then
echo "::notice::$f has non-stamp conflicts -> manual resolution required."
ALL_STAMP=0; break
fi
done
if [ "$ALL_STAMP" != "1" ]; then
git merge --abort || true
notify "has non-version-stamp conflicts and cannot be auto-resolved."
exit 0
fi
echo "All conflicts are VERSION-stamp-only; resolving in favour of dev."
for f in ${CONFLICTS}; do
keep_ours "$f"
git add "$f"
done
# Best-effort: normalise stamps to dev's version if mokocli is available.
if [ -f /opt/mokocli/cli/version_check.php ]; then
php /opt/mokocli/cli/version_check.php --fix || true
git add -A
fi
git commit -m "chore(sync): cascade main -> dev (auto-resolved version stamps) [skip ci]" >/dev/null
git push origin dev
echo "Cascade auto-resolved and pushed to dev."
# Close the now-redundant PR (its changes are in dev) with an explanatory comment.
curl -s -H "$AUTH" -H "Content-Type: application/json" -X POST "${API}/issues/${PR}/comments" \
-d '{"body":"Auto-resolved VERSION-stamp-only conflicts and pushed the merge to dev. Closing."}' >/dev/null || true
curl -s -H "$AUTH" -H "Content-Type: application/json" -X PATCH "${API}/pulls/${PR}" \
-d '{"state":"closed"}' >/dev/null || true