8e86b7eadb
Show live repository progress, elapsed time, active work, and recent outcomes during long syncs. Add a persistent post-run summary that prioritizes failures, reports protected and divergent branches, focuses the first issue, and lets users cycle through attention items with f.
163 lines
4.7 KiB
Go
163 lines
4.7 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func git(t *testing.T, dir string, args ...string) {
|
|
t.Helper()
|
|
cmd := exec.Command("git", append([]string{"-C", dir}, args...)...)
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
t.Fatalf("git %v: %v\n%s", args, err, out)
|
|
}
|
|
}
|
|
|
|
func makeRepo(t *testing.T, path string) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(path, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
git(t, path, "init", "-q")
|
|
git(t, path, "config", "user.name", "Test")
|
|
git(t, path, "config", "user.email", "test@example.com")
|
|
if err := os.WriteFile(filepath.Join(path, "tracked.txt"), []byte("clean\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
git(t, path, "add", "tracked.txt")
|
|
git(t, path, "commit", "-qm", "initial")
|
|
git(t, path, "remote", "add", "origin", "https://example.invalid/repo.git")
|
|
}
|
|
|
|
func TestDiscoverNestedRepositories(t *testing.T) {
|
|
root := t.TempDir()
|
|
parent := filepath.Join(root, "parent")
|
|
child := filepath.Join(parent, "apps", "child")
|
|
makeRepo(t, parent)
|
|
makeRepo(t, child)
|
|
|
|
paths, err := discover(root, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(paths) != 2 || paths[0] != parent || paths[1] != child {
|
|
t.Fatalf("discover() = %#v", paths)
|
|
}
|
|
}
|
|
|
|
func TestNestedRepoDoesNotDirtyParent(t *testing.T) {
|
|
root := t.TempDir()
|
|
parent := filepath.Join(root, "parent")
|
|
child := filepath.Join(parent, "child")
|
|
makeRepo(t, parent)
|
|
makeRepo(t, child)
|
|
|
|
paths, _ := discover(root, nil)
|
|
got := inspect(root, parent, paths)
|
|
if !got.Eligible {
|
|
t.Fatalf("parent blocked by nested repo: %+v", got)
|
|
}
|
|
if len(got.NestedRepoEntries) == 0 {
|
|
t.Fatalf("expected nested repository entry to be recorded: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestDirtyFileIsReportedButRepositoryRemainsEligible(t *testing.T) {
|
|
root := t.TempDir()
|
|
path := filepath.Join(root, "repo")
|
|
makeRepo(t, path)
|
|
if err := os.WriteFile(filepath.Join(path, "tracked.txt"), []byte("changed\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
got := inspect(root, path, []string{path})
|
|
if !got.Eligible || len(got.Dirty) != 1 {
|
|
t.Fatalf("dirty repository was not eligible with its changes reported: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestUntrackedFileAlongsideNestedRepoIsStillReported(t *testing.T) {
|
|
root := t.TempDir()
|
|
parent := filepath.Join(root, "parent")
|
|
child := filepath.Join(parent, "child")
|
|
makeRepo(t, parent)
|
|
makeRepo(t, child)
|
|
if err := os.WriteFile(filepath.Join(parent, "notes.txt"), []byte("work\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
paths, _ := discover(root, nil)
|
|
got := inspect(root, parent, paths)
|
|
if !got.Eligible || len(got.Dirty) != 1 {
|
|
t.Fatalf("untracked file should be reported without blocking parent: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestSkippedDirectoriesAreNotSearched(t *testing.T) {
|
|
root := t.TempDir()
|
|
makeRepo(t, filepath.Join(root, "node_modules", "dependency"))
|
|
makeRepo(t, filepath.Join(root, "real"))
|
|
paths, err := discover(root, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(paths) != 1 || paths[0] != filepath.Join(root, "real") {
|
|
t.Fatalf("discover() = %#v", paths)
|
|
}
|
|
}
|
|
|
|
func TestStrictSyncRechecksAndSkipsNewlyDirtyRepository(t *testing.T) {
|
|
root := t.TempDir()
|
|
path := filepath.Join(root, "repo")
|
|
makeRepo(t, path)
|
|
planned := inspect(root, path, []string{path})
|
|
if !planned.Eligible {
|
|
t.Fatalf("fixture should initially be eligible: %+v", planned)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(path, "late-change.txt"), []byte("do not touch\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var progress []syncProgressEvent
|
|
results := syncSelectedWithProgress(root, []repo{planned}, []repo{planned}, 1, time.Minute, io.Discard, true, true, func(event syncProgressEvent) {
|
|
progress = append(progress, event)
|
|
})
|
|
if len(results) != 1 || !results[0].Skipped || results[0].Success {
|
|
t.Fatalf("syncAll() = %+v", results)
|
|
}
|
|
if len(progress) != 1 || progress[0].Started || !progress[0].Result.Skipped {
|
|
t.Fatalf("progress events = %+v, want one finished skip", progress)
|
|
}
|
|
}
|
|
|
|
func TestScanReportsDirtyEligibleRepository(t *testing.T) {
|
|
root := t.TempDir()
|
|
path := filepath.Join(root, "repo")
|
|
makeRepo(t, path)
|
|
if err := os.WriteFile(filepath.Join(path, "untracked.txt"), []byte("work\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
code := run([]string{"scan", "--root", root}, bytes.NewReader(nil), &stdout, &stderr)
|
|
if code != 0 {
|
|
t.Fatalf("run() code = %d, stderr = %q", code, stderr.String())
|
|
}
|
|
if !bytes.Contains(stdout.Bytes(), []byte("dirty worktree (1 changes), checked-out branch protected")) {
|
|
t.Fatalf("scan did not report dirty eligible repository:\n%s", stdout.String())
|
|
}
|
|
}
|
|
|
|
func TestIndentNormalizesGitProgressOutput(t *testing.T) {
|
|
got := indent("first\rsecond\r\nthird", " ")
|
|
want := " first\n second\n third"
|
|
if got != want {
|
|
t.Fatalf("indent() = %q, want %q", got, want)
|
|
}
|
|
}
|