Add bin/omarchy-release: the interactive release front door
A release train has three human moments, each one command: start (release branch on basecamp/omarchy + notes staging PR + edge→rc advance for minor/major), rc (pin both PKGBUILDs to the branch head as X.Y.ZrcN on the pkgs rc branch, trigger the rc channel build, wait for publish, optional RC ISO), and ship (final pins → promote rc→stable → tag → pins to master → GitHub release from the staging PR body → final ISO → website bump, each step skip-if-done so a crashed run resumes). Bare omarchy-release is the shepherd: it derives the train state from observed reality (remote branches, rc-branch pins, published channel dbs, tags — no state files) and offers the correct next step. Versions are inferred from branch names (v4-0-2 ⇒ 4.0.2rcN ⇒ v4.0.2). ship refuses to promote a commit no RC was cut from. pick is a multi-select over merged quattro PRs, cherry-picking merge commits. doctor pre-flights every credential and connection. self-test wired into CI. bin/omarchy-pkgs stays as the pin engine, driven with its db URL pointed at the rc channel and pins committed to the standing rc branch (rebuilt as master + pins per cut and force-pushed; the server rc worktree follows with reset --hard).
This commit is contained in:
@@ -28,4 +28,5 @@ jobs:
|
||||
pacman -Syu --noconfirm jq
|
||||
./bin/sync-upstream self-test
|
||||
./bin/omarchy-pkgs self-test
|
||||
./bin/omarchy-release self-test
|
||||
'
|
||||
|
||||
@@ -34,3 +34,4 @@ pkgbuilds/symfony-cli/symfony*
|
||||
pkgbuilds/yay/yay/
|
||||
.srcdest/
|
||||
.repo-host
|
||||
.worktrees/
|
||||
|
||||
@@ -40,6 +40,27 @@ docker run --rm --platform linux/arm64 alpine:latest uname -m
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Cutting an Omarchy release
|
||||
|
||||
`bin/omarchy-release` is the front door. A release train has three human
|
||||
moments, each one command — and bare `omarchy-release` observes reality
|
||||
(branches, pins, published channels, tags) and walks you to the next one:
|
||||
|
||||
```bash
|
||||
bin/omarchy-release # shepherd: status + guided next step
|
||||
bin/omarchy-release start 4.0.2 # open the train: branch v4-0-2 + staging PR
|
||||
bin/omarchy-release pick # choose merged PRs to cherry-pick (multi-select)
|
||||
bin/omarchy-release rc # publish the next 4.0.2rcN to the rc channel
|
||||
bin/omarchy-release ship # final pins, promote rc → stable, tag,
|
||||
# GitHub release, ISO, website — one swoop
|
||||
bin/omarchy-release doctor # verify credentials/connections up front
|
||||
```
|
||||
|
||||
Versions are inferred from branch names (`v4-0-2` ⇒ `4.0.2rcN` ⇒ tag
|
||||
`v4.0.2`); `start` is the only place a version is typed. Every command is
|
||||
idempotent — re-runs skip whatever is already done. `ship` refuses to promote
|
||||
a commit no RC was cut from.
|
||||
|
||||
### Full release
|
||||
|
||||
Ship the tested rc channel to stable (copies packages + signatures, then
|
||||
|
||||
+6
-2
@@ -2,8 +2,12 @@
|
||||
# Omarchy release management for the omarchy + omarchy-settings package pair.
|
||||
#
|
||||
# Cuts a release by rewriting both PKGBUILDs in lockstep (same _tag/_commit/
|
||||
# pkgver/sha256sums), committing, pushing to master, and poking the build host.
|
||||
# RCs publish to edge only; stable receives finals via `bin/repo migrate`.
|
||||
# pkgver/sha256sums), committing, pushing, and poking the build host.
|
||||
#
|
||||
# This is the pin ENGINE. The release front door is bin/omarchy-release, which
|
||||
# drives it against the rc channel (OMARCHY_EDGE_DB_URL points at the rc db,
|
||||
# pins commit to the rc branch) as part of the edge → rc → stable pipeline.
|
||||
# Direct invocations work as before and target the current branch + edge.
|
||||
#
|
||||
# Versioning convention (see the PKGBUILD header comments):
|
||||
# finals X.Y.Z from upstream tag vX.Y.Z
|
||||
|
||||
Executable
+916
@@ -0,0 +1,916 @@
|
||||
#!/bin/bash
|
||||
# omarchy-release — the front door for cutting Omarchy releases.
|
||||
#
|
||||
# A release train has three human moments, each one command:
|
||||
# start open the train: release branch + staging PR (+ advance edge -> rc)
|
||||
# rc cut a candidate: pin PKGBUILDs to the branch head, publish to rc
|
||||
# ship make it official: final pins, promote rc -> stable, tag, GH release,
|
||||
# ISO, website
|
||||
#
|
||||
# Run bare `omarchy-release` to be shepherded: it observes reality (branches,
|
||||
# pins, published channels, tags) and offers the correct next step. Every
|
||||
# subcommand is idempotent — a re-run checks what is already done and skips it.
|
||||
#
|
||||
# Versions are inferred from branch names: branch v4-0-2 -> RCs 4.0.2rcN ->
|
||||
# tag v4.0.2. `start` is the only place a version is typed.
|
||||
|
||||
set -e
|
||||
|
||||
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
|
||||
source "$BUILD_ROOT/helpers/message-helpers.sh"
|
||||
source "$BUILD_ROOT/helpers/host-helpers.sh"
|
||||
|
||||
UPSTREAM_URL="${OMARCHY_UPSTREAM_URL:-https://github.com/basecamp/omarchy.git}"
|
||||
UPSTREAM_REPO="${OMARCHY_UPSTREAM_REPO:-basecamp/omarchy}"
|
||||
SITE_REPO="${OMARCHY_SITE_REPO:-omacom-io/omarchy-site}"
|
||||
ISO_REPO="${OMARCHY_ISO_REPO:-omacom-io/omarchy-iso}"
|
||||
DEV_BRANCH="${OMARCHY_DEV_BRANCH:-quattro}"
|
||||
|
||||
PKGS_DB_BASE="${OMARCHY_PKGS_DB_BASE:-https://pkgs.omarchy.org}"
|
||||
RC_DB_URL="$PKGS_DB_BASE/rc/x86_64/omarchy.db.tar.zst"
|
||||
|
||||
SRCDEST_DIR="$BUILD_ROOT/.srcdest"
|
||||
MIRROR_CLONE="$SRCDEST_DIR/omarchy" # bare mirror (shared with bin/omarchy-pkgs)
|
||||
WORK_CLONE="$SRCDEST_DIR/omarchy-work" # working clone for pick/cherry-pick
|
||||
RC_WORKTREE="$BUILD_ROOT/.worktrees/rc" # pkgs repo rc branch worktree
|
||||
|
||||
ASSUME_YES=false
|
||||
REPO_HOST_OVERRIDE=""
|
||||
|
||||
show_usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 [command] [options]
|
||||
|
||||
Run with no command to be shepherded through whatever the next step is.
|
||||
|
||||
Commands:
|
||||
start [X.Y.Z] Open a release train: create branch v X-Y-Z on $UPSTREAM_REPO
|
||||
(from the previous tag for a patch, from $DEV_BRANCH for a
|
||||
minor/major), open the release-notes staging PR, and for a
|
||||
minor/major advance edge -> rc on the build host
|
||||
pick [pr#|sha ...] Cherry-pick merged PRs onto the release branch; with no
|
||||
args, choose from a list of merged $DEV_BRANCH PRs
|
||||
rc Cut the next X.Y.ZrcN into the rc channel
|
||||
ship Final pins, promote rc -> stable, tag, GitHub release,
|
||||
ISO (prompted), website bump
|
||||
status Show where the train stands (read-only)
|
||||
doctor Verify every credential and connection the flow needs
|
||||
self-test Run helper unit tests
|
||||
|
||||
Options:
|
||||
--yes Skip confirmation prompts (for scripting/CI)
|
||||
--host <host> Build host ssh destination (else \$OMARCHY_REPO_HOST or .repo-host)
|
||||
--iso / --no-iso (rc, ship) Build the ISO without asking / skip it
|
||||
--no-wait (rc, ship) Do not poll for the published build
|
||||
-h, --help Show this help
|
||||
EOF
|
||||
}
|
||||
|
||||
confirm() {
|
||||
local prompt="$1" reply
|
||||
[[ "$ASSUME_YES" == true ]] && return 0
|
||||
read -r -p "$prompt [y/N] " reply
|
||||
[[ "$reply" =~ ^[Yy]$ ]]
|
||||
}
|
||||
|
||||
# --- version <-> branch ------------------------------------------------------
|
||||
|
||||
version_to_branch() { # 4.0.2 -> v4-0-2
|
||||
local v="${1#v}"
|
||||
[[ "$v" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || return 1
|
||||
echo "v${v//./-}"
|
||||
}
|
||||
|
||||
branch_to_version() { # v4-0-2 -> 4.0.2
|
||||
local b="$1"
|
||||
[[ "$b" =~ ^v([0-9]+)-([0-9]+)-([0-9]+)$ ]] || return 1
|
||||
echo "${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.${BASH_REMATCH[3]}"
|
||||
}
|
||||
|
||||
version_is_patch() { # Z > 0
|
||||
[[ "$1" =~ ^[0-9]+\.[0-9]+\.([0-9]+)$ ]] && ((BASH_REMATCH[1] > 0))
|
||||
}
|
||||
|
||||
previous_patch_tag() { # 4.0.2 -> v4.0.1
|
||||
[[ "$1" =~ ^([0-9]+\.[0-9]+)\.([0-9]+)$ ]] || return 1
|
||||
echo "v${BASH_REMATCH[1]}.$((BASH_REMATCH[2] - 1))"
|
||||
}
|
||||
|
||||
# --- upstream queries --------------------------------------------------------
|
||||
|
||||
ls_remote() { git ls-remote "$UPSTREAM_URL" "$@"; }
|
||||
|
||||
tag_exists() { [[ -n "$(ls_remote "refs/tags/$1" | head -1)" ]]; }
|
||||
|
||||
branch_head() { ls_remote "refs/heads/$1" | awk '{print $1}'; }
|
||||
|
||||
resolve_tag_commit() {
|
||||
local tag="$1" peeled sha
|
||||
peeled=$(ls_remote "refs/tags/$tag^{}" | awk '{print $1}')
|
||||
sha=$(ls_remote "refs/tags/$tag" | awk '{print $1}')
|
||||
echo "${peeled:-$sha}"
|
||||
}
|
||||
|
||||
# Newest v*-*-* release branch whose final tag does not exist yet. Shipping
|
||||
# creates the tag, so "tag exists" is what closes a train.
|
||||
open_train_branch() {
|
||||
local branch best_ver="" best_branch="" ver
|
||||
while IFS= read -r branch; do
|
||||
ver=$(branch_to_version "$branch") || continue
|
||||
tag_exists "v$ver" && continue
|
||||
if [[ -z "$best_ver" ]] || [[ $(vercmp "$ver" "$best_ver") -gt 0 ]]; then
|
||||
best_ver="$ver" best_branch="$branch"
|
||||
fi
|
||||
done < <(ls_remote 'refs/heads/v*' | awk -F/ '{print $3}')
|
||||
[[ -n "$best_branch" ]] && echo "$best_branch"
|
||||
}
|
||||
|
||||
ensure_mirror_clone() {
|
||||
if [[ -d "$MIRROR_CLONE" ]]; then
|
||||
git -C "$MIRROR_CLONE" fetch --quiet origin
|
||||
else
|
||||
mkdir -p "$SRCDEST_DIR"
|
||||
print_info "Cloning $UPSTREAM_URL (cached in $SRCDEST_DIR)..."
|
||||
git clone --mirror --quiet "$UPSTREAM_URL" "$MIRROR_CLONE"
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_work_clone() {
|
||||
if [[ -d "$WORK_CLONE" ]]; then
|
||||
git -C "$WORK_CLONE" fetch --quiet origin
|
||||
else
|
||||
mkdir -p "$SRCDEST_DIR"
|
||||
print_info "Cloning $UPSTREAM_URL working copy..."
|
||||
git clone --quiet "$UPSTREAM_URL" "$WORK_CLONE"
|
||||
fi
|
||||
}
|
||||
|
||||
# --- published channel state -------------------------------------------------
|
||||
|
||||
# Prints omarchy's published version in a channel; empty when absent, rc 2 when
|
||||
# the database cannot be read (callers must not mistake an outage for absence).
|
||||
published_version() {
|
||||
local channel="$1" tmp descs
|
||||
tmp=$(mktemp) || return 2
|
||||
if ! curl -sf "$PKGS_DB_BASE/$channel/x86_64/omarchy.db.tar.zst" -o "$tmp"; then
|
||||
rm -f "$tmp"
|
||||
return 2
|
||||
fi
|
||||
if ! descs=$(tar -xO --zstd -f "$tmp" --wildcards '*/desc' 2>/dev/null); then
|
||||
rm -f "$tmp"
|
||||
return 2
|
||||
fi
|
||||
rm -f "$tmp"
|
||||
awk '
|
||||
function emit() {
|
||||
if (!found && name == "omarchy" && version != "") { print version; found = 1 }
|
||||
name = ""; version = ""
|
||||
}
|
||||
$0 == "%FILENAME%" { emit(); next }
|
||||
$0 == "%NAME%" { getline; name = $0; next }
|
||||
$0 == "%VERSION%" { getline; version = $0; next }
|
||||
END { emit() }
|
||||
' <<<"$descs"
|
||||
}
|
||||
|
||||
# The rc branch of THIS repo carries the current pins. Read them without
|
||||
# touching the working tree.
|
||||
rc_branch_pin() { # prints "pkgver commit", empty when no rc branch
|
||||
local ref="origin/rc" pkgbuild
|
||||
git -C "$BUILD_ROOT" fetch --quiet origin rc 2>/dev/null || true
|
||||
pkgbuild=$(git -C "$BUILD_ROOT" show "$ref:pkgbuilds/omarchy/PKGBUILD" 2>/dev/null) || return 0
|
||||
local pkgver commit
|
||||
pkgver=$(grep -E '^pkgver=' <<<"$pkgbuild" | head -1 | cut -d= -f2)
|
||||
commit=$(grep -E '^_commit=' <<<"$pkgbuild" | head -1 | cut -d= -f2 | tr -d "'\"")
|
||||
[[ -n "$pkgver" ]] && echo "$pkgver $commit"
|
||||
}
|
||||
|
||||
# --- build host --------------------------------------------------------------
|
||||
|
||||
repo_host() { resolve_repo_host "$REPO_HOST_OVERRIDE"; }
|
||||
|
||||
host_or_print() { # prints the host, or prints manual instructions and fails
|
||||
local host what="$1" cmd="$2"
|
||||
if host=$(repo_host); then
|
||||
echo "$host"
|
||||
return 0
|
||||
fi
|
||||
print_warning "No build host configured (set OMARCHY_REPO_HOST, --host, or $BUILD_ROOT/.repo-host)"
|
||||
echo "Run this on the build host to $what:" >&2
|
||||
echo " $cmd" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
trigger_rc_build() {
|
||||
local host
|
||||
host=$(host_or_print "build the rc channel" \
|
||||
"git -C /root/omarchy-pkgs-rc fetch origin rc && git -C /root/omarchy-pkgs-rc reset --hard origin/rc && touch /root/.state/.sync-needed-rc && systemctl start --no-block omarchy-auto-release-rc.service") || return 1
|
||||
print_info "Triggering rc build on $host..."
|
||||
ssh "$host" 'git -C /root/omarchy-pkgs-rc fetch origin rc && git -C /root/omarchy-pkgs-rc reset --hard origin/rc && mkdir -p /root/.state && touch /root/.state/.sync-needed-rc && systemctl start --no-block omarchy-auto-release-rc.service'
|
||||
}
|
||||
|
||||
host_advance() { # host_advance <from> <to> [extra args...]
|
||||
local from="$1" to="$2"
|
||||
shift 2
|
||||
local host
|
||||
host=$(host_or_print "advance $from -> $to" \
|
||||
"cd /root/omarchy-pkgs && bin/repo advance --from $from --to $to --skip-prod-check $*") || return 1
|
||||
print_info "Advancing $from -> $to on $host..."
|
||||
ssh "$host" "cd /root/omarchy-pkgs && git pull --ff-only && bin/repo advance --from $from --to $to --skip-prod-check $*"
|
||||
}
|
||||
|
||||
wait_for_published() { # wait_for_published <channel> <version> [timeout-seconds]
|
||||
local channel="$1" want="$2" timeout="${3:-3600}" waited=0 got
|
||||
print_info "Waiting for $want to appear in the $channel channel (up to $((timeout / 60))m)..."
|
||||
while ((waited < timeout)); do
|
||||
got=$(published_version "$channel" 2>/dev/null) || got=""
|
||||
if [[ "${got%-*}" == "$want" ]]; then
|
||||
print_success "$channel now serves omarchy $got"
|
||||
return 0
|
||||
fi
|
||||
sleep 60
|
||||
waited=$((waited + 60))
|
||||
printf '.' >&2
|
||||
done
|
||||
echo "" >&2
|
||||
print_warning "Timed out waiting for $want in $channel (currently: ${got:-unknown})"
|
||||
print_info "The build may still be running — re-run this command to resume."
|
||||
return 1
|
||||
}
|
||||
|
||||
# --- pkgs rc branch (pin commits) --------------------------------------------
|
||||
|
||||
# The rc branch is rebuilt as origin/master + pin commit(s) for each cut and
|
||||
# force-pushed; the build host follows with reset --hard. History on it is
|
||||
# disposable — the pins fully describe the release.
|
||||
prepare_rc_worktree() {
|
||||
git -C "$BUILD_ROOT" fetch --quiet origin
|
||||
if [[ ! -d "$RC_WORKTREE" ]]; then
|
||||
mkdir -p "$(dirname "$RC_WORKTREE")"
|
||||
git -C "$BUILD_ROOT" worktree add --quiet -B rc "$RC_WORKTREE" origin/master
|
||||
else
|
||||
git -C "$RC_WORKTREE" checkout --quiet -B rc origin/master
|
||||
git -C "$RC_WORKTREE" reset --quiet --hard origin/master
|
||||
fi
|
||||
}
|
||||
|
||||
cut_pins() { # cut_pins <omarchy-pkgs release args...>
|
||||
prepare_rc_worktree
|
||||
# The pin engine's "edge DB" is the ordering floor and rc auto-numbering
|
||||
# source; in the pipeline model that floor is the rc channel.
|
||||
(cd "$RC_WORKTREE" &&
|
||||
OMARCHY_EDGE_DB_URL="$RC_DB_URL" bin/omarchy-pkgs release "$@" --no-push --yes)
|
||||
git -C "$RC_WORKTREE" push --force-with-lease origin rc
|
||||
}
|
||||
|
||||
# --- ISO ---------------------------------------------------------------------
|
||||
|
||||
iso_checkout() { # prints a usable omarchy-iso checkout, cloning to tmp if needed
|
||||
if [[ -n "${OMARCHY_ISO_DIR:-}" && -d "${OMARCHY_ISO_DIR:-}" ]]; then
|
||||
echo "$OMARCHY_ISO_DIR"
|
||||
return 0
|
||||
fi
|
||||
if [[ -d "$BUILD_ROOT/../omarchy-iso/.git" ]]; then
|
||||
realpath "$BUILD_ROOT/../omarchy-iso"
|
||||
return 0
|
||||
fi
|
||||
local tmp="$SRCDEST_DIR/omarchy-iso"
|
||||
if [[ -d "$tmp" ]]; then
|
||||
git -C "$tmp" pull --ff-only --quiet || true
|
||||
else
|
||||
print_info "Cloning $ISO_REPO..." >&2
|
||||
git clone --quiet "https://github.com/$ISO_REPO.git" "$tmp"
|
||||
fi
|
||||
echo "$tmp"
|
||||
}
|
||||
|
||||
build_iso() { # build_iso <version> [--rc]
|
||||
local version="$1" rc_flag="${2:-}" dir
|
||||
dir=$(iso_checkout) || return 1
|
||||
if ! command -v docker >/dev/null || ! docker info >/dev/null 2>&1; then
|
||||
print_warning "Docker unavailable — cannot build the ISO here. Run on a Docker machine:"
|
||||
echo " cd $dir && bin/omarchy-iso-release ${rc_flag:+$rc_flag }$version"
|
||||
return 1
|
||||
fi
|
||||
print_info "Building ${rc_flag:+RC }ISO $version (this takes a while)..."
|
||||
(cd "$dir" && PATH="$dir/bin:$PATH" bin/omarchy-iso-release ${rc_flag:+"$rc_flag"} "$version")
|
||||
}
|
||||
|
||||
maybe_iso() { # maybe_iso <version> <rc|final> <iso_mode: ask|yes|no>
|
||||
local version="$1" kind="$2" mode="$3" rc_flag=""
|
||||
[[ "$kind" == "rc" ]] && rc_flag="--rc"
|
||||
case "$mode" in
|
||||
no) return 0 ;;
|
||||
yes) build_iso "$version" "$rc_flag" ;;
|
||||
ask)
|
||||
if confirm "Build and upload the ${kind} ISO for $version?"; then
|
||||
build_iso "$version" "$rc_flag"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# --- website -----------------------------------------------------------------
|
||||
|
||||
update_website() { # update_website <version>
|
||||
local version="$1" tmp="$SRCDEST_DIR/omarchy-site"
|
||||
print_info "Updating $SITE_REPO ISO references to $version..."
|
||||
if [[ -d "$tmp" ]]; then
|
||||
git -C "$tmp" fetch --quiet origin && git -C "$tmp" reset --quiet --hard origin/HEAD
|
||||
else
|
||||
git clone --quiet "git@github.com:$SITE_REPO.git" "$tmp" || {
|
||||
print_warning "Could not clone $SITE_REPO — update the ISO URL there manually"
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
local matches
|
||||
matches=$(grep -rIlE 'omarchy-[0-9]+\.[0-9]+\.[0-9]+(-rc)?\.iso' "$tmp" --exclude-dir=.git || true)
|
||||
if [[ -z "$matches" ]]; then
|
||||
print_warning "No omarchy-<version>.iso references found in $SITE_REPO — update it manually"
|
||||
return 1
|
||||
fi
|
||||
echo "$matches" | while IFS= read -r f; do
|
||||
sed -i -E "s/omarchy-[0-9]+\.[0-9]+\.[0-9]+\.iso/omarchy-$version.iso/g" "$f"
|
||||
echo " updated: ${f#"$tmp"/}"
|
||||
done
|
||||
if git -C "$tmp" diff --quiet; then
|
||||
print_info "Website already references $version"
|
||||
return 0
|
||||
fi
|
||||
git -C "$tmp" --no-pager diff --stat
|
||||
confirm "Push this ISO URL bump to $SITE_REPO?" || {
|
||||
print_info "Left uncommitted in $tmp"
|
||||
return 1
|
||||
}
|
||||
git -C "$tmp" commit --quiet -am "Point ISO download at omarchy-$version.iso" &&
|
||||
git -C "$tmp" push --quiet origin HEAD
|
||||
print_success "Website updated to omarchy-$version.iso"
|
||||
}
|
||||
|
||||
# --- commands ----------------------------------------------------------------
|
||||
|
||||
cmd_start() {
|
||||
local version="${1:-}"
|
||||
if [[ -z "$version" ]]; then
|
||||
read -r -p "Version to release (X.Y.Z): " version
|
||||
fi
|
||||
version="${version#v}"
|
||||
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
print_error "Invalid version '$version' (expected X.Y.Z)"
|
||||
exit 1
|
||||
fi
|
||||
local branch
|
||||
branch=$(version_to_branch "$version")
|
||||
|
||||
print_header "Open release train $version"
|
||||
|
||||
if tag_exists "v$version"; then
|
||||
print_error "v$version is already tagged on $UPSTREAM_REPO — this train has shipped"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Base: previous tag for a patch, dev branch head for a minor/major.
|
||||
local base_ref base_sha kind
|
||||
if version_is_patch "$version"; then
|
||||
kind="patch"
|
||||
base_ref=$(previous_patch_tag "$version")
|
||||
if ! tag_exists "$base_ref"; then
|
||||
print_error "Base tag $base_ref not found on $UPSTREAM_REPO"
|
||||
exit 1
|
||||
fi
|
||||
base_sha=$(resolve_tag_commit "$base_ref")
|
||||
else
|
||||
kind="minor/major"
|
||||
base_ref="$DEV_BRANCH"
|
||||
base_sha=$(branch_head "$DEV_BRANCH")
|
||||
fi
|
||||
|
||||
if [[ -n "$(branch_head "$branch")" ]]; then
|
||||
print_success "Release branch $branch already exists — adopting it"
|
||||
else
|
||||
print_info "Release type: $kind — branching $branch from $base_ref (${base_sha:0:12})"
|
||||
confirm "Create branch $branch on $UPSTREAM_REPO?" || exit 1
|
||||
ensure_mirror_clone
|
||||
git -C "$MIRROR_CLONE" push --quiet origin "$base_sha:refs/heads/$branch"
|
||||
print_success "Created $branch"
|
||||
fi
|
||||
|
||||
# Staging PR: the body is the release-notes staging ground. A branch with no
|
||||
# commits beyond its base cannot carry a PR yet; created lazily by pick.
|
||||
if command -v gh >/dev/null; then
|
||||
if gh pr view "$branch" --repo "$UPSTREAM_REPO" >/dev/null 2>&1; then
|
||||
print_success "Staging PR for $branch already open"
|
||||
elif ! gh pr create --repo "$UPSTREAM_REPO" --draft --base "$DEV_BRANCH" --head "$branch" \
|
||||
--title "Release v$version" \
|
||||
--body "Staging ground for the v$version release notes. Edit this body — it becomes the GitHub release text." 2>/dev/null; then
|
||||
print_info "Staging PR not created yet (no commits on $branch) — it opens after the first pick"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Minor/major trains ship new edge packages: carry edge forward into rc so
|
||||
# RC testing runs against the set stable users will get.
|
||||
if [[ "$kind" != "patch" ]]; then
|
||||
host_advance edge rc || true
|
||||
else
|
||||
print_info "Patch train: rc already mirrors stable — nothing to advance"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_success "Train $version is open"
|
||||
print_info "Next: omarchy-release pick (cherry-pick fixes), then omarchy-release rc"
|
||||
}
|
||||
|
||||
cmd_pick() {
|
||||
local branch version
|
||||
branch=$(open_train_branch) || true
|
||||
if [[ -z "$branch" ]]; then
|
||||
print_error "No open release train — run: omarchy-release start"
|
||||
exit 1
|
||||
fi
|
||||
version=$(branch_to_version "$branch")
|
||||
print_header "Pick changes onto $branch ($version)"
|
||||
|
||||
ensure_work_clone
|
||||
git -C "$WORK_CLONE" checkout --quiet -B "$branch" "origin/$branch"
|
||||
|
||||
local -a shas=() labels=()
|
||||
if [[ $# -gt 0 ]]; then
|
||||
local arg sha title
|
||||
for arg in "$@"; do
|
||||
if [[ "$arg" =~ ^[0-9]+$ ]]; then
|
||||
sha=$(gh pr view "$arg" --repo "$UPSTREAM_REPO" --json mergeCommit --jq '.mergeCommit.oid' 2>/dev/null)
|
||||
title=$(gh pr view "$arg" --repo "$UPSTREAM_REPO" --json title --jq '.title' 2>/dev/null)
|
||||
if [[ -z "$sha" || "$sha" == "null" ]]; then
|
||||
print_error "PR #$arg has no merge commit (not merged?)"
|
||||
exit 1
|
||||
fi
|
||||
shas+=("$sha") labels+=("PR #$arg: $title")
|
||||
else
|
||||
shas+=("$arg") labels+=("commit $arg")
|
||||
fi
|
||||
done
|
||||
else
|
||||
# Interactive: merged dev-branch PRs whose merge commits are not on the
|
||||
# release branch yet, oldest first so picks apply in merge order.
|
||||
print_info "Loading merged $DEV_BRANCH PRs not yet on $branch..."
|
||||
local list
|
||||
list=$(gh pr list --repo "$UPSTREAM_REPO" --state merged --base "$DEV_BRANCH" \
|
||||
--limit 30 --json number,title,mergeCommit,mergedAt \
|
||||
--jq 'sort_by(.mergedAt) | .[] | "\(.mergeCommit.oid)\t#\(.number) \(.title)"')
|
||||
local -a cand_shas=() cand_labels=()
|
||||
while IFS=$'\t' read -r sha label; do
|
||||
[[ -z "$sha" || "$sha" == "null" ]] && continue
|
||||
git -C "$WORK_CLONE" merge-base --is-ancestor "$sha" "origin/$branch" 2>/dev/null && continue
|
||||
cand_shas+=("$sha") cand_labels+=("$label")
|
||||
done <<<"$list"
|
||||
if [[ ${#cand_shas[@]} -eq 0 ]]; then
|
||||
print_success "Nothing to pick — every recent merged PR is already on $branch"
|
||||
exit 0
|
||||
fi
|
||||
echo ""
|
||||
local i
|
||||
for i in "${!cand_labels[@]}"; do
|
||||
printf ' %2d) %s\n' "$((i + 1))" "${cand_labels[$i]}"
|
||||
done
|
||||
echo ""
|
||||
local selection
|
||||
read -r -p "Pick which? (e.g. 1,3-5 or 'all'): " selection
|
||||
if [[ "$selection" == "all" ]]; then
|
||||
shas=("${cand_shas[@]}") labels=("${cand_labels[@]}")
|
||||
else
|
||||
local part
|
||||
for part in ${selection//,/ }; do
|
||||
if [[ "$part" =~ ^([0-9]+)-([0-9]+)$ ]]; then
|
||||
for ((i = BASH_REMATCH[1]; i <= BASH_REMATCH[2]; i++)); do
|
||||
shas+=("${cand_shas[$((i - 1))]}") labels+=("${cand_labels[$((i - 1))]}")
|
||||
done
|
||||
elif [[ "$part" =~ ^[0-9]+$ ]]; then
|
||||
shas+=("${cand_shas[$((part - 1))]}") labels+=("${cand_labels[$((part - 1))]}")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
[[ ${#shas[@]} -eq 0 ]] && { print_info "Nothing selected."; exit 0; }
|
||||
|
||||
local i
|
||||
for i in "${!shas[@]}"; do
|
||||
print_info "Cherry-picking ${labels[$i]}..."
|
||||
if ! git -C "$WORK_CLONE" cherry-pick -x -m 1 "${shas[$i]}" 2>/dev/null &&
|
||||
! { git -C "$WORK_CLONE" cherry-pick --abort 2>/dev/null; git -C "$WORK_CLONE" cherry-pick -x "${shas[$i]}"; }; then
|
||||
print_error "Cherry-pick conflict on ${labels[$i]}"
|
||||
echo "Resolve it in $WORK_CLONE (git cherry-pick --continue), push, and re-run."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
git -C "$WORK_CLONE" push --quiet origin "$branch"
|
||||
print_success "Picked ${#shas[@]} change(s) onto $branch"
|
||||
|
||||
# The staging PR can exist now that the branch has commits of its own.
|
||||
if command -v gh >/dev/null && ! gh pr view "$branch" --repo "$UPSTREAM_REPO" >/dev/null 2>&1; then
|
||||
gh pr create --repo "$UPSTREAM_REPO" --draft --base "$DEV_BRANCH" --head "$branch" \
|
||||
--title "Release v$version" \
|
||||
--body "Staging ground for the v$version release notes. Edit this body — it becomes the GitHub release text." 2>/dev/null &&
|
||||
print_success "Opened the release-notes staging PR" || true
|
||||
fi
|
||||
print_info "Next: omarchy-release rc"
|
||||
}
|
||||
|
||||
cmd_rc() {
|
||||
local iso_mode="$1" wait="$2"
|
||||
local branch version
|
||||
branch=$(open_train_branch) || true
|
||||
if [[ -z "$branch" ]]; then
|
||||
print_error "No open release train — run: omarchy-release start"
|
||||
exit 1
|
||||
fi
|
||||
version=$(branch_to_version "$branch")
|
||||
print_header "Cut RC for train $version"
|
||||
|
||||
local head
|
||||
head=$(branch_head "$branch")
|
||||
print_info "Branch $branch head: ${head:0:12}"
|
||||
|
||||
# Idempotence: if the current rc pin already matches the branch head and is
|
||||
# published, there is nothing to cut.
|
||||
local pin
|
||||
pin=$(rc_branch_pin)
|
||||
if [[ -n "$pin" ]]; then
|
||||
local pin_ver="${pin%% *}" pin_commit="${pin##* }"
|
||||
if [[ "$pin_commit" == "$head" && "$pin_ver" =~ ^${version//./\\.}rc[0-9]+$ ]]; then
|
||||
local pub
|
||||
pub=$(published_version rc 2>/dev/null) || pub=""
|
||||
if [[ "${pub%-*}" == "$pin_ver" ]]; then
|
||||
print_success "$pin_ver is already cut from this head and published to rc"
|
||||
maybe_iso "$version" rc "$iso_mode"
|
||||
return 0
|
||||
fi
|
||||
print_info "$pin_ver is pinned from this head but not published yet — re-triggering the build"
|
||||
trigger_rc_build || true
|
||||
[[ "$wait" == true ]] && wait_for_published rc "$pin_ver"
|
||||
maybe_iso "$version" rc "$iso_mode"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
confirm "Pin omarchy + omarchy-settings to $branch@${head:0:12} as the next ${version}rcN and publish to rc?" || exit 1
|
||||
|
||||
cut_pins rc --base "$version" --ref "$branch"
|
||||
|
||||
local new_pin new_ver
|
||||
new_pin=$(rc_branch_pin)
|
||||
new_ver="${new_pin%% *}"
|
||||
print_success "Pinned $new_ver (rc branch pushed)"
|
||||
|
||||
trigger_rc_build || true
|
||||
if [[ "$wait" == true ]]; then
|
||||
wait_for_published rc "$new_ver" || return 1
|
||||
fi
|
||||
maybe_iso "$version" rc "$iso_mode"
|
||||
echo ""
|
||||
print_info "Test the candidate, then: omarchy-release ship (or pick + rc again)"
|
||||
}
|
||||
|
||||
cmd_ship() {
|
||||
local iso_mode="$1" wait="$2"
|
||||
local branch version
|
||||
branch=$(open_train_branch) || true
|
||||
if [[ -z "$branch" ]]; then
|
||||
print_error "No open release train — nothing to ship"
|
||||
exit 1
|
||||
fi
|
||||
version=$(branch_to_version "$branch")
|
||||
print_header "Ship $version"
|
||||
|
||||
local head pin pin_ver pin_commit
|
||||
head=$(branch_head "$branch")
|
||||
pin=$(rc_branch_pin)
|
||||
pin_ver="${pin%% *}"
|
||||
pin_commit="${pin##* }"
|
||||
|
||||
# The ship guard: only a commit an RC was actually cut from may ship.
|
||||
if [[ "$pin_ver" != "$version" ]]; then
|
||||
if [[ -z "$pin" || ! "$pin_ver" =~ ^${version//./\\.}rc[0-9]+$ ]]; then
|
||||
print_error "No RC has been cut for $version — run: omarchy-release rc"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$pin_commit" != "$head" ]]; then
|
||||
local behind
|
||||
behind=$(git -C "$WORK_CLONE" rev-list --count "$pin_commit..origin/$branch" 2>/dev/null || echo "?")
|
||||
print_error "$branch has moved since $pin_ver was cut ($behind commit(s) untested)"
|
||||
echo "Cut another candidate first: omarchy-release rc"
|
||||
[[ "$ASSUME_YES" == true ]] && exit 1
|
||||
confirm "Ship anyway from ${head:0:12} WITHOUT an RC of it? (not recommended)" || exit 1
|
||||
fi
|
||||
local pub
|
||||
pub=$(published_version rc 2>/dev/null) || pub=""
|
||||
if [[ "${pub%-*}" != "$pin_ver" ]]; then
|
||||
print_error "$pin_ver is pinned but rc serves '${pub:-nothing}' — the candidate build hasn't published"
|
||||
echo "Wait for it (or re-run: omarchy-release rc), then ship."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_info "This will, in order (steps already done are skipped):"
|
||||
echo " 1. Pin the final $version from $branch@${head:0:12} and publish it to rc"
|
||||
echo " 2. Promote the rc channel to stable (packages + signatures + db)"
|
||||
echo " 3. Tag v$version on $UPSTREAM_REPO"
|
||||
echo " 4. Merge the final pins to master (edge overlap + record)"
|
||||
echo " 5. Create the GitHub release from the staging PR body"
|
||||
echo " 6. Build + upload the final ISO (${iso_mode})"
|
||||
echo " 7. Point the website at the new ISO"
|
||||
echo ""
|
||||
confirm "Ship $version?" || exit 1
|
||||
|
||||
# 1. Final pins into rc
|
||||
local rc_pub stable_pub
|
||||
rc_pub=$(published_version rc 2>/dev/null) || rc_pub=""
|
||||
if [[ "${rc_pub%-*}" == "$version" ]]; then
|
||||
print_success "1/7 Final $version already published to rc"
|
||||
else
|
||||
if [[ "$pin_ver" != "$version" ]]; then
|
||||
print_info "1/7 Pinning final $version..."
|
||||
cut_pins "v$version" --commit "$head"
|
||||
else
|
||||
print_info "1/7 Final $version pinned — re-triggering build"
|
||||
fi
|
||||
trigger_rc_build || true
|
||||
wait_for_published rc "$version" || exit 1
|
||||
fi
|
||||
|
||||
# 2. Promote rc -> stable
|
||||
stable_pub=$(published_version stable 2>/dev/null) || stable_pub=""
|
||||
if [[ "${stable_pub%-*}" == "$version" ]]; then
|
||||
print_success "2/7 Stable already serves $version"
|
||||
else
|
||||
host_advance rc stable || exit 1
|
||||
stable_pub=$(published_version stable 2>/dev/null) || stable_pub=""
|
||||
if [[ "${stable_pub%-*}" != "$version" ]]; then
|
||||
print_error "Promotion ran but stable serves '${stable_pub:-nothing}' — investigate before continuing"
|
||||
exit 1
|
||||
fi
|
||||
print_success "2/7 Promoted to stable: omarchy $stable_pub"
|
||||
fi
|
||||
|
||||
# 3. Tag
|
||||
if tag_exists "v$version"; then
|
||||
print_success "3/7 Tag v$version already exists"
|
||||
else
|
||||
ensure_mirror_clone
|
||||
git -C "$MIRROR_CLONE" push --quiet origin "$head:refs/tags/v$version"
|
||||
print_success "3/7 Tagged v$version at ${head:0:12}"
|
||||
fi
|
||||
|
||||
# 4. Final pins onto master (keeps edge overlap publishing and the repo record)
|
||||
local master_ver
|
||||
master_ver=$(git -C "$BUILD_ROOT" show origin/master:pkgbuilds/omarchy/PKGBUILD 2>/dev/null | grep -E '^pkgver=' | head -1 | cut -d= -f2)
|
||||
if [[ "$master_ver" == "$version" ]]; then
|
||||
print_success "4/7 master already carries the $version pins"
|
||||
else
|
||||
local pin_sha
|
||||
pin_sha=$(git -C "$BUILD_ROOT" rev-parse origin/rc 2>/dev/null || true)
|
||||
print_info "4/7 Bringing the final pin commit to master..."
|
||||
local sync_wt="$BUILD_ROOT/.worktrees/master-sync"
|
||||
git -C "$BUILD_ROOT" fetch --quiet origin
|
||||
if [[ ! -d "$sync_wt" ]]; then
|
||||
git -C "$BUILD_ROOT" worktree add --quiet -B release-master-sync "$sync_wt" origin/master
|
||||
else
|
||||
git -C "$sync_wt" checkout --quiet -B release-master-sync origin/master
|
||||
fi
|
||||
if (cd "$sync_wt" && git cherry-pick "$pin_sha" && git push --quiet origin HEAD:master); then
|
||||
print_success "4/7 Final pins merged to master"
|
||||
else
|
||||
(cd "$sync_wt" && git cherry-pick --abort 2>/dev/null || true)
|
||||
print_warning "4/7 Could not fast-path the pins to master — cherry-pick $pin_sha onto master manually"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 5. GitHub release from the staging PR body
|
||||
if gh release view "v$version" --repo "$UPSTREAM_REPO" >/dev/null 2>&1; then
|
||||
print_success "5/7 GitHub release v$version already exists"
|
||||
else
|
||||
local notes
|
||||
notes=$(gh pr view "$branch" --repo "$UPSTREAM_REPO" --json body --jq '.body' 2>/dev/null) || notes=""
|
||||
if [[ -z "$notes" || "$notes" == "null" ]]; then
|
||||
notes="Omarchy $version"
|
||||
print_warning "No staging PR body found — using a bare title; edit the release afterwards"
|
||||
fi
|
||||
gh release create "v$version" --repo "$UPSTREAM_REPO" --title "v$version" --notes "$notes"
|
||||
print_success "5/7 GitHub release v$version created"
|
||||
fi
|
||||
|
||||
# 6. ISO
|
||||
maybe_iso "$version" final "$iso_mode" || true
|
||||
|
||||
# 7. Website (only meaningful once the final ISO exists)
|
||||
if [[ "$iso_mode" != "no" ]]; then
|
||||
update_website "$version" || true
|
||||
else
|
||||
print_info "7/7 --no-iso: leaving the website untouched"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_success "Shipped $version — rc and stable are in parity for the next patch train"
|
||||
print_info "Close the loop: merge or close the staging PR for $branch on $UPSTREAM_REPO"
|
||||
}
|
||||
|
||||
# --- status / shepherd -------------------------------------------------------
|
||||
|
||||
gather_status() {
|
||||
EDGE_VER=$(published_version edge 2>/dev/null) || EDGE_VER="<unreachable>"
|
||||
RC_VER=$(published_version rc 2>/dev/null) || RC_VER="<unreachable>"
|
||||
STABLE_VER=$(published_version stable 2>/dev/null) || STABLE_VER="<unreachable>"
|
||||
TRAIN=$(open_train_branch 2>/dev/null) || TRAIN=""
|
||||
TRAIN_VER=""
|
||||
TRAIN_HEAD=""
|
||||
PIN=""
|
||||
if [[ -n "$TRAIN" ]]; then
|
||||
TRAIN_VER=$(branch_to_version "$TRAIN")
|
||||
TRAIN_HEAD=$(branch_head "$TRAIN")
|
||||
PIN=$(rc_branch_pin)
|
||||
fi
|
||||
}
|
||||
|
||||
next_step() { # prints "<command>|<description>"
|
||||
if [[ -z "$TRAIN" ]]; then
|
||||
echo "start|No open train — start the next release"
|
||||
return
|
||||
fi
|
||||
local pin_ver="${PIN%% *}" pin_commit="${PIN##* }"
|
||||
if [[ -z "$PIN" || ! "$pin_ver" =~ ^${TRAIN_VER//./\\.}(rc[0-9]+)?$ ]]; then
|
||||
echo "rc|Cut the first candidate for $TRAIN_VER"
|
||||
elif [[ "$pin_ver" == "$TRAIN_VER" ]]; then
|
||||
echo "ship|Final $TRAIN_VER is pinned — finish shipping (re-runs are safe)"
|
||||
elif [[ "$pin_commit" != "$TRAIN_HEAD" ]]; then
|
||||
echo "rc|$TRAIN has commits newer than $pin_ver — cut the next candidate"
|
||||
elif [[ "${RC_VER%-*}" != "$pin_ver" ]]; then
|
||||
echo "rc|$pin_ver is pinned but not published — re-run rc to re-trigger/wait"
|
||||
else
|
||||
echo "ship|$pin_ver is published to rc — test it, then ship"
|
||||
fi
|
||||
}
|
||||
|
||||
print_status() {
|
||||
print_header "Omarchy release status"
|
||||
echo " Channels (omarchy):"
|
||||
echo " edge: $EDGE_VER"
|
||||
echo " rc: $RC_VER"
|
||||
echo " stable: $STABLE_VER"
|
||||
echo ""
|
||||
if [[ -z "$TRAIN" ]]; then
|
||||
echo " No open release train."
|
||||
else
|
||||
echo " Open train: $TRAIN_VER (branch $TRAIN @ ${TRAIN_HEAD:0:12})"
|
||||
if [[ -n "$PIN" ]]; then
|
||||
echo " Current pin: ${PIN%% *} from commit ${PIN##* }"
|
||||
else
|
||||
echo " Current pin: <none>"
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
local step
|
||||
step=$(next_step)
|
||||
echo " Next: omarchy-release ${step%%|*} — ${step#*|}"
|
||||
}
|
||||
|
||||
cmd_shepherd() {
|
||||
gather_status
|
||||
print_status
|
||||
local step cmd
|
||||
step=$(next_step)
|
||||
cmd="${step%%|*}"
|
||||
echo ""
|
||||
if confirm "Run 'omarchy-release $cmd' now?"; then
|
||||
case "$cmd" in
|
||||
start) cmd_start ;;
|
||||
pick) cmd_pick ;;
|
||||
rc) cmd_rc "$ISO_MODE" "$WAIT" ;;
|
||||
ship) cmd_ship "$ISO_MODE" "$WAIT" ;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
# --- doctor ------------------------------------------------------------------
|
||||
|
||||
cmd_doctor() {
|
||||
print_header "omarchy-release doctor"
|
||||
local failures=0
|
||||
check() {
|
||||
local label="$1"; shift
|
||||
if "$@" >/dev/null 2>&1; then
|
||||
print_success "$label"
|
||||
else
|
||||
print_error "$label"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
}
|
||||
check "git available" command -v git
|
||||
check "gh available" command -v gh
|
||||
check "gh authenticated" gh auth status
|
||||
check "gh can see $UPSTREAM_REPO" gh repo view "$UPSTREAM_REPO"
|
||||
check "gh can see $SITE_REPO" gh repo view "$SITE_REPO"
|
||||
check "vercmp available (pacman)" command -v vercmp
|
||||
check "makepkg available (checksums)" command -v makepkg
|
||||
check "curl available" command -v curl
|
||||
check "upstream reachable ($UPSTREAM_URL)" git ls-remote "$UPSTREAM_URL" HEAD
|
||||
local ch
|
||||
for ch in edge stable; do
|
||||
if published_version "$ch" >/dev/null 2>&1; then
|
||||
print_success "$ch channel db readable"
|
||||
else
|
||||
print_error "$ch channel db readable"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
done
|
||||
if published_version rc >/dev/null 2>&1; then
|
||||
print_success "rc channel db readable"
|
||||
else
|
||||
print_warning "rc channel db not readable — bootstrap it first: bin/repo bootstrap-rc (on the build host)"
|
||||
fi
|
||||
local host
|
||||
if host=$(repo_host); then
|
||||
check "build host ssh ($host)" ssh -o ConnectTimeout=10 "$host" true
|
||||
else
|
||||
print_warning "no build host configured (OMARCHY_REPO_HOST / .repo-host) — builds trigger on the 6h timer only"
|
||||
fi
|
||||
if command -v docker >/dev/null && docker info >/dev/null 2>&1; then
|
||||
print_success "docker available (ISO builds possible here)"
|
||||
else
|
||||
print_warning "docker unavailable — ISO builds will print instructions instead"
|
||||
fi
|
||||
echo ""
|
||||
if ((failures == 0)); then
|
||||
print_success "Ready to release"
|
||||
else
|
||||
print_error "$failures check(s) failed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# --- self-test ---------------------------------------------------------------
|
||||
|
||||
cmd_self_test() {
|
||||
local failures=0
|
||||
expect() { # expect <label> <got> <want>
|
||||
if [[ "$2" == "$3" ]]; then
|
||||
echo " ok: $1"
|
||||
else
|
||||
echo " FAIL: $1 — got '$2', want '$3'"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
}
|
||||
print_header "omarchy-release self-test"
|
||||
expect "version_to_branch 4.0.2" "$(version_to_branch 4.0.2)" "v4-0-2"
|
||||
expect "version_to_branch v4.1.0" "$(version_to_branch v4.1.0)" "v4-1-0"
|
||||
expect "version_to_branch garbage rejects" "$(version_to_branch 4.0 2>/dev/null || echo reject)" "reject"
|
||||
expect "branch_to_version v4-0-2" "$(branch_to_version v4-0-2)" "4.0.2"
|
||||
expect "branch_to_version v10-2-33" "$(branch_to_version v10-2-33)" "10.2.33"
|
||||
expect "branch_to_version quattro rejects" "$(branch_to_version quattro 2>/dev/null || echo reject)" "reject"
|
||||
expect "version_is_patch 4.0.2" "$(version_is_patch 4.0.2 && echo yes || echo no)" "yes"
|
||||
expect "version_is_patch 4.1.0" "$(version_is_patch 4.1.0 && echo yes || echo no)" "no"
|
||||
expect "version_is_patch 5.0.0" "$(version_is_patch 5.0.0 && echo yes || echo no)" "no"
|
||||
expect "previous_patch_tag 4.0.2" "$(previous_patch_tag 4.0.2)" "v4.0.1"
|
||||
expect "previous_patch_tag 4.0.10" "$(previous_patch_tag 4.0.10)" "v4.0.9"
|
||||
echo ""
|
||||
if ((failures == 0)); then
|
||||
print_success "Self-test passed"
|
||||
else
|
||||
print_error "$failures self-test failure(s)"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# --- dispatch ----------------------------------------------------------------
|
||||
|
||||
COMMAND=""
|
||||
ISO_MODE="ask"
|
||||
WAIT=true
|
||||
ARGS=()
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--yes) ASSUME_YES=true; shift ;;
|
||||
--host) REPO_HOST_OVERRIDE="$2"; shift 2 ;;
|
||||
--iso) ISO_MODE="yes"; shift ;;
|
||||
--no-iso) ISO_MODE="no"; shift ;;
|
||||
--no-wait) WAIT=false; shift ;;
|
||||
-h | --help) show_usage; exit 0 ;;
|
||||
start | pick | rc | ship | status | doctor | self-test)
|
||||
COMMAND="$1"; shift ;;
|
||||
*)
|
||||
ARGS+=("$1"); shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
case "$COMMAND" in
|
||||
start) cmd_start "${ARGS[@]}" ;;
|
||||
pick) cmd_pick "${ARGS[@]}" ;;
|
||||
rc) cmd_rc "$ISO_MODE" "$WAIT" ;;
|
||||
ship) cmd_ship "$ISO_MODE" "$WAIT" ;;
|
||||
status) gather_status; print_status ;;
|
||||
doctor) cmd_doctor ;;
|
||||
self-test) cmd_self_test ;;
|
||||
"") cmd_shepherd ;;
|
||||
*) print_error "Unknown command: $COMMAND"; show_usage; exit 1 ;;
|
||||
esac
|
||||
@@ -6,8 +6,11 @@ Wants=network-online.target
|
||||
[Service]
|
||||
Type=oneshot
|
||||
# The rc channel builds from the rc branch worktree, but publishes into the
|
||||
# primary checkout's channel tree via OMARCHY_REPO_ROOT.
|
||||
ExecStartPre=/usr/bin/git -C /root/omarchy-pkgs-rc pull --ff-only
|
||||
# primary checkout's channel tree via OMARCHY_REPO_ROOT. The rc branch is
|
||||
# rebuilt (master + pin commits) each release train and force-pushed, so this
|
||||
# worktree follows with a hard reset rather than a fast-forward pull.
|
||||
ExecStartPre=/usr/bin/git -C /root/omarchy-pkgs-rc fetch origin rc
|
||||
ExecStartPre=/usr/bin/git -C /root/omarchy-pkgs-rc reset --hard origin/rc
|
||||
ExecStart=/bin/bash -c 'source /root/.omarchy/build-credentials && /root/omarchy-pkgs-rc/bin/auto-release rc'
|
||||
Environment=OMARCHY_STATE_DIR=/root/.state
|
||||
Environment=OMARCHY_REPO_ROOT=/root/omarchy-pkgs/pkgs.omarchy.org
|
||||
|
||||
Reference in New Issue
Block a user