Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions cli/bash/commands/basectl/subcommands/gh_branch_worktree.sh
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,13 @@ base_gh_worktree_dirty() {
[[ -n "$(git -C "$path" status --porcelain --ignore-submodules=none)" ]]
}

base_gh_worktree_is_valid() {
local path="$1"

[[ -f "$path/.git" ]] || return 1
git -C "$path" rev-parse --is-inside-work-tree >/dev/null 2>&1
}

base_gh_worktree_prune_delete_branch() {
local branch="$1"
local merge_source="$2"
Expand Down Expand Up @@ -937,6 +944,12 @@ base_gh_worktree_prune() {
skipped_current=$((skipped_current + 1))
continue
fi
if ! base_gh_worktree_is_valid "$path"; then
printf 'FAIL %s (%s) invalid linked-worktree metadata; path and branch retained; inspect the path, then run git worktree prune for stale metadata\n' \
"$path" "$branch"
failed=$((failed + 1))
continue
fi
if base_gh_worktree_dirty "$path"; then
printf 'SKIP %s (%s) dirty worktree\n' "$path" "$branch"
skipped_dirty=$((skipped_dirty + 1))
Expand Down
42 changes: 42 additions & 0 deletions cli/bash/commands/basectl/tests/gh-branch-worktree.bats
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,48 @@ EOF
! git -C "$repo" show-ref --verify --quiet refs/heads/merged-work
}

@test "basectl gh worktree prune retains invalid linked worktree metadata without raw git fatal" {
local repo invalid_worktree

repo="$TEST_TMPDIR/repo"
invalid_worktree="$TEST_TMPDIR/invalid-worktree"
init_git_repo "$repo"
printf 'hello\n' > "$repo/README.md"
commit_all "$repo" "Initial commit"
git -C "$repo" branch merged-work
mkdir -p "$invalid_worktree"
printf 'orphaned checkout\n' > "$invalid_worktree/README.md"

run env \
HOME="$TEST_HOME" \
BASE_HOME="$BASE_REPO_ROOT" \
INVALID_WORKTREE="$invalid_worktree" \
bash -c '
cd "$1"
source "$BASE_HOME/base_init.sh"
source "$BASE_HOME/cli/bash/commands/basectl/subcommands/gh.sh"
base_gh_list_worktree_branches() {
printf "%s\\t%s\\n" "$INVALID_WORKTREE" "merged-work"
}
base_gh_branch_cleanup_state() {
printf -v "$3" "%s" merged
printf -v "$4" "%s" github
printf -v "$5" "%s" ""
printf -v "$6" "%s" ""
printf -v "$7" "%s" 0
return 0
}
base_gh_subcommand_main worktree prune --yes
' bash "$repo"

[ "$status" -eq 1 ]
[[ "$output" == *"invalid linked-worktree metadata; path and branch retained"* ]]
[[ "$output" != *"fatal: not a git repository"* ]]
[[ "$output" == *"Summary: 0 removed, 0 skipped current/default, 0 skipped dirty, 0 skipped unmerged, 1 failed."* ]]
[ -d "$invalid_worktree" ]
git -C "$repo" show-ref --verify --quiet refs/heads/merged-work
}

@test "basectl gh worktree prune reports retained branches after deletion failure" {
local repo worktree

Expand Down
2 changes: 1 addition & 1 deletion docs/command-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ daily project loop commands from the local checkout.
| `basectl gh pr create/status/checks/ready/merge` | Create and manage pull requests through Base's workflow wrapper. `pr create` rejects a noncanonical current branch before invoking GitHub, auto-injects `Fixes #<issue>` unless `--no-fixes` is passed, and uses `github.pr` from `base_manifest.yaml` when present. | passes through `gh` options; `pr create` also accepts `--no-fixes` |
| `basectl gh branch stale` | Report stale local branches. Stable inspection JSON uses the shared v1 envelope. | `--days <days>`, `--format <text\|json>` |
| `basectl gh branch prune` | Prune safe merged branches. Pass `--closed-unmerged` to include branches whose pull requests were closed without merging. | `--dry-run`, `--yes`, `--remote`, `--closed-unmerged` |
| `basectl gh worktree prune` | Prune stale merged worktrees. Pass `--closed-unmerged` to include clean worktrees tied to closed, unmerged pull requests. | `--dry-run`, `--yes`, `--closed-unmerged` |
| `basectl gh worktree prune` | Prune stale merged branch-attached worktrees. Pass `--closed-unmerged` to include clean worktrees tied to closed, unmerged pull requests; detached worktree metadata remains an explicit `git worktree prune` operation. | `--dry-run`, `--yes`, `--closed-unmerged` |
| `basectl gh project doctor` | Inspect GitHub Project metadata against the Base Project schema. | `--project <title>`, `--owner <login>`, `--schema base-project` |
| `basectl gh project configure` | Create or repair Base-managed Project metadata. | `--project <title>`, `--owner <login>`, `--repo <owner/name>`, `--schema base-project`, `--config <path>`, `--copy-fields-from <title>`, `--replace-project`, `--initiative-option <name>`, `--dry-run` |
| `basectl gh project issue set-fields <number>` | Add an issue to the Project if needed and update metadata fields. The target Project must be linked to the issue repository unless the intentional cross-repository exception is enabled. | `--project <title>`, `--repo <owner/name>`, `--config <path>`, `--allow-cross-repo`, field options |
Expand Down
10 changes: 10 additions & 0 deletions docs/github-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,16 @@ worktree and local branch; the remote branch remains for
GitHub verification failures and rerun the preview before applying it with
`--yes`.

Worktree pruning only handles branch-attached worktrees with valid linked
worktree metadata. If a branch-attached path still exists but is no longer a
valid Git worktree, Base retains the path and local branch, reports a failure,
and does not delete the directory contents. Inspect or preserve anything needed
from that path, then use `git worktree prune --dry-run -v` followed by
`git worktree prune -v` to remove stale Git administrative records. Git's prune
only removes stale worktree metadata; it does not delete an orphaned directory.
Detached worktrees are intentionally outside Base's branch/PR cleanup boundary
and should be handled through the explicit Git command after review.

If a worktree is removed but its local branch cannot be deleted, the command
retains the branch, reports the incomplete cleanup, and exits nonzero. Resolve
the branch condition before treating the cleanup as complete.
Expand Down
Loading