From 17d1426a52ce6f9c5e2fd4991c0281872266c7cc Mon Sep 17 00:00:00 2001 From: Thomas Vilte Date: Thu, 30 Jul 2026 18:25:13 -0300 Subject: [PATCH] fix(config): show correct config type and avoid duplication --- internal/commands/config/show.go | 36 ++++++++----------------- internal/commands/config/show_test.go | 39 +++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 25 deletions(-) diff --git a/internal/commands/config/show.go b/internal/commands/config/show.go index 6e319ff..a44d42f 100644 --- a/internal/commands/config/show.go +++ b/internal/commands/config/show.go @@ -14,7 +14,17 @@ func (c *ConfigCommandFactory) newShowCommand(t *i18n.Translations, cfg *config. Name: "show", Usage: t.GetMessage("config_show_usage", 0, nil), Action: func(ctx context.Context, command *cli.Command) error { - fmt.Println(t.GetMessage("config_local.global_config_header", 0, nil)) + // LoadConfigWithHierarchy resolves to the repo-local config + // entirely whenever one exists (not a field-by-field merge with + // global) — so `cfg` here is local data whenever we're inside a + // git repo, and it must be labeled as such instead of always + // saying "Global". + isLocal := config.GetRepoConfigPath() != "" + if isLocal { + fmt.Println(t.GetMessage("config_local.local_config_header", 0, nil)) + } else { + fmt.Println(t.GetMessage("config_local.global_config_header", 0, nil)) + } fmt.Printf("━━━━━━━━━━━━━━━━━━━━━━━\n") fmt.Printf("%s\n", t.GetMessage("language_label", 0, struct{ Lang string }{cfg.Language})) @@ -72,30 +82,6 @@ func (c *ConfigCommandFactory) newShowCommand(t *i18n.Translations, cfg *config. } } - localPath := config.GetRepoConfigPath() - if localPath != "" { - localCfg, err := config.LoadConfig(localPath) - if err == nil { - fmt.Println() - fmt.Println(t.GetMessage("config_local.local_config_header", 0, nil)) - fmt.Printf("━━━━━━━━━━━━━━━━━━━━━━━\n") - fmt.Printf("%s\n", t.GetMessage("language_label", 0, struct{ Lang string }{localCfg.Language})) - fmt.Printf("%s\n", t.GetMessage("emojis_label", 0, struct{ Emoji bool }{localCfg.UseEmoji})) - fmt.Printf("%s\n", t.GetMessage("config_models.active_ai_label", 0, struct{ IA config.AI }{localCfg.AIConfig.ActiveAI})) - - if localCfg.GitFallback.UserName != "" || localCfg.GitFallback.UserEmail != "" { - fmt.Println() - fmt.Println(t.GetMessage("config_git.fallback_header", 0, nil)) - if localCfg.GitFallback.UserName != "" { - fmt.Printf("%s\n", t.GetMessage("config_git.fallback_name", 0, struct{ Name string }{localCfg.GitFallback.UserName})) - } - if localCfg.GitFallback.UserEmail != "" { - fmt.Printf("%s\n", t.GetMessage("config_git.fallback_email", 0, struct{ Email string }{localCfg.GitFallback.UserEmail})) - } - } - } - } - return nil }, } diff --git a/internal/commands/config/show_test.go b/internal/commands/config/show_test.go index f0c1b5b..8b9068b 100644 --- a/internal/commands/config/show_test.go +++ b/internal/commands/config/show_test.go @@ -5,6 +5,7 @@ import ( "context" "io" "os" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -114,4 +115,42 @@ func TestShowCommand(t *testing.T) { assert.Contains(t, output, "gemini: gemini-1.5-flash") assert.Contains(t, output, "openai: gpt-4o") }) + + t.Run("labels the resolved config as local (not global) when run inside a repo, with no duplicate section", func(t *testing.T) { + // Inside a git repo, LoadConfigWithHierarchy resolves entirely to + // the local config (not a merge with global) — the display must + // reflect that, and must not print the same data twice under both + // a "Global" and a "Local" header. + cfg, translations, _, cleanup := setupConfigTest(t) + cfg.GitFallback = config.GitConfig{UserName: "Fallback Name", UserEmail: "fallback@example.com"} + assert.NoError(t, config.SaveConfig(cfg)) + defer cleanup() + + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + cmd := NewConfigCommandFactory().newShowCommand(translations, cfg) + app := &cli.Command{Commands: []*cli.Command{cmd}} + + err := app.Run(context.Background(), []string{"config", "show"}) + + if err := w.Close(); err != nil { + assert.NoError(t, err) + } + os.Stdout = oldStdout + var buf bytes.Buffer + if _, err := io.Copy(&buf, r); err != nil { + assert.NoError(t, err) + } + output := buf.String() + + assert.NoError(t, err) + assert.Contains(t, output, "Local Configuration") + assert.NotContains(t, output, "Global Configuration") + assert.Equal(t, 1, strings.Count(output, "Fallback Name"), + "the fallback identity should be printed exactly once, not duplicated across sections") + assert.Equal(t, 1, strings.Count(output, "━━━━━━━━━━━━━━━━━━━━━━━"), + "there should be exactly one configuration section header") + }) }