refactor(git): allow status in template

This commit is contained in:
Jan De Dobbeleer 2021-10-31 20:34:13 +01:00 committed by Jan De Dobbeleer
parent 64b63be4d2
commit f8fa3ae8af
2 changed files with 162 additions and 73 deletions

View file

@ -11,21 +11,20 @@ import (
// Repo represents a git repository
type Repo struct {
Working *GitStatus
Staging *GitStatus
Ahead int
Behind int
HEAD string
Upstream string
StashCount int
WorktreeCount int
Working *GitStatus
Staging *GitStatus
Ahead int
Behind int
HEAD string
Upstream string
StashCount int
WorktreeCount int
IsWorkTree bool
gitWorkingFolder string // .git working folder, can be different of root if using worktree
isWorkTree bool
gitRootFolder string // .git root folder
gitRootFolder string // .git root folder
}
// GitStatus represents part of the status of a git repository
type GitStatus struct {
Unmerged int
@ -35,7 +34,37 @@ type GitStatus struct {
Changed bool
}
func (s *GitStatus) string() string {
func (s *GitStatus) parse(output []string, working bool) {
if len(output) <= 1 {
return
}
for _, line := range output[1:] {
if len(line) < 2 {
continue
}
code := line[0:1]
if working {
code = line[1:2]
}
switch code {
case "?":
if working {
s.Added++
}
case "D":
s.Deleted++
case "A":
s.Added++
case "U":
s.Unmerged++
case "M", "R", "C", "m":
s.Modified++
}
}
s.Changed = s.Added > 0 || s.Deleted > 0 || s.Modified > 0 || s.Unmerged > 0
}
func (s *GitStatus) String() string {
var status string
stringIfValue := func(value int, prefix string) string {
if value > 0 {
@ -57,10 +86,19 @@ type git struct {
}
const (
// BranchIcon the icon to use as branch indicator
BranchIcon Property = "branch_icon"
// DisplayBranchStatus show branch status or not
DisplayBranchStatus Property = "display_branch_status"
// DisplayStatus shows the status of the repository
DisplayStatus Property = "display_status"
// DisplayStashCount show stash count or not
DisplayStashCount Property = "display_stash_count"
// DisplayWorktreeCount show worktree count or not
DisplayWorktreeCount Property = "display_worktree_count"
// DisplayUpstreamIcon show or hide the upstream icon
DisplayUpstreamIcon Property = "display_upstream_icon"
// BranchIcon the icon to use as branch indicator
BranchIcon Property = "branch_icon"
// BranchIdenticalIcon the icon to display when the remote and local branch are identical
BranchIdenticalIcon Property = "branch_identical_icon"
// BranchAheadIcon the icon to display when the local branch is ahead of the remote
@ -73,10 +111,6 @@ const (
LocalWorkingIcon Property = "local_working_icon"
// LocalStagingIcon the icon to use as the local staging area changes indicator
LocalStagingIcon Property = "local_staged_icon"
// DisplayStatus shows the status of the repository
DisplayStatus Property = "display_status"
// DisplayStatusDetail shows the detailed status of the repository
DisplayStatusDetail Property = "display_status_detail"
// RebaseIcon shows before the rebase context
RebaseIcon Property = "rebase_icon"
// CherryPickIcon shows before the cherry-pick context
@ -89,16 +123,12 @@ const (
NoCommitsIcon Property = "no_commits_icon"
// TagIcon shows before the tag context
TagIcon Property = "tag_icon"
// DisplayStashCount show stash count or not
DisplayStashCount Property = "display_stash_count"
// StashCountIcon shows before the stash context
StashCountIcon Property = "stash_count_icon"
// StatusSeparatorIcon shows between staging and working area
StatusSeparatorIcon Property = "status_separator_icon"
// MergeIcon shows before the merge context
MergeIcon Property = "merge_icon"
// DisplayUpstreamIcon show or hide the upstream icon
DisplayUpstreamIcon Property = "display_upstream_icon"
// GithubIcon shows√ when upstream is github
GithubIcon Property = "github_icon"
// BitbucketIcon shows when upstream is bitbucket
@ -109,6 +139,11 @@ const (
GitlabIcon Property = "gitlab_icon"
// GitIcon shows when the upstream can't be identified
GitIcon Property = "git_icon"
// Deprecated
// DisplayStatusDetail shows the detailed status of the repository
DisplayStatusDetail Property = "display_status_detail"
// WorkingColor if set, the color to use on the working area
WorkingColor Property = "working_color"
// StagingColor if set, the color to use on the staging area
@ -125,8 +160,6 @@ const (
AheadColor Property = "ahead_color"
// BranchMaxLength truncates the length of the branch name
BranchMaxLength Property = "branch_max_length"
// DisplayWorktreeCount show worktree count or not
DisplayWorktreeCount Property = "display_worktree_count"
// WorktreeCountIcon shows before the worktree context
WorktreeCountIcon Property = "worktree_count_icon"
)
@ -143,7 +176,10 @@ func (g *git) enabled() bool {
return false
}
g.repo = &Repo{}
g.repo = &Repo{
Staging: &GitStatus{},
Working: &GitStatus{},
}
if gitdir.isDir {
g.repo.gitWorkingFolder = gitdir.path
g.repo.gitRootFolder = gitdir.path
@ -161,7 +197,7 @@ func (g *git) enabled() bool {
// :ind+5 = index + /.git
ind := strings.LastIndex(g.repo.gitWorkingFolder, "/.git/worktrees")
g.repo.gitRootFolder = g.repo.gitWorkingFolder[:ind+5]
g.repo.isWorkTree = true
g.repo.IsWorkTree = true
return true
}
return false
@ -194,7 +230,7 @@ func (g *git) string() string {
if len(segmentTemplate) > 0 {
template := &textTemplate{
Template: segmentTemplate,
Context: g,
Context: g.repo,
Env: g.env,
}
text, err := template.render()
@ -270,7 +306,7 @@ func (g *git) getStatusDetailString(status *GitStatus, color, icon Property, def
if !g.props.getBool(DisplayStatusDetail, true) {
return g.colorStatusString(prefix, "", foregroundColor)
}
return g.colorStatusString(prefix, status.string(), foregroundColor)
return g.colorStatusString(prefix, status.String(), foregroundColor)
}
func (g *git) colorStatusString(prefix, status, color string) string {
@ -304,8 +340,8 @@ 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.parse(splittedOutput, true)
g.repo.Staging.parse(splittedOutput, false)
status := g.parseGitStatusInfo(splittedOutput[0])
if status["local"] != "" {
g.repo.Ahead, _ = strconv.Atoi(status["ahead"])
@ -487,38 +523,6 @@ 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{}
if len(output) <= 1 {
return &status
}
for _, line := range output[1:] {
if len(line) < 2 {
continue
}
code := line[0:1]
if working {
code = line[1:2]
}
switch code {
case "?":
if working {
status.Added++
}
case "D":
status.Deleted++
case "A":
status.Added++
case "U":
status.Unmerged++
case "M", "R", "C", "m":
status.Modified++
}
}
status.Changed = status.Added > 0 || status.Deleted > 0 || status.Modified > 0 || status.Unmerged > 0
return &status
}
func (g *git) getStashContext() int {
stashContent := g.getGitFileContents(g.repo.gitRootFolder, "logs/refs/stash")
if stashContent == "" {

View file

@ -131,6 +131,8 @@ func setupHEADContextEnv(context *detachedContext) *git {
env: env,
repo: &Repo{
gitWorkingFolder: "",
Working: &GitStatus{},
Staging: &GitStatus{},
},
}
return g
@ -453,7 +455,7 @@ func TestGitStatusUnmerged(t *testing.T) {
status := &GitStatus{
Unmerged: 1,
}
assert.Equal(t, expected, status.string())
assert.Equal(t, expected, status.String())
}
func TestGitStatusUnmergedModified(t *testing.T) {
@ -462,17 +464,17 @@ func TestGitStatusUnmergedModified(t *testing.T) {
Unmerged: 1,
Modified: 3,
}
assert.Equal(t, expected, status.string())
assert.Equal(t, expected, status.String())
}
func TestGitStatusEmpty(t *testing.T) {
expected := ""
status := &GitStatus{}
assert.Equal(t, expected, status.string())
assert.Equal(t, expected, status.String())
}
func TestParseGitStatsWorking(t *testing.T) {
g := &git{}
status := &GitStatus{}
output := []string{
"## amazing-feat",
" M change.go",
@ -484,7 +486,7 @@ func TestParseGitStatsWorking(t *testing.T) {
" R change.go",
" C change.go",
}
status := g.parseGitStats(output, true)
status.parse(output, true)
assert.Equal(t, 3, status.Modified)
assert.Equal(t, 1, status.Unmerged)
assert.Equal(t, 3, status.Added)
@ -493,7 +495,7 @@ func TestParseGitStatsWorking(t *testing.T) {
}
func TestParseGitStatsStaging(t *testing.T) {
g := &git{}
status := &GitStatus{}
output := []string{
"## amazing-feat",
" M change.go",
@ -505,7 +507,7 @@ func TestParseGitStatsStaging(t *testing.T) {
"MR change.go",
"AC change.go",
}
status := g.parseGitStats(output, false)
status.parse(output, false)
assert.Equal(t, 1, status.Modified)
assert.Equal(t, 0, status.Unmerged)
assert.Equal(t, 1, status.Added)
@ -514,24 +516,24 @@ func TestParseGitStatsStaging(t *testing.T) {
}
func TestParseGitStatsNoChanges(t *testing.T) {
g := &git{}
status := &GitStatus{}
expected := &GitStatus{}
output := []string{
"## amazing-feat",
}
status := g.parseGitStats(output, false)
status.parse(output, false)
assert.Equal(t, expected, status)
assert.False(t, status.Changed)
}
func TestParseGitStatsInvalidLine(t *testing.T) {
g := &git{}
status := &GitStatus{}
expected := &GitStatus{}
output := []string{
"## amazing-feat",
"#",
}
status := g.parseGitStats(output, false)
status.parse(output, false)
assert.Equal(t, expected, status)
assert.False(t, status.Changed)
}
@ -990,3 +992,86 @@ func TestGetGitCommand(t *testing.T) {
assert.Equal(t, tc.Expected, g.getGitCommand(), tc.Case)
}
}
func TestGitTemplate(t *testing.T) {
cases := []struct {
Case string
Expected string
Template string
Repo *Repo
}{
{
Case: "Only HEAD name",
Expected: "main",
Template: "{{ .HEAD }}",
Repo: &Repo{
HEAD: "main",
Behind: 2,
},
},
{
Case: "Working area changes",
Expected: "main \uF044 +2 ~3",
Template: "{{ .HEAD }}{{ if .Working.Changed }} \uF044{{ .Working.String }}{{ end }}",
Repo: &Repo{
HEAD: "main",
Working: &GitStatus{
Added: 2,
Modified: 3,
Changed: true,
},
},
},
{
Case: "No working area changes",
Expected: "main",
Template: "{{ .HEAD }}{{ if .Working.Changed }} \uF044{{ .Working.String }}{{ end }}",
Repo: &Repo{
HEAD: "main",
Working: &GitStatus{
Changed: false,
},
},
},
{
Case: "Working and staging area changes",
Expected: "main \uF046 +5 ~1 \uF044 +2 ~3",
Template: "{{ .HEAD }}{{ if .Staging.Changed }} \uF046{{ .Staging.String }}{{ end }}{{ if .Working.Changed }} \uF044{{ .Working.String }}{{ end }}",
Repo: &Repo{
HEAD: "main",
Working: &GitStatus{
Added: 2,
Modified: 3,
Changed: true,
},
Staging: &GitStatus{
Added: 5,
Modified: 1,
Changed: true,
},
},
},
{
Case: "No local changes",
Expected: "main",
Template: "{{ .HEAD }}{{ if .Staging.Changed }} \uF046{{ .Staging.String }}{{ end }}{{ if .Working.Changed }} \uF044{{ .Working.String }}{{ end }}",
Repo: &Repo{
HEAD: "main",
Staging: &GitStatus{},
Working: &GitStatus{},
},
},
}
for _, tc := range cases {
g := &git{
props: &properties{
values: map[Property]interface{}{
SegmentTemplate: tc.Template,
},
},
repo: tc.Repo,
}
assert.Equal(t, tc.Expected, g.string(), tc.Case)
}
}