1// Copyright 2013 The go-github AUTHORS. All rights reserved. 2// 3// Use of this source code is governed by a BSD-style 4// license that can be found in the LICENSE file. 5 6package github 7 8import ( 9 "context" 10 "errors" 11 "fmt" 12 "io" 13 "mime" 14 "net/http" 15 "os" 16 "path/filepath" 17 "strings" 18) 19 20// RepositoryRelease represents a GitHub release in a repository. 21type RepositoryRelease struct { 22 ID *int64 `json:"id,omitempty"` 23 TagName *string `json:"tag_name,omitempty"` 24 TargetCommitish *string `json:"target_commitish,omitempty"` 25 Name *string `json:"name,omitempty"` 26 Body *string `json:"body,omitempty"` 27 Draft *bool `json:"draft,omitempty"` 28 Prerelease *bool `json:"prerelease,omitempty"` 29 CreatedAt *Timestamp `json:"created_at,omitempty"` 30 PublishedAt *Timestamp `json:"published_at,omitempty"` 31 URL *string `json:"url,omitempty"` 32 HTMLURL *string `json:"html_url,omitempty"` 33 AssetsURL *string `json:"assets_url,omitempty"` 34 Assets []ReleaseAsset `json:"assets,omitempty"` 35 UploadURL *string `json:"upload_url,omitempty"` 36 ZipballURL *string `json:"zipball_url,omitempty"` 37 TarballURL *string `json:"tarball_url,omitempty"` 38 Author *User `json:"author,omitempty"` 39 NodeID *string `json:"node_id,omitempty"` 40} 41 42func (r RepositoryRelease) String() string { 43 return Stringify(r) 44} 45 46// ReleaseAsset represents a GitHub release asset in a repository. 47type ReleaseAsset struct { 48 ID *int64 `json:"id,omitempty"` 49 URL *string `json:"url,omitempty"` 50 Name *string `json:"name,omitempty"` 51 Label *string `json:"label,omitempty"` 52 State *string `json:"state,omitempty"` 53 ContentType *string `json:"content_type,omitempty"` 54 Size *int `json:"size,omitempty"` 55 DownloadCount *int `json:"download_count,omitempty"` 56 CreatedAt *Timestamp `json:"created_at,omitempty"` 57 UpdatedAt *Timestamp `json:"updated_at,omitempty"` 58 BrowserDownloadURL *string `json:"browser_download_url,omitempty"` 59 Uploader *User `json:"uploader,omitempty"` 60 NodeID *string `json:"node_id,omitempty"` 61} 62 63func (r ReleaseAsset) String() string { 64 return Stringify(r) 65} 66 67// ListReleases lists the releases for a repository. 68// 69// GitHub API docs: https://developer.github.com/v3/repos/releases/#list-releases-for-a-repository 70func (s *RepositoriesService) ListReleases(ctx context.Context, owner, repo string, opt *ListOptions) ([]*RepositoryRelease, *Response, error) { 71 u := fmt.Sprintf("repos/%s/%s/releases", owner, repo) 72 u, err := addOptions(u, opt) 73 if err != nil { 74 return nil, nil, err 75 } 76 77 req, err := s.client.NewRequest("GET", u, nil) 78 if err != nil { 79 return nil, nil, err 80 } 81 82 var releases []*RepositoryRelease 83 resp, err := s.client.Do(ctx, req, &releases) 84 if err != nil { 85 return nil, resp, err 86 } 87 return releases, resp, nil 88} 89 90// GetRelease fetches a single release. 91// 92// GitHub API docs: https://developer.github.com/v3/repos/releases/#get-a-single-release 93func (s *RepositoriesService) GetRelease(ctx context.Context, owner, repo string, id int64) (*RepositoryRelease, *Response, error) { 94 u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id) 95 return s.getSingleRelease(ctx, u) 96} 97 98// GetLatestRelease fetches the latest published release for the repository. 99// 100// GitHub API docs: https://developer.github.com/v3/repos/releases/#get-the-latest-release 101func (s *RepositoriesService) GetLatestRelease(ctx context.Context, owner, repo string) (*RepositoryRelease, *Response, error) { 102 u := fmt.Sprintf("repos/%s/%s/releases/latest", owner, repo) 103 return s.getSingleRelease(ctx, u) 104} 105 106// GetReleaseByTag fetches a release with the specified tag. 107// 108// GitHub API docs: https://developer.github.com/v3/repos/releases/#get-a-release-by-tag-name 109func (s *RepositoriesService) GetReleaseByTag(ctx context.Context, owner, repo, tag string) (*RepositoryRelease, *Response, error) { 110 u := fmt.Sprintf("repos/%s/%s/releases/tags/%s", owner, repo, tag) 111 return s.getSingleRelease(ctx, u) 112} 113 114func (s *RepositoriesService) getSingleRelease(ctx context.Context, url string) (*RepositoryRelease, *Response, error) { 115 req, err := s.client.NewRequest("GET", url, nil) 116 if err != nil { 117 return nil, nil, err 118 } 119 120 release := new(RepositoryRelease) 121 resp, err := s.client.Do(ctx, req, release) 122 if err != nil { 123 return nil, resp, err 124 } 125 return release, resp, nil 126} 127 128// CreateRelease adds a new release for a repository. 129// 130// GitHub API docs: https://developer.github.com/v3/repos/releases/#create-a-release 131func (s *RepositoriesService) CreateRelease(ctx context.Context, owner, repo string, release *RepositoryRelease) (*RepositoryRelease, *Response, error) { 132 u := fmt.Sprintf("repos/%s/%s/releases", owner, repo) 133 134 req, err := s.client.NewRequest("POST", u, release) 135 if err != nil { 136 return nil, nil, err 137 } 138 139 r := new(RepositoryRelease) 140 resp, err := s.client.Do(ctx, req, r) 141 if err != nil { 142 return nil, resp, err 143 } 144 return r, resp, nil 145} 146 147// EditRelease edits a repository release. 148// 149// GitHub API docs: https://developer.github.com/v3/repos/releases/#edit-a-release 150func (s *RepositoriesService) EditRelease(ctx context.Context, owner, repo string, id int64, release *RepositoryRelease) (*RepositoryRelease, *Response, error) { 151 u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id) 152 153 req, err := s.client.NewRequest("PATCH", u, release) 154 if err != nil { 155 return nil, nil, err 156 } 157 158 r := new(RepositoryRelease) 159 resp, err := s.client.Do(ctx, req, r) 160 if err != nil { 161 return nil, resp, err 162 } 163 return r, resp, nil 164} 165 166// DeleteRelease delete a single release from a repository. 167// 168// GitHub API docs: https://developer.github.com/v3/repos/releases/#delete-a-release 169func (s *RepositoriesService) DeleteRelease(ctx context.Context, owner, repo string, id int64) (*Response, error) { 170 u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id) 171 172 req, err := s.client.NewRequest("DELETE", u, nil) 173 if err != nil { 174 return nil, err 175 } 176 return s.client.Do(ctx, req, nil) 177} 178 179// ListReleaseAssets lists the release's assets. 180// 181// GitHub API docs: https://developer.github.com/v3/repos/releases/#list-assets-for-a-release 182func (s *RepositoriesService) ListReleaseAssets(ctx context.Context, owner, repo string, id int64, opt *ListOptions) ([]*ReleaseAsset, *Response, error) { 183 u := fmt.Sprintf("repos/%s/%s/releases/%d/assets", owner, repo, id) 184 u, err := addOptions(u, opt) 185 if err != nil { 186 return nil, nil, err 187 } 188 189 req, err := s.client.NewRequest("GET", u, nil) 190 if err != nil { 191 return nil, nil, err 192 } 193 194 var assets []*ReleaseAsset 195 resp, err := s.client.Do(ctx, req, &assets) 196 if err != nil { 197 return nil, resp, err 198 } 199 return assets, resp, nil 200} 201 202// GetReleaseAsset fetches a single release asset. 203// 204// GitHub API docs: https://developer.github.com/v3/repos/releases/#get-a-single-release-asset 205func (s *RepositoriesService) GetReleaseAsset(ctx context.Context, owner, repo string, id int64) (*ReleaseAsset, *Response, error) { 206 u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) 207 208 req, err := s.client.NewRequest("GET", u, nil) 209 if err != nil { 210 return nil, nil, err 211 } 212 213 asset := new(ReleaseAsset) 214 resp, err := s.client.Do(ctx, req, asset) 215 if err != nil { 216 return nil, resp, err 217 } 218 return asset, resp, nil 219} 220 221// DownloadReleaseAsset downloads a release asset or returns a redirect URL. 222// 223// DownloadReleaseAsset returns an io.ReadCloser that reads the contents of the 224// specified release asset. It is the caller's responsibility to close the ReadCloser. 225// If a redirect is returned, the redirect URL will be returned as a string instead 226// of the io.ReadCloser. Exactly one of rc and redirectURL will be zero. 227// 228// GitHub API docs: https://developer.github.com/v3/repos/releases/#get-a-single-release-asset 229func (s *RepositoriesService) DownloadReleaseAsset(ctx context.Context, owner, repo string, id int64) (rc io.ReadCloser, redirectURL string, err error) { 230 u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) 231 232 req, err := s.client.NewRequest("GET", u, nil) 233 if err != nil { 234 return nil, "", err 235 } 236 req.Header.Set("Accept", defaultMediaType) 237 238 s.client.clientMu.Lock() 239 defer s.client.clientMu.Unlock() 240 241 var loc string 242 saveRedirect := s.client.client.CheckRedirect 243 s.client.client.CheckRedirect = func(req *http.Request, via []*http.Request) error { 244 loc = req.URL.String() 245 return errors.New("disable redirect") 246 } 247 defer func() { s.client.client.CheckRedirect = saveRedirect }() 248 249 req = withContext(ctx, req) 250 resp, err := s.client.client.Do(req) 251 if err != nil { 252 if !strings.Contains(err.Error(), "disable redirect") { 253 return nil, "", err 254 } 255 return nil, loc, nil // Intentionally return no error with valid redirect URL. 256 } 257 258 if err := CheckResponse(resp); err != nil { 259 resp.Body.Close() 260 return nil, "", err 261 } 262 263 return resp.Body, "", nil 264} 265 266// EditReleaseAsset edits a repository release asset. 267// 268// GitHub API docs: https://developer.github.com/v3/repos/releases/#edit-a-release-asset 269func (s *RepositoriesService) EditReleaseAsset(ctx context.Context, owner, repo string, id int64, release *ReleaseAsset) (*ReleaseAsset, *Response, error) { 270 u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) 271 272 req, err := s.client.NewRequest("PATCH", u, release) 273 if err != nil { 274 return nil, nil, err 275 } 276 277 asset := new(ReleaseAsset) 278 resp, err := s.client.Do(ctx, req, asset) 279 if err != nil { 280 return nil, resp, err 281 } 282 return asset, resp, nil 283} 284 285// DeleteReleaseAsset delete a single release asset from a repository. 286// 287// GitHub API docs: https://developer.github.com/v3/repos/releases/#delete-a-release-asset 288func (s *RepositoriesService) DeleteReleaseAsset(ctx context.Context, owner, repo string, id int64) (*Response, error) { 289 u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) 290 291 req, err := s.client.NewRequest("DELETE", u, nil) 292 if err != nil { 293 return nil, err 294 } 295 return s.client.Do(ctx, req, nil) 296} 297 298// UploadReleaseAsset creates an asset by uploading a file into a release repository. 299// To upload assets that cannot be represented by an os.File, call NewUploadRequest directly. 300// 301// GitHub API docs: https://developer.github.com/v3/repos/releases/#upload-a-release-asset 302func (s *RepositoriesService) UploadReleaseAsset(ctx context.Context, owner, repo string, id int64, opt *UploadOptions, file *os.File) (*ReleaseAsset, *Response, error) { 303 u := fmt.Sprintf("repos/%s/%s/releases/%d/assets", owner, repo, id) 304 u, err := addOptions(u, opt) 305 if err != nil { 306 return nil, nil, err 307 } 308 309 stat, err := file.Stat() 310 if err != nil { 311 return nil, nil, err 312 } 313 if stat.IsDir() { 314 return nil, nil, errors.New("the asset to upload can't be a directory") 315 } 316 317 mediaType := mime.TypeByExtension(filepath.Ext(file.Name())) 318 req, err := s.client.NewUploadRequest(u, file, stat.Size(), mediaType) 319 if err != nil { 320 return nil, nil, err 321 } 322 323 asset := new(ReleaseAsset) 324 resp, err := s.client.Do(ctx, req, asset) 325 if err != nil { 326 return nil, resp, err 327 } 328 return asset, resp, nil 329} 330