Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 64 additions & 13 deletions pkg/github/repositories.go
Original file line number Diff line number Diff line change
Expand Up @@ -1574,41 +1574,92 @@ func GetTag(t translations.TranslationHelperFunc) inventory.ServerTool {
}

// First get the tag reference
ref, resp, err := client.Git.GetRef(ctx, owner, repo, "refs/tags/"+tag)
ref, refResp, err := client.Git.GetRef(ctx, owner, repo, "refs/tags/"+tag)
if err != nil {
return ghErrors.NewGitHubAPIErrorResponse(ctx,
"failed to get tag reference",
resp,
refResp,
err,
), nil, nil
}
defer func() { _ = resp.Body.Close() }()
defer func() { _ = refResp.Body.Close() }()

if resp.StatusCode != http.StatusOK {
body, err := io.ReadAll(resp.Body)
if refResp.StatusCode != http.StatusOK {
body, err := io.ReadAll(refResp.Body)
if err != nil {
return nil, nil, fmt.Errorf("failed to read response body: %w", err)
}
return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to get tag reference", resp, body), nil, nil
return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to get tag reference", refResp, body), nil, nil
}

// Handle both annotated and lightweight tags.
// Annotated tags have ref.Object.Type == "tag" and point to a tag object.
// Lightweight tags have ref.Object.Type == "commit" and point directly to a commit.
if ref.Object.Type != nil && *ref.Object.Type == "commit" {
// Lightweight tag — resolve the commit directly
commit, commitResp, err := client.Git.GetCommit(ctx, owner, repo, *ref.Object.SHA)
if err != nil {
return ghErrors.NewGitHubAPIErrorResponse(ctx,
"failed to get commit for lightweight tag",
commitResp,
err,
), nil, nil
}
defer func() { _ = commitResp.Body.Close() }()

if commitResp.StatusCode != http.StatusOK {
body, err := io.ReadAll(commitResp.Body)
if err != nil {
return nil, nil, fmt.Errorf("failed to read response body: %w", err)
}
return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to get commit for lightweight tag", commitResp, body), nil, nil
}

// Return a tag-like structure for consistency
lightweightTag := &github.Tag{
SHA: ref.Object.SHA,
Tag: github.Ptr(tag),
Object: &github.GitObject{
Type: github.Ptr("commit"),
SHA: ref.Object.SHA,
},
}
if commit.Message != nil {
lightweightTag.Message = commit.Message
}
if commit.Author != nil {
lightweightTag.Tagger = &github.CommitAuthor{
Name: commit.Author.Name,
Email: commit.Author.Email,
Date: commit.Author.Date,
}
}

r, err := json.Marshal(lightweightTag)
if err != nil {
return nil, nil, fmt.Errorf("failed to marshal response: %w", err)
}

return utils.NewToolResultText(string(r)), nil, nil
}

// Then get the tag object
tagObj, resp, err := client.Git.GetTag(ctx, owner, repo, *ref.Object.SHA)
// Annotated tag — fetch the tag object
tagObj, tagResp, err := client.Git.GetTag(ctx, owner, repo, *ref.Object.SHA)
if err != nil {
return ghErrors.NewGitHubAPIErrorResponse(ctx,
"failed to get tag object",
resp,
tagResp,
err,
), nil, nil
}
defer func() { _ = resp.Body.Close() }()
defer func() { _ = tagResp.Body.Close() }()

if resp.StatusCode != http.StatusOK {
body, err := io.ReadAll(resp.Body)
if tagResp.StatusCode != http.StatusOK {
body, err := io.ReadAll(tagResp.Body)
if err != nil {
return nil, nil, fmt.Errorf("failed to read response body: %w", err)
}
return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to get tag object", resp, body), nil, nil
return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to get tag object", tagResp, body), nil, nil
}

r, err := json.Marshal(tagObj)
Expand Down
90 changes: 89 additions & 1 deletion pkg/github/repositories_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2840,7 +2840,16 @@ func Test_GetTag(t *testing.T) {
mockTagRef := &github.Reference{
Ref: github.Ptr("refs/tags/v1.0.0"),
Object: &github.GitObject{
SHA: github.Ptr("v1.0.0-tag-sha"),
SHA: github.Ptr("v1.0.0-tag-sha"),
Type: github.Ptr("tag"),
},
}

mockLightweightTagRef := &github.Reference{
Ref: github.Ptr("refs/tags/v2.0.0"),
Object: &github.GitObject{
SHA: github.Ptr("lightweight-commit-sha"),
Type: github.Ptr("commit"),
},
}

Expand All @@ -2854,6 +2863,15 @@ func Test_GetTag(t *testing.T) {
},
}

mockCommit := &github.Commit{
SHA: github.Ptr("lightweight-commit-sha"),
Message: github.Ptr("Initial commit"),
Author: &github.CommitAuthor{
Name: github.Ptr("Test User"),
Email: github.Ptr("test@example.com"),
},
}

tests := []struct {
name string
mockedClient *http.Client
Expand Down Expand Up @@ -2892,6 +2910,48 @@ func Test_GetTag(t *testing.T) {
expectError: false,
expectedTag: mockTagObj,
},
{
name: "successful lightweight tag retrieval",
mockedClient: NewMockedHTTPClient(
WithRequestMatchHandler(
GetReposGitRefByOwnerByRepoByRef,
expectPath(
t,
"/repos/owner/repo/git/ref/tags/v2.0.0",
).andThen(
mockResponse(t, http.StatusOK, mockLightweightTagRef),
),
),
WithRequestMatchHandler(
GetReposGitCommitsByOwnerByRepoByCommitSHA,
expectPath(
t,
"/repos/owner/repo/git/commits/lightweight-commit-sha",
).andThen(
mockResponse(t, http.StatusOK, mockCommit),
),
),
),
requestArgs: map[string]any{
"owner": "owner",
"repo": "repo",
"tag": "v2.0.0",
},
expectError: false,
expectedTag: &github.Tag{
SHA: github.Ptr("lightweight-commit-sha"),
Tag: github.Ptr("v2.0.0"),
Message: github.Ptr("Initial commit"),
Tagger: &github.CommitAuthor{
Name: github.Ptr("Test User"),
Email: github.Ptr("test@example.com"),
},
Object: &github.GitObject{
Type: github.Ptr("commit"),
SHA: github.Ptr("lightweight-commit-sha"),
},
},
},
{
name: "tag reference not found",
mockedClient: NewMockedHTTPClient(
Expand All @@ -2911,6 +2971,29 @@ func Test_GetTag(t *testing.T) {
expectError: true,
expectedErrMsg: "failed to get tag reference",
},
{
name: "lightweight tag commit not found",
mockedClient: NewMockedHTTPClient(
WithRequestMatch(
GetReposGitRefByOwnerByRepoByRef,
mockLightweightTagRef,
),
WithRequestMatchHandler(
GetReposGitCommitsByOwnerByRepoByCommitSHA,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte(`{"message": "Commit does not exist"}`))
}),
),
),
requestArgs: map[string]any{
"owner": "owner",
"repo": "repo",
"tag": "v2.0.0",
},
expectError: true,
expectedErrMsg: "failed to get commit for lightweight tag",
},
{
name: "tag object not found",
mockedClient: NewMockedHTTPClient(
Expand Down Expand Up @@ -2976,6 +3059,11 @@ func Test_GetTag(t *testing.T) {
assert.Equal(t, *tc.expectedTag.Message, *returnedTag.Message)
assert.Equal(t, *tc.expectedTag.Object.Type, *returnedTag.Object.Type)
assert.Equal(t, *tc.expectedTag.Object.SHA, *returnedTag.Object.SHA)
if tc.expectedTag.Tagger != nil {
require.NotNil(t, returnedTag.Tagger, "expected Tagger to be set")
assert.Equal(t, *tc.expectedTag.Tagger.Name, *returnedTag.Tagger.Name)
assert.Equal(t, *tc.expectedTag.Tagger.Email, *returnedTag.Tagger.Email)
}
})
}
}
Expand Down