fix(git-shed): harden merged branch cleanup#75
Conversation
List merged locals via for-each-ref, fix stale parsing for + worktree lines, remove linked worktrees before branch delete, and tolerate per-branch delete failures.
Review Summary by QodoHarden git-shed merged branch cleanup and worktree handling
WalkthroughsDescription• Replace branch parsing with git for-each-ref to avoid malformed tokens • Add linked worktree removal before branch deletion operations • Fix stale branch detection to handle + prefix like * prefix • Implement per-branch error tolerance with graceful failure handling Diagramflowchart LR
A["Branch Detection"] -->|for-each-ref| B["Clean Branch Names"]
B -->|Check Worktrees| C["Remove Linked Worktrees"]
C -->|git branch -d/-D| D["Delete Branches"]
D -->|Error Handling| E["Continue on Failures"]
F["Stale Detection"] -->|Handle + prefix| G["Fixed Parsing"]
File Changes1. git-shed
|
Code Review by Qodo
1. Worktree path parsing broken
|
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Code Review
This pull request enhances the git-shed script by adding the ability to automatically remove linked worktrees when deleting merged or stale branches. It introduces a helper function to identify and force-remove worktrees and refactors branch identification to use git for-each-ref for better reliability. Review feedback suggests improving the worktree detection logic to handle paths with spaces using the --porcelain flag and adding --no-color to git branch commands to prevent ANSI escape codes from breaking the parsing logic.
| worktree=$( | ||
| git worktree list | grep -F "[$branch]" | awk '{print $1}' || true | ||
| ) |
There was a problem hiding this comment.
The current method of parsing git worktree list is fragile. Using awk '{print $1}' will fail if the worktree path contains spaces (common on macOS and Windows). Additionally, grep -F "[$branch]" could produce false positives if the path itself contains the branch name in brackets (e.g., /Users/name/projects/[feat]/repo).
Using git worktree list --porcelain provides a more reliable way to associate a branch with its worktree path by matching the full ref name.
worktree=$(
git worktree list --porcelain | awk -v target="refs/heads/$branch" '
/^worktree / { wt = substr($0, 10) }
/^branch / && $2 == target { print wt }
'
)
| git branch -vv | | ||
| awk '/: gone]/ { print ($1 == "*" || $1 == "+" ? $2 : $1) }' |
There was a problem hiding this comment.
It is safer to use --no-color with git branch -vv to ensure that ANSI escape codes do not interfere with the awk field parsing. If color is enabled in the user's git config, the * or + prefixes might be wrapped in escape sequences, causing the $1 == "*" check to fail.
git branch -vv --no-color |
awk '/: gone]/ { print ($1 == "*" || $1 == "+" ? $2 : $1) }'
| toplevel=$(git rev-parse --show-toplevel) | ||
| worktree=$( | ||
| git worktree list | grep -F "[$branch]" | awk '{print $1}' || true | ||
| ) | ||
| if [[ -z "$worktree" || "$worktree" == "$toplevel" ]]; then | ||
| return 0 | ||
| fi | ||
| if $DRY_RUN; then | ||
| echo "[DRY-RUN] Would remove worktree: $worktree (checks out $branch)" | ||
| return 0 | ||
| fi | ||
| echo "Removing worktree: $worktree" | ||
| if ! git worktree remove --force "$worktree"; then | ||
| echo "Retrying worktree remove with --force --force..." >&2 | ||
| git worktree remove --force --force "$worktree" || return 1 | ||
| fi |
There was a problem hiding this comment.
1. Worktree path parsing broken 🐞 Bug ☼ Reliability
remove_linked_worktree_for_branch parses git worktree list via awk '{print $1}', which
truncates worktree paths containing whitespace and can also yield multiple newline-separated paths;
the result is then passed as a single path to git worktree remove. This can fail to remove the
linked worktree (or target the wrong directory), leaving merged/stale branches undeletable and
undermining the script’s new hardening behavior.
Agent Prompt
## Issue description
`remove_linked_worktree_for_branch` currently identifies a linked worktree by running `git worktree list | ... | awk '{print $1}'`. This is not safe because the first whitespace-delimited field is not a robust way to capture a filesystem path (paths may contain spaces), and multiple matches will be collapsed into a single multi-line variable that is then passed as one argument to `git worktree remove`.
## Issue Context
The function’s output (`$worktree`) is used directly in `git worktree remove --force "$worktree"`, so any truncation or multi-line value can cause failure to remove the correct linked worktree, preventing subsequent `git branch -d/-D` from succeeding.
## Fix Focus Areas
- git-shed[71-92]
## Suggested fix (high level)
- Replace `git worktree list` parsing with `git worktree list --porcelain` and extract `worktree` entries whose `branch` matches `refs/heads/$branch`.
- Support multiple matches by iterating (e.g., `while IFS= read -r wt; do ...; done`).
- Keep paths intact (no whitespace splitting); avoid `awk '{print $1}'`/`cut -d' '`.
Example approach:
```bash
worktrees=$(git worktree list --porcelain | awk -v br="refs/heads/$branch" '
$1=="worktree" { wt=$2 }
$1=="branch" && $2==br { print wt }
')
while IFS= read -r wt; do
[[ -z "$wt" ]] && continue
... git worktree remove ...
done <<< "$worktrees"
```
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Summary
git-shedfailed when listing merged branches checked out in another worktree (+prefix): the delete loop triedgit branch -d '+'. It also failed when deleting a merged branch that was still the active ref in a linked worktree. Stale upstream parsing ingit branch -vvmis-read+lines the same way.This updates
git-shedto resolve merged names withgit for-each-ref, treat+like*for stale scans, remove linked worktrees beforegit branch -d/-D(retrying with--force --forcewhen locked), and continue on per-branch failures.Test plan
make test SCRIPTS=git-shed PARALLEL=false