feat(git): display worktree count

resolves #913
This commit is contained in:
Laurent Nullens 2021-09-04 20:32:55 +02:00 committed by GitHub
parent e422e87384
commit bb246377ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 73 additions and 26 deletions

View file

@ -61,10 +61,12 @@ An alternative is to use the [Posh-Git segment][poshgit]
- display_status: `boolean` - display the local changes or not - defaults to `false` - display_status: `boolean` - display the local changes or not - defaults to `false`
- display_status_detail: `boolean` - display the local changes in detail or not - defaults to `true` - display_status_detail: `boolean` - display the local changes in detail or not - defaults to `true`
- display_stash_count: `boolean` show stash count or not - defaults to `false` - display_stash_count: `boolean` show stash count or not - defaults to `false`
- display_worktree_count: `boolean` show worktree count or not - defaults to `false`
- status_separator_icon: `string` icon/text to display between staging and working area changes - defaults to ` |` - status_separator_icon: `string` icon/text to display between staging and working area changes - defaults to ` |`
- local_working_icon: `string` - the icon to display in front of the working area changes - defaults to `\uF044` - local_working_icon: `string` - the icon to display in front of the working area changes - defaults to `\uF044`
- local_staged_icon: `string` - the icon to display in front of the staged area changes - defaults to `\uF046` - local_staged_icon: `string` - the icon to display in front of the staged area changes - defaults to `\uF046`
- stash_count_icon: `string` icon/text to display before the stash context - defaults to `\uF692` - stash_count_icon: `string` icon/text to display before the stash context - defaults to `\uF692`
- worktree_count_icon: `string` icon/text to display before the worktree context - defaults to `\uF1BB`
### HEAD context ### HEAD context

View file

