Skip to content

Commit d9473d0

Browse files
committed
libgit2: add remaining checkout strategy tests
This commit is a follow up on 4dc3185 and adds tests for the remaining checkout strategies, while consolidating some of the logic. The consolidated logic ensures that (SemVer) tag and commit checkouts happen using the same "checkout detached HEAD" logic. The branch checkout is left unmodified, and simply checks out at the current HEAD of the given branch. Signed-off-by: Hidde Beydals <[email protected]>
1 parent 4dc3185 commit d9473d0

File tree

2 files changed

+290
-101
lines changed

2 files changed

+290
-101
lines changed

pkg/git/libgit2/checkout.go

Lines changed: 54 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -98,31 +98,12 @@ func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, auth *git.
9898
},
9999
})
100100
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)
101+
return nil, "", fmt.Errorf("unable to clone '%s', error: %w", url, gitutil.LibGit2Error(err))
114102
}
115-
commit, err := repo.LookupCommit(head.Target())
103+
commit, err := checkoutDetachedDwim(repo, c.tag)
116104
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)
105+
return nil, "", err
124106
}
125-
126107
return &Commit{commit}, fmt.Sprintf("%s/%s", c.tag, commit.Id().String()), nil
127108
}
128109

@@ -140,30 +121,19 @@ func (c *CheckoutCommit) Checkout(ctx context.Context, path, url string, auth *g
140121
CertificateCheckCallback: auth.CertCallback,
141122
},
142123
},
143-
CheckoutBranch: c.branch,
144124
})
145125
if err != nil {
146-
return nil, "", fmt.Errorf("unable to clone '%s', error: %w", url, err)
126+
return nil, "", fmt.Errorf("unable to clone '%s', error: %w", url, gitutil.LibGit2Error(err))
147127
}
128+
148129
oid, err := git2go.NewOid(c.commit)
149130
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)
131+
return nil, "", fmt.Errorf("could not create oid for '%s': %w", c.commit, err)
159132
}
160-
err = repo.CheckoutTree(tree, &git2go.CheckoutOptions{
161-
Strategy: git2go.CheckoutForce,
162-
})
133+
commit, err := checkoutDetachedHEAD(repo, oid)
163134
if err != nil {
164135
return nil, "", fmt.Errorf("git checkout error: %w", err)
165136
}
166-
167137
return &Commit{commit}, fmt.Sprintf("%s/%s", c.branch, commit.Id().String()), nil
168138
}
169139

@@ -187,7 +157,7 @@ func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, auth *g
187157
},
188158
})
189159
if err != nil {
190-
return nil, "", fmt.Errorf("unable to clone '%s', error: %w", url, err)
160+
return nil, "", fmt.Errorf("unable to clone '%s', error: %w", url, gitutil.LibGit2Error(err))
191161
}
192162

193163
tags := make(map[string]string)
@@ -255,28 +225,62 @@ func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, auth *g
255225
v := matchedVersions[len(matchedVersions)-1]
256226
t := v.Original()
257227

258-
ref, err := repo.References.Dwim(t)
228+
commit, err := checkoutDetachedDwim(repo, t)
229+
return &Commit{commit}, fmt.Sprintf("%s/%s", t, commit.Id().String()), nil
230+
}
231+
232+
// checkoutDetachedDwim attempts to perform a detached HEAD checkout by first DWIMing the short name
233+
// to get a concrete reference, and then calling checkoutDetachedHEAD.
234+
func checkoutDetachedDwim(repo *git2go.Repository, name string) (*git2go.Commit, error) {
235+
ref, err := repo.References.Dwim(name)
259236
if err != nil {
260-
return nil, "", fmt.Errorf("unable to find tag '%s': %w", t, err)
237+
return nil, fmt.Errorf("unable to find '%s': %w", name, err)
261238
}
262-
err = repo.SetHeadDetached(ref.Target())
239+
defer ref.Free()
240+
c, err := ref.Peel(git2go.ObjectCommit)
263241
if err != nil {
264-
return nil, "", fmt.Errorf("git checkout error: %w", err)
242+
return nil, fmt.Errorf("could not get commit for ref '%s': %w", ref.Name(), err)
265243
}
266-
head, err := repo.Head()
244+
defer c.Free()
245+
commit, err := c.AsCommit()
267246
if err != nil {
268-
return nil, "", fmt.Errorf("git resolve HEAD error: %w", err)
247+
return nil, fmt.Errorf("could not get commit object for ref '%s': %w", ref.Name(), err)
269248
}
270-
commit, err := repo.LookupCommit(head.Target())
249+
defer commit.Free()
250+
return checkoutDetachedHEAD(repo, commit.Id())
251+
}
252+
253+
// checkoutDetachedHEAD attempts to perform a detached HEAD checkout for the given commit.
254+
func checkoutDetachedHEAD(repo *git2go.Repository, oid *git2go.Oid) (*git2go.Commit, error) {
255+
commit, err := repo.LookupCommit(oid)
271256
if err != nil {
272-
return nil, "", fmt.Errorf("git commit '%s' not found: %w", head.Target().String(), err)
257+
return nil, fmt.Errorf("git commit '%s' not found: %w", oid.String(), err)
273258
}
274-
err = repo.CheckoutHead(&git2go.CheckoutOptions{
259+
if err = repo.SetHeadDetached(commit.Id()); err != nil {
260+
commit.Free()
261+
return nil, fmt.Errorf("could not detach HEAD at '%s': %w", oid.String(), err)
262+
}
263+
if err = repo.CheckoutHead(&git2go.CheckoutOptions{
275264
Strategy: git2go.CheckoutForce,
276-
})
265+
}); err != nil {
266+
commit.Free()
267+
return nil, fmt.Errorf("git checkout error: %w", err)
268+
}
269+
return commit, nil
270+
}
271+
272+
// headCommit returns the current HEAD of the repository, or an error.
273+
func headCommit(repo *git2go.Repository) (*git2go.Commit, error) {
274+
head, err := repo.Head()
277275
if err != nil {
278-
return nil, "", fmt.Errorf("git checkout error: %w", err)
276+
return nil, err
279277
}
278+
defer head.Free()
280279

281-
return &Commit{commit}, fmt.Sprintf("%s/%s", t, commit.Id().String()), nil
280+
commit, err := repo.LookupCommit(head.Target())
281+
if err != nil {
282+
return nil, err
283+
}
284+
285+
return commit, nil
282286
}

0 commit comments

Comments
 (0)