initial commit [141.14.129.234,mike]
This commit is contained in:
+108
@@ -0,0 +1,108 @@
|
||||
package main
|
||||
|
||||
// overview.go — the `overview` command (also reachable as `status -a`): a
|
||||
// one-line-per-project summary of every git project under BASE, showing the
|
||||
// dirty state and how far each branch is ahead/behind its upstream.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// projStatus is the collected state of one project for the overview.
|
||||
type projStatus struct {
|
||||
name string
|
||||
branch string
|
||||
dirty bool
|
||||
ahead, behind int
|
||||
hasUpstream bool
|
||||
}
|
||||
|
||||
// overviewAll prints a status summary for all git projects under BASE.
|
||||
func overviewAll() {
|
||||
entries, err := os.ReadDir(BASE)
|
||||
if err != nil {
|
||||
errorln("cannot read " + BASE + ": " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var rows []projStatus
|
||||
width := 0
|
||||
for _, e := range entries {
|
||||
if !e.IsDir() || strings.HasPrefix(e.Name(), ".") {
|
||||
continue
|
||||
}
|
||||
dir := BASE + "/" + e.Name()
|
||||
if !isDir(dir + "/.git") {
|
||||
continue
|
||||
}
|
||||
rows = append(rows, projectStatus(e.Name(), dir))
|
||||
if len(e.Name()) > width {
|
||||
width = len(e.Name())
|
||||
}
|
||||
}
|
||||
|
||||
if len(rows) == 0 {
|
||||
fmt.Println(col(cGray, "no git projects under "+BASE))
|
||||
return
|
||||
}
|
||||
|
||||
dirtyN, syncN := 0, 0
|
||||
for _, r := range rows {
|
||||
fmt.Println(formatProjStatus(r, width))
|
||||
if r.dirty {
|
||||
dirtyN++
|
||||
}
|
||||
if r.hasUpstream && r.ahead == 0 && r.behind == 0 && !r.dirty {
|
||||
syncN++
|
||||
}
|
||||
}
|
||||
fmt.Printf("%s\n", col(cGray, fmt.Sprintf("%d projects · %d dirty · %d in sync", len(rows), dirtyN, syncN)))
|
||||
}
|
||||
|
||||
// projectStatus gathers the git state of a single project directory.
|
||||
func projectStatus(name, dir string) projStatus {
|
||||
s := projStatus{name: name, branch: "-"}
|
||||
if out, err := gitCapture(dir, "rev-parse", "--abbrev-ref", "HEAD"); err == nil {
|
||||
s.branch = strings.TrimSpace(out)
|
||||
}
|
||||
if out, err := gitCapture(dir, "status", "--porcelain"); err == nil && strings.TrimSpace(out) != "" {
|
||||
s.dirty = true
|
||||
}
|
||||
// left/right counts against the upstream: "<behind>\t<ahead>"
|
||||
if out, err := gitCapture(dir, "rev-list", "--left-right", "--count", "@{upstream}...HEAD"); err == nil {
|
||||
if _, e := fmt.Sscanf(strings.TrimSpace(out), "%d\t%d", &s.behind, &s.ahead); e == nil {
|
||||
s.hasUpstream = true
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// formatProjStatus renders one aligned overview row.
|
||||
func formatProjStatus(s projStatus, width int) string {
|
||||
var marks []string
|
||||
if s.dirty {
|
||||
marks = append(marks, col(cYellow, "*"))
|
||||
}
|
||||
if s.ahead > 0 {
|
||||
marks = append(marks, col(cGreen, fmt.Sprintf("↑%d", s.ahead)))
|
||||
}
|
||||
if s.behind > 0 {
|
||||
marks = append(marks, col(cRed, fmt.Sprintf("↓%d", s.behind)))
|
||||
}
|
||||
state := strings.Join(marks, " ")
|
||||
if state == "" {
|
||||
if s.hasUpstream {
|
||||
state = col(cGreen, "✓")
|
||||
} else {
|
||||
state = col(cGray, "✓ (no upstream)")
|
||||
}
|
||||
}
|
||||
|
||||
line := " " + col(cGreen, padRight(s.name, width+2)) + state
|
||||
if s.branch != "master" && s.branch != "main" && s.branch != "-" {
|
||||
line += col(cGray, " ("+s.branch+")")
|
||||
}
|
||||
return line
|
||||
}
|
||||
Reference in New Issue
Block a user