Skip to content

Add remote comparison mode with auto-fetch and wt config command#25

Merged
agarcher merged 3 commits intomainfrom
compare-origin
Jan 13, 2026
Merged

Add remote comparison mode with auto-fetch and wt config command#25
agarcher merged 3 commits intomainfrom
compare-origin

Conversation

@agarcher
Copy link
Owner

@agarcher agarcher commented Jan 13, 2026

Summary

  • Add wt config command for managing user settings stored in ~/.config/wt/config.yaml
  • Add option to compare worktrees against remote branches (e.g., origin/main) instead of local
  • Add auto-fetch before list/cleanup when configured
  • list and cleanup now display repository path and comparison ref
  • Show spinner while fetching from remote

Closes #22

Configuration

Setting Default Description
remote "" Remote to compare against (empty = local comparison)
fetch false Auto-fetch before list/cleanup (only when remote is set)

Example usage:

wt config --global remote origin    # compare to origin/main
wt config --global fetch true       # auto-fetch before list/cleanup
wt config remote upstream           # per-repo override
wt config --list                    # show all settings

Test plan

  • make test passes
  • make lint passes
  • Manual test: wt config --global remote origin creates config file
  • Manual test: wt list shows "Repository:" and "Comparing to:" headers
  • Manual test: wt config --global fetch true shows spinner during fetch

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • New configuration command for global and per-repo get/set/unset/list/show-origin.
    • Persistent user config with remote comparison mode, fetch toggle, and default-branch support.
    • Comparison setup now optionally fetches from remotes with a progress spinner and shows chosen comparison target.
  • Documentation

    • README updated with config options, user configuration section, examples, and file structure.
  • Tests

    • Large suite added to cover config, userconfig, git operations, and workflow scenarios.

✏️ Tip: You can customize this high-level summary in your review settings.

agarcher and others added 2 commits January 12, 2026 19:21
Implements issue #22 - option to compare worktrees against origin/main
and fetch automatically before list/cleanup commands.

Changes:
- Add `wt config` command for managing user settings
- Add user config file at ~/.config/wt/config.yaml with:
  - Global `remote` and `fetch` settings
  - Per-repo overrides in `repos:` section
- Add `default_branch` field to repo config (.wt.yaml)
- list/cleanup now print repo root and comparison ref
- Spinner shown while fetching from remote
- Falls back to local branch if remote ref doesn't exist

Configuration:
- remote="" (default): compare to local branch, no network
- remote="origin": compare to origin/branch
- remote="origin" + fetch=true: fetch before comparing

Example usage:
  wt config --global remote origin    # compare to origin/main
  wt config --global fetch true       # auto-fetch before list/cleanup
  wt config remote upstream           # per-repo override
  wt config --list                    # show all settings
  wt config --show-origin             # show where values come from

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Documentation:
- Add User Configuration section to README
- Document wt config command options
- Document remote/fetch configuration keys
- Document per-repo overrides and config file structure
- Add example output showing new Repository/Comparing headers
- Add default_branch to repo config example

Tests:
- Add TestConfigHelp, TestConfigSetAndGetGlobal, TestConfigSetPerRepo
- Add TestConfigList, TestConfigShowOrigin, TestConfigUnset
- Add TestConfigInvalidKey, TestConfigFetchWithoutRemoteWarning
- Add TestListShowsRepoAndComparisonRef
- Add TestRefExists and TestFetchRemoteQuiet in git_test.go
- Add setupTestRepoWithIsolatedHome helper for config test isolation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Jan 13, 2026

📝 Walkthrough

Walkthrough

Adds a user-level config system and CLI subcommand, a SetupCompare initialization flow (including optional remote fetch), git helper functions for remote/ref operations, and refactors list/cleanup to use the new compare context; documentation and extensive tests were added.

Changes

Cohort / File(s) Summary
Documentation
README.md
Documented default_branch, new wt config command, "Config Options" and "User Configuration" sections with YAML examples and per-repo overrides.
User configuration
internal/userconfig/userconfig.go, internal/userconfig/userconfig_test.go
New UserConfig/RepoConfig types persisted to ~/.config/wt/config.yaml; get/set/unset, per-repo overrides, load/save, and tests for defaults, overrides, and persistence.
Config CLI
internal/commands/config.go, internal/commands/commands_test.go
Added config subcommand supporting --global/--unset/--list/--show-origin, with validation and persistence; many tests for config behaviors and helpers.
Setup/Comparison
internal/commands/compare.go
New CompareSetup type and SetupCompare(cmd) implementing repo discovery, repo/user config merging, remote/branch selection, optional fetch with spinner, and returning comparison context.
Command refactors
internal/commands/cleanup.go, internal/commands/list.go
Replaced direct config/load logic with SetupCompare usage (setup.RepoRoot, setup.Config, setup.ComparisonRef) across list and cleanup flows, and updated hook env construction.
Git helpers & tests
internal/git/git.go, internal/git/git_test.go
Added FetchRemote, FetchRemoteQuiet, RefExists, UpdateRemoteHead and tests for ref existence and fetch error handling.
Repo config struct
internal/config/config.go
Added DefaultBranch string field to Config struct (YAML tag default_branch).
Tests (commands)
internal/commands/commands_test.go
Large additions: config command tests, cleanup/list/worktree scenarios, HOME isolation helper and state resets.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Cmd as cleanup/list Command
    participant Setup as SetupCompare
    participant RepoCfg as Repo Config Loader
    participant UserCfg as User Config Loader
    participant Git as Git Ops
    participant Spinner as Spinner UI

    User->>Cmd: run cleanup/list
    Cmd->>Setup: SetupCompare(cmd)
    Setup->>RepoCfg: determine repo root & load repo config
    RepoCfg-->>Setup: repo root + config
    Setup->>UserCfg: load user config
    UserCfg-->>Setup: user config
    Setup->>Setup: choose remote & comparison branch
    alt remote configured
        Setup->>Spinner: start spinner
        Setup->>Git: FetchRemoteQuiet(repoRoot, remote)
        Git-->>Setup: fetch result
        Setup->>Git: UpdateRemoteHead(repoRoot, remote)
        Git-->>Setup: update result
        Setup->>Spinner: stop spinner
        Setup->>Git: RefExists(repoRoot, remoteRef)
        Git-->>Setup: ref exists?
    end
    Setup-->>Cmd: CompareSetup{RepoRoot, Config, ComparisonRef}
    Cmd->>Git: perform list/cleanup operations using CompareSetup values
    Git-->>Cmd: results
    Cmd-->>User: output
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped through configs, branch and fetch,
A tidy setup, no stale-check stretch,
Compare to origin, spinner bright,
Worktrees tidy by moonlit night. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.61% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: adding a remote comparison mode, auto-fetch capability, and the wt config command. It is specific, concise, and reflects the primary objectives.
Linked Issues check ✅ Passed All objectives from issue #22 are met: remote comparison mode implemented via SetupCompare with ComparisonRef selection, auto-fetch before list/cleanup via fetchWithSpinner, and configurable settings via wt config command with global/per-repo options.
Out of Scope Changes check ✅ Passed All changes align with the linked issue objectives. No extraneous modifications detected; all file changes support remote comparison, auto-fetch, and configuration management functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @internal/commands/config.go:
- Around line 119-161: In printConfigShowOrigin, capture and check the error
returned by userconfig.GetConfigPath() (currently ignored) and avoid using a
possibly empty configPath directly in formatted output; if GetConfigPath()
returns an error or empty string set a safe fallback like "(unknown config
path)" or omit the path in the fmt.Fprintf calls so the output isn't confusing,
and reference the existing configPath variable and userconfig.GetConfigPath()
when making the change; do not change function signature—just replace the
current ignored error handling with a checked error and a fallback string used
wherever configPath is printed.
🧹 Nitpick comments (3)
internal/userconfig/userconfig.go (1)

81-104: Consider atomic write for Save to prevent corruption.

If the process is interrupted during WriteFile, the config could be left in a corrupted state. Writing to a temp file and renaming is a common pattern for atomic saves. This is a minor reliability concern for a CLI tool.

