package api
import (
"errors"
"net/url"
"sort"
"strconv"
"strings"
)
type Release struct {
ID interface{} `json:"id"`
TagName string `json:"tag_name"`
TargetCommitish string `json:"target_commitish"`
Name string `json:"name"`
Body string `json:"body"`
Draft bool `json:"draft"`
Prerelease bool `json:"prerelease"`
HTMLURL string `json:"html_url"`
AssetsURL string `json:"assets_url"`
UploadURL string `json:"upload_url"`
CreatedAt FlexibleTime `json:"created_at"`
PublishedAt *FlexibleTime `json:"published_at"`
Author *User `json:"author"`
Assets []ReleaseAsset `json:"assets"`
}
type ReleaseAsset struct {
ID int `json:"id"`
Name string `json:"name"`
Label string `json:"label"`
State string `json:"state"`
ContentType string `json:"content_type"`
Size int `json:"size"`
Downloads int `json:"download_count"`
URL string `json:"url"`
BrowserDownloadURL string `json:"browser_download_url"`
CreatedAt FlexibleTime `json:"created_at"`
UpdatedAt FlexibleTime `json:"updated_at"`
}
type AssetUploadURL struct {
URL string `json:"url"`
Headers map[string]string `json:"headers"`
}
type ReleaseListOptions struct {
PerPage int `url:"per_page,omitempty"`
Page int `url:"page,omitempty"`
Direction string `url:"direction,omitempty"`
}
type CreateReleaseOptions struct {
TagName string `json:"tag_name"`
Name string `json:"name,omitempty"`
Body string `json:"body,omitempty"`
Draft bool `json:"draft,omitempty"`
Prerelease bool `json:"prerelease,omitempty"`
ReleaseStatus string `json:"release_status,omitempty"`
TargetCommitish string `json:"target_commitish,omitempty"`
}
type UpdateReleaseOptions struct {
TagName string `json:"tag_name,omitempty"`
TargetCommitish string `json:"target_commitish,omitempty"`
Name string `json:"name,omitempty"`
Body string `json:"body,omitempty"`
Draft *bool `json:"draft,omitempty"`
Prerelease *bool `json:"prerelease,omitempty"`
}
type GitCodeUpdateReleaseOptions struct {
Name string `json:"name"`
Body string `json:"body"`
ReleaseStatus string `json:"release_status,omitempty"`
}
func ListReleases(client *Client, owner, repo string, opts *ReleaseListOptions) ([]Release, error) {
path := buildPath(escapedRepoPath(owner, repo)+"/releases", opts)
var releases []Release
err := client.Get(path, &releases)
if err != nil {
return nil, err
}
sort.Slice(releases, func(i, j int) bool {
return releases[i].CreatedAt.Time.After(releases[j].CreatedAt.Time)
})
return releases, nil
}
func GetRelease(client *Client, owner, repo, tag string) (*Release, error) {
escapedTag := url.PathEscape(tag)
var release Release
err := client.Get(escapedRepoPath(owner, repo)+"/releases/tags/"+escapedTag, &release)
if err != nil {
return nil, err
}
return &release, nil
}
func GetReleaseByID(client *Client, owner, repo string, id int64) (*Release, error) {
var release Release
err := client.Get(escapedRepoPath(owner, repo)+"/releases/"+strconv.FormatInt(id, 10), &release)
if err != nil {
return nil, err
}
return &release, nil
}
func GetLatestRelease(client *Client, owner, repo string) (*Release, error) {
var release Release
err := client.Get(escapedRepoPath(owner, repo)+"/releases/latest", &release)
if err != nil {
return nil, err
}
return &release, nil
}
func CreateRelease(client *Client, owner, repo string, opts *CreateReleaseOptions) (*Release, error) {
var release Release
err := client.Post(escapedRepoPath(owner, repo)+"/releases", opts, &release)
if err != nil {
return nil, err
}
return &release, nil
}
func UpdateRelease(client *Client, owner, repo string, id int64, opts *UpdateReleaseOptions) (*Release, error) {
var release Release
err := client.Patch(escapedRepoPath(owner, repo)+"/releases/"+strconv.FormatInt(id, 10), opts, &release)
if err != nil {
return nil, err
}
return &release, nil
}
func UpdateReleaseByTag(client *Client, owner, repo, tag string, opts *UpdateReleaseOptions) (*Release, error) {
release, err := GetRelease(client, owner, repo, tag)
if err != nil {
return nil, err
}
id, err := release.GetID()
if err != nil {
return nil, err
}
return UpdateRelease(client, owner, repo, id, opts)
}
func UpdateReleaseByTagDirect(client *Client, owner, repo, tag string, opts *GitCodeUpdateReleaseOptions) (*Release, error) {
escapedTag := url.PathEscape(tag)
path := escapedRepoPath(owner, repo) + "/releases/" + escapedTag
var release Release
err := client.Patch(path, opts, &release)
if err != nil {
return nil, err
}
return &release, nil
}
func DeleteRelease(client *Client, owner, repo string, id int64) error {
return client.Delete(escapedRepoPath(owner, repo) + "/releases/" + strconv.FormatInt(id, 10))
}
func DeleteReleaseByTag(client *Client, owner, repo, tag string) error {
err := DeleteReleaseByTagDirect(client, owner, repo, tag)
if err == nil {
return nil
}
if !isNotFoundError(err) {
return err
}
release, err := GetRelease(client, owner, repo, tag)
if err != nil {
return err
}
id, err := release.GetID()
if err != nil {
return err
}
return DeleteRelease(client, owner, repo, id)
}
func DeleteReleaseByTagDirect(client *Client, owner, repo, tag string) error {
escapedTag := url.PathEscape(tag)
return client.Delete(escapedRepoPath(owner, repo) + "/releases/" + escapedTag)
}
func DeleteReleaseByTagKnown(client *Client, owner, repo, tag string, release *Release) error {
err := DeleteReleaseByTagDirect(client, owner, repo, tag)
if err == nil {
return nil
}
if !isNotFoundError(err) {
return err
}
id, idErr := release.GetID()
if idErr != nil {
return idErr
}
return DeleteRelease(client, owner, repo, id)
}
func isNotFoundError(err error) bool {
if apiErr, ok := err.(*APIError); ok {
return apiErr.StatusCode == 404
}
if err != nil {
return strings.Contains(err.Error(), "404")
}
return false
}
func (r *Release) GetID() (int64, error) {
if r.ID == nil {
return 0, ErrNoReleaseID
}
switch v := r.ID.(type) {
case float64:
return int64(v), nil
case int64:
return v, nil
case int:
return int64(v), nil
case string:
var id int64
for _, c := range v {
if c >= '0' && c <= '9' {
id = id*10 + int64(c-'0')
} else {
return 0, ErrNoReleaseID
}
}
return id, nil
default:
return 0, ErrNoReleaseID
}
}
func ListReleaseAssets(client *Client, owner, repo string, releaseID int64) ([]ReleaseAsset, error) {
var assets []ReleaseAsset
err := client.Get(escapedRepoPath(owner, repo)+"/releases/"+strconv.FormatInt(releaseID, 10)+"/assets", &assets)
if err != nil {
return nil, err
}
return assets, nil
}
func GetReleaseAsset(client *Client, owner, repo string, assetID int64) (*ReleaseAsset, error) {
var asset ReleaseAsset
err := client.Get(escapedRepoPath(owner, repo)+"/releases/assets/"+strconv.FormatInt(assetID, 10), &asset)
if err != nil {
return nil, err
}
return &asset, nil
}
func DeleteReleaseAsset(client *Client, owner, repo string, assetID int64) error {
return client.Delete(escapedRepoPath(owner, repo) + "/releases/assets/" + strconv.FormatInt(assetID, 10))
}
func DeleteReleaseAssetByTag(client *Client, owner, repo, tag, attachFileID string) error {
escapedTag := url.PathEscape(tag)
return client.Delete(escapedRepoPath(owner, repo) + "/releases/" + escapedTag + "/attach_files/" + url.PathEscape(attachFileID))
}
func GetReleaseUploadURL(client *Client, owner, repo, tag, filename string) (*AssetUploadURL, error) {
escapedTag := url.PathEscape(tag)
path := escapedRepoPath(owner, repo) + "/releases/" + escapedTag + "/upload_url" + newQueryBuilder().Set("file_name", filename).String()
var result AssetUploadURL
err := client.Get(path, &result)
if err != nil {
return nil, err
}
return &result, nil
}
func UploadReleaseAsset(client *Client, owner, repo string, releaseID int64, filename string, content []byte, contentType string) (*ReleaseAsset, error) {
if contentType == "" {
contentType = "application/octet-stream"
}
return client.UploadAsset(escapedRepoPath(owner, repo)+"/releases/"+strconv.FormatInt(releaseID, 10)+"/assets", filename, content, contentType)
}
func UploadReleaseAssetByTag(client *Client, owner, repo, tag, filename string, content []byte, contentType string) error {
if contentType == "" {
contentType = "application/octet-stream"
}
uploadInfo, err := GetReleaseUploadURL(client, owner, repo, tag, filename)
if err != nil {
return fmtError(err, "failed to get upload URL")
}
if uploadInfo.URL == "" {
return fmtError(nil, "upload URL is empty")
}
return client.UploadToURL(uploadInfo.URL, filename, content, contentType, uploadInfo.Headers)
}
var ErrNoReleaseID = errors.New("release id was not returned by GitCode API")
func fmtError(cause error, msg string) error {
return &releaseError{msg: msg, cause: cause}
}
type releaseError struct {
msg string
cause error
}
func (e *releaseError) Error() string {
if e.cause != nil {
return e.msg + ": " + e.cause.Error()
}
return e.msg
}
func (e *releaseError) Unwrap() error {
return e.cause
}
func buildPath(base string, opts *ReleaseListOptions) string {
if opts == nil {
return base
}
return base + newQueryBuilder().
SetInt("per_page", opts.PerPage).
SetInt("page", opts.Page).
Set("direction", strings.TrimSpace(opts.Direction)).
String()
}