feat: own the branch synchronization engine

Replace the external hub sync dependency with an attributed internal implementation that fetches and updates branches independently. Allow dirty repositories while protecting affected checked-out branches, retain all-clean strict mode, add real-remote safety tests, and update the CLI, TUI, JSON output, version, and documentation.
This commit is contained in:
2026-09-06 19:41:53 +01:00
parent ca53874b48
commit a980153b3e
8 changed files with 703 additions and 170 deletions
+11 -102
View File
@@ -2,63 +2,14 @@ package main
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
)
func TestMain(m *testing.M) {
if os.Getenv("STACK_SYNC_TEST_HELPER") == "hub" {
marker := os.Getenv("STACK_SYNC_TEST_MARKER")
if err := os.WriteFile(marker, []byte(strings.Join(os.Args[1:], " ")), 0o644); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
os.Exit(0)
}
os.Exit(m.Run())
}
func installFakeHub(t *testing.T, dir string) string {
t.Helper()
source, err := os.Executable()
if err != nil {
t.Fatal(err)
}
name := "hub"
if runtime.GOOS == "windows" {
name += ".exe"
}
destination := filepath.Join(dir, name)
if err := os.Link(source, destination); err == nil {
return destination
}
input, err := os.Open(source)
if err != nil {
t.Fatal(err)
}
defer input.Close()
output, err := os.OpenFile(destination, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o755)
if err != nil {
t.Fatal(err)
}
if _, err := io.Copy(output, input); err != nil {
_ = output.Close()
t.Fatal(err)
}
if err := output.Close(); err != nil {
t.Fatal(err)
}
return destination
}
func git(t *testing.T, dir string, args ...string) {
t.Helper()
cmd := exec.Command("git", append([]string{"-C", dir}, args...)...)
@@ -116,7 +67,7 @@ func TestNestedRepoDoesNotDirtyParent(t *testing.T) {
}
}
func TestDirtyFileBlocksRepository(t *testing.T) {
func TestDirtyFileIsReportedButRepositoryRemainsEligible(t *testing.T) {
root := t.TempDir()
path := filepath.Join(root, "repo")
makeRepo(t, path)
@@ -125,12 +76,12 @@ func TestDirtyFileBlocksRepository(t *testing.T) {
}
got := inspect(root, path, []string{path})
if got.Eligible || len(got.Dirty) != 1 {
t.Fatalf("dirty repository was not blocked: %+v", got)
if !got.Eligible || len(got.Dirty) != 1 {
t.Fatalf("dirty repository was not eligible with its changes reported: %+v", got)
}
}
func TestUntrackedFileAlongsideNestedRepoStillBlocksParent(t *testing.T) {
func TestUntrackedFileAlongsideNestedRepoIsStillReported(t *testing.T) {
root := t.TempDir()
parent := filepath.Join(root, "parent")
child := filepath.Join(parent, "child")
@@ -142,8 +93,8 @@ func TestUntrackedFileAlongsideNestedRepoStillBlocksParent(t *testing.T) {
paths, _ := discover(root, nil)
got := inspect(root, parent, paths)
if got.Eligible || len(got.Dirty) != 1 {
t.Fatalf("untracked file should block parent: %+v", got)
if !got.Eligible || len(got.Dirty) != 1 {
t.Fatalf("untracked file should be reported without blocking parent: %+v", got)
}
}
@@ -160,7 +111,7 @@ func TestSkippedDirectoriesAreNotSearched(t *testing.T) {
}
}
func TestSyncRechecksAndSkipsNewlyDirtyRepository(t *testing.T) {
func TestStrictSyncRechecksAndSkipsNewlyDirtyRepository(t *testing.T) {
root := t.TempDir()
path := filepath.Join(root, "repo")
makeRepo(t, path)
@@ -172,55 +123,13 @@ func TestSyncRechecksAndSkipsNewlyDirtyRepository(t *testing.T) {
t.Fatal(err)
}
bin := filepath.Join(root, "bin")
if err := os.Mkdir(bin, 0o755); err != nil {
t.Fatal(err)
}
marker := filepath.Join(root, "hub-was-called")
installFakeHub(t, bin)
t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH"))
t.Setenv("STACK_SYNC_TEST_HELPER", "hub")
t.Setenv("STACK_SYNC_TEST_MARKER", marker)
results := syncAll(root, []repo{planned}, 1, time.Minute, io.Discard, true)
results := syncAll(root, []repo{planned}, 1, time.Minute, io.Discard, true, true)
if len(results) != 1 || !results[0].Skipped || results[0].Success {
t.Fatalf("syncAll() = %+v", results)
}
if _, err := os.Stat(marker); !os.IsNotExist(err) {
t.Fatalf("hub ran despite the newly dirty worktree")
}
}
func TestSyncInvokesHubForCleanRepository(t *testing.T) {
root := t.TempDir()
path := filepath.Join(root, "repo")
makeRepo(t, path)
planned := inspect(root, path, []string{path})
bin := filepath.Join(root, "bin")
if err := os.Mkdir(bin, 0o755); err != nil {
t.Fatal(err)
}
marker := filepath.Join(root, "hub-was-called")
installFakeHub(t, bin)
t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH"))
t.Setenv("STACK_SYNC_TEST_HELPER", "hub")
t.Setenv("STACK_SYNC_TEST_MARKER", marker)
results := syncAll(root, []repo{planned}, 1, time.Minute, io.Discard, true)
if len(results) != 1 || !results[0].Success || results[0].Skipped {
t.Fatalf("syncAll() = %+v", results)
}
args, err := os.ReadFile(marker)
if err != nil {
t.Fatalf("hub was not called: %v", err)
}
if string(args) != "sync --color=never" {
t.Fatalf("hub arguments = %q", args)
}
}
func TestScanWithBlockedRepositoryIsAValidReport(t *testing.T) {
func TestScanReportsDirtyEligibleRepository(t *testing.T) {
root := t.TempDir()
path := filepath.Join(root, "repo")
makeRepo(t, path)
@@ -233,8 +142,8 @@ func TestScanWithBlockedRepositoryIsAValidReport(t *testing.T) {
if code != 0 {
t.Fatalf("run() code = %d, stderr = %q", code, stderr.String())
}
if !bytes.Contains(stdout.Bytes(), []byte("BLOCKED: dirty worktree")) {
t.Fatalf("scan did not report blocked repository:\n%s", stdout.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())
}
}