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/git/root_test.go
T
simonfrankenandClaude Sonnet 5.5 4c1be3f9ab Show the pin, tip and checkout as a spine in the detail pane
Replace the Pinned/Checkout/Tip rows and the list of commits between pin
and tip with one line of history, newest on top: a node per commit with
its roles (pin, tip, you, staged, HEAD) and the number of commits between
two nodes. A staged pin shows the committed pin as its own node and notes
when it moves back. All three on one commit read "in sync".

Add Status.Drift, Committed and CommittedDrift and Status.Spine(); drop
Status.Between and Repo.FetchedAt. The fetch age moves from the header to
the detail pane (right-aligned in the name's line). The header summary is
right-aligned, ordered by importance and cut with an ellipsis if too narrow.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 16:34:14 +02:00

170 lines
5.6 KiB
Go

package git
import (
"context"
"os"
"path/filepath"
"slices"
"testing"
"submarine/internal/submodule"
)
// rootFixture is ownFixture with a bare origin for the superproject, main
// pushed and tracking it. It returns the provider, the fixture root and the
// path of the bare repository.
func rootFixture(t *testing.T) (*Provider, string, string) {
t.Helper()
p, root := ownFixture(t)
bare := filepath.Join(root, "remotes", "super.git")
git(t, root, "init", "-q", "--bare", bare)
git(t, p.root, "remote", "add", "origin", bare)
git(t, p.root, "push", "-q", "-u", "origin", "main")
return p, root, bare
}
func rootStatus(t *testing.T, p *Provider) *submodule.Status {
t.Helper()
st, err := p.Status(context.Background(), "")
if err != nil {
t.Fatal(err)
}
if !st.Root {
t.Error("Status.Root = false for the superproject")
}
return st
}
func issueKinds(st *submodule.Status) []submodule.IssueKind {
var kinds []submodule.IssueKind
for _, is := range st.Issues() {
kinds = append(kinds, is.Kind)
}
return kinds
}
func TestTreeRoot(t *testing.T) {
p, root, bare := rootFixture(t)
repo, err := p.Tree(context.Background())
if err != nil {
t.Fatal(err)
}
if r := repo.Root; r == nil || !r.IsRoot() || r.Name != "super" || r.URL != bare || len(r.Children) != 0 {
t.Errorf("Root = %+v (fixture %s)", r, root)
}
}
// The fixture has dirty, conflicting and staged submodules; none of them
// makes the superproject itself changed, their rows show them.
func TestRootStatusIgnoresSubmodules(t *testing.T) {
p, _, _ := rootFixture(t)
st := rootStatus(t, p)
if kinds := issueKinds(st); len(kinds) != 0 {
t.Errorf("issues = %v, want none", kinds)
}
if st.Changes != nil || st.PinStaged || st.Presence != submodule.PresenceOK {
t.Errorf("Changes = %+v, PinStaged = %v", st.Changes, st.PinStaged)
}
if st.Pin.Ref != "origin/main" || st.Pin.Source != submodule.RefFromCurrentBranch || !st.Pin.Pushed {
t.Errorf("Pin = %+v", st.Pin)
}
if st.Pinned == nil || st.Pinned != st.CheckedOut && *st.Pinned != *st.CheckedOut {
t.Errorf("Pinned = %+v, CheckedOut = %+v, want HEAD for both", st.Pinned, st.CheckedOut)
}
}
func TestRootStatusUnpushed(t *testing.T) {
p, _, _ := rootFixture(t)
git(t, p.root, "commit", "--allow-empty", "-m", "Local work")
st := rootStatus(t, p)
if kinds := issueKinds(st); !slices.Equal(kinds, []submodule.IssueKind{submodule.IssueUnpushed}) {
t.Errorf("issues = %v, want unpushed", kinds)
}
if st.Pin.Ahead != 1 || st.Pin.Behind != 0 {
t.Errorf("ahead/behind = %d/%d, want 1/0", st.Pin.Ahead, st.Pin.Behind)
}
if d := st.Issues()[0].Detail; d != "HEAD not pushed to any remote" {
t.Errorf("detail = %q", d)
}
}
// A submodule the current branch no longer has stays on disk after a branch
// switch (its .git is a file into .git/modules). It is an untracked
// repository of the superproject.
func TestRootStatusLeftoverSubmodule(t *testing.T) {
p, _, _ := rootFixture(t)
git(t, p.root, "switch", "-q", "-c", "nosub")
git(t, p.root, "rm", "-q", "--cached", "src/ui")
git(t, p.root, "commit", "-q", "-m", "Drop src/ui")
git(t, p.root, "switch", "-q", "main")
git(t, p.root, "switch", "-q", "nosub")
if _, err := os.Stat(filepath.Join(p.root, "src", "ui", ".git")); err != nil {
t.Fatalf("src/ui should be left behind: %v", err)
}
st := rootStatus(t, p)
if kinds := issueKinds(st); !slices.Equal(kinds, []submodule.IssueKind{submodule.IssueUnpushed, submodule.IssueChanged}) {
t.Fatalf("issues = %v, want unpushed (new branch) and changed", kinds)
}
if d := st.Issues()[1].Detail; d != "untracked repository" {
t.Errorf("detail = %q, want untracked repository", d)
}
if st.Changes == nil || len(st.Changes.Files) != 1 {
t.Fatalf("Changes = %+v", st.Changes)
}
if f := st.Changes.Files[0]; f.Path != "src/ui/" || f.Kind != submodule.ChangeUntracked || !f.Repo {
t.Errorf("file = %+v", f)
}
}
func TestRootStatusModified(t *testing.T) {
p, _, _ := rootFixture(t)
if err := os.WriteFile(filepath.Join(p.root, "README"), []byte("edited\n"), 0o644); err != nil {
t.Fatal(err)
}
st := rootStatus(t, p)
if kinds := issueKinds(st); !slices.Equal(kinds, []submodule.IssueKind{submodule.IssueChanged}) {
t.Errorf("issues = %v, want changed", kinds)
}
if st.Worktree != submodule.WorktreeModified {
t.Errorf("Worktree = %v", st.Worktree)
}
}
// The ref of the superproject is the upstream of its branch, even if another
// remote branch is closer, so that it agrees with the header's ahead/behind.
func TestRootStatusRefIsUpstream(t *testing.T) {
p, root, bare := rootFixture(t)
other := filepath.Join(root, "other")
git(t, root, "clone", "-q", bare, other)
git(t, other, "switch", "-q", "-c", "feat")
git(t, other, "commit", "--allow-empty", "-m", "Feature")
git(t, other, "push", "-q", "origin", "feat")
git(t, other, "switch", "-q", "main")
for range 3 {
git(t, other, "commit", "--allow-empty", "-m", "Upstream")
}
git(t, other, "push", "-q", "origin", "main")
git(t, p.root, "fetch", "-q")
st := rootStatus(t, p)
if st.Pin.Ref != "origin/main" || st.Pin.Source != submodule.RefFromCurrentBranch {
t.Errorf("ref = %q (%v), want origin/main via the current branch", st.Pin.Ref, st.Pin.Source)
}
if st.Pin.Ahead != 0 || st.Pin.Behind != 3 {
t.Errorf("ahead/behind = %d/%d, want 0/3", st.Pin.Ahead, st.Pin.Behind)
}
}
func TestRootStatusDetached(t *testing.T) {
p, _, _ := rootFixture(t)
git(t, p.root, "switch", "-q", "--detach")
st := rootStatus(t, p)
if st.Checkout != submodule.CheckoutDetached {
t.Errorf("Checkout = %v, want detached", st.Checkout)
}
if !st.Pin.Pushed {
t.Error("detached HEAD on a pushed commit should be pushed")
}
}