312 lines
11 KiB
Bash
312 lines
11 KiB
Bash
#!/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'
|