Files
RedBear-OS/local/scripts/bump-release.sh
T
vasilito 99e5641127 feat: release-bump pipeline + external graphics version sync
Pipeline (3 operator asks):
- bump-release.sh: canonical orchestrator (forks + sources + external)
- upgrade-forks.sh --to=<tag>: rebase forks with diverged-mode guard
- bump-graphics-recipes.sh: map-driven group-aware graphics bumps
- check-external-versions.sh: drift checker for Qt6/KF6/Plasma/Mesa/Wayland
- refresh-fork-upstream-map.sh: append-only map updater with --check
- post-checkout-version-sync.sh + install-git-hooks.sh: opt-in branch hook
- external_version_lib.py: shared version-parsing/bumping library
- external-upstream-map.toml: ~80 external package entries
- bump-fork.sh: deprecated (REDBEAR_I_KNOW_BUMP_FORK_IS_DEPRECATED=1)
- RELEASE-BUMP-WORKFLOW.md: operator runbook

Quality fixes (8 defects from two independent audits):
- blake2b stable cache keys (was hash(), non-portable)
- atomic cache writes via os.replace
- version_sort_key pre-release demotion (was sorting after finals)
- apply_ver_transform re.error tolerance
- grep || true (pipefail abort)
- cd failure detection in upgrade-forks
- sed URL escape (injection hardening)
- refresh-fork-upstream-map last-row drop fix

Doc cleanup:
- Archive 5 obsolete plans to local/docs/archived/
- Remove 14 stale/superseded docs
- Update 18 docs to reference bump-release.sh and fix inbound links
- TOOLS.md drift fixes
2026-07-18 14:45:41 +09:00

580 lines
22 KiB
Bash
Executable File

