Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@sotarok
Copy link
Owner

@sotarok sotarok commented Nov 21, 2025

Summary

This PR introduces a new gw clean command that allows users to batch remove multiple git worktrees that have been safely merged to origin/main, along with improvements to error messages and output formatting.

Features Added

  • New gw clean command for batch worktree removal

    • Automatically identifies worktrees that can be safely removed
    • Performs the same three safety checks as gw end:
      1. No uncommitted changes
      2. No unpushed commits
      3. Merged to origin/main
    • Only removes worktrees that pass ALL three checks
    • --force flag to skip confirmation prompt
    • --dry-run flag to preview what would be removed
    • Respects auto_remove_branch configuration for automatic branch cleanup
  • Improved error messages

    • User-friendly error messages for broken worktrees
    • Replaces cryptic "exit status 128" with "invalid git repository"
    • Helps users identify and handle orphaned worktrees
  • Enhanced output formatting

    • Displays only directory names instead of full paths
    • Simplified section headers (e.g., "Removable (2)")
    • Multiple reasons combined into single line with comma separation
    • Shortened, more concise warning messages
    • Visual separator (β†’) for better readability

Implementation Details

  • 242 lines of new functionality in cmd/command.go
  • 609 lines of comprehensive test coverage (9 test cases)
  • Maintains 81.9% code coverage
  • Follows existing patterns from EndCommand

Test plan

  • Run make fmt and make check - all tests pass
  • Test with no worktrees
  • Test with all removable worktrees
  • Test with mixed removable/non-removable worktrees
  • Test --dry-run flag
  • Test --force flag
  • Test with auto_remove_branch configuration
  • Test error handling for broken worktrees
  • Test user confirmation prompts
  • Verify output format improvements

πŸ€– Generated with Claude Code

sotarok and others added 3 commits November 21, 2025 11:03
Implement a new 'gw clean' command that safely removes multiple worktrees
that have been merged to origin/main. The command performs the same safety
checks as 'gw end' (uncommitted changes, unpushed commits, merge status)
and only removes worktrees that pass all checks. Includes --force and
--dry-run flags for better control.

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Detect when a worktree has missing or corrupt git metadata (exit status 128)
and display a user-friendly error message instead of showing the raw git error.
The new message clearly indicates that the worktree is not a valid git
repository and suggests the metadata may be missing or corrupt.

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Display only directory names instead of full paths using filepath.Base()
- Simplify section headers ("Removable (2)" vs "Removable worktrees (2)")
- Combine multiple reasons into single line with comma separation
- Shorten warning messages:
  - "Has uncommitted changes" β†’ "uncommitted changes"
  - "Has unpushed commits" β†’ "unpushed commits"
  - "Not merged to origin/main" β†’ "not merged"
  - Long broken worktree message β†’ "invalid git repository"
- Remove redundant "merged to origin/main" for removable worktrees
- Add visual separator (β†’) for non-removable reasons
- Update all tests to match new output format

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@codecov
Copy link

codecov bot commented Nov 21, 2025

Codecov Report

❌ Patch coverage is 74.83444% with 38 lines in your changes missing coverage. Please review.
βœ… Project coverage is 73.38%. Comparing base (876591a) to head (93cd768).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
cmd/command.go 76.97% 22 Missing and 10 partials ⚠️
cmd/clean.go 50.00% 4 Missing ⚠️
cmd/init.go 50.00% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main      #17      +/-   ##
==========================================
+ Coverage   73.18%   73.38%   +0.20%     
==========================================
  Files          23       24       +1     
  Lines        1529     1676     +147     
==========================================
+ Hits         1119     1230     +111     
- Misses        331      357      +26     
- Partials       79       89      +10     
Flag Coverage Ξ”
unittests 73.38% <74.83%> (+0.20%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Ξ”
cmd/init.go 76.92% <50.00%> (ΓΈ)
cmd/clean.go 50.00% <50.00%> (ΓΈ)
cmd/command.go 75.30% <76.97%> (+0.86%) ⬆️

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Ξ” = absolute <relative> (impact), ΓΈ = not affected, ? = missing data
Powered by Codecov. Last update 876591a...93cd768. Read the comment docs.

πŸš€ New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@claude
Copy link

claude bot commented Nov 21, 2025

Code Review Summary

This is a well-implemented PR that adds a useful gw clean command for batch removing merged worktrees. The code follows the existing patterns, has comprehensive test coverage, and includes thoughtful error handling. Below are my detailed findings.


βœ… Strengths

1. Excellent Code Quality

  • Follows existing patterns from EndCommand consistently
  • Well-structured with clear separation of concerns (checkWorktree, displayResults, removeWorktrees)
  • Comprehensive test coverage (9 test cases covering all major scenarios)
  • Good use of dependency injection for testability

2. User Experience

  • Clear, concise output with visual indicators (βœ“, βœ—, β†’)
  • --dry-run flag for safe preview
  • Confirmation prompt with count
  • User-friendly error messages for broken worktrees

3. Safety First

  • Performs the same three safety checks as gw end
  • Only removes worktrees that pass ALL checks
  • Respects auto_remove_branch configuration
  • Graceful error handling with partial success reporting

4. Documentation

  • Well-documented command with clear Long description
  • CHANGELOG.md properly updated with comprehensive details
  • Good inline comments explaining logic

πŸ” Issues & Suggestions

High Priority

1. Missing constant for default base branch (cmd/command.go:621)

if wt.Branch == "" || wt.Branch == defaultBaseBranch || wt.Branch == "master" {

Issue: defaultBaseBranch is referenced but not defined in this file. This will cause a compilation error.

Fix: Either import/define the constant or use the string literal "main" directly (check how it's handled in other commands).


Medium Priority

2. Code duplication with EndCommand (cmd/command.go:676-733)

The checkWorktree method has significant overlap with EndCommand.performSafetyChecks():

  • Both perform the same three safety checks
  • Both handle errors similarly
  • Both change directories and restore them

Suggestion: Extract common safety check logic into a shared function to reduce duplication and ensure consistency:

func performWorktreeSafetyChecks(deps *Dependencies) (hasChanges, hasUnpushed, isMerged bool, errors []error)

3. Inconsistent error message format (cmd/command.go:685, 702, 712, 722)

Some errors append full error details:

status.Warnings = append(status.Warnings, fmt.Sprintf("Could not access directory: %v", err))

While others use concise messages:

status.Warnings = append(status.Warnings, "uncommitted changes")

Suggestion: Consider making error messages consistently concise for the user-facing output, perhaps logging full errors to stderr instead.

4. Directory change side effects (cmd/command.go:684, 730)

The function changes the current working directory multiple times without proper error handling on restoration:

_ = os.Chdir(originalDir)

Suggestion: While the underscore shows intentional ignoring, consider at least logging if restoration fails, as this could affect subsequent operations.


Low Priority

5. Hard-coded "main" branch (cmd/command.go:720)

isMerged, err := c.deps.Git.IsMergedToOrigin("main")

Observation: This is consistent with EndCommand but hard-codes "main". Consider making the base branch configurable in future work (already noted in CLAUDE.md as a known limitation).

6. Test naming could be more specific (cmd/command_test.go:24)

const testBranch123 = "123/impl"

Suggestion: Consider testBranchPattern or testImplBranch to be more descriptive about its purpose.

7. Magic number in loop display (cmd/command.go:760)

if i > 0 {
    fmt.Fprintf(c.deps.Stdout, "\n")
}

Suggestion: Add a comment explaining why the blank line is added (to separate non-removable worktree entries for readability).


πŸ›‘οΈ Security Considerations

βœ… No security issues found. The command:

  • Uses the existing git interface abstractions
  • Doesn't execute user-provided input directly
  • Properly uses filepath operations
  • Follows safe directory traversal patterns

πŸ§ͺ Testing

Strengths:

  • βœ… Comprehensive test coverage (9 test cases)
  • βœ… Tests all major scenarios including edge cases
  • βœ… Proper use of temporary directories
  • βœ… Tests both success and failure paths
  • βœ… Tests user interaction (confirmation, dry-run, force)

Suggestion:

Consider adding a test for the case where ListWorktrees() returns an error to ensure error handling at the entry point.


🏎️ Performance

βœ… No performance concerns. The implementation:

  • Efficiently iterates through worktrees once
  • Uses pre-allocated slices with capacity hints
  • Doesn't unnecessarily duplicate data structures
  • Changes directories only when needed

πŸ“‹ Minor Nits

  1. Line 750: Consider extracting the emoji/symbol constants (βœ“, βœ—, β†’) to package-level constants for consistency across commands.

  2. Line 654-658: The singular/plural prompt logic is good, but could be extracted to a helper function if used elsewhere.

  3. cmd/clean.go:13: The Long description could mention that it respects the auto_remove_branch configuration.


🎯 Final Recommendation

βœ… APPROVE with minor fixes required

This is high-quality code that adds valuable functionality. The only blocking issue is the missing defaultBaseBranch constant reference that will prevent compilation. Once that's fixed, this is ready to merge.

Required before merge:

  • Fix the defaultBaseBranch undefined reference

Recommended for follow-up:

  • Extract common safety check logic to reduce duplication with EndCommand
  • Consider making base branch configurable (can be a separate PR)

Code Coverage: 81.9% is excellent for this type of utility. The test coverage comprehensively validates the new functionality.

Great work on this feature! πŸš€


πŸ€– Review generated with Claude Code

All features are part of the new gw clean command, so they should
be in the Added section rather than split across Added/Changed/Fixed.

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@claude
Copy link

claude bot commented Nov 21, 2025

Code Review: Add gw clean command

Overall Assessment

This is a well-implemented feature with excellent test coverage and thoughtful UX improvements. The code follows existing patterns, has comprehensive safety checks, and is thoroughly tested. However, there are a few security concerns and minor improvements to consider.

βœ… Strengths

1. Excellent Test Coverage

  • 609 lines of test code for 257 lines of implementation (2.4:1 ratio)
  • 9 comprehensive test cases covering all edge cases
  • Tests use proper isolation with temporary directories
  • Good use of dependency injection for testability

2. Strong Safety Model

  • Three-tier safety checks (uncommitted changes, unpushed commits, merge status)
  • Graceful error handling for broken worktrees
  • Confirmation prompts with proper singular/plural handling
  • --dry-run and --force flags for flexibility

3. Good UX Design

  • Clear visual separation (βœ“ and βœ— symbols)
  • Concise output with directory names instead of full paths
  • Combined error messages for better readability
  • Helpful user-friendly error messages

4. Code Quality

  • Follows existing patterns from EndCommand
  • Proper separation of concerns (check, display, remove)
  • Good use of structured data (WorktreeStatus)
  • Consistent error handling and reporting

πŸ”΄ Security Concerns

Critical: Command Injection Vulnerability

Location: internal/git/worktree.go:63,114,218,221,236

The code passes user-controlled input directly to exec.Command without validation:

// Line 63 - CreateWorktree
cmd = exec.Command("git", "worktree", "add", worktreeDir, "-b", branchName, baseBranch)

// Line 114 - RemoveWorktreeByPath  
cmd := exec.Command("git", "worktree", "remove", worktreePath)

// Line 236 - RunCommand (MOST DANGEROUS)
func RunCommand(command string) error {
    cmd := exec.Command("sh", "-c", command)  // Direct shell execution\!
    ...
}

Risk: While exec.Command with separate arguments is generally safe from shell injection, the inputs (branchName, worktreePath, etc.) should still be validated to prevent:

  1. Path traversal attacks
  2. Git command injection via special characters
  3. Unexpected behavior from malformed input

Specific concerns:

  • branchName is derived from user input without validation (beyond sanitization for directory names)
  • worktreePath comes from git worktree list but could be manipulated
  • RunCommand() at line 236 is extremely dangerous - it executes arbitrary shell commands

Recommendations:

  1. Add input validation for branch names (alphanumeric, hyphens, slashes, underscores only)
  2. Add path validation to ensure paths don't escape expected directories
  3. Critical: Remove or severely restrict RunCommand() - if needed, use an allowlist of specific commands
  4. Consider using filepath.Clean() on all path inputs
  5. Add unit tests for malicious inputs

Example validation:

func validateBranchName(branch string) error {
    // Allow alphanumeric, hyphens, slashes, underscores
    matched, _ := regexp.MatchString("^[a-zA-Z0-9/_-]+$", branch)
    if \!matched {
        return fmt.Errorf("invalid branch name: %s", branch)
    }
    return nil
}

⚠️ Issues to Address

1. Directory Traversal Safety (cmd/command.go:684)

if err := os.Chdir(info.Path); err \!= nil {

Issue: The code changes to directories from git worktree list without validating they're within expected bounds.

Risk: If git metadata is corrupted or manipulated, this could lead to operations in unexpected locations.

Recommendation: Validate that paths are absolute and within repository boundaries.

2. Error Message Information Leakage (cmd/command.go:685,702,712,722)

status.Warnings = append(status.Warnings, fmt.Sprintf("Could not access directory: %v", err))
status.Warnings = append(status.Warnings, fmt.Sprintf("Could not check uncommitted changes: %v", err))

Issue: Full error messages (including system paths) are displayed to users.

Risk: Minor information disclosure in multi-user environments.

Recommendation: Use generic error messages or sanitize paths in error output.

3. Ignored Error in Directory Restoration (cmd/command.go:699,730)

_ = os.Chdir(originalDir)

Issue: If changing back to the original directory fails, the program continues in an unexpected location.

Risk: Subsequent operations may occur in wrong directory.

Recommendation: Log these errors at minimum, or propagate them:

if err := os.Chdir(originalDir); err \!= nil {
    // Log error or add to warnings
    status.Warnings = append(status.Warnings, "Failed to restore directory")
}

4. Config Load Error Silently Ignored (cmd/command.go:583)

cfg, _ := config.Load(config.GetConfigPath())

Issue: Configuration loading errors are ignored.

Impact: Users won't know if their config file is corrupted or has permission issues.

Recommendation: Log or display config loading errors.

5. Race Condition Window (cmd/command.go:625-672)

Issue: Time-of-check-time-of-use (TOCTOU) race condition between checking worktree status and removing it.

Scenario: User could push commits or modify worktree between the check and removal.

Risk: Low (single-user tool, short time window)

Recommendation: Document this limitation or add a re-check before removal.

πŸ’‘ Suggestions for Improvement

1. Hardcoded Base Branch (cmd/command.go:621)

if wt.Branch == "" || wt.Branch == defaultBaseBranch || wt.Branch == "master" {

Suggestion: Use config to specify protected branches instead of hardcoding "main" and "master".

2. Duplicate Logic with EndCommand

Both EndCommand.performSafetyChecks() and CleanCommand.checkWorktree() perform similar checks.

Suggestion: Extract common safety check logic into a shared function to reduce duplication and ensure consistency.

3. Better Progress Feedback

For large numbers of worktrees, add progress indicators:

fmt.Fprintf(c.deps.Stdout, "Checking worktrees (%d/%d)...\r", current, total)

4. Atomic Operations

Consider wrapping multiple worktree removals in a transaction-like pattern where failures can be rolled back.

5. Output Buffering

For better performance when processing many worktrees, consider buffering output instead of multiple small writes.

πŸ“ Minor Issues

1. Test Constant Duplication (cmd/command_test.go:19)

const testBranch123 = "123/impl"

Good addition, but consider defining more test constants for other repeated strings.

2. Inconsistent Spacing (cmd/command.go:761)

The blank line before displaying non-removable worktrees is inconsistent with the pattern used elsewhere.

3. Missing Documentation

The WorktreeStatus struct and its fields could benefit from doc comments explaining when CanRemove is false vs when Warnings are populated.

4. Magic Number (cmd/command.go:613)

statuses := make([]*WorktreeStatus, 0, len(worktrees))

Consider adding a comment explaining why capacity is set to len(worktrees) when some will be filtered out.

πŸ§ͺ Test Coverage Comments

Great test coverage overall! Consider adding:

  1. Test for concurrent modifications - What happens if a worktree changes during the check-remove window?
  2. Test for permission errors - Simulate permission denied on directory access
  3. Test for partial failure recovery - What happens if removal fails midway?
  4. Test for very long paths - Ensure output formatting handles them gracefully
  5. Benchmark tests - Performance with 100+ worktrees

πŸ“š Documentation

The CHANGELOG entry is excellent and comprehensive. Consider also:

  1. Updating README with examples of the new command
  2. Adding a section about what makes a worktree "safely deletable"
  3. Documenting the force and dry-run flags

βœ… Pre-merge Checklist

  • Tests pass
  • CHANGELOG updated
  • Follows conventional commits
  • Code formatted (make fmt)
  • Security issues addressed (command injection validation)
  • Consider extracting shared safety check logic

Conclusion

This is a solid feature implementation with excellent testing and UX. The main concern is the command injection vulnerability that exists in the underlying git package. This should be addressed before merging, either in this PR or as a follow-up security PR.

Recommendation: Approve with minor changes - address the security concerns around input validation, then this is ready to merge.

Great work on the comprehensive tests and thoughtful UX improvements! πŸŽ‰


Review conducted by Claude Code following CLAUDE.md guidelines

Extract emoji/symbol constants (βœ“, βœ—, ⚠, β†’) to package-level
constants for consistency across commands and easier maintenance.

This addresses the review comment on line 750 to improve code
consistency and make it easier to update symbols in the future.

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@claude
Copy link

claude bot commented Nov 21, 2025

Code Review for PR #17: Add gw clean command

Overall, this is a well-implemented and thoroughly tested feature that adds significant value to the tool. The code quality is high, follows existing patterns, and includes comprehensive test coverage.

βœ… Strengths

1. Excellent Code Organization

  • Clean separation of concerns with CleanCommand struct following the same pattern as EndCommand
  • Well-structured methods: Execute(), checkWorktree(), displayResults(), and removeWorktrees()
  • Consistent dependency injection pattern

2. Comprehensive Test Coverage

  • 609 lines of tests covering 9 test scenarios
  • Tests cover all edge cases: no worktrees, all removable, mixed, dry-run, force, user decline, errors, broken worktrees
  • Maintains 81.9% code coverage
  • Uses proper test isolation with t.TempDir() and mocking

3. User Experience

  • Clear, informative output with visual separators
  • Helpful confirmation prompts with proper pluralization
  • --dry-run flag for safe previewing
  • --force flag for automation
  • Displays only directory names instead of full paths

4. Safety First

  • Reuses the same three safety checks from gw end
  • Graceful error handling for broken worktrees
  • User-friendly error messages

5. Documentation

  • Well-updated CHANGELOG.md
  • Clear command help text

πŸ” Issues & Concerns

Critical: Hardcoded Emoji Symbols 🚨

Lines: cmd/command.go:758, 767, 797, 802, 810, 820, 823

The code uses hardcoded emoji symbols instead of the symbol constants defined at lines 14-22. This creates inconsistency with the refactored init.go which properly uses the constants.

Impact:

  • Inconsistent with the PRs own refactoring of init.go
  • Makes future updates harder
  • Violates DRY principle

Recommendation: Replace hardcoded emojis with symbol constants (symbolSuccess, symbolError, symbolWarning)

Security: Directory Traversal Risk ⚠️

Line: cmd/command.go:692

The code changes to arbitrary worktree directories without validating the path. Add path validation before os.Chdir().

Code Quality: Error Handling Inconsistency

Lines: cmd/command.go:692-695, 707, 738

Use defer for guaranteed directory restoration instead of ignoring errors with underscore.

Minor: Magic String main

Line: cmd/command.go:728

The branch name main is hardcoded in IsMergedToOrigin(). Consider using defaultBaseBranch constant or making it configurable.

πŸ“Š Test Coverage Analysis

The tests are exemplary. One gap: No test for the ListWorktrees() error case (line 616).

πŸ”’ Security Assessment

Overall Risk Level: LOW βœ…

🎯 Recommendations Summary

Must Fix:

  1. Use symbol constants instead of hardcoded emojis

Should Fix:

  1. Add defer for directory restoration
  2. Validate paths before os.Chdir()

Nice to Have:

  1. Add test for ListWorktrees() error case
  2. Document or make configurable the hardcoded main branch
  3. Add usage examples to help text

βœ… Conclusion

This is excellent work! The feature is well-designed, thoroughly tested, and adds real value.

The only critical issue is the inconsistent use of hardcoded emojis instead of the symbol constants.

Once the emoji constants are fixed, this PR will be ready to merge.

Recommendation: Approve with minor changes βœ…

@sotarok sotarok merged commit 3c90129 into main Nov 25, 2025
11 checks passed
@sotarok sotarok deleted the feat/cmd-clean branch November 25, 2025 12:45
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.

2 participants