refactor(git): add template capabilities

This commit is contained in:
Jan De Dobbeleer 2021-10-31 19:52:12 +01:00 committed by Jan De Dobbeleer
parent 0620fdc474
commit 64b63be4d2
2 changed files with 165 additions and 141 deletions

View file

@ -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
}

View file

@ -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)