♻️ Optional: Atomic write pattern
 func Save(cfg *UserConfig) error {
 	configPath, err := GetConfigPath()
 	if err != nil {
 		return err
 	}

 	// Ensure directory exists
 	dir := filepath.Dir(configPath)
 	if err := os.MkdirAll(dir, 0755); err != nil {
 		return fmt.Errorf("failed to create config directory: %w", err)
 	}

 	data, err := yaml.Marshal(cfg)
 	if err != nil {
 		return fmt.Errorf("failed to marshal config: %w", err)
 	}

-	if err := os.WriteFile(configPath, data, 0644); err != nil {
-		return fmt.Errorf("failed to write config: %w", err)
+	// Write to temp file first, then rename for atomicity
+	tmpFile := configPath + ".tmp"
+	if err := os.WriteFile(tmpFile, data, 0644); err != nil {
+		return fmt.Errorf("failed to write config: %w", err)
+	}
+	if err := os.Rename(tmpFile, configPath); err != nil {
+		_ = os.Remove(tmpFile) // Clean up on failure
+		return fmt.Errorf("failed to save config: %w", err)
 	}

 	return nil
 }
internal/commands/config.go (2)

92-117: Consider consistency in output format for --list.

The printConfigList function shows global values with (global) suffix but per-repo values use repos.<path>.<key> prefix format. This asymmetry might be intentional for clarity, but consider whether users might expect a more uniform format.


240-263: Consider supporting --global with --unset for symmetry.

Currently, --unset only works for per-repo values (lines 247-250 require being in a git repository). Users might expect wt config --global --unset remote to clear the global remote value.

Suggested enhancement
 func unsetConfig(cmd *cobra.Command, cfg *userconfig.UserConfig, key string) error {
 	// Validate key
 	if !isValidKey(key) {
 		return fmt.Errorf("unknown config key: %s\nValid keys: %s", key, strings.Join(userconfig.ValidKeys(), ", "))
 	}

-	// Get current repo
-	repoRoot, err := config.GetMainRepoRoot()
-	if err != nil {
-		return fmt.Errorf("not in a git repository")
-	}
-
-	// Unset per-repo value
-	if err := cfg.UnsetForRepo(repoRoot, key); err != nil {
-		return err
+	if configGlobal {
+		// Unset global value
+		if err := cfg.UnsetGlobal(key); err != nil {
+			return err
+		}
+	} else {
+		// Get current repo
+		repoRoot, err := config.GetMainRepoRoot()
+		if err != nil {
+			return fmt.Errorf("not in a git repository (use --global for global config)")
+		}
+
+		// Unset per-repo value
+		if err := cfg.UnsetForRepo(repoRoot, key); err != nil {
+			return err
+		}
 	}
 
 	// Save config

This would require adding an UnsetGlobal method to the userconfig package.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3ea82dd and 203349e.

📒 Files selected for processing (11)
  • README.md
  • internal/commands/cleanup.go
  • internal/commands/commands_test.go
  • internal/commands/compare.go
  • internal/commands/config.go
  • internal/commands/list.go
  • internal/config/config.go
  • internal/git/git.go
  • internal/git/git_test.go
  • internal/userconfig/userconfig.go
  • internal/userconfig/userconfig_test.go
🧰 Additional context used
📓 Path-based instructions (5)
internal/git/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Place Git worktree command wrappers and porcelain output parsing in internal/git/ package

Files:

  • internal/git/git_test.go
  • internal/git/git.go
internal/{git,commands}/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Detect if running in main repo vs worktree by checking if .git is a file (worktree) or directory (main repo)

Files:

  • internal/git/git_test.go
  • internal/commands/cleanup.go
  • internal/git/git.go
  • internal/commands/list.go
  • internal/commands/compare.go
  • internal/commands/config.go
  • internal/commands/commands_test.go
internal/{git,hooks}/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Store worktree numeric index in .git/worktrees/<name>/wt-index for use in port offsets and resource isolation

Files:

  • internal/git/git_test.go
  • internal/git/git.go
internal/commands/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

internal/commands/**/*.go: Use go test -v -run TestName ./internal/commands/ to run a single test
Place Cobra command implementations in internal/commands/ package
Use cmd.Println() for stderr output and fmt.Fprintln(cmd.OutOrStdout(), ...) for stdout output (paths, listings) in Cobra commands

Files:

  • internal/commands/cleanup.go
  • internal/commands/list.go
  • internal/commands/compare.go
  • internal/commands/config.go
  • internal/commands/commands_test.go
internal/config/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Place configuration loading and parsing logic in internal/config/ package

Files:

  • internal/config/config.go
🧠 Learnings (12)
📓 Common learnings
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: `wt` is a Git worktree manager CLI built with Go and Cobra that manages worktree lifecycle with hooks
📚 Learning: 2026-01-12T02:19:51.920Z
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: Applies to internal/git/**/*.go : Place Git worktree command wrappers and porcelain output parsing in internal/git/ package

Applied to files:

  • internal/git/git_test.go
  • internal/commands/cleanup.go
  • internal/git/git.go
  • internal/commands/list.go
  • internal/commands/compare.go
  • internal/commands/commands_test.go
📚 Learning: 2026-01-12T02:19:51.920Z
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: Applies to internal/{git,commands}/**/*.go : Detect if running in main repo vs worktree by checking if `.git` is a file (worktree) or directory (main repo)

Applied to files:

  • internal/commands/cleanup.go
  • internal/commands/list.go
  • internal/commands/compare.go
  • internal/commands/commands_test.go
📚 Learning: 2026-01-12T02:19:51.920Z
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: Applies to internal/{git,hooks}/**/*.go : Store worktree numeric index in `.git/worktrees/<name>/wt-index` for use in port offsets and resource isolation

Applied to files:

  • internal/commands/cleanup.go
  • internal/git/git.go
  • internal/commands/list.go
  • internal/commands/compare.go
  • README.md
  • internal/commands/commands_test.go
📚 Learning: 2026-01-12T02:19:51.920Z
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: `wt` is a Git worktree manager CLI built with Go and Cobra that manages worktree lifecycle with hooks

Applied to files:

  • internal/commands/cleanup.go
  • internal/commands/list.go
  • internal/commands/config.go
  • README.md
  • internal/commands/commands_test.go
📚 Learning: 2026-01-12T02:19:51.920Z
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: Applies to internal/hooks/**/*.go : Pass standardized hook environment variables: `WT_NAME`, `WT_PATH`, `WT_BRANCH`, `WT_REPO_ROOT`, `WT_WORKTREE_DIR`, `WT_INDEX`

Applied to files:

  • internal/commands/cleanup.go
  • internal/commands/list.go
  • README.md
  • internal/commands/commands_test.go
📚 Learning: 2026-01-12T02:19:51.920Z
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: Applies to cmd/wt/main.go : Entry point should be cmd/wt/main.go and delegate to `commands.Execute()`

Applied to files:

  • internal/commands/cleanup.go
  • internal/commands/list.go
  • internal/commands/config.go
📚 Learning: 2026-01-12T02:19:51.920Z
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: Applies to internal/commands/**/*.go : Use `cmd.Println()` for stderr output and `fmt.Fprintln(cmd.OutOrStdout(), ...)` for stdout output (paths, listings) in Cobra commands

Applied to files:

  • internal/commands/cleanup.go
  • internal/commands/config.go
📚 Learning: 2026-01-12T02:19:51.920Z
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: Applies to internal/commands/**/*.go : Place Cobra command implementations in internal/commands/ package

Applied to files:

  • internal/commands/cleanup.go
  • internal/commands/config.go
📚 Learning: 2026-01-12T02:19:51.920Z
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: Applies to internal/config/**/*.go : Place configuration loading and parsing logic in internal/config/ package

Applied to files:

  • internal/commands/cleanup.go
  • internal/userconfig/userconfig_test.go
  • internal/commands/list.go
  • internal/commands/config.go
  • internal/userconfig/userconfig.go
📚 Learning: 2026-01-12T02:19:51.920Z
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: Applies to internal/commands/**/*.go : Use `go test -v -run TestName ./internal/commands/` to run a single test

Applied to files:

  • internal/userconfig/userconfig_test.go
  • internal/commands/commands_test.go
📚 Learning: 2026-01-12T02:19:51.920Z
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: Use shell wrapper function (`wt()`) generated by `wt init <shell>` to handle the actual `cd`, solving the subprocess directory change limitation

Applied to files:

  • README.md
🧬 Code graph analysis (7)
internal/git/git_test.go (1)
internal/git/git.go (3)
  • GetCurrentBranch (105-115)
  • RefExists (228-232)
  • FetchRemoteQuiet (221-225)
internal/commands/cleanup.go (4)
internal/commands/compare.go (1)
  • SetupCompare (23-89)
internal/git/git.go (7)
  • ListWorktrees (56-95)
  • GetMergedBranches (310-333)
  • GetWorktreeName (165-177)
  • GetWorktreeStatus (501-544)
  • GetWorktreeIndex (560-569)
  • RemoveWorktree (43-53)
  • DeleteBranch (199-209)
internal/config/config.go (1)
  • Config (21-28)
internal/hooks/hooks.go (2)
  • RunPreDelete (99-105)
  • RunPostDelete (108-114)
internal/userconfig/userconfig_test.go (1)
internal/userconfig/userconfig.go (8)
  • DefaultUserConfig (35-41)
  • UserConfig (25-32)
  • RepoConfig (19-22)
  • Save (82-104)
  • ConfigDir (13-13)
  • ConfigFile (15-15)
  • Load (54-79)
  • ValidKeys (230-232)
internal/commands/list.go (3)
internal/commands/compare.go (1)
  • SetupCompare (23-89)
internal/git/git.go (5)
  • ListWorktrees (56-95)
  • GetMergedBranches (310-333)
  • GetWorktreeName (165-177)
  • GetWorktreeStatus (501-544)
  • GetWorktreeIndex (560-569)
internal/config/config.go (1)
  • Config (21-28)
internal/commands/compare.go (3)
internal/config/config.go (4)
  • Config (21-28)
  • GetMainRepoRoot (113-159)
  • Load (54-79)
  • DefaultConfig (45-51)
internal/userconfig/userconfig.go (1)
  • Load (54-79)
internal/git/git.go (4)
  • GetDefaultBranch (242-261)
  • RefExists (228-232)
  • FetchRemoteQuiet (221-225)
  • UpdateRemoteHead (235-239)
internal/commands/config.go (2)
internal/config/config.go (2)
  • Load (54-79)
  • GetMainRepoRoot (113-159)
internal/userconfig/userconfig.go (5)
  • Load (54-79)
  • UserConfig (25-32)
  • GetConfigPath (44-50)
  • ValidKeys (230-232)
  • Save (82-104)
internal/userconfig/userconfig.go (1)
internal/config/config.go (1)
  • Load (54-79)
🔇 Additional comments (32)
internal/config/config.go (1)

25-25: LGTM!

The new DefaultBranch field follows the existing pattern. Leaving it uninitialized (empty string) correctly signals auto-detection, which aligns with the PR's fallback behavior described in the objectives.

internal/git/git.go (1)

211-239: LGTM!

The new git utility functions follow existing patterns in the codebase and are correctly placed in the internal/git/ package per the coding guidelines. The implementations are straightforward wrappers around git commands.

internal/userconfig/userconfig.go (4)

1-9: LGTM!

New userconfig package provides clean separation between repository-level config (.wt.yaml) and user-level config (~/.config/wt/config.yaml). This architectural choice is sensible.


18-32: Good use of pointer type for distinguishing unset from false.

Using *bool for RepoConfig.Fetch correctly allows distinguishing between "not set" (nil) and "explicitly set to false" - essential for the per-repo override fallback logic.


52-79: Load function handles edge cases appropriately.

The function correctly returns defaults when the file doesn't exist (with nil error), and reinitializes the Repos map after unmarshaling to handle YAML files that omit the repos key.


159-187: Clean removal of empty repo entries.

Good design to remove the entire repo entry from the map when all its values are unset, keeping the config file clean.

internal/git/git_test.go (2)

1089-1117: LGTM!

Good test coverage for RefExists including edge cases like HEAD and non-existent remote refs. The table-driven approach is consistent with other tests in this file.


1119-1128: LGTM!

Testing the error path for FetchRemoteQuiet when no remote is configured is appropriate. Testing the success path would require more complex setup with an actual remote.

README.md (2)

117-119: LGTM!

Clear documentation for the new default_branch config option with appropriate context about auto-detection fallback.


223-283: LGTM!

Comprehensive documentation for the new wt config command and user configuration system. The examples clearly demonstrate both global and per-repo configuration workflows.

internal/commands/commands_test.go (3)

22-26: LGTM!

Good addition of flag resets for the new config command flags, preventing state pollution between tests.


909-941: LGTM!

Well-designed helper that isolates HOME directory for config tests and properly restores the original HOME in the cleanup function.


943-1187: Comprehensive test coverage for config command.

Tests cover all flags (--global, --list, --show-origin, --unset), both global and per-repo settings, error cases (invalid key), and warning behavior (fetch without remote). The tests also verify the updated list output includes repository and comparison ref information.

internal/userconfig/userconfig_test.go (4)

1-7: LGTM!

Clean test file setup with appropriate imports.


50-80: Good test for pointer boolean semantics.

This test correctly verifies the *bool pointer behavior in GetFetchForRepo, ensuring per-repo false overrides global true, and nil falls back to global.


123-149: Good coverage of unset cleanup behavior.

Test verifies both partial unset (remote unset, fetch remains) and complete unset (repo entry removed from map).


151-195: LGTM!

Thorough round-trip test for Load/Save with proper HOME isolation to avoid affecting actual user config.

internal/commands/cleanup.go (4)

51-56: LGTM! Clean refactor to centralized setup.

The transition to SetupCompare properly encapsulates repository discovery, config loading, and comparison reference determination. The error handling is appropriately simplified since SetupCompare handles the detailed error wrapping.


58-70: Consistent usage of setup context for worktree operations.

All git operations correctly use setup.RepoRoot, setup.Config.WorktreeDir, and setup.ComparisonRef. The merged branches cache properly uses the comparison ref for determining merge status.


193-234: Hook environment and cleanup operations properly updated.

The hook Env struct correctly receives setup.RepoRoot and setup.Config.WorktreeDir. All cleanup operations (RemoveWorktree, DeleteBranch, RunPreDelete, RunPostDelete) consistently use the setup context. As per coding guidelines, cmd.Println()/cmd.Printf() is used for stderr output.


241-244: Correct stdout output for shell integration.

Using fmt.Fprintln(cmd.OutOrStdout(), ...) for the repo root path aligns with the coding guidelines for outputting paths/listings to stdout.

internal/commands/list.go (3)

51-56: LGTM! Consistent with cleanup.go refactoring.

The SetupCompare integration follows the same pattern as cleanup.go, providing repository context, configuration, and comparison reference in a unified way.


58-69: Proper usage of setup context for worktree listing.

The worktrees listing and merged branches cache correctly use setup.RepoRoot and setup.ComparisonRef. The worktreesDir path is correctly derived from setup.Config.WorktreeDir.


85-92: Consistent worktree operations.

GetWorktreeName, GetWorktreeStatus, and GetWorktreeIndex all correctly use the setup-derived values. Silently ignoring errors here is acceptable for a display-oriented command where partial information is preferable to failure.

internal/commands/config.go (4)

52-90: Well-structured command dispatcher.

The runConfig function cleanly handles all flag combinations and argument counts. The error messages provide helpful usage hints. The dispatch logic is clear and maintainable.


163-192: LGTM! Proper validation and output routing.

The getConfig function correctly validates keys, handles global vs per-repo lookups, and uses fmt.Fprintln(cmd.OutOrStdout(), ...) for value output as per coding guidelines.


194-238: Good validation and warning behavior.

The setConfig function properly validates the fetch boolean value and warns users when setting fetch=true without a configured remote. The warning correctly uses cmd.PrintErrln() for stderr output.


265-272: LGTM! Clean key validation helper.

The isValidKey function correctly uses userconfig.ValidKeys() as the source of truth for valid configuration keys.

internal/commands/compare.go (4)

14-19: Clean struct design for comparison context.

The CompareSetup struct appropriately encapsulates the three pieces of context needed by list and cleanup commands: repository root, configuration, and comparison reference.


21-53: Robust fallback chain for default branch detection.

The branch determination follows a sensible priority: repo config DefaultBranch → auto-detect via git.GetDefaultBranch → fallback to "main". This ensures the command works in various repository configurations.


55-78: Good remote ref validation with graceful fallback.

The code properly verifies the remote ref exists before using it and falls back to the local branch with a warning if the remote ref doesn't exist. This prevents confusing errors when remotes are misconfigured.


91-135: Use cmd.Println() for stderr output per coding guidelines.

The spinner implementation uses fmt.Fprintf(cmd.ErrOrStderr(), ...) for status messages, but the coding guidelines require using cmd.Println() for stderr output in Cobra commands. Replace the fprintf calls with cmd.Println() for consistency.

Windows Terminal supports ANSI escape sequences natively by default, so the \033[K sequence works without issues on modern terminals.

⛔ Skipped due to learnings
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: Applies to internal/commands/**/*.go : Use `cmd.Println()` for stderr output and `fmt.Fprintln(cmd.OutOrStdout(), ...)` for stdout output (paths, listings) in Cobra commands

- Use atomic file writes (temp file + rename) in userconfig.Save() to
  prevent config corruption if interrupted during write
- Support wt config --global --unset <key> to clear global config values
- Handle GetConfigPath error in printConfigShowOrigin with "(unknown)" fallback

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (5)
internal/userconfig/userconfig.go (3)

54-58: Inconsistent error handling in Load()

On line 57, when GetConfigPath() fails, the function returns both the default config AND the error. This differs from lines 63-65 where file-not-found returns defaults with nil error. Callers may be confused about when to check the error.

Consider returning nil, err when GetConfigPath() fails, or document that callers should use the returned config even when error is non-nil.


156-157: Strict boolean parsing for fetch.

The check value == "true" is case-sensitive and only accepts lowercase "true". Values like "TRUE", "True", or "1" will be treated as false. This is fine since setConfig() in config.go validates the input, but consider using strings.EqualFold(value, "true") for more forgiving parsing.


244-267: Inconsistent handling of unknown keys in GetForRepo.

GetGlobal returns an error for unknown keys, but GetForRepo silently returns ("", false) for unknown keys (falls through at lines 252-264). This inconsistency could mask bugs where an invalid key is passed.

Consider adding a default case that returns an error, or document that unknown keys return ("", false).

♻️ Suggested fix
 func (c *UserConfig) GetForRepo(repoPath, key string) (string, bool) {
 	repoConfig, ok := c.Repos[repoPath]
 	if !ok {
 		return "", false
 	}

 	switch key {
 	case "remote":
 		if repoConfig.Remote != "" {
 			return repoConfig.Remote, true
 		}
 	case "fetch":
 		if repoConfig.Fetch != nil {
 			if *repoConfig.Fetch {
 				return "true", true
 			}
 			return "false", true
 		}
+	default:
+		// Unknown key - could log or return error in future
 	}

 	return "", false
 }
internal/userconfig/userconfig_test.go (1)

255-267: Consider adding edge case tests.

The current test suite covers happy paths well. For completeness, consider adding tests for:

  • Malformed YAML handling (verify error message)
  • GetForRepo with unknown keys (documents current behavior)

These are optional improvements and not blockers.

internal/commands/config.go (1)

59-75: Consider validating flag combinations.

When --list or --show-origin is used with positional arguments, they are silently ignored. This could confuse users. Consider warning or erroring when incompatible options are combined.

Example: wt config --list remote silently ignores remote.

♻️ Suggested validation
 // Handle --list
 if configList {
+    if len(args) > 0 {
+        cmd.PrintErrln("Warning: arguments ignored with --list")
+    }
     return printConfigList(cmd, cfg)
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 203349e and d97c2be.

📒 Files selected for processing (4)
  • internal/commands/commands_test.go
  • internal/commands/config.go
  • internal/userconfig/userconfig.go
  • internal/userconfig/userconfig_test.go
🧰 Additional context used
📓 Path-based instructions (2)
internal/commands/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

internal/commands/**/*.go: Use go test -v -run TestName ./internal/commands/ to run a single test
Place Cobra command implementations in internal/commands/ package
Use cmd.Println() for stderr output and fmt.Fprintln(cmd.OutOrStdout(), ...) for stdout output (paths, listings) in Cobra commands

Files:

  • internal/commands/config.go
  • internal/commands/commands_test.go
internal/{git,commands}/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Detect if running in main repo vs worktree by checking if .git is a file (worktree) or directory (main repo)

Files:

  • internal/commands/config.go
  • internal/commands/commands_test.go
🧠 Learnings (11)
📓 Common learnings
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: `wt` is a Git worktree manager CLI built with Go and Cobra that manages worktree lifecycle with hooks
📚 Learning: 2026-01-12T02:19:51.920Z
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: Applies to internal/commands/**/*.go : Place Cobra command implementations in internal/commands/ package

Applied to files:

  • internal/commands/config.go
📚 Learning: 2026-01-12T02:19:51.920Z
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: Applies to internal/commands/**/*.go : Use `cmd.Println()` for stderr output and `fmt.Fprintln(cmd.OutOrStdout(), ...)` for stdout output (paths, listings) in Cobra commands

Applied to files:

  • internal/commands/config.go
📚 Learning: 2026-01-12T02:19:51.920Z
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: Applies to internal/config/**/*.go : Place configuration loading and parsing logic in internal/config/ package

Applied to files:

  • internal/commands/config.go
  • internal/userconfig/userconfig.go
  • internal/userconfig/userconfig_test.go
📚 Learning: 2026-01-12T02:19:51.920Z
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: Applies to internal/git/**/*.go : Place Git worktree command wrappers and porcelain output parsing in internal/git/ package

Applied to files:

  • internal/commands/config.go
  • internal/commands/commands_test.go
📚 Learning: 2026-01-12T02:19:51.920Z
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: Applies to internal/commands/**/*.go : Use `go test -v -run TestName ./internal/commands/` to run a single test

Applied to files:

  • internal/commands/config.go
  • internal/commands/commands_test.go
  • internal/userconfig/userconfig_test.go
📚 Learning: 2026-01-12T02:19:51.920Z
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: Applies to internal/shell/**/*.go : Place shell integration function generation in internal/shell/ package

Applied to files:

  • internal/commands/config.go
📚 Learning: 2026-01-12T02:19:51.920Z
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: `wt` is a Git worktree manager CLI built with Go and Cobra that manages worktree lifecycle with hooks

Applied to files:

  • internal/commands/config.go
  • internal/commands/commands_test.go
📚 Learning: 2026-01-12T02:19:51.920Z
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: Applies to cmd/wt/main.go : Entry point should be cmd/wt/main.go and delegate to `commands.Execute()`

Applied to files:

  • internal/commands/config.go
📚 Learning: 2026-01-12T02:19:51.920Z
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: Applies to internal/{git,hooks}/**/*.go : Store worktree numeric index in `.git/worktrees/<name>/wt-index` for use in port offsets and resource isolation

Applied to files:

  • internal/commands/commands_test.go
📚 Learning: 2026-01-12T02:19:51.920Z
Learnt from: CR
Repo: agarcher/wt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T02:19:51.920Z
Learning: Applies to internal/hooks/**/*.go : Pass standardized hook environment variables: `WT_NAME`, `WT_PATH`, `WT_BRANCH`, `WT_REPO_ROOT`, `WT_WORKTREE_DIR`, `WT_INDEX`

Applied to files:

  • internal/commands/commands_test.go
🧬 Code graph analysis (2)
internal/userconfig/userconfig.go (1)
internal/config/config.go (1)
  • Load (54-79)
internal/userconfig/userconfig_test.go (1)
internal/userconfig/userconfig.go (8)
  • DefaultUserConfig (35-41)
  • UserConfig (25-32)
  • RepoConfig (19-22)
  • Save (83-131)
  • ConfigDir (13-13)
  • ConfigFile (15-15)
  • Load (54-79)
  • ValidKeys (270-272)
🔇 Additional comments (14)
internal/userconfig/userconfig.go (3)

100-127: LGTM - Atomic write pattern correctly implemented.

The temp-file-plus-rename approach prevents config corruption if interrupted. The deferred cleanup on error is well-structured.

One optional consideration: calling tempFile.Sync() before Close() would ensure data is flushed to disk before rename, providing stronger durability guarantees on crash. This is typically a nice-to-have for config files.


133-149: LGTM!

The per-repo override logic is clean. Using a pointer for Fetch in RepoConfig correctly distinguishes between "unset" (nil) and "explicitly false", enabling proper fallback to global defaults.


177-227: LGTM!

The map-of-structs modification pattern (get copy, modify, write back) is correctly implemented. The automatic cleanup of empty repo entries in UnsetForRepo is a nice touch for keeping the config file clean.

internal/commands/commands_test.go (3)

22-26: LGTM - Config flags properly reset between tests.

Adding the config flag resets ensures test isolation and prevents state pollution between test runs.


909-941: LGTM - Well-designed test helper for config isolation.

The helper correctly:

  1. Creates an isolated HOME directory
  2. Resolves symlinks (important for macOS)
  3. Restores the original HOME on cleanup
  4. Chains cleanup functions properly

This ensures tests don't pollute the user's actual config.


943-1222: LGTM - Comprehensive test coverage for config command.

The tests cover:

  • Help output validation
  • Global and per-repo set/get operations
  • List and show-origin commands
  • Unset operations (both scopes)
  • Invalid key error handling
  • Warning messages for fetch without remote
  • Integration with list command output

All tests properly use the isolated HOME helper for test isolation. Based on learnings, these tests can be run individually with go test -v -run TestName ./internal/commands/.

internal/userconfig/userconfig_test.go (3)

9-21: LGTM!

The test correctly verifies default config state: empty remote, false fetch, and initialized Repos map.


23-80: LGTM - Good table-driven tests for per-repo override behavior.

Tests cover both GetRemoteForRepo and GetFetchForRepo with:

  • Per-repo override cases
  • Fallback to global defaults
  • Tri-state fetch handling (nil vs false vs true)

182-226: LGTM - Save/Load round-trip test is thorough.

The test properly:

  1. Overrides HOME to an isolated temp directory
  2. Creates config with global and per-repo values
  3. Saves and verifies file creation
  4. Loads and verifies all values are preserved

The cleanup with defer ensures the temp directory and HOME are restored.

internal/commands/config.go (5)

1-50: LGTM - Well-structured command definition.

The command follows Cobra best practices:

  • Clear usage string and examples
  • Comprehensive long description
  • Flags properly registered in init()

As per coding guidelines, the command is correctly placed in internal/commands/.


92-117: LGTM!

The list output is well-formatted and uses cmd.OutOrStdout() as per coding guidelines. The conditional display of fetch=false only when remote is set avoids confusing output.


119-164: LGTM - Informative origin display.

The show-origin output clearly indicates:

  • Effective values for the current repo
  • Source of each value (global, per-repo, or default)
  • Config file path for non-default values
  • Repo-specific default_branch from .wt.yaml

Uses cmd.OutOrStdout() correctly per coding guidelines.


197-241: LGTM - Robust setConfig with validation and warnings.

Key features:

  • Validates key before setting
  • Validates fetch must be "true" or "false"
  • Warns when setting fetch=true without remote configured
  • Uses cmd.PrintErrln() for warnings (stderr) per coding guidelines
  • Saves config after modification

243-282: LGTM!

The unsetConfig properly handles both global and per-repo scopes, validates keys, and saves after modification. The isValidKey helper is simple and effective for the current set of keys.

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.

Option to compare to origin/main and fetch automatically

1 participant