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:
@@ -3,7 +3,6 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -181,8 +180,6 @@ func (m tuiModel) handleKey(key tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
count := len(m.selectedRepos())
|
||||
if count == 0 {
|
||||
m.status = "Select at least one eligible repository"
|
||||
} else if _, err := exec.LookPath("hub"); err != nil {
|
||||
m.status = "hub is required for sync but was not found on PATH"
|
||||
} else {
|
||||
m.mode = modeConfirm
|
||||
}
|
||||
@@ -231,6 +228,8 @@ func (m tuiModel) View() string {
|
||||
state := readyStyle.Render("ready")
|
||||
if !r.Eligible {
|
||||
state = blockStyle.Render("blocked")
|
||||
} else if len(r.Dirty) > 0 {
|
||||
state = titleStyle.Render("ready (dirty)")
|
||||
}
|
||||
line := fmt.Sprintf("%s %-3s %-*s %-16s %s", pointer, check, nameWidth, truncateMiddle(r.RelativePath, nameWidth), truncateMiddle(r.Branch, 16), state)
|
||||
if i == m.cursor {
|
||||
@@ -250,9 +249,9 @@ func (m tuiModel) View() string {
|
||||
|
||||
switch m.mode {
|
||||
case modeConfirm:
|
||||
b.WriteString(blockStyle.Bold(true).Render(fmt.Sprintf("Run hub sync in %d selected repositories?", selected)))
|
||||
b.WriteString(blockStyle.Bold(true).Render(fmt.Sprintf("Synchronize branches in %d selected repositories?", selected)))
|
||||
b.WriteString("\n")
|
||||
b.WriteString(mutedStyle.Render("hub may delete local branches it considers merged. y confirm • n/esc cancel"))
|
||||
b.WriteString(mutedStyle.Render("Dirty checked-out branches are protected; merged branches with deleted upstreams may be removed. y confirm • n/esc cancel"))
|
||||
case modeSyncing:
|
||||
spinner := []string{"◐", "◓", "◑", "◒"}[m.frame%4]
|
||||
b.WriteString(titleStyle.Render(fmt.Sprintf("%s Syncing %d repositories…", spinner, selected)))
|
||||
@@ -301,7 +300,11 @@ func (m tuiModel) detailView(width int) string {
|
||||
r := m.repos[m.cursor]
|
||||
var lines []string
|
||||
if r.Eligible {
|
||||
lines = append(lines, readyStyle.Render("READY")+" "+r.RelativePath+" "+mutedStyle.Render(r.Branch))
|
||||
state := readyStyle.Render("READY")
|
||||
if len(r.Dirty) > 0 {
|
||||
state = titleStyle.Render("READY · CHECKED-OUT BRANCH PROTECTED")
|
||||
}
|
||||
lines = append(lines, state+" "+r.RelativePath+" "+mutedStyle.Render(r.Branch))
|
||||
} else {
|
||||
lines = append(lines, blockStyle.Render("BLOCKED")+" "+r.RelativePath+" "+r.BlockReason)
|
||||
}
|
||||
@@ -372,7 +375,7 @@ func (m tuiModel) scanCmd() tea.Cmd {
|
||||
|
||||
func (m tuiModel) syncCmd(selected []repo) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
results := syncSelected(m.root, selected, m.repos, m.opts.jobs, m.opts.timeout, io.Discard, true)
|
||||
results := syncSelected(m.root, selected, m.repos, m.opts.jobs, m.opts.timeout, io.Discard, true, false)
|
||||
paths, err := discover(m.root, m.opts.exclusions)
|
||||
if err != nil {
|
||||
return syncDoneMsg{results: results, err: err}
|
||||
|
||||
Reference in New Issue
Block a user