Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@mislav
Copy link
Contributor

@mislav mislav commented Aug 19, 2020

New commands:

  • gh release list
  • gh release view <tag>
  • gh release create <tag> [<files>...]
  • gh release upload <tag> <files>...
  • gh release download <tag> [<pattern>]

Example output:

Screen Shot 2020-08-26 at 12 33 55

Example help:

$ gh release create -h
Create a new release

USAGE
  gh release create <tag> [<files>...] [flags]

FLAGS
  -d, --draft             Save the release as a draft instead of publishing it
  -n, --notes string      Release notes
  -F, --notes-file file   Read release notes from file
  -p, --prerelease        Mark the release as a prerelease
      --target branch     Target branch or commit SHA (default: main branch)
  -t, --title string      Release title

INHERITED FLAGS
      --help                     Show help for command
  -R, --repo [HOST/]OWNER/REPO   Select another repository using the [HOST/]OWNER/REPO format

TODO:

  • TESTS
  • list: render draft/prerelease information
  • view: add metadata to rendered output
  • view: add ability to view latest release for a repository
  • create: add flags for metadata
  • create: enable interactive flow
  • create: accept a list of files for upload
  • create: offer options to pre-populate release notes
    • Use commit messages as a template
    • Use pull request titles as a template
    • Use annotated git tag as template
  • gh release upload command
    • retry uploads on HTTP 50x
  • gh release download command
  • gh release delete command
  • enable all commands to reference unpublished releases by tag name

Fixes #378

mislav added 6 commits August 19, 2020 18:25
We install an HTTP middleware that adds the "Authorization" header on
every HTTP request. However, our asset download process might redirect
to a 3rd-party host (Amazon S3) and we want to allow those requests but
not require that they are authenticated.

Furthermore, we need the ability to specify the `Accept` request header
without it being overwritten by middleware, so now middleware only adds
headers that are not present in a request.
@mislav mislav marked this pull request as ready for review August 21, 2020 16:35
@eddumelendez
Copy link
Contributor

looking forward to it 😍

@ampinsk
Copy link
Contributor

ampinsk commented Aug 26, 2020

I'm so excited for this one, it looks great! Had one nit from going through things:

Should we autocomplete the title from the tag?

$ gh release create 3.4.2
? Title (3.4.2)

@mislav
Copy link
Contributor Author

mislav commented Sep 1, 2020

Should we autocomplete the title from the tag?

$ gh release create 3.4.2
? Title (3.4.2)

@ampinsk We can consider this, but the release title is technically optional on the server and, if left blank, will inherit the title of the git tag (if any) or the subject of the git commit that the tag points to. If people are actively counting on this behavior by intentionally leaving the title blank, then by pre-filling the title to match the tag name, we effectively disallow that fallback on the client.

I'm on the fence about what the ideal behavior would be, but for the sake of predictability and parity with platform behavior, I'm currently leaning to allowing blank titles.

@ampinsk
Copy link
Contributor

ampinsk commented Sep 1, 2020

@mislav was definitely just a nit, totally fine with dropping it!

Copy link
Contributor

@vilmibm vilmibm left a comment

Choose a reason for hiding this comment

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

this was a lot to review at once >_>

i didn't see any blockers to merging, but had some light requests here and there so i'm marking request changes. overall i found the new commands to be very comprehensible and well organized, thanks!

