@@ -12,6 +12,7 @@ import (
1212 "github.com/go-git/go-git/v6/plumbing"
1313 "github.com/go-git/go-git/v6/plumbing/format/pktline"
1414 "github.com/go-git/go-git/v6/plumbing/protocol/packp"
15+ "github.com/go-git/go-git/v6/plumbing/protocol/packp/capability"
1516 "github.com/go-git/go-git/v6/plumbing/transport"
1617)
1718
@@ -21,6 +22,10 @@ type RefService struct {
2122 Protocol string // "v1" or "v2"
2223 V1Adv * packp.AdvRefs
2324 V2Caps * V2Capabilities
25+ // HeadTarget is the branch that HEAD points to on the source, when
26+ // advertised as a symref. Empty for detached HEAD or when the source
27+ // does not advertise symref information.
28+ HeadTarget plumbing.ReferenceName
2429 // Verbose, when true, streams source-side sideband progress ("Counting
2530 // objects", "Compressing objects", ...) to stderr and asks the source
2631 // upload-pack to emit progress by not sending the no-progress option.
@@ -36,7 +41,7 @@ func ListSourceRefs(ctx context.Context, conn *Conn, protocolMode string, refPre
3641 if err != nil {
3742 return nil , nil , err
3843 }
39- return refs , & RefService {Protocol : "v1" , V1Adv : adv }, nil
44+ return refs , & RefService {Protocol : "v1" , V1Adv : adv , HeadTarget : headTargetFromAdv ( adv ) }, nil
4045
4146 case "auto" , "v2" :
4247 data , err := RequestInfoRefs (ctx , conn , transport .UploadPackService , "version=2" )
@@ -47,11 +52,11 @@ func ListSourceRefs(ctx context.Context, conn *Conn, protocolMode string, refPre
4752 if ! caps .Supports ("ls-refs" ) || ! caps .Supports ("fetch" ) {
4853 return nil , nil , errors .New ("source does not advertise required protocol v2 commands" )
4954 }
50- refs , err := listSourceRefsV2 (ctx , conn , caps , refPrefixes )
55+ refs , headTarget , err := listSourceRefsV2 (ctx , conn , caps , refPrefixes )
5156 if err != nil {
5257 return nil , nil , err
5358 }
54- return refs , & RefService {Protocol : "v2" , V2Caps : caps }, nil
59+ return refs , & RefService {Protocol : "v2" , V2Caps : caps , HeadTarget : headTarget }, nil
5560 }
5661 if protocolMode == "v2" {
5762 return nil , nil , errors .New ("source did not negotiate protocol v2" )
@@ -65,7 +70,7 @@ func ListSourceRefs(ctx context.Context, conn *Conn, protocolMode string, refPre
6570 if err != nil {
6671 return nil , nil , err
6772 }
68- return refs , & RefService {Protocol : "v1" , V1Adv : adv }, nil
73+ return refs , & RefService {Protocol : "v1" , V1Adv : adv , HeadTarget : headTargetFromAdv ( adv ) }, nil
6974
7075 default :
7176 return nil , nil , fmt .Errorf ("unsupported protocol mode %q" , protocolMode )
@@ -136,46 +141,81 @@ func listSourceRefsV1(ctx context.Context, conn *Conn) (*packp.AdvRefs, []*plumb
136141 return adv , refs , nil
137142}
138143
139- func listSourceRefsV2 (ctx context.Context , conn * Conn , caps * V2Capabilities , prefixes []string ) ([]* plumbing.Reference , error ) {
140- args := []string {"peel" }
144+ func listSourceRefsV2 (ctx context.Context , conn * Conn , caps * V2Capabilities , prefixes []string ) ([]* plumbing.Reference , plumbing.ReferenceName , error ) {
145+ // Always include "HEAD" so the server returns the symref-target attribute
146+ // for HEAD. Without this, callers that pass only "refs/heads/" or
147+ // "refs/tags/" prefixes filter HEAD out of the response and lose the
148+ // default-branch hint that bootstrap planning uses as a trunk cutoff.
149+ args := []string {"peel" , "symrefs" , "ref-prefix HEAD" }
141150 for _ , prefix := range prefixes {
142151 args = append (args , "ref-prefix " + prefix )
143152 }
144153 body , err := EncodeCommand ("ls-refs" , caps .RequestCapabilities (), args )
145154 if err != nil {
146- return nil , err
155+ return nil , "" , err
147156 }
148157 data , err := PostRPC (ctx , conn , transport .UploadPackService , body , true , "upload-pack ls-refs" )
149158 if err != nil {
150- return nil , err
159+ return nil , "" , err
151160 }
152161 return decodeV2LSRefs (bytes .NewReader (data ))
153162}
154163
155- func decodeV2LSRefs (r * bytes.Reader ) ([]* plumbing.Reference , error ) {
164+ func decodeV2LSRefs (r * bytes.Reader ) ([]* plumbing.Reference , plumbing. ReferenceName , error ) {
156165 reader := NewPacketReader (r )
157166 var refs []* plumbing.Reference
167+ var headTarget plumbing.ReferenceName
158168 for {
159169 kind , payload , err := reader .ReadPacket ()
160170 if err != nil {
161- return nil , err
171+ return nil , "" , err
162172 }
163173 if kind == PacketFlush {
164- return refs , nil
174+ return refs , headTarget , nil
165175 }
166176 if kind != PacketData {
167- return nil , fmt .Errorf ("unexpected packet type %v in ls-refs response" , kind )
177+ return nil , "" , fmt .Errorf ("unexpected packet type %v in ls-refs response" , kind )
168178 }
169179 fields := strings .Fields (strings .TrimSpace (string (payload )))
170180 if len (fields ) < 2 {
171- return nil , fmt .Errorf ("malformed ls-refs response line %q" , payload )
181+ return nil , "" , fmt .Errorf ("malformed ls-refs response line %q" , payload )
172182 }
173183 hash := plumbing .NewHash (fields [0 ])
174184 name := plumbing .ReferenceName (fields [1 ])
185+ if name == plumbing .HEAD {
186+ // HEAD is surfaced via headTarget only; not appended to the ref
187+ // slice because it is a symbolic ref, matching v1 behavior where
188+ // symrefs are filtered out by downstream RefHashMap.
189+ for _ , attr := range fields [2 :] {
190+ if target , ok := strings .CutPrefix (attr , "symref-target:" ); ok {
191+ headTarget = plumbing .ReferenceName (target )
192+ break
193+ }
194+ }
195+ continue
196+ }
175197 refs = append (refs , plumbing .NewHashReference (name , hash ))
176198 }
177199}
178200
201+ // headTargetFromAdv extracts the branch HEAD points to from v1 advertised
202+ // capabilities. Returns empty when HEAD is detached or no symref is advertised.
203+ func headTargetFromAdv (adv * packp.AdvRefs ) plumbing.ReferenceName {
204+ if adv == nil || adv .Capabilities == nil {
205+ return ""
206+ }
207+ for _ , value := range adv .Capabilities .Get (capability .SymRef ) {
208+ parts := strings .SplitN (value , ":" , 2 )
209+ if len (parts ) != 2 {
210+ continue
211+ }
212+ if plumbing .ReferenceName (parts [0 ]) == plumbing .HEAD {
213+ return plumbing .ReferenceName (parts [1 ])
214+ }
215+ }
216+ return ""
217+ }
218+
179219func decodeV1AdvRefs (data []byte ) (* packp.AdvRefs , error ) {
180220 rd := bufio .NewReader (bytes .NewReader (data ))
181221 consumedSmartHeader , err := consumeSmartInfoRefsHeader (rd )
0 commit comments