This repository has been archived on 2026-10-25. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
submarine/internal/submodule/status.go
T
Simon FrankenandClaude Opus 5.5 2212b24fe1 Add submarine TUI prototype with mock data
Bubble Tea prototype for browsing repositories with many nested git
submodules: collapsible tree, pins shown as ref + distance, summarized
issue words with severity levels, detail pane, async loading via a
Provider interface backed by mock data. Includes docs/ with
requirements, state model, UX guide, architecture and decision log,
plus AGENTS.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 14:59:53 +02:00

82 lines
2.3 KiB
Go

package submodule
// Level is how much attention an issue deserves.
type Level int
const (
LevelQuiet Level = iota // nothing to do
LevelWarn // look at it
LevelDanger // act now
)
// IssueKind is the summarized category of a problem.
type IssueKind int
const (
IssueMissing IssueKind = iota // not initialized or directory missing
IssueUnpushed // pin not reachable from any remote ref
IssueConflict // merge conflict in the worktree
IssueOffPin // checkout differs from pin
IssueChanged // uncommitted changes or untracked files
)
// Issue is one problem of a submodule. Detail distinguishes the states that
// are summarized into one kind.
type Issue struct {
Kind IssueKind
Level Level
Detail string
}
// Issues summarizes the status, most severe first. Being behind or ahead of
// the remote, a staged pin and a detached HEAD are not issues.
func (s *Status) Issues() []Issue {
if s == nil {
return nil
}
switch s.Presence {
case NotInitialized:
return []Issue{{IssueMissing, LevelWarn, "not initialized"}}
case DirMissing:
return []Issue{{IssueMissing, LevelWarn, "directory missing"}}
}
var out []Issue
if !s.Pin.Pushed {
out = append(out, Issue{IssueUnpushed, LevelDanger, "pin not pushed to any remote"})
}
if s.Worktree == WorktreeConflict {
out = append(out, Issue{IssueConflict, LevelDanger, "merge conflict"})
}
if s.Checkout == CheckoutDiffers {
out = append(out, Issue{IssueOffPin, LevelWarn, "checkout differs from pin"})
}
switch s.Worktree {
case WorktreeModified:
out = append(out, Issue{IssueChanged, LevelWarn, "uncommitted changes"})
case WorktreeUntracked:
out = append(out, Issue{IssueChanged, LevelWarn, "untracked files"})
}
return out
}
// HiddenIssues counts the issues of all loaded descendants and returns the
// most severe level among them.
func (m *Submodule) HiddenIssues() (count int, worst Level) {
Walk(m.Children, func(c *Submodule) {
for _, is := range c.Status.Issues() {
count++
worst = max(worst, is.Level)
}
})
return count, worst
}
// Walk calls fn for every submodule in depth-first order.
func Walk(mods []*Submodule, fn func(*Submodule)) {
for _, m := range mods {
fn(m)
Walk(m.Children, fn)
}
}