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)
|
||||
}
|
||||
|
||||
// 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
|
||||
var peers []string
|
||||
if federationPeer != "" {
|
||||
peers = []string{federationPeer}
|
||||
} else {
|
||||
remotes, err := ds.ListRemotes(ctx)
|
||||
if err != nil {
|
||||
FatalErrorRespectJSON("failed to list peers: %v", err)
|
||||
}
|
||||
for _, r := range remotes {
|
||||
for _, r := range allRemotes {
|
||||
peers = append(peers, r.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(peers) == 0 {
|
||||
if jsonOutput {
|
||||
outputJSON(map[string]interface{}{"peers": []string{}})
|
||||
outputJSON(map[string]interface{}{
|
||||
"peers": []string{},
|
||||
"pendingChanges": 0,
|
||||
})
|
||||
} else {
|
||||
fmt.Println("No federation peers configured.")
|
||||
}
|
||||
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 {
|
||||
ps := peerStatus{
|
||||
URL: remoteURLs[peer],
|
||||
}
|
||||
|
||||
// Get sync status
|
||||
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 {
|
||||
outputJSON(map[string]interface{}{
|
||||
"peers": peers,
|
||||
"statuses": statuses,
|
||||
"peers": peerStatuses,
|
||||
"pendingChanges": pendingChanges,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
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 {
|
||||
fmt.Printf(" Ahead: %d commits\n", status.LocalAhead)
|
||||
fmt.Printf(" Behind: %d commits\n", status.LocalBehind)
|
||||
} 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 {
|
||||
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
|
||||
}
|
||||
|
||||
// TODO: Track last sync time in metadata
|
||||
status.LastSync = time.Time{} // Zero time indicates never synced
|
||||
// Get last sync time from metadata
|
||||
status.LastSync = s.getLastSyncTime(ctx, peer)
|
||||
|
||||
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:
|
||||
// 1. Fetch from peer
|
||||
// 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
|
||||
}
|
||||
|
||||
// Record last sync time
|
||||
_ = s.setLastSyncTime(ctx, peer)
|
||||
|
||||
result.EndTime = time.Now()
|
||||
return result, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user