@@ -76,6 +76,7 @@ func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, auth *g
7676 if err != nil {
7777 return nil , "" , fmt .Errorf ("git resolve HEAD error: %w" , err )
7878 }
79+ defer head .Free ()
7980 commit , err := repo .LookupCommit (head .Target ())
8081 if err != nil {
8182 return nil , "" , fmt .Errorf ("git commit '%s' not found: %w" , head .Target (), err )
@@ -98,31 +99,12 @@ func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, auth *git.
9899 },
99100 })
100101 if err != nil {
101- return nil , "" , fmt .Errorf ("unable to clone '%s', error: %w" , url , err )
102- }
103- ref , err := repo .References .Dwim (c .tag )
104- if err != nil {
105- return nil , "" , fmt .Errorf ("unable to find tag '%s': %w" , c .tag , err )
106- }
107- err = repo .SetHeadDetached (ref .Target ())
108- if err != nil {
109- return nil , "" , fmt .Errorf ("git checkout error: %w" , err )
110- }
111- head , err := repo .Head ()
112- if err != nil {
113- return nil , "" , fmt .Errorf ("git resolve HEAD error: %w" , err )
102+ return nil , "" , fmt .Errorf ("unable to clone '%s', error: %w" , url , gitutil .LibGit2Error (err ))
114103 }
115- commit , err := repo . LookupCommit ( head . Target () )
104+ commit , err := checkoutDetachedDwim ( repo , c . tag )
116105 if err != nil {
117- return nil , "" , fmt .Errorf ("git commit '%s' not found: %w" , head .Target (), err )
118- }
119- err = repo .CheckoutHead (& git2go.CheckoutOptions {
120- Strategy : git2go .CheckoutForce ,
121- })
122- if err != nil {
123- return nil , "" , fmt .Errorf ("git checkout error: %w" , err )
106+ return nil , "" , err
124107 }
125-
126108 return & Commit {commit }, fmt .Sprintf ("%s/%s" , c .tag , commit .Id ().String ()), nil
127109}
128110
@@ -140,30 +122,19 @@ func (c *CheckoutCommit) Checkout(ctx context.Context, path, url string, auth *g
140122 CertificateCheckCallback : auth .CertCallback ,
141123 },
142124 },
143- CheckoutBranch : c .branch ,
144125 })
145126 if err != nil {
146- return nil , "" , fmt .Errorf ("unable to clone '%s', error: %w" , url , err )
127+ return nil , "" , fmt .Errorf ("unable to clone '%s', error: %w" , url , gitutil . LibGit2Error ( err ) )
147128 }
129+
148130 oid , err := git2go .NewOid (c .commit )
149131 if err != nil {
150- return nil , "" , fmt .Errorf ("git commit '%s' could not be parsed" , c .commit )
151- }
152- commit , err := repo .LookupCommit (oid )
153- if err != nil {
154- return nil , "" , fmt .Errorf ("git commit '%s' not found: %w" , c .commit , err )
155- }
156- tree , err := repo .LookupTree (commit .TreeId ())
157- if err != nil {
158- return nil , "" , fmt .Errorf ("git worktree error: %w" , err )
132+ return nil , "" , fmt .Errorf ("could not create oid for '%s': %w" , c .commit , err )
159133 }
160- err = repo .CheckoutTree (tree , & git2go.CheckoutOptions {
161- Strategy : git2go .CheckoutForce ,
162- })
134+ commit , err := checkoutDetachedHEAD (repo , oid )
163135 if err != nil {
164136 return nil , "" , fmt .Errorf ("git checkout error: %w" , err )
165137 }
166-
167138 return & Commit {commit }, fmt .Sprintf ("%s/%s" , c .branch , commit .Id ().String ()), nil
168139}
169140
@@ -187,7 +158,7 @@ func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, auth *g
187158 },
188159 })
189160 if err != nil {
190- return nil , "" , fmt .Errorf ("unable to clone '%s', error: %w" , url , err )
161+ return nil , "" , fmt .Errorf ("unable to clone '%s', error: %w" , url , gitutil . LibGit2Error ( err ) )
191162 }
192163
193164 tags := make (map [string ]string )
@@ -198,6 +169,7 @@ func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, auth *g
198169 // Due to this, first attempt to resolve it as a simple tag (commit), but fallback to attempting to
199170 // resolve it as an annotated tag in case this results in an error.
200171 if c , err := repo .LookupCommit (id ); err == nil {
172+ defer c .Free ()
201173 // Use the commit metadata as the decisive timestamp.
202174 tagTimestamps [cleanName ] = c .Committer ().When
203175 tags [cleanName ] = name
@@ -207,14 +179,17 @@ func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, auth *g
207179 if err != nil {
208180 return fmt .Errorf ("could not lookup '%s' as simple or annotated tag: %w" , cleanName , err )
209181 }
182+ defer t .Free ()
210183 commit , err := t .Peel (git2go .ObjectCommit )
211184 if err != nil {
212185 return fmt .Errorf ("could not get commit for tag '%s': %w" , t .Name (), err )
213186 }
187+ defer commit .Free ()
214188 c , err := commit .AsCommit ()
215189 if err != nil {
216190 return fmt .Errorf ("could not get commit object for tag '%s': %w" , t .Name (), err )
217191 }
192+ defer c .Free ()
218193 tagTimestamps [t .Name ()] = c .Committer ().When
219194 tags [t .Name ()] = name
220195 return nil
@@ -255,28 +230,62 @@ func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, auth *g
255230 v := matchedVersions [len (matchedVersions )- 1 ]
256231 t := v .Original ()
257232
258- ref , err := repo .References .Dwim (t )
233+ commit , err := checkoutDetachedDwim (repo , t )
234+ return & Commit {commit }, fmt .Sprintf ("%s/%s" , t , commit .Id ().String ()), nil
235+ }
236+
237+ // checkoutDetachedDwim attempts to perform a detached HEAD checkout by first DWIMing the short name
238+ // to get a concrete reference, and then calling checkoutDetachedHEAD.
239+ func checkoutDetachedDwim (repo * git2go.Repository , name string ) (* git2go.Commit , error ) {
240+ ref , err := repo .References .Dwim (name )
259241 if err != nil {
260- return nil , "" , fmt .Errorf ("unable to find tag '%s': %w" , t , err )
242+ return nil , fmt .Errorf ("unable to find '%s': %w" , name , err )
261243 }
262- err = repo .SetHeadDetached (ref .Target ())
244+ defer ref .Free ()
245+ c , err := ref .Peel (git2go .ObjectCommit )
263246 if err != nil {
264- return nil , "" , fmt .Errorf ("git checkout error : %w" , err )
247+ return nil , fmt .Errorf ("could not get commit for ref '%s' : %w" , ref . Name () , err )
265248 }
266- head , err := repo .Head ()
249+ defer c .Free ()
250+ commit , err := c .AsCommit ()
267251 if err != nil {
268- return nil , "" , fmt .Errorf ("git resolve HEAD error : %w" , err )
252+ return nil , fmt .Errorf ("could not get commit object for ref '%s' : %w" , ref . Name () , err )
269253 }
270- commit , err := repo .LookupCommit (head .Target ())
254+ defer commit .Free ()
255+ return checkoutDetachedHEAD (repo , commit .Id ())
256+ }
257+
258+ // checkoutDetachedHEAD attempts to perform a detached HEAD checkout for the given commit.
259+ func checkoutDetachedHEAD (repo * git2go.Repository , oid * git2go.Oid ) (* git2go.Commit , error ) {
260+ commit , err := repo .LookupCommit (oid )
271261 if err != nil {
272- return nil , "" , fmt .Errorf ("git commit '%s' not found: %w" , head . Target () .String (), err )
262+ return nil , fmt .Errorf ("git commit '%s' not found: %w" , oid .String (), err )
273263 }
274- err = repo .CheckoutHead (& git2go.CheckoutOptions {
264+ if err = repo .SetHeadDetached (commit .Id ()); err != nil {
265+ commit .Free ()
266+ return nil , fmt .Errorf ("could not detach HEAD at '%s': %w" , oid .String (), err )
267+ }
268+ if err = repo .CheckoutHead (& git2go.CheckoutOptions {
275269 Strategy : git2go .CheckoutForce ,
276- })
270+ }); err != nil {
271+ commit .Free ()
272+ return nil , fmt .Errorf ("git checkout error: %w" , err )
273+ }
274+ return commit , nil
275+ }
276+
277+ // headCommit returns the current HEAD of the repository, or an error.
278+ func headCommit (repo * git2go.Repository ) (* git2go.Commit , error ) {
279+ head , err := repo .Head ()
277280 if err != nil {
278- return nil , "" , fmt . Errorf ( "git checkout error: %w" , err )
281+ return nil , err
279282 }
283+ defer head .Free ()
280284
281- return & Commit {commit }, fmt .Sprintf ("%s/%s" , t , commit .Id ().String ()), nil
285+ commit , err := repo .LookupCommit (head .Target ())
286+ if err != nil {
287+ return nil , err
288+ }
289+
290+ return commit , nil
282291}
0 commit comments