github.com/google/go-github/v42@v42.0.0/github/repos_releases.go (about) 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 6 package github 7 8 import ( 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. 21 type RepositoryRelease struct { 22 TagName *string `json:"tag_name,omitempty"` 23 TargetCommitish *string `json:"target_commitish,omitempty"` 24 Name *string `json:"name,omitempty"` 25 Body *string `json:"body,omitempty"` 26 Draft *bool `json:"draft,omitempty"` 27 Prerelease *bool `json:"prerelease,omitempty"` 28 DiscussionCategoryName *string `json:"discussion_category_name,omitempty"` 29 GenerateReleaseNotes *bool `json:"generate_release_notes,omitempty"` 30 31 // The following fields are not used in CreateRelease or EditRelease: 32 ID *int64 `json:"id,omitempty"` 33 CreatedAt *Timestamp `json:"created_at,omitempty"` 34 PublishedAt *Timestamp `json:"published_at,omitempty"` 35 URL *string `json:"url,omitempty"` 36 HTMLURL *string `json:"html_url,omitempty"` 37 AssetsURL *string `json:"assets_url,omitempty"` 38 Assets []*ReleaseAsset `json:"assets,omitempty"` 39 UploadURL *string `json:"upload_url,omitempty"` 40 ZipballURL *string `json:"zipball_url,omitempty"` 41 TarballURL *string `json:"tarball_url,omitempty"` 42 Author *User `json:"author,omitempty"` 43 NodeID *string `json:"node_id,omitempty"` 44 } 45 46 func (r RepositoryRelease) String() string { 47 return Stringify(r) 48 } 49 50 // RepositoryReleaseNotes represents a GitHub-generated release notes. 51 type RepositoryReleaseNotes struct { 52 Name string `json:"name"` 53 Body string `json:"body"` 54 } 55 56 // GenerateNotesOptions represents the options to generate release notes. 57 type GenerateNotesOptions struct { 58 TagName string `json:"tag_name"` 59 PreviousTagName *string `json:"previous_tag_name,omitempty"` 60 TargetCommitish *string `json:"target_commitish,omitempty"` 61 } 62 63 // ReleaseAsset represents a GitHub release asset in a repository. 64 type ReleaseAsset struct { 65 ID *int64 `json:"id,omitempty"` 66 URL *string `json:"url,omitempty"` 67 Name *string `json:"name,omitempty"` 68 Label *string `json:"label,omitempty"` 69 State *string `json:"state,omitempty"` 70 ContentType *string `json:"content_type,omitempty"` 71 Size *int `json:"size,omitempty"` 72 DownloadCount *int `json:"download_count,omitempty"` 73 CreatedAt *Timestamp `json:"created_at,omitempty"` 74 UpdatedAt *Timestamp `json:"updated_at,omitempty"` 75 BrowserDownloadURL *string `json:"browser_download_url,omitempty"` 76 Uploader *User `json:"uploader,omitempty"` 77 NodeID *string `json:"node_id,omitempty"` 78 } 79 80 func (r ReleaseAsset) String() string { 81 return Stringify(r) 82 } 83 84 // ListReleases lists the releases for a repository. 85 // 86 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#list-releases 87 func (s *RepositoriesService) ListReleases(ctx context.Context, owner, repo string, opts *ListOptions) ([]*RepositoryRelease, *Response, error) { 88 u := fmt.Sprintf("repos/%s/%s/releases", owner, repo) 89 u, err := addOptions(u, opts) 90 if err != nil { 91 return nil, nil, err 92 } 93 94 req, err := s.client.NewRequest("GET", u, nil) 95 if err != nil { 96 return nil, nil, err 97 } 98 99 var releases []*RepositoryRelease 100 resp, err := s.client.Do(ctx, req, &releases) 101 if err != nil { 102 return nil, resp, err 103 } 104 return releases, resp, nil 105 } 106 107 // GetRelease fetches a single release. 108 // 109 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#get-a-release 110 func (s *RepositoriesService) GetRelease(ctx context.Context, owner, repo string, id int64) (*RepositoryRelease, *Response, error) { 111 u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id) 112 return s.getSingleRelease(ctx, u) 113 } 114 115 // GetLatestRelease fetches the latest published release for the repository. 116 // 117 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#get-the-latest-release 118 func (s *RepositoriesService) GetLatestRelease(ctx context.Context, owner, repo string) (*RepositoryRelease, *Response, error) { 119 u := fmt.Sprintf("repos/%s/%s/releases/latest", owner, repo) 120 return s.getSingleRelease(ctx, u) 121 } 122 123 // GetReleaseByTag fetches a release with the specified tag. 124 // 125 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#get-a-release-by-tag-name 126 func (s *RepositoriesService) GetReleaseByTag(ctx context.Context, owner, repo, tag string) (*RepositoryRelease, *Response, error) { 127 u := fmt.Sprintf("repos/%s/%s/releases/tags/%s", owner, repo, tag) 128 return s.getSingleRelease(ctx, u) 129 } 130 131 // GenerateReleaseNotes generates the release notes for the given tag. 132 // TODO: api docs 133 // GitHub API docs: 134 func (s *RepositoriesService) GenerateReleaseNotes(ctx context.Context, owner, repo string, opts *GenerateNotesOptions) (*RepositoryReleaseNotes, *Response, error) { 135 u := fmt.Sprintf("repos/%s/%s/releases/generate-notes", owner, repo) 136 req, err := s.client.NewRequest("POST", u, opts) 137 if err != nil { 138 return nil, nil, err 139 } 140 141 r := new(RepositoryReleaseNotes) 142 resp, err := s.client.Do(ctx, req, r) 143 if err != nil { 144 return nil, resp, err 145 } 146 147 return r, resp, nil 148 } 149 150 func (s *RepositoriesService) getSingleRelease(ctx context.Context, url string) (*RepositoryRelease, *Response, error) { 151 req, err := s.client.NewRequest("GET", url, nil) 152 if err != nil { 153 return nil, nil, err 154 } 155 156 release := new(RepositoryRelease) 157 resp, err := s.client.Do(ctx, req, release) 158 if err != nil { 159 return nil, resp, err 160 } 161 return release, resp, nil 162 } 163 164 // repositoryReleaseRequest is a subset of RepositoryRelease and 165 // is used internally by CreateRelease and EditRelease to pass 166 // only the known fields for these endpoints. 167 // 168 // See https://github.com/google/go-github/issues/992 for more 169 // information. 170 type repositoryReleaseRequest struct { 171 TagName *string `json:"tag_name,omitempty"` 172 TargetCommitish *string `json:"target_commitish,omitempty"` 173 Name *string `json:"name,omitempty"` 174 Body *string `json:"body,omitempty"` 175 Draft *bool `json:"draft,omitempty"` 176 Prerelease *bool `json:"prerelease,omitempty"` 177 GenerateReleaseNotes *bool `json:"generate_release_notes,omitempty"` 178 DiscussionCategoryName *string `json:"discussion_category_name,omitempty"` 179 } 180 181 // CreateRelease adds a new release for a repository. 182 // 183 // Note that only a subset of the release fields are used. 184 // See RepositoryRelease for more information. 185 // 186 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#create-a-release 187 func (s *RepositoriesService) CreateRelease(ctx context.Context, owner, repo string, release *RepositoryRelease) (*RepositoryRelease, *Response, error) { 188 u := fmt.Sprintf("repos/%s/%s/releases", owner, repo) 189 190 releaseReq := &repositoryReleaseRequest{ 191 TagName: release.TagName, 192 TargetCommitish: release.TargetCommitish, 193 Name: release.Name, 194 Body: release.Body, 195 Draft: release.Draft, 196 Prerelease: release.Prerelease, 197 DiscussionCategoryName: release.DiscussionCategoryName, 198 GenerateReleaseNotes: release.GenerateReleaseNotes, 199 } 200 201 req, err := s.client.NewRequest("POST", u, releaseReq) 202 if err != nil { 203 return nil, nil, err 204 } 205 206 r := new(RepositoryRelease) 207 resp, err := s.client.Do(ctx, req, r) 208 if err != nil { 209 return nil, resp, err 210 } 211 return r, resp, nil 212 } 213 214 // EditRelease edits a repository release. 215 // 216 // Note that only a subset of the release fields are used. 217 // See RepositoryRelease for more information. 218 // 219 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#update-a-release 220 func (s *RepositoriesService) EditRelease(ctx context.Context, owner, repo string, id int64, release *RepositoryRelease) (*RepositoryRelease, *Response, error) { 221 u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id) 222 223 releaseReq := &repositoryReleaseRequest{ 224 TagName: release.TagName, 225 TargetCommitish: release.TargetCommitish, 226 Name: release.Name, 227 Body: release.Body, 228 Draft: release.Draft, 229 Prerelease: release.Prerelease, 230 DiscussionCategoryName: release.DiscussionCategoryName, 231 GenerateReleaseNotes: release.GenerateReleaseNotes, 232 } 233 234 req, err := s.client.NewRequest("PATCH", u, releaseReq) 235 if err != nil { 236 return nil, nil, err 237 } 238 239 r := new(RepositoryRelease) 240 resp, err := s.client.Do(ctx, req, r) 241 if err != nil { 242 return nil, resp, err 243 } 244 return r, resp, nil 245 } 246 247 // DeleteRelease delete a single release from a repository. 248 // 249 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#delete-a-release 250 func (s *RepositoriesService) DeleteRelease(ctx context.Context, owner, repo string, id int64) (*Response, error) { 251 u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id) 252 253 req, err := s.client.NewRequest("DELETE", u, nil) 254 if err != nil { 255 return nil, err 256 } 257 return s.client.Do(ctx, req, nil) 258 } 259 260 // ListReleaseAssets lists the release's assets. 261 // 262 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#list-release-assets 263 func (s *RepositoriesService) ListReleaseAssets(ctx context.Context, owner, repo string, id int64, opts *ListOptions) ([]*ReleaseAsset, *Response, error) { 264 u := fmt.Sprintf("repos/%s/%s/releases/%d/assets", owner, repo, id) 265 u, err := addOptions(u, opts) 266 if err != nil { 267 return nil, nil, err 268 } 269 270 req, err := s.client.NewRequest("GET", u, nil) 271 if err != nil { 272 return nil, nil, err 273 } 274 275 var assets []*ReleaseAsset 276 resp, err := s.client.Do(ctx, req, &assets) 277 if err != nil { 278 return nil, resp, err 279 } 280 return assets, resp, nil 281 } 282 283 // GetReleaseAsset fetches a single release asset. 284 // 285 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#get-a-release-asset 286 func (s *RepositoriesService) GetReleaseAsset(ctx context.Context, owner, repo string, id int64) (*ReleaseAsset, *Response, error) { 287 u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) 288 289 req, err := s.client.NewRequest("GET", u, nil) 290 if err != nil { 291 return nil, nil, err 292 } 293 294 asset := new(ReleaseAsset) 295 resp, err := s.client.Do(ctx, req, asset) 296 if err != nil { 297 return nil, resp, err 298 } 299 return asset, resp, nil 300 } 301 302 // DownloadReleaseAsset downloads a release asset or returns a redirect URL. 303 // 304 // DownloadReleaseAsset returns an io.ReadCloser that reads the contents of the 305 // specified release asset. It is the caller's responsibility to close the ReadCloser. 306 // If a redirect is returned, the redirect URL will be returned as a string instead 307 // of the io.ReadCloser. Exactly one of rc and redirectURL will be zero. 308 // 309 // followRedirectsClient can be passed to download the asset from a redirected 310 // location. Passing http.DefaultClient is recommended unless special circumstances 311 // exist, but it's possible to pass any http.Client. If nil is passed the 312 // redirectURL will be returned instead. 313 // 314 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#get-a-release-asset 315 func (s *RepositoriesService) DownloadReleaseAsset(ctx context.Context, owner, repo string, id int64, followRedirectsClient *http.Client) (rc io.ReadCloser, redirectURL string, err error) { 316 u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) 317 318 req, err := s.client.NewRequest("GET", u, nil) 319 if err != nil { 320 return nil, "", err 321 } 322 req.Header.Set("Accept", defaultMediaType) 323 324 s.client.clientMu.Lock() 325 defer s.client.clientMu.Unlock() 326 327 var loc string 328 saveRedirect := s.client.client.CheckRedirect 329 s.client.client.CheckRedirect = func(req *http.Request, via []*http.Request) error { 330 loc = req.URL.String() 331 return errors.New("disable redirect") 332 } 333 defer func() { s.client.client.CheckRedirect = saveRedirect }() 334 335 req = withContext(ctx, req) 336 resp, err := s.client.client.Do(req) 337 if err != nil { 338 if !strings.Contains(err.Error(), "disable redirect") { 339 return nil, "", err 340 } 341 if followRedirectsClient != nil { 342 rc, err := s.downloadReleaseAssetFromURL(ctx, followRedirectsClient, loc) 343 return rc, "", err 344 } 345 return nil, loc, nil // Intentionally return no error with valid redirect URL. 346 } 347 348 if err := CheckResponse(resp); err != nil { 349 resp.Body.Close() 350 return nil, "", err 351 } 352 353 return resp.Body, "", nil 354 } 355 356 func (s *RepositoriesService) downloadReleaseAssetFromURL(ctx context.Context, followRedirectsClient *http.Client, url string) (rc io.ReadCloser, err error) { 357 req, err := http.NewRequest("GET", url, nil) 358 if err != nil { 359 return nil, err 360 } 361 req = withContext(ctx, req) 362 req.Header.Set("Accept", "*/*") 363 resp, err := followRedirectsClient.Do(req) 364 if err != nil { 365 return nil, err 366 } 367 if err := CheckResponse(resp); err != nil { 368 resp.Body.Close() 369 return nil, err 370 } 371 return resp.Body, nil 372 } 373 374 // EditReleaseAsset edits a repository release asset. 375 // 376 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#update-a-release-asset 377 func (s *RepositoriesService) EditReleaseAsset(ctx context.Context, owner, repo string, id int64, release *ReleaseAsset) (*ReleaseAsset, *Response, error) { 378 u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) 379 380 req, err := s.client.NewRequest("PATCH", u, release) 381 if err != nil { 382 return nil, nil, err 383 } 384 385 asset := new(ReleaseAsset) 386 resp, err := s.client.Do(ctx, req, asset) 387 if err != nil { 388 return nil, resp, err 389 } 390 return asset, resp, nil 391 } 392 393 // DeleteReleaseAsset delete a single release asset from a repository. 394 // 395 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#delete-a-release-asset 396 func (s *RepositoriesService) DeleteReleaseAsset(ctx context.Context, owner, repo string, id int64) (*Response, error) { 397 u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) 398 399 req, err := s.client.NewRequest("DELETE", u, nil) 400 if err != nil { 401 return nil, err 402 } 403 return s.client.Do(ctx, req, nil) 404 } 405 406 // UploadReleaseAsset creates an asset by uploading a file into a release repository. 407 // To upload assets that cannot be represented by an os.File, call NewUploadRequest directly. 408 // 409 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#upload-a-release-asset 410 func (s *RepositoriesService) UploadReleaseAsset(ctx context.Context, owner, repo string, id int64, opts *UploadOptions, file *os.File) (*ReleaseAsset, *Response, error) { 411 u := fmt.Sprintf("repos/%s/%s/releases/%d/assets", owner, repo, id) 412 u, err := addOptions(u, opts) 413 if err != nil { 414 return nil, nil, err 415 } 416 417 stat, err := file.Stat() 418 if err != nil { 419 return nil, nil, err 420 } 421 if stat.IsDir() { 422 return nil, nil, errors.New("the asset to upload can't be a directory") 423 } 424 425 mediaType := mime.TypeByExtension(filepath.Ext(file.Name())) 426 if opts.MediaType != "" { 427 mediaType = opts.MediaType 428 } 429 430 req, err := s.client.NewUploadRequest(u, file, stat.Size(), mediaType) 431 if err != nil { 432 return nil, nil, err 433 } 434 435 asset := new(ReleaseAsset) 436 resp, err := s.client.Do(ctx, req, asset) 437 if err != nil { 438 return nil, resp, err 439 } 440 return asset, resp, nil 441 }