@ -57,6 +57,7 @@ type environmentInfo interface {
hasFilesInDir(dir, pattern string) bool hasFilesInDir(dir, pattern string) bool
hasFolder(folder string) bool hasFolder(folder string) bool
getFileContent(file string) string getFileContent(file string) string
getFoldersList(path string) []string
getPathSeperator() string getPathSeperator() string
getCurrentUser() string getCurrentUser() string
isRunningAsRoot() bool isRunningAsRoot() bool
@ -219,6 +220,21 @@ func (env *environment) getFileContent(file string) string {
return string(content) return string(content)
} }
func (env *environment) getFoldersList(path string) []string {
defer env.tracer.trace(time.Now(), "getFoldersList", path)
content, err := os.ReadDir(path)
if err != nil {
return nil
}
var folderNames []string
for _, s := range content {
if s.IsDir() {
folderNames = append(folderNames, s.Name())
}
}
return folderNames
}
func (env *environment) getPathSeperator() string { func (env *environment) getPathSeperator() string {
defer env.tracer.trace(time.Now(), "getPathSeperator") defer env.tracer.trace(time.Now(), "getPathSeperator")
return string(os.PathSeparator) return string(os.PathSeparator)

View file

@ -20,6 +20,7 @@ type gitRepo struct {
gitWorkingFolder string // .git working folder, can be different of root if using worktree gitWorkingFolder string // .git working folder, can be different of root if using worktree
isWorkTree bool isWorkTree bool
gitRootFolder string // .git root folder gitRootFolder string // .git root folder
worktreeCount int
} }
type gitStatus struct { type gitStatus struct {
@ -120,6 +121,10 @@ const (
AheadColor Property = "ahead_color" AheadColor Property = "ahead_color"
// BranchMaxLength truncates the length of the branch name // BranchMaxLength truncates the length of the branch name
BranchMaxLength Property = "branch_max_length" 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"
) )
func (g *git) enabled() bool { func (g *git) enabled() bool {
@ -189,6 +194,9 @@ func (g *git) string() string {
if g.repo.stashCount != 0 { if g.repo.stashCount != 0 {
fmt.Fprintf(buffer, " %s%d", g.props.getString(StashCountIcon, "\uF692 "), g.repo.stashCount) 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)
}
return buffer.String() return buffer.String()
} }
@ -270,6 +278,9 @@ func (g *git) setGitStatus() {
if g.props.getBool(DisplayStashCount, false) { 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()
}
} }
func (g *git) SetStatusColor() { func (g *git) SetStatusColor() {
@ -315,30 +326,30 @@ func (g *git) getGitHEADContext(ref string) string {
ref = fmt.Sprintf("%s%s", branchIcon, ref) ref = fmt.Sprintf("%s%s", branchIcon, ref)
} }
// rebase // rebase
if g.hasGitFolder("rebase-merge") { if g.env.hasFolder(g.repo.gitWorkingFolder + "/rebase-merge") {
head := g.getGitFileContents("rebase-merge/head-name") head := g.getGitFileContents(g.repo.gitWorkingFolder, "rebase-merge/head-name")
origin := strings.Replace(head, "refs/heads/", "", 1) origin := strings.Replace(head, "refs/heads/", "", 1)
origin = g.truncateBranch(origin) origin = g.truncateBranch(origin)
onto := g.getGitRefFileSymbolicName("rebase-merge/onto") onto := g.getGitRefFileSymbolicName("rebase-merge/onto")
onto = g.truncateBranch(onto) onto = g.truncateBranch(onto)
step := g.getGitFileContents("rebase-merge/msgnum") step := g.getGitFileContents(g.repo.gitWorkingFolder, "rebase-merge/msgnum")
total := g.getGitFileContents("rebase-merge/end") total := g.getGitFileContents(g.repo.gitWorkingFolder, "rebase-merge/end")
icon := g.props.getString(RebaseIcon, "\uE728 ") icon := g.props.getString(RebaseIcon, "\uE728 ")
return fmt.Sprintf("%s%s%s onto %s%s (%s/%s) at %s", icon, branchIcon, origin, branchIcon, onto, step, total, ref) return fmt.Sprintf("%s%s%s onto %s%s (%s/%s) at %s", icon, branchIcon, origin, branchIcon, onto, step, total, ref)
} }
if g.hasGitFolder("rebase-apply") { if g.env.hasFolder(g.repo.gitWorkingFolder + "/rebase-apply") {
head := g.getGitFileContents("rebase-apply/head-name") head := g.getGitFileContents(g.repo.gitWorkingFolder, "rebase-apply/head-name")
origin := strings.Replace(head, "refs/heads/", "", 1) origin := strings.Replace(head, "refs/heads/", "", 1)
origin = g.truncateBranch(origin) origin = g.truncateBranch(origin)
step := g.getGitFileContents("rebase-apply/next") step := g.getGitFileContents(g.repo.gitWorkingFolder, "rebase-apply/next")
total := g.getGitFileContents("rebase-apply/last") total := g.getGitFileContents(g.repo.gitWorkingFolder, "rebase-apply/last")
icon := g.props.getString(RebaseIcon, "\uE728 ") icon := g.props.getString(RebaseIcon, "\uE728 ")
return fmt.Sprintf("%s%s%s (%s/%s) at %s", icon, branchIcon, origin, step, total, ref) return fmt.Sprintf("%s%s%s (%s/%s) at %s", icon, branchIcon, origin, step, total, ref)
} }
// merge // merge
if g.hasGitFile("MERGE_MSG") && g.hasGitFile("MERGE_HEAD") { if g.hasGitFile("MERGE_MSG") && g.hasGitFile("MERGE_HEAD") {
icon := g.props.getString(MergeIcon, "\uE727 ") icon := g.props.getString(MergeIcon, "\uE727 ")
mergeContext := g.getGitFileContents("MERGE_MSG") mergeContext := g.getGitFileContents(g.repo.gitWorkingFolder, "MERGE_MSG")
matches := findNamedRegexMatch(`Merge branch '(?P<head>.*)' into`, mergeContext) matches := findNamedRegexMatch(`Merge branch '(?P<head>.*)' into`, mergeContext)
if matches != nil && matches["head"] != "" { if matches != nil && matches["head"] != "" {
branch := g.truncateBranch(matches["head"]) branch := g.truncateBranch(matches["head"])
@ -351,15 +362,15 @@ func (g *git) getGitHEADContext(ref string) string {
// reverts then CHERRY_PICK_HEAD/REVERT_HEAD will not exist so we have to read // reverts then CHERRY_PICK_HEAD/REVERT_HEAD will not exist so we have to read
// the todo file. // the todo file.
if g.hasGitFile("CHERRY_PICK_HEAD") { if g.hasGitFile("CHERRY_PICK_HEAD") {
sha := g.getGitFileContents("CHERRY_PICK_HEAD") sha := g.getGitFileContents(g.repo.gitWorkingFolder, "CHERRY_PICK_HEAD")
icon := g.props.getString(CherryPickIcon, "\uE29B ") icon := g.props.getString(CherryPickIcon, "\uE29B ")
return fmt.Sprintf("%s%s onto %s", icon, sha[0:6], ref) return fmt.Sprintf("%s%s onto %s", icon, sha[0:6], ref)
} else if g.hasGitFile("REVERT_HEAD") { } else if g.hasGitFile("REVERT_HEAD") {
sha := g.getGitFileContents("REVERT_HEAD") sha := g.getGitFileContents(g.repo.gitWorkingFolder, "REVERT_HEAD")
icon := g.props.getString(RevertIcon, "\uF0E2 ") icon := g.props.getString(RevertIcon, "\uF0E2 ")
return fmt.Sprintf("%s%s onto %s", icon, sha[0:6], ref) return fmt.Sprintf("%s%s onto %s", icon, sha[0:6], ref)
} else if g.hasGitFile("sequencer/todo") { } else if g.hasGitFile("sequencer/todo") {
todo := g.getGitFileContents("sequencer/todo") todo := g.getGitFileContents(g.repo.gitWorkingFolder, "sequencer/todo")
matches := findNamedRegexMatch(`^(?P<action>p|pick|revert)\s+(?P<sha>\S+)`, todo) matches := findNamedRegexMatch(`^(?P<action>p|pick|revert)\s+(?P<sha>\S+)`, todo)
if matches != nil && matches["sha"] != "" { if matches != nil && matches["sha"] != "" {
action := matches["action"] action := matches["action"]
@ -389,25 +400,18 @@ func (g *git) hasGitFile(file string) bool {
return g.env.hasFilesInDir(g.repo.gitWorkingFolder, file) return g.env.hasFilesInDir(g.repo.gitWorkingFolder, file)
} }
func (g *git) hasGitFolder(folder string) bool { func (g *git) getGitFileContents(folder, file string) string {
path := g.repo.gitWorkingFolder + "/" + folder return strings.Trim(g.env.getFileContent(folder+"/"+file), " \r\n")
return g.env.hasFolder(path)
}
func (g *git) getGitFileContents(file string) string {
path := g.repo.gitWorkingFolder + "/" + file
content := g.env.getFileContent(path)
return strings.Trim(content, " \r\n")
} }
func (g *git) getGitRefFileSymbolicName(refFile string) string { func (g *git) getGitRefFileSymbolicName(refFile string) string {
ref := g.getGitFileContents(refFile) ref := g.getGitFileContents(g.repo.gitWorkingFolder, refFile)
return g.getGitCommandOutput("name-rev", "--name-only", "--exclude=tags/*", ref) return g.getGitCommandOutput("name-rev", "--name-only", "--exclude=tags/*", ref)
} }
func (g *git) getPrettyHEADName() string { func (g *git) getPrettyHEADName() string {
var ref string var ref string
HEAD := g.getGitFileContents("HEAD") HEAD := g.getGitFileContents(g.repo.gitWorkingFolder, "HEAD")
branchPrefix := "ref: refs/heads/" branchPrefix := "ref: refs/heads/"
if strings.HasPrefix(HEAD, branchPrefix) { if strings.HasPrefix(HEAD, branchPrefix) {
ref = strings.TrimPrefix(HEAD, branchPrefix) ref = strings.TrimPrefix(HEAD, branchPrefix)
@ -462,7 +466,7 @@ func (g *git) parseGitStats(output []string, working bool) *gitStatus {
} }
func (g *git) getStashContext() int { func (g *git) getStashContext() int {
stashContent := g.getGitFileContents("logs/refs/stash") stashContent := g.getGitFileContents(g.repo.gitRootFolder, "logs/refs/stash")
if stashContent == "" { if stashContent == "" {
return 0 return 0
} }
@ -470,6 +474,14 @@ func (g *git) getStashContext() int {
return len(lines) return len(lines)
} }
func (g *git) getWorktreeContext() int {
if !g.env.hasFolder(g.repo.gitRootFolder + "/worktrees") {
return 0
}
worktreeFolders := g.env.getFoldersList(g.repo.gitRootFolder + "/worktrees")
return len(worktreeFolders)
}
func (g *git) parseGitStatusInfo(branchInfo string) map[string]string { func (g *git) parseGitStatusInfo(branchInfo string) map[string]string {
var branchRegex = `^## (?P<local>\S+?)(\.{3}(?P<upstream>\S+?)( \[(?P<upstream_status>(ahead (?P<ahead>\d+)(, )?)?(behind (?P<behind>\d+))?(gone)?)])?)?$` var branchRegex = `^## (?P<local>\S+?)(\.{3}(?P<upstream>\S+?)( \[(?P<upstream_status>(ahead (?P<ahead>\d+)(, )?)?(behind (?P<behind>\d+))?(gone)?)])?)?$`
return findNamedRegexMatch(branchRegex, branchInfo) return findNamedRegexMatch(branchRegex, branchInfo)

View file

@ -45,7 +45,7 @@ const (
MappedLocationsEnabled Property = "mapped_locations_enabled" MappedLocationsEnabled Property = "mapped_locations_enabled"
// StackCountEnabled enables the stack count display // StackCountEnabled enables the stack count display
StackCountEnabled Property = "stack_count_enabled" StackCountEnabled Property = "stack_count_enabled"
// Maximum path depth to display whithout shortening // MaxDepth Maximum path depth to display whithout shortening
MaxDepth Property = "max_depth" MaxDepth Property = "max_depth"
) )

View file

@ -49,6 +49,11 @@ func (env *MockedEnvironment) getFileContent(file string) string {
return args.String(0) return args.String(0)
} }
func (env *MockedEnvironment) getFoldersList(path string) []string {
args := env.Called(path)
return args.Get(0).([]string)
}
func (env *MockedEnvironment) getPathSeperator() string { func (env *MockedEnvironment) getPathSeperator() string {
args := env.Called(nil) args := env.Called(nil)
return args.String(0) return args.String(0)

View file

@ -570,6 +570,12 @@
"description": "Display the stash count or not", "description": "Display the stash count or not",
"default": false "default": false
}, },
"display_worktree_count": {
"type": "boolean",
"title": "Display Worktree Count",
"description": "Display the worktree count or not",
"default": false
},
"status_separator_icon": { "status_separator_icon": {
"type": "string", "type": "string",
"title": "Status Separator Icon", "title": "Status Separator Icon",
@ -592,7 +598,13 @@
"type": "string", "type": "string",
"title": "Stash Count Icon", "title": "Stash Count Icon",
"description": "The icon to display before the stash context", "description": "The icon to display before the stash context",
"default": "\uF692" "default": "\uF692 "
},
"worktree_count_icon": {
"type": "string",
"title": "Worktree Count Icon",
"description": "The icon to display before the worktree context",
"default": "\uF1bb "
}, },
"commit_icon": { "commit_icon": {
"type": "string", "type": "string",