feat(dev-env): shell scripts (git-hooks, worktree, deploy, regtest, nav) and smoke test
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7960f82494
commit
dbd9e76027
13 changed files with 2496 additions and 0 deletions
312
modules/dev-env/scripts/worktree.sh
Normal file
312
modules/dev-env/scripts/worktree.sh
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
#!/usr/bin/env bash
|
||||
# dev-env: git worktree helpers
|
||||
#
|
||||
# Provides:
|
||||
# - wt / wts / wtu generic worktree listing/sync/upstream-fetch
|
||||
# - wtn / worktree-spawn create a new branch + worktree from any project
|
||||
# - bw-spawn spawn a boilerplate-website experiment + install
|
||||
# - lnbits-status show dev/main divergence vs upstream
|
||||
# - lnbits-sync-dev merge upstream/dev into dev worktree
|
||||
# - lnbits-sync-main merge upstream/main into main worktree
|
||||
#
|
||||
# Ported from ~/dev/.devenv.d/80-worktrees.sh with the bare-repo path
|
||||
# now coming from /etc/dev-env/config.sh.
|
||||
|
||||
# Shared config loader (_devenv_load_config) lives in /etc/dev-env/lib.sh.
|
||||
if [[ -r /etc/dev-env/lib.sh ]]; then
|
||||
# shellcheck disable=SC1091
|
||||
source /etc/dev-env/lib.sh
|
||||
fi
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Generic worktree helpers
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
worktree-list() {
|
||||
_devenv_load_config
|
||||
local repo_name="${1:-}"
|
||||
local repos_dir="${REPOS_DIR:-$DEV_ROOT/repos}"
|
||||
|
||||
if [[ -n "$repo_name" ]]; then
|
||||
local bare_repo="$repos_dir/${repo_name}.git"
|
||||
if [[ -d "$bare_repo" ]]; then
|
||||
git -C "$bare_repo" worktree list
|
||||
else
|
||||
echo "Repo not found: $repo_name"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo "=== All Worktrees ==="
|
||||
for repo in "$repos_dir"/*.git; do
|
||||
[[ -d "$repo" ]] || continue
|
||||
echo ""
|
||||
echo "--- $(basename "$repo" .git) ---"
|
||||
git -C "$repo" worktree list
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
worktree-sync() {
|
||||
_devenv_load_config
|
||||
local repos_dir="${REPOS_DIR:-$DEV_ROOT/repos}"
|
||||
echo "=== Syncing Worktrees ==="
|
||||
for repo in "$repos_dir"/*.git; do
|
||||
[[ -d "$repo" ]] || continue
|
||||
local name
|
||||
name="$(basename "$repo" .git)"
|
||||
echo ""
|
||||
echo "--- $name ---"
|
||||
git -C "$repo" fetch --all --quiet 2>/dev/null || true
|
||||
git -C "$repo" worktree list | while read -r line; do
|
||||
# format: <path> <sha> [<branch>]
|
||||
local path branch status
|
||||
path="$(awk '{print $1}' <<<"$line")"
|
||||
branch="$(awk '{print $3}' <<<"$line" | tr -d '[]')"
|
||||
if [[ -d "$path" ]]; then
|
||||
status="$(git -C "$path" status -sb 2>/dev/null | head -1)"
|
||||
printf " %-20s %s\n" "${branch:-?}" "$status"
|
||||
fi
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
worktree-update-upstream() {
|
||||
_devenv_load_config
|
||||
local repo_name="${1:-}"
|
||||
local repos_dir="${REPOS_DIR:-$DEV_ROOT/repos}"
|
||||
|
||||
if [[ -z "$repo_name" ]]; then
|
||||
echo "Usage: wtu <repo-name>"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local bare_repo="$repos_dir/${repo_name}.git"
|
||||
if [[ ! -d "$bare_repo" ]]; then
|
||||
echo "Repo not found: $bare_repo"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! git -C "$bare_repo" remote get-url upstream &>/dev/null; then
|
||||
echo "No upstream remote configured for $repo_name"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "Fetching upstream..."
|
||||
git -C "$bare_repo" fetch upstream
|
||||
|
||||
echo ""
|
||||
echo "=== Upstream Status: $repo_name ==="
|
||||
|
||||
# Determine upstream base branch (main or master).
|
||||
local base_branch="main"
|
||||
git -C "$bare_repo" show-ref --verify --quiet refs/remotes/upstream/main || base_branch="master"
|
||||
|
||||
# Show each local branch's divergence vs the upstream base.
|
||||
git -C "$bare_repo" for-each-ref --format='%(refname:short)' refs/heads | while read -r br; do
|
||||
local behind ahead
|
||||
behind=$(git -C "$bare_repo" rev-list --count "$br..upstream/$base_branch" 2>/dev/null || echo ?)
|
||||
ahead=$(git -C "$bare_repo" rev-list --count "upstream/$base_branch..$br" 2>/dev/null || echo ?)
|
||||
printf " %-24s behind:%s ahead:%s\n" "$br" "$behind" "$ahead"
|
||||
done
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Spawn a new worktree (== branch) from an existing project.
|
||||
#-------------------------------------------------------------------------------
|
||||
# Usage: worktree-spawn <repo> <new-branch> [base-branch]
|
||||
#
|
||||
# Locates the bare repo, creates <new-branch> from <base-branch> (default
|
||||
# main, else master), and adds a worktree at the project root next to the
|
||||
# base worktree. Suited to "spin off an experiment from the boilerplate"
|
||||
# flows — e.g. `worktree-spawn boilerplate-website interior-designer`.
|
||||
|
||||
worktree-spawn() {
|
||||
_devenv_load_config
|
||||
local repo_name="${1:-}"
|
||||
local new_branch="${2:-}"
|
||||
local base_branch="${3:-}"
|
||||
|
||||
if [[ -z "$repo_name" || -z "$new_branch" ]]; then
|
||||
echo "Usage: worktree-spawn <repo-name> <new-branch-name> [base-branch]"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local repos_dir="${REPOS_DIR:-$DEV_ROOT/repos}"
|
||||
local bare_repo="$repos_dir/${repo_name}.git"
|
||||
|
||||
[[ -d "$bare_repo" ]] || { echo "Repo not found: $bare_repo"; return 1; }
|
||||
|
||||
if [[ -z "$base_branch" ]]; then
|
||||
if git -C "$bare_repo" show-ref --verify --quiet refs/heads/main; then
|
||||
base_branch="main"
|
||||
elif git -C "$bare_repo" show-ref --verify --quiet refs/heads/master; then
|
||||
base_branch="master"
|
||||
else
|
||||
echo "No main/master in $repo_name; pass base branch explicitly."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
local base_path
|
||||
base_path="$(git -C "$bare_repo" worktree list --porcelain | awk -v t="refs/heads/$base_branch" '
|
||||
/^worktree / { wt = $2 }
|
||||
/^branch / && $2 == t { print wt; exit }
|
||||
')"
|
||||
[[ -n "$base_path" ]] || { echo "No worktree for $repo_name@$base_branch"; return 1; }
|
||||
|
||||
local project_root new_path
|
||||
project_root="$(dirname "$base_path")"
|
||||
new_path="$project_root/$new_branch"
|
||||
|
||||
[[ -e "$new_path" ]] && { echo "Path already exists: $new_path"; return 1; }
|
||||
|
||||
echo "Creating branch '$new_branch' from '$base_branch' in $repo_name..."
|
||||
git -C "$bare_repo" branch "$new_branch" "$base_branch" || return 1
|
||||
|
||||
echo "Adding worktree at $new_path..."
|
||||
git -C "$bare_repo" worktree add "$new_path" "$new_branch" || return 1
|
||||
|
||||
echo ""
|
||||
echo "Ready: $new_path"
|
||||
[[ -f "$new_path/package.json" ]] && echo "Next: cd $new_path && pnpm install"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Spawn a boilerplate-website experiment AND install deps.
|
||||
#-------------------------------------------------------------------------------
|
||||
# Usage: bw-spawn <experiment-name>
|
||||
#
|
||||
# Wraps worktree-spawn with the boilerplate-website repo pre-filled and
|
||||
# runs `pnpm install` in the new worktree. The site is ready to `pnpm dev`
|
||||
# the moment this returns.
|
||||
|
||||
bw-spawn() {
|
||||
local name="${1:-}"
|
||||
if [[ -z "$name" ]]; then
|
||||
echo "Usage: bw-spawn <experiment-name>"
|
||||
return 1
|
||||
fi
|
||||
|
||||
worktree-spawn boilerplate-website "$name" main || return $?
|
||||
|
||||
_devenv_load_config
|
||||
local wt_path="${DEV_ROOT:-$HOME/dev}/boilerplate-website/$name"
|
||||
if [[ -f "$wt_path/package.json" ]]; then
|
||||
echo ""
|
||||
echo "Installing deps..."
|
||||
( cd "$wt_path" && pnpm install ) || return $?
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Done. cd $wt_path && pnpm dev"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# lnbits-specific workflow helpers
|
||||
#-------------------------------------------------------------------------------
|
||||
# Mental model:
|
||||
# Our `dev` and `main` are aiolabs branches that diverge from upstream
|
||||
# and carry aiolabs-only commits on top. The sync helpers below
|
||||
# `merge upstream/<branch>` to vendor upstream changes — they don't
|
||||
# fast-forward.
|
||||
#
|
||||
# `main` feeds every host (deploy flake's `lnbits` input is ?ref=main).
|
||||
# The former `demo` branch is retired — what used to be demo-only
|
||||
# behavior now lives on main behind per-host overrides on the
|
||||
# server-deploy flake's `lnbits` input. `dev` is the in-progress
|
||||
# staging branch; we cut releases by merging dev → main.
|
||||
#
|
||||
# `lnbits-status` reports divergence vs upstream so we know how stale
|
||||
# the vendor base is.
|
||||
|
||||
_lnbits_paths() {
|
||||
_devenv_load_config
|
||||
BARE="${REPOS_DIR:-$DEV_ROOT/repos}/lnbits.git"
|
||||
LNBITS_ROOT="${LNBITS_DIR:-$DEV_ROOT/lnbits}"
|
||||
}
|
||||
|
||||
lnbits-status() {
|
||||
_lnbits_paths
|
||||
if [[ ! -d "$BARE" ]]; then
|
||||
echo "lnbits bare repo not found: $BARE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
git -C "$BARE" fetch upstream --quiet 2>/dev/null || true
|
||||
git -C "$BARE" fetch origin --quiet 2>/dev/null || true
|
||||
|
||||
echo "=== lnbits Branch Status ==="
|
||||
echo ""
|
||||
echo "Branch layout (our branches diverge from upstream; sync via merge):"
|
||||
echo " dev = aiolabs staging (vendored from upstream/dev)"
|
||||
echo " main = aiolabs stable (vendored from upstream/main)"
|
||||
echo ""
|
||||
|
||||
for env in dev main; do
|
||||
local wt="$LNBITS_ROOT/$env"
|
||||
if [[ ! -d "$wt" ]]; then
|
||||
printf " %-6s: (worktree not created)\n" "$env"
|
||||
continue
|
||||
fi
|
||||
local branch behind_u ahead_u
|
||||
branch="$(git -C "$wt" branch --show-current)"
|
||||
case "$env" in
|
||||
dev)
|
||||
behind_u=$(git -C "$BARE" rev-list --count "$branch..upstream/dev" 2>/dev/null || echo ?)
|
||||
ahead_u=$(git -C "$BARE" rev-list --count "upstream/dev..$branch" 2>/dev/null || echo ?)
|
||||
printf " dev: %-3s behind upstream/dev %-3s ahead\n" "$behind_u" "$ahead_u"
|
||||
;;
|
||||
main)
|
||||
behind_u=$(git -C "$BARE" rev-list --count "$branch..upstream/main" 2>/dev/null || echo ?)
|
||||
ahead_u=$(git -C "$BARE" rev-list --count "upstream/main..$branch" 2>/dev/null || echo ?)
|
||||
printf " main: %-3s behind upstream/main %-3s ahead\n" "$behind_u" "$ahead_u"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " lnbits-sync-dev # merge upstream/dev into dev"
|
||||
echo " lnbits-sync-main # merge upstream/main into main worktree"
|
||||
}
|
||||
|
||||
lnbits-sync-dev() {
|
||||
_lnbits_paths
|
||||
local dev_dir="$LNBITS_ROOT/dev"
|
||||
[[ -d "$BARE" ]] || { echo "bare repo missing: $BARE"; return 1; }
|
||||
[[ -d "$dev_dir" ]] || { echo "dev worktree missing: $dev_dir"; return 1; }
|
||||
|
||||
echo "Fetching upstream..."
|
||||
git -C "$BARE" fetch upstream
|
||||
|
||||
echo "Merging upstream/dev into dev..."
|
||||
git -C "$dev_dir" merge upstream/dev
|
||||
|
||||
echo ""
|
||||
echo "Dev synced. Review, then: lb dev && git push"
|
||||
}
|
||||
|
||||
lnbits-sync-main() {
|
||||
_lnbits_paths
|
||||
local main_dir="$LNBITS_ROOT/main"
|
||||
[[ -d "$BARE" ]] || { echo "bare repo missing: $BARE"; return 1; }
|
||||
[[ -d "$main_dir" ]] || { echo "main worktree missing: $main_dir"; return 1; }
|
||||
|
||||
echo "Fetching upstream..."
|
||||
git -C "$BARE" fetch upstream
|
||||
|
||||
echo "Merging upstream/main into main worktree..."
|
||||
git -C "$main_dir" merge upstream/main
|
||||
|
||||
echo ""
|
||||
echo "Main synced. Review, then: lb main && git push"
|
||||
}
|
||||
|
||||
# Aliases keep the original two-letter shortcuts.
|
||||
alias wt='worktree-list'
|
||||
alias wts='worktree-sync'
|
||||
alias wtu='worktree-update-upstream'
|
||||
alias wtn='worktree-spawn'
|
||||
alias lbs='lnbits-status'
|
||||
alias lbsd='lnbits-sync-dev'
|
||||
alias lbsm='lnbits-sync-main'
|
||||
Loading…
Add table
Add a link
Reference in a new issue