This repository has been archived on 2026-06-22. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
lnbits-sensei/modules/dev-env/scripts/pr-helpers.sh
Padreug 25353f548d fix(dev-env): confirm before force-deleting an unmerged PR branch
git-pr-cleanup (prc) tried `branch -d` then fell back to `branch -D`
unconditionally, silently destroying an unmerged branch when prc was
run before the PR merged or against the wrong branch. Keep the safe
`-d` for the normal post-merge path, but prompt before forcing so
unmerged commits aren't lost without consent; decline keeps the branch.
2026-06-20 09:52:17 +02:00

153 lines
5 KiB
Bash

#!/usr/bin/env bash
# dev-env: upstream PR worktree helpers
#
# Sourced into interactive shells. Provides prb / prc / prl. Each PR gets
# a throwaway worktree at ${UPSTREAM_PRS_DIR}/<repo>-<branch> based on
# upstream/main (or master), ready to push to the `fork` remote and open
# a PR. Reads paths from /etc/dev-env/config.sh.
_devenv_load_config() {
if [[ -r /etc/dev-env/config.sh ]]; then
# shellcheck disable=SC1091
source /etc/dev-env/config.sh
fi
}
# Create a PR worktree at ${UPSTREAM_PRS_DIR}/<repo>-<branch>.
#
# Usage: git-pr-branch <repo-name> <branch-name>
git-pr-branch() {
_devenv_load_config
local repo_name="${1:-}"
local branch_name="${2:-}"
local repos_dir="${REPOS_DIR:-$DEV_ROOT/repos}"
local prs_dir="${UPSTREAM_PRS_DIR:-$DEV_ROOT/upstream-prs}"
if [[ -z "$repo_name" || -z "$branch_name" ]]; then
echo "Usage: git-pr-branch <repo-name> <branch-name>"
echo "Example: git-pr-branch lnbits fix-invoice-bug"
echo ""
echo "Creates a worktree at $prs_dir/<repo>-<branch>"
echo "based on upstream/main, ready for an upstream PR."
return 1
fi
local bare_repo="$repos_dir/${repo_name}.git"
local pr_path="$prs_dir/${repo_name}-${branch_name}"
if [[ ! -d "$bare_repo" ]]; then
echo "Repo not found: $bare_repo"
echo "Available repos:"
ls -1 "$repos_dir"/*.git 2>/dev/null | xargs -n1 basename | sed 's/\.git$//'
return 1
fi
if [[ -d "$pr_path" ]]; then
echo "PR worktree already exists: $pr_path"
echo "To remove it: git-pr-cleanup $repo_name $branch_name"
return 1
fi
# Fetch upstream
echo "Fetching upstream..."
git -C "$bare_repo" fetch upstream 2>/dev/null || {
echo "No 'upstream' remote on $repo_name — cannot create PR branch"
return 1
}
# Determine base branch (main or master)
local base_branch="main"
git -C "$bare_repo" show-ref --verify --quiet "refs/remotes/upstream/main" \
|| base_branch="master"
echo "Creating branch '$branch_name' from upstream/$base_branch..."
git -C "$bare_repo" branch "$branch_name" "upstream/$base_branch" 2>/dev/null \
|| git -C "$bare_repo" branch -f "$branch_name" "upstream/$base_branch"
mkdir -p "$prs_dir"
git -C "$bare_repo" worktree add "$pr_path" "$branch_name"
if ! git -C "$bare_repo" remote get-url fork &>/dev/null; then
echo ""
echo "Note: 'fork' remote not configured for $repo_name."
echo "Add it with:"
echo " git -C $bare_repo remote add fork git@github.com:${GITHUB_FORK_USER:-<user>}/${repo_name}.git"
fi
cat <<EOF
Ready! Your PR worktree is at:
cd $pr_path
Workflow:
1. cd $pr_path
2. Edit, test, commit
3. git push fork $branch_name
4. Open the PR on GitHub (against upstream/$base_branch)
5. After merge: git-pr-cleanup $repo_name $branch_name
EOF
}
# Remove a PR worktree after the PR is merged (or abandoned).
git-pr-cleanup() {
_devenv_load_config
local repo_name="${1:-}"
local branch_name="${2:-}"
local repos_dir="${REPOS_DIR:-$DEV_ROOT/repos}"
local prs_dir="${UPSTREAM_PRS_DIR:-$DEV_ROOT/upstream-prs}"
if [[ -z "$repo_name" || -z "$branch_name" ]]; then
echo "Usage: git-pr-cleanup <repo-name> <branch-name>"
echo ""
echo "Active PR worktrees:"
ls -1 "$prs_dir" 2>/dev/null || echo " (none)"
return 1
fi
local bare_repo="$repos_dir/${repo_name}.git"
local pr_path="$prs_dir/${repo_name}-${branch_name}"
[[ -d "$pr_path" ]] || { echo "PR worktree not found: $pr_path"; return 1; }
echo "Removing worktree: $pr_path"
git -C "$bare_repo" worktree remove "$pr_path"
echo "Deleting branch: $branch_name"
if ! git -C "$bare_repo" branch -d "$branch_name" 2>/dev/null; then
echo " Branch '$branch_name' is not fully merged."
read -r -p " Force-delete it (unmerged commits will be lost)? [y/N] " -n 1 REPLY
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
git -C "$bare_repo" branch -D "$branch_name"
else
echo " Kept branch '$branch_name' (worktree already removed)."
return 1
fi
fi
echo "Done."
}
# List all active PR worktrees.
git-pr-list() {
_devenv_load_config
local prs_dir="${UPSTREAM_PRS_DIR:-$DEV_ROOT/upstream-prs}"
echo "=== Active PR Worktrees ==="
if [[ -d "$prs_dir" && -n "$(ls -A "$prs_dir" 2>/dev/null)" ]]; then
for pr_dir in "$prs_dir"/*; do
[[ -d "$pr_dir" ]] || continue
local name branch status
name="$(basename "$pr_dir")"
branch="$(git -C "$pr_dir" branch --show-current 2>/dev/null || echo '?')"
status="$(git -C "$pr_dir" status -sb 2>/dev/null | head -1 || echo '?')"
printf " %-40s %s %s\n" "$name" "$branch" "$status"
done
else
echo " (none)"
fi
}
alias prb='git-pr-branch'
alias prc='git-pr-cleanup'
alias prl='git-pr-list'