@@ -11,8 +11,10 @@ import (
1111 "github.com/go-git/go-billy/v5"
1212 "github.com/go-git/go-billy/v5/memfs"
1313 gogit "github.com/go-git/go-git/v5"
14+ "github.com/go-git/go-git/v5/config"
1415 "github.com/go-git/go-git/v5/plumbing"
1516 "github.com/go-git/go-git/v5/plumbing/object"
17+ "github.com/go-git/go-git/v5/plumbing/transport"
1618 gogithttp "github.com/go-git/go-git/v5/plumbing/transport/http"
1719 "github.com/go-git/go-git/v5/storage/memory"
1820
@@ -124,6 +126,12 @@ func (c *Client) clone(ctx context.Context) (*gogit.Repository, *gogit.Worktree,
124126 Depth : 1 ,
125127 Auth : c .auth ,
126128 })
129+ if errors .Is (err , transport .ErrEmptyRemoteRepository ) {
130+ // A freshly created GitOps repo has no commits yet, so go-git cannot
131+ // clone it. Initialize an in-memory repo on the target branch wired to
132+ // the remote; the first WriteFile commit then creates the branch on push.
133+ return c .initEmpty ()
134+ }
127135 if err != nil {
128136 return nil , nil , fmt .Errorf ("clone: %w" , err )
129137 }
@@ -134,6 +142,32 @@ func (c *Client) clone(ctx context.Context) (*gogit.Repository, *gogit.Worktree,
134142 return repo , wt , nil
135143}
136144
145+ // initEmpty builds an in-memory repository positioned on c.branch with the
146+ // remote configured, for when the remote exists but has no commits.
147+ func (c * Client ) initEmpty () (* gogit.Repository , * gogit.Worktree , error ) {
148+ repo , err := gogit .Init (memory .NewStorage (), memfs .New ())
149+ if err != nil {
150+ return nil , nil , fmt .Errorf ("init empty repo: %w" , err )
151+ }
152+ if _ , err := repo .CreateRemote (& config.RemoteConfig {
153+ Name : gogit .DefaultRemoteName ,
154+ URLs : []string {c .repoURL },
155+ }); err != nil {
156+ return nil , nil , fmt .Errorf ("create remote: %w" , err )
157+ }
158+ // Point HEAD at the target branch so the initial commit lands there even
159+ // though it does not exist yet (orphan/initial commit).
160+ headRef := plumbing .NewSymbolicReference (plumbing .HEAD , plumbing .NewBranchReferenceName (c .branch ))
161+ if err := repo .Storer .SetReference (headRef ); err != nil {
162+ return nil , nil , fmt .Errorf ("set HEAD to %s: %w" , c .branch , err )
163+ }
164+ wt , err := repo .Worktree ()
165+ if err != nil {
166+ return nil , nil , fmt .Errorf ("worktree: %w" , err )
167+ }
168+ return repo , wt , nil
169+ }
170+
137171func (c * Client ) commitAndPush (ctx context.Context , repo * gogit.Repository , wt * gogit.Worktree , msg string ) (string , error ) {
138172 hash , err := wt .Commit (msg , & gogit.CommitOptions {
139173 Author : & object.Signature {
@@ -145,8 +179,12 @@ func (c *Client) commitAndPush(ctx context.Context, repo *gogit.Repository, wt *
145179 if err != nil {
146180 return "" , fmt .Errorf ("commit: %w" , err )
147181 }
182+ // Push the branch explicitly so this also creates the branch on a
183+ // previously-empty remote (where there is no tracking config to default to).
184+ branchRef := plumbing .NewBranchReferenceName (c .branch )
148185 if err := repo .PushContext (ctx , & gogit.PushOptions {
149186 RemoteName : gogit .DefaultRemoteName ,
187+ RefSpecs : []config.RefSpec {config .RefSpec (fmt .Sprintf ("%s:%s" , branchRef , branchRef ))},
150188 Auth : c .auth ,
151189 }); err != nil && ! errors .Is (err , gogit .NoErrAlreadyUpToDate ) {
152190 return "" , fmt .Errorf ("push: %w" , err )
0 commit comments