Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package gh for openSUSE:Factory checked in at 2023-09-21 22:23:33 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/gh (Old) and /work/SRC/openSUSE:Factory/.gh.new.1770 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "gh" Thu Sep 21 22:23:33 2023 rev:38 rq:1112794 version:2.35.0 Changes: -------- --- /work/SRC/openSUSE:Factory/gh/gh.changes 2023-09-08 21:16:19.068136269 +0200 +++ /work/SRC/openSUSE:Factory/.gh.new.1770/gh.changes 2023-09-21 22:24:16.091521209 +0200 @@ -1,0 +2,18 @@ +Wed Sep 20 12:15:33 UTC 2023 - pdos...@suse.com + +- Update to version 2.35.0: + * Cleanup release create around new --notes-from-tag flag (#8016) + * Document when gh auth login writes oauth token to plain text (#7781) + * Show full permissions URL in `gh cs create` (#7983) + * build(deps): bump goreleaser/goreleaser-action from 4 to 5 + * moved remoteTagExists under if NotesFromTag exists and added three tests + * Clarify list repo behavior (#7964) + * Reinforce not opening PRs without approval on an issue + * Delete local tag when running `gh release delete --cleanup-tag` (#7884) + * Add `--all` flag to `alias delete` command (#7900) + * Move homebrew bump fork to personal repo + * Revert "Add a test workflow for homebrew bump investigations (#7951)" + * Add a test workflow for homebrew bump investigations (#7951) + * Add `gh release create [<tag>] --notes-from-tag` + +------------------------------------------------------------------- Old: ---- cli-2.34.0.tar.gz New: ---- cli-2.35.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ gh.spec ++++++ --- /var/tmp/diff_new_pack.skKXie/_old 2023-09-21 22:24:17.563574625 +0200 +++ /var/tmp/diff_new_pack.skKXie/_new 2023-09-21 22:24:17.563574625 +0200 @@ -19,7 +19,7 @@ %define goflags "-buildmode=pie -trimpath -mod=vendor -modcacherw" %define sname cli Name: gh -Version: 2.34.0 +Version: 2.35.0 Release: 0 Summary: The official CLI for GitHub License: MIT @@ -40,6 +40,7 @@ Requires: %{name} = %{version} Requires: bash-completion Supplements: (gh and bash-completion) +BuildArch: noarch %description bash-completion Bash command line completion support for %{name}. ++++++ _service ++++++ --- /var/tmp/diff_new_pack.skKXie/_old 2023-09-21 22:24:17.591575641 +0200 +++ /var/tmp/diff_new_pack.skKXie/_new 2023-09-21 22:24:17.595575787 +0200 @@ -3,7 +3,7 @@ <param name="url">https://github.com/cli/cli</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v2.34.0</param> + <param name="revision">v2.35.0</param> <param name="versionformat">@PARENT_TAG@</param> <param name="changesgenerate">enable</param> <param name="versionrewrite-pattern">v(.*)</param> @@ -16,7 +16,7 @@ <param name="compression">gz</param> </service> <service name="go_modules" mode="disabled"> - <param name="archive">cli-2.34.0.tar.gz</param> + <param name="archive">cli-2.35.0.tar.gz</param> </service> </services> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.skKXie/_old 2023-09-21 22:24:17.611576367 +0200 +++ /var/tmp/diff_new_pack.skKXie/_new 2023-09-21 22:24:17.615576513 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/cli/cli</param> - <param name="changesrevision">e5f499f3012907f2191e85bb1d6fbfc16b71173f</param></service></servicedata> + <param name="changesrevision">94fbbdf9b5b81a433c8bb60cd16b8d179822d834</param></service></servicedata> (No newline at EOF) ++++++ cli-2.34.0.tar.gz -> cli-2.35.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cli-2.34.0/.github/CONTRIBUTING.md new/cli-2.35.0/.github/CONTRIBUTING.md --- old/cli-2.34.0/.github/CONTRIBUTING.md 2023-09-06 09:46:43.000000000 +0200 +++ new/cli-2.35.0/.github/CONTRIBUTING.md 2023-09-19 10:26:52.000000000 +0200 @@ -16,6 +16,7 @@ Please avoid: * Opening pull requests for issues marked `needs-design`, `needs-investigation`, or `blocked`. +* Opening pull requests that haven't been approved for work in an issue * Adding installation instructions specifically for your OS/package manager. * Opening pull requests for any issue marked `core`. These issues require additional context from the core CLI team at GitHub and any external pull requests will not be accepted. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cli-2.34.0/.github/workflows/deployment.yml new/cli-2.35.0/.github/workflows/deployment.yml --- old/cli-2.34.0/.github/workflows/deployment.yml 2023-09-06 09:46:43.000000000 +0200 +++ new/cli-2.35.0/.github/workflows/deployment.yml 2023-09-19 10:26:52.000000000 +0200 @@ -40,7 +40,7 @@ with: go-version: ${{ inputs.go_version }} - name: Install GoReleaser - uses: goreleaser/goreleaser-action@v4 + uses: goreleaser/goreleaser-action@v5 with: version: "~1.17.1" install-only: true @@ -92,7 +92,7 @@ security set-key-partition-list -S "apple-tool:,apple:,codesign:" -s -k "$keychain_password" "$keychain" rm "$RUNNER_TEMP/cert.p12" - name: Install GoReleaser - uses: goreleaser/goreleaser-action@v4 + uses: goreleaser/goreleaser-action@v5 with: version: "~1.17.1" install-only: true @@ -140,7 +140,7 @@ env: CERT_CONTENTS: ${{ secrets.WINDOWS_CERT_PFX }} - name: Install GoReleaser - uses: goreleaser/goreleaser-action@v4 + uses: goreleaser/goreleaser-action@v5 with: version: "~1.17.1" install-only: true @@ -342,6 +342,6 @@ formula-name: gh formula-path: Formula/g/gh.rb tag-name: ${{ inputs.tag_name }} - push-to: cli/homebrew-core + push-to: williammartin/homebrew-core env: COMMITTER_TOKEN: ${{ secrets.HOMEBREW_PR_PAT }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cli-2.34.0/git/client.go new/cli-2.35.0/git/client.go --- old/cli-2.34.0/git/client.go 2023-09-06 09:46:43.000000000 +0200 +++ new/cli-2.35.0/git/client.go 2023-09-19 10:26:52.000000000 +0200 @@ -322,6 +322,19 @@ return } +func (c *Client) DeleteLocalTag(ctx context.Context, tag string) error { + args := []string{"tag", "-d", tag} + cmd, err := c.Command(ctx, args...) + if err != nil { + return err + } + _, err = cmd.Output() + if err != nil { + return err + } + return nil +} + func (c *Client) DeleteLocalBranch(ctx context.Context, branch string) error { args := []string{"branch", "-D", branch} cmd, err := c.Command(ctx, args...) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cli-2.34.0/git/client_test.go new/cli-2.35.0/git/client_test.go --- old/cli-2.34.0/git/client_test.go 2023-09-06 09:46:43.000000000 +0200 +++ new/cli-2.35.0/git/client_test.go 2023-09-19 10:26:52.000000000 +0200 @@ -558,6 +558,45 @@ } } +func TestClientDeleteLocalTag(t *testing.T) { + tests := []struct { + name string + cmdExitStatus int + cmdStdout string + cmdStderr string + wantCmdArgs string + wantErrorMsg string + }{ + { + name: "delete local tag", + wantCmdArgs: `path/to/git tag -d v1.0`, + }, + { + name: "git error", + cmdExitStatus: 1, + cmdStderr: "git error message", + wantCmdArgs: `path/to/git tag -d v1.0`, + wantErrorMsg: "failed to run git: git error message", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd, cmdCtx := createCommandContext(t, tt.cmdExitStatus, tt.cmdStdout, tt.cmdStderr) + client := Client{ + GitPath: "path/to/git", + commandContext: cmdCtx, + } + err := client.DeleteLocalTag(context.Background(), "v1.0") + assert.Equal(t, tt.wantCmdArgs, strings.Join(cmd.Args[3:], " ")) + if tt.wantErrorMsg == "" { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, tt.wantErrorMsg) + } + }) + } +} + func TestClientDeleteLocalBranch(t *testing.T) { tests := []struct { name string diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cli-2.34.0/internal/config/config.go new/cli-2.35.0/internal/config/config.go --- old/cli-2.34.0/internal/config/config.go 2023-09-06 09:46:43.000000000 +0200 +++ new/cli-2.35.0/internal/config/config.go 2023-09-19 10:26:52.000000000 +0200 @@ -220,7 +220,7 @@ // Login will set user, git protocol, and auth token for the given hostname. // If the encrypt option is specified it will first try to store the auth token // in encrypted storage and will fall back to the plain text config file. -func (c *AuthConfig) Login(hostname, username, token, gitProtocol string, secureStorage bool) error { +func (c *AuthConfig) Login(hostname, username, token, gitProtocol string, secureStorage bool) (bool, error) { var setErr error if secureStorage { if setErr = keyring.Set(keyringServiceName(hostname), "", token); setErr == nil { @@ -228,8 +228,10 @@ _ = c.cfg.Remove([]string{hosts, hostname, oauthToken}) } } + insecureStorageUsed := false if !secureStorage || setErr != nil { c.cfg.Set([]string{hosts, hostname, oauthToken}, token) + insecureStorageUsed = true } if username != "" { c.cfg.Set([]string{hosts, hostname, "user"}, username) @@ -237,7 +239,7 @@ if gitProtocol != "" { c.cfg.Set([]string{hosts, hostname, "git_protocol"}, gitProtocol) } - return ghConfig.Write(c.cfg) + return insecureStorageUsed, ghConfig.Write(c.cfg) } // Logout will remove user, git protocol, and auth token for the given hostname. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cli-2.34.0/pkg/cmd/alias/delete/delete.go new/cli-2.35.0/pkg/cmd/alias/delete/delete.go --- old/cli-2.34.0/pkg/cmd/alias/delete/delete.go 2023-09-06 09:46:43.000000000 +0200 +++ new/cli-2.35.0/pkg/cmd/alias/delete/delete.go 2023-09-19 10:26:52.000000000 +0200 @@ -2,6 +2,7 @@ import ( "fmt" + "sort" "github.com/cli/cli/v2/internal/config" "github.com/cli/cli/v2/pkg/cmdutil" @@ -14,6 +15,7 @@ IO *iostreams.IOStreams Name string + All bool } func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Command { @@ -23,12 +25,19 @@ } cmd := &cobra.Command{ - Use: "delete <alias>", - Short: "Delete an alias", - Args: cobra.ExactArgs(1), + Use: "delete {<alias> | --all}", + Short: "Delete set aliases", + Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - opts.Name = args[0] - + if len(args) == 0 && !opts.All { + return cmdutil.FlagErrorf("specify an alias to delete or `--all`") + } + if len(args) > 0 && opts.All { + return cmdutil.FlagErrorf("cannot use `--all` with alias name") + } + if len(args) > 0 { + opts.Name = args[0] + } if runF != nil { return runF(opts) } @@ -36,6 +45,8 @@ }, } + cmd.Flags().BoolVar(&opts.All, "all", false, "Delete all aliases") + return cmd } @@ -47,25 +58,40 @@ aliasCfg := cfg.Aliases() - expansion, err := aliasCfg.Get(opts.Name) - if err != nil { - return fmt.Errorf("no such alias %s", opts.Name) - + aliases := make(map[string]string) + if opts.All { + aliases = aliasCfg.All() + if len(aliases) == 0 { + return cmdutil.NewNoResultsError("no aliases configured") + } + } else { + expansion, err := aliasCfg.Get(opts.Name) + if err != nil { + return fmt.Errorf("no such alias %s", opts.Name) + } + aliases[opts.Name] = expansion + } + + for name := range aliases { + if err := aliasCfg.Delete(name); err != nil { + return fmt.Errorf("failed to delete alias %s: %w", name, err) + } } - err = aliasCfg.Delete(opts.Name) - if err != nil { - return fmt.Errorf("failed to delete alias %s: %w", opts.Name, err) - } - - err = cfg.Write() - if err != nil { + if err := cfg.Write(); err != nil { return err } if opts.IO.IsStdoutTTY() { cs := opts.IO.ColorScheme() - fmt.Fprintf(opts.IO.ErrOut, "%s Deleted alias %s; was %s\n", cs.SuccessIconWithColor(cs.Red), opts.Name, expansion) + keys := make([]string, 0, len(aliases)) + for k := range aliases { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + fmt.Fprintf(opts.IO.ErrOut, "%s Deleted alias %s; was %s\n", cs.SuccessIconWithColor(cs.Red), k, aliases[k]) + } } return nil diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cli-2.34.0/pkg/cmd/alias/delete/delete_test.go new/cli-2.35.0/pkg/cmd/alias/delete/delete_test.go --- old/cli-2.34.0/pkg/cmd/alias/delete/delete_test.go 2023-09-06 09:46:43.000000000 +0200 +++ new/cli-2.35.0/pkg/cmd/alias/delete/delete_test.go 2023-09-19 10:26:52.000000000 +0200 @@ -11,78 +11,174 @@ "github.com/cli/cli/v2/pkg/iostreams" "github.com/google/shlex" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) -func TestAliasDelete(t *testing.T) { - _ = config.StubWriteConfig(t) - +func TestNewCmdDelete(t *testing.T) { tests := []struct { - name string - config string - cli string - isTTY bool - wantStdout string - wantStderr string - wantErr string + name string + input string + output DeleteOptions + wantErr bool + errMsg string }{ { - name: "no aliases", - config: "", - cli: "co", - isTTY: true, - wantStdout: "", - wantStderr: "", - wantErr: "no such alias co", + name: "no arguments", + input: "", + wantErr: true, + errMsg: "specify an alias to delete or `--all`", + }, + { + name: "specified alias", + input: "co", + output: DeleteOptions{ + Name: "co", + }, + }, + { + name: "all flag", + input: "--all", + output: DeleteOptions{ + All: true, + }, + }, + { + name: "specified alias and all flag", + input: "co --all", + wantErr: true, + errMsg: "cannot use `--all` with alias name", }, { - name: "delete one", + name: "too many arguments", + input: "il co", + wantErr: true, + errMsg: "accepts at most 1 arg(s), received 2", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ios, _, _, _ := iostreams.Test() + f := &cmdutil.Factory{ + IOStreams: ios, + } + argv, err := shlex.Split(tt.input) + assert.NoError(t, err) + var gotOpts *DeleteOptions + cmd := NewCmdDelete(f, func(opts *DeleteOptions) error { + gotOpts = opts + return nil + }) + cmd.SetArgs(argv) + cmd.SetIn(&bytes.Buffer{}) + cmd.SetOut(io.Discard) + cmd.SetErr(io.Discard) + _, err = cmd.ExecuteC() + if tt.wantErr { + assert.EqualError(t, err, tt.errMsg) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.output.Name, gotOpts.Name) + assert.Equal(t, tt.output.All, gotOpts.All) + }) + } +} + +func TestDeleteRun(t *testing.T) { + tests := []struct { + name string + config string + isTTY bool + opts *DeleteOptions + wantAliases map[string]string + wantStdout string + wantStderr string + wantErrMsg string + }{ + { + name: "delete alias", config: heredoc.Doc(` aliases: il: issue list co: pr checkout `), - cli: "co", - isTTY: true, - wantStdout: "", + isTTY: true, + opts: &DeleteOptions{ + Name: "co", + All: false, + }, + wantAliases: map[string]string{ + "il": "issue list", + }, wantStderr: "â Deleted alias co; was pr checkout\n", }, + { + name: "delete all aliases", + config: heredoc.Doc(` + aliases: + il: issue list + co: pr checkout + `), + isTTY: true, + opts: &DeleteOptions{ + All: true, + }, + wantAliases: map[string]string{}, + wantStderr: "â Deleted alias co; was pr checkout\nâ Deleted alias il; was issue list\n", + }, + { + name: "delete alias that does not exist", + config: heredoc.Doc(` + aliases: + il: issue list + co: pr checkout + `), + isTTY: true, + opts: &DeleteOptions{ + Name: "unknown", + }, + wantAliases: map[string]string{ + "il": "issue list", + "co": "pr checkout", + }, + wantErrMsg: "no such alias unknown", + }, + { + name: "delete all aliases when none exist", + isTTY: true, + opts: &DeleteOptions{ + All: true, + }, + wantAliases: map[string]string{}, + wantErrMsg: "no aliases configured", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - cfg := config.NewFromString(tt.config) - ios, _, stdout, stderr := iostreams.Test() - ios.SetStdoutTTY(tt.isTTY) ios.SetStdinTTY(tt.isTTY) + ios.SetStdoutTTY(tt.isTTY) ios.SetStderrTTY(tt.isTTY) + tt.opts.IO = ios - factory := &cmdutil.Factory{ - IOStreams: ios, - Config: func() (config.Config, error) { - return cfg, nil - }, + cfg := config.NewFromString(tt.config) + tt.opts.Config = func() (config.Config, error) { + return cfg, nil } - cmd := NewCmdDelete(factory, nil) - - argv, err := shlex.Split(tt.cli) - require.NoError(t, err) - cmd.SetArgs(argv) - - cmd.SetIn(&bytes.Buffer{}) - cmd.SetOut(io.Discard) - cmd.SetErr(io.Discard) - - _, err = cmd.ExecuteC() - if tt.wantErr != "" { - assert.EqualError(t, err, tt.wantErr) - return + err := deleteRun(tt.opts) + if tt.wantErrMsg != "" { + assert.EqualError(t, err, tt.wantErrMsg) + writeCalls := cfg.WriteCalls() + assert.Equal(t, 0, len(writeCalls)) + } else { + assert.NoError(t, err) + writeCalls := cfg.WriteCalls() + assert.Equal(t, 1, len(writeCalls)) } - require.NoError(t, err) assert.Equal(t, tt.wantStdout, stdout.String()) assert.Equal(t, tt.wantStderr, stderr.String()) + assert.Equal(t, tt.wantAliases, cfg.Aliases().All()) }) } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cli-2.34.0/pkg/cmd/auth/login/login.go new/cli-2.35.0/pkg/cmd/auth/login/login.go --- old/cli-2.34.0/pkg/cmd/auth/login/login.go 2023-09-06 09:46:43.000000000 +0200 +++ new/cli-2.35.0/pkg/cmd/auth/login/login.go 2023-09-19 10:26:52.000000000 +0200 @@ -58,7 +58,9 @@ Authenticate with a GitHub host. The default authentication mode is a web-based browser flow. After completion, an - authentication token will be stored internally. + authentication token will be stored securely in the system credential store. + If a credential store is not found or there is an issue using it gh will fallback to writing the token to a plain text file. + See %[1]sgh auth status%[1]s for its stored location. Alternatively, use %[1]s--with-token%[1]s to pass in a token on standard input. The minimum required scopes for the token are: "repo", "read:org". @@ -172,7 +174,8 @@ return fmt.Errorf("error validating token: %w", err) } // Adding a user key ensures that a nonempty host section gets written to the config file. - return authCfg.Login(hostname, "x-access-token", opts.Token, opts.GitProtocol, !opts.InsecureStorage) + _, loginErr := authCfg.Login(hostname, "x-access-token", opts.Token, opts.GitProtocol, !opts.InsecureStorage) + return loginErr } existingToken, _ := authCfg.Token(hostname) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cli-2.34.0/pkg/cmd/auth/refresh/refresh.go new/cli-2.35.0/pkg/cmd/auth/refresh/refresh.go --- old/cli-2.34.0/pkg/cmd/auth/refresh/refresh.go 2023-09-06 09:46:43.000000000 +0200 +++ new/cli-2.35.0/pkg/cmd/auth/refresh/refresh.go 2023-09-19 10:26:52.000000000 +0200 @@ -48,7 +48,8 @@ if err != nil { return err } - return authCfg.Login(hostname, username, token, "", secureStorage) + _, loginErr := authCfg.Login(hostname, username, token, "", secureStorage) + return loginErr }, HttpClient: &http.Client{}, GitClient: f.GitClient, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cli-2.34.0/pkg/cmd/auth/shared/login_flow.go new/cli-2.35.0/pkg/cmd/auth/shared/login_flow.go --- old/cli-2.34.0/pkg/cmd/auth/shared/login_flow.go 2023-09-06 09:46:43.000000000 +0200 +++ new/cli-2.35.0/pkg/cmd/auth/shared/login_flow.go 2023-09-19 10:26:52.000000000 +0200 @@ -22,7 +22,7 @@ const defaultSSHKeyTitle = "GitHub CLI" type iconfig interface { - Login(string, string, string, string, bool) error + Login(string, string, string, string, bool) (bool, error) } type LoginOptions struct { @@ -188,9 +188,13 @@ fmt.Fprintf(opts.IO.ErrOut, "%s Configured git protocol\n", cs.SuccessIcon()) } - if err := cfg.Login(hostname, username, authToken, gitProtocol, opts.SecureStorage); err != nil { + insecureStorageUsed, err := cfg.Login(hostname, username, authToken, gitProtocol, opts.SecureStorage) + if err != nil { return err } + if insecureStorageUsed { + fmt.Fprintf(opts.IO.ErrOut, "%s Authentication credentials saved in plain text\n", cs.Yellow("!")) + } if credentialFlow.ShouldSetup() { err := credentialFlow.Setup(hostname, username, authToken) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cli-2.34.0/pkg/cmd/auth/shared/login_flow_test.go new/cli-2.35.0/pkg/cmd/auth/shared/login_flow_test.go --- old/cli-2.34.0/pkg/cmd/auth/shared/login_flow_test.go 2023-09-06 09:46:43.000000000 +0200 +++ new/cli-2.35.0/pkg/cmd/auth/shared/login_flow_test.go 2023-09-19 10:26:52.000000000 +0200 @@ -18,11 +18,11 @@ type tinyConfig map[string]string -func (c tinyConfig) Login(host, username, token, gitProtocol string, encrypt bool) error { +func (c tinyConfig) Login(host, username, token, gitProtocol string, encrypt bool) (bool, error) { c[fmt.Sprintf("%s:%s", host, "user")] = username c[fmt.Sprintf("%s:%s", host, "oauth_token")] = token c[fmt.Sprintf("%s:%s", host, "git_protocol")] = gitProtocol - return nil + return false, nil } func TestLogin_ssh(t *testing.T) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cli-2.34.0/pkg/cmd/codespace/create.go new/cli-2.35.0/pkg/cmd/codespace/create.go --- old/cli-2.34.0/pkg/cmd/codespace/create.go 2023-09-06 09:46:43.000000000 +0200 +++ new/cli-2.35.0/pkg/cmd/codespace/create.go 2023-09-19 10:26:52.000000000 +0200 @@ -11,7 +11,6 @@ "github.com/cli/cli/v2/internal/codespaces" "github.com/cli/cli/v2/internal/codespaces/api" "github.com/cli/cli/v2/internal/ghrepo" - "github.com/cli/cli/v2/internal/text" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/spf13/cobra" ) @@ -336,13 +335,12 @@ var ( isInteractive = a.io.CanPrompt() cs = a.io.ColorScheme() - displayURL = text.DisplayURL(allowPermissionsURL) ) fmt.Fprintf(a.io.ErrOut, "You must authorize or deny additional permissions requested by this codespace before continuing.\n") if !isInteractive { - fmt.Fprintf(a.io.ErrOut, "%s in your browser to review and authorize additional permissions: %s\n", cs.Bold("Open this URL"), displayURL) + fmt.Fprintf(a.io.ErrOut, "%s in your browser to review and authorize additional permissions: %s\n", cs.Bold("Open this URL"), allowPermissionsURL) fmt.Fprintf(a.io.ErrOut, "Alternatively, you can run %q with the %q option to continue without authorizing additional permissions.\n", a.io.ColorScheme().Bold("create"), cs.Bold("--default-permissions")) return nil, cmdutil.SilentError } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cli-2.34.0/pkg/cmd/codespace/create_test.go new/cli-2.35.0/pkg/cmd/codespace/create_test.go --- old/cli-2.34.0/pkg/cmd/codespace/create_test.go 2023-09-06 09:46:43.000000000 +0200 +++ new/cli-2.35.0/pkg/cmd/codespace/create_test.go 2023-09-19 10:26:52.000000000 +0200 @@ -356,7 +356,39 @@ wantErr: cmdutil.SilentError, wantStderr: ` â Codespaces usage for this repository is paid for by monalisa You must authorize or deny additional permissions requested by this codespace before continuing. -Open this URL in your browser to review and authorize additional permissions: example.com/permissions +Open this URL in your browser to review and authorize additional permissions: https://example.com/permissions +Alternatively, you can run "create" with the "--default-permissions" option to continue without authorizing additional permissions. +`, + }, + { + name: "create codespace that requires accepting additional permissions for devcontainer path", + fields: fields{ + apiClient: apiCreateDefaults(&apiClientMock{ + CreateCodespaceFunc: func(ctx context.Context, params *api.CreateCodespaceParams) (*api.Codespace, error) { + if params.Branch != "feature-branch" { + return nil, fmt.Errorf("got branch %q, want %q", params.Branch, "main") + } + if params.IdleTimeoutMinutes != 30 { + return nil, fmt.Errorf("idle timeout minutes was %v", params.IdleTimeoutMinutes) + } + return &api.Codespace{}, api.AcceptPermissionsRequiredError{ + AllowPermissionsURL: "https://example.com/permissions?ref=feature-branch&devcontainer_path=.devcontainer/actions/devcontainer.json", + } + }, + }), + }, + opts: createOptions{ + repo: "monalisa/dotfiles", + branch: "feature-branch", + devContainerPath: ".devcontainer/actions/devcontainer.json", + machine: "GIGA", + showStatus: false, + idleTimeout: 30 * time.Minute, + }, + wantErr: cmdutil.SilentError, + wantStderr: ` â Codespaces usage for this repository is paid for by monalisa +You must authorize or deny additional permissions requested by this codespace before continuing. +Open this URL in your browser to review and authorize additional permissions: https://example.com/permissions?ref=feature-branch&devcontainer_path=.devcontainer/actions/devcontainer.json Alternatively, you can run "create" with the "--default-permissions" option to continue without authorizing additional permissions. `, }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cli-2.34.0/pkg/cmd/release/create/create.go new/cli-2.35.0/pkg/cmd/release/create/create.go --- old/cli-2.34.0/pkg/cmd/release/create/create.go 2023-09-06 09:46:43.000000000 +0200 +++ new/cli-2.35.0/pkg/cmd/release/create/create.go 2023-09-19 10:26:52.000000000 +0200 @@ -59,6 +59,7 @@ GenerateNotes bool NotesStartTag string VerifyTag bool + NotesFromTag bool } func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Command { @@ -92,6 +93,8 @@ To create a release from an annotated git tag, first create one locally with git, push the tag to GitHub, then run this command. + Use %[1]s--notes-from-tag%[1]s to automatically generate the release notes + from the annotated git tag. When using automatically generated release notes, a release title will also be automatically generated unless a title was explicitly passed. Additional release notes can be prepended to @@ -146,9 +149,17 @@ return cmdutil.FlagErrorf("tag required when not running interactively") } + if opts.NotesFromTag && (opts.GenerateNotes || opts.NotesStartTag != "") { + return cmdutil.FlagErrorf("using `--notes-from-tag` with `--generate-notes` or `--notes-start-tag` is not supported") + } + + if opts.NotesFromTag && opts.RepoOverride != "" { + return cmdutil.FlagErrorf("using `--notes-from-tag` with `--repo` is not supported") + } + opts.Concurrency = 5 - opts.BodyProvided = cmd.Flags().Changed("notes") || opts.GenerateNotes + opts.BodyProvided = cmd.Flags().Changed("notes") || opts.GenerateNotes || opts.NotesFromTag if notesFile != "" { b, err := cmdutil.ReadFile(notesFile, opts.IO.In) if err != nil { @@ -176,6 +187,7 @@ cmd.Flags().StringVar(&opts.NotesStartTag, "notes-start-tag", "", "Tag to use as the starting point for generating release notes") cmdutil.NilBoolFlag(cmd, &opts.IsLatest, "latest", "", "Mark this release as \"Latest\" (default: automatic based on date and version)") cmd.Flags().BoolVarP(&opts.VerifyTag, "verify-tag", "", false, "Abort in case the git tag doesn't already exist in the remote repository") + cmd.Flags().BoolVarP(&opts.NotesFromTag, "notes-from-tag", "", false, "Automatically generate notes from annotated tag") _ = cmdutil.RegisterBranchCompletionFlags(f.GitClient, cmd, "target") @@ -241,6 +253,12 @@ var tagDescription string if opts.RepoOverride == "" { tagDescription, _ = gitTagInfo(opts.GitClient, opts.TagName) + + if opts.NotesFromTag && tagDescription == "" { + return fmt.Errorf("cannot generate release notes from tag %s as it does not exist locally", + opts.TagName) + } + // If there is a local tag with the same name as specified // the user may not want to create a new tag on the remote // as the local one might be annotated or signed. @@ -427,6 +445,13 @@ params["generate_release_notes"] = true } } + if opts.NotesFromTag { + if opts.Body == "" { + params["body"] = tagDescription + } else { + params["body"] = fmt.Sprintf("%s\n%s", opts.Body, tagDescription) + } + } hasAssets := len(opts.Assets) > 0 draftWhileUploading := false diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cli-2.34.0/pkg/cmd/release/create/create_test.go new/cli-2.35.0/pkg/cmd/release/create/create_test.go --- old/cli-2.34.0/pkg/cmd/release/create/create_test.go 2023-09-06 09:46:43.000000000 +0200 +++ new/cli-2.35.0/pkg/cmd/release/create/create_test.go 2023-09-19 10:26:52.000000000 +0200 @@ -321,6 +321,30 @@ VerifyTag: true, }, }, + { + name: "with --notes-from-tag", + args: "v1.2.3 --notes-from-tag", + isTTY: false, + want: CreateOptions{ + TagName: "v1.2.3", + BodyProvided: true, + Concurrency: 5, + Assets: []*shared.AssetForUpload(nil), + NotesFromTag: true, + }, + }, + { + name: "with --notes-from-tag and --generate-notes", + args: "v1.2.3 --notes-from-tag --generate-notes", + isTTY: false, + wantErr: "using `--notes-from-tag` with `--generate-notes` or `--notes-start-tag` is not supported", + }, + { + name: "with --notes-from-tag and --notes-start-tag", + args: "v1.2.3 --notes-from-tag --notes-start-tag v1.2.3", + isTTY: false, + wantErr: "using `--notes-from-tag` with `--generate-notes` or `--notes-start-tag` is not supported", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -375,6 +399,7 @@ assert.Equal(t, tt.want.NotesStartTag, opts.NotesStartTag) assert.Equal(t, tt.want.IsLatest, opts.IsLatest) assert.Equal(t, tt.want.VerifyTag, opts.VerifyTag) + assert.Equal(t, tt.want.NotesFromTag, opts.NotesFromTag) require.Equal(t, len(tt.want.Assets), len(opts.Assets)) for i := range tt.want.Assets { @@ -391,6 +416,7 @@ isTTY bool opts CreateOptions httpStubs func(t *testing.T, reg *httpmock.Registry) + runStubs func(rs *run.CommandStubber) wantErr string wantStdout string wantStderr string @@ -405,6 +431,9 @@ BodyProvided: true, Target: "", }, + runStubs: func(rs *run.CommandStubber) { + rs.Register(`git tag --list`, 0, "") + }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.RESTPayload(201, `{ "url": "https://api.github.com/releases/123", @@ -434,6 +463,9 @@ Target: "", DiscussionCategory: "General", }, + runStubs: func(rs *run.CommandStubber) { + rs.Register(`git tag --list`, 0, "") + }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.RESTPayload(201, `{ "url": "https://api.github.com/releases/123", @@ -463,6 +495,9 @@ BodyProvided: true, Target: "main", }, + runStubs: func(rs *run.CommandStubber) { + rs.Register(`git tag --list`, 0, "") + }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.RESTPayload(201, `{ "url": "https://api.github.com/releases/123", @@ -491,6 +526,9 @@ Draft: true, Target: "", }, + runStubs: func(rs *run.CommandStubber) { + rs.Register(`git tag --list`, 0, "") + }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.RESTPayload(201, `{ "url": "https://api.github.com/releases/123", @@ -519,6 +557,9 @@ BodyProvided: true, GenerateNotes: false, }, + runStubs: func(rs *run.CommandStubber) { + rs.Register(`git tag --list`, 0, "") + }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.RESTPayload(201, `{ "url": "https://api.github.com/releases/123", @@ -547,6 +588,9 @@ BodyProvided: true, GenerateNotes: true, }, + runStubs: func(rs *run.CommandStubber) { + rs.Register(`git tag --list`, 0, "") + }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.RESTPayload(201, `{ "url": "https://api.github.com/releases/123", @@ -576,6 +620,9 @@ GenerateNotes: true, NotesStartTag: "v1.1.0", }, + runStubs: func(rs *run.CommandStubber) { + rs.Register(`git tag --list`, 0, "") + }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases/generate-notes"), httpmock.RESTPayload(200, `{ @@ -616,6 +663,9 @@ GenerateNotes: true, NotesStartTag: "v1.1.0", }, + runStubs: func(rs *run.CommandStubber) { + rs.Register(`git tag --list`, 0, "") + }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases/generate-notes"), httpmock.RESTPayload(200, `{ @@ -664,6 +714,9 @@ }, Concurrency: 1, }, + runStubs: func(rs *run.CommandStubber) { + rs.Register(`git tag --list`, 0, "") + }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { reg.Register(httpmock.REST("HEAD", "repos/OWNER/REPO/releases/tags/v1.2.3"), httpmock.StatusStringResponse(404, ``)) reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.RESTPayload(201, `{ @@ -721,6 +774,9 @@ }, Concurrency: 1, }, + runStubs: func(rs *run.CommandStubber) { + rs.Register(`git tag --list`, 0, "") + }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { reg.Register(httpmock.REST("HEAD", "repos/OWNER/REPO/releases/tags/v1.2.3"), httpmock.StatusStringResponse(200, ``)) }, @@ -748,6 +804,9 @@ }, Concurrency: 1, }, + runStubs: func(rs *run.CommandStubber) { + rs.Register(`git tag --list`, 0, "") + }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { reg.Register(httpmock.REST("HEAD", "repos/OWNER/REPO/releases/tags/v1.2.3"), httpmock.StatusStringResponse(404, ``)) reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.StatusStringResponse(201, `{ @@ -782,6 +841,9 @@ }, Concurrency: 1, }, + runStubs: func(rs *run.CommandStubber) { + rs.Register(`git tag --list`, 0, "") + }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { reg.Register(httpmock.REST("HEAD", "repos/OWNER/REPO/releases/tags/v1.2.3"), httpmock.StatusStringResponse(404, ``)) reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.StatusStringResponse(201, `{ @@ -817,6 +879,9 @@ }, Concurrency: 1, }, + runStubs: func(rs *run.CommandStubber) { + rs.Register(`git tag --list`, 0, "") + }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { reg.Register(httpmock.REST("HEAD", "repos/OWNER/REPO/releases/tags/v1.2.3"), httpmock.StatusStringResponse(200, ``)) }, @@ -845,6 +910,9 @@ DiscussionCategory: "general", Concurrency: 1, }, + runStubs: func(rs *run.CommandStubber) { + rs.Register(`git tag --list`, 0, "") + }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { reg.Register(httpmock.REST("HEAD", "repos/OWNER/REPO/releases/tags/v1.2.3"), httpmock.StatusStringResponse(404, ``)) reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.RESTPayload(201, `{ @@ -884,6 +952,94 @@ wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3-final\n", wantStderr: ``, }, + { + name: "with generate notes from tag", + isTTY: false, + opts: CreateOptions{ + TagName: "v1.2.3", + BodyProvided: true, + Concurrency: 5, + Assets: []*shared.AssetForUpload(nil), + NotesFromTag: true, + }, + runStubs: func(rs *run.CommandStubber) { + rs.Register(`git tag --list`, 0, "some tag message") + }, + httpStubs: func(t *testing.T, reg *httpmock.Registry) { + reg.Register( + httpmock.GraphQL("RepositoryFindRef"), + httpmock.StringResponse(`{"data":{"repository":{"ref": {"id": "tag id"}}}}`), + ) + reg.Register( + httpmock.REST("POST", "repos/OWNER/REPO/releases"), + httpmock.RESTPayload(201, `{ + "url": "https://api.github.com/releases/123", + "upload_url": "https://api.github.com/assets/upload", + "html_url": "https://github.com/OWNER/REPO/releases/tag/v1.2.3" + }`, func(payload map[string]interface{}) { + assert.Equal(t, map[string]interface{}{ + "tag_name": "v1.2.3", + "draft": false, + "prerelease": false, + "body": "some tag message", + }, payload) + })) + }, + wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n", + wantStderr: "", + }, + { + name: "with generate notes from tag and notes provided", + isTTY: false, + opts: CreateOptions{ + TagName: "v1.2.3", + Body: "some notes here", + BodyProvided: true, + Concurrency: 5, + Assets: []*shared.AssetForUpload(nil), + NotesFromTag: true, + }, + runStubs: func(rs *run.CommandStubber) { + rs.Register(`git tag --list`, 0, "some tag message") + }, + httpStubs: func(t *testing.T, reg *httpmock.Registry) { + reg.Register( + httpmock.GraphQL("RepositoryFindRef"), + httpmock.StringResponse(`{"data":{"repository":{"ref": {"id": "tag id"}}}}`), + ) + reg.Register( + httpmock.REST("POST", "repos/OWNER/REPO/releases"), + httpmock.RESTPayload(201, `{ + "url": "https://api.github.com/releases/123", + "upload_url": "https://api.github.com/assets/upload", + "html_url": "https://github.com/OWNER/REPO/releases/tag/v1.2.3" + }`, func(payload map[string]interface{}) { + assert.Equal(t, map[string]interface{}{ + "tag_name": "v1.2.3", + "draft": false, + "prerelease": false, + "body": "some notes here\nsome tag message", + }, payload) + })) + }, + wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n", + wantStderr: "", + }, + { + name: "with generate notes from tag and tag does not exist", + isTTY: false, + opts: CreateOptions{ + TagName: "v1.2.3", + BodyProvided: true, + Concurrency: 5, + Assets: []*shared.AssetForUpload(nil), + NotesFromTag: true, + }, + runStubs: func(rs *run.CommandStubber) { + rs.Register(`git tag --list`, 0, "") + }, + wantErr: "cannot generate release notes from tag v1.2.3 as it does not exist locally", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -908,6 +1064,12 @@ tt.opts.GitClient = &git.Client{GitPath: "some/path/git"} + rs, teardown := run.Stub() + defer teardown(t) + if tt.runStubs != nil { + tt.runStubs(rs) + } + err := createRun(&tt.opts) if tt.wantErr != "" { require.EqualError(t, err, tt.wantErr) @@ -1451,34 +1613,34 @@ }, } for _, tt := range tests { - ios, _, stdout, stderr := iostreams.Test() - ios.SetStdoutTTY(true) - ios.SetStdinTTY(true) - ios.SetStderrTTY(true) - tt.opts.IO = ios - - reg := &httpmock.Registry{} - defer reg.Verify(t) - tt.httpStubs(reg) - tt.opts.HttpClient = func() (*http.Client, error) { - return &http.Client{Transport: reg}, nil - } - - tt.opts.BaseRepo = func() (ghrepo.Interface, error) { - return ghrepo.FromFullName("OWNER/REPO") - } - - tt.opts.Config = func() (config.Config, error) { - return config.NewBlankConfig(), nil - } - - tt.opts.Edit = func(_, _, val string, _ io.Reader, _, _ io.Writer) (string, error) { - return val, nil - } + t.Run(tt.name, func(t *testing.T) { + ios, _, stdout, stderr := iostreams.Test() + ios.SetStdoutTTY(true) + ios.SetStdinTTY(true) + ios.SetStderrTTY(true) + tt.opts.IO = ios + + reg := &httpmock.Registry{} + defer reg.Verify(t) + tt.httpStubs(reg) + tt.opts.HttpClient = func() (*http.Client, error) { + return &http.Client{Transport: reg}, nil + } - tt.opts.GitClient = &git.Client{GitPath: "some/path/git"} + tt.opts.BaseRepo = func() (ghrepo.Interface, error) { + return ghrepo.FromFullName("OWNER/REPO") + } + + tt.opts.Config = func() (config.Config, error) { + return config.NewBlankConfig(), nil + } + + tt.opts.Edit = func(_, _, val string, _ io.Reader, _, _ io.Writer) (string, error) { + return val, nil + } + + tt.opts.GitClient = &git.Client{GitPath: "some/path/git"} - t.Run(tt.name, func(t *testing.T) { pm := prompter.NewMockPrompter(t) if tt.prompterStubs != nil { tt.prompterStubs(t, pm) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cli-2.34.0/pkg/cmd/release/delete/delete.go new/cli-2.35.0/pkg/cmd/release/delete/delete.go --- old/cli-2.34.0/pkg/cmd/release/delete/delete.go 2023-09-06 09:46:43.000000000 +0200 +++ new/cli-2.35.0/pkg/cmd/release/delete/delete.go 2023-09-19 10:26:52.000000000 +0200 @@ -6,6 +6,7 @@ "net/http" "github.com/cli/cli/v2/api" + "github.com/cli/cli/v2/git" "github.com/cli/cli/v2/internal/ghinstance" "github.com/cli/cli/v2/internal/ghrepo" "github.com/cli/cli/v2/pkg/cmd/release/shared" @@ -19,10 +20,12 @@ } type DeleteOptions struct { - HttpClient func() (*http.Client, error) - IO *iostreams.IOStreams - BaseRepo func() (ghrepo.Interface, error) - Prompter iprompter + HttpClient func() (*http.Client, error) + GitClient *git.Client + IO *iostreams.IOStreams + BaseRepo func() (ghrepo.Interface, error) + RepoOverride string + Prompter iprompter TagName string SkipConfirm bool @@ -33,6 +36,7 @@ opts := &DeleteOptions{ IO: f.IOStreams, HttpClient: f.HttpClient, + GitClient: f.GitClient, Prompter: f.Prompter, } @@ -43,6 +47,7 @@ RunE: func(cmd *cobra.Command, args []string) error { // support `-R, --repo` override opts.BaseRepo = f.BaseRepo + opts.RepoOverride, _ = cmd.Flags().GetString("repo") opts.TagName = args[0] @@ -93,12 +98,13 @@ } var cleanupMessage string - mustCleanupTag := opts.CleanupTag - if mustCleanupTag { - err = deleteTag(httpClient, baseRepo, release.TagName) - if err != nil { + if opts.CleanupTag { + if err := deleteTag(httpClient, baseRepo, release.TagName); err != nil { return err } + if opts.RepoOverride == "" { + _ = opts.GitClient.DeleteLocalTag(context.Background(), release.TagName) + } cleanupMessage = " and tag" } @@ -108,7 +114,7 @@ iofmt := opts.IO.ColorScheme() fmt.Fprintf(opts.IO.ErrOut, "%s Deleted release%s %s\n", iofmt.SuccessIconWithColor(iofmt.Red), cleanupMessage, release.TagName) - if !release.IsDraft && !mustCleanupTag { + if !release.IsDraft && !opts.CleanupTag { fmt.Fprintf(opts.IO.ErrOut, "%s Note that the %s git tag still remains in the repository\n", iofmt.WarningIcon(), release.TagName) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cli-2.34.0/pkg/cmd/release/delete/delete_test.go new/cli-2.35.0/pkg/cmd/release/delete/delete_test.go --- old/cli-2.34.0/pkg/cmd/release/delete/delete_test.go 2023-09-06 09:46:43.000000000 +0200 +++ new/cli-2.35.0/pkg/cmd/release/delete/delete_test.go 2023-09-19 10:26:52.000000000 +0200 @@ -7,8 +7,10 @@ "testing" "github.com/MakeNowJust/heredoc" + "github.com/cli/cli/v2/git" "github.com/cli/cli/v2/internal/ghrepo" "github.com/cli/cli/v2/internal/prompter" + "github.com/cli/cli/v2/internal/run" "github.com/cli/cli/v2/pkg/cmd/release/shared" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/httpmock" @@ -110,6 +112,7 @@ isTTY bool opts DeleteOptions prompterStubs func(*prompter.PrompterMock) + runStubs func(*run.CommandStubber) wantErr string wantStdout string wantStderr string @@ -164,6 +167,9 @@ SkipConfirm: true, CleanupTag: true, }, + runStubs: func(rs *run.CommandStubber) { + rs.Register(`git tag -d v1.2.3`, 0, "") + }, wantStdout: ``, wantStderr: heredoc.Doc(` â Deleted release and tag v1.2.3 @@ -177,6 +183,9 @@ SkipConfirm: false, CleanupTag: true, }, + runStubs: func(rs *run.CommandStubber) { + rs.Register(`git tag -d v1.2.3`, 0, "") + }, wantStdout: ``, wantStderr: ``, }, @@ -203,6 +212,12 @@ fakeHTTP.Register(httpmock.REST("DELETE", "repos/OWNER/REPO/releases/23456"), httpmock.StatusStringResponse(204, "")) fakeHTTP.Register(httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/tags/v1.2.3"), httpmock.StatusStringResponse(204, "")) + rs, teardown := run.Stub() + defer teardown(t) + if tt.runStubs != nil { + tt.runStubs(rs) + } + tt.opts.IO = ios tt.opts.Prompter = pm tt.opts.HttpClient = func() (*http.Client, error) { @@ -211,6 +226,7 @@ tt.opts.BaseRepo = func() (ghrepo.Interface, error) { return ghrepo.FromFullName("OWNER/REPO") } + tt.opts.GitClient = &git.Client{GitPath: "some/path/git"} err := deleteRun(&tt.opts) if tt.wantErr != "" { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cli-2.34.0/pkg/cmd/repo/list/list.go new/cli-2.35.0/pkg/cmd/repo/list/list.go --- old/cli-2.34.0/pkg/cmd/repo/list/list.go 2023-09-06 09:46:43.000000000 +0200 +++ new/cli-2.35.0/pkg/cmd/repo/list/list.go 2023-09-19 10:26:52.000000000 +0200 @@ -6,6 +6,7 @@ "strings" "time" + "github.com/MakeNowJust/heredoc" "github.com/spf13/cobra" "github.com/cli/cli/v2/api" @@ -52,9 +53,16 @@ ) cmd := &cobra.Command{ - Use: "list [<owner>]", - Args: cobra.MaximumNArgs(1), - Short: "List repositories owned by user or organization", + Use: "list [<owner>]", + Args: cobra.MaximumNArgs(1), + Short: "List repositories owned by user or organization", + Long: heredoc.Docf(` + List repositories owned by a user or organization. + + Note that the list will only include repositories owned by the provided argument, + and the %[1]s--fork%[1]s or %[1]s--source%[1]s flags will not traverse ownership boundaries. For example, + when listing the forks in an organization, the output would not include those owned by individual users. + `, "`"), Aliases: []string{"ls"}, RunE: func(c *cobra.Command, args []string) error { if opts.Limit < 1 { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cli-2.34.0/pkg/httpmock/stub.go new/cli-2.35.0/pkg/httpmock/stub.go --- old/cli-2.34.0/pkg/httpmock/stub.go 2023-09-06 09:46:43.000000000 +0200 +++ new/cli-2.35.0/pkg/httpmock/stub.go 2023-09-19 10:26:52.000000000 +0200 @@ -181,6 +181,7 @@ return httpResponse(responseStatus, req, bytes.NewBufferString(responseBody)), nil } } + func GraphQLMutation(body string, cb func(map[string]interface{})) Responder { return func(req *http.Request) (*http.Response, error) { var bodyData struct { ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/gh/vendor.tar.gz /work/SRC/openSUSE:Factory/.gh.new.1770/vendor.tar.gz differ: char 5, line 1