mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2025-01-13 20:27:28 -08:00
refactor(git): allow status in template
This commit is contained in:
parent
64b63be4d2
commit
f8fa3ae8af
|
@ -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 == "" {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue