feat: Support multiple commit prefixes

This implementation, unlike that proposed in https://github.com/jesseduffield/lazygit/pull/4253
keeps the yaml schema easy, and does a migration from the single
elements to a sequence of elements.
This commit is contained in:
Chris McDonnell 2025-02-10 22:34:22 -05:00 committed by Stefan Haller
parent a7bfeca9c0
commit 2fa4ee2cac
14 changed files with 395 additions and 65 deletions

View file

@ -341,14 +341,6 @@ git:
# If true, do not allow force pushes # If true, do not allow force pushes
disableForcePushing: false disableForcePushing: false
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix
commitPrefix:
# pattern to match on. E.g. for 'feature/AB-123' to match on the AB-123 use "^\\w+\\/(\\w+-\\w+).*"
pattern: ""
# Replace directive. E.g. for 'feature/AB-123' to start the commit message with 'AB-123 ' use "[$1] "
replace: ""
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-branch-name-prefix # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-branch-name-prefix
branchPrefix: "" branchPrefix: ""
@ -922,27 +914,40 @@ Where:
## Predefined commit message prefix ## Predefined commit message prefix
In situations where certain naming pattern is used for branches and commits, pattern can be used to populate commit message with prefix that is parsed from the branch name. In situations where certain naming pattern is used for branches and commits, pattern can be used to populate commit message with prefix that is parsed from the branch name.
If you define multiple naming patterns, they will be attempted in order until one matches.
Example: Example hitting first match:
- Branch name: feature/AB-123 - Branch name: feature/AB-123
- Commit message: [AB-123] Adding feature - Generated commit message prefix: [AB-123]
Example hitting second match:
- Branch name: CD-456_fix_problem
- Generated commit message prefix: (CD-456)
```yaml ```yaml
git: git:
commitPrefix: commitPrefix:
pattern: "^\\w+\\/(\\w+-\\w+).*" - pattern: "^\\w+\\/(\\w+-\\w+).*"
replace: '[$1] ' replace: '[$1] '
- pattern: "^([^_]+)_.*" # Take all text prior to the first underscore
replace: '($1) '
``` ```
If you want repository-specific prefixes, you can map them with `commitPrefixes`. If you have both `commitPrefixes` defined and an entry in `commitPrefixes` for the current repo, the `commitPrefixes` entry is given higher precedence. Repository folder names must be an exact match. If you want repository-specific prefixes, you can map them with `commitPrefixes`. If you have both entries in `commitPrefix` defined and an repository match in `commitPrefixes` for the current repo, the `commitPrefixes` entries will be attempted first. Repository folder names must be an exact match.
```yaml ```yaml
git: git:
commitPrefixes: commitPrefixes:
my_project: # This is repository folder name my_project: # This is repository folder name
pattern: "^\\w+\\/(\\w+-\\w+).*" - pattern: "^\\w+\\/(\\w+-\\w+).*"
replace: '[$1] ' replace: '[$1] '
commitPrefix:
- pattern: "^(\\w+)-.*" # A more general match for any leading word
replace : '[$1] '
- pattern: ".*" # The final fallthrough regex that copies over the whole branch name
replace : '[$0] '
``` ```
> [!IMPORTANT] > [!IMPORTANT]

View file

