From 64b63be4d2a8941fc7bf1fe92081e73ecf30949e Mon Sep 17 00:00:00 2001 From: Jan De Dobbeleer Date: Sun, 31 Oct 2021 19:52:12 +0100 Subject: [PATCH] refactor(git): add template capabilities --- src/segment_git.go | 148 +++++++++++++++++++++---------------- src/segment_git_test.go | 158 ++++++++++++++++++++-------------------- 2 files changed, 165 insertions(+), 141 deletions(-) diff --git a/src/segment_git.go b/src/segment_git.go index 36f9677d..e12514c3 100644 --- a/src/segment_git.go +++ b/src/segment_git.go @@ -9,29 +9,33 @@ import ( "gopkg.in/ini.v1" ) -type gitRepo struct { - working *gitStatus - staging *gitStatus - ahead int - behind int +// Repo represents a git repository +type Repo struct { + Working *GitStatus + Staging *GitStatus + Ahead int + Behind int HEAD string - upstream string - stashCount int + Upstream string + StashCount int + WorktreeCount int + gitWorkingFolder string // .git working folder, can be different of root if using worktree - isWorkTree bool - gitRootFolder string // .git root folder - worktreeCount int + isWorkTree bool + gitRootFolder string // .git root folder } -type gitStatus struct { - unmerged int - deleted int - added int - modified int - changed bool + +// GitStatus represents part of the status of a git repository +type GitStatus struct { + Unmerged int + Deleted int + Added int + Modified int + Changed bool } -func (s *gitStatus) string() string { +func (s *GitStatus) string() string { var status string stringIfValue := func(value int, prefix string) string { if value > 0 { @@ -39,17 +43,17 @@ func (s *gitStatus) string() string { } return "" } - status += stringIfValue(s.added, "+") - status += stringIfValue(s.modified, "~") - status += stringIfValue(s.deleted, "-") - status += stringIfValue(s.unmerged, "x") + status += stringIfValue(s.Added, "+") + status += stringIfValue(s.Modified, "~") + status += stringIfValue(s.Deleted, "-") + status += stringIfValue(s.Unmerged, "x") return status } type git struct { props *properties env environmentInfo - repo *gitRepo + repo *Repo } const ( @@ -139,7 +143,7 @@ func (g *git) enabled() bool { return false } - g.repo = &gitRepo{} + g.repo = &Repo{} if gitdir.isDir { g.repo.gitWorkingFolder = gitdir.path g.repo.gitRootFolder = gitdir.path @@ -185,12 +189,32 @@ func (g *git) string() string { if statusColorsEnabled { g.SetStatusColor() } + // use template if available + segmentTemplate := g.props.getString(SegmentTemplate, "") + if len(segmentTemplate) > 0 { + template := &textTemplate{ + Template: segmentTemplate, + Context: g, + Env: g.env, + } + text, err := template.render() + if err != nil { + return err.Error() + } + return text + } + // legacy render string if no template + // remove this for 6.0 + return g.renderDeprecatedString(displayStatus) +} + +func (g *git) renderDeprecatedString(displayStatus bool) string { if !displayStatus { return g.getPrettyHEADName() } buffer := new(bytes.Buffer) // remote (if available) - if g.repo.upstream != "" && g.props.getBool(DisplayUpstreamIcon, false) { + if g.repo.Upstream != "" && g.props.getBool(DisplayUpstreamIcon, false) { fmt.Fprintf(buffer, "%s", g.getUpstreamSymbol()) } // branchName @@ -198,20 +222,20 @@ func (g *git) string() string { if g.props.getBool(DisplayBranchStatus, true) { buffer.WriteString(g.getBranchStatus()) } - if g.repo.staging.changed { - fmt.Fprint(buffer, g.getStatusDetailString(g.repo.staging, StagingColor, LocalStagingIcon, " \uF046")) + if g.repo.Staging.Changed { + fmt.Fprint(buffer, g.getStatusDetailString(g.repo.Staging, StagingColor, LocalStagingIcon, " \uF046")) } - if g.repo.staging.changed && g.repo.working.changed { + if g.repo.Staging.Changed && g.repo.Working.Changed { fmt.Fprint(buffer, g.props.getString(StatusSeparatorIcon, " |")) } - if g.repo.working.changed { - fmt.Fprint(buffer, g.getStatusDetailString(g.repo.working, WorkingColor, LocalWorkingIcon, " \uF044")) + if g.repo.Working.Changed { + fmt.Fprint(buffer, g.getStatusDetailString(g.repo.Working, WorkingColor, LocalWorkingIcon, " \uF044")) } - if g.repo.stashCount != 0 { - fmt.Fprintf(buffer, " %s%d", g.props.getString(StashCountIcon, "\uF692 "), g.repo.stashCount) + if g.repo.StashCount != 0 { + fmt.Fprintf(buffer, " %s%d", g.props.getString(StashCountIcon, "\uF692 "), g.repo.StashCount) } - if g.repo.worktreeCount != 0 { - fmt.Fprintf(buffer, " %s%d", g.props.getString(WorktreeCountIcon, "\uf1bb "), g.repo.worktreeCount) + if g.repo.WorktreeCount != 0 { + fmt.Fprintf(buffer, " %s%d", g.props.getString(WorktreeCountIcon, "\uf1bb "), g.repo.WorktreeCount) } return buffer.String() } @@ -222,25 +246,25 @@ func (g *git) init(props *properties, env environmentInfo) { } func (g *git) getBranchStatus() string { - if g.repo.ahead > 0 && g.repo.behind > 0 { - return fmt.Sprintf(" %s%d %s%d", g.props.getString(BranchAheadIcon, "\u2191"), g.repo.ahead, g.props.getString(BranchBehindIcon, "\u2193"), g.repo.behind) + if g.repo.Ahead > 0 && g.repo.Behind > 0 { + return fmt.Sprintf(" %s%d %s%d", g.props.getString(BranchAheadIcon, "\u2191"), g.repo.Ahead, g.props.getString(BranchBehindIcon, "\u2193"), g.repo.Behind) } - if g.repo.ahead > 0 { - return fmt.Sprintf(" %s%d", g.props.getString(BranchAheadIcon, "\u2191"), g.repo.ahead) + if g.repo.Ahead > 0 { + return fmt.Sprintf(" %s%d", g.props.getString(BranchAheadIcon, "\u2191"), g.repo.Ahead) } - if g.repo.behind > 0 { - return fmt.Sprintf(" %s%d", g.props.getString(BranchBehindIcon, "\u2193"), g.repo.behind) + if g.repo.Behind > 0 { + return fmt.Sprintf(" %s%d", g.props.getString(BranchBehindIcon, "\u2193"), g.repo.Behind) } - if g.repo.behind == 0 && g.repo.ahead == 0 && g.repo.upstream != "" { + if g.repo.Behind == 0 && g.repo.Ahead == 0 && g.repo.Upstream != "" { return fmt.Sprintf(" %s", g.props.getString(BranchIdenticalIcon, "\u2261")) } - if g.repo.upstream == "" { + if g.repo.Upstream == "" { return fmt.Sprintf(" %s", g.props.getString(BranchGoneIcon, "\u2262")) } return "" } -func (g *git) getStatusDetailString(status *gitStatus, color, icon Property, defaultIcon string) string { +func (g *git) getStatusDetailString(status *GitStatus, color, icon Property, defaultIcon string) string { prefix := g.props.getString(icon, defaultIcon) foregroundColor := g.props.getColor(color, g.props.foreground) if !g.props.getBool(DisplayStatusDetail, true) { @@ -260,7 +284,7 @@ func (g *git) colorStatusString(prefix, status, color string) string { } func (g *git) getUpstreamSymbol() string { - upstream := replaceAllString("/.*", g.repo.upstream, "") + upstream := replaceAllString("/.*", g.repo.Upstream, "") url := g.getOriginURL(upstream) if strings.Contains(url, "github") { return g.props.getString(GithubIcon, "\uF408 ") @@ -280,22 +304,22 @@ func (g *git) getUpstreamSymbol() string { func (g *git) setGitStatus() { output := g.getGitCommandOutput("status", "-unormal", "--short", "--branch") splittedOutput := strings.Split(output, "\n") - g.repo.working = g.parseGitStats(splittedOutput, true) - g.repo.staging = g.parseGitStats(splittedOutput, false) + g.repo.Working = g.parseGitStats(splittedOutput, true) + g.repo.Staging = g.parseGitStats(splittedOutput, false) status := g.parseGitStatusInfo(splittedOutput[0]) if status["local"] != "" { - g.repo.ahead, _ = strconv.Atoi(status["ahead"]) - g.repo.behind, _ = strconv.Atoi(status["behind"]) + g.repo.Ahead, _ = strconv.Atoi(status["ahead"]) + g.repo.Behind, _ = strconv.Atoi(status["behind"]) if status["upstream_status"] != "gone" { - g.repo.upstream = status["upstream"] + g.repo.Upstream = status["upstream"] } } g.repo.HEAD = g.getGitHEADContext(status["local"]) if g.props.getBool(DisplayStashCount, false) { - g.repo.stashCount = g.getStashContext() + g.repo.StashCount = g.getStashContext() } if g.props.getBool(DisplayWorktreeCount, false) { - g.repo.worktreeCount = g.getWorktreeContext() + g.repo.WorktreeCount = g.getWorktreeContext() } } @@ -308,13 +332,13 @@ func (g *git) SetStatusColor() { } func (g *git) getStatusColor(defaultValue string) string { - if g.repo.staging.changed || g.repo.working.changed { + if g.repo.Staging.Changed || g.repo.Working.Changed { return g.props.getColor(LocalChangesColor, defaultValue) - } else if g.repo.ahead > 0 && g.repo.behind > 0 { + } else if g.repo.Ahead > 0 && g.repo.Behind > 0 { return g.props.getColor(AheadAndBehindColor, defaultValue) - } else if g.repo.ahead > 0 { + } else if g.repo.Ahead > 0 { return g.props.getColor(AheadColor, defaultValue) - } else if g.repo.behind > 0 { + } else if g.repo.Behind > 0 { return g.props.getColor(BehindColor, defaultValue) } return defaultValue @@ -463,8 +487,8 @@ func (g *git) getPrettyHEADName() string { return fmt.Sprintf("%s%s", g.props.getString(CommitIcon, "\uF417"), ref) } -func (g *git) parseGitStats(output []string, working bool) *gitStatus { - status := gitStatus{} +func (g *git) parseGitStats(output []string, working bool) *GitStatus { + status := GitStatus{} if len(output) <= 1 { return &status } @@ -479,19 +503,19 @@ func (g *git) parseGitStats(output []string, working bool) *gitStatus { switch code { case "?": if working { - status.added++ + status.Added++ } case "D": - status.deleted++ + status.Deleted++ case "A": - status.added++ + status.Added++ case "U": - status.unmerged++ + status.Unmerged++ case "M", "R", "C", "m": - status.modified++ + status.Modified++ } } - status.changed = status.added > 0 || status.deleted > 0 || status.modified > 0 || status.unmerged > 0 + status.Changed = status.Added > 0 || status.Deleted > 0 || status.Modified > 0 || status.Unmerged > 0 return &status } diff --git a/src/segment_git_test.go b/src/segment_git_test.go index 161725e8..d2597b02 100644 --- a/src/segment_git_test.go +++ b/src/segment_git_test.go @@ -129,7 +129,7 @@ func setupHEADContextEnv(context *detachedContext) *git { env.On("getRuntimeGOOS", nil).Return("unix") g := &git{ env: env, - repo: &gitRepo{ + repo: &Repo{ gitWorkingFolder: "", }, } @@ -382,7 +382,7 @@ func TestGetStashContextZeroEntries(t *testing.T) { env := new(MockedEnvironment) env.On("getFileContent", "/logs/refs/stash").Return(tc.StashContent) g := &git{ - repo: &gitRepo{ + repo: &Repo{ gitWorkingFolder: "", }, env: env, @@ -450,24 +450,24 @@ func TestParseGitBranchInfoRemoteGone(t *testing.T) { func TestGitStatusUnmerged(t *testing.T) { expected := " x1" - status := &gitStatus{ - unmerged: 1, + status := &GitStatus{ + Unmerged: 1, } assert.Equal(t, expected, status.string()) } func TestGitStatusUnmergedModified(t *testing.T) { expected := " ~3 x1" - status := &gitStatus{ - unmerged: 1, - modified: 3, + status := &GitStatus{ + Unmerged: 1, + Modified: 3, } assert.Equal(t, expected, status.string()) } func TestGitStatusEmpty(t *testing.T) { expected := "" - status := &gitStatus{} + status := &GitStatus{} assert.Equal(t, expected, status.string()) } @@ -485,11 +485,11 @@ func TestParseGitStatsWorking(t *testing.T) { " C change.go", } status := g.parseGitStats(output, true) - assert.Equal(t, 3, status.modified) - assert.Equal(t, 1, status.unmerged) - assert.Equal(t, 3, status.added) - assert.Equal(t, 1, status.deleted) - assert.True(t, status.changed) + assert.Equal(t, 3, status.Modified) + assert.Equal(t, 1, status.Unmerged) + assert.Equal(t, 3, status.Added) + assert.Equal(t, 1, status.Deleted) + assert.True(t, status.Changed) } func TestParseGitStatsStaging(t *testing.T) { @@ -506,34 +506,34 @@ func TestParseGitStatsStaging(t *testing.T) { "AC change.go", } status := g.parseGitStats(output, false) - assert.Equal(t, 1, status.modified) - assert.Equal(t, 0, status.unmerged) - assert.Equal(t, 1, status.added) - assert.Equal(t, 2, status.deleted) - assert.True(t, status.changed) + assert.Equal(t, 1, status.Modified) + assert.Equal(t, 0, status.Unmerged) + assert.Equal(t, 1, status.Added) + assert.Equal(t, 2, status.Deleted) + assert.True(t, status.Changed) } func TestParseGitStatsNoChanges(t *testing.T) { g := &git{} - expected := &gitStatus{} + expected := &GitStatus{} output := []string{ "## amazing-feat", } status := g.parseGitStats(output, false) assert.Equal(t, expected, status) - assert.False(t, status.changed) + assert.False(t, status.Changed) } func TestParseGitStatsInvalidLine(t *testing.T) { g := &git{} - expected := &gitStatus{} + expected := &GitStatus{} output := []string{ "## amazing-feat", "#", } status := g.parseGitStats(output, false) assert.Equal(t, expected, status) - assert.False(t, status.changed) + assert.False(t, status.Changed) } func bootstrapUpstreamTest(upstream string) *git { @@ -552,8 +552,8 @@ func bootstrapUpstreamTest(upstream string) *git { } g := &git{ env: env, - repo: &gitRepo{ - upstream: "origin/main", + repo: &Repo{ + Upstream: "origin/main", }, props: props, } @@ -596,9 +596,9 @@ func TestGetUpstreamSymbolGit(t *testing.T) { func TestGetStatusColorLocalChangesStaging(t *testing.T) { expected := changesColor - repo := &gitRepo{ - staging: &gitStatus{ - changed: true, + repo := &Repo{ + Staging: &GitStatus{ + Changed: true, }, } g := &git{ @@ -614,10 +614,10 @@ func TestGetStatusColorLocalChangesStaging(t *testing.T) { func TestGetStatusColorLocalChangesWorking(t *testing.T) { expected := changesColor - repo := &gitRepo{ - staging: &gitStatus{}, - working: &gitStatus{ - changed: true, + repo := &Repo{ + Staging: &GitStatus{}, + Working: &GitStatus{ + Changed: true, }, } g := &git{ @@ -633,11 +633,11 @@ func TestGetStatusColorLocalChangesWorking(t *testing.T) { func TestGetStatusColorAheadAndBehind(t *testing.T) { expected := changesColor - repo := &gitRepo{ - staging: &gitStatus{}, - working: &gitStatus{}, - ahead: 1, - behind: 3, + repo := &Repo{ + Staging: &GitStatus{}, + Working: &GitStatus{}, + Ahead: 1, + Behind: 3, } g := &git{ repo: repo, @@ -652,11 +652,11 @@ func TestGetStatusColorAheadAndBehind(t *testing.T) { func TestGetStatusColorAhead(t *testing.T) { expected := changesColor - repo := &gitRepo{ - staging: &gitStatus{}, - working: &gitStatus{}, - ahead: 1, - behind: 0, + repo := &Repo{ + Staging: &GitStatus{}, + Working: &GitStatus{}, + Ahead: 1, + Behind: 0, } g := &git{ repo: repo, @@ -671,11 +671,11 @@ func TestGetStatusColorAhead(t *testing.T) { func TestGetStatusColorBehind(t *testing.T) { expected := changesColor - repo := &gitRepo{ - staging: &gitStatus{}, - working: &gitStatus{}, - ahead: 0, - behind: 5, + repo := &Repo{ + Staging: &GitStatus{}, + Working: &GitStatus{}, + Ahead: 0, + Behind: 5, } g := &git{ repo: repo, @@ -690,11 +690,11 @@ func TestGetStatusColorBehind(t *testing.T) { func TestGetStatusColorDefault(t *testing.T) { expected := changesColor - repo := &gitRepo{ - staging: &gitStatus{}, - working: &gitStatus{}, - ahead: 0, - behind: 0, + repo := &Repo{ + Staging: &GitStatus{}, + Working: &GitStatus{}, + Ahead: 0, + Behind: 0, } g := &git{ repo: repo, @@ -709,9 +709,9 @@ func TestGetStatusColorDefault(t *testing.T) { func TestSetStatusColorForeground(t *testing.T) { expected := changesColor - repo := &gitRepo{ - staging: &gitStatus{ - changed: true, + repo := &Repo{ + Staging: &GitStatus{ + Changed: true, }, } g := &git{ @@ -731,9 +731,9 @@ func TestSetStatusColorForeground(t *testing.T) { func TestSetStatusColorBackground(t *testing.T) { expected := changesColor - repo := &gitRepo{ - staging: &gitStatus{ - changed: true, + repo := &Repo{ + Staging: &GitStatus{ + Changed: true, }, } g := &git{ @@ -770,9 +770,9 @@ func TestStatusColorsWithoutDisplayStatus(t *testing.T) { func TestGetStatusDetailStringDefault(t *testing.T) { expected := "icon +1" - status := &gitStatus{ - changed: true, - added: 1, + status := &GitStatus{ + Changed: true, + Added: 1, } g := &git{ props: &properties{ @@ -784,9 +784,9 @@ func TestGetStatusDetailStringDefault(t *testing.T) { func TestGetStatusDetailStringDefaultColorOverride(t *testing.T) { expected := "<#123456>icon +1" - status := &gitStatus{ - changed: true, - added: 1, + status := &GitStatus{ + Changed: true, + Added: 1, } g := &git{ props: &properties{ @@ -801,9 +801,9 @@ func TestGetStatusDetailStringDefaultColorOverride(t *testing.T) { func TestGetStatusDetailStringDefaultColorOverrideAndIconColorOverride(t *testing.T) { expected := "<#789123>work<#123456> +1" - status := &gitStatus{ - changed: true, - added: 1, + status := &GitStatus{ + Changed: true, + Added: 1, } g := &git{ props: &properties{ @@ -819,9 +819,9 @@ func TestGetStatusDetailStringDefaultColorOverrideAndIconColorOverride(t *testin func TestGetStatusDetailStringDefaultColorOverrideNoIconColorOverride(t *testing.T) { expected := "<#123456>work +1" - status := &gitStatus{ - changed: true, - added: 1, + status := &GitStatus{ + Changed: true, + Added: 1, } g := &git{ props: &properties{ @@ -837,9 +837,9 @@ func TestGetStatusDetailStringDefaultColorOverrideNoIconColorOverride(t *testing func TestGetStatusDetailStringNoStatus(t *testing.T) { expected := "icon" - status := &gitStatus{ - changed: true, - added: 1, + status := &GitStatus{ + Changed: true, + Added: 1, } g := &git{ props: &properties{ @@ -854,9 +854,9 @@ func TestGetStatusDetailStringNoStatus(t *testing.T) { func TestGetStatusDetailStringNoStatusColorOverride(t *testing.T) { expected := "<#123456>icon" - status := &gitStatus{ - changed: true, - added: 1, + status := &GitStatus{ + Changed: true, + Added: 1, } g := &git{ props: &properties{ @@ -896,10 +896,10 @@ func TestGetBranchStatus(t *testing.T) { BranchGoneIcon: "gone", }, }, - repo: &gitRepo{ - ahead: tc.Ahead, - behind: tc.Behind, - upstream: tc.Upstream, + repo: &Repo{ + Ahead: tc.Ahead, + Behind: tc.Behind, + Upstream: tc.Upstream, }, } assert.Equal(t, tc.Expected, g.getBranchStatus(), tc.Case)