#!/usr/bin/env bash
# bump-release.sh — Canonical release-bump orchestrator for Red Bear OS.
#
# Policy (release-bump-pipeline.md):
# - bump-release.sh DECIDES per fork.
# - sync-versions.sh EXECUTES suffix-only label rewrites (single code path).
# - refresh-fork-upstream-map.sh maintains the map's column-3 tag.
# - upgrade-forks.sh --to=<tag> performs actual fork source bumps.
#
# Fork routing by map mode (release-bump-pipeline.md design constraint #2):
# - snapshot/tracked → eligible for source bump via upgrade-forks.sh --to
# - diverged → report-only; operator-manual rebase (Phase 2.4+).
# Override with --force-diverged.
# - base (tag=main) → always suffix-only; member crates untouched.
# - bootloader → NEVER source-bumped (no merge-base with upstream;
# one-time 'git replace --graft' is the documented path).
# Upgrade candidates are resolved by latest upstream semver tag EXCLUDING any
# tag already reachable from the fork's HEAD (ancestor tags are history, not
# upgrades — see latest_tag_for_url).
#
# Modes:
# (no args) Fork upstream discovery REPORT + label sync (delegates to
# sync-versions.sh --no-regen) + hint to rerun with
# --with-sources / --with-external when upgrades exist.
# --with-sources For each fork whose map mode is snapshot/tracked AND whose
# latest upstream semver tag is newer than the current base
# (current base = fork Cargo.toml version stripped of +rb*):
# upgrade-forks.sh --to=<tag> <fork>
# On success: set fork Cargo.toml version to
# "<tag>+rb<branch>", update the map row's column 3 in
# place, regen that fork's Cargo.lock (best-effort), report
# the rb-backup/* branch. On failure: report, leave label
# and map untouched, continue with remaining forks. After
# all source bumps: re-run sync-versions.sh --no-regen.
# --with-external Invoke local/scripts/bump-graphics-recipes.sh (pass
# through --dry-run / --no-fetch). Tolerate its absence
# with a warning (a sibling unit is rewriting it
# concurrently).
# --check Fully read-only; print the same report; exit 1 if any
# label drift (via sync-versions.sh --check) OR any fork
# has an available source bump; exit 0 when fully synced.
# --dry-run Print decisions, mutate nothing (implies no delegation
# that mutates).
# --no-fetch Skip all network (ls-remote); use the map's current tags
# as "latest" (actions become report-only).
# --regen Pass --regen instead of --no-regen to sync-versions.sh.
# --force-diverged Pass --force-diverged to upgrade-forks.sh for diverged
# forks (still reports them first).
# --fork=<name> Scope all fork operations to one fork.
#
# GUARDS:
# - Current git branch must match ^[0-9]+\.[0-9]+\.[0-9]+$ (anchored semver).
# Otherwise refuse — release bumps only happen on release branches.
# - REDBEAR_CI=1: only --check / --dry-run are allowed. --with-sources and
# --with-external are refused.
#
# NEVER AUTO-COMMITS. The script ends with an exact copy-paste commit guide
# listing parent-repo files changed (map, Cargo.toml of cookbook + Cat 1
# crates) and per-fork git -C local/sources/<fork> commit + push to
# submodule/<fork> reminders.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
SCRIPT_DIR="$ROOT/local/scripts"
MAP="$ROOT/local/fork-upstream-map.toml"
cd "$ROOT"
# ---- argument parsing ------------------------------------------------------
WITH_SOURCES=0
WITH_EXTERNAL=0
CHECK_ONLY=0
DRY_RUN=0
NO_FETCH=0
REGEN=0
FORCE_DIVERGED=0
FORK_FILTER=""
for arg in "$@"; do
case "$arg" in
--with-sources) WITH_SOURCES=1 ;;
--with-external) WITH_EXTERNAL=1 ;;
--check) CHECK_ONLY=1 ;;
--dry-run) DRY_RUN=1 ;;
--no-fetch) NO_FETCH=1 ;;
--regen) REGEN=1 ;;
--force-diverged) FORCE_DIVERGED=1 ;;
--fork=*) FORK_FILTER="${arg#*=}" ;;
-h|--help)
sed -n '2,55p' "$0"
exit 0
;;
*) echo "Unknown option: $arg" >&2; exit 1 ;;
esac
done
# ---- branch guard ----------------------------------------------------------
BRANCH="$(git branch --show-current 2>/dev/null || echo "")"
if [[ -z "$BRANCH" ]]; then
echo "ERROR: cannot determine current git branch" >&2
exit 1
fi
if [[ ! "$BRANCH" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "ERROR: branch '$BRANCH' is not anchored semver (X.Y.Z)" >&2
echo " Release bumps only happen on release branches." >&2
exit 1
fi
# ---- CI guard --------------------------------------------------------------
if [[ "${REDBEAR_CI:-0}" == "1" ]]; then
if [[ $WITH_SOURCES -eq 1 || $WITH_EXTERNAL -eq 1 ]]; then
echo "ERROR: REDBEAR_CI=1 forbids --with-sources / --with-external" >&2
echo " Use --check or --dry-run only." >&2
exit 1
fi
fi
# Aggregate read-only flag: anything that must NOT mutate the tree.
READ_ONLY=0
[[ $CHECK_ONLY -eq 1 || $DRY_RUN -eq 1 ]] && READ_ONLY=1
# ---- helpers ---------------------------------------------------------------
SEMVER_RE='^[0-9]+\.[0-9]+\.[0-9]+$'
# Resolve the newest GENUINE upgrade tag for a remote via `git ls-remote`.
# Output: "<tag>|<raw-count>" — tag empty when nothing qualifies.
# Two filters: (a) tags already reachable from the fork's HEAD are history,
# not upgrades (relibc's 0.6.0/2020 outranks its newer 0.2.x API line
# lexically); (b) tags not version-greater than the current base are not
# upgrades either (the base tag itself is usually an ancestor of HEAD).
latest_tag_for_url() {
local url="$1" fork="${2:-}" base="${3:-}"
[[ $NO_FETCH -eq 0 ]] || { echo "|0"; return 0; }
local tag forkdir="" raw=0
[[ -n "$fork" && -d "$ROOT/local/sources/$fork/.git" ]] \
&& forkdir="$ROOT/local/sources/$fork"
while IFS= read -r tag; do
[[ -z "$tag" ]] && continue
raw=$((raw + 1))
if [[ -n "$forkdir" ]] \
&& git -C "$forkdir" rev-parse --verify --quiet "refs/tags/$tag" >/dev/null 2>&1 \
&& git -C "$forkdir" merge-base --is-ancestor "refs/tags/$tag" HEAD 2>/dev/null; then
continue
fi
if [[ -n "$base" ]] && ! version_greater "$tag" "$base"; then
continue
fi
echo "${tag}|${raw}"
return 0
done < <(git ls-remote --tags --sort=-v:refname "$url" 2>/dev/null \
| awk '{print $2}' \
| sed 's|refs/tags/||' \
| sed 's|\^{}$||' \
| grep -E "$SEMVER_RE" || true)
echo "|${raw}"
return 0
}
# version_greater <a> <b> → true iff a is version-greater than b (sort -V).
version_greater() {
[[ "$1" != "$2" ]] \
&& [[ "$(printf '%s\n%s\n' "$2" "$1" | sort -V | tail -1)" == "$1" ]]
}
# Compare two semvers. Echo -1 / 0 / 1. Echo "ERR" if either is non-semver.
semver_cmp() {
local a="$1" b="$2"
if [[ ! "$a" =~ $SEMVER_RE || ! "$b" =~ $SEMVER_RE ]]; then
echo "ERR"; return
fi
local a1 a2 a3 b1 b2 b3
IFS=. read -r a1 a2 a3 <<<"$a"
IFS=. read -r b1 b2 b3 <<<"$b"
if (( a1 < b1 )); then echo "-1"; return; fi
if (( a1 > b1 )); then echo "1"; return; fi
if (( a2 < b2 )); then echo "-1"; return; fi
if (( a2 > b2 )); then echo "1"; return; fi
if (( a3 < b3 )); then echo "-1"; return; fi
if (( a3 > b3 )); then echo "1"; return; fi
echo "0"
}
# Strip any +rb<x.y.z> build-metadata suffix from a version string.
strip_rb_suffix() {
echo "$1" | sed -E 's/\+rb[0-9]+(\.[0-9]+\.[0-9]+)?$//'
}
# Read a fork's current base version from its Cargo.toml (stripped of +rb*).
fork_current_base_version() {
local fork="$1"
local ct="$ROOT/local/sources/$fork/Cargo.toml"
[[ -f "$ct" ]] || { echo ""; return; }
local ver
ver=$(grep -E '^version[[:space:]]*=' "$ct" | head -1 | sed 's/.*"\(.*\)".*/\1/' || true)
strip_rb_suffix "$ver"
}
# ---- parse the map into 4 parallel arrays ----------------------------------
declare -a MAP_FORKS=()
declare -a MAP_URLS=()
declare -a MAP_TAGS=()
declare -a MAP_MODES=()
parse_map() {
MAP_FORKS=()
MAP_URLS=()
MAP_TAGS=()
MAP_MODES=()
[[ -f "$MAP" ]] || return 0
while IFS=$'\t' read -r fork url tag mode; do
[[ -z "$fork" ]] && continue
MAP_FORKS+=("$fork")
MAP_URLS+=("$url")
MAP_TAGS+=("$tag")
MAP_MODES+=("${mode:-}")
done < <(awk '
/^[^#[:space:]]/ {
fork = $1; url = $2; tag = $3; mode = $4
sub(/[ \t]*#.*/, "", mode)
print fork "\t" url "\t" tag "\t" mode
}
' "$MAP")
}
# In-place column-3 update for a single fork (byte-exact awk rewrite).
# A sidecar tmp file is consumed by `mv`; no trap needed (if mv fails the
# tmp file lingers harmlessly under $MAP.tmp.<pid>).
update_map_tag_inplace() {
local fork="$1" new_tag="$2"
[[ -f "$MAP" ]] || return 0
local tmp="${MAP}.tmp.$$"
awk -v fork="$fork" -v new_tag="$new_tag" '
$1 == fork && /^[^#[:space:]]/ {
prefix_re = fork "[ \t]+[^ \t]+[ \t]+"
if (match($0, prefix_re)) {
prefix = substr($0, 1, RLENGTH)
rest = substr($0, RLENGTH + 1)
if (match(rest, /^[^ \t]+/)) {
suffix = substr(rest, RLENGTH + 1)
$0 = prefix new_tag suffix
}
}
}
{ print }
' "$MAP" > "$tmp" && mv "$tmp" "$MAP"
# Clean up a straggler if mv failed for some reason.
[[ -e "$tmp" ]] && rm -f "$tmp" || true
}
# ---- counters --------------------------------------------------------------
SOURCE_BUMPS_AVAILABLE=0
SOURCE_BUMPS_APPLIED=0
SOURCE_BUMPS_FAILED=0
LABEL_DRIFT=0
# Latest-tag cache filled by phase_report; phase_source_bumps reuses it so
# each fork costs exactly one ls-remote per run.
declare -A LATEST_FOR=()
# ---- phase: report ---------------------------------------------------------
phase_report() {
echo "=== Fork upstream discovery report ==="
echo "branch=$BRANCH read_only=$READ_ONLY no_fetch=$NO_FETCH"
echo ""
local idx fork url current mode latest cargo_base basis cmp action
for ((idx = 0; idx < ${#MAP_FORKS[@]}; idx++)); do
fork="${MAP_FORKS[$idx]}"
url="${MAP_URLS[$idx]}"
current="${MAP_TAGS[$idx]}"
mode="${MAP_MODES[$idx]}"
if [[ -n "$FORK_FILTER" && "$fork" != "$FORK_FILTER" ]]; then
continue
fi
if [[ "$current" == "main" ]]; then
echo "fork=$fork mode=$mode current=$current latest=(skip) action=skip-main"
continue
fi
if [[ "$fork" == "bootloader" ]]; then
echo "fork=$fork mode=$mode current=$current latest=(skip) action=report-only note=no-merge-base-needs-git-replace-graft"
continue
fi
if [[ "$mode" == "diverged" ]]; then
echo "fork=$fork mode=diverged current=$current latest=(skip) action=report-only"
continue
fi
# Decision basis: the fork's Cargo.toml base is ground truth; the map
# tag is the fallback when the fork has no Cargo.toml version.
cargo_base=$(fork_current_base_version "$fork")
basis="$current"
[[ -n "$cargo_base" ]] && basis="$cargo_base"
local result raw
latest=""
if [[ $NO_FETCH -eq 0 ]]; then
result=$(latest_tag_for_url "$url" "$fork" "$basis")
latest="${result%%|*}"
raw="${result##*|}"
# Tags existed upstream but none qualified as newer → already current.
[[ -z "$latest" && "$raw" -gt 0 ]] && latest="$basis"
else
latest="$basis"
fi
LATEST_FOR[$fork]="$latest"
if [[ -z "$latest" ]]; then
echo "fork=$fork mode=$mode current=$current latest=(unknown) action=skip-no-tag"
continue
fi
cmp=$(semver_cmp "$basis" "$latest")
case "$cmp" in
-1)
action="upgrade"
SOURCE_BUMPS_AVAILABLE=$((SOURCE_BUMPS_AVAILABLE + 1))
;;
0) action="ok" ;;
1) action="map-ahead-of-upstream" ;;
*) action="skip-non-semver" ;;
esac
echo "fork=$fork mode=$mode current=$current latest=$latest cargo=$cargo_base basis=$basis action=$action"
done
echo ""
echo "source_bumps_available=$SOURCE_BUMPS_AVAILABLE"
}
# ---- phase: label sync -----------------------------------------------------
phase_label_sync() {
local sync_flag="--no-regen"
[[ $REGEN -eq 1 ]] && sync_flag="--regen"
if [[ $READ_ONLY -eq 1 ]]; then
echo ""
echo "=== Label sync (read-only: sync-versions.sh --check) ==="
if "$SCRIPT_DIR/sync-versions.sh" --check >/tmp/br-label-sync.$$ 2>&1; then
LABEL_DRIFT=0
echo " OK: no label drift"
else
LABEL_DRIFT=1
echo " DRIFT detected (sync-versions.sh --check exit non-zero)"
grep -E '^(DRIFT|FAIL)' /tmp/br-label-sync.$$ | head -20 | sed 's/^/ /' || true
fi
rm -f /tmp/br-label-sync.$$
return
fi
echo ""
echo "=== Label sync: sync-versions.sh $sync_flag ==="
"$SCRIPT_DIR/sync-versions.sh" "$sync_flag" || true
}
# ---- phase: source bumps ---------------------------------------------------
phase_source_bumps() {
if [[ $WITH_SOURCES -eq 0 ]]; then
return
fi
if [[ $READ_ONLY -eq 1 ]]; then
echo ""
echo "(--dry-run / --check: source bumps NOT executed)"
if [[ $SOURCE_BUMPS_AVAILABLE -gt 0 ]]; then
echo "(rerun with --with-sources to perform $SOURCE_BUMPS_AVAILABLE bump(s))"
fi
return
fi
echo ""
echo "=== Fork source bumps (--with-sources) ==="
local idx fork url current mode latest cargo_base basis cmp ct old_ver new_ver backup_branch
local -a upgrade_args=()
for ((idx = 0; idx < ${#MAP_FORKS[@]}; idx++)); do
fork="${MAP_FORKS[$idx]}"
url="${MAP_URLS[$idx]}"
current="${MAP_TAGS[$idx]}"
mode="${MAP_MODES[$idx]}"
if [[ -n "$FORK_FILTER" && "$fork" != "$FORK_FILTER" ]]; then continue; fi
if [[ "$current" == "main" ]]; then continue; fi
# Bootloader is never source-bumped, even with --force-diverged: its
# git history is genuinely unrelated to upstream (no merge-base), so
# the rebase machinery cannot work. The documented path is a one-time
# operator 'git replace --graft' intervention (RELEASE-BUMP-WORKFLOW.md).
if [[ "$fork" == "bootloader" ]]; then
echo " (bootloader: never auto-bumped — no merge-base with upstream;"
echo " one-time operator 'git replace --graft' intervention required;"
echo " see local/docs/RELEASE-BUMP-WORKFLOW.md)"
continue
fi
case "$mode" in
snapshot|tracked) ;;
diverged)
# phase_report already showed action=report-only; with
# --force-diverged we could attempt, but the map's current
# tag is the manual base. Skip unless --force-diverged.
if [[ $FORCE_DIVERGED -eq 1 ]]; then
echo " ($fork: diverged + --force-diverged; attempting)"
else
continue
fi
;;
*) continue ;;
esac
cargo_base=$(fork_current_base_version "$fork")
basis="$current"
[[ -n "$cargo_base" ]] && basis="$cargo_base"
# Reuse the report phase's ls-remote result; only fetch when this fork
# was not covered (e.g. report filtered it out).
if [[ -v "LATEST_FOR[$fork]" ]]; then
latest="${LATEST_FOR[$fork]}"
elif [[ $NO_FETCH -eq 0 ]]; then
local result raw
result=$(latest_tag_for_url "$url" "$fork" "$basis")
latest="${result%%|*}"
raw="${result##*|}"
[[ -z "$latest" && "$raw" -gt 0 ]] && latest="$basis"
else
latest="$basis"
fi
[[ -z "$latest" ]] && continue
cmp=$(semver_cmp "$basis" "$latest")
[[ "$cmp" == "-1" ]] || continue
echo ""
echo "--- Bumping $fork: $basis -> $latest ---"
upgrade_args=(--to="$latest")
[[ $FORCE_DIVERGED -eq 1 ]] && upgrade_args+=(--force-diverged)
upgrade_args+=("$fork")
if ! "$SCRIPT_DIR/upgrade-forks.sh" "${upgrade_args[@]}"; then
echo " FAIL: upgrade-forks.sh ${upgrade_args[*]} failed" >&2
echo " Leaving label + map untouched for $fork" >&2
SOURCE_BUMPS_FAILED=$((SOURCE_BUMPS_FAILED + 1))
continue
fi
# On success: set fork Cargo.toml version to <tag>+rb<branch>.
# Quote-splice via awk with literal index() — sed regexes break on the
# '.'/'+' metacharacters present in versions like 0.9.0+rb0.3.1.
ct="$ROOT/local/sources/$fork/Cargo.toml"
if [[ -f "$ct" ]]; then
old_ver=$(grep -E '^version[[:space:]]*=' "$ct" | head -1 | sed 's/.*"\(.*\)".*/\1/' || true)
new_ver="${latest}+rb${BRANCH}"
if [[ -n "$old_ver" ]]; then
awk -v old="$old_ver" -v new="$new_ver" '
$0 ~ "^version[[:space:]]*=" {
q1 = index($0, "\"")
q2 = index(substr($0, q1 + 1), "\"") + q1
if (q1 > 0 && q2 > q1 && substr($0, q1 + 1, q2 - q1 - 1) == old) {
$0 = substr($0, 1, q1) new substr($0, q2)
}
print; next
}
{ print }
' "$ct" > "$ct.tmp.$$" && mv "$ct.tmp.$$" "$ct"
[[ -e "$ct.tmp.$$" ]] && rm -f "$ct.tmp.$$" || true
echo " Cargo.toml: $old_ver -> $new_ver"
fi
fi
# Regen the fork's Cargo.lock (best-effort: warn on failure, continue)
if [[ -f "$ROOT/local/sources/$fork/Cargo.toml" ]]; then
if (cd "$ROOT/local/sources/$fork" && cargo generate-lockfile >/dev/null 2>&1); then
echo " Cargo.lock: regenerated"
else
echo " WARN: cargo generate-lockfile failed for $fork (continuing)" >&2
fi
fi
# Update the map row's column 3 in place
update_map_tag_inplace "$fork" "$latest"
echo " Map: column 3 ($current -> $latest)"
# Report the rb-backup/* branch created by upgrade-forks.sh
backup_branch=$(git -C "$ROOT/local/sources/$fork" branch --list 'rb-backup/*' 2>/dev/null | tail -1 | sed 's/^[* ]*//' || true)
if [[ -n "$backup_branch" ]]; then
echo " Backup branch: $backup_branch"
fi
SOURCE_BUMPS_APPLIED=$((SOURCE_BUMPS_APPLIED + 1))
done
# Re-sync labels after all source bumps land
echo ""
echo "=== Post-bump label sync (sync-versions.sh --no-regen) ==="
"$SCRIPT_DIR/sync-versions.sh" --no-regen || true
}
# ---- phase: external bumps -------------------------------------------------
phase_external() {
[[ $WITH_EXTERNAL -eq 0 ]] && return
local script="$SCRIPT_DIR/bump-graphics-recipes.sh"
if [[ ! -e "$script" ]]; then
echo ""
echo "WARN: bump-graphics-recipes.sh not present; skipping external bump" >&2
echo " (a sibling unit is rewriting it concurrently)" >&2
return
fi
local -a args=()
[[ $DRY_RUN -eq 1 ]] && args+=(--dry-run)
[[ $NO_FETCH -eq 1 ]] && args+=(--no-fetch)
echo ""
echo "=== External bumps (bump-graphics-recipes.sh ${args[*]:-}) ==="
"$script" "${args[@]}" || true
}
# ---- phase: commit guide ---------------------------------------------------
print_commit_guide() {
echo ""
echo "=== Commit guide (NEVER executed automatically by this script) ==="
echo ""
echo "Parent-repo files potentially changed:"
echo " local/fork-upstream-map.toml (column-3 tag updates)"
echo " Cargo.toml (cookbook root, on label drift)"
echo " local/recipes/*/source/Cargo.toml (Cat 1 crates, on label drift)"
echo " local/sources/<fork>/Cargo.toml (fork base version, on source bump)"
echo " local/sources/<fork>/Cargo.lock (fork lockfile, on source bump)"
echo ""
echo "Commit them on branch '$BRANCH':"
echo " git -C \"$ROOT\" add local/fork-upstream-map.toml Cargo.toml \\"
echo " local/recipes/ local/sources/"
echo " git -C \"$ROOT\" commit -m \"release: bump fork tags + labels to $BRANCH\""
echo ""
echo "Per-fork commits + pushes (for source bumps; replace <tag> per fork):"
if [[ -n "$FORK_FILTER" ]]; then
echo " git -C \"$ROOT/local/sources/$FORK_FILTER\" add -A"
echo " git -C \"$ROOT/local/sources/$FORK_FILTER\" commit -m \"bump: $FORK_FILTER to <tag>+rb$BRANCH\""
echo " git -C \"$ROOT/local/sources/$FORK_FILTER\" push origin submodule/$FORK_FILTER"
else
local idx fork mode current
for ((idx = 0; idx < ${#MAP_FORKS[@]}; idx++)); do
fork="${MAP_FORKS[$idx]}"
mode="${MAP_MODES[$idx]}"
current="${MAP_TAGS[$idx]}"
[[ "$current" == "main" ]] && continue
[[ "$mode" == "diverged" ]] && continue
echo " # $fork (current tag: $current)"
echo " git -C \"$ROOT/local/sources/$fork\" add -A"
echo " git -C \"$ROOT/local/sources/$fork\" commit -m \"bump: $fork to <tag>+rb$BRANCH\""
echo " git -C \"$ROOT/local/sources/$fork\" push origin submodule/$fork"
done
fi
}
# ---- main ------------------------------------------------------------------
parse_map
phase_report
phase_label_sync
phase_source_bumps
phase_external
print_commit_guide
if [[ $CHECK_ONLY -eq 1 ]]; then
echo ""
if [[ $LABEL_DRIFT -gt 0 ]]; then
echo "CHECK FAILED: label drift detected"
exit 1
fi
if [[ $SOURCE_BUMPS_AVAILABLE -gt 0 ]]; then
echo "CHECK FAILED: $SOURCE_BUMPS_AVAILABLE fork(s) have available source bumps"
exit 1
fi
echo "CHECK PASSED: no drift, no available source bumps"
exit 0
fi
exit 0