Skip to content

Commit ae27f9e

Browse files
committed
libgit2: fix checkout logic for CheckoutBranch
Use the target commit, to checkout tree and set the head to the desired branch instead of doing a hard reset to the target commit. Signed-off-by: Sanskar Jaiswal <jaiswalsanskar078@gmail.com>
1 parent c68c62c commit ae27f9e

File tree

2 files changed

+62
-6
lines changed

2 files changed

+62
-6
lines changed

pkg/git/libgit2/checkout.go

+31-6
Original file line numberDiff line numberDiff line change
@@ -161,25 +161,50 @@ func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, opts *g
161161
}
162162
defer upstreamCommit.Free()
163163

164-
// Once the index has been updated with Fetch, and we know the tip commit,
165-
// a hard reset can be used to align the local worktree with the remote branch's.
166-
err = repo.ResetToCommit(upstreamCommit, git2go.ResetHard, &git2go.CheckoutOptions{
164+
// We try to lookup the branch (and create it if it doesn't exist), so that we can
165+
// switch the repo to the specified branch. This is done so that users of this api
166+
// can expect the repo to be at the desired branch, when cloned.
167+
localBranch, err := repo.LookupBranch(c.Branch, git2go.BranchLocal)
168+
if git2go.IsErrorCode(err, git2go.ErrorCodeNotFound) {
169+
localBranch, err = repo.CreateBranch(c.Branch, upstreamCommit, false)
170+
if err != nil {
171+
return nil, fmt.Errorf("unable to create local branch '%s': %w", c.Branch, err)
172+
}
173+
} else if err != nil {
174+
return nil, fmt.Errorf("unable to lookup branch '%s': %w", c.Branch, err)
175+
}
176+
defer localBranch.Free()
177+
178+
tree, err := repo.LookupTree(upstreamCommit.TreeId())
179+
if err != nil {
180+
return nil, fmt.Errorf("unable to lookup tree for branch '%s': %w", c.Branch, err)
181+
}
182+
defer tree.Free()
183+
184+
err = repo.CheckoutTree(tree, &git2go.CheckoutOpts{
185+
// the remote branch should take precedence if it exists at this point in time.
167186
Strategy: git2go.CheckoutForce,
168187
})
169188
if err != nil {
170-
return nil, fmt.Errorf("unable to hard reset to commit for '%s': %w", managed.EffectiveURL(url), gitutil.LibGit2Error(err))
189+
return nil, fmt.Errorf("unable to checkout tree for branch '%s': %w", c.Branch, err)
190+
}
191+
192+
// Set the current head to point to the requested branch.
193+
err = repo.SetHead("refs/heads/" + c.Branch)
194+
if err != nil {
195+
return nil, fmt.Errorf("unable to set HEAD to branch '%s':%w", c.Branch, err)
171196
}
172197

173198
// Use the current worktree's head as reference for the commit to be returned.
174199
head, err := repo.Head()
175200
if err != nil {
176-
return nil, fmt.Errorf("git resolve HEAD error: %w", err)
201+
return nil, fmt.Errorf("unable to resolve HEAD: %w", err)
177202
}
178203
defer head.Free()
179204

180205
cc, err := repo.LookupCommit(head.Target())
181206
if err != nil {
182-
return nil, fmt.Errorf("failed to lookup HEAD commit '%s' for branch '%s': %w", head.Target(), c.Branch, err)
207+
return nil, fmt.Errorf("unable to lookup HEAD commit '%s' for branch '%s': %w", head.Target(), c.Branch, err)
183208
}
184209
defer cc.Free()
185210

pkg/git/libgit2/managed_test.go

+31
Original file line numberDiff line numberDiff line change
@@ -517,13 +517,15 @@ func TestManagedCheckoutBranch_Checkout(t *testing.T) {
517517

518518
repo, err := git2go.OpenRepository(filepath.Join(server.Root(), repoPath))
519519
g.Expect(err).ToNot(HaveOccurred())
520+
defer repo.Free()
520521

521522
branchRef, err := repo.References.Lookup(fmt.Sprintf("refs/heads/%s", git.DefaultBranch))
522523
g.Expect(err).ToNot(HaveOccurred())
523524
defer branchRef.Free()
524525

525526
commit, err := repo.LookupCommit(branchRef.Target())
526527
g.Expect(err).ToNot(HaveOccurred())
528+
defer commit.Free()
527529

528530
authOpts := &git.AuthOptions{
529531
TransportOptionsURL: getTransportOptionsURL(git.HTTP),
@@ -552,6 +554,33 @@ func TestManagedCheckoutBranch_Checkout(t *testing.T) {
552554
g.Expect(err).ToNot(HaveOccurred())
553555
g.Expect(cc.String()).To(Equal(git.DefaultBranch + "/" + commit.Id().String()))
554556
g.Expect(git.IsConcreteCommit(*cc)).To(Equal(true))
557+
558+
// Create a new branch and push it.
559+
err = createBranch(repo, "test", nil)
560+
g.Expect(err).ToNot(HaveOccurred())
561+
transportOptsURL := getTransportOptionsURL(git.HTTP)
562+
managed.AddTransportOptions(transportOptsURL, managed.TransportOptions{
563+
TargetURL: repoURL,
564+
})
565+
defer managed.RemoveTransportOptions(transportOptsURL)
566+
origin, err := repo.Remotes.Create("origin", transportOptsURL)
567+
defer origin.Free()
568+
g.Expect(err).ToNot(HaveOccurred())
569+
err = origin.Push([]string{"refs/heads/test:refs/heads/test"}, &git2go.PushOptions{})
570+
g.Expect(err).ToNot(HaveOccurred())
571+
572+
branch.Branch = "test"
573+
tmpDir2 := t.TempDir()
574+
cc, err = branch.Checkout(ctx, tmpDir2, repoURL, authOpts)
575+
g.Expect(err).ToNot(HaveOccurred())
576+
577+
// Check if the repo HEAD points to the branch.
578+
repo, err = git2go.OpenRepository(tmpDir2)
579+
g.Expect(err).ToNot(HaveOccurred())
580+
head, err := repo.Head()
581+
defer head.Free()
582+
g.Expect(err).ToNot(HaveOccurred())
583+
g.Expect(head.Branch().Name()).To(Equal("test"))
555584
}
556585

557586
func TestManagedCheckoutTag_Checkout(t *testing.T) {
@@ -573,13 +602,15 @@ func TestManagedCheckoutTag_Checkout(t *testing.T) {
573602

574603
repo, err := git2go.OpenRepository(filepath.Join(server.Root(), repoPath))
575604
g.Expect(err).ToNot(HaveOccurred())
605+
defer repo.Free()
576606

577607
branchRef, err := repo.References.Lookup(fmt.Sprintf("refs/heads/%s", git.DefaultBranch))
578608
g.Expect(err).ToNot(HaveOccurred())
579609
defer branchRef.Free()
580610

581611
commit, err := repo.LookupCommit(branchRef.Target())
582612
g.Expect(err).ToNot(HaveOccurred())
613+
defer commit.Free()
583614
_, err = tag(repo, commit.Id(), false, "tag-1", time.Now())
584615

585616
checkoutTag := CheckoutTag{

0 commit comments

Comments
 (0)