Add remote comparison mode with auto-fetch and wt config command#25
Add remote comparison mode with auto-fetch and wt config command#25
Conversation
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>
📝 WalkthroughWalkthroughAdds 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
There was a problem hiding this comment.
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
printConfigListfunction shows global values with(global)suffix but per-repo values userepos.<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,
--unsetonly works for per-repo values (lines 247-250 require being in a git repository). Users might expectwt config --global --unset remoteto 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 configThis would require adding an
UnsetGlobalmethod to the userconfig package.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
README.mdinternal/commands/cleanup.gointernal/commands/commands_test.gointernal/commands/compare.gointernal/commands/config.gointernal/commands/list.gointernal/config/config.gointernal/git/git.gointernal/git/git_test.gointernal/userconfig/userconfig.gointernal/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.gointernal/git/git.go
internal/{git,commands}/**/*.go
📄 CodeRabbit inference engine (CLAUDE.md)
Detect if running in main repo vs worktree by checking if
.gitis a file (worktree) or directory (main repo)
Files:
internal/git/git_test.gointernal/commands/cleanup.gointernal/git/git.gointernal/commands/list.gointernal/commands/compare.gointernal/commands/config.gointernal/commands/commands_test.go
internal/{git,hooks}/**/*.go
📄 CodeRabbit inference engine (CLAUDE.md)
Store worktree numeric index in
.git/worktrees/<name>/wt-indexfor use in port offsets and resource isolation
Files:
internal/git/git_test.gointernal/git/git.go
internal/commands/**/*.go
📄 CodeRabbit inference engine (CLAUDE.md)
internal/commands/**/*.go: Usego test -v -run TestName ./internal/commands/to run a single test
Place Cobra command implementations in internal/commands/ package
Usecmd.Println()for stderr output andfmt.Fprintln(cmd.OutOrStdout(), ...)for stdout output (paths, listings) in Cobra commands
Files:
internal/commands/cleanup.gointernal/commands/list.gointernal/commands/compare.gointernal/commands/config.gointernal/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.gointernal/commands/cleanup.gointernal/git/git.gointernal/commands/list.gointernal/commands/compare.gointernal/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.gointernal/commands/list.gointernal/commands/compare.gointernal/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.gointernal/git/git.gointernal/commands/list.gointernal/commands/compare.goREADME.mdinternal/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.gointernal/commands/list.gointernal/commands/config.goREADME.mdinternal/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.gointernal/commands/list.goREADME.mdinternal/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.gointernal/commands/list.gointernal/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.gointernal/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.gointernal/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.gointernal/userconfig/userconfig_test.gointernal/commands/list.gointernal/commands/config.gointernal/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.gointernal/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
DefaultBranchfield 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
userconfigpackage 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
*boolforRepoConfig.Fetchcorrectly 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
reposkey.
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
RefExistsincluding 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
FetchRemoteQuietwhen 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_branchconfig option with appropriate context about auto-detection fallback.
223-283: LGTM!Comprehensive documentation for the new
wt configcommand 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 updatedlistoutput 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
*boolpointer behavior inGetFetchForRepo, 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
SetupCompareproperly encapsulates repository discovery, config loading, and comparison reference determination. The error handling is appropriately simplified sinceSetupComparehandles the detailed error wrapping.
58-70: Consistent usage of setup context for worktree operations.All git operations correctly use
setup.RepoRoot,setup.Config.WorktreeDir, andsetup.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
Envstruct correctly receivessetup.RepoRootandsetup.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
SetupCompareintegration follows the same pattern ascleanup.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.RepoRootandsetup.ComparisonRef. TheworktreesDirpath is correctly derived fromsetup.Config.WorktreeDir.
85-92: Consistent worktree operations.
GetWorktreeName,GetWorktreeStatus, andGetWorktreeIndexall 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
runConfigfunction 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
getConfigfunction correctly validates keys, handles global vs per-repo lookups, and usesfmt.Fprintln(cmd.OutOrStdout(), ...)for value output as per coding guidelines.
194-238: Good validation and warning behavior.The
setConfigfunction properly validates thefetchboolean value and warns users when settingfetch=truewithout a configured remote. The warning correctly usescmd.PrintErrln()for stderr output.
265-272: LGTM! Clean key validation helper.The
isValidKeyfunction correctly usesuserconfig.ValidKeys()as the source of truth for valid configuration keys.internal/commands/compare.go (4)
14-19: Clean struct design for comparison context.The
CompareSetupstruct appropriately encapsulates the three pieces of context needed bylistandcleanupcommands: 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 viagit.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: Usecmd.Println()for stderr output per coding guidelines.The spinner implementation uses
fmt.Fprintf(cmd.ErrOrStderr(), ...)for status messages, but the coding guidelines require usingcmd.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[Ksequence 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>
There was a problem hiding this comment.
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 withnilerror. Callers may be confused about when to check the error.Consider returning
nil, errwhenGetConfigPath()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 sincesetConfig()in config.go validates the input, but consider usingstrings.EqualFold(value, "true")for more forgiving parsing.
244-267: Inconsistent handling of unknown keys in GetForRepo.
GetGlobalreturns an error for unknown keys, butGetForReposilently 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
defaultcase 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
--listor--show-originis 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 remotesilently ignoresremote.♻️ 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
📒 Files selected for processing (4)
internal/commands/commands_test.gointernal/commands/config.gointernal/userconfig/userconfig.gointernal/userconfig/userconfig_test.go
🧰 Additional context used
📓 Path-based instructions (2)
internal/commands/**/*.go
📄 CodeRabbit inference engine (CLAUDE.md)
internal/commands/**/*.go: Usego test -v -run TestName ./internal/commands/to run a single test
Place Cobra command implementations in internal/commands/ package
Usecmd.Println()for stderr output andfmt.Fprintln(cmd.OutOrStdout(), ...)for stdout output (paths, listings) in Cobra commands
Files:
internal/commands/config.gointernal/commands/commands_test.go
internal/{git,commands}/**/*.go
📄 CodeRabbit inference engine (CLAUDE.md)
Detect if running in main repo vs worktree by checking if
.gitis a file (worktree) or directory (main repo)
Files:
internal/commands/config.gointernal/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.gointernal/userconfig/userconfig.gointernal/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.gointernal/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.gointernal/commands/commands_test.gointernal/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.gointernal/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()beforeClose()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
FetchinRepoConfigcorrectly 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
UnsetForRepois 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:
- Creates an isolated HOME directory
- Resolves symlinks (important for macOS)
- Restores the original HOME on cleanup
- 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:
- Overrides HOME to an isolated temp directory
- Creates config with global and per-repo values
- Saves and verifies file creation
- Loads and verifies all values are preserved
The cleanup with
deferensures 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 offetch=falseonly 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=truewithout 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.
Summary
wt configcommand for managing user settings stored in~/.config/wt/config.yamlorigin/main) instead of locallist/cleanupwhen configuredlistandcleanupnow display repository path and comparison refCloses #22
Configuration
remote""fetchfalseExample usage:
Test plan
make testpassesmake lintpasseswt config --global remote origincreates config filewt listshows "Repository:" and "Comparing to:" headerswt config --global fetch trueshows spinner during fetch🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Tests
✏️ Tip: You can customize this high-level summary in your review settings.