@ -217,6 +217,26 @@ func loadUserConfig(configFiles []*ConfigFile, base *UserConfig) (*UserConfig, e
// from one container to another, or changing the type of a key (e.g. from bool // from one container to another, or changing the type of a key (e.g. from bool
// to an enum). // to an enum).
func migrateUserConfig(path string, content []byte) ([]byte, error) { func migrateUserConfig(path string, content []byte) ([]byte, error) {
changedContent, err := computeMigratedConfig(path, content)
if err != nil {
return nil, err
}
// Write config back if changed
if string(changedContent) != string(content) {
fmt.Println("Provided user config is deprecated but auto-fixable. Attempting to write fixed version back to file...")
if err := os.WriteFile(path, changedContent, 0o644); err != nil {
return nil, fmt.Errorf("While attempting to write back fixed user config to %s, an error occurred: %s", path, err)
}
fmt.Printf("Success. New config written to %s\n", path)
return changedContent, nil
}
return content, nil
}
// A pure function helper for testing purposes
func computeMigratedConfig(path string, content []byte) ([]byte, error) {
changedContent := content changedContent := content
pathsToReplace := []struct { pathsToReplace := []struct {
@ -241,19 +261,18 @@ func migrateUserConfig(path string, content []byte) ([]byte, error) {
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err) return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
} }
// Add more migrations here... changedContent, err = changeElementToSequence(changedContent, []string{"git", "commitPrefix"})
if err != nil {
// Write config back if changed return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
if string(changedContent) != string(content) {
fmt.Println("Provided user config is deprecated but auto-fixable. Attempting to write fixed version back to file...")
if err := os.WriteFile(path, changedContent, 0o644); err != nil {
return nil, fmt.Errorf("While attempting to write back fixed user config to %s, an error occurred: %s", path, err)
}
fmt.Printf("Success. New config written to %s\n", path)
return changedContent, nil
} }
return content, nil changedContent, err = changeCommitPrefixesMap(changedContent)
if err != nil {
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
}
// Add more migrations here...
return changedContent, nil
} }
func changeNullKeybindingsToDisabled(changedContent []byte) ([]byte, error) { func changeNullKeybindingsToDisabled(changedContent []byte) ([]byte, error) {
@ -267,6 +286,46 @@ func changeNullKeybindingsToDisabled(changedContent []byte) ([]byte, error) {
}) })
} }
func changeElementToSequence(changedContent []byte, path []string) ([]byte, error) {
return yaml_utils.TransformNode(changedContent, path, func(node *yaml.Node) (bool, error) {
if node.Kind == yaml.MappingNode {
nodeContentCopy := node.Content
node.Kind = yaml.SequenceNode
node.Value = ""
node.Tag = "!!seq"
node.Content = []*yaml.Node{{
Kind: yaml.MappingNode,
Content: nodeContentCopy,
}}
return true, nil
}
return false, nil
})
}
func changeCommitPrefixesMap(changedContent []byte) ([]byte, error) {
return yaml_utils.TransformNode(changedContent, []string{"git", "commitPrefixes"}, func(prefixesNode *yaml.Node) (bool, error) {
if prefixesNode.Kind == yaml.MappingNode {
for _, contentNode := range prefixesNode.Content {
if contentNode.Kind == yaml.MappingNode {
nodeContentCopy := contentNode.Content
contentNode.Kind = yaml.SequenceNode
contentNode.Value = ""
contentNode.Tag = "!!seq"
contentNode.Content = []*yaml.Node{{
Kind: yaml.MappingNode,
Content: nodeContentCopy,
}}
}
}
return true, nil
}
return false, nil
})
}
func (c *AppConfig) GetDebug() bool { func (c *AppConfig) GetDebug() bool {
return c.debug return c.debug
} }

View file

@ -0,0 +1,78 @@
package config
import (
"testing"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)
func TestCommitPrefixMigrations(t *testing.T) {
scenarios := []struct {
name string
input string
expected string
}{
{
"Empty String",
"",
"",
}, {
"Single CommitPrefix Rename",
`
git:
commitPrefix:
pattern: "^\\w+-\\w+.*"
replace: '[JIRA $0] '`,
`
git:
commitPrefix:
- pattern: "^\\w+-\\w+.*"
replace: '[JIRA $0] '`,
}, {
"Complicated CommitPrefixes Rename",
`
git:
commitPrefixes:
foo:
pattern: "^\\w+-\\w+.*"
replace: '[OTHER $0] '
CrazyName!@#$^*&)_-)[[}{f{[]:
pattern: "^foo.bar*"
replace: '[FUN $0] '`,
`
git:
commitPrefixes:
foo:
- pattern: "^\\w+-\\w+.*"
replace: '[OTHER $0] '
CrazyName!@#$^*&)_-)[[}{f{[]:
- pattern: "^foo.bar*"
replace: '[FUN $0] '`,
}, {
"Incomplete Configuration",
"git:",
"git:",
},
}
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
expectedConfig := GetDefaultConfig()
err := yaml.Unmarshal([]byte(s.expected), expectedConfig)
if err != nil {
t.Error(err)
}
actual, err := computeMigratedConfig("path doesn't matter", []byte(s.input))
if err != nil {
t.Error(err)
}
actualConfig := GetDefaultConfig()
err = yaml.Unmarshal(actual, actualConfig)
if err != nil {
t.Error(err)
}
assert.Equal(t, expectedConfig, actualConfig)
})
}
}

