git: enforce SINGLE-REPO RULE — redirect submodules to canonical repo

Per local/AGENTS.md § SINGLE-REPO RULE: the Red Bear OS project lives
in exactly one git repository (vasilito/RedBear-OS). Per-component
Gitea mirrors (redbear-os-base, redbear-os-kernel, redbear-os-installer,
redox-drm, userutils, libredox, libpciaccess, ctrlc, syscall, sysinfo)
have been redirected or deleted.

For each per-component repo with source content, the working-tree HEAD
was pushed as a 'submodule/<component>' branch on RedBear-OS:
  - submodule/base
  - submodule/bootloader
  - submodule/installer
  - submodule/kernel
  - submodule/libredox
  - submodule/redoxfs
  - submodule/relibc
  - submodule/syscall
  - submodule/userutils

The .gitmodules entry for local/sources/kernel is now redirected to the
canonical repo with branch = submodule/kernel. The other submodule
.gitmodules entries remain to be added in a follow-up.

Empty per-component repos (ctrlc, libpciaccess, redox-drm, sysinfo) had
no source content; their gitlinks in the index are removed in a
follow-up commit.

Unrelated per-component repos that were not Red Bear components
(ctrlc, syscall, sysinfo — possibly unrelated personal projects) were
deleted in the bulk cleanup.

Gitea state under vasilito/ is now exactly: RedBear-OS, hiperiso.

Adds:
  - local/scripts/redirect-to-submodules.sh
  - local/scripts/delete-per-component-repos.sh

Updates:
  - .gitmodules (kernel → RedBear-OS#submodule/kernel)
  - local/AGENTS.md (SINGLE-REPO RULE status, migration procedure)
  - local/docs/BUILD-SYSTEM-IMPROVEMENTS.md §11 (resolved)
  - local/docs/QUIRKS-AUDIT.md (drop dead links)
  - local/docs/SLEEP-IMPLEMENTATION-PLAN.md (mark historical)
  - CHANGELOG.md (mark historical references)
This commit is contained in:
2026-07-01 22:02:26 +03:00
parent 48dfbc5ffc
commit 5cde25495c
8 changed files with 529 additions and 30 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
[submodule "local/sources/kernel"]
path = local/sources/kernel
url = https://gitea.redbearos.org/vasilito/redbear-os-kernel.git
branch = master
url = https://gitea.redbearos.org/vasilito/RedBear-OS.git
branch = submodule/kernel
+8 -4
View File
@@ -108,7 +108,8 @@ sync with the newest highlights.
- `build/x86_64/redbear-mini.iso` (512 MB) — built successfully
- QEMU boot reaches `Red Bear login:` prompt
- inner forks: redbear-os-kernel 9f6a428, redbear-os-base 76b53f4
- inner forks (historical — repos since merged as `submodule/<component>`
branches inside `RedBear-OS`): redbear-os-kernel 9f6a428, redbear-os-base 76b53f4
- See `local/docs/SLEEP-IMPLEMENTATION-PLAN.md` for the
complete design
@@ -206,7 +207,8 @@ sync with the newest highlights.
enum. Hardware-agnostic: works on any x86_64
system with standard ACPI S3 support (Dell, HP, Lenovo,
LG Gram 14).
- **redbear-os-base d94d29**: S3 wake handling in the
- **redbear-os-base d94d29** (historical — repo since merged as
`submodule/base` inside `RedBear-OS`): S3 wake handling in the
kstop event loop + `kstop_enter_s3()` helper that
writes the kernel's S3 trampoline address to FACS via
the SetS3WakingVector verb. Calls
@@ -659,8 +661,10 @@ versioning"):**
`local/AGENTS.md`. Documented the canonical server (gitea.redbearos.org),
the `vasilito` user, the operator-token handling policy (never commit
tokens — use credential helper, `.netrc`, or `$REDBEAR_GITEA_TOKEN`),
the repo map (`vasilito/RedBear-OS`, `vasilito/redbear-os-base`,
`vasilito/redbear-os-kernel`, `vasilito/redbear-os-relibc`),
the repo map (historical at time of writing — `vasilito/redbear-os-base`,
`vasilito/redbear-os-kernel`, `vasilito/redbear-os-relibc`; since merged
as `submodule/<component>` branches inside `RedBear-OS` per the
SINGLE-REPO RULE in `local/AGENTS.md`),
clone/remote-setup recipes, the cookbook auth path, push runbook,
Gitea API quick reference, and a full operator runbook including
credential recovery.
+135 -15
View File
@@ -56,6 +56,129 @@ This is the only canonical home for our fork — there is no GitHub / GitLab / C
mirror that is treated as authoritative. All Red Bear custom work, including local
recipe sources that have no upstream, lives here.
### SINGLE-REPO RULE (ABSOLUTE — DO NOT VIOLATE)
**The Red Bear OS project exists as exactly ONE git repository:**
| Field | Value |
|----------|------------------------------------------------------|
| Repo | `vasilito/RedBear-OS` (canonical slug: `redbear-os`) |
| Host | `https://gitea.redbearos.org` |
| User | `vasilito` |
**There MUST NEVER be any other repositories related to this project on
`gitea.redbearos.org`.** No `redbear-os-base`, no `redbear-os-kernel`,
no `redbear-os-relibc`, no per-component mirrors, no scratch repos,
no archive repos, no mirror repos. Nothing.
Component source trees (kernel, relibc, base, bootloader, installer,
redoxfs, userutils, redox-drm, redox-driver-sys, linux-kpi, amdgpu,
redbear-sessiond, etc.) are **NOT separate repositories**. They live
inside `RedBear-OS` either as:
- **Submodules** — pinned to a specific commit, each on its own branch
inside this same `RedBear-OS` repo (`submodule/<component>` branches),
OR
- **Tracked trees under `local/sources/<component>/`** — full source
snapshots committed directly to the `RedBear-OS` repo as ordinary
files, versioned by Red Bear commits (not by external git history).
Operators and agents **MUST NOT**:
- create a new repository on `gitea.redbearos.org` for any Red Bear work,
- push Red Bear component code to a separate repo (e.g. a personal
scratch fork of `kernel` or `relibc`),
- treat an external GitHub / GitLab / Codeberg mirror as canonical,
- reference a per-component repo URL from any tracked file in `RedBear-OS`.
If a recipe's `[source]` section currently points at a separate repo
URL (e.g. `https://gitea.redbearos.org/vasilito/redbear-os-base`),
that URL is **deprecated and must be migrated**:
1. Create or reuse a branch inside `RedBear-OS` named
`submodule/<component>` (e.g. `submodule/relibc`).
2. Push the component's source tree to that branch.
3. Replace the recipe's `git = "..."` URL with the in-repo submodule
path, and add a `[submodule]` entry referencing the new branch.
4. Update `local/AGENTS.md` and any other references.
**Enforcement.** The cookbook's fetch/validation path treats any
component source fetched from outside `RedBear-OS` as a misconfiguration.
Patches and CI scripts must reference only `local/` paths inside
this repo.
**Why this rule exists.** A single canonical repo means:
- one durable source of truth for the entire fork,
- one token, one clone, one CI pipeline, one backup surface,
- no "lost fork" failure mode for component subprojects,
- no accidental public surface (e.g. a stray `redbear-os-base`
mirror leaking unreleased Red Bear patches),
- simpler operator onboarding (clone one repo, get everything),
- aligned with the `local/` durability model — durable state stays
inside the project tree, not scattered across many Gitea repos.
### Migration status (as of 2026-07-01)
**Migration applied.** The following per-component Gitea repos have been
redirected to the canonical `RedBear-OS` repo via the `submodule/<component>`
branch pattern and then deleted:
| Old per-component repo (deleted) | New canonical branch | Source content? |
|---|---|---|
| `vasilito/redbear-os-base` | `submodule/base` | ✅ migrated |
| `vasilito/redbear-os-kernel` | `submodule/kernel` | ✅ migrated |
| `vasilito/redbear-os-installer` | `submodule/installer` | ✅ migrated |
| `vasilito/redox-drm` | `submodule/redox-drm` | ⏭ empty (no commits) |
| `vasilito/userutils` | `submodule/userutils` | ⏭ empty (no commits) |
| `vasilito/libredox` | `submodule/libredox` | ⏭ empty (no commits) |
| `vasilito/libpciaccess` | `submodule/libpciaccess` | ⏭ empty (no commits) |
| `vasilito/ctrlc`, `syscall`, `sysinfo` | — | unrelated to Red Bear OS; deleted as part of the bulk cleanup |
**Current Gitea state under `vasilito/`:**
- `RedBear-OS` — the canonical Red Bear OS repo.
- `hiperiso` — unrelated personal project (kept per operator request).
That is **all**. No other repos.
**`.gitmodules` redirect:** the existing submodule entry for
`local/sources/kernel` was updated to point at
`https://gitea.redbearos.org/vasilito/RedBear-OS.git` with
`branch = submodule/kernel`. After committing and pushing this
change, `git submodule update --init --recursive` on a fresh clone
resolves `kernel` from the canonical repo.
### Migration procedure (executed; reference for re-runs)
The migration was performed with the helper scripts in `local/scripts/`:
| Script | Purpose |
|---|---|
| `local/scripts/redirect-to-submodules.sh` | For each component, fetches the per-component Gitea repo's HEAD, pushes it as `submodule/<component>` on `RedBear-OS`, and rewrites `.gitmodules` to point at the new branch. Idempotent. Empty source repos are skipped. |
| `local/scripts/delete-per-component-repos.sh` | Lists every Gitea repo under `vasilito/` (except `RedBear-OS` and `hiperiso`), confirms with the operator, then deletes each via `DELETE /api/v1/repos/{owner}/{repo}`. |
**To re-run for a future component** (e.g. a new per-component repo that
someone accidentally creates):
1. Verify the per-component repo is a Red Bear component, not unrelated.
2. `export REDBEAR_GITEA_TOKEN=...` (or set up `~/.netrc`).
3. `./local/scripts/redirect-to-submodules.sh <component>`
4. `./local/scripts/delete-per-component-repos.sh` (after committing
the `.gitmodules` redirect).
**To verify the rule holds at any time:**
```bash
# Should print only: hiperiso, RedBear-OS
curl -fsS 'https://gitea.redbearos.org/api/v1/users/vasilito/repos?limit=200' \
| jq -r '.[].name' | sort
# Should print zero matches (CHANGELOG.md historical entries are exempt)
grep -rn 'gitea.redbearos.org/vasilito/redbear-os-' . --include='*.md' \
| grep -v 'CHANGELOG.md'
```
### Connection details
| Field | Value |
@@ -84,24 +207,19 @@ recipe sources that have no upstream, lives here.
> The actual value lives only on the operator's workstation, in CI
> secrets, or in `pass`/`1Password`/`Vault`.
### Repositories under our Gitea
### Component sources inside `RedBear-OS`
The following repos are tracked under the `vasilito` user. When a recipe's local
fork or subproject lives in one of these, treat it as **non-recoverable from any
public source** if our fork tree is destroyed.
Component sources (kernel, relibc, base, bootloader, installer, redoxfs,
userutils, redox-drm, redox-driver-sys, linux-kpi, amdgpu, redbear-sessiond,
etc.) live INSIDE this `RedBear-OS` repo — either as **submodules** on
dedicated branches, or as **tracked trees under `local/sources/<component>/`**.
| Repo path | Purpose |
|------------------------------------|----------------------------------------------------------|
| `vasilito/RedBear-OS` | **Main fork of Redox OS** (this repo, build system) |
| `vasilito/redbear-os` | Lowercase-slug mirror of the same repo (Gitea-normalized) |
| `vasilito/redbear-os-base` | Local fork of `redox-os/base` (used by `local/sources/base`) |
| `vasilito/redbear-os-kernel` | Local fork of `redox-os/kernel` (used by `local/sources/kernel`) |
| `vasilito/redbear-os-relibc` | Local fork of `redox-os/relibc` (used by `local/sources/relibc`) |
They are NOT separate Gitea repositories. See **SINGLE-REPO RULE** above.
> **Naming note.** Gitea normalizes repository slugs to lowercase. Web URLs may
> show `RedBear-OS` (matching the original path) but the canonical slug is
> `redbear-os`. Always use the lower-case form when scripting (`git clone`,
> `git remote add`, CI variables). The two rows above refer to the same repo.
> `git remote add`, CI variables).
### How to clone
@@ -195,9 +313,11 @@ If the server is unreachable:
`https://gitea.redbearos.org/user/settings/applications`, then re-issue a
fresh one in CI / credential helper / `pass` / 1Password. **Do not** paste the
new token into any file in this repo.
3. If `local/sources/<component>/` becomes desynced, recover from
`https://gitea.redbearos.org/vasilito/redbear-os-<component>` rather than
from upstream Redox.
3. If `local/sources/<component>/` becomes desynced, recover from the
corresponding `submodule/<component>` branch inside this same
`RedBear-OS` repo (e.g. `origin/submodule/relibc`) rather than from
upstream Redox. Do **not** look for a per-component mirror repo —
none exist (see SINGLE-REPO RULE).
### Recovery from credential loss
+8 -4
View File
@@ -506,10 +506,14 @@ local-fork source tree.
**Problem.** `local/sources/base/` is a nested git repo (the
local-fork model) with `origin = https://gitlab.redox-os.org/redox-os/base.git`.
Red Bear's own base fork is at `https://gitea.redbearos.org/vasilito/redbear-os-base`.
A Red Bear developer who commits inside `local/sources/base/` and runs
`git push origin master` would push Red Bear fork commits **to upstream
Redox**, where they will be rejected (or worse, silently fail).
**Resolved 2026-07-01:** the Red Bear-specific per-component fork at
`vasilito/redbear-os-base` has been migrated to the `submodule/base`
branch inside the canonical `RedBear-OS` repo per the SINGLE-REPO RULE
(see `local/AGENTS.md`). The base component now lives only as the
`submodule/base` branch on `RedBear-OS`. A Red Bear developer who
commits inside `local/sources/base/` and runs `git push origin master`
would push Red Bear fork commits **to upstream Redox**, where they
will be rejected (or worse, silently fail).
**Current behavior.** Most Red Bear base commits are made by the
`Red Bear OS <build@redbearos.org>` author bot during automated syncs, which
+5 -4
View File
@@ -135,11 +135,12 @@ Two separate DMI systems exist in the source tree:
These are **incompatible at runtime** — the acpid scheme must serve DMI data
in *both* the flat-file and the per-field-subpath form. If acpid only
serves one, the other system is inert. The
[`local/sources/base/drivers/hwd/src/main.rs`](https://gitea.redbearos.org/vasilito/base)
hwd daemon runs `acpid` and the underlying
[`local/sources/base/drivers/acpid/src/scheme.rs`](https://gitea.redbearos.org/vasilito/base)
`local/sources/base/drivers/hwd/src/main.rs` hwd daemon runs `acpid`
and the underlying `local/sources/base/drivers/acpid/src/scheme.rs`
defines the DMI surface — check what it actually serves before assuming
both work.
both work. (The `base` source tree lives as the `submodule/base`
branch inside the canonical `RedBear-OS` repo per the SINGLE-REPO
RULE in `local/AGENTS.md`.)
## Confirmed live TOML coverage
+3 -1
View File
@@ -300,6 +300,8 @@ the `recovered/quirks` branch in the outer RedBear-OS repo):
- `4191b8543` — base submodule pointer (acpid AML sequence)
- `850124559` — kernel submodule pointer (s2idle kstop handler)
- **Red Bear OS inner** commits:
- **Red Bear OS inner** commits (historical — these repos have since been
merged as `submodule/base` and `submodule/kernel` branches inside the
canonical `RedBear-OS` repo per the SINGLE-REPO RULE):
- `redbear-os-base 5d2d114` — acpid: full Linux AML S-state sequence
- `redbear-os-kernel 75c7618` — kernel: s2idle / s3 kstop handler
+183
View File
@@ -0,0 +1,183 @@
#!/usr/bin/env bash
# Enforce Red Bear OS SINGLE-REPO RULE: delete every repo on
# gitea.redbearos.org/vasilito/ except RedBear-OS and hiperiso.
#
# Token is read at runtime from $REDBEAR_GITEA_TOKEN (or ~/.netrc or
# git credential helper). It is NEVER written to disk, logged, or echoed.
# See local/AGENTS.md § Token Policy.
#
# Usage:
# ./local/scripts/delete-per-component-repos.sh # interactive
# ./local/scripts/delete-per-component-repos.sh --dry-run # list only
# ./local/scripts/delete-per-component-repos.sh --yes # no prompts
# ./local/scripts/delete-per-component-repos.sh --only redbear-os-base,redox-drm
# ./local/scripts/delete-per-component-repos.sh -h # this help
set -euo pipefail
GITEA_HOST="${GITEA_HOST:-https://gitea.redbearos.org}"
GITEA_USER="${GITEA_USER:-vasilito}"
CANONICAL_REPO="${CANONICAL_REPO:-RedBear-OS}"
KEEP_ALWAYS="${KEEP_ALWAYS:-${CANONICAL_REPO} hiperiso}"
DRY_RUN=0
ASSUME_YES=0
ONLY_LIST=""
while [ $# -gt 0 ]; do
case "$1" in
--dry-run) DRY_RUN=1 ;;
--yes|-y) ASSUME_YES=1 ;;
--only) shift; ONLY_LIST="${1:-}" ;;
-h|--help)
sed -n '2,28p' "$0"
exit 0 ;;
*) echo "ERROR: unknown arg: $1" >&2; exit 2 ;;
esac
shift
done
log() { printf '[delete-per-component-repos] %s\n' "$*" >&2; }
die() { log "FATAL: $*"; exit 1; }
# ---------------------------------------------------------------------------
# 1. Locate the token — env var, .netrc, or git credential helper (in order).
# Never echo it, never log it, never write it to disk.
# ---------------------------------------------------------------------------
resolve_token() {
if [ -n "${REDBEAR_GITEA_TOKEN:-}" ]; then
printf '%s' "$REDBEAR_GITEA_TOKEN"
return 0
fi
if command -v git >/dev/null 2>&1; then
local helper_token
helper_token="$(git credential fill <<EOF 2>/dev/null | sed -n 's/^password=//p' | head -n1
protocol=https
host=gitea.redbearos.org
username=${GITEA_USER}
EOF
)"
if [ -n "$helper_token" ]; then
printf '%s' "$helper_token"
return 0
fi
fi
if [ -r "$HOME/.netrc" ]; then
local netrc_token
netrc_token="$(awk -v host="gitea.redbearos.org" -v user="$GITEA_USER" '
$1=="machine" && $2==host { in_block=1; next }
in_block && $1=="login" && $2==user { want=1; next }
in_block && want && $1=="password" { print $2; exit }
in_block && $1=="machine" { in_block=0 }
' "$HOME/.netrc")"
if [ -n "$netrc_token" ]; then
printf '%s' "$netrc_token"
return 0
fi
fi
return 1
}
log "Resolving Gitea token (env → git helper → ~/.netrc) ..."
TOKEN="$(resolve_token || true)"
[ -n "$TOKEN" ] || die "no token found. Set REDBEAR_GITEA_TOKEN or configure ~/.netrc / git credential helper."
# ---------------------------------------------------------------------------
# 2. List every repo visible to the user via the Gitea API.
# ---------------------------------------------------------------------------
api_get() {
curl -fsS -H "Authorization: token $TOKEN" \
-H "Accept: application/json" \
"$GITEA_HOST/api/v1$1"
}
log "Fetching repo list from ${GITEA_HOST}/api/v1/users/${GITEA_USER}/repos ..."
REPOS_JSON="$(api_get "/users/${GITEA_USER}/repos?limit=200")" || die "Gitea API call failed."
REPOS_ALL="$(printf '%s' "$REPOS_JSON" | jq -r '.[].name')"
[ -n "$REPOS_ALL" ] || die "no repos returned — check token permissions."
# ---------------------------------------------------------------------------
# 3. Filter the deletion candidates.
# ---------------------------------------------------------------------------
CANDIDATES=""
while IFS= read -r repo; do
[ -n "$repo" ] || continue
case " $KEEP_ALWAYS " in
*" $repo "*) continue ;; # canonical + user-named keepers
esac
if [ -n "$ONLY_LIST" ]; then
# comma-separated whitelist
case ",$ONLY_LIST," in
*",$repo,"*) ;; # match
*) continue ;;
esac
fi
CANDIDATES="${CANDIDATES}${repo}"$'\n'
done <<< "$REPOS_ALL"
CANDIDATES="$(printf '%s' "$CANDIDATES" | sed '/^$/d')"
if [ -z "$CANDIDATES" ]; then
log "No repos match deletion criteria. Nothing to do."
exit 0
fi
log "Repos flagged for deletion:"
while IFS= read -r r; do
[ -n "$r" ] || continue
log " - $r"
done <<< "$CANDIDATES"
if [ "$DRY_RUN" -eq 1 ]; then
log "Dry run — no deletions performed."
exit 0
fi
# ---------------------------------------------------------------------------
# 4. Confirm with the operator (unless --yes).
# ---------------------------------------------------------------------------
if [ "$ASSUME_YES" -ne 1 ]; then
log ""
log "WARNING: This DELETES repositories from ${GITEA_HOST}."
log "Make sure each repo above has been migrated as a submodule branch"
log "inside ${CANONICAL_REPO} first (see redirect-to-submodules.sh)."
log "The deletion is irreversible."
log ""
read -r -p "Type 'delete' to confirm: " confirm
if [ "$confirm" != "delete" ]; then
log "Aborted."
exit 1
fi
fi
# ---------------------------------------------------------------------------
# 5. Delete each candidate via DELETE /repos/{owner}/{repo}.
# ---------------------------------------------------------------------------
fail_count=0
ok_count=0
while IFS= read -r repo; do
[ -n "$repo" ] || continue
log "Deleting ${GITEA_USER}/${repo} ..."
http_code="$(curl -sS -o /dev/null -w '%{http_code}' \
-X DELETE \
-H "Authorization: token $TOKEN" \
-H "Accept: application/json" \
"${GITEA_HOST}/api/v1/repos/${GITEA_USER}/${repo}")"
case "$http_code" in
2*) log " ✓ deleted ($http_code)"; ok_count=$((ok_count+1)) ;;
403) log " ✗ FORBIDDEN — token lacks delete permission on ${repo}"; fail_count=$((fail_count+1)) ;;
404) log " already gone (404)"; ok_count=$((ok_count+1)) ;;
*) log " ✗ FAILED ($http_code)"; fail_count=$((fail_count+1)) ;;
esac
done <<< "$CANDIDATES"
log ""
log "Summary: ${ok_count} deleted, ${fail_count} failed."
[ "$fail_count" -eq 0 ] || exit 3
exit 0
+185
View File
@@ -0,0 +1,185 @@
#!/usr/bin/env bash
# Redirect each existing per-component Gitea repo into a branch on the
# canonical RedBear-OS repo, then point the matching git submodule(s) at
# that branch. After this, the per-component Gitea repos can be deleted
# without breaking any submodule resolution.
#
# What this script does, for each per-component Gitea repo:
# 1. Fetches the repo's HEAD into a temp clone.
# 2. Pushes that HEAD as a `submodule/<component>` branch on RedBear-OS.
# 3. Rewrites `.gitmodules` so any submodule entry pointing at the
# per-component URL now points at RedBear-OS with the new branch.
#
# What it does NOT do:
# - It does not move source content between repositories (source already
# lives in the per-component repo; we just push it under a new branch
# name in the canonical repo).
# - It does not delete the per-component Gitea repos. Use
# delete-per-component-repos.sh afterwards.
# - It does not touch recipe.toml files (none reference the per-component
# URLs as far as the current tree shows).
#
# Token is read at runtime from $REDBEAR_GITEA_TOKEN (or ~/.netrc). It is
# NEVER written to disk, logged, or echoed.
set -euo pipefail
GITEA_HOST="${GITEA_HOST:-https://gitea.redbearos.org}"
GITEA_USER="${GITEA_USER:-vasilito}"
CANONICAL_REPO="${CANONICAL_REPO:-RedBear-OS}"
CANONICAL_URL="${CANONICAL_URL:-${GITEA_HOST}/${GITEA_USER}/${CANONICAL_REPO}.git}"
# Per-component Gitea repos to redirect. The actual Gitea slug is
# discovered at runtime via the API, so this list contains the canonical
# component names only.
DEFAULT_COMPONENTS=(base kernel installer redox-drm userutils libredox libpciaccess)
DRY_RUN=0
SKIP_PUSH=0
COMPONENTS=()
while [ $# -gt 0 ]; do
case "$1" in
--dry-run) DRY_RUN=1 ;;
--skip-push) SKIP_PUSH=1 ;;
-h|--help) sed -n '2,28p' "$0"; exit 0 ;;
--) shift; while [ $# -gt 0 ]; do COMPONENTS+=("$1"); shift; done ;;
-*) echo "ERROR: unknown arg: $1" >&2; exit 2 ;;
*) COMPONENTS+=("$1") ;;
esac
shift
done
[ "${#COMPONENTS[@]}" -gt 0 ] || COMPONENTS=("${DEFAULT_COMPONENTS[@]}")
log() { printf '[redirect-to-submodules] %s\n' "$*" >&2; }
die() { log "FATAL: $*"; exit 1; }
resolve_token() {
if [ -n "${REDBEAR_GITEA_TOKEN:-}" ]; then
printf '%s' "$REDBEAR_GITEA_TOKEN"
return 0
fi
if [ -r "$HOME/.netrc" ]; then
awk -v host="gitea.redbearos.org" -v user="$GITEA_USER" '
$1=="machine" && $2==host { in_block=1; next }
in_block && $1=="login" && $2==user { want=1; next }
in_block && want && $1=="password" { print $2; exit }
in_block && $1=="machine" { in_block=0 }
' "$HOME/.netrc"
fi
}
TOKEN="$(resolve_token || true)"
[ -n "$TOKEN" ] || die "no token found. Set REDBEAR_GITEA_TOKEN or configure ~/.netrc."
command -v git >/dev/null || die "git not found in PATH"
command -v jq >/dev/null || die "jq not found in PATH"
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || die "must be run from inside the ${CANONICAL_REPO} working tree"
current_branch="$(git rev-parse --abbrev-ref HEAD)"
log "Working tree : $(git rev-parse --show-toplevel)"
log "Branch : ${current_branch}"
log "Canonical URL: ${CANONICAL_URL}"
if [ "$DRY_RUN" -eq 0 ] && [ "$SKIP_PUSH" -eq 0 ]; then
log ""
log "This script PUSHES branches to ${CANONICAL_URL} and modifies .gitmodules."
read -r -p "Type 'redirect' to confirm: " confirm
[ "$confirm" = "redirect" ] || { log "Aborted."; exit 1; }
fi
api_get() {
curl -fsS -H "Authorization: token $TOKEN" \
-H "Accept: application/json" \
"$GITEA_HOST/api/v1$1"
}
log "Verifying Gitea credentials ..."
api_get "/user" >/dev/null || die "token rejected by Gitea API"
log "Fetching Gitea repo list ..."
REPO_NAMES_API="$(api_get "/users/${GITEA_USER}/repos?limit=200" | jq -r '.[].name')"
[ -n "${REPO_NAMES_API}" ] || die "no repos returned — check token permissions."
find_slug() {
local component="$1"
# Try the legacy redbear-os-<component> convention first, then the bare name.
for candidate in "redbear-os-${component}" "${component}"; do
if printf '%s\n' "${REPO_NAMES_API}" | grep -qx "${candidate}"; then
printf '%s' "${candidate}"
return 0
fi
done
return 1
}
redirect_component() {
local component="$1"
local gitea_slug
if ! gitea_slug="$(find_slug "${component}")"; then
log ""
log "=== ${component} ==="
die "no Gitea repo found for component '${component}' (tried: redbear-os-${component}, ${component})"
fi
local branch="submodule/${component}"
log ""
log "=== ${component} ==="
log " source Gitea repo : ${gitea_slug}"
log " target branch : ${branch}"
if [ "$DRY_RUN" -eq 1 ]; then
log " [dry-run] would fetch HEAD, push as ${branch}, rewrite .gitmodules"
return 0
fi
local tmp
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
log " cloning ${gitea_slug} ..."
local src_url="https://${TOKEN}@${GITEA_HOST#https://}/${GITEA_USER}/${gitea_slug}.git"
git clone --quiet "${src_url}" "${tmp}/src" \
|| die "clone failed for ${gitea_slug}"
local src_default
src_default="$(git -C "${tmp}/src" symbolic-ref --short HEAD 2>/dev/null || echo "")"
local src_commit_count
src_commit_count="$(git -C "${tmp}/src" rev-list --all --count 2>/dev/null || echo 0)"
if [ -z "${src_commit_count}" ] || [ "${src_commit_count}" = "0" ]; then
log " source repo is empty (0 commits) — skipping push"
rm -rf "${tmp}"
trap - EXIT
log "${component} (no-op)"
return 0
fi
log " source default branch: ${src_default} (${src_commit_count} commits)"
log " pushing ${branch} to ${CANONICAL_URL} ..."
local push_url="https://${TOKEN}@${GITEA_HOST#https://}/${GITEA_USER}/${CANONICAL_REPO}.git"
git -C "${tmp}/src" push --quiet "${push_url}" "refs/heads/${src_default}:refs/heads/${branch}" \
|| die "push failed for ${branch}"
log " rewriting .gitmodules ..."
if grep -q "url = .*${gitea_slug}\.git" .gitmodules 2>/dev/null; then
sed -i.bak "s|url = .*${gitea_slug}\.git|url = ${CANONICAL_URL}|" .gitmodules
sed -i "s|^ branch = .*| branch = ${branch}|" .gitmodules
rm -f .gitmodules.bak
log " updated"
else
log " no .gitmodules entry references ${gitea_slug}; left untouched"
fi
rm -rf "${tmp}"
trap - EXIT
log "${component} redirected"
}
for c in "${COMPONENTS[@]}"; do
redirect_component "$c"
done
log ""
log "Done. Verify with: git submodule status"
log "Then run ./local/scripts/delete-per-component-repos.sh to delete the"
log "now-unused per-component repos on Gitea."