feat(federation): enhance bd federation status command (bd-wkumz.5)
Improve the federation status command to show more comprehensive information similar to `git remote -v` with health info: - Show peer URLs alongside peer names - Display pending local changes count (uncommitted) - Test connectivity to each peer (via fetch) - Track and display last sync time in metadata table - Show reachability status with error messages on failure Changes: - cmd/bd/federation.go: Enhanced status output with URLs, connectivity checks, pending changes, and last sync time - internal/storage/dolt/federation.go: Added getLastSyncTime/setLastSyncTime methods using metadata table, record sync time on successful sync Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Steve Yegge
parent
b7242a67d1
commit
c3f68caf7a
@@ -241,52 +241,117 @@ func runFederationStatus(cmd *cobra.Command, args []string) {
|
|||||||
FatalErrorRespectJSON("%v", err)
|
FatalErrorRespectJSON("%v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get all remotes for URL lookup
|
||||||
|
allRemotes, err := ds.ListRemotes(ctx)
|
||||||
|
if err != nil {
|
||||||
|
FatalErrorRespectJSON("failed to list remotes: %v", err)
|
||||||
|
}
|
||||||
|
remoteURLs := make(map[string]string)
|
||||||
|
for _, r := range allRemotes {
|
||||||
|
remoteURLs[r.Name] = r.URL
|
||||||
|
}
|
||||||
|
|
||||||
// Get peers to check
|
// Get peers to check
|
||||||
var peers []string
|
var peers []string
|
||||||
if federationPeer != "" {
|
if federationPeer != "" {
|
||||||
peers = []string{federationPeer}
|
peers = []string{federationPeer}
|
||||||
} else {
|
} else {
|
||||||
remotes, err := ds.ListRemotes(ctx)
|
for _, r := range allRemotes {
|
||||||
if err != nil {
|
|
||||||
FatalErrorRespectJSON("failed to list peers: %v", err)
|
|
||||||
}
|
|
||||||
for _, r := range remotes {
|
|
||||||
peers = append(peers, r.Name)
|
peers = append(peers, r.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(peers) == 0 {
|
if len(peers) == 0 {
|
||||||
if jsonOutput {
|
if jsonOutput {
|
||||||
outputJSON(map[string]interface{}{"peers": []string{}})
|
outputJSON(map[string]interface{}{
|
||||||
|
"peers": []string{},
|
||||||
|
"pendingChanges": 0,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("No federation peers configured.")
|
fmt.Println("No federation peers configured.")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var statuses []*storage.SyncStatus
|
// Get pending local changes
|
||||||
|
doltStatus, _ := ds.Status(ctx)
|
||||||
|
pendingChanges := 0
|
||||||
|
if doltStatus != nil {
|
||||||
|
pendingChanges = len(doltStatus.Staged) + len(doltStatus.Unstaged)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect status for each peer
|
||||||
|
type peerStatus struct {
|
||||||
|
Status *storage.SyncStatus
|
||||||
|
URL string
|
||||||
|
Reachable bool
|
||||||
|
ReachError string
|
||||||
|
}
|
||||||
|
var peerStatuses []peerStatus
|
||||||
|
|
||||||
for _, peer := range peers {
|
for _, peer := range peers {
|
||||||
|
ps := peerStatus{
|
||||||
|
URL: remoteURLs[peer],
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get sync status
|
||||||
status, _ := ds.SyncStatus(ctx, peer)
|
status, _ := ds.SyncStatus(ctx, peer)
|
||||||
statuses = append(statuses, status)
|
ps.Status = status
|
||||||
|
|
||||||
|
// Test connectivity by attempting a fetch
|
||||||
|
fetchErr := ds.Fetch(ctx, peer)
|
||||||
|
if fetchErr == nil {
|
||||||
|
ps.Reachable = true
|
||||||
|
// Re-get status after successful fetch for accurate ahead/behind
|
||||||
|
status, _ = ds.SyncStatus(ctx, peer)
|
||||||
|
ps.Status = status
|
||||||
|
} else {
|
||||||
|
ps.ReachError = fetchErr.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
peerStatuses = append(peerStatuses, ps)
|
||||||
}
|
}
|
||||||
|
|
||||||
if jsonOutput {
|
if jsonOutput {
|
||||||
outputJSON(map[string]interface{}{
|
outputJSON(map[string]interface{}{
|
||||||
"peers": peers,
|
"peers": peerStatuses,
|
||||||
"statuses": statuses,
|
"pendingChanges": pendingChanges,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n%s Federation Status:\n\n", ui.RenderAccent("🌐"))
|
fmt.Printf("\n%s Federation Status:\n\n", ui.RenderAccent("🌐"))
|
||||||
for _, status := range statuses {
|
|
||||||
fmt.Printf(" %s\n", ui.RenderAccent(status.Peer))
|
// Show local pending changes
|
||||||
|
if pendingChanges > 0 {
|
||||||
|
fmt.Printf(" %s %d pending local changes\n\n", ui.RenderWarn("⚠"), pendingChanges)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ps := range peerStatuses {
|
||||||
|
status := ps.Status
|
||||||
|
fmt.Printf(" %s %s\n", ui.RenderAccent(status.Peer), ui.RenderMuted(ps.URL))
|
||||||
|
|
||||||
|
// Connectivity status
|
||||||
|
if ps.Reachable {
|
||||||
|
fmt.Printf(" %s Reachable\n", ui.RenderPass("✓"))
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" %s Unreachable: %s\n", ui.RenderFail("✗"), ps.ReachError)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync status
|
||||||
if status.LocalAhead >= 0 {
|
if status.LocalAhead >= 0 {
|
||||||
fmt.Printf(" Ahead: %d commits\n", status.LocalAhead)
|
fmt.Printf(" Ahead: %d commits\n", status.LocalAhead)
|
||||||
fmt.Printf(" Behind: %d commits\n", status.LocalBehind)
|
fmt.Printf(" Behind: %d commits\n", status.LocalBehind)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(" Status: not fetched yet\n")
|
fmt.Printf(" Sync: %s\n", ui.RenderMuted("not fetched yet"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Last sync time
|
||||||
|
if !status.LastSync.IsZero() {
|
||||||
|
fmt.Printf(" Last sync: %s\n", status.LastSync.Format("2006-01-02 15:04:05"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conflicts
|
||||||
if status.HasConflicts {
|
if status.HasConflicts {
|
||||||
fmt.Printf(" %s Unresolved conflicts\n", ui.RenderWarn("⚠"))
|
fmt.Printf(" %s Unresolved conflicts\n", ui.RenderWarn("⚠"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,12 +118,36 @@ func (s *DoltStore) SyncStatus(ctx context.Context, peer string) (*storage.SyncS
|
|||||||
status.HasConflicts = true
|
status.HasConflicts = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Track last sync time in metadata
|
// Get last sync time from metadata
|
||||||
status.LastSync = time.Time{} // Zero time indicates never synced
|
status.LastSync = s.getLastSyncTime(ctx, peer)
|
||||||
|
|
||||||
return status, nil
|
return status, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getLastSyncTime retrieves the last sync time for a peer from metadata.
|
||||||
|
func (s *DoltStore) getLastSyncTime(ctx context.Context, peer string) time.Time {
|
||||||
|
key := "last_sync_" + peer
|
||||||
|
var value string
|
||||||
|
err := s.db.QueryRowContext(ctx, "SELECT value FROM metadata WHERE `key` = ?", key).Scan(&value)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
t, err := time.Parse(time.RFC3339, value)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// setLastSyncTime records the last sync time for a peer in metadata.
|
||||||
|
func (s *DoltStore) setLastSyncTime(ctx context.Context, peer string) error {
|
||||||
|
key := "last_sync_" + peer
|
||||||
|
value := time.Now().Format(time.RFC3339)
|
||||||
|
_, err := s.db.ExecContext(ctx,
|
||||||
|
"REPLACE INTO metadata (`key`, value) VALUES (?, ?)", key, value)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Sync performs a full bidirectional sync with a peer:
|
// Sync performs a full bidirectional sync with a peer:
|
||||||
// 1. Fetch from peer
|
// 1. Fetch from peer
|
||||||
// 2. Merge peer's changes (handling conflicts per strategy)
|
// 2. Merge peer's changes (handling conflicts per strategy)
|
||||||
@@ -195,6 +219,9 @@ func (s *DoltStore) Sync(ctx context.Context, peer string, strategy string) (*Sy
|
|||||||
result.Pushed = true
|
result.Pushed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Record last sync time
|
||||||
|
_ = s.setLastSyncTime(ctx, peer)
|
||||||
|
|
||||||
result.EndTime = time.Now()
|
result.EndTime = time.Now()
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user