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: "\t" 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 }