diff --git a/src/segment_git.go b/src/segment_git.go index e12514c3..9a9fcbad 100644 --- a/src/segment_git.go +++ b/src/segment_git.go @@ -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 == "" { diff --git a/src/segment_git_test.go b/src/segment_git_test.go index d2597b02..26a89df5 100644 --- a/src/segment_git_test.go +++ b/src/segment_git_test.go @@ -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) + } +}