From 8f3032af3657458f9a4a633b377bc0662acc84e9 Mon Sep 17 00:00:00 2001 From: vilmibm Date: Mon, 12 Sep 2022 15:08:37 -0700 Subject: [PATCH 1/5] use Prompter in auth commands --- pkg/cmd/auth/login/login.go | 3 +-- pkg/cmd/auth/logout/logout.go | 13 +++++------ pkg/cmd/auth/logout/logout_test.go | 30 +++++++++++++------------ pkg/cmd/auth/refresh/refresh.go | 13 +++-------- pkg/cmd/auth/refresh/refresh_test.go | 32 ++++++++++++++------------- pkg/cmd/auth/shared/git_credential.go | 3 +-- pkg/cmd/auth/shared/login_flow.go | 3 +-- pkg/cmd/auth/shared/prompt.go | 10 +++++++++ 8 files changed, 54 insertions(+), 53 deletions(-) create mode 100644 pkg/cmd/auth/shared/prompt.go diff --git a/pkg/cmd/auth/login/login.go b/pkg/cmd/auth/login/login.go index b0d0e02cf..48cb73bca 100644 --- a/pkg/cmd/auth/login/login.go +++ b/pkg/cmd/auth/login/login.go @@ -9,7 +9,6 @@ import ( "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/internal/config" "github.com/cli/cli/v2/internal/ghinstance" - "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/pkg/cmd/auth/shared" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" @@ -20,7 +19,7 @@ type LoginOptions struct { IO *iostreams.IOStreams Config func() (config.Config, error) HttpClient func() (*http.Client, error) - Prompter prompter.Prompter + Prompter shared.Prompt MainExecutable string diff --git a/pkg/cmd/auth/logout/logout.go b/pkg/cmd/auth/logout/logout.go index 029d5971a..98735b2e6 100644 --- a/pkg/cmd/auth/logout/logout.go +++ b/pkg/cmd/auth/logout/logout.go @@ -4,14 +4,12 @@ import ( "fmt" "net/http" - "github.com/AlecAivazis/survey/v2" "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/internal/config" "github.com/cli/cli/v2/pkg/cmd/auth/shared" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" - "github.com/cli/cli/v2/pkg/prompt" "github.com/spf13/cobra" ) @@ -19,6 +17,7 @@ type LogoutOptions struct { HttpClient func() (*http.Client, error) IO *iostreams.IOStreams Config func() (config.Config, error) + Prompter shared.Prompt Hostname string } @@ -27,6 +26,7 @@ func NewCmdLogout(f *cmdutil.Factory, runF func(*LogoutOptions) error) *cobra.Co HttpClient: f.HttpClient, IO: f.IOStreams, Config: f.Config, + Prompter: f.Prompter, } cmd := &cobra.Command{ @@ -79,15 +79,12 @@ func logoutRun(opts *LogoutOptions) error { if len(candidates) == 1 { hostname = candidates[0] } else { - //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter - err = prompt.SurveyAskOne(&survey.Select{ - Message: "What account do you want to log out of?", - Options: candidates, - }, &hostname) - + selected, err := opts.Prompter.Select( + "What account to you want to log out of?", "", candidates) if err != nil { return fmt.Errorf("could not prompt: %w", err) } + hostname = candidates[selected] } } else { var found bool diff --git a/pkg/cmd/auth/logout/logout_test.go b/pkg/cmd/auth/logout/logout_test.go index 71bca8cd7..0a5e32f7c 100644 --- a/pkg/cmd/auth/logout/logout_test.go +++ b/pkg/cmd/auth/logout/logout_test.go @@ -7,10 +7,10 @@ import ( "testing" "github.com/cli/cli/v2/internal/config" + "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/httpmock" "github.com/cli/cli/v2/pkg/iostreams" - "github.com/cli/cli/v2/pkg/prompt" "github.com/google/shlex" "github.com/stretchr/testify/assert" ) @@ -92,21 +92,23 @@ func Test_NewCmdLogout(t *testing.T) { func Test_logoutRun_tty(t *testing.T) { tests := []struct { - name string - opts *LogoutOptions - askStubs func(*prompt.AskStubber) - cfgHosts []string - wantHosts string - wantErrOut *regexp.Regexp - wantErr string + name string + opts *LogoutOptions + prompterStubs func(*prompter.PrompterMock) + cfgHosts []string + wantHosts string + wantErrOut *regexp.Regexp + wantErr string }{ { name: "no arguments, multiple hosts", opts: &LogoutOptions{}, cfgHosts: []string{"cheryl.mason", "github.com"}, wantHosts: "cheryl.mason:\n oauth_token: abc123\n", - askStubs: func(as *prompt.AskStubber) { - as.StubPrompt("What account do you want to log out of?").AnswerWith("github.com") + prompterStubs: func(pm *prompter.PrompterMock) { + pm.SelectFunc = func(_, _ string, opts []string) (int, error) { + return prompter.IndexFor(opts, "github.com") + } }, wantErrOut: regexp.MustCompile(`Logged out of github.com account 'cybilb'`), }, @@ -158,11 +160,11 @@ func Test_logoutRun_tty(t *testing.T) { return &http.Client{Transport: reg}, nil } - //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock - as := prompt.NewAskStubber(t) - if tt.askStubs != nil { - tt.askStubs(as) + pm := &prompter.PrompterMock{} + if tt.prompterStubs != nil { + tt.prompterStubs(pm) } + tt.opts.Prompter = pm err := logoutRun(tt.opts) if tt.wantErr != "" { diff --git a/pkg/cmd/auth/refresh/refresh.go b/pkg/cmd/auth/refresh/refresh.go index 31737e976..7b1142e7e 100644 --- a/pkg/cmd/auth/refresh/refresh.go +++ b/pkg/cmd/auth/refresh/refresh.go @@ -5,15 +5,12 @@ import ( "net/http" "strings" - "github.com/AlecAivazis/survey/v2" "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/internal/authflow" "github.com/cli/cli/v2/internal/config" - "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/pkg/cmd/auth/shared" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" - "github.com/cli/cli/v2/pkg/prompt" "github.com/spf13/cobra" ) @@ -21,7 +18,7 @@ type RefreshOptions struct { IO *iostreams.IOStreams Config func() (config.Config, error) httpClient *http.Client - Prompter prompter.Prompter + Prompter shared.Prompt MainExecutable string @@ -97,15 +94,11 @@ func refreshRun(opts *RefreshOptions) error { if len(candidates) == 1 { hostname = candidates[0] } else { - //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter - err := prompt.SurveyAskOne(&survey.Select{ - Message: "What account do you want to refresh auth for?", - Options: candidates, - }, &hostname) - + selected, err := opts.Prompter.Select("What account do you want to refresh auth for?", "", candidates) if err != nil { return fmt.Errorf("could not prompt: %w", err) } + hostname = candidates[selected] } } else { var found bool diff --git a/pkg/cmd/auth/refresh/refresh_test.go b/pkg/cmd/auth/refresh/refresh_test.go index 0cb754117..ffacb22d9 100644 --- a/pkg/cmd/auth/refresh/refresh_test.go +++ b/pkg/cmd/auth/refresh/refresh_test.go @@ -8,10 +8,10 @@ import ( "testing" "github.com/cli/cli/v2/internal/config" + "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/httpmock" "github.com/cli/cli/v2/pkg/iostreams" - "github.com/cli/cli/v2/pkg/prompt" "github.com/google/shlex" "github.com/stretchr/testify/assert" ) @@ -132,14 +132,14 @@ type authArgs struct { func Test_refreshRun(t *testing.T) { tests := []struct { - name string - opts *RefreshOptions - askStubs func(*prompt.AskStubber) - cfgHosts []string - oldScopes string - wantErr string - nontty bool - wantAuthArgs authArgs + name string + opts *RefreshOptions + prompterStubs func(*prompter.PrompterMock) + cfgHosts []string + oldScopes string + wantErr string + nontty bool + wantAuthArgs authArgs }{ { name: "no hosts configured", @@ -193,8 +193,10 @@ func Test_refreshRun(t *testing.T) { opts: &RefreshOptions{ Hostname: "", }, - askStubs: func(as *prompt.AskStubber) { - as.StubPrompt("What account do you want to refresh auth for?").AnswerWith("github.com") + prompterStubs: func(pm *prompter.PrompterMock) { + pm.SelectFunc = func(_, _ string, opts []string) (int, error) { + return prompter.IndexFor(opts, "github.com") + } }, wantAuthArgs: authArgs{ hostname: "github.com", @@ -272,11 +274,11 @@ func Test_refreshRun(t *testing.T) { ) tt.opts.httpClient = &http.Client{Transport: httpReg} - //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock - as := prompt.NewAskStubber(t) - if tt.askStubs != nil { - tt.askStubs(as) + pm := &prompter.PrompterMock{} + if tt.prompterStubs != nil { + tt.prompterStubs(pm) } + tt.opts.Prompter = pm err := refreshRun(tt.opts) if tt.wantErr != "" { diff --git a/pkg/cmd/auth/shared/git_credential.go b/pkg/cmd/auth/shared/git_credential.go index e6cbe3c29..9c9a1cf6b 100644 --- a/pkg/cmd/auth/shared/git_credential.go +++ b/pkg/cmd/auth/shared/git_credential.go @@ -10,14 +10,13 @@ import ( "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/git" "github.com/cli/cli/v2/internal/ghinstance" - "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/internal/run" "github.com/google/shlex" ) type GitCredentialFlow struct { Executable string - Prompter prompter.Prompter + Prompter Prompt shouldSetup bool helper string diff --git a/pkg/cmd/auth/shared/login_flow.go b/pkg/cmd/auth/shared/login_flow.go index 16386c20c..9cc9d9865 100644 --- a/pkg/cmd/auth/shared/login_flow.go +++ b/pkg/cmd/auth/shared/login_flow.go @@ -10,7 +10,6 @@ import ( "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/internal/authflow" "github.com/cli/cli/v2/internal/ghinstance" - "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/pkg/cmd/ssh-key/add" "github.com/cli/cli/v2/pkg/iostreams" "github.com/cli/cli/v2/pkg/ssh" @@ -34,7 +33,7 @@ type LoginOptions struct { Scopes []string Executable string GitProtocol string - Prompter prompter.Prompter + Prompter Prompt sshContext ssh.Context } diff --git a/pkg/cmd/auth/shared/prompt.go b/pkg/cmd/auth/shared/prompt.go new file mode 100644 index 000000000..c0d47372c --- /dev/null +++ b/pkg/cmd/auth/shared/prompt.go @@ -0,0 +1,10 @@ +package shared + +type Prompt interface { + Select(string, string, []string) (int, error) + Confirm(string, bool) (bool, error) + InputHostname() (string, error) + AuthToken() (string, error) + Input(string, string) (string, error) + Password(string) (string, error) +} From 27294c197f7fcb19a9e151a9116d42d19c5e9b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Thu, 15 Sep 2022 18:25:16 +0200 Subject: [PATCH 2/5] gist clone: fix clone URL for GHE gists Use `HOSTNAME/gist/ID` instead of `gist.HOSTNAME/ID` as clone URL for Enterprise since it's not guaranteed that the `gist.` subdomain is configured for a GHE instance. --- pkg/cmd/gist/clone/clone.go | 9 ++++- pkg/cmd/gist/clone/clone_test.go | 57 ++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/gist/clone/clone.go b/pkg/cmd/gist/clone/clone.go index c1dfee31b..41fa104fa 100644 --- a/pkg/cmd/gist/clone/clone.go +++ b/pkg/cmd/gist/clone/clone.go @@ -7,6 +7,7 @@ import ( "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/git" "github.com/cli/cli/v2/internal/config" + "github.com/cli/cli/v2/internal/ghinstance" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" "github.com/spf13/cobra" @@ -92,9 +93,15 @@ func cloneRun(opts *CloneOptions) error { } func formatRemoteURL(hostname string, gistID string, protocol string) string { + if ghinstance.IsEnterprise(hostname) { + if protocol == "ssh" { + return fmt.Sprintf("git@%s:gist/%s.git", hostname, gistID) + } + return fmt.Sprintf("https://%s/gist/%s.git", hostname, gistID) + } + if protocol == "ssh" { return fmt.Sprintf("git@gist.%s:%s.git", hostname, gistID) } - return fmt.Sprintf("https://gist.%s/%s.git", hostname, gistID) } diff --git a/pkg/cmd/gist/clone/clone_test.go b/pkg/cmd/gist/clone/clone_test.go index ebf9b5c31..ccca76b25 100644 --- a/pkg/cmd/gist/clone/clone_test.go +++ b/pkg/cmd/gist/clone/clone_test.go @@ -116,3 +116,60 @@ func Test_GistClone_flagError(t *testing.T) { t.Errorf("unexpected error %v", err) } } + +func Test_formatRemoteURL(t *testing.T) { + type args struct { + hostname string + gistID string + protocol string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "github.com HTTPS", + args: args{ + hostname: "github.com", + protocol: "https", + gistID: "ID", + }, + want: "https://gist.github.com/ID.git", + }, + { + name: "github.com SSH", + args: args{ + hostname: "github.com", + protocol: "ssh", + gistID: "ID", + }, + want: "git@gist.github.com:ID.git", + }, + { + name: "Enterprise HTTPS", + args: args{ + hostname: "acme.org", + protocol: "https", + gistID: "ID", + }, + want: "https://acme.org/gist/ID.git", + }, + { + name: "Enterprise SSH", + args: args{ + hostname: "acme.org", + protocol: "ssh", + gistID: "ID", + }, + want: "git@acme.org:gist/ID.git", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := formatRemoteURL(tt.args.hostname, tt.args.gistID, tt.args.protocol); got != tt.want { + t.Errorf("formatRemoteURL() = %v, want %v", got, tt.want) + } + }) + } +} From b47ed57694dc08a897ae39115d59bb7eca943d2e Mon Sep 17 00:00:00 2001 From: Ashwin Jeyaseelan <8gitbrix@github.com> Date: Thu, 15 Sep 2022 15:53:28 -0700 Subject: [PATCH 3/5] Revert "Revert "Added functionality for org-level codespaces secrets (#6171)" (#6223)" This reverts commit eaabd35b76ba78c2bac0530fd9cd08156a5da8c1. --- pkg/cmd/secret/delete/delete.go | 2 +- pkg/cmd/secret/delete/delete_test.go | 17 +++++++++++++++++ pkg/cmd/secret/list/list.go | 2 +- pkg/cmd/secret/secret.go | 2 +- pkg/cmd/secret/set/set.go | 2 +- pkg/cmd/secret/set/set_test.go | 12 ++++++++++++ pkg/cmd/secret/shared/shared.go | 2 +- pkg/cmd/secret/shared/shared_test.go | 2 +- 8 files changed, 35 insertions(+), 6 deletions(-) diff --git a/pkg/cmd/secret/delete/delete.go b/pkg/cmd/secret/delete/delete.go index bf1eb5c77..4759dd1a2 100644 --- a/pkg/cmd/secret/delete/delete.go +++ b/pkg/cmd/secret/delete/delete.go @@ -41,7 +41,7 @@ func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Co Delete a secret on one of the following levels: - repository (default): available to Actions runs or Dependabot in a repository - environment: available to Actions runs for a deployment environment in a repository - - organization: available to Actions runs or Dependabot within an organization + - organization: available to Actions runs, Dependabot, or Codespaces within an organization - user: available to Codespaces for your user `), Args: cobra.ExactArgs(1), diff --git a/pkg/cmd/secret/delete/delete_test.go b/pkg/cmd/secret/delete/delete_test.go index 30030b104..3e4108703 100644 --- a/pkg/cmd/secret/delete/delete_test.go +++ b/pkg/cmd/secret/delete/delete_test.go @@ -73,6 +73,15 @@ func TestNewCmdDelete(t *testing.T) { Application: "Dependabot", }, }, + { + name: "Codespaces org", + cli: "cool --app codespaces --org UmbrellaCorporation", + wants: DeleteOptions{ + SecretName: "cool", + OrgName: "UmbrellaCorporation", + Application: "Codespaces", + }, + }, } for _, tt := range tests { @@ -219,6 +228,14 @@ func Test_removeRun_org(t *testing.T) { }, wantPath: "orgs/UmbrellaCorporation/dependabot/secrets/tVirus", }, + { + name: "Codespaces org", + opts: &DeleteOptions{ + Application: "codespaces", + OrgName: "UmbrellaCorporation", + }, + wantPath: "orgs/UmbrellaCorporation/codespaces/secrets/tVirus", + }, } for _, tt := range tests { diff --git a/pkg/cmd/secret/list/list.go b/pkg/cmd/secret/list/list.go index 2a3d5a100..a7336570c 100644 --- a/pkg/cmd/secret/list/list.go +++ b/pkg/cmd/secret/list/list.go @@ -46,7 +46,7 @@ func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Comman List secrets on one of the following levels: - repository (default): available to Actions runs or Dependabot in a repository - environment: available to Actions runs for a deployment environment in a repository - - organization: available to Actions runs or Dependabot within an organization + - organization: available to Actions runs, Dependabot, or Codespaces within an organization - user: available to Codespaces for your user `), Aliases: []string{"ls"}, diff --git a/pkg/cmd/secret/secret.go b/pkg/cmd/secret/secret.go index 8e331e813..ea8d6f40d 100644 --- a/pkg/cmd/secret/secret.go +++ b/pkg/cmd/secret/secret.go @@ -15,7 +15,7 @@ func NewCmdSecret(f *cmdutil.Factory) *cobra.Command { Short: "Manage GitHub secrets", Long: heredoc.Doc(` Secrets can be set at the repository, or organization level for use in - GitHub Actions or Dependabot. User and repository secrets can be set for + GitHub Actions or Dependabot. User, organization, and repository secrets can be set for use in GitHub Codespaces. Environment secrets can be set for use in GitHub Actions. Run "gh help secret set" to learn how to get started. `), diff --git a/pkg/cmd/secret/set/set.go b/pkg/cmd/secret/set/set.go index 2ba90b888..d56b3d074 100644 --- a/pkg/cmd/secret/set/set.go +++ b/pkg/cmd/secret/set/set.go @@ -58,7 +58,7 @@ func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command Set a value for a secret on one of the following levels: - repository (default): available to Actions runs or Dependabot in a repository - environment: available to Actions runs for a deployment environment in a repository - - organization: available to Actions runs or Dependabot within an organization + - organization: available to Actions runs, Dependabot, or Codespaces within an organization - user: available to Codespaces for your user Organization and user secrets can optionally be restricted to only be available to diff --git a/pkg/cmd/secret/set/set_test.go b/pkg/cmd/secret/set/set_test.go index ee86f1d10..d9313f81f 100644 --- a/pkg/cmd/secret/set/set_test.go +++ b/pkg/cmd/secret/set/set_test.go @@ -165,6 +165,18 @@ func TestNewCmdSet(t *testing.T) { Application: "Dependabot", }, }, + { + name: "Codespaces org", + cli: `random_secret -ocoolOrg -b"random value" -vselected -r"coolRepo,cli/cli" -aCodespaces`, + wants: SetOptions{ + SecretName: "random_secret", + Visibility: shared.Selected, + RepositoryNames: []string{"coolRepo", "cli/cli"}, + Body: "random value", + OrgName: "coolOrg", + Application: "Codespaces", + }, + }, } for _, tt := range tests { diff --git a/pkg/cmd/secret/shared/shared.go b/pkg/cmd/secret/shared/shared.go index 14ecd293a..9fe687416 100644 --- a/pkg/cmd/secret/shared/shared.go +++ b/pkg/cmd/secret/shared/shared.go @@ -85,7 +85,7 @@ func IsSupportedSecretEntity(app App, entity SecretEntity) bool { case Actions: return entity == Repository || entity == Organization || entity == Environment case Codespaces: - return entity == User || entity == Repository + return entity == User || entity == Organization || entity == Repository case Dependabot: return entity == Repository || entity == Organization default: diff --git a/pkg/cmd/secret/shared/shared_test.go b/pkg/cmd/secret/shared/shared_test.go index 4b534522f..eb121f0a8 100644 --- a/pkg/cmd/secret/shared/shared_test.go +++ b/pkg/cmd/secret/shared/shared_test.go @@ -167,9 +167,9 @@ func TestIsSupportedSecretEntity(t *testing.T) { supportedEntities: []SecretEntity{ User, Repository, + Organization, }, unsupportedEntities: []SecretEntity{ - Organization, Environment, Unknown, }, From de83df12d272966d8ab18b4cb42721473113097d Mon Sep 17 00:00:00 2001 From: Sam Coe Date: Mon, 19 Sep 2022 14:54:18 +0400 Subject: [PATCH 4/5] Stub out time.Now for search tests (#6299) --- pkg/cmd/search/repos/repos.go | 12 ++++++++---- pkg/cmd/search/repos/repos_test.go | 4 +++- pkg/cmd/search/shared/shared.go | 10 +++++++--- pkg/cmd/search/shared/shared_test.go | 7 ++++--- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/pkg/cmd/search/repos/repos.go b/pkg/cmd/search/repos/repos.go index 412b36cbb..23e5bfbef 100644 --- a/pkg/cmd/search/repos/repos.go +++ b/pkg/cmd/search/repos/repos.go @@ -20,6 +20,7 @@ type ReposOptions struct { Browser browser.Browser Exporter cmdutil.Exporter IO *iostreams.IOStreams + Now time.Time Query search.Query Searcher search.Searcher WebMode bool @@ -148,10 +149,14 @@ func reposRun(opts *ReposOptions) error { if len(result.Items) == 0 { return cmdutil.NewNoResultsError("no repositories matched your search") } - return displayResults(io, result) + + return displayResults(io, opts.Now, result) } -func displayResults(io *iostreams.IOStreams, results search.RepositoriesResult) error { +func displayResults(io *iostreams.IOStreams, now time.Time, results search.RepositoriesResult) error { + if now.IsZero() { + now = time.Now() + } cs := io.ColorScheme() tp := utils.NewTablePrinter(io) for _, repo := range results.Items { @@ -172,13 +177,12 @@ func displayResults(io *iostreams.IOStreams, results search.RepositoriesResult) tp.AddField(text.RemoveExcessiveWhitespace(description), nil, nil) tp.AddField(info, nil, infoColor) if tp.IsTTY() { - tp.AddField(text.FuzzyAgoAbbr(time.Now(), repo.UpdatedAt), nil, cs.Gray) + tp.AddField(text.FuzzyAgoAbbr(now, repo.UpdatedAt), nil, cs.Gray) } else { tp.AddField(repo.UpdatedAt.Format(time.RFC3339), nil, nil) } tp.EndRow() } - if io.IsStdoutTTY() { header := fmt.Sprintf("Showing %d of %d repositories\n\n", len(results.Items), results.Total) fmt.Fprintf(io.Out, "\n%s", header) diff --git a/pkg/cmd/search/repos/repos_test.go b/pkg/cmd/search/repos/repos_test.go index 010ee84cc..d0410b196 100644 --- a/pkg/cmd/search/repos/repos_test.go +++ b/pkg/cmd/search/repos/repos_test.go @@ -149,6 +149,8 @@ func TestNewCmdRepos(t *testing.T) { } func TestReposRun(t *testing.T) { + var now = time.Date(2022, 2, 28, 12, 30, 0, 0, time.UTC) + var updatedAt = time.Date(2021, 2, 28, 12, 30, 0, 0, time.UTC) var query = search.Query{ Keywords: []string{"cli"}, Kind: "repositories", @@ -158,7 +160,6 @@ func TestReposRun(t *testing.T) { Topic: []string{"golang"}, }, } - var updatedAt = time.Date(2021, 2, 28, 12, 30, 0, 0, time.UTC) tests := []struct { errMsg string name string @@ -270,6 +271,7 @@ func TestReposRun(t *testing.T) { ios.SetStdoutTTY(tt.tty) ios.SetStderrTTY(tt.tty) tt.opts.IO = ios + tt.opts.Now = now t.Run(tt.name, func(t *testing.T) { err := reposRun(tt.opts) if tt.wantErr { diff --git a/pkg/cmd/search/shared/shared.go b/pkg/cmd/search/shared/shared.go index 7531cfedf..f670548b3 100644 --- a/pkg/cmd/search/shared/shared.go +++ b/pkg/cmd/search/shared/shared.go @@ -31,6 +31,7 @@ type IssuesOptions struct { Entity EntityType Exporter cmdutil.Exporter IO *iostreams.IOStreams + Now time.Time Query search.Query Searcher search.Searcher WebMode bool @@ -88,10 +89,13 @@ func SearchIssues(opts *IssuesOptions) error { return cmdutil.NewNoResultsError(msg) } - return displayIssueResults(io, opts.Entity, result) + return displayIssueResults(io, opts.Now, opts.Entity, result) } -func displayIssueResults(io *iostreams.IOStreams, et EntityType, results search.IssuesResult) error { +func displayIssueResults(io *iostreams.IOStreams, now time.Time, et EntityType, results search.IssuesResult) error { + if now.IsZero() { + now = time.Now() + } cs := io.ColorScheme() tp := utils.NewTablePrinter(io) for _, issue := range results.Items { @@ -120,7 +124,7 @@ func displayIssueResults(io *iostreams.IOStreams, et EntityType, results search. tp.AddField(text.RemoveExcessiveWhitespace(issue.Title), nil, nil) tp.AddField(listIssueLabels(&issue, cs, tp.IsTTY()), nil, nil) if tp.IsTTY() { - tp.AddField(text.FuzzyAgo(time.Now(), issue.UpdatedAt), nil, cs.Gray) + tp.AddField(text.FuzzyAgo(now, issue.UpdatedAt), nil, cs.Gray) } else { tp.AddField(issue.UpdatedAt.String(), nil, nil) } diff --git a/pkg/cmd/search/shared/shared_test.go b/pkg/cmd/search/shared/shared_test.go index 66c9c9cc2..fe6c8c57b 100644 --- a/pkg/cmd/search/shared/shared_test.go +++ b/pkg/cmd/search/shared/shared_test.go @@ -23,7 +23,9 @@ func TestSearcher(t *testing.T) { } func TestSearchIssues(t *testing.T) { - query := search.Query{ + var now = time.Date(2022, 2, 28, 12, 30, 0, 0, time.UTC) + var updatedAt = time.Date(2021, 2, 28, 12, 30, 0, 0, time.UTC) + var query = search.Query{ Keywords: []string{"keyword"}, Kind: "issues", Limit: 30, @@ -33,8 +35,6 @@ func TestSearchIssues(t *testing.T) { Is: []string{"public", "locked"}, }, } - - var updatedAt = time.Date(2021, 2, 28, 12, 30, 0, 0, time.UTC) tests := []struct { errMsg string name string @@ -193,6 +193,7 @@ func TestSearchIssues(t *testing.T) { ios.SetStdoutTTY(tt.tty) ios.SetStderrTTY(tt.tty) tt.opts.IO = ios + tt.opts.Now = now t.Run(tt.name, func(t *testing.T) { err := SearchIssues(tt.opts) if tt.wantErr { From 6e9974c2ef8508b2a0332a0a76819a94d602992a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Mon, 19 Sep 2022 15:50:51 +0200 Subject: [PATCH 5/5] Escape liquid tags in site docs It is good practice to wrap all dynamic content in `{% raw %}..{% endraw %}` Liquid tags so that no syntax within can mistakenly get interpreted as Liquid tags. Fixes rendering of `gh auth login` help page. --- internal/docs/markdown.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/docs/markdown.go b/internal/docs/markdown.go index 74f505a6b..e33df5f45 100644 --- a/internal/docs/markdown.go +++ b/internal/docs/markdown.go @@ -85,6 +85,7 @@ func GenMarkdown(cmd *cobra.Command, w io.Writer) error { // GenMarkdownCustom creates custom markdown output. func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) string) error { + fmt.Fprint(w, "{% raw %}") fmt.Fprintf(w, "## %s\n\n", cmd.CommandPath()) hasLong := cmd.Long != "" @@ -112,6 +113,7 @@ func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) if err := printOptions(w, cmd); err != nil { return err } + fmt.Fprint(w, "{% endraw %}\n") if len(cmd.Example) > 0 { fmt.Fprint(w, "### Examples\n\n{% highlight bash %}{% raw %}\n")