#!/usr/bin/env bash # push-fork-branches.sh — Stage Round 3+ fork advances to origin. # # Per AGENTS.md "BRANCH AND SUBMODULE POLICY", pushing diverged # fork branches requires operator review. This script prints the # necessary commands so the operator can run them manually after # review. # # Usage: # ./local/scripts/push-fork-branches.sh # show diff + commands # ./local/scripts/push-fork-branches.sh --execute # execute safe-by-default # # Default mode: NO-OP. The script only prints what SHOULD be pushed. # The operator must hand-run the commands shown. # # Reasons for "do not auto-push": # 1. Multiple forks diverged: 202 ahead (userutils), 448 ahead (syscall), # and small diverges (base 202 ahead, etc). # 2. Pushing would clobber commits that origin has that we don't # (Phase 1.0 + Phase 2.0 will be lost). # 3. Per AGENTS.md: "Agents MUST NOT create new branches. … no pushing # new branches." This script never creates branches — it only # fast-forwards existing submodule/ branches to commits that # have already been made inside the local forks. # # Strategy: for each fork with `N ahead`, use `git push --force-with-lease` # against the tracked upstream ref (submodule/). The force-with-lease # ensures we don't clobber concurrent pushes we haven't seen. # Note: do NOT use `set -e` here — some ahead/behind calculations # legitimately return empty strings when fork and ref are equal, and # the pipefail behavior interferes with the cd sub-shells. Keep this # script tolerant of harmless failures so its output is diagnostic. set -uo pipefail ROOT="$(cd "$(dirname "$0")/../.." && pwd)" cd "$ROOT" FORKS=(base bootloader installer kernel libredox relibc syscall userutils) echo "=== Red Bear fork-branch status vs origin (2026-07-12) ===" echo "" printf "%-12s %-15s %-10s %-10s\n" "fork" "local" "ahead" "behind" echo "---------- --------------- ---------- ----------" REVIEW_NEEDED=() for fork in "${FORKS[@]}"; do cd "local/sources/$fork" || continue remote_sha=$(git ls-remote origin "refs/heads/submodule/$fork" 2>&1 | awk '{print $1}') local_sha=$(git rev-parse HEAD 2>&1) # Avoid the ambiguous `HEAD` syntax by using the commit SHA directly. # The fork's `HEAD` may also be ambiguous when the working tree has # a stale symlink that confuses git's revision parser. if [ -n "$local_sha" ] && [ -n "$remote_sha" ]; then ahead=$(git rev-list --count "$remote_sha..$local_sha" 2>/dev/null | head -1 || echo "?") # Reverse direction: count commits on remote not in local behind=$(git rev-list --count "$local_sha..$remote_sha" 2>/dev/null | head -1 || echo "?") else ahead="?" behind="?" fi printf "%-12s %-15s %-10s %-10s\n" "$fork" "${local_sha:0:12}" "${ahead:-?}" "${behind:-?}" if [ "${behind:-0}" -eq 0 ] 2>/dev/null && [ "${ahead:-0}" -gt 0 ] 2>/dev/null; then REVIEW_NEEDED+=("$fork:$ahead") fi cd "$ROOT" done echo "" echo "=== Push commands (review BEFORE running) ===" echo "" echo "# Each line: cd into the fork, push to origin's submodule/ ref." echo "# --force-with-lease prevents clobbering concurrent work we haven't seen." echo "" for fork in "${REVIEW_NEEDED[@]}"; do fname="${fork%:*}" cnt="${fork#*:}" echo "# $fname: $cnt commits ahead — review via:" echo " cd local/sources/$fname && git log origin/submodule/$fname..HEAD --oneline" echo " # Then push (REVIEW the diff first):" echo " cd $ROOT/local/sources/$fname && git push --force-with-lease=refs/heads/submodule/$fname:\$(git ls-remote origin refs/heads/submodule/$fname | awk '{print \$1}') origin HEAD:refs/heads/submodule/$fname" echo "" done echo "=== Other forks ===" echo "" echo "libredox: dropped .cargo-ok + .cargo_vcs_info.json, tightened .gitignore." echo " → Push with: cd local/sources/libredox && git push origin HEAD:refs/heads/submodule/libredox" echo "" echo "bootloader: 927 vs 77 file divergence (mark 'diverged' in fork-upstream-map.toml)." echo " → DO NOT auto-push. Phase 2.4+ work: upgrade-forks.sh bootloader manual run." echo "" echo "installer: 30 vs 22 file divergence (mark 'diverged')." echo " → DO NOT auto-push. Phase 2.4+ work." echo "" echo "kernel: branch 0.3.1 (local release branch, not submodule/kernel)." echo " → Kernel's branch naming is intentional. DO NOT push to submodule/kernel." echo "" if [ "${1:-}" = "--execute" ]; then echo "" echo "=== EXECUTING ===" echo "If you ran --execute, you are forcibly advancing submodule/\$fork refs." echo "Press Ctrl-C within 5 seconds to abort." sleep 5 for fork in "${REVIEW_NEEDED[@]}"; do fname="${fork%:*}" cd "$ROOT/local/sources/$fname" || continue remote_sha=$(git ls-remote origin "refs/heads/submodule/$fname" 2>/dev/null | awk '{print $1}') if [ -n "$remote_sha" ]; then echo "Pushing $fname (force-with-lease=$remote_sha)..." if git push --force-with-lease="refs/heads/submodule/$fname:$remote_sha" \ origin "HEAD:refs/heads/submodule/$fname" 2>&1 | tail -3; then echo " $fname: pushed OK" else echo " $fname: PUSH FAILED — operator review required" fi fi cd "$ROOT" done fi echo "" echo "=== To push libredox cleanup (no force-with-lease needed, fast-forward): ===" echo " cd local/sources/libredox && git push origin HEAD:refs/heads/submodule/libredox"