99e5641127
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
580 lines
22 KiB
Bash
Executable File
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
|