From 573cd8a005718a3ee0acca6b050ec22d3b1f8565 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sat, 26 Sep 2026 13:57:13 +0200 Subject: [PATCH] Migrate gui.branchColors to gui.branchColorPatterns gui.branchColors has been deprecated in favor of gui.branchColorPatterns since 0.44.0. We are about to move gui.branchColorPatterns into gui.theme, and the deprecated key would have to move along with it. Migrate it instead, so that we can remove it. gui.branchColors matched its keys against the part of a branch name before the first slash. The pattern ^(/|$), with the key escaped, matches the same branches. If gui.branchColorPatterns is set, gui.branchColors has no effect; in that case the migration removes it. This check is done per file. So if the global config sets gui.branchColorPatterns and a repo config sets only gui.branchColors, the repo's colors were ignored so far, and now they apply. Co-Authored-By: Claude Opus 5.5 (1M context) --- pkg/config/app_config.go | 43 +++++++++++ pkg/config/app_config_test.go | 104 ++++++++++++++++++++++++++ pkg/config/user_config.go | 3 - pkg/gui/gui.go | 7 +- pkg/gui/presentation/branches.go | 18 +---- pkg/gui/presentation/branches_test.go | 2 +- schema-master/config.json | 7 -- 7 files changed, 153 insertions(+), 31 deletions(-) diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go index ade614f7d..00ce83bc0 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "reflect" + "regexp" "runtime" "strings" "time" @@ -364,6 +365,11 @@ func computeMigratedConfig(path string, content []byte, changes *ChangesSet) ([] return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %w", path, err) } + err = migrateBranchColors(&rootNode, changes) + if err != nil { + return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %w", path, err) + } + // Add more migrations here... if reflect.DeepEqual(rootNode, originalCopy) { @@ -630,6 +636,43 @@ func migratePagersToDiffRenderers(rootNode *yaml.Node, changes *ChangesSet) erro }) } +// The deprecated gui.branchColors matched its keys against the part of a branch +// name before the first slash. Turn each key into a pattern that matches the +// same branches. If the file has a non-empty gui.branchColorPatterns, +// gui.branchColors was ignored, so remove it. +func migrateBranchColors(rootNode *yaml.Node, changes *ChangesSet) error { + return yaml_utils.TransformNode(rootNode, []string{"gui"}, func(guiNode *yaml.Node) error { + branchColorsKeyNode, branchColorsValueNode := yaml_utils.LookupKey(guiNode, "branchColors") + if branchColorsKeyNode == nil || branchColorsValueNode.Kind != yaml.MappingNode { + return nil + } + + patternsKeyNode, patternsValueNode := yaml_utils.LookupKey(guiNode, "branchColorPatterns") + if patternsKeyNode != nil { + switch { + case patternsValueNode.Kind == yaml.MappingNode && len(patternsValueNode.Content) > 0: + yaml_utils.RemoveKey(guiNode, "branchColors") + changes.Add("Removed 'gui.branchColors'; it had no effect because 'gui.branchColorPatterns' is set") + return nil + case patternsValueNode.Kind == yaml.MappingNode || patternsValueNode.Tag == "!!null": + yaml_utils.RemoveKey(guiNode, "branchColorPatterns") + default: + return nil + } + } + + branchColorsKeyNode.Value = "branchColorPatterns" + for i := 0; i < len(branchColorsValueNode.Content)-1; i += 2 { + keyNode := branchColorsValueNode.Content[i] + keyNode.Value = "^" + regexp.QuoteMeta(keyNode.Value) + "(/|$)" + keyNode.Tag = "!!str" + } + changes.Add("Converted 'gui.branchColors' to 'gui.branchColorPatterns'") + + return nil + }) +} + func hasNonNullScalarValue(node *yaml.Node) bool { return node != nil && node.Kind == yaml.ScalarNode && node.Tag != "!!null" && node.Value != "" } diff --git a/pkg/config/app_config_test.go b/pkg/config/app_config_test.go index 180f4b882..16e960828 100644 --- a/pkg/config/app_config_test.go +++ b/pkg/config/app_config_test.go @@ -821,3 +821,107 @@ func TestPagerMigration(t *testing.T) { }) } } + +func TestBranchColorsMigration(t *testing.T) { + scenarios := []struct { + name string + input string + expected string + expectedDidChange bool + expectedChanges []string + }{ + { + name: "No branchColors", + input: "gui:\n" + + " branchColorPatterns:\n" + + " '^docs/': blue\n", + expectedDidChange: false, + expectedChanges: []string{}, + }, + { + name: "branchColors is not an object", + input: "gui:\n" + + " branchColors: 5\n", + expectedDidChange: false, + expectedChanges: []string{}, + }, + { + name: "branchColors is converted to patterns in place", + input: "gui:\n" + + " scrollHeight: 2\n" + + " branchColors:\n" + + " feature: green\n" + + " v1.x: '#ff0000'\n" + + " 123: red\n" + + " mouseEvents: false\n", + expected: "gui:\n" + + " scrollHeight: 2\n" + + " branchColorPatterns:\n" + + " ^feature(/|$): green\n" + + " ^v1\\.x(/|$): '#ff0000'\n" + + " ^123(/|$): red\n" + + " mouseEvents: false\n", + expectedDidChange: true, + expectedChanges: []string{"Converted 'gui.branchColors' to 'gui.branchColorPatterns'"}, + }, + { + name: "branchColors is removed if branchColorPatterns is set", + input: "gui:\n" + + " branchColors:\n" + + " feature: green\n" + + " branchColorPatterns:\n" + + " '^docs/': blue\n", + expected: "gui:\n" + + " branchColorPatterns:\n" + + " '^docs/': blue\n", + expectedDidChange: true, + expectedChanges: []string{"Removed 'gui.branchColors'; it had no effect because 'gui.branchColorPatterns' is set"}, + }, + { + name: "branchColors replaces an empty branchColorPatterns", + input: "gui:\n" + + " branchColorPatterns: {}\n" + + " branchColors:\n" + + " feature: green\n", + expected: "gui:\n" + + " branchColorPatterns:\n" + + " ^feature(/|$): green\n", + expectedDidChange: true, + expectedChanges: []string{"Converted 'gui.branchColors' to 'gui.branchColorPatterns'"}, + }, + { + name: "branchColors replaces a null branchColorPatterns", + input: "gui:\n" + + " branchColorPatterns:\n" + + " branchColors:\n" + + " feature: green\n", + expected: "gui:\n" + + " branchColorPatterns:\n" + + " ^feature(/|$): green\n", + expectedDidChange: true, + expectedChanges: []string{"Converted 'gui.branchColors' to 'gui.branchColorPatterns'"}, + }, + { + name: "branchColors is kept if branchColorPatterns is not an object", + input: "gui:\n" + + " branchColorPatterns: 5\n" + + " branchColors:\n" + + " feature: green\n", + expectedDidChange: false, + expectedChanges: []string{}, + }, + } + + for _, s := range scenarios { + t.Run(s.name, func(t *testing.T) { + changes := NewChangesSet() + actual, didChange, err := computeMigratedConfig("path doesn't matter", []byte(s.input), changes) + assert.NoError(t, err) + assert.Equal(t, s.expectedDidChange, didChange) + if didChange { + assert.Equal(t, s.expected, string(actual)) + } + assert.Equal(t, s.expectedChanges, changes.ToSliceFromOldest()) + }) + } +} diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index d05808159..b25488e87 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -71,9 +71,6 @@ type GuiConfig struct { // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-author-color AuthorColors map[string]string `yaml:"authorColors"` // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color - // Deprecated: use branchColorPatterns instead - BranchColors map[string]string `yaml:"branchColors" jsonschema:"deprecated"` - // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color BranchColorPatterns map[string]string `yaml:"branchColorPatterns"` // Custom icons for filenames and file extensions // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-files-icon--color diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 3d04e160d..a5dafcdbc 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -526,12 +526,7 @@ func (gui *Gui) onUserConfigLoaded() error { icons.SetNerdFontsVersion("") } - if len(userConfig.Gui.BranchColorPatterns) > 0 { - presentation.SetCustomBranches(userConfig.Gui.BranchColorPatterns, true) - } else { - // Fall back to the deprecated branchColors config - presentation.SetCustomBranches(userConfig.Gui.BranchColors, false) - } + presentation.SetCustomBranches(userConfig.Gui.BranchColorPatterns) return nil } diff --git a/pkg/gui/presentation/branches.go b/pkg/gui/presentation/branches.go index f58f34dc6..18025ebd8 100644 --- a/pkg/gui/presentation/branches.go +++ b/pkg/gui/presentation/branches.go @@ -21,7 +21,6 @@ import ( type colorMatcher struct { patterns map[string]*style.TextStyle - isRegex bool // NOTE: this value is needed only until the deprecated branchColors config is removed and only regex color patterns are used } var colorPatterns *colorMatcher @@ -201,17 +200,9 @@ func GetBranchTextStyle(name string) style.TextStyle { } func (m *colorMatcher) match(name string) (*style.TextStyle, bool) { - if m.isRegex { - for pattern, style := range m.patterns { - if matched, _ := regexp.MatchString(pattern, name); matched { - return style, true - } - } - } else { - // old behavior using the deprecated branchColors behavior matching on branch type - branchType := strings.Split(name, "/")[0] - if value, ok := m.patterns[branchType]; ok { - return value, true + for pattern, style := range m.patterns { + if matched, _ := regexp.MatchString(pattern, name); matched { + return style, true } } @@ -271,10 +262,9 @@ func divergenceStr( return result } -func SetCustomBranches(customBranchColors map[string]string, isRegex bool) { +func SetCustomBranches(customBranchColors map[string]string) { colorPatterns = &colorMatcher{ patterns: utils.SetCustomColors(customBranchColors), - isRegex: isRegex, } } diff --git a/pkg/gui/presentation/branches_test.go b/pkg/gui/presentation/branches_test.go index 3d83ca0ba..60aca80f2 100644 --- a/pkg/gui/presentation/branches_test.go +++ b/pkg/gui/presentation/branches_test.go @@ -419,7 +419,7 @@ func Test_getBranchDisplayStrings(t *testing.T) { defer color.ForceSetColorLevel(oldColorLevel) c := common.NewDummyCommon() - SetCustomBranches(c.UserConfig().Gui.BranchColorPatterns, true) + SetCustomBranches(c.UserConfig().Gui.BranchColorPatterns) for i, s := range scenarios { icons.SetNerdFontsVersion(lo.Ternary(s.useIcons, "3", "")) diff --git a/schema-master/config.json b/schema-master/config.json index 9434f6016..f159995d8 100644 --- a/schema-master/config.json +++ b/schema-master/config.json @@ -537,13 +537,6 @@ "type": "object", "description": "See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-author-color" }, - "branchColors": { - "additionalProperties": { - "type": "string" - }, - "type": "object", - "description": "See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color\nDeprecated: use branchColorPatterns instead" - }, "branchColorPatterns": { "additionalProperties": { "type": "string"