@@ -20,8 +20,11 @@ import (
2020 "github.com/go-git/go-billy/v5"
2121 "github.com/go-git/go-billy/v5/memfs"
2222 "github.com/go-git/go-git/v5"
23+ "github.com/go-git/go-git/v5/config"
2324 "github.com/go-git/go-git/v5/plumbing"
2425 "github.com/go-git/go-git/v5/plumbing/cache"
26+ "github.com/go-git/go-git/v5/plumbing/filemode"
27+ "github.com/go-git/go-git/v5/plumbing/format/index"
2528 "github.com/go-git/go-git/v5/plumbing/format/pktline"
2629 "github.com/go-git/go-git/v5/plumbing/object"
2730 "github.com/go-git/go-git/v5/plumbing/protocol/packp"
@@ -272,38 +275,107 @@ func NewRepo(t *testing.T, fs billy.Filesystem, commits ...CommitFunc) *git.Repo
272275
273276// CreateGitServerWithSubmodule creates a parent git repo with a submodule pointing to another repo.
274277// Returns the parent server and the submodule server.
278+ // The submodule is properly registered with a gitlink entry in the tree.
275279func CreateGitServerWithSubmodule (t * testing.T , opts Options , submoduleOpts Options ) (parentSrv * httptest.Server , submoduleSrv * httptest.Server ) {
276280 t .Helper ()
277281
278- // Create the submodule repo first
279- submoduleSrv = CreateGitServer (t , submoduleOpts )
282+ // Create the submodule repo first and get its HEAD commit
283+ submoduleFS := memfs .New ()
284+ submoduleCommits := make ([]CommitFunc , 0 )
285+ for path , content := range submoduleOpts .Files {
286+ submoduleCommits = append (submoduleCommits , Commit (t , path , content , "submodule commit" ))
287+ }
288+ submoduleRepo := NewRepo (t , submoduleFS , submoduleCommits ... )
289+
290+ // Get the submodule's HEAD commit hash
291+ submoduleHead , err := submoduleRepo .Head ()
292+ require .NoError (t , err )
293+ submoduleHash := submoduleHead .Hash ()
294+
295+ // Start the submodule server
296+ if submoduleOpts .AuthMW == nil {
297+ submoduleOpts .AuthMW = mwtest .BasicAuthMW (submoduleOpts .Username , submoduleOpts .Password )
298+ }
299+ if submoduleOpts .TLS {
300+ submoduleSrv = httptest .NewTLSServer (submoduleOpts .AuthMW (NewServer (submoduleFS )))
301+ } else {
302+ submoduleSrv = httptest .NewServer (submoduleOpts .AuthMW (NewServer (submoduleFS )))
303+ }
280304
281- // Create the parent repo with .gitmodules pointing to submodule
305+ // Create the parent repo with .gitmodules and gitlink entry
282306 if opts .AuthMW == nil {
283307 opts .AuthMW = mwtest .BasicAuthMW (opts .Username , opts .Password )
284308 }
285309
286- fs := memfs .New ()
310+ parentFS := memfs .New ()
287311 commits := make ([]CommitFunc , 0 )
288312 for path , content := range opts .Files {
289313 commits = append (commits , Commit (t , path , content , "my test commit" ))
290314 }
291- // Add gitmodules file pointing to the submodule server
292- gitmodulesContent := fmt .Sprintf (`[submodule "submod"]
293- path = submod
294- url = %s
295- ` , submoduleSrv .URL )
296- commits = append (commits , Commit (t , ".gitmodules" , gitmodulesContent , "add submodule" ))
297- _ = NewRepo (t , fs , commits ... )
315+
316+ // Add .gitmodules file and gitlink entry for the submodule
317+ commits = append (commits , CommitSubmodule (t , "submod" , submoduleSrv .URL , submoduleHash ))
318+
319+ _ = NewRepo (t , parentFS , commits ... )
298320
299321 if opts .TLS {
300- parentSrv = httptest .NewTLSServer (opts .AuthMW (NewServer (fs )))
322+ parentSrv = httptest .NewTLSServer (opts .AuthMW (NewServer (parentFS )))
301323 } else {
302- parentSrv = httptest .NewServer (opts .AuthMW (NewServer (fs )))
324+ parentSrv = httptest .NewServer (opts .AuthMW (NewServer (parentFS )))
303325 }
304326 return parentSrv , submoduleSrv
305327}
306328
329+ // CommitSubmodule creates a commit that adds a submodule with proper .gitmodules and gitlink entry.
330+ func CommitSubmodule (t * testing.T , path , url string , hash plumbing.Hash ) CommitFunc {
331+ return func (fs billy.Filesystem , repo * git.Repository ) {
332+ t .Helper ()
333+ tree , err := repo .Worktree ()
334+ require .NoError (t , err )
335+
336+ // Create .gitmodules file
337+ gitmodulesContent := fmt .Sprintf ("[submodule %q]\n \t path = %s\n \t url = %s\n " , path , path , url )
338+ WriteFile (t , fs , ".gitmodules" , gitmodulesContent )
339+ _ , err = tree .Add (".gitmodules" )
340+ require .NoError (t , err )
341+
342+ // Add submodule config to .git/config
343+ cfg , err := repo .Config ()
344+ require .NoError (t , err )
345+ cfg .Submodules [path ] = & config.Submodule {
346+ Name : path ,
347+ Path : path ,
348+ URL : url ,
349+ }
350+ err = repo .SetConfig (cfg )
351+ require .NoError (t , err )
352+
353+ // Create the gitlink entry (mode 160000 commit reference)
354+ // We need to add it directly to the index
355+ idx , err := repo .Storer .Index ()
356+ require .NoError (t , err )
357+
358+ // Add a gitlink entry - this is a special index entry with mode 160000
359+ idx .Entries = append (idx .Entries , & index.Entry {
360+ Mode : filemode .Submodule ,
361+ Hash : hash ,
362+ Name : path ,
363+ })
364+ err = repo .Storer .SetIndex (idx )
365+ require .NoError (t , err )
366+
367+ // Commit the changes
368+ _ , err = tree .Commit ("add submodule" , & git.CommitOptions {
369+ Author : & object.Signature {
370+ Name : "Example" ,
371+ Email : "test@example.com" ,
372+ When : time .Now (),
373+ },
374+ })
375+ require .NoError (t , err )
376+ }
377+ }
378+
307379// WriteFile writes a file to the filesystem.
308380func WriteFile (t * testing.T , fs billy.Filesystem , path , content string ) {
309381 t .Helper ()
0 commit comments