added gprune --deep parameter to allow deletion of forge merge

This commit is contained in:
fatalerrors
2026-06-03 15:47:29 +02:00
parent 462efef034
commit bcc86814b5
2 changed files with 51 additions and 8 deletions

View File

@@ -497,11 +497,20 @@ export -f gwip
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Delete merged local branches (except protected branches) # Delete merged local branches (except protected branches)
# Usage: gprune [main-branch] # Usage: gprune [-D|--deep] [main-branch]
#
# Default mode: deletes branches already merged into <main-branch> locally
# (git branch --merged).
# Deep mode (-D/--deep): additionally deletes branches whose tracking remote
# has disappeared (remote pruned after a merge request / pull request merge).
# Those branches are detected via `git fetch --prune` + remote-tracking gone.
# This is the common case when the MR was merged upstream and the remote
# branch was deleted by the forge. Deletion uses `git branch -D` (force)
# because the local branch has no merged ancestor that git can verify locally.
gprune() gprune()
{ {
local PARSED local PARSED deep=0
PARSED=$(getopt -o h --long help -n 'gprune' -- "$@") PARSED=$(getopt -o hD --long help,deep -n 'gprune' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after # shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"gprune --help\" to display usage." disp E "Invalid options, use \"gprune --help\" to display usage."
@@ -512,10 +521,20 @@ gprune()
while true; do while true; do
case "$1" in case "$1" in
-h|--help) -h|--help)
printf "gprune: Delete local branches already merged into main branch.\n" printf "gprune: Delete local branches already merged into main branch.\n\n"
printf "Usage: gprune [main-branch]\n" printf "Usage: gprune [-D|--deep] [main-branch]\n\n"
printf "Options:\n"
printf "\t-D, --deep\tAlso delete branches whose upstream was removed\n"
printf "\t\t\t(remote deleted after MR/PR merge). Uses 'git branch -D'.\n"
printf "\t-h, --help\tDisplay this help screen\n\n"
printf "Arguments:\n"
printf "\tmain-branch\tBase branch to check merges against (default: auto-detected)\n"
return 0 return 0
;; ;;
-D|--deep)
deep=1
shift
;;
--) --)
shift shift
break break
@@ -532,6 +551,7 @@ gprune()
local base="${1:-$(_git_default_branch "$GIT_DEFAULT_REMOTE")}" current deleted=0 local base="${1:-$(_git_default_branch "$GIT_DEFAULT_REMOTE")}" current deleted=0
current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || return 1 current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || return 1
# ── Standard mode: branches locally merged into base ──────────────────────
disp I "Pruning branches merged into $base..." disp I "Pruning branches merged into $base..."
while IFS= read -r b; do while IFS= read -r b; do
@@ -540,12 +560,35 @@ gprune()
[[ $b == "$base" ]] && continue [[ $b == "$base" ]] && continue
[[ $b == "master" || $b == "main" || $b == "develop" || $b == "dev" ]] && continue [[ $b == "master" || $b == "main" || $b == "develop" || $b == "dev" ]] && continue
git branch -d "$b" >/dev/null 2>&1 && { git branch -d "$b" >/dev/null 2>&1 && {
printf "Deleted: %s\n" "$b" printf "Deleted (merged): %s\n" "$b"
((deleted++)) ((deleted++))
} }
done < <(git branch --merged "$base" | sed -E 's/^\*?\s*//') done < <(git branch --merged "$base" | sed -E 's/^\*?\s*//')
(( deleted == 0 )) && disp I "No merged branches to delete." # ── Deep mode: branches whose remote tracking ref was deleted upstream ─────
if (( deep )); then
disp I "Deep mode: pruning remote-tracking refs, then checking for gone branches..."
git fetch --prune --quiet
while IFS= read -r b; do
[[ -z $b ]] && continue
[[ $b == "$current" ]] && continue
[[ $b == "$base" ]] && continue
[[ $b == "master" || $b == "main" || $b == "develop" || $b == "dev" ]] && continue
# Verify the upstream is truly gone (not just unset).
local upstream
upstream=$(git rev-parse --abbrev-ref "${b}@{upstream}" 2>/dev/null)
[[ -z "$upstream" ]] && continue # no tracking branch at all — skip
# If the remote ref still exists, skip (not deleted upstream).
git show-ref --verify --quiet "refs/remotes/$upstream" 2>/dev/null && continue
git branch -D "$b" >/dev/null 2>&1 && {
printf "Deleted (gone upstream): %s\n" "$b"
((deleted++))
}
done < <(git branch -vv | sed -E 's/^\*?\s*//' | awk '/: gone]/ {print $1}')
fi
(( deleted == 0 )) && disp I "No branches to delete."
} }
export -f gprune export -f gprune
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@@ -70,7 +70,7 @@ help()
printf "gacp\t\tAdd, commit and push changes (auto-pull if needed)\n" printf "gacp\t\tAdd, commit and push changes (auto-pull if needed)\n"
printf "genpwd\t\tGenerate one or more random secure passwords with configurable constraints\n" printf "genpwd\t\tGenerate one or more random secure passwords with configurable constraints\n"
printf "ggraph\t\tDisplay decorated git history graph\n" printf "ggraph\t\tDisplay decorated git history graph\n"
printf "gprune\t\tDelete local branches already merged into main branch\n" printf "gprune\t\tDelete local branches already merged, or after remote deletion (MR / PR)\n"
printf "greset\t\tReset branch to upstream (stash local, drop local commits)\n" printf "greset\t\tReset branch to upstream (stash local, drop local commits)\n"
printf "groot\t\tDisplay repository root path (or cd to it with -g)\n" printf "groot\t\tDisplay repository root path (or cd to it with -g)\n"
printf "gsync\t\tFetch and rebase current branch onto upstream\n" printf "gsync\t\tFetch and rebase current branch onto upstream\n"