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