api/client.go Outdated
return func(tr http.RoundTripper) http.RoundTripper {
return &funcTripper{roundTrip: func(req *http.Request) (*http.Response, error) {
req.Header.Add(name, value)
if len(req.Header.Values(name)) == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

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

why forbid overriding, here? I would expect AddHeader to let me replace an existing header with a new value.

Copy link

Choose a reason for hiding this comment

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

Copy link

Choose a reason for hiding this comment

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

Copy link

Choose a reason for hiding this comment

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

😄

Copy link

Choose a reason for hiding this comment

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

ResponseHeader: logTraffic,
ResponseBody: logTraffic,
Formatters: []httpretty.Formatter{&httpretty.JSONFormatter{}},
MaxResponseBody: 10000,
Copy link
Contributor

Choose a reason for hiding this comment

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

is this intentional? I do this a lot locally so I certainly wouldn't mind it being in trunk.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good spot! Yes, it's intentional— I realized that I wasn't able to debug large response payloads without this.

Copy link

Choose a reason for hiding this comment

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

is this intentional? I do this a lot locally so I certainly wouldn't mind it being in trunk.

Copy link

Choose a reason for hiding this comment

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

is this intentional? I do this a lot locally so I certainly wouldn't mind it being in trunk.

return func(tr http.RoundTripper) http.RoundTripper {
return &funcTripper{roundTrip: func(req *http.Request) (*http.Response, error) {
req.Header.Add(name, value)
if req.Header.Get(name) == "" {
Copy link
Contributor

Choose a reason for hiding this comment

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

why prevent overriding, here? I'd think that repeated calls to AddHeader would update a header value, not silently fail to update.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's a good question! The short answer is that I had to add the guard to allow file downloads. Without this change, a file download request would set an Accept: application/octet-stream, but additional Accept values would get added by our request middleware via AddHeader and AddHeaderFunc, and thus JSON payload would get returned and the download sabotaged.

When I then thought about our AddHeader and AddHeaderFunc utilities, I realized that they are more in the service of adding default request headers (e.g. default Accept, default User-Agent, default Authorization), but if a request already contained any of those headers explicitly, there would be no reason to apply the default.

You have a good point that maybe we should change the verb from "Add" to something else that reflects the current semantics better?

Copy link
Contributor

Choose a reason for hiding this comment

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

ah, that makes sense -- in that case yeah I think we can rename from AddHeader to something like SetHeaderDefault or similar

Copy link

Choose a reason for hiding this comment

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

Copy link

Choose a reason for hiding this comment

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

Copy link

Choose a reason for hiding this comment

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

}

func publishRelease(httpClient *http.Client, releaseURL string) error {
req, err := http.NewRequest("PATCH", releaseURL, bytes.NewBufferString(`{"draft":false}`))
Copy link
Contributor

Choose a reason for hiding this comment

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

what is your motivation for moving away from client.REST? I don't have an issue with it, just curious.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

First and foremost is because REST requires me to instantiate an api.Client, which is an outdated pattern that I would like us to move away from since it acted as a wrapper around http.Client, but did not provide any benefits.

An additional reason was that I was hoping to see how comfortable I was letting specific commands control most aspects of how they process HTTP requests. This is with the goal of decreasing inter-package dependencies. I'm satisfied by this approach, but I do believe that error handling should be shared between all commands, as well as authentication.

},
}

cmd.Flags().StringVarP(&opts.Destination, "dir", "C", ".", "The directory to download files into")
Copy link
Contributor

Choose a reason for hiding this comment

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

why C for this? D, d, or o all seem more intuitive

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question! I guess I was biased towards -C because commands like tar, make, and git support it. Wget does -P, --directory-prefix. I will change to -D 👍

}

cmd := &cobra.Command{
Use: "download <tag> [<pattern>]",
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's worth documenting what kind of pattern this can be; when I was testing this out I wasn't sure if it was glob, plain wildcard, or regular expression.

var downloadError error
for i := 0; i < len(toDownload); i++ {
if err := <-results; err != nil {
downloadError = err
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if it's worth a break here if we're not going to collect errors per-download. Otherwise we're discarding errors for all but the last download which is confusing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good thought, but we cannot break here since we need to invoke <-results as many times as there were download jobs. You're right that all but the latest error gets discarded. Right now I don't think that's such an issue, but if people notice this, we could collect all errors into one meta-error

}

// FindDraftRelease interates over all releases in a repository until it finds one that matches tagName.
func FindDraftRelease(httpClient *http.Client, baseRepo ghrepo.Interface, tagName string) (*Release, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

does this exist because draft releases aren't included when using releases/tag/:foo? I'm confused how the Draft part of this function's name relates to what it actually does.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, the API doesn't let us query draft releases by tag name (makes sense, since there can be multiple drafts referring to the same tag name), so I have to resort to listing all releases. You are right that this lookup returns any release that matches a tag, not necessarily draft. To stay true to the function name, I will add code that only scopes the lookup to draft releases.

bold = ansi.ColorFunc("default+b")
)

func NewColorScheme(enabled bool) *ColorScheme {
Copy link
Contributor

Choose a reason for hiding this comment

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

i like this approach to color! but please open a tech debt issue to port the whole codebase to it so we don't end up with competing color systems.

@tierninho
Copy link
Contributor

tierninho commented Sep 4, 2020

Two questions, is this path error due to the test mode or a bug?

gh release create TAG1 -R tierninho/blah
? Title TRIAL
? Release notes Write my own
/var/folders/cn/b6t8mw1s6xqcfysnl8jfjdg80000gn/T/874767777.md:1: command not found: \ufeff
exit status 127

and is it ok to skip the title? It will create a Draft title more than once.

gh release create TAG1 -R tierninho/blah
? Title
? Release notes Leave blank
? Is this a prerelease? No
? Submit? Save as draft
https://github.com/tierninho/Blah/releases/tag/untagged-caba1054e408fa5fef50

Screen Shot 2020-09-04 at 8 16 32 AM


Questions:

  • where are the assets downloaded to if you use the -R flag?
  • is there a -w flag coming as it might be useful in gh release view [tag] -w for instance?
  • Should there be a success message if upload succeeded?
  • Is the -d and -p supposed to be allowed at same time? I looks life a draft supersedes a prerelease.

@vilmibm
Copy link
Contributor

vilmibm commented Sep 8, 2020

@tierninho

Two questions, is this path error due to the test mode or a bug?

that error looks related to a bad value set for editor?

@mislav
Copy link
Contributor Author

mislav commented Sep 9, 2020

@tierninho All great questions!

  • where are the assets downloaded to if you use the -R flag?

The same as without the -R flag: the current directory, or the one specified by --dir.

  • is there a -w flag coming as it might be useful in gh release view [tag] -w for instance?

That's a good idea! I will add it.

  • Should there be a success message if upload succeeded?

We haven't talked about it, but a message like “6 files successfully uploaded” might be nice. I'll consider adding it!

  • Is the -d and -p supposed to be allowed at same time? I looks life a draft supersedes a prerelease.

Yes because a draft will usually get published, and when it does then the original "prerelease" bit will matter. The "draft" and "prerelease" bits are thus independent of each other.

@cyrilletuzi
Copy link

@mislav First, thank you for this feature! <3

But is there a way to use the latest created tag (for example gh release create latest), instead of having to write the tag number manually (ie. gh release create v1.0.1)?

It seems very important to me to really be usable in an automation process. For exemple:

  • for my VS Code extension, my release script is:
vsce publish patch && git push && git push --tags
  • for my npm packages, my release script is:
npm run build && npm version minor && npm publish dist && git push && git push --tags;

In both cases, the tag creation is automated (by vsce publish or npm version), so I don't have access to the tag number directly. So I would not be able to be able to add the last automation step with gh release create.

@mislav
Copy link
Contributor Author

mislav commented Sep 10, 2020

@cyrilletuzi That's an excellent feature request and something that I haven't considered; thank you! Would you mind opening a separate issue so we can discuss and track this?

In the meantime, would it be an option for you to use the output of git tag --sort=-creatordate | head -1?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Asset and release management

7 participants