Mark untracked directories that are git repositories

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-21 20:12:13 +02:00
co-authored by Claude Sonnet 5.5
parent 69d0f9b704
commit af6d03a5b1
7 changed files with 45 additions and 3 deletions
+1 -1
View File
@@ -72,7 +72,7 @@ truth for what counts as a problem.
| `conflict` | Merge conflict in the worktree | Danger |
| `missing` | Not initialized; directory missing | Warn |
| `off pin` | Checkout differs from pin | Warn |
| `changed` | Uncommitted changes; untracked files | Warn |
| `changed` | Uncommitted changes; untracked files (worded "untracked repository" if all of them are git repositories) | Warn |
Rules:
+3 -1
View File
@@ -91,7 +91,9 @@ Changes 5 files · 2M 1A 1D 1R · +37 −12
meaning, the color only supports it (N4).
- Letters and colors: `!` red bold, `M` yellow, `A` green, `D` red, `R`
cyan, `?` muted. Line counts: green `+n`, red `−n`; `bin` for binary
files, `submodule` for a moved nested pin, nothing for untracked files.
files, `submodule` for a moved nested pin, `repository` for an untracked
directory that is a git repository itself (cloned, never added as a
submodule), nothing for other untracked files.
- Order: kind (`!`, `M`, `A`, `D`, `R`, `?`), then changed lines, largest
first, because the biggest edits are the ones people remember.
- Budget: 10 files. Every kind that occurs gets at least one line, the
+3
View File
@@ -387,6 +387,9 @@ func changes(ctx context.Context, dir string, entries []entry) (*submodule.Chang
c.Added += n.added
c.Removed += n.removed
}
if e.kind == submodule.ChangeUntracked && strings.HasSuffix(e.path, "/") {
f.Repo = initialized(filepath.Join(dir, filepath.FromSlash(e.path)))
}
if e.kind != submodule.ChangeRemoved {
if fi, err := os.Stat(filepath.Join(dir, filepath.FromSlash(e.path))); err == nil {
f.Modified = fi.ModTime()
+1
View File
@@ -132,6 +132,7 @@ type FileChange struct {
Removed int // removed lines
Binary bool // line counts are unknown
Submodule bool // a nested submodule pointer changed
Repo bool // an untracked directory that is a git repository itself
Modified time.Time // modification time of the file; zero if it doesn't exist
}
+16 -1
View File
@@ -55,7 +55,7 @@ func (s *Status) Issues() []Issue {
case WorktreeModified:
out = append(out, Issue{IssueChanged, LevelWarn, "uncommitted changes"})
case WorktreeUntracked:
out = append(out, Issue{IssueChanged, LevelWarn, "untracked files"})
out = append(out, Issue{IssueChanged, LevelWarn, s.untrackedText()})
}
return out
}
@@ -79,3 +79,18 @@ func Walk(mods []*Submodule, fn func(*Submodule)) {
Walk(m.Children, fn)
}
}
// untrackedText words the untracked-files issue: "untracked repository" if
// everything untracked is a git repository of its own, e.g. a clone that was
// never added as a submodule.
func (s *Status) untrackedText() string {
if s.Changes == nil || s.Changes.Total != len(s.Changes.Files) {
return "untracked files"
}
for _, f := range s.Changes.Files {
if f.Kind != ChangeUntracked || !f.Repo {
return "untracked files"
}
}
return "untracked repository"
}
+19
View File
@@ -50,3 +50,22 @@ func TestHiddenIssues(t *testing.T) {
t.Errorf("HiddenIssues() = %d, %v; want 2, LevelDanger", count, worst)
}
}
func TestUntrackedRepositoryWording(t *testing.T) {
repo := FileChange{Path: "lib/x/", Kind: ChangeUntracked, Repo: true}
file := FileChange{Path: "a.txt", Kind: ChangeUntracked}
for _, c := range []struct {
files []FileChange
want string
}{
{[]FileChange{repo}, "untracked repository"},
{[]FileChange{repo, file}, "untracked files"},
{[]FileChange{file}, "untracked files"},
} {
s := &Status{Worktree: WorktreeUntracked, Changes: &Changes{Total: len(c.files), Files: c.files}}
got := s.Issues()
if len(got) == 0 || got[len(got)-1].Detail != c.want {
t.Errorf("%v: got %v, want %q", c.files, got, c.want)
}
}
}
+2
View File
@@ -163,6 +163,8 @@ func (m Model) fileLines(files []submodule.FileChange) []string {
switch {
case f.Submodule:
r.counts = muted.Render("submodule")
case f.Repo:
r.counts = muted.Render("repository")
case f.Binary:
r.counts = muted.Render("bin")
default: