From 28c9b265376c130d876bc56216509138a5fa4494 Mon Sep 17 00:00:00 2001 From: zerone0x Date: Fri, 27 Mar 2026 00:09:13 +0800 Subject: [PATCH] feat: delete local tracking branches when deleting environments When `container-use delete` removes an environment, it now also cleans up any local branches in the user's repo that were tracking the environment's remote branch (e.g., `cu-` created by checkout). Closes #335 Co-Authored-By: Claude Opus 4.6 --- repository/git.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/repository/git.go b/repository/git.go index 55dacfbb..52f77956 100644 --- a/repository/git.go +++ b/repository/git.go @@ -113,9 +113,62 @@ func (r *Repository) deleteLocalRemoteBranch(id string) error { return err } + // Delete any local branches in the user repo that were tracking this environment's remote branch. + // The checkout command creates local branches (default: cu-) that track container-use/. + r.deleteUserTrackingBranches(id) + return nil } +// deleteUserTrackingBranches finds and deletes local branches in the user repo +// that were tracking the container-use remote branch for the given environment. +func (r *Repository) deleteUserTrackingBranches(id string) { + remoteRef := fmt.Sprintf("%s/%s", containerUseRemote, id) + + // List all local branches with their upstream tracking info + output, err := RunGitCommand(context.Background(), r.userRepoPath, "for-each-ref", + "--format=%(refname:short) %(upstream:short)", "refs/heads/") + if err != nil { + slog.Warn("Failed to list local branches for cleanup", "err", err) + return + } + + for line := range strings.SplitSeq(strings.TrimSpace(output), "\n") { + line = strings.TrimSpace(line) + if line == "" { + continue + } + + parts := strings.SplitN(line, " ", 2) + if len(parts) != 2 { + continue + } + + branchName := parts[0] + upstream := parts[1] + + if upstream == remoteRef { + // Check if the user is currently on this branch + currentBranch, err := RunGitCommand(context.Background(), r.userRepoPath, "branch", "--show-current") + if err == nil && strings.TrimSpace(currentBranch) == branchName { + slog.Warn("Cannot delete branch because it is currently checked out", + "branch", branchName, "environment", id) + fmt.Printf("Warning: branch '%s' is currently checked out and was not deleted. Switch to another branch first.\n", branchName) + continue + } + + slog.Info("Deleting local tracking branch", "branch", branchName, "environment", id) + if _, err := RunGitCommand(context.Background(), r.userRepoPath, "branch", "-D", branchName); err != nil { + slog.Warn("Failed to delete local tracking branch", + "branch", branchName, "environment", id, "err", err) + fmt.Printf("Warning: failed to delete local branch '%s': %v\n", branchName, err) + } else { + fmt.Printf("Deleted local branch '%s'.\n", branchName) + } + } + } +} + // initializeWorktree initializes a new worktree for environment creation. // It pushes the specified gitRef to create a new branch with the given id, then creates a worktree from that branch. // Returns the worktree path, any submodule warning, and an error.