# Surface source HEAD symref in Result and ProbeResult

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

Soph·2mo ago·3 files·+108 added/-29 removed

The session already captures source HEAD's symref target during ref
discovery (it's used by bootstrap-batch trunk ordering), but never
escapes to callers. Expose it on syncer.Result.SourceHEAD,
syncer.ProbeResult.SourceHEAD, and the matching internalbridge fields,
so library callers and the CLI can compare against the target's
intended default branch without out-of-band metadata.

Human Lines() output picks up a "source-head: <ref>" line; JSON output
gets `execution.sourceHead` for sync paths and `sourceHead` on
probe. Field is `omitempty` so detached-HEAD sources omit it.

Refs #45.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

## Sessions

1d796b864aa7View transcript

## Changes

3

- internal/syncer

- Mintegration_test.go+50

- Msyncer.go+54/-29

- internalbridge

- Mmodel.go+4

```go
2780 unmodified lines
}
```

// Sync surfaces the source's symref HEAD target on Result, parsed from the
// upload-pack advertisement we already make. Used by callers to know the
// source's default branch without out-of-band metadata.
func TestRun_IntegrationSyncSurfacesSourceHEAD(t *testing.T) {
    sourceRepo, sourceFS := newSourceRepo(t)
    makeCommits(t, sourceRepo, sourceFS, 1)

targetRepo, err := git.Init(memory.NewStorage())
    if err != nil {
        t.Fatalf("init target repo: %v", err)
    }

sourceServer := newSmartHTTPRepoServerV2(t, sourceRepo)
    targetServer := newSmartHTTPRepoServer(t, targetRepo)
    defer sourceServer.Close()
    defer targetServer.Close()

result, err := Run(context.Background(), Config{
        Source:       Endpoint{URL: sourceServer.RepoURL()},
        Target:       Endpoint{URL: targetServer.RepoURL()},
        ProtocolMode: protocolModeAuto,
    })
    if err != nil {
        t.Fatalf("sync: %v", err)
    }
    if got, want := result.SourceHEAD, plumbing.NewBranchReferenceName(testBranch); got != want {
        t.Errorf("SourceHEAD = %q, want %q", got, want)
    }
}

// Probe surfaces the source's symref HEAD target without performing a sync.
func TestProbe_IntegrationSurfacesSourceHEAD(t *testing.T) {
    sourceRepo, sourceFS := newSourceRepo(t)
    makeCommits(t, sourceRepo, sourceFS, 1)

sourceServer := newSmartHTTPRepoServerV2(t, sourceRepo)
    defer sourceServer.Close()

result, err := Probe(context.Background(), Config{
        Source:       Endpoint{URL: sourceServer.RepoURL()},
        ProtocolMode: protocolModeAuto,
    })
    if err != nil {
        t.Fatalf("probe: %v", err)
    }
    if got, want := result.SourceHEAD, plumbing.NewBranchReferenceName(testBranch); got != want {
        t.Errorf("SourceHEAD = %q, want %q", got, want)
    }
}

func TestRun_IntegrationAllRefsBootstrapsCustomNamespace(t *testing.T) {
    sourceRepo, sourceFS := newSourceRepo(t)
    makeCommits(t, sourceRepo, sourceFS, 2)
}

// Result holds the outcome of a sync or bootstrap operation.
// SourceHEAD carries the source's symref HEAD target when advertised; lets
// callers compare against the target's intended default branch.
type Result struct {
    Plans              []BranchPlan `json:"plans"`
    Pushed             int          `json:"pushed"`
    Skipped            int          `json:"skipped"`
    Blocked            int          `json:"blocked"`
    Deleted            int          `json:"deleted"`
    Warned             int          `json:"warned"`
    DryRun             bool         `json:"dryRun"`
    OperationMode      string       `json:"operationMode"`
    Relay              bool         `json:"relay"`
    RelayMode          string       `json:"relayMode"`
    RelayReason        string       `json:"relayReason"`
    Batching           bool         `json:"batching"`
    BatchCount         int          `json:"batchCount"`
    PlannedBatchCount  int          `json:"plannedBatchCount"`
    TempRefs           []string     `json:"tempRefs"`
    BootstrapSuggested bool         `json:"bootstrapSuggested"`
    Stats              Stats        `json:"stats"`
    Measurement        Measurement  `json:"measurement"`
    Protocol           string       `json:"protocol"`
    SourceHEAD         plumbing.ReferenceName `json:"sourceHead,omitempty"`
}

func (r Result) Lines() []string {
    if r.Batching && len(r.TempRefs) > 0 {
        lines = append(lines, "batching: temp-refs="+strings.Join(r.TempRefs, ","))
    }
    if r.SourceHEAD != "" {
        lines = append(lines, "source-head: "+r.SourceHEAD.String())
    }
    return lines
}

// ProbeResult holds the outcome of a probe operation.
type ProbeResult struct {
    SourceURL     string      `json:"sourceUrl"`
    TargetURL     string      `json:"targetUrl,omitempty"`
    RequestedMode string      `json:"requestedMode"`
    Protocol      string      `json:"protocol"`
    RefPrefixes   []string    `json:"refPrefixes"`
    Capabilities  []string    `json:"sourceCapabilities"`
    Refs          []RefInfo   `json:"refs"`
    Stats         Stats       `json:"stats"`
    Measurement   Measurement `json:"measurement"`
    SourceHEAD    plumbing.ReferenceName `json:"sourceHead,omitempty"`
}

func (r ProbeResult) Lines() []string {
    if len(r.RefPrefixes) > 0 {
        lines = append(lines, "ref-prefixes: "+strings.Join(r.RefPrefixes, ", "))
    }
    if r.SourceHEAD != "" {
        lines = append(lines, "source-head: "+r.SourceHEAD.String())
    }
    if len(r.Capabilities) > 0 {
        lines = append(lines, "source-capabilities: "+strings.Join(r.Capabilities, ", "))
    }
}
