@@ -1033,6 +1033,116 @@ cmd_list() {
10331033}
10341034
10351035# Clean command (remove prunable worktrees)
1036+ # Remove worktrees whose PRs/MRs are merged (handles squash merges)
1037+ # Usage: _clean_merged repo_root base_dir prefix yes_mode dry_run
1038+ _clean_merged () {
1039+ local repo_root=" $1 " base_dir=" $2 " prefix=" $3 " yes_mode=" $4 " dry_run=" $5 "
1040+
1041+ log_step " Checking for worktrees with merged PRs/MRs..."
1042+
1043+ # Detect hosting provider (GitHub, GitLab, etc.)
1044+ local provider
1045+ provider=$( detect_provider) || true
1046+ if [ -z " $provider " ]; then
1047+ local remote_url
1048+ remote_url=$( git remote get-url origin 2> /dev/null || true)
1049+ if [ -z " $remote_url " ]; then
1050+ log_error " No remote URL configured for 'origin'"
1051+ else
1052+ # Sanitize URL to avoid leaking embedded credentials (e.g., https://token@host/...)
1053+ local safe_url=" ${remote_url%%@* } "
1054+ if [ " $safe_url " != " $remote_url " ]; then
1055+ safe_url=" <redacted>@${remote_url#*@ } "
1056+ fi
1057+ log_error " Could not detect hosting provider from remote URL: $safe_url "
1058+ log_info " Set manually: git gtr config set gtr.provider github (or gitlab)"
1059+ fi
1060+ exit 1
1061+ fi
1062+
1063+ # Ensure provider CLI is available and authenticated
1064+ ensure_provider_cli " $provider " || exit 1
1065+
1066+ # Fetch latest from origin
1067+ log_step " Fetching from origin..."
1068+ git fetch origin --prune 2> /dev/null || log_warn " Could not fetch from origin"
1069+
1070+ local removed=0
1071+ local skipped=0
1072+
1073+ # Get main repo branch to exclude it
1074+ local main_branch
1075+ main_branch=$( current_branch " $repo_root " )
1076+
1077+ # Iterate through worktree directories
1078+ for dir in " $base_dir /${prefix} " * ; do
1079+ [ -d " $dir " ] || continue
1080+
1081+ local branch
1082+ branch=$( current_branch " $dir " )
1083+
1084+ if [ -z " $branch " ] || [ " $branch " = " (detached)" ]; then
1085+ log_warn " Skipping $dir (detached HEAD)"
1086+ skipped=$(( skipped + 1 ))
1087+ continue
1088+ fi
1089+
1090+ # Skip if same as main repo branch
1091+ if [ " $branch " = " $main_branch " ]; then
1092+ continue
1093+ fi
1094+
1095+ # Check if worktree has uncommitted changes
1096+ if ! git -C " $dir " diff --quiet 2> /dev/null || \
1097+ ! git -C " $dir " diff --cached --quiet 2> /dev/null; then
1098+ log_warn " Skipping $branch (has uncommitted changes)"
1099+ skipped=$(( skipped + 1 ))
1100+ continue
1101+ fi
1102+
1103+ # Check for untracked files
1104+ if [ -n " $( git -C " $dir " ls-files --others --exclude-standard 2> /dev/null) " ]; then
1105+ log_warn " Skipping $branch (has untracked files)"
1106+ skipped=$(( skipped + 1 ))
1107+ continue
1108+ fi
1109+
1110+ # Check if branch has a merged PR/MR
1111+ if check_branch_merged " $provider " " $branch " ; then
1112+ if [ " $dry_run " -eq 1 ]; then
1113+ log_info " [dry-run] Would remove: $branch ($dir )"
1114+ removed=$(( removed + 1 ))
1115+ elif [ " $yes_mode " -eq 1 ] || prompt_yes_no " Remove worktree and delete branch '$branch '?" ; then
1116+ log_step " Removing worktree: $branch "
1117+ local remove_output
1118+ if remove_output=$( git worktree remove " $dir " 2>&1 ) ; then
1119+ # Also delete the local branch
1120+ git branch -d " $branch " 2> /dev/null || git branch -D " $branch " 2> /dev/null || true
1121+ log_info " Removed: $branch "
1122+ removed=$(( removed + 1 ))
1123+ else
1124+ if [ -n " $remove_output " ]; then
1125+ log_error " Failed to remove worktree: $remove_output "
1126+ else
1127+ log_error " Failed to remove worktree: $branch "
1128+ fi
1129+ fi
1130+ else
1131+ log_warn " Skipped: $branch (user declined)"
1132+ skipped=$(( skipped + 1 ))
1133+ fi
1134+ fi
1135+ # Branches without merged PRs are silently skipped (this is the normal case)
1136+ done
1137+
1138+ echo " "
1139+ if [ " $dry_run " -eq 1 ]; then
1140+ log_info " Dry run complete. Would remove: $removed , Skipped: $skipped "
1141+ else
1142+ log_info " Merged cleanup complete. Removed: $removed , Skipped: $skipped "
1143+ fi
1144+ }
1145+
10361146cmd_clean () {
10371147 local merged_mode=0
10381148 local yes_mode=0
@@ -1104,109 +1214,7 @@ EOF
11041214
11051215 # --merged mode: remove worktrees with merged PRs/MRs (handles squash merges)
11061216 if [ " $merged_mode " -eq 1 ]; then
1107- log_step " Checking for worktrees with merged PRs/MRs..."
1108-
1109- # Detect hosting provider (GitHub, GitLab, etc.)
1110- local provider
1111- provider=$( detect_provider) || true
1112- if [ -z " $provider " ]; then
1113- local remote_url
1114- remote_url=$( git remote get-url origin 2> /dev/null || true)
1115- if [ -z " $remote_url " ]; then
1116- log_error " No remote URL configured for 'origin'"
1117- else
1118- # Sanitize URL to avoid leaking embedded credentials (e.g., https://token@host/...)
1119- local safe_url=" ${remote_url%%@* } "
1120- if [ " $safe_url " != " $remote_url " ]; then
1121- safe_url=" <redacted>@${remote_url#*@ } "
1122- fi
1123- log_error " Could not detect hosting provider from remote URL: $safe_url "
1124- log_info " Set manually: git gtr config set gtr.provider github (or gitlab)"
1125- fi
1126- exit 1
1127- fi
1128-
1129- # Ensure provider CLI is available and authenticated
1130- ensure_provider_cli " $provider " || exit 1
1131-
1132- # Fetch latest from origin
1133- log_step " Fetching from origin..."
1134- git fetch origin --prune 2> /dev/null || log_warn " Could not fetch from origin"
1135-
1136- local removed=0
1137- local skipped=0
1138-
1139- # Get main repo branch to exclude it
1140- local main_branch
1141- main_branch=$( current_branch " $repo_root " )
1142-
1143- # Iterate through worktree directories
1144- for dir in " $base_dir /${prefix} " * ; do
1145- [ -d " $dir " ] || continue
1146-
1147- local branch
1148- branch=$( current_branch " $dir " )
1149-
1150- if [ -z " $branch " ] || [ " $branch " = " (detached)" ]; then
1151- log_warn " Skipping $dir (detached HEAD)"
1152- skipped=$(( skipped + 1 ))
1153- continue
1154- fi
1155-
1156- # Skip if same as main repo branch
1157- if [ " $branch " = " $main_branch " ]; then
1158- continue
1159- fi
1160-
1161- # Check if worktree has uncommitted changes
1162- if ! git -C " $dir " diff --quiet 2> /dev/null || \
1163- ! git -C " $dir " diff --cached --quiet 2> /dev/null; then
1164- log_warn " Skipping $branch (has uncommitted changes)"
1165- skipped=$(( skipped + 1 ))
1166- continue
1167- fi
1168-
1169- # Check for untracked files
1170- if [ -n " $( git -C " $dir " ls-files --others --exclude-standard 2> /dev/null) " ]; then
1171- log_warn " Skipping $branch (has untracked files)"
1172- skipped=$(( skipped + 1 ))
1173- continue
1174- fi
1175-
1176- # Check if branch has a merged PR/MR
1177- if check_branch_merged " $provider " " $branch " ; then
1178- if [ " $dry_run " -eq 1 ]; then
1179- log_info " [dry-run] Would remove: $branch ($dir )"
1180- removed=$(( removed + 1 ))
1181- elif [ " $yes_mode " -eq 1 ] || prompt_yes_no " Remove worktree and delete branch '$branch '?" ; then
1182- log_step " Removing worktree: $branch "
1183- local remove_output
1184- if remove_output=$( git worktree remove " $dir " 2>&1 ) ; then
1185- # Also delete the local branch
1186- git branch -d " $branch " 2> /dev/null || git branch -D " $branch " 2> /dev/null || true
1187- log_info " Removed: $branch "
1188- removed=$(( removed + 1 ))
1189- else
1190- if [ -n " $remove_output " ]; then
1191- log_error " Failed to remove worktree: $remove_output "
1192- else
1193- log_error " Failed to remove worktree: $branch "
1194- fi
1195- fi
1196- else
1197- log_warn " Skipped: $branch (user declined)"
1198- skipped=$(( skipped + 1 ))
1199- fi
1200- fi
1201- # Branches without merged PRs are silently skipped (this is the normal case)
1202- done
1203-
1204- echo " "
1205- if [ " $dry_run " -eq 1 ]; then
1206- log_info " Dry run complete. Would remove: $removed , Skipped: $skipped "
1207- else
1208- log_info " Merged cleanup complete. Removed: $removed , Skipped: $skipped "
1209- fi
1217+ _clean_merged " $repo_root " " $base_dir " " $prefix " " $yes_mode " " $dry_run "
12101218 fi
12111219}
12121220
0 commit comments