github api: release, tag, commit
This commit is contained in:
1
.github/codecov.yml
vendored
1
.github/codecov.yml
vendored
@@ -7,3 +7,4 @@ coverage:
|
||||
- "internal/mockable"
|
||||
- "internal/packages"
|
||||
- "internal/typepatch"
|
||||
- "internal/github"
|
||||
|
||||
76
internal/github/commit.go
Normal file
76
internal/github/commit.go
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Author represents a github user or bot.
|
||||
type Author struct {
|
||||
Login string `json:"login"` // github-actions[bot]
|
||||
ID int `json:"id"` // 41898282
|
||||
NodeID string `json:"node_id"` // MDM6Qm90NDE4OTgyODI=
|
||||
AvatarURL string `json:"avatar_url"` // https://avatars.githubusercontent.com/in/15368?v=4
|
||||
URL string `json:"url"` // https://api.github.com/users/github-actions%5Bbot%5D
|
||||
HtmlURL string `json:"html_url"` // https://github.com/apps/github-actions
|
||||
Type string `json:"type"` // Bot
|
||||
SiteAdmin bool `json:"site_admin"` // false
|
||||
}
|
||||
|
||||
// CommitAuthor represents the author of a GitHub commit.
|
||||
type CommitAuthor struct {
|
||||
Name string `json:"name"` // xushiwei
|
||||
Email string `json:"email"` // x@goplus.org
|
||||
Date string `json:"date"` // 2025-04-21T14:13:29Z
|
||||
}
|
||||
|
||||
// CommitSummary represents the summary of a GitHub commit.
|
||||
type CommitSummary struct {
|
||||
Author CommitAuthor `json:"author"`
|
||||
Message string `json:"message"` // Merge pull request #2296 from goplus/main\n\nv1.4.0
|
||||
}
|
||||
|
||||
// CommitDetail represents the details of a GitHub commit.
|
||||
type CommitDetail struct {
|
||||
NodeID string `json:"node_id"` // C_kwDOAtpGOtoAKDE2OGEwODlmOWY5ZTNhNDdhMTliMTRjZDczODQ4N2M2ZTJkMTMxYmE
|
||||
Commit CommitSummary `json:"commit"`
|
||||
Author Author `json:"author"`
|
||||
}
|
||||
|
||||
func commitURL(pkgPath, sha string) string {
|
||||
return "https://api.github.com/repos/" + pkgPath + "/commits/" + sha
|
||||
}
|
||||
|
||||
// GetCommit retrieves the details of a specific commit from a GitHub repository.
|
||||
func GetCommit(pkgPath, shaOrURL string) (ret *CommitDetail, err error) {
|
||||
url := shaOrURL
|
||||
if !strings.HasPrefix(shaOrURL, "https://") {
|
||||
url = commitURL(pkgPath, shaOrURL)
|
||||
}
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
ret = new(CommitDetail)
|
||||
err = json.NewDecoder(resp.Body).Decode(ret)
|
||||
return
|
||||
}
|
||||
75
internal/github/release.go
Normal file
75
internal/github/release.go
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ReleaseAsset represents a GitHub release asset.
|
||||
type ReleaseAsset struct {
|
||||
URL string `json:"url"` // https://api.github.com/repos/flintlib/flint/releases/assets/242245930
|
||||
ID int `json:"id"` // 242245930
|
||||
NodeID string `json:"node_id"` // RA_kwDOAC8YHs4OcGEq
|
||||
Name string `json:"name"` // flint-3.2.2.tar.gz
|
||||
ContentType string `json:"content_type"` // application/x-gtar
|
||||
State string `json:"state"` // uploaded
|
||||
Size int64 `json:"size"` // 123456
|
||||
DownloadCount int `json:"download_count"` // 176
|
||||
UpdatedAt string `json:"updated_at"` // 2025-03-31T08:54:16Z
|
||||
BrowserDownloadURL string `json:"browser_download_url"` // https://github.com/flintlib/flint/releases/download/v3.2.2/flint-3.2.2.tar.gz
|
||||
}
|
||||
|
||||
// Release represents a GitHub release.
|
||||
type Release struct {
|
||||
URL string `json:"url"` // https://api.github.com/repos/flintlib/flint/releases/209285187
|
||||
ID int `json:"id"` // 209285187
|
||||
NodeID string `json:"node_id"` // RE_kwDOAC8YHs4MeXBD
|
||||
TagName string `json:"tag_name"` // v3.2.2
|
||||
TargetCommitish string `json:"target_commitish"` // b8223680e38ad048355a421bf7f617bb6c5d5e12
|
||||
Name string `json:"name"` // FLINT v3.2.2
|
||||
PublishedAt string `json:"published_at"` // 2025-03-31T08:54:16Z
|
||||
Body string `json:"body"` // Release Notes
|
||||
TarballURL string `json:"tarball_url"` // https://api.github.com/repos/flintlib/flint/tarball/v3.2.2
|
||||
ZipballURL string `json:"zipball_url"` // https://api.github.com/repos/flintlib/flint/zipball/v3.2.2
|
||||
Author Author `json:"author"`
|
||||
Assets []*ReleaseAsset `json:"assets"`
|
||||
Prerelease bool `json:"prerelease"`
|
||||
}
|
||||
|
||||
// releaseURL constructs the URL for a GitHub release.
|
||||
func releaseURL(pkgPath, ver string) string {
|
||||
if ver == "" || ver == "latest" {
|
||||
return "https://api.github.com/repos/" + pkgPath + "/releases/latest"
|
||||
}
|
||||
return "https://api.github.com/repos/" + pkgPath + "/releases/tags/" + ver
|
||||
}
|
||||
|
||||
// GetRelease fetches the release information from GitHub.
|
||||
func GetRelease(pkgPath, ver string) (ret *Release, err error) {
|
||||
url := releaseURL(pkgPath, ver)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
ret = new(Release)
|
||||
err = json.NewDecoder(resp.Body).Decode(ret)
|
||||
return
|
||||
}
|
||||
125
internal/github/tag.go
Normal file
125
internal/github/tag.go
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrBreak = errors.New("break")
|
||||
ErrNotFound = errors.New("not found")
|
||||
)
|
||||
|
||||
// Commit represents a commit in a GitHub repository.
|
||||
type Commit struct {
|
||||
SHA string `json:"sha"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// Tag represents a GitHub tag.
|
||||
type Tag struct {
|
||||
Name string `json:"name"`
|
||||
ZipballURL string `json:"zipball_url"`
|
||||
TarballURL string `json:"tarball_url"`
|
||||
Commit Commit `json:"commit"`
|
||||
NodeID string `json:"node_id"`
|
||||
}
|
||||
|
||||
// tagsURL constructs the URL for fetching tags from a GitHub repository.
|
||||
func tagsURL(pkgPath string) string {
|
||||
return "https://api.github.com/repos/" + pkgPath + "/tags"
|
||||
}
|
||||
|
||||
// GetTag retrieves a specific tag from a GitHub repository.
|
||||
func GetTag(pkgPath, ver string) (tag *Tag, err error) {
|
||||
err = ErrNotFound
|
||||
EnumTags(pkgPath, 0, func(tags []*Tag, page, total int) error {
|
||||
for _, t := range tags {
|
||||
if t.Name == ver {
|
||||
tag = t
|
||||
err = nil
|
||||
return ErrBreak
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// EnumTags enumerates the tags of a GitHub repository.
|
||||
func EnumTags(pkgPath string, page int, pager func(tags []*Tag, page, total int) error) (err error) {
|
||||
total := 0
|
||||
ubase := tagsURL(pkgPath)
|
||||
|
||||
loop:
|
||||
u := ubase
|
||||
if page > 0 {
|
||||
vals := url.Values{"page": []string{strconv.Itoa(page + 1)}}
|
||||
u += "?" + vals.Encode()
|
||||
}
|
||||
resp, err := http.Get(u)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var tags []*Tag
|
||||
err = json.NewDecoder(resp.Body).Decode(&tags)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Link: <https://api.github.com/repositories/47859258/tags?page=2>; rel="next",
|
||||
// <https://api.github.com/repositories/47859258/tags?page=5>; rel="last"
|
||||
if total == 0 {
|
||||
const relLast = `rel="last"`
|
||||
total = page + 1
|
||||
link := resp.Header.Get("Link")
|
||||
for _, part := range strings.Split(link, ",") {
|
||||
if strings.HasSuffix(part, relLast) {
|
||||
left := strings.TrimSpace(part[:len(part)-len(relLast)])
|
||||
lastUrl := strings.TrimSuffix(strings.TrimPrefix(left, "<"), ">;")
|
||||
if pos := strings.LastIndexByte(lastUrl, '?'); pos >= 0 {
|
||||
if vals, e := url.ParseQuery(lastUrl[pos+1:]); e == nil {
|
||||
if n, e := strconv.Atoi(vals.Get("page")); e == nil {
|
||||
total = n
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
err = pager(tags, page, total)
|
||||
if err != nil {
|
||||
if err == ErrBreak {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
page++
|
||||
if page < total {
|
||||
goto loop
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user