Move stable gitsync model behind bridge · Entire

Move stable gitsync model behind bridge

ccf984d→main·

Sessions

1f9417321410View transcript

Changes

4

37 unmodified lines

38
39
40
41
41
42
43
44
9 unmodified lines

54
55
56
57
57
58
59
60
9 unmodified lines

70
71
72
73
73
74
75
76

37 unmodified lines

if err != nil {
        return ProbeResult{}, err
    }
    return fromSyncerProbeResult(result), nil
    return internalbridge.FromProbeResult(result), nil
}

// Plan computes ref actions without pushing.
9 unmodified lines

if err != nil {
        return PlanResult{}, err
    }
    return fromSyncerResult(result), nil
    return internalbridge.FromSyncResult(result), nil
}

// Sync executes a sync between two remotes.
9 unmodified lines

if err != nil {
        return SyncResult{}, err
    }
    return fromSyncerResult(result), nil
    return internalbridge.FromSyncResult(result), nil
}

func (c *Client) buildProbeConfig(ctx context.Context, req ProbeRequest) (internalbridge.Config, error) {

Mpkg/gitsync/client.go+3/-3


9 unmodified lines

10
11
12
13
14
15
16
71 unmodified lines

88
89
90
90
91
92
93
94
9 unmodified lines

104
105
106
106
107
108
109
110

9 unmodified lines

"github.com/soph/git-sync/internal/planner"
    "github.com/soph/git-sync/internal/syncer"
    "github.com/soph/git-sync/pkg/gitsync/internalbridge"

func TestBuildSyncConfigUsesDefaultProtocolAndMaterializedLimit(t *testing.T) {
71 unmodified lines
}

func TestFromSyncerResultZeroHashesAreEmptyStrings(t *testing.T) {
    got := hashString(plumbing.ZeroHash)
    got := internalbridge.HashString(plumbing.ZeroHash)
    if got != "" {
        t.Fatalf("hashString(zero) = %q, want empty string", got)
    }
    9 unmodified lines
}

func TestFromSyncerResultShapesStableSummary(t *testing.T) {
    got := fromSyncerResult(syncer.Result{
    got := internalbridge.FromSyncResult(syncer.Result{
        Plans: []planner.BranchPlan{
            {
                Branch:     "main",

Mpkg/gitsync/client_test.go+3/-2


package internalbridge

import (
    "context"

"github.com/go-git/go-git/v6/plumbing"

"github.com/soph/git-sync/internal/planner"
    "github.com/soph/git-sync/internal/syncer"
    "github.com/soph/git-sync/internal/validation"
    "github.com/soph/git-sync/pkg/gitsync/internalbridge"
)

// ProtocolMode controls source-side protocol negotiation.

// RefKind distinguishes branch refs from tag refs.
type RefKind string

const (
    RefKindBranch RefKind = RefKind(planner.RefKindBranch)
    RefKindTag    RefKind = RefKind(planner.RefKindTag)
)

type Action string

const (
    ActionCreate Action = Action(planner.ActionCreate)
    ActionUpdate Action = Action(planner.ActionUpdate)
    ActionDelete Action = Action(planner.ActionDelete)
    ActionSkip   Action = Action(planner.ActionSkip)
    ActionBlock  Action = Action(planner.ActionBlock)
)

// RefPlan describes the outcome for a single ref.
type RefResult struct {
    Branch     string  `json:"branch"`
    SourceRef  string  `json:"source_ref"`
    TargetRef  string  `json:"target_ref"`
    SourceHash string  `json:"source_hash"`
    TargetHash string  `json:"target_hash"`
    Kind       RefKind `json:"kind"`
    Action     Action  `json:"action"`
    Reason     string  `json:"reason"`
}

// RefPlan is kept as an alias for early adopters while the stable result
// surface converges on the more explicit RefResult name.
type RefPlan = RefResult

// RefInfo identifies a named ref.
type RefInfo struct {
    Name string `json:"name"`
    Hash string `json:"hash"`
}

// ServiceStats tracks transfer statistics for a single service.
type ServiceStats struct {
    Name          string `json:"name"`
    Requests      int    `json:"requests"`
    RequestBytes  int64  `json:"request_bytes"`
    ResponseBytes int64  `json:"response_bytes"`
    Wants         int    `json:"wants"`
    Haves         int    `json:"haves"`
    Commands      int    `json:"commands"`
}

// Stats summarizes transfer metrics.
type Stats struct {
    Enabled bool                     `json:"enabled"`
    Items   map[string]*ServiceStats `json:"items"`
}

// Measurement summarizes elapsed time and Go heap usage.
type Measurement struct {
    Enabled            bool   `json:"enabled"`
    ElapsedMillis      int64  `json:"elapsed_millis"`
    PeakAllocBytes     uint64 `json:"peak_alloc_bytes"`
    PeakHeapInuseBytes uint64 `json:"peak_heap_inuse_bytes"`
    TotalAllocBytes    uint64 `json:"total_alloc_bytes"`
    GCCount            uint32 `json:"gc_count"`
}

// ProbeResult holds structured probe output suitable for workers.
type ProbeResult struct {
    SourceURL     string      `json:"source_url"`
    TargetURL     string      `json:"target_url,omitempty"`
    RequestedMode string      `json:"requested_mode"`
    Protocol      string      `json:"protocol"`
    RefPrefixes   []string    `json:"ref_prefixes"`
    Capabilities  []string    `json:"source_capabilities"`
    TargetCaps    []string    `json:"target_capabilities,omitempty"`
    Refs          []RefInfo   `json:"refs"`
    Stats         Stats       `json:"stats"`
    Measurement   Measurement `json:"measurement"`
}

// SyncCounts summarizes per-run ref outcomes.
type SyncCounts struct {
    Applied int `json:"applied"`
    Skipped int `json:"skipped"`
    Blocked int `json:"blocked"`
    Deleted int `json:"deleted"`
}

// BatchSummary describes batching behavior when it was used.
type BatchSummary struct {
    Enabled bool `json:"enabled"`
    Planned int  `json:"planned"`
    Done    int  `json:"done"`
}

// ExecutionSummary describes how a sync was carried out.
type ExecutionSummary struct {
    DryRun             bool         `json:"dry_run"`
    Protocol           string       `json:"protocol"`
    Relay              bool         `json:"relay"`
    Mode               string       `json:"mode"`
    Reason             string       `json:"reason"`
    BootstrapSuggested bool         `json:"bootstrap_suggested"`
    Batch              BatchSummary `json:"batch"`
}

// SyncResult holds structured sync output suitable for workers.
type SyncResult struct {
    Refs        []RefResult      `json:"refs"`
    Counts      SyncCounts       `json:"counts"`
    Execution   ExecutionSummary `json:"execution"`
    Stats       Stats            `json:"stats"`
    Measurement Measurement      `json:"measurement"`
}

// PlanResult is the dry-run form of SyncResult.
type PlanResult = SyncResult

func fromSyncerProbeResult(result syncer.ProbeResult) ProbeResult {
    out := ProbeResult{
        SourceURL:     result.SourceURL,
        TargetURL:     result.TargetURL,
        RequestedMode: result.RequestedMode,
        Protocol:      result.Protocol,
        RefPrefixes:   append([]string(nil), result.RefPrefixes...),
        Capabilities:  append([]string(nil), result.Capabilities...),
        TargetCaps:    append([]string(nil), result.TargetCaps...),
        Refs:          make([]RefInfo, 0, len(result.Refs)),
        Stats:         fromSyncerStats(result.Stats),
        Measurement:   fromSyncerMeasurement(result.Measurement),
    }
    for _, ref := range result.Refs {
        out.Refs = append(out.Refs, RefInfo{Name: ref.Name, Hash: ref.Hash.String()})
    }
    return out
}

func fromSyncerResult(result syncer.Result) SyncResult {
    out := SyncResult{
        Refs: make([]RefResult, 0, len(result.Plans)),
        Counts: SyncCounts{
            Applied: result.Pushed,
            Skipped: result.Skipped,
            Blocked: result.Blocked,
            Deleted: result.Deleted,
        },
        Execution: ExecutionSummary{
            DryRun:             result.DryRun,
            Protocol:           result.Protocol,
            Relay:              result.Relay,
            Mode:               result.RelayMode,
            Reason:             result.RelayReason,
            BootstrapSuggested: result.BootstrapSuggested,
            Batch: BatchSummary{
                Enabled: result.Batching,
                Planned: result.PlannedBatchCount,
                Done:    result.BatchCount,
            },
        },
        Stats:       fromSyncerStats(result.Stats),
        Measurement: fromSyncerMeasurement(result.Measurement),
    }
    for _, plan := range result.Plans {
        out.Refs = append(out.Refs, RefResult{
            Branch:     plan.Branch,
            SourceRef:  plan.SourceRef.String(),
            TargetRef:  plan.TargetRef.String(),
            SourceHash: hashString(plan.SourceHash),
            TargetHash: hashString(plan.TargetHash),
            Kind:       RefKind(plan.Kind),
            Action:     Action(plan.Action),
            Reason:     plan.Reason,
        })
    }
    return out
}

func fromSyncerStats(stats syncer.Stats) Stats {
    out := Stats{Enabled: stats.Enabled, Items: make(map[string]*ServiceStats, len(stats.Items))}
    for key, item := range stats.Items {
        copyItem := *item
        out.Items[key] = &ServiceStats{
            Name:          copyItem.Name,
            Requests:      copyItem.Requests,
            RequestBytes:  copyItem.RequestBytes,
            ResponseBytes: copyItem.ResponseBytes,
            Wants:         copyItem.Wants,
            Haves:         copyItem.Haves,
            Commands:      copyItem.Commands,
        }
    }
    return out
}

func fromSyncerMeasurement(m syncer.Measurement) Measurement {
    return Measurement{
        Enabled:            m.Enabled,
        ElapsedMillis:      m.ElapsedMillis,
        PeakAllocBytes:     m.PeakAllocBytes,
        PeakHeapInuseBytes: m.PeakHeapInuseBytes,
        TotalAllocBytes:    m.TotalAllocBytes,
        GCCount:            m.GCCount,
    }
}

func hashString(hash plumbing.Hash) string {
    if hash.IsZero() {
        return ""
    }
    return hash.String()
}