Drop the "not cloned here" line from overview

It answered a question `list` already answers, and it did so on every
run: the point of the overview is the state of the projects you have,
not a second listing of the server. reportInventory became
reportUnpublished and now reports one thing -- the local projects the
server has never seen, which are the ones `init` is for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-26 17:45:02 +02:00
co-authored by Claude Opus 5
parent cb1ff98c3d
commit 8ca05d6ad2
4 changed files with 37 additions and 45 deletions
+5 -5
View File
@@ -162,7 +162,6 @@ thing that sees the local base directory *and* the git server at once.
scratch (wip) laptop 1h
4 projects · 2 dirty · 2 in sync
not on the server (init) experiments, sandbox
not cloned here (clone) oldproject
```
Every field sits in its own column, so the eye can go down one instead of
@@ -184,10 +183,11 @@ every message, so `overview` can say where a project was last worked on without
storing anything. On a setup spanning a laptop and a workstation that is usually
the piece of information you actually wanted.
The two lists at the end are the join no git command can do: local projects the
server has never seen (`init` them) and server repositories missing on this
machine (`clone` them). If the server cannot be reached, mgsh says so instead of
claiming everything is missing.
The line at the end is the join no git command can do: the local projects the
git server has never seen, which are the ones `init` is for. If the server
cannot be reached, mgsh says so instead of claiming everything is missing. The
other direction — repositories on the server that are not here — is what `list`
shows.
### Credential check
+14 -31
View File
@@ -5,9 +5,8 @@ package main
// mgsh is the only thing that sees all three places a project can live: the
// local base directory, the internal ssh server, and the public mirrors. Joining
// those answers the questions plain git cannot — which projects were never
// pushed to the server, which exist there but not on this machine, and which
// machine last touched each one (every `push` stamps "[user@host]" into the
// commit message, so that comes for free).
// pushed to the server, and which machine last touched each one (every `push`
// stamps "[user@host]" into the commit message, so that comes for free).
import (
"fmt"
@@ -99,7 +98,7 @@ func overviewAll() {
len(rows), dirtyN, syncN)))
}
reportInventory(local, srv.names, srv.err)
reportUnpublished(local, srv.names, srv.err)
}
// scanProjects collects the state of every project concurrently. Each project
@@ -121,47 +120,31 @@ func scanProjects(names []string) []projStatus {
return rows
}
// reportInventory names the projects that live on only one side: local ones the
// server has never seen (candidates for `init`) and server repositories missing
// here (candidates for `clone`).
func reportInventory(local, server []string, err error) {
// reportUnpublished names the local projects the git server has never seen —
// the ones `init` is for. The reverse direction, server repositories missing
// here, is what `list` is for and is not repeated.
func reportUnpublished(local, server []string, err error) {
if err != nil {
fmt.Println(col(cGray, " git server not reachable — local view only"))
return
}
have := map[string]bool{}
for _, n := range local {
have[n] = true
}
onServer := map[string]bool{}
for _, n := range server {
onServer[n] = true
}
var missingRemote, missingLocal []string
var missing []string
for _, n := range local {
if !onServer[n] {
missingRemote = append(missingRemote, n)
missing = append(missing, n)
}
}
for _, n := range server {
if !have[n] {
missingLocal = append(missingLocal, n)
}
}
sort.Strings(missingRemote)
sort.Strings(missingLocal)
// the two labels share a column so their contents line up as well
const labelW = 24
if len(missingRemote) > 0 {
fmt.Printf(" %s %s\n", col(cYellow, padRight("not on the server (init)", labelW)),
col(cGray, strings.Join(missingRemote, ", ")))
}
if len(missingLocal) > 0 {
fmt.Printf(" %s %s\n", col(cCyan, padRight("not cloned here (clone)", labelW)),
col(cGray, strings.Join(missingLocal, ", ")))
if len(missing) == 0 {
return
}
sort.Strings(missing)
fmt.Printf(" %s %s\n", col(cYellow, "not on the server (init)"),
col(cGray, strings.Join(missing, ", ")))
}
// projectStatus gathers the git state of a single project directory.
+17 -8
View File
@@ -82,11 +82,11 @@ func TestCommitHostRe(t *testing.T) {
}
}
// TestReportInventoryUnreachableServer: when the server cannot be listed, the
// TestReportUnpublishedUnreachableServer: when the server cannot be listed, the
// overview must say so rather than claim every project is missing there.
func TestReportInventoryUnreachableServer(t *testing.T) {
func TestReportUnpublishedUnreachableServer(t *testing.T) {
out := captureStdout(t, func() {
reportInventory([]string{"a", "b"}, nil, errors.New("network is unreachable"))
reportUnpublished([]string{"a", "b"}, nil, errors.New("network is unreachable"))
})
if strings.Contains(out, "not on the server") {
t.Errorf("an unreachable server was reported as missing repositories: %q", out)
@@ -96,20 +96,29 @@ func TestReportInventoryUnreachableServer(t *testing.T) {
}
}
// TestReportInventorySplitsSides is the join that plain git cannot do.
func TestReportInventorySplitsSides(t *testing.T) {
// TestReportUnpublished names only the local projects the server has never
// seen. The other direction — repositories there but not here — is what `list`
// is for, and repeating it here only added noise.
func TestReportUnpublished(t *testing.T) {
out := captureStdout(t, func() {
reportInventory([]string{"both", "onlyhere"}, []string{"both", "onlythere"}, nil)
reportUnpublished([]string{"both", "onlyhere"}, []string{"both", "onlythere"}, nil)
})
if !strings.Contains(out, "not on the server (init)") || !strings.Contains(out, "onlyhere") {
t.Errorf("local-only project not reported: %q", out)
}
if !strings.Contains(out, "not cloned here (clone)") || !strings.Contains(out, "onlythere") {
t.Errorf("server-only project not reported: %q", out)
if strings.Contains(out, "onlythere") {
t.Errorf("server-only project should no longer be listed: %q", out)
}
if strings.Contains(out, "both") {
t.Errorf("a project present on both sides should not be listed: %q", out)
}
// nothing unpublished: no line at all, not an empty label
if out := captureStdout(t, func() {
reportUnpublished([]string{"both"}, []string{"both"}, nil)
}); strings.TrimSpace(out) != "" {
t.Errorf("nothing to report should print nothing, got %q", out)
}
}
func TestShortAge(t *testing.T) {
+1 -1
View File
@@ -1 +1 @@
4.0.36
4.0.38