From 415015c66ab99600193f161c711df0e843ac5d0a Mon Sep 17 00:00:00 2001 From: Henry Maddocks Date: Sun, 10 May 2026 12:29:17 +0200 Subject: [PATCH] Pull git-flow prefix parsing into a config-level helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lift the inline parsing in FinishCmdObj into parseGitFlowPrefixMap on ConfigCommands. The caller now does a direct map lookup against the parsed prefix → branchType map instead of iterating the raw config output and suffix-matching. This is preparation for adding git-flow-next support, which needs to merge a second config schema into the same map. One incidental change: a branch name without a slash now returns NotAGitFlowBranch immediately, rather than falling through the line loop with an empty suffix. Previously a configured gitflow.prefix.X whose value happened to equal the entire branch name could match — never a useful outcome. --- pkg/commands/git_commands/config.go | 37 ++++++++++ pkg/commands/git_commands/config_test.go | 94 ++++++++++++++++++++++++ pkg/commands/git_commands/flow.go | 24 ++---- 3 files changed, 137 insertions(+), 18 deletions(-) create mode 100644 pkg/commands/git_commands/config_test.go diff --git a/pkg/commands/git_commands/config.go b/pkg/commands/git_commands/config.go index a72fe504c..1332c0cd1 100644 --- a/pkg/commands/git_commands/config.go +++ b/pkg/commands/git_commands/config.go @@ -1,6 +1,7 @@ package git_commands import ( + "regexp" "strings" "github.com/jesseduffield/lazygit/pkg/commands/git_config" @@ -116,6 +117,42 @@ func (self *ConfigCommands) GetGitFlowPrefixes() string { return self.gitConfig.GetGeneral("--local --get-regexp gitflow.prefix") } +// parseGitFlowPrefixMap parses git-flow config output into a prefix → branchType map. +// Line format: "gitflow.prefix. ". Prefixes are normalized to end in "/". +func parseGitFlowPrefixMap(legacyOutput string) map[string]string { + legacyRegexp := regexp.MustCompile(`gitflow\.prefix\.(\S+)\s+(.*)`) + prefixToType := make(map[string]string) + for line := range strings.SplitSeq(legacyOutput, "\n") { + line = strings.TrimSpace(line) + if line == "" { + continue + } + if m := legacyRegexp.FindStringSubmatch(line); len(m) == 3 { + prefix := normalizeGitFlowPrefix(m[2]) + if prefix == "" { + continue + } + prefixToType[prefix] = m[1] + } + } + return prefixToType +} + +func normalizeGitFlowPrefix(prefix string) string { + prefix = strings.TrimSpace(prefix) + if prefix == "" { + return "" + } + if !strings.HasSuffix(prefix, "/") { + return prefix + "/" + } + return prefix +} + +func (self *ConfigCommands) GetGitFlowPrefixMap() map[string]string { + return parseGitFlowPrefixMap(self.GetGitFlowPrefixes()) +} + func (self *ConfigCommands) GetCoreCommentChar() byte { if commentCharStr := self.gitConfig.Get("core.commentChar"); len(commentCharStr) == 1 { return commentCharStr[0] diff --git a/pkg/commands/git_commands/config_test.go b/pkg/commands/git_commands/config_test.go new file mode 100644 index 000000000..42369da10 --- /dev/null +++ b/pkg/commands/git_commands/config_test.go @@ -0,0 +1,94 @@ +package git_commands + +import ( + "testing" + + "github.com/jesseduffield/lazygit/pkg/commands/git_config" + "github.com/jesseduffield/lazygit/pkg/common" + "github.com/stretchr/testify/assert" +) + +func TestParseGitFlowPrefixMap(t *testing.T) { + type scenario struct { + testName string + legacyOutput string + expected map[string]string + } + scenarios := []scenario{ + { + testName: "empty input", + legacyOutput: "", + expected: map[string]string{}, + }, + { + testName: "feature and hotfix", + legacyOutput: "gitflow.prefix.feature feature/\ngitflow.prefix.hotfix hotfix/", + expected: map[string]string{"feature/": "feature", "hotfix/": "hotfix"}, + }, + { + testName: "prefix normalized with trailing slash", + legacyOutput: "gitflow.prefix.feature feature", + expected: map[string]string{"feature/": "feature"}, + }, + { + testName: "malformed lines skipped", + legacyOutput: "gitflow.prefix.feature feature/\nnot-a-valid-line\ngitflow.prefix.hotfix hotfix/", + expected: map[string]string{"feature/": "feature", "hotfix/": "hotfix"}, + }, + { + testName: "blank lines and whitespace ignored", + legacyOutput: " \n gitflow.prefix.feature feature/ \n \n ", + expected: map[string]string{"feature/": "feature"}, + }, + } + for _, s := range scenarios { + t.Run(s.testName, func(t *testing.T) { + got := parseGitFlowPrefixMap(s.legacyOutput) + assert.Equal(t, s.expected, got) + }) + } +} + +func TestGetGitFlowPrefixMap(t *testing.T) { + type scenario struct { + testName string + gitConfigMockResponses map[string]string + expected map[string]string + } + scenarios := []scenario{ + { + testName: "empty when no config", + gitConfigMockResponses: nil, + expected: map[string]string{}, + }, + { + testName: "correct map from legacy output", + gitConfigMockResponses: map[string]string{ + "--local --get-regexp gitflow.prefix": "gitflow.prefix.feature feature/\ngitflow.prefix.hotfix hotfix/", + }, + expected: map[string]string{"feature/": "feature", "hotfix/": "hotfix"}, + }, + { + testName: "prefix normalized with trailing slash", + gitConfigMockResponses: map[string]string{ + "--local --get-regexp gitflow.prefix": "gitflow.prefix.feature feature", + }, + expected: map[string]string{"feature/": "feature"}, + }, + { + testName: "malformed lines skipped", + gitConfigMockResponses: map[string]string{ + "--local --get-regexp gitflow.prefix": "gitflow.prefix.feature feature/\nnot-a-valid-line\n", + }, + expected: map[string]string{"feature/": "feature"}, + }, + } + + for _, s := range scenarios { + t.Run(s.testName, func(t *testing.T) { + config := NewConfigCommands(common.NewDummyCommon(), git_config.NewFakeGitConfig(s.gitConfigMockResponses)) + got := config.GetGitFlowPrefixMap() + assert.Equal(t, s.expected, got) + }) + } +} diff --git a/pkg/commands/git_commands/flow.go b/pkg/commands/git_commands/flow.go index fc00c11a1..985b6457f 100644 --- a/pkg/commands/git_commands/flow.go +++ b/pkg/commands/git_commands/flow.go @@ -1,7 +1,6 @@ package git_commands import ( - "regexp" "strings" "github.com/go-errors/errors" @@ -25,26 +24,15 @@ func (self *FlowCommands) GitFlowEnabled() bool { } func (self *FlowCommands) FinishCmdObj(branchName string) (*oscommands.CmdObj, error) { - prefixes := self.config.GetGitFlowPrefixes() + prefixMap := self.config.GetGitFlowPrefixMap() - // need to find out what kind of branch this is - prefix := strings.SplitAfterN(branchName, "/", 2)[0] - suffix := strings.Replace(branchName, prefix, "", 1) - - branchType := "" - for line := range strings.SplitSeq(strings.TrimSpace(prefixes), "\n") { - if strings.HasPrefix(line, "gitflow.prefix.") && strings.HasSuffix(line, prefix) { - - regex := regexp.MustCompile("gitflow.prefix.([^ ]*) .*") - matches := regex.FindAllStringSubmatch(line, 1) - - if len(matches) > 0 && len(matches[0]) > 1 { - branchType = matches[0][1] - break - } - } + prefixPart, suffix, ok := strings.Cut(branchName, "/") + if !ok || prefixPart == "" || suffix == "" { + return nil, errors.New(self.Tr.NotAGitFlowBranch) } + prefix := prefixPart + "/" + branchType := prefixMap[prefix] if branchType == "" { return nil, errors.New(self.Tr.NotAGitFlowBranch) }