Merge pull request #1078 from xushiwei/q
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/mockable"
|
||||||
- "internal/packages"
|
- "internal/packages"
|
||||||
- "internal/typepatch"
|
- "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