Fix clean path parsing, cwd guard, and remove/branch error handling#9
Conversation
Paths with spaces were truncated by awk $2; --older-than could delete the active worktree via string equality; remove failures were misreported as unmerged so branch -D was wrongly suggested. Co-authored-by: Cursor <cursoragent@cursor.com>
📝 WalkthroughWalkthrough
ChangesWorktree cleanup
Estimated code review effort: 3 (Moderate) | ~20 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@git-trees`:
- Around line 443-447: Update the stale-worktree cleanup loop around git
worktree remove "$wt" to capture removal failures, report the affected worktree,
and preserve a non-zero status across iterations. Replace the unconditional
final success return with the tracked cleanup status so any failed removal
causes the function to return non-zero.
- Around line 388-395: Update both branch-delete cleanup sites in git-trees
(lines 388-395 and 413-420) to capture the actual git branch -d error instead of
suppressing it. Report the captured error for general failures, and suggest git
branch -D only when the error indicates an unmerged tip; preserve the existing
cleanup flow for successful deletions and worktree-removal failures.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
| if [ -n "$p" ]; then | ||
| if ! git worktree remove "$p"; then | ||
| echo " ! worktree remove failed — skipped branch delete for $br" >&2 | ||
| continue | ||
| fi | ||
| fi | ||
| git branch -d "$br" 2>/dev/null \ | ||
| || echo " ! unmerged — use: git branch -D $br" |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | grep -E '(^|/)git-trees$|(^|/)git-trees' || true
echo "== relevant git-trees lines =="
if [ -f git-trees ]; then
cat -n git-trees | sed -n '360,430p'
fi
echo "== search branch error wording =="
rg -n "unmerged|use: git branch -D|branch -d|branch -D" git-trees || true
echo "== git branch error strings in working git? =="
if command -V git >/dev/null; then
tmp="$(mktemp -d)"
cd "$tmp"
git init -q
git checkout -b test1 >/dev/null 2>&1 || true
git branch -d test2 2>&1 >/tmp/git-output
git branch -d test1 2>&1 | sed 's/Warning: /Warning: /' > /tmp/git-output2
cat /tmp/git-output
echo "---"
cat /tmp/git-output2
fiRepository: brightdigit/git-trees
Length of output: 3522
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate git-trees source =="
git ls-files | grep -E '(^|/)git-trees$|(^|/)git-trees[' || true
echo "== git version availability =="
git version 2>/dev/null || true
echo "== deterministic git branch -d failure modes =="
tmp="$(mktemp -d)"
cd "$tmp"
git init -q
git config user.email test@example.com
git config user.name Test
git commit --allow-empty -m base >/dev/null
git checkout -b main-branch >/dev/null
git branch -d nonexistent 2>&1 | sed 's#<path:[[:alnum:]]*/##'"# <path>/"
git branch -d main-branch 2>&1 | sed 's#<path:[[:alnum:]]*/##'"# <path>/"
(cd "$tmp" && git branch -d main-branch)Repository: brightdigit/git-trees
Length of output: 460
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmp="$(mktemp -d)"
cd "$tmp"
git init -q
git config user.email test@example.com
git config user.name Test
git commit --allow-empty -m base >/dev/null
git checkout -b main >/dev/null
git branch feature
echo "== missing branch: =="
git branch -d missing 2>&1
echo "== unmerged branch: =="
git branch -d feature 2>&1
echo "== branch -D missing: =="
git branch -D missing 2>&1
echo "== branch -d exit status, unmerged: =="
(set -e; exit 0) 2>/dev/null || true
set +e
git branch -d feature >/tmp/branch-output 2>&1
status=$?
set -e
echo "status=$status"
cat /tmp/branch-outputRepository: brightdigit/git-trees
Length of output: 310
🏁 Script executed:
#!/bin/bash
set -u
tmp="$(mktemp -d)"
cd "$tmp"
git init -q
git config user.email test@example.com
git config user.name Test
git commit --allow-empty -m base >/tmp/commit.log 2>&1 || true
git checkout -b main >/tmp/checkout.log 2>&1 || true
git branch feature >/tmp/branch.log 2>&1 || true
echo "repo files:"
git ls-files
echo "== missing branch =="
if git branch -d missing 2>/tmp/branch-missing.log >/tmp/branch-missing.out; then
echo "missing status=0 unexpected"
else
echo "missing status=$?"
cat /tmp/branch-missing.out /tmp/branch-missing.log
fi
echo "== unmerged branch =="
if git branch -d feature 2>/tmp/branch-unmerged.log >/tmp/branch-unmerged.out; then
echo "unmerged status=0 unexpected"
else
echo "unmerged status=$?"
cat /tmp/branch-unmerged.out /tmp/branch-unmerged.log
fi
echo "== branch -D missing =="
if git branch -D missing 2>/tmp/branch-d-missing.log >/tmp/branch-d-missing.out; then
echo "D status=0 unexpected"
else
echo "D status=$?"
cat /tmp/branch-d-missing.out /tmp/branch-d-missing.log
fiRepository: brightdigit/git-trees
Length of output: 427
🏁 Script executed:
#!/bin/bash
set -u
tmp="$(mktemp -d)"
cd "$tmp"
git init -q
git config user.email test@example.com
git config user.name Test
git commit --allow-empty -m base >/tmp/commit.log 2>&1 || true
git checkout -b main >/tmp/checkout.log 2>&1 || true
git branch feature >/tmp/branch.log 2>&1 || true
echo "== repo files =="
git ls-files
echo "== status of unmerged delete =="
(git branch -d feature >/tmp/branch-unmerged.out 2>/tmp/branch-unmerged.log)
status=$?
echo "unmerged status=$status"
cat /tmp/branch-unmerged.out /tmp/branch-unmerged.log
echo "== status of non-existent delete =="
(git branch -d missing >/tmp/branch-missing.out 2>/tmp/branch-missing.log)
status=$?
echo "missing status=$status"
cat /tmp/branch-missing.out /tmp/branch-missing.log
echo "== status of current branch delete =="
(git branch -d main >/tmp/branch-current.out 2>/tmp/branch-current.log)
status=$?
echo "current status=$status"
cat /tmp/branch-current.out /tmp/branch-current.logRepository: brightdigit/git-trees
Length of output: 541
🌐 Web query:
git branch -d unmerged tip error message "did not merge" "not fully merged"
💡 Result:
The error message "The branch is not fully merged" occurs when you attempt to delete a local Git branch using the command git branch -d <branch_name> while that branch contains commits that Git cannot find in your current checked-out branch [1][2]. Git uses the -d (lowercase) flag as a safety mechanism to prevent you from accidentally deleting work that has not been integrated into your current branch [1][3]. Why this happens: 1. True Unmerged Work: You may genuinely have new commits on the branch that have not been merged [4][2]. 2. Squash Merges: If you merged your branch using a "squash" strategy on a platform like GitHub, GitLab, or Bitbucket, Git may not recognize the branch as "merged" locally because the commit hashes on the target branch are different from the ones on your local branch [5][6]. 3. Outdated Local History: If you merged a pull request on a remote server but have not pulled those changes into your local repository, your local Git still views the branch as having unique, unmerged commits [7]. How to resolve it: If you are confident that the branch's work is already integrated elsewhere (or if you simply want to discard the unmerged work), you can force the deletion by using the capital -D flag [1][8]: git branch -D <branch_name> Alternatively, you can switch to the target branch (e.g., main or master) and perform a git pull to ensure your local history is up to date before attempting the standard delete again [7]. To identify which commits are causing the "not merged" status, you can run: git log <branch_name> --not <main_branch_name> [4]
Citations:
- 1: https://stackoverflow.com/questions/7548926/git-error-the-branch-x-is-not-fully-merged
- 2: https://www.cloudbees.com/blog/git-delete-branch-how-to-for-both-local-and-remote
- 3: https://refine.dev/blog/git-delete-remote-branch-and-local-branch/
- 4: https://brianchildress.co/git-branch-not-fully-merged/
- 5: https://forum.gitlab.com/t/the-branch-is-not-fully-merged/37032
- 6: https://medium.com/bananatag-engineering-blog/squash-merges-are-evil-171f55139c51
- 7: https://superuser.com/questions/218551/git-branch-not-fully-merged
- 8: https://superuser.com/questions/1421057/delete-an-unmerged-branch-in-git
🏁 Script executed:
#!/bin/bash
set -u
tmp="$(mktemp -d)"
cd "$tmp"
git init -q
git config user.email test@example.com
git config user.name Test
git commit --allow-empty -m base >/tmp/commit.log 2>&1 || true
git checkout -b main >/tmp/checkout.log 2>&1 || true
echo "== unmergeable branch delete in fresh repo on current branch =="
(git branch feature 2>/tmp/branch-create-old.log) 2>&1
(git branch -d feature >/tmp/branch-create-old.out 2>/tmp/branch-create-old.log)
old_status=$?
echo "old unmergeable status=$old_status"
cat /tmp/branch-create-old.out /tmp/branch-create-old.log
git checkout -b branch1 >/tmp/checkout.log 2>&1
git branch feature2 >/tmp/branch.log 2>&1
echo "== unmergeable branch delete with feature2 not merged into branch1 =="
(git branch -d feature2 >/tmp/branch-unmerged.out 2>/tmp/branch-unmerged.log)
status=$?
echo "unmergeable status=$status"
cat /tmp/branch-unmerged.out /tmp/branch-unmerged.log
echo "== current checked-out branch delete from branch3 with feature2 not merged =="
git checkout -b branch3 >/tmp/checkout.log 2>&1
(git branch -d feature2 >/tmp/branch-unmerged-from-other.out 2>/tmp/branch-unmerged-from-other.log)
status=$?
echo "unmergeable from branch3 status=$status"
cat /tmp/branch-unmerged-from-other.out /tmp/branch-unmerged-from-other.log
echo "== checked-out branch delete from branch3 with feature2 merged into branch3 =="
(git branch -d branch3 >/tmp/branch-current-from-other.out 2>/tmp/branch-current-from-other.log)
status=$?
echo "checkout branch from branch3 status=$status"
cat /tmp/branch-current-from-other.out /tmp/branch-current-from-other.logRepository: brightdigit/git-trees
Length of output: 818
🏁 Script executed:
#!/bin/bash
set -u
tmp="$(mktemp -d)"
cd "$tmp"
git init -q
git config user.email test@example.com
git config user.name Test
git checkout -b main >/tmp/checkout.log 2>&1 || true
git commit --allow-empty -m base >/tmp/commit.log 2>&1 || true
git checkout -b b1 >/tmp/checkout.log 2>&1 || true
git commit --allow-empty -m b1 >/tmp/commit.log 2>&1 || true
git checkout -b b2 >/tmp/checkout.log 2>&1 || true
git commit --allow-empty -m b2 >/tmp/commit.log 2>&1 || true
echo "== b2 with unique commits not merged into b1 =="
(git branch -d b2 >/tmp/merge-status.out 2>/tmp/merge-status.log)
status=$?
echo "status=$status"
cat /tmp/merge-status.out /tmp/merge-status.log
echo "== b2 already merged into b1 =="
git checkout b1 >/tmp/checkout.log 2>&1 || true
git merge --no-ff b2 >/tmp/merge.log 2>&1 || true
(git branch -d b2 >/tmp/merge-found.out 2>/tmp/merge-found.log)
status=$?
echo "status=$status"
cat /tmp/merge-found.out /tmp/merge-found.log
echo "== b1 merged directly into main and tried from b2 =="
git checkout main >/tmp/checkout.log 2>&1 || true
git merge --no-ff b1 >/tmp/merge.log 2>&1 || true
(git branch -d b1 >/tmp/m2-found.out 2>/tmp/m2-found.log)
status=$?
echo "status=$status"
cat /tmp/m2-found.out /tmp/m2-found.logRepository: brightdigit/git-trees
Length of output: 517
🌐 Web query:
git branch -d unmerged commits fatal error unmerged tip error output
💡 Result:
When you attempt to delete a local branch using git branch -d, Git checks whether the branch's tip commit is an ancestor of the current HEAD or its upstream branch [1][2]. If it is not, Git prevents the deletion with the error: error: The branch '...' is not fully merged [3][2]. This message is a safety feature designed to prevent the accidental loss of commits that have not been incorporated into your current work or upstream tracking branch [1][2]. Common scenarios where this occurs include: 1. Squash merges: If you merge a pull request using a "squash" strategy on a platform like GitHub or GitLab, the resulting commit on the target branch has a new hash and does not contain the individual commits from your local branch [4][5]. Because your local history does not match the upstream history, Git considers the branch "unmerged" [5]. 2. Outdated local branches: You may have already merged the work into the remote repository, but your local repository is unaware of these updates [4]. Running git fetch or git pull may resolve this if it brings your local tracking branch up to date with the remote [4]. 3. Multiple branches: Git only checks the current HEAD and the configured upstream. If you merged the branch into a different local branch, Git will not automatically see that, and the error will persist [2]. To resolve this, you have two primary options: - Confirm you have safely integrated your changes: If you are certain the work is captured elsewhere, you can force the deletion using the uppercase -D flag [6][2]: git branch -D - Investigate unmerged work: If you want to verify what has not been merged, you can list the commits with [7]: git log --not Note: If the branch is currently checked out, you must switch to a different branch before deleting it, or you will receive a different error message indicating the branch is in use [3][8].
Citations:
- 1: https://stackoverflow.com/questions/71887748/why-does-git-allow-deletion-of-a-remote-branch-with-unmerged-changes-but-not-a-l
- 2: https://stackoverflow.com/questions/7548926/git-error-the-branch-x-is-not-fully-merged
- 3: https://forum.freecodecamp.org/t/im-getting-an-error-when-i-tried-to-delete-my-branch/490574
- 4: https://superuser.com/questions/218551/git-branch-not-fully-merged
- 5: https://forum.gitlab.com/t/the-branch-is-not-fully-merged/37032
- 6: https://superuser.com/questions/1421057/delete-an-unmerged-branch-in-git
- 7: https://brianchildress.co/git-branch-not-fully-merged/
- 8: https://www.spinics.net/lists/git/msg475648.html
Make branch-delete error handling reason-aware.
Both cleanup passes suppress git branch -d errors and unconditionally tell users the branch is unmerged, but branch -d can also fail for missing checked-out, or otherwise unprotected branches. Capture the real error and only suggest git branch -D when it is the unmerged-tip failure.
📍 Affects 1 file
git-trees#L388-L395(this comment)git-trees#L413-L420
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@git-trees` around lines 388 - 395, Update both branch-delete cleanup sites in
git-trees (lines 388-395 and 413-420) to capture the actual git branch -d error
instead of suppressing it. Report the captured error for general failures, and
suggest git branch -D only when the error indicates an unmerged tip; preserve
the existing cleanup flow for successful deletions and worktree-removal
failures.
| if [ -n "$(find "$wt" -maxdepth 0 -mtime +"$days" 2>/dev/null)" ]; then | ||
| echo " $wt" | ||
| [ "$apply" -eq 1 ] && git worktree remove "$wt" | ||
| fi | ||
| done < <(git worktree list --porcelain | awk '/^worktree /{print $2}') | ||
| done < <(git worktree list --porcelain | awk '/^worktree /{sub(/^worktree /, ""); print}') |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Propagate stale-worktree removal failures.
If git worktree remove "$wt" fails, the loop continues and the function’s unconditional final return 0 reports success. Track the failure, report it, and return non-zero so automation cannot mistake an incomplete cleanup for success.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@git-trees` around lines 443 - 447, Update the stale-worktree cleanup loop
around git worktree remove "$wt" to capture removal failures, report the
affected worktree, and preserve a non-zero status across iterations. Replace the
unconditional final success return with the tracked cleanup status so any failed
removal causes the function to return non-zero.
Summary
worktreeprefix so spaces are preserved in list/clean/path lookup--older-than --applynever removes the tree you are in (or under)git branch -Dfor a true unmerged tip after the worktree is goneFixes #3
Fixes #2
Fixes #6
Test plan
bash -n git-treesgit trees list --jsonshows full pathsgit trees clean --older-than 1 --applyskips that worktreegit branch -DMade with Cursor
Summary by CodeRabbit