# Add failing tests for HEAD propagation: source/target HEAD in Result + ProbeResult

`7d1c3b8`·  
  
Soph·2mo ago·1 file·+103 added/-0 removed

issue #45: git-sync mirrors refs but never propagates the source's HEAD  
symref target to the target. A repo mirrored from a default=main source  
into a default=master target ends up with a dangling target HEAD; a  
subsequent `git clone target` checks out master instead of main.

Step 1 of the implementation: pin the public contract via three  
failing integration tests that drive the API shape.

- TestRun_IntegrationBootstrapSurfacesHEADInResult: source default  
  master, target init'd with --default-branch=main, asserts Result  
  exposes both HEAD targets as plumbing.ReferenceName.
- TestRun_IntegrationBootstrapHEADMatch: same default on both sides,  
  asserts SourceHEAD == TargetHEAD so callers can detect mismatch via  
  field equality.
- TestProbe_IntegrationSurfacesBothHEADs: probe surfaces the same info  
  without mutating target, useful for previewing the mismatch.

The tests fail to build today (Result and ProbeResult don't have the  
SourceHEAD/TargetHEAD fields yet); subsequent commits add the fields  
and the capture logic.

Refs #45.

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

## Sessions

ce76aab97a1fView transcript

## Changes

1  
- internal/syncer

- Mintegration_test.go+103

```
1849 unmodified lines

... // ProbeResult.SourceHEAD and TargetHEAD let `git-sync probe` preview the  
// default-branch mismatch without actually mutating the target.  
func TestProbe_IntegrationSurfacesBothHEADs(t *testing.T) {  
    sourceRepo, sourceFS := newSourceRepo(t)  
    makeCommits(t, sourceRepo, sourceFS, 1)

targetRepo, err := git.Init(memory.NewStorage(), nil, git.WithDefaultBranch("refs/heads/main"))  
    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 := Probe(context.Background(), Config{  
        Source:       Endpoint{URL: sourceServer.RepoURL()},  
        Target:       Endpoint{URL: targetServer.RepoURL()},  
        ProtocolMode: protocolModeAuto,  
    })  
    if err != nil {  
        t.Fatalf("probe: %v", err)  
    }  
    wantSource := plumbing.NewBranchReferenceName(testBranch)  
    if result.SourceHEAD != wantSource {  
        t.Errorf("SourceHEAD = %q, want %q", result.SourceHEAD, wantSource)  
    }  
    wantTarget := plumbing.ReferenceName("refs/heads/main")  
    if result.TargetHEAD != wantTarget {  
        t.Errorf("TargetHEAD = %q, want %q", result.TargetHEAD, wantTarget)  
    }  
}

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

// Result.SourceHEAD and Result.TargetHEAD capture each side's symref HEAD  
// target, so library callers can detect a default-branch mismatch without  
// scraping the notice stream. Bootstrap into an empty target whose bare-repo  
// HEAD default differs from the source's surfaces this case.  
func TestRun_IntegrationBootstrapSurfacesHEADInResult(t *testing.T) {  
    sourceRepo, sourceFS := newSourceRepo(t)  
    makeCommits(t, sourceRepo, sourceFS, 1)

// go-git Init defaults HEAD to refs/heads/master; pin target to  
    // refs/heads/main so the symrefs diverge.  
    targetRepo, err := git.Init(memory.NewStorage(), nil, git.WithDefaultBranch("refs/heads/main"))  
    if err != nil {  
        t.Fatalf("init target repo: %v", err)  
    }

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)  
    }  
    wantSourceHEAD := plumbing.NewBranchReferenceName(testBranch)  
    if result.SourceHEAD != wantSourceHEAD {  
        t.Errorf("SourceHEAD = %q, want %q", result.SourceHEAD, wantSourceHEAD)  
    }  
    wantTargetHEAD := plumbing.ReferenceName("refs/heads/main")  
    if result.TargetHEAD != wantTargetHEAD {  
        t.Errorf("TargetHEAD = %q, want %q", result.TargetHEAD, wantTargetHEAD)  
    }  
}

// When source HEAD and target HEAD match, the sync surfaces both fields  
// identically — no mismatch from the consumer's perspective.  
func TestRun_IntegrationBootstrapHEADMatch(t *testing.T) {  
    ...  
}

// --exclude-ref-prefix trims namespaces from --all-refs auto-discovery. The  
// GitHub use case is `--all-refs --exclude-ref-prefix refs/pull/`: mirror  
// branches/tags/notes but skip the fork-commit blowup from PR refs.
```
