# Move stable gitsync model behind bridge

`ccf984d`→[main](/content/gh/entireio/git-sync/commits/main/index.html)·

## Sessions

1f9417321410View transcript

## Changes

4

- pkg/gitsync
  
  - Mclient.go+3/-3

- Mclient_test.go+3/-2

- internalbridge
    
    - Amodel.go+207

- Mtypes.go+22/-210

```
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()
}
