Skip to content

fix(git-shed): harden merged branch cleanup#75

Open
michen00 wants to merge 1 commit into
mainfrom
fix/git-shed-hardening
Open

fix(git-shed): harden merged branch cleanup#75
michen00 wants to merge 1 commit into
mainfrom
fix/git-shed-hardening

Conversation

@michen00
Copy link
Copy Markdown
Owner

Summary

git-shed failed when listing merged branches checked out in another worktree (+ prefix): the delete loop tried git branch -d '+'. It also failed when deleting a merged branch that was still the active ref in a linked worktree. Stale upstream parsing in git branch -vv mis-read + lines the same way.

This updates git-shed to resolve merged names with git for-each-ref, treat + like * for stale scans, remove linked worktrees before git branch -d / -D (retrying with --force --force when locked), and continue on per-branch failures.

Test plan

  • make test SCRIPTS=git-shed PARALLEL=false

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.
@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Harden git-shed merged branch cleanup and worktree handling

🐞 Bug fix ✨ Enhancement

Grey Divider

Walkthroughs

Description
• 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
Diagram
flowchart 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"]
Loading

Grey Divider

File Changes

1. git-shed 🐞 Bug fix +52/-19

Robust branch cleanup with worktree and error handling

• Replace git branch --merged with git for-each-ref to extract clean branch names without line
 prefixes
• Add remove_linked_worktree_for_branch() function to remove worktrees before branch deletion
• Update stale branch detection to treat + prefix (linked worktree) same as * prefix (current
 branch)
• Implement error handling to skip branches on deletion failure instead of aborting entire operation
• Update documentation to mention linked worktree removal during merged branch cleanup

git-shed


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented May 15, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0)

Grey Divider


Action required

1. Worktree path parsing broken 🐞 Bug ☼ Reliability
Description
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.
Code

git-shed[R75-90]

+	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
Evidence
The script derives the worktree path by taking the first whitespace-delimited token from `git
worktree list output and then passes that value as the sole path argument to git worktree remove`;
this directly couples whitespace-sensitive parsing to a destructive filesystem operation.

git-shed[71-92]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## 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


Grey Divider

Qodo Logo

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread git-shed
Comment on lines +76 to +78
worktree=$(
git worktree list | grep -F "[$branch]" | awk '{print $1}' || true
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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 }
		'
	)

Comment thread git-shed
Comment on lines +137 to +138
git branch -vv |
awk '/: gone]/ { print ($1 == "*" || $1 == "+" ? $2 : $1) }'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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) }'

Comment thread git-shed
Comment on lines +75 to +90
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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant