Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions github/repos_releases.go
Original file line number Diff line number Diff line change
Expand Up @@ -488,3 +488,59 @@ func (s *RepositoriesService) UploadReleaseAsset(ctx context.Context, owner, rep
}
return asset, resp, nil
}

// UploadReleaseAssetFromRelease uploads an asset using the UploadURL that's embedded
// in a RepositoryRelease object.
//
// This is a convenience wrapper that extracts the release.UploadURL (which is usually
// templated like "https://uploads.github.com/.../assets{?name,label}") and uploads
// the provided file using the existing upload helpers.
//
// GitHub API docs: https://docs.github.com/rest/releases/assets#upload-a-release-asset
//
//meta:operation POST /repos/{owner}/{repo}/releases/{release_id}/assets
func (s *RepositoriesService) UploadReleaseAssetFromRelease(ctx context.Context, release *RepositoryRelease, opts *UploadOptions, file *os.File) (*ReleaseAsset, *Response, error) {
Copy link
Collaborator

@gmlewis gmlewis Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a file *os.File here is unnecessarily restrictive.
Note that the method that this function calls (NewUploadRequest) takes:

func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, size int64, mediaType string, opts ...RequestOption) (*http.Request, error) {

which allows uploading of data from a much more flexible range of sources.

Please adopt the same API in this helper method.

if release == nil || release.UploadURL == nil {
return nil, nil, errors.New("release UploadURL must be provided")
}
if file == nil {
return nil, nil, errors.New("file must be provided")
}

// Extract upload URL and remove template part (e.g. "{?name,label}") if present.
uploadURL := *release.UploadURL
if idx := strings.Index(uploadURL, "{"); idx != -1 {
uploadURL = uploadURL[:idx]
}

// addOptions will append query params for name/label (same as UploadReleaseAsset)
u, err := addOptions(uploadURL, opts)
if err != nil {
return nil, nil, err
}

stat, err := file.Stat()
if err != nil {
return nil, nil, err
}
if stat.IsDir() {
return nil, nil, errors.New("the asset to upload can't be a directory")
}

mediaType := mime.TypeByExtension(filepath.Ext(file.Name()))
if opts != nil && opts.MediaType != "" {
mediaType = opts.MediaType
}

req, err := s.client.NewUploadRequest(u, file, stat.Size(), mediaType)
if err != nil {
return nil, nil, err
}

asset := new(ReleaseAsset)
resp, err := s.client.Do(ctx, req, asset)
if err != nil {
return nil, resp, err
}
return asset, resp, nil
}
Loading