Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add status line info to the submodule list view #3939

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions pkg/commands/git_commands/submodule.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,62 @@ func (self *SubmoduleCommands) GetConfigs(parentModule *models.SubmoduleConfig)
}
}

// TODO: unsure if this is the best way to go about this.
// TODO: might be better to do a single `git submodule foreach git status` and parse the Output
getSubmoduleHead := func(cmds *SubmoduleCommands, path string) (string, error) {
// git -C </path/to/submodule> symbolic-ref -q HEAD
// Output is empty in detached state
// Output if on a branch: refs/heads/main
cmdArgs := NewGitCmd("symbolic-ref").
Dir(path).
Arg("-q", "HEAD").
ToArgv()

return cmds.cmd.New(cmdArgs).RunWithOutput()
}

formatSubmoduleHead := func(head string, prefix string) string {
// refs/heads/main ---> main
if strings.HasPrefix(head, prefix) {
return head[len(prefix) : len(head)-1]
}
return head
}

getPorcelainStatus := func(cmds *SubmoduleCommands, path string) (int, int, int, error) {
cmdArgs := NewGitCmd("status").
Dir(path).
Arg("--porcelain").
ToArgv()

output, err := cmds.cmd.New(cmdArgs).RunWithOutput()
if err != nil {
return -1, -1, -1, err
}
lines := strings.Split(output, "\n")

stagedCount := 0
unstagedCount := 0
untrackedCount := 0

for _, line := range lines {
if len(line) == 0 {
continue
}
firstChar := line[0:1]
switch firstChar {
case " ":
unstagedCount++
case "?":
untrackedCount++
default:
stagedCount++
}

}
return stagedCount, unstagedCount, untrackedCount, nil
}

configs := []*models.SubmoduleConfig{}
lastConfigIdx := -1
for scanner.Scan() {
Expand All @@ -70,6 +126,17 @@ func (self *SubmoduleCommands) GetConfigs(parentModule *models.SubmoduleConfig)
if lastConfigIdx != -1 {
if path, ok := firstMatch(line, `\s*path\s*=\s*(.*)\s*`); ok {
configs[lastConfigIdx].Path = path
head, err := getSubmoduleHead(self, path)
if err == nil {
formattedHead := formatSubmoduleHead(head, "refs/heads/")
configs[lastConfigIdx].Head = strings.TrimSpace(formattedHead)
}
stagedCount, unstagedCount, untrackedCount, err := getPorcelainStatus(self, path)
if err == nil {
configs[lastConfigIdx].NumStagedFiles = stagedCount
configs[lastConfigIdx].NumUnstagedChanges = unstagedCount
configs[lastConfigIdx].NumUntrackedChanges = untrackedCount
}
nestedConfigs, err := self.GetConfigs(configs[lastConfigIdx])
if err == nil {
configs = append(configs, nestedConfigs...)
Expand Down
10 changes: 7 additions & 3 deletions pkg/commands/models/submodule_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ package models
import "path/filepath"

type SubmoduleConfig struct {
Name string
Path string
Url string
Name string
Path string
Url string
Head string
NumStagedFiles int
NumUnstagedChanges int
NumUntrackedChanges int

ParentModule *SubmoduleConfig // nil if top-level
}
Expand Down
32 changes: 31 additions & 1 deletion pkg/gui/presentation/submodules.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package presentation

import (
"fmt"

"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/theme"
"github.com/samber/lo"
Expand All @@ -13,7 +15,11 @@ func GetSubmoduleListDisplayStrings(submodules []*models.SubmoduleConfig) [][]st
}

func getSubmoduleDisplayStrings(s *models.SubmoduleConfig) []string {
name := s.Name
// Pad right with some spaces to the end of the HEAD so that it (hopefully) aligns well.
// Put the HEAD first because those are more likely to be similar lengths than the repo name.
name := fmt.Sprintf("%-20s %s", s.Head,
s.Name,
)
if s.ParentModule != nil {
indentation := ""
for p := s.ParentModule; p != nil; p = p.ParentModule {
Expand All @@ -23,5 +29,29 @@ func getSubmoduleDisplayStrings(s *models.SubmoduleConfig) []string {
name = indentation + "- " + s.Name
}

if s.NumStagedFiles != 0 {
name = fmt.Sprintf(
"%s +%d",
name,
s.NumStagedFiles,
)
}

if s.NumUnstagedChanges != 0 {
name = fmt.Sprintf(
"%s !%d",
name,
s.NumUnstagedChanges,
)
}

if s.NumUntrackedChanges != 0 {
name = fmt.Sprintf(
"%s ?%d ",
name,
s.NumUntrackedChanges,
)
}

return []string{theme.DefaultTextColor.Sprint(name)}
}