chore(git-workflow): add 3-repo feature branch skills + helper scripts

This commit is contained in:
Gergő Törcsvári 2026-05-29 08:15:27 +02:00
commit 645f89b816
No known key found for this signature in database
GPG key ID: 8E75F2CDE64E5322
10 changed files with 500 additions and 0 deletions

View file

@ -0,0 +1,33 @@
#!/bin/bash
# Exit 0 if all 3 repos are clean (no staged, no unstaged, no untracked files).
# Exit 1 with a readable message listing the dirty repos otherwise.
# Untracked files count as dirty - this is intentional: starting a new feature
# while you have uncommitted new files is almost always a mistake.
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=./repos.sh
source "$SCRIPT_DIR/repos.sh"
dirty=()
for repo in "${REPOS[@]}"; do
p=$(repo_path "$repo")
# `git status --porcelain` is empty iff worktree clean (covers modified, staged, and untracked).
if [ -n "$(git -C "$p" status --porcelain)" ]; then
dirty+=("$repo")
fi
done
if [ ${#dirty[@]} -eq 0 ]; then
echo "All 3 repos clean."
exit 0
fi
echo "Dirty repos: ${dirty[*]}" >&2
for repo in "${dirty[@]}"; do
p=$(repo_path "$repo")
echo "--- $repo ---" >&2
git -C "$p" status --short >&2
done
exit 1

View file

@ -0,0 +1,36 @@
#!/bin/bash
# Assert all 3 repos are on the given branch.
# Exit 0 if all match. Exit 1 with details if any mismatch (including detached HEAD).
# Usage: ./assert-on-branch.sh <branch-name>
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=./repos.sh
source "$SCRIPT_DIR/repos.sh"
if [ $# -ne 1 ]; then
echo "Usage: $0 <branch-name>" >&2
exit 2
fi
WANT="$1"
mismatched=()
for repo in "${REPOS[@]}"; do
p=$(repo_path "$repo")
cur=$(git -C "$p" branch --show-current)
if [ "$cur" != "$WANT" ]; then
mismatched+=("$repo: on '${cur:-DETACHED-HEAD}' (want '$WANT')")
fi
done
if [ ${#mismatched[@]} -eq 0 ]; then
echo "All 3 repos on branch: $WANT"
exit 0
fi
echo "Branch mismatch:" >&2
for line in "${mismatched[@]}"; do
echo " $line" >&2
done
exit 1

View file

@ -0,0 +1,26 @@
#!/bin/bash
# Run a git command in each of the 3 repos. Echoes before each run.
# Usage: ./for-each-repo.sh <git-subcommand-and-args>
# e.g. ./for-each-repo.sh status --short
# ./for-each-repo.sh fetch origin
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=./repos.sh
source "$SCRIPT_DIR/repos.sh"
if [ $# -eq 0 ]; then
echo "Usage: $0 <git-subcommand> [args...]" >&2
exit 2
fi
for repo in "${REPOS[@]}"; do
p=$(repo_path "$repo")
echo "=== $repo ($p) ==="
echo "+ git -C $p $*"
if ! git -C "$p" "$@"; then
echo "FAILED in repo: $repo" >&2
exit 1
fi
done

View file

@ -0,0 +1,63 @@
#!/bin/bash
# Emit one JSON line per repo describing its state.
# Fields: repo, path, branch, dirty, ahead, behind, head_sha, main, rebase_in_progress, up_to_date_with_main
# "up_to_date_with_main" means origin/<main> is an ancestor of HEAD (i.e. rebase not needed).
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=./repos.sh
source "$SCRIPT_DIR/repos.sh"
json_escape() {
# Minimal JSON string escape: backslash and double-quote only. Branch/sha never contain control chars in practice.
local s="$1"
s="${s//\\/\\\\}"
s="${s//\"/\\\"}"
printf '%s' "$s"
}
for repo in "${REPOS[@]}"; do
p=$(repo_path "$repo")
main=$(repo_main "$repo")
branch=$(git -C "$p" branch --show-current || echo "")
head_sha=$(git -C "$p" rev-parse --short HEAD)
if [ -z "$(git -C "$p" status --porcelain)" ]; then
dirty="false"
else
dirty="true"
fi
ahead=0
behind=0
if [ -n "$branch" ] && git -C "$p" rev-parse --verify --quiet "origin/$main" >/dev/null; then
read -r behind ahead < <(git -C "$p" rev-list --left-right --count "origin/$main...HEAD" 2>/dev/null || echo "0 0")
fi
if [ -d "$p/.git/rebase-merge" ] || [ -d "$p/.git/rebase-apply" ]; then
rebase="true"
else
rebase="false"
fi
up_to_date="false"
if git -C "$p" rev-parse --verify --quiet "origin/$main" >/dev/null; then
if git -C "$p" merge-base --is-ancestor "origin/$main" HEAD 2>/dev/null; then
up_to_date="true"
fi
fi
printf '{"repo":"%s","path":"%s","branch":"%s","dirty":%s,"ahead":%s,"behind":%s,"head_sha":"%s","main":"%s","rebase_in_progress":%s,"up_to_date_with_main":%s}\n' \
"$(json_escape "$repo")" \
"$(json_escape "$p")" \
"$(json_escape "$branch")" \
"$dirty" \
"$ahead" \
"$behind" \
"$(json_escape "$head_sha")" \
"$(json_escape "$main")" \
"$rebase" \
"$up_to_date"
done

34
scripts/git-workflow/repos.sh Executable file
View file

@ -0,0 +1,34 @@
#!/bin/bash
# Sourced library. Single source of truth for the 3-repo layout.
# Usage: source "$(dirname "$0")/repos.sh"
ROOT_DIR="/Users/torcsi/dev/kicad-wasm"
REPOS=(root kicad wxwidgets)
PATH_root="$ROOT_DIR"
PATH_kicad="$ROOT_DIR/kicad"
PATH_wxwidgets="$ROOT_DIR/wxwidgets"
MAIN_root="main"
MAIN_kicad="wasm-port"
MAIN_wxwidgets="wasm-port"
repo_path() {
local var="PATH_$1"
echo "${!var}"
}
repo_main() {
local var="MAIN_$1"
echo "${!var}"
}
run_git() {
# Echo before run so the user always sees what we're doing.
local repo="$1"; shift
local p
p=$(repo_path "$repo")
echo "+ git -C $p $*" >&2
git -C "$p" "$@"
}