View file

@ -256,9 +256,9 @@ type GitConfig struct {
// If true, do not allow force pushes // If true, do not allow force pushes
DisableForcePushing bool `yaml:"disableForcePushing"` DisableForcePushing bool `yaml:"disableForcePushing"`
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix
CommitPrefix *CommitPrefixConfig `yaml:"commitPrefix"` CommitPrefix []CommitPrefixConfig `yaml:"commitPrefix"`
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix
CommitPrefixes map[string]CommitPrefixConfig `yaml:"commitPrefixes"` CommitPrefixes map[string][]CommitPrefixConfig `yaml:"commitPrefixes"`
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-branch-name-prefix // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-branch-name-prefix
BranchPrefix string `yaml:"branchPrefix"` BranchPrefix string `yaml:"branchPrefix"`
// If true, parse emoji strings in commit messages e.g. render :rocket: as 🚀 // If true, parse emoji strings in commit messages e.g. render :rocket: as 🚀
@ -784,7 +784,7 @@ func GetDefaultConfig() *UserConfig {
BranchLogCmd: "git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --", BranchLogCmd: "git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --",
AllBranchesLogCmd: "git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium", AllBranchesLogCmd: "git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium",
DisableForcePushing: false, DisableForcePushing: false,
CommitPrefixes: map[string]CommitPrefixConfig(nil), CommitPrefixes: map[string][]CommitPrefixConfig(nil),
BranchPrefix: "", BranchPrefix: "",
ParseEmoji: false, ParseEmoji: false,
TruncateCopiedCommitHashesTo: 12, TruncateCopiedCommitHashesTo: 12,

View file

@ -152,8 +152,8 @@ func (self *WorkingTreeHelper) HandleCommitPress() error {
message := self.c.Contexts().CommitMessage.GetPreservedMessageAndLogError() message := self.c.Contexts().CommitMessage.GetPreservedMessageAndLogError()
if message == "" { if message == "" {
commitPrefixConfig := self.commitPrefixConfigForRepo() commitPrefixConfigs := self.commitPrefixConfigsForRepo()
if commitPrefixConfig != nil { for _, commitPrefixConfig := range commitPrefixConfigs {
prefixPattern := commitPrefixConfig.Pattern prefixPattern := commitPrefixConfig.Pattern
prefixReplace := commitPrefixConfig.Replace prefixReplace := commitPrefixConfig.Replace
branchName := self.refHelper.GetCheckedOutRef().Name branchName := self.refHelper.GetCheckedOutRef().Name
@ -165,6 +165,7 @@ func (self *WorkingTreeHelper) HandleCommitPress() error {
if rgx.MatchString(branchName) { if rgx.MatchString(branchName) {
prefix := rgx.ReplaceAllString(branchName, prefixReplace) prefix := rgx.ReplaceAllString(branchName, prefixReplace)
message = prefix message = prefix
break
} }
} }
} }
@ -228,11 +229,11 @@ func (self *WorkingTreeHelper) prepareFilesForCommit() error {
return nil return nil
} }
func (self *WorkingTreeHelper) commitPrefixConfigForRepo() *config.CommitPrefixConfig { func (self *WorkingTreeHelper) commitPrefixConfigsForRepo() []config.CommitPrefixConfig {
cfg, ok := self.c.UserConfig().Git.CommitPrefixes[self.c.Git().RepoPaths.RepoName()] cfg, ok := self.c.UserConfig().Git.CommitPrefixes[self.c.Git().RepoPaths.RepoName()]
if ok { if ok {
return &cfg return append(cfg, self.c.UserConfig().Git.CommitPrefix...)
} else {
return self.c.UserConfig().Git.CommitPrefix
} }
return self.c.UserConfig().Git.CommitPrefix
} }

View file

@ -10,7 +10,7 @@ var CommitWipWithPrefix = NewIntegrationTest(NewIntegrationTestArgs{
ExtraCmdArgs: []string{}, ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(cfg *config.AppConfig) { SetupConfig: func(cfg *config.AppConfig) {
cfg.GetUserConfig().Git.CommitPrefixes = map[string]config.CommitPrefixConfig{"repo": {Pattern: "^\\w+\\/(\\w+-\\w+).*", Replace: "[$1]: "}} cfg.GetUserConfig().Git.CommitPrefixes = map[string][]config.CommitPrefixConfig{"repo": {{Pattern: "^\\w+\\/(\\w+-\\w+).*", Replace: "[$1]: "}}}
}, },
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {
shell.NewBranch("feature/TEST-002") shell.NewBranch("feature/TEST-002")

View file

@ -0,0 +1,53 @@
package commit
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var CommitWithFallthroughPrefix = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Commit with multiple CommitPrefixConfig",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(cfg *config.AppConfig) {
cfg.GetUserConfig().Git.CommitPrefix = []config.CommitPrefixConfig{
{Pattern: "^doesntmatch-(\\w+).*", Replace: "[BAD $1]: "},
{Pattern: "^\\w+\\/(\\w+-\\w+).*", Replace: "[GOOD $1]: "},
}
cfg.GetUserConfig().Git.CommitPrefixes = map[string][]config.CommitPrefixConfig{
"DifferentProject": {{Pattern: "^otherthatdoesn'tmatch-(\\w+).*", Replace: "[BAD $1]: "}},
}
},
SetupRepo: func(shell *Shell) {
shell.NewBranch("feature/TEST-001")
shell.CreateFile("test-commit-prefix", "This is foo bar")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
IsEmpty()
t.Views().Files().
IsFocused().
PressPrimaryAction().
Press(keys.Files.CommitChanges)
t.ExpectPopup().CommitMessagePanel().
Title(Equals("Commit summary")).
InitialText(Equals("[GOOD TEST-001]: ")).
Type("my commit message").
Cancel()
t.Views().Files().
IsFocused().
Press(keys.Files.CommitChanges)
t.ExpectPopup().CommitMessagePanel().
Title(Equals("Commit summary")).
InitialText(Equals("[GOOD TEST-001]: my commit message")).
Type(". Added something else").
Confirm()
t.Views().Commits().Focus()
t.Views().Main().Content(Contains("[GOOD TEST-001]: my commit message. Added something else"))
},
})

View file

@ -10,7 +10,7 @@ var CommitWithGlobalPrefix = NewIntegrationTest(NewIntegrationTestArgs{
ExtraCmdArgs: []string{}, ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(cfg *config.AppConfig) { SetupConfig: func(cfg *config.AppConfig) {
cfg.GetUserConfig().Git.CommitPrefix = &config.CommitPrefixConfig{Pattern: "^\\w+\\/(\\w+-\\w+).*", Replace: "[$1]: "} cfg.GetUserConfig().Git.CommitPrefix = []config.CommitPrefixConfig{{Pattern: "^\\w+\\/(\\w+-\\w+).*", Replace: "[$1]: "}}
}, },
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {
shell.NewBranch("feature/TEST-001") shell.NewBranch("feature/TEST-001")

View file

@ -10,10 +10,10 @@ var CommitWithNonMatchingBranchName = NewIntegrationTest(NewIntegrationTestArgs{
ExtraCmdArgs: []string{}, ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(cfg *config.AppConfig) { SetupConfig: func(cfg *config.AppConfig) {
cfg.GetUserConfig().Git.CommitPrefix = &config.CommitPrefixConfig{ cfg.GetUserConfig().Git.CommitPrefix = []config.CommitPrefixConfig{{
Pattern: "^\\w+\\/(\\w+-\\w+).*", Pattern: "^\\w+\\/(\\w+-\\w+).*",
Replace: "[$1]: ", Replace: "[$1]: ",
} }}
}, },
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {
shell.NewBranch("branchnomatch") shell.NewBranch("branchnomatch")

View file

@ -10,11 +10,11 @@ var CommitWithPrefix = NewIntegrationTest(NewIntegrationTestArgs{
ExtraCmdArgs: []string{}, ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(cfg *config.AppConfig) { SetupConfig: func(cfg *config.AppConfig) {
cfg.GetUserConfig().Git.CommitPrefixes = map[string]config.CommitPrefixConfig{ cfg.GetUserConfig().Git.CommitPrefixes = map[string][]config.CommitPrefixConfig{
"repo": { "repo": {{
Pattern: `^\w+/(\w+-\w+).*`, Pattern: `^\w+/(\w+-\w+).*`,
Replace: "[$1]: ", Replace: "[$1]: ",
}, }},
} }
}, },
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View file

@ -93,6 +93,7 @@ var tests = []*components.IntegrationTest{
commit.CommitMultiline, commit.CommitMultiline,
commit.CommitSwitchToEditor, commit.CommitSwitchToEditor,
commit.CommitWipWithPrefix, commit.CommitWipWithPrefix,
commit.CommitWithFallthroughPrefix,
commit.CommitWithGlobalPrefix, commit.CommitWithGlobalPrefix,
commit.CommitWithNonMatchingBranchName, commit.CommitWithNonMatchingBranchName,
commit.CommitWithPrefix, commit.CommitWithPrefix,

View file

@ -99,6 +99,55 @@ func lookupKey(node *yaml.Node, key string) (*yaml.Node, *yaml.Node) {
return nil, nil return nil, nil
} }
// Walks a yaml document to the specified path, and then applies the transformation to that node.
//
// The transform must return true if it made changes to the node.
// If the requested path is not defined in the document, no changes are made to the document.
//
// If no changes are made, the original document is returned.
// If changes are made, a newly marshalled document is returned. (This may result in different indentation for all nodes)
func TransformNode(yamlBytes []byte, path []string, transform func(node *yaml.Node) (bool, error)) ([]byte, error) {
// Parse the YAML file.
var node yaml.Node
err := yaml.Unmarshal(yamlBytes, &node)
if err != nil {
return nil, fmt.Errorf("failed to parse YAML: %w", err)
}
// Empty document: nothing to do.
if len(node.Content) == 0 {
return yamlBytes, nil
}
body := node.Content[0]
if didTransform, err := transformNode(body, path, transform); err != nil || !didTransform {
return yamlBytes, err
}
// Convert the updated YAML node back to YAML bytes.
updatedYAMLBytes, err := yaml.Marshal(body)
if err != nil {
return nil, fmt.Errorf("failed to convert YAML node to bytes: %w", err)
}
return updatedYAMLBytes, nil
}
// A recursive function to walk down the tree. See TransformNode for more details.
func transformNode(node *yaml.Node, path []string, transform func(node *yaml.Node) (bool, error)) (bool, error) {
if len(path) == 0 {
return transform(node)
}
keyNode, valueNode := lookupKey(node, path[0])
if keyNode == nil {
return false, nil
}
return transformNode(valueNode, path[1:], transform)
}
// takes a yaml document in bytes, a path to a key, and a new name for the key. // takes a yaml document in bytes, a path to a key, and a new name for the key.
// Will rename the key to the new name if it exists, and do nothing otherwise. // Will rename the key to the new name if it exists, and do nothing otherwise.
func RenameYamlKey(yamlBytes []byte, path []string, newKey string) ([]byte, error) { func RenameYamlKey(yamlBytes []byte, path []string, newKey string) ([]byte, error) {
@ -106,7 +155,7 @@ func RenameYamlKey(yamlBytes []byte, path []string, newKey string) ([]byte, erro
var node yaml.Node var node yaml.Node
err := yaml.Unmarshal(yamlBytes, &node) err := yaml.Unmarshal(yamlBytes, &node)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse YAML: %w", err) return nil, fmt.Errorf("failed to parse YAML: %w for bytes %s", err, string(yamlBytes))
} }
// Empty document: nothing to do. // Empty document: nothing to do.

View file

@ -1,6 +1,7 @@
package yaml_utils package yaml_utils
import ( import (
"fmt"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -314,3 +315,80 @@ func TestWalk_inPlaceChanges(t *testing.T) {
}) })
} }
} }
func TestTransformNode(t *testing.T) {
transformIntValueToString := func(node *yaml.Node) (bool, error) {
if node.Kind == yaml.ScalarNode {
if node.ShortTag() == "!!int" {
node.Tag = "!!str"
return true, nil
} else if node.ShortTag() == "!!str" {
// We have already transformed it,
return false, nil
} else {
return false, fmt.Errorf("Node was of bad type")
}
} else {
return false, fmt.Errorf("Node was not a scalar")
}
}
tests := []struct {
name string
in string
path []string
transform func(node *yaml.Node) (bool, error)
expectedOut string
}{
{
name: "Path not present",
in: "foo: 1",
path: []string{"bar"},
transform: transformIntValueToString,
expectedOut: "foo: 1",
},
{
name: "Part of path present",
in: `
foo:
bar: 2`,
path: []string{"foo", "baz"},
transform: transformIntValueToString,
expectedOut: `
foo:
bar: 2`,
},
{
name: "Successfully Transforms to string",
in: `
foo:
bar: 2`,
path: []string{"foo", "bar"},
transform: transformIntValueToString,
expectedOut: `foo:
bar: "2"
`, // Note the indentiation change and newlines because of how it re-marshalls
},
{
name: "Does nothing when already transformed",
in: `
foo:
bar: "2"`,
path: []string{"foo", "bar"},
transform: transformIntValueToString,
expectedOut: `
foo:
bar: "2"`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result, err := TransformNode([]byte(test.in), test.path, test.transform)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, test.expectedOut, string(result))
})
}
}

View file

@ -638,28 +638,7 @@
"default": false "default": false
}, },
"commitPrefix": { "commitPrefix": {
"properties": { "items": {
"pattern": {
"type": "string",
"description": "pattern to match on. E.g. for 'feature/AB-123' to match on the AB-123 use \"^\\\\w+\\\\/(\\\\w+-\\\\w+).*\"",
"examples": [
"^\\w+\\/(\\w+-\\w+).*"
]
},
"replace": {
"type": "string",
"description": "Replace directive. E.g. for 'feature/AB-123' to start the commit message with 'AB-123 ' use \"[$1] \"",
"examples": [
"[$1]"
]
}
},
"additionalProperties": false,
"type": "object",
"description": "See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix"
},
"commitPrefixes": {
"additionalProperties": {
"properties": { "properties": {
"pattern": { "pattern": {
"type": "string", "type": "string",
@ -679,6 +658,33 @@
"additionalProperties": false, "additionalProperties": false,
"type": "object" "type": "object"
}, },
"type": "array",
"description": "See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix"
},
"commitPrefixes": {
"additionalProperties": {
"items": {
"properties": {
"pattern": {
"type": "string",
"description": "pattern to match on. E.g. for 'feature/AB-123' to match on the AB-123 use \"^\\\\w+\\\\/(\\\\w+-\\\\w+).*\"",
"examples": [
"^\\w+\\/(\\w+-\\w+).*"
]
},
"replace": {
"type": "string",
"description": "Replace directive. E.g. for 'feature/AB-123' to start the commit message with 'AB-123 ' use \"[$1] \"",
"examples": [
"[$1]"
]
}
},
"additionalProperties": false,
"type": "object"
},
"type": "array"
},
"type": "object", "type": "object",
"description": "See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix" "description": "See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix"
}, },