Skip to content

Commit f8528c0

Browse files
authored
Merge pull request #142 from analytically/feature/nestedtag
fix(in): handle nested annotated tags when resolving commits
2 parents 1088e2c + 91edb96 commit f8528c0

2 files changed

Lines changed: 112 additions & 14 deletions

File tree

github.go

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -317,32 +317,40 @@ func (g *GitHubClient) ResolveTagToCommitSHA(tagName string) (string, error) {
317317
if err != nil {
318318
return "", err
319319
}
320-
321320
res.Body.Close()
322321

323-
// Lightweight tag
324322
if *ref.Object.Type == "commit" {
325323
return *ref.Object.SHA, nil
326324
}
327325

328-
// Fail if we're not pointing to a annotated tag
326+
// Fail if we're not pointing to a tag or commit
329327
if *ref.Object.Type != "tag" {
330-
return "", fmt.Errorf("could not resolve tag %q to commit: returned type is not 'commit' or 'tag'", tagName)
328+
return "", fmt.Errorf("could not resolve tag %q to commit: ref type is %q, expected 'commit' or 'tag'", tagName, *ref.Object.Type)
331329
}
332330

333-
// Resolve tag to commit sha
334-
tag, res, err := g.client.Git.GetTag(context.TODO(), g.owner, g.repository, *ref.Object.SHA)
335-
if err != nil {
336-
return "", err
337-
}
331+
// Follow the chain of annotated tags until we reach a commit
332+
currentSHA := *ref.Object.SHA
333+
maxDepth := 10
338334

339-
res.Body.Close()
340-
341-
if *tag.Object.Type != "commit" {
342-
return "", fmt.Errorf("could not resolve tag %q to commit: returned type is not 'commit'", tagName)
335+
for i := 0; i < maxDepth; i++ {
336+
tag, res, err := g.client.Git.GetTag(context.TODO(), g.owner, g.repository, currentSHA)
337+
if err != nil {
338+
return "", fmt.Errorf("could not get tag object %q: %w", currentSHA, err)
339+
}
340+
res.Body.Close()
341+
342+
switch *tag.Object.Type {
343+
case "commit":
344+
return *tag.Object.SHA, nil
345+
case "tag":
346+
// Another annotated tag, continue following the chain
347+
currentSHA = *tag.Object.SHA
348+
default:
349+
return "", fmt.Errorf("could not resolve tag %q to commit: tag object points to %q, expected 'commit' or 'tag'", tagName, *tag.Object.Type)
350+
}
343351
}
344352

345-
return *tag.Object.SHA, nil
353+
return "", fmt.Errorf("could not resolve tag %q to commit: exceeded maximum tag chain depth of %d", tagName, maxDepth)
346354
}
347355

348356
func oauthClient(ctx context.Context, source Source) (*http.Client, error) {

github_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,96 @@ var _ = Describe("GitHub Client", func() {
597597
})
598598

599599
})
600+
Context("When GitHub returns a chain of annotated tags", func() {
601+
BeforeEach(func() {
602+
// First call: get the ref which points to the first tag
603+
server.AppendHandlers(
604+
ghttp.CombineHandlers(
605+
ghttp.VerifyRequest("GET", "/repos/concourse/concourse/git/ref/tags/v1.0.0-rc90"),
606+
ghttp.RespondWith(200, `{ "ref": "refs/tags/v1.0.0-rc90", "object" : { "type": "tag", "sha": "first-tag-sha"} }`),
607+
),
608+
)
609+
610+
// Second call: get the first tag object, which points to another tag
611+
server.AppendHandlers(
612+
ghttp.CombineHandlers(
613+
ghttp.VerifyRequest("GET", "/repos/concourse/concourse/git/tags/first-tag-sha"),
614+
ghttp.RespondWith(200, `{ "object" : { "type": "tag", "sha": "second-tag-sha"} }`),
615+
),
616+
)
617+
618+
// Third call: get the second tag object, which finally points to a commit
619+
server.AppendHandlers(
620+
ghttp.CombineHandlers(
621+
ghttp.VerifyRequest("GET", "/repos/concourse/concourse/git/tags/second-tag-sha"),
622+
ghttp.RespondWith(200, `{ "object" : { "type": "commit", "sha": "final-commit-sha"} }`),
623+
),
624+
)
625+
})
626+
627+
It("follows the chain and returns the final commit SHA", func() {
628+
commitSHA, err := client.ResolveTagToCommitSHA("v1.0.0-rc90")
629+
630+
Expect(err).ShouldNot(HaveOccurred())
631+
Expect(commitSHA).To(Equal("final-commit-sha"))
632+
})
633+
})
634+
635+
Context("When GitHub returns a very deep chain of annotated tags", func() {
636+
BeforeEach(func() {
637+
// First call: get the ref
638+
server.AppendHandlers(
639+
ghttp.CombineHandlers(
640+
ghttp.VerifyRequest("GET", "/repos/concourse/concourse/git/ref/tags/deeply-nested"),
641+
ghttp.RespondWith(200, `{ "ref": "refs/tags/deeply-nested", "object" : { "type": "tag", "sha": "tag-sha-0"} }`),
642+
),
643+
)
644+
645+
// Add 11 tag-to-tag redirections (exceeding our max depth of 10)
646+
for i := 0; i < 11; i++ {
647+
currentSHA := fmt.Sprintf("tag-sha-%d", i)
648+
nextSHA := fmt.Sprintf("tag-sha-%d", i+1)
649+
server.AppendHandlers(
650+
ghttp.CombineHandlers(
651+
ghttp.VerifyRequest("GET", fmt.Sprintf("/repos/concourse/concourse/git/tags/%s", currentSHA)),
652+
ghttp.RespondWith(200, fmt.Sprintf(`{ "object" : { "type": "tag", "sha": "%s"} }`, nextSHA)),
653+
),
654+
)
655+
}
656+
})
657+
658+
It("returns an error when max depth is exceeded", func() {
659+
_, err := client.ResolveTagToCommitSHA("deeply-nested")
660+
661+
Expect(err).Should(HaveOccurred())
662+
Expect(err.Error()).To(ContainSubstring("exceeded maximum tag chain depth"))
663+
})
664+
})
665+
666+
Context("When a tag in the chain points to an unexpected object type", func() {
667+
BeforeEach(func() {
668+
server.AppendHandlers(
669+
ghttp.CombineHandlers(
670+
ghttp.VerifyRequest("GET", "/repos/concourse/concourse/git/ref/tags/bad-tag"),
671+
ghttp.RespondWith(200, `{ "ref": "refs/tags/bad-tag", "object" : { "type": "tag", "sha": "tag-sha"} }`),
672+
),
673+
)
674+
675+
server.AppendHandlers(
676+
ghttp.CombineHandlers(
677+
ghttp.VerifyRequest("GET", "/repos/concourse/concourse/git/tags/tag-sha"),
678+
ghttp.RespondWith(200, `{ "object" : { "type": "tree", "sha": "tree-sha"} }`),
679+
),
680+
)
681+
})
682+
683+
It("returns an error for unexpected object types", func() {
684+
_, err := client.ResolveTagToCommitSHA("bad-tag")
685+
686+
Expect(err).Should(HaveOccurred())
687+
Expect(err.Error()).To(ContainSubstring("expected 'commit' or 'tag'"))
688+
})
689+
})
600690
})
601691

602692
Describe("DownloadReleaseAsset", func() {

0 commit comments

Comments
 (0)