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:
@@ -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