feat(sync): replace hub with an internal branch engine
Fetch and prune the preferred remote, then reconcile every local branch independently. Fast-forward branches that are behind, preserve divergent work, and delete a branch only when its configured upstream is gone and its commits are already contained by the remote default branch. Keep dirty repositories eligible while protecting the active branch and refs checked out in linked worktrees. Preserve the previous all-clean policy behind --strict, expose structured per-branch results, add real bare-remote integration tests, and retain the upstream MIT notice for the adapted hub sync behavior.
This commit is contained in:
+20
-8
@@ -10,20 +10,21 @@ import (
|
||||
func testTUIRepos() []repo {
|
||||
return []repo{
|
||||
{Path: "/workspace/clean", RelativePath: "clean", Branch: "main", Eligible: true},
|
||||
{Path: "/workspace/dirty", RelativePath: "dirty", Branch: "develop", BlockReason: "dirty worktree (1 changes)", Dirty: []string{" M file.txt"}},
|
||||
{Path: "/workspace/dirty", RelativePath: "dirty", Branch: "develop", Dirty: []string{" M file.txt"}, Eligible: true},
|
||||
{Path: "/workspace/blocked", RelativePath: "blocked", BlockReason: "detached HEAD"},
|
||||
}
|
||||
}
|
||||
|
||||
func TestTUIOnlySelectsEligibleRepositories(t *testing.T) {
|
||||
m := newTUIModel("/workspace", testTUIRepos(), options{})
|
||||
if got := len(m.selectedRepos()); got != 1 {
|
||||
t.Fatalf("selected repositories = %d, want 1", got)
|
||||
if got := len(m.selectedRepos()); got != 2 {
|
||||
t.Fatalf("selected repositories = %d, want 2", got)
|
||||
}
|
||||
|
||||
m.cursor = 1
|
||||
m.cursor = 2
|
||||
updated, _ := m.Update(tea.KeyMsg{Type: tea.KeySpace})
|
||||
m = updated.(tuiModel)
|
||||
if got := len(m.selectedRepos()); got != 1 {
|
||||
if got := len(m.selectedRepos()); got != 2 {
|
||||
t.Fatalf("blocked repository became selectable; selected = %d", got)
|
||||
}
|
||||
}
|
||||
@@ -45,16 +46,27 @@ func TestTUIRequiresExplicitConfirmation(t *testing.T) {
|
||||
|
||||
func TestTUIViewExplainsBlockedRepository(t *testing.T) {
|
||||
m := newTUIModel("/workspace", testTUIRepos(), options{})
|
||||
m.width, m.height, m.cursor = 100, 30, 1
|
||||
m.width, m.height, m.cursor = 100, 30, 2
|
||||
view := m.View()
|
||||
for _, want := range []string{"STACK SYNC", "dirty worktree", "file.txt", "1 blocked"} {
|
||||
for _, want := range []string{"STACK SYNC", "detached HEAD", "1 blocked"} {
|
||||
if !strings.Contains(view, want) {
|
||||
t.Fatalf("view missing %q:\n%s", want, view)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTUIViewShowsLastHubOutputForFocusedRepository(t *testing.T) {
|
||||
func TestTUIViewExplainsDirtyBranchProtection(t *testing.T) {
|
||||
m := newTUIModel("/workspace", testTUIRepos(), options{})
|
||||
m.width, m.height, m.cursor = 100, 30, 1
|
||||
view := m.View()
|
||||
for _, want := range []string{"ready (dirty)", "CHECKED-OUT BRANCH PROTECTED", "file.txt"} {
|
||||
if !strings.Contains(view, want) {
|
||||
t.Fatalf("view missing %q:\n%s", want, view)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTUIViewShowsLastSyncOutputForFocusedRepository(t *testing.T) {
|
||||
m := newTUIModel("/workspace", testTUIRepos(), options{})
|
||||
m.width, m.height = 100, 30
|
||||
m.lastResult = []syncResult{{Path: "clean", Success: true, Message: "Updated branch main", Duration: "120ms"}}
|
||||
|
||||
Reference in New Issue
Block a user