Skip to content

Commit

Permalink
feat(git): branch_patterns
Browse files Browse the repository at this point in the history
Property that allows to extract some part, based on a regex, from the branch name.
  • Loading branch information
Zelnes committed Jan 29, 2025
1 parent 49b118d commit 34b0cdc
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/segments/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ const (
IgnoreSubmodules properties.Property = "ignore_submodules"
// MappedBranches allows overriding certain branches with an icon/text
MappedBranches properties.Property = "mapped_branches"
// BranchPatterns allows for a custom regexes to be used to extract parts of the branch name
BranchPatterns properties.Property = "branch_patterns"

DETACHED = "(detached)"
BRANCHPREFIX = "ref: refs/heads/"
Expand Down
50 changes: 50 additions & 0 deletions src/segments/scm.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package segments

import (
"fmt"
"regexp"
"strconv"
"strings"
"unicode/utf8"

Expand All @@ -14,8 +16,15 @@ const (
NativeFallback properties.Property = "native_fallback"
// Override the built-in status formats
StatusFormats properties.Property = "status_formats"
// Regex patterns to extract fields from the branch name pattern property
BranchPatternRegex string = `^(.*?)(?::([-+]?\d+)?)?$`
// Default group for branch pattern, when none is provided
BranchPatternDefaultGroup int = 0
)

// BranchPattern regex to extract fields
var /* const */ BranchPatternRegexExtractor = regexp.MustCompile(BranchPatternRegex)

// ScmStatus represents part of the status of a repository
type ScmStatus struct {
Formats map[string]string
Expand Down Expand Up @@ -102,6 +111,30 @@ const (
FullBranchPath properties.Property = "full_branch_path"
)

func extractBranchPatternFields(str string) (string, int) {
matches := BranchPatternRegexExtractor.FindStringSubmatch(str)

if len(matches) > 0 {
regex := matches[1]
var number int
var err error

if matches[2] != "" {
number, err = strconv.Atoi(matches[2])

if err != nil {
fmt.Printf("Error converting number: %v\n", err)
}
} else {
number = BranchPatternDefaultGroup // Default value if no number is provided
}

return regex, number
}

return "", 0
}

func (s *scm) formatBranch(branch string) string {
mappedBranches := s.props.GetKeyValueMap(MappedBranches, make(map[string]string))
for key, value := range mappedBranches {
Expand All @@ -119,6 +152,23 @@ func (s *scm) formatBranch(branch string) string {
break
}

branchPatterns := s.props.GetStringArray(BranchPatterns, []string{})
for _, str := range branchPatterns {
pattern, matchIdx := extractBranchPatternFields(str)

if pattern == "" || matchIdx < 0 {
continue
}

re := regexp.MustCompile(pattern)
matches := re.FindStringSubmatch(branch)

if len(matches) > matchIdx {
branch = matches[matchIdx]
break
}
}

fullBranchPath := s.props.GetBool(FullBranchPath, true)
if !fullBranchPath && strings.Contains(branch, "/") {
index := strings.LastIndex(branch, "/")
Expand Down
106 changes: 106 additions & 0 deletions src/segments/scm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,109 @@ func TestFormatBranch(t *testing.T) {
assert.Equal(t, tc.Expected, got, tc.Case)
}
}

func TestBranchPatterns(t *testing.T) {
cases := []struct {
Case string
Input string
BranchPatterns []string
MappedBranches map[string]string
Expected string
}{
{
Case: "No patterns",
Input: "main",
Expected: "main",
},
{
Case: "No match",
Input: "main",
BranchPatterns: []string{
"feature/(.*)",
},
Expected: "main",
},
{
Case: "Match",
Input: "feature/my-new-feature",
BranchPatterns: []string{
"feature/(.*)",
},
Expected: "feature/my-new-feature",
},
{
Case: "Match with index omitted",
Input: "feature/my-new-feature",
BranchPatterns: []string{
"feature/(.*):",
},
Expected: "feature/my-new-feature",
},
{
Case: "Match with index",
Input: "feature/my-new-feature",
BranchPatterns: []string{
"feature/(.*):1",
},
Expected: "my-new-feature",
},
{
Case: "Index not a number",
Input: "feature/my-new-feature",
BranchPatterns: []string{
"feature/(.*):not-a-number",
},
Expected: "feature/my-new-feature",
},
{
Case: "Match with index out of bounds",
Input: "feature/my-new-feature",
BranchPatterns: []string{
"feature/(.*):2",
},
Expected: "feature/my-new-feature",
},
{
Case: "Match with negative index",
Input: "feature/my-new-feature",
BranchPatterns: []string{
"feature/(.*):-2",
},
Expected: "feature/my-new-feature",
},
{
Case: "Match with multiple patterns",
Input: "feature/my-new-feature",
BranchPatterns: []string{
"no-match/(.*):1",
"(.*)/(.*):2",
},
Expected: "my-new-feature",
},
{
Case: "Branch mapping, with BranchMaxLength",
Input: "feat/PROJECT-123-with-long-name",
Expected: "🚀 PROJECT-123",
BranchPatterns: []string{
".* [A-Z0-9]+-[0-9]+",
},
MappedBranches: map[string]string{
"feat/*": "🚀 ",
"bug/*": "🐛 ",
},
},
}

for _, tc := range cases {
props := properties.Map{
BranchPatterns: tc.BranchPatterns,
MappedBranches: tc.MappedBranches,
}

g := &Git{}
g.Init(props, nil)

got := g.formatBranch(tc.Input)
assert.Equal(t, tc.Expected, got, tc.Case)
}
}
9 changes: 9 additions & 0 deletions themes/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,15 @@
"description": "Custom glyph/text for specific branches",
"default": {}
},
"branch_patterns": {
"type": "array",
"title": "Branch Patterns",
"description": "Regular expressions and match index (in the format \"pattern:matchIdx\") to match against the branch name. The match index 0 (default, when omitted) is the full match, 1 is the first capture group, etc.",
"default": [],
"items": {
"type": "string"
}
},
"cache_duration": {
"type": "string",
"title": "Cache duration",
Expand Down
4 changes: 4 additions & 0 deletions website/docs/segments/scm/git.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ import Config from "@site/src/components/Config.js";
"feat/*": "🚀 ",
"bug/*": "🐛 ",
},
branch_patterns: [
".* [A-Z0-9]+-[0-9]+"
],
},
}}
/>
Expand All @@ -63,6 +66,7 @@ You can set the following properties to `true` to enable fetching additional inf
| `status_formats` | `map[string]string` | | a key, value map allowing to override how individual status items are displayed. For example, `"status_formats": { "Added": "Added: %d" }` will display the added count as `Added: 1` instead of `+1`. See the [Status](#status) section for available overrides. |
| `source` | `string` | `cli` | <ul><li>`cli`: fetch the information using the git CLI</li><li>`pwsh`: fetch the information from the [posh-git][poshgit] PowerShell Module</li></ul> |
| `mapped_branches` | `object` | | custom glyph/text for specific branches. You can use `*` at the end as a wildcard character for matching |
| `branch_patterns` | `array` | | regular expressions and match index (that must be positive) in the format `"pattern:matchIdx"` to match against the branch name. The match index `0` (default, when omitted) is the full match, 1 is the first capture group, etc.<ul><li>Applied after `mapped_branches`</li><li>Pattern order matters</li></ul> |
| `full_branch_path` | `bool` | `true` | display the full branch path instead of only the last part (e.g. `feature/branch` instead of `branch`) |

### Icons
Expand Down

0 comments on commit 34b0cdc

Please sign in to comment.