Merge remote-tracking branch 'origin' into fix-actions-help
This commit is contained in:
commit
8dd1e12f64
9 changed files with 568 additions and 99 deletions
18
README.md
18
README.md
|
|
@ -14,13 +14,12 @@ GitHub CLI is available for repositories hosted on GitHub.com and GitHub Enterpr
|
|||
|
||||
If anything feels off, or if you feel that some functionality is missing, please check out the [contributing page][contributing]. There you will find instructions for sharing your feedback, building the tool locally, and submitting pull requests to the project.
|
||||
|
||||
|
||||
<!-- this anchor is linked to from elsewhere, so avoid renaming it -->
|
||||
## Installation
|
||||
|
||||
### macOS
|
||||
|
||||
`gh` is available via [Homebrew][], [MacPorts][], and as a downloadable binary from the [releases page][].
|
||||
`gh` is available via [Homebrew][], [MacPorts][], [Conda][], and as a downloadable binary from the [releases page][].
|
||||
|
||||
#### Homebrew
|
||||
|
||||
|
|
@ -34,16 +33,23 @@ If anything feels off, or if you feel that some functionality is missing, please
|
|||
| ---------------------- | ---------------------------------------------- |
|
||||
| `sudo port install gh` | `sudo port selfupdate && sudo port upgrade gh` |
|
||||
|
||||
#### Conda
|
||||
|
||||
| Install: | Upgrade: |
|
||||
|------------------------------------------|-----------------------------------------|
|
||||
| `conda install gh --channel conda-forge` | `conda update gh --channel conda-forge` |
|
||||
|
||||
Additional Conda installation options available on the [gh-feedstock page](https://github.com/conda-forge/gh-feedstock#installing-gh).
|
||||
|
||||
### Linux
|
||||
|
||||
`gh` is available via [Homebrew](#homebrew), and as downloadable binaries from the [releases page][].
|
||||
`gh` is available via [Homebrew](#homebrew), [Conda](#Conda), and as downloadable binaries from the [releases page][].
|
||||
|
||||
For more information and distro-specific instructions, see the [Linux installation docs](./docs/install_linux.md).
|
||||
|
||||
### Windows
|
||||
|
||||
`gh` is available via [WinGet][], [scoop][], [Chocolatey][], and as downloadable MSI.
|
||||
|
||||
`gh` is available via [WinGet][], [scoop][], [Chocolatey][], [Conda](#Conda), and as downloadable MSI.
|
||||
|
||||
#### WinGet
|
||||
|
||||
|
|
@ -86,13 +92,13 @@ what an official GitHub CLI tool can look like with a fundamentally different de
|
|||
tools bring GitHub to the terminal, `hub` behaves as a proxy to `git`, and `gh` is a standalone
|
||||
tool. Check out our [more detailed explanation][gh-vs-hub] to learn more.
|
||||
|
||||
|
||||
[manual]: https://cli.github.com/manual/
|
||||
[Homebrew]: https://brew.sh
|
||||
[MacPorts]: https://www.macports.org
|
||||
[winget]: https://github.com/microsoft/winget-cli
|
||||
[scoop]: https://scoop.sh
|
||||
[Chocolatey]: https://chocolatey.org
|
||||
[Conda]: https://docs.conda.io/en/latest/
|
||||
[releases page]: https://github.com/cli/cli/releases/latest
|
||||
[hub]: https://github.com/github/hub
|
||||
[contributing]: ./.github/CONTRIBUTING.md
|
||||
|
|
|
|||
|
|
@ -93,15 +93,6 @@ func mainRun() exitCode {
|
|||
return exitError
|
||||
}
|
||||
|
||||
if prompt, _ := cfg.Get("", "prompt"); prompt == "disabled" {
|
||||
cmdFactory.IOStreams.SetNeverPrompt(true)
|
||||
}
|
||||
if ghPager, ghPagerExists := os.LookupEnv("GH_PAGER"); ghPagerExists {
|
||||
cmdFactory.IOStreams.SetPager(ghPager)
|
||||
} else if pager, _ := cfg.Get("", "pager"); pager != "" {
|
||||
cmdFactory.IOStreams.SetPager(pager)
|
||||
}
|
||||
|
||||
// TODO: remove after FromFullName has been revisited
|
||||
if host, err := cfg.DefaultHost(); err == nil {
|
||||
ghrepo.SetDefaultHost(host)
|
||||
|
|
@ -181,15 +172,7 @@ func mainRun() exitCode {
|
|||
// enable `--repo` override
|
||||
if cmd.Flags().Lookup("repo") != nil {
|
||||
repoOverride, _ := cmd.Flags().GetString("repo")
|
||||
if repoFromEnv := os.Getenv("GH_REPO"); repoOverride == "" && repoFromEnv != "" {
|
||||
repoOverride = repoFromEnv
|
||||
}
|
||||
if repoOverride != "" {
|
||||
// FIXME: reimplement the repo override to avoid having to mutate the factory
|
||||
cmdFactory.BaseRepo = func() (ghrepo.Interface, error) {
|
||||
return ghrepo.FromFullName(repoOverride)
|
||||
}
|
||||
}
|
||||
cmdFactory.BaseRepo = cmdutil.OverrideBaseRepoFunc(cmdFactory, repoOverride)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/context"
|
||||
"github.com/cli/cli/git"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
|
|
@ -14,11 +16,93 @@ import (
|
|||
)
|
||||
|
||||
func New(appVersion string) *cmdutil.Factory {
|
||||
io := iostreams.System()
|
||||
f := &cmdutil.Factory{
|
||||
Config: configFunc(), // No factory dependencies
|
||||
Branch: branchFunc(), // No factory dependencies
|
||||
Executable: executable(), // No factory dependencies
|
||||
}
|
||||
|
||||
f.IOStreams = ioStreams(f) // Depends on Config
|
||||
f.HttpClient = httpClientFunc(f, appVersion) // Depends on Config, IOStreams, and appVersion
|
||||
f.Remotes = remotesFunc(f) // Depends on Config
|
||||
f.BaseRepo = BaseRepoFunc(f) // Depends on Remotes
|
||||
f.Browser = browser(f) // Depends on IOStreams
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func BaseRepoFunc(f *cmdutil.Factory) func() (ghrepo.Interface, error) {
|
||||
return func() (ghrepo.Interface, error) {
|
||||
remotes, err := f.Remotes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return remotes[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
func SmartBaseRepoFunc(f *cmdutil.Factory) func() (ghrepo.Interface, error) {
|
||||
return func() (ghrepo.Interface, error) {
|
||||
httpClient, err := f.HttpClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiClient := api.NewClientFromHTTP(httpClient)
|
||||
|
||||
remotes, err := f.Remotes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repoContext, err := context.ResolveRemotesToRepos(remotes, apiClient, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baseRepo, err := repoContext.BaseRepo(f.IOStreams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return baseRepo, nil
|
||||
}
|
||||
}
|
||||
|
||||
func remotesFunc(f *cmdutil.Factory) func() (context.Remotes, error) {
|
||||
rr := &remoteResolver{
|
||||
readRemotes: git.Remotes,
|
||||
getConfig: f.Config,
|
||||
}
|
||||
return rr.Resolver()
|
||||
}
|
||||
|
||||
func httpClientFunc(f *cmdutil.Factory, appVersion string) func() (*http.Client, error) {
|
||||
return func() (*http.Client, error) {
|
||||
io := f.IOStreams
|
||||
cfg, err := f.Config()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewHTTPClient(io, cfg, appVersion, true), nil
|
||||
}
|
||||
}
|
||||
|
||||
func browser(f *cmdutil.Factory) cmdutil.Browser {
|
||||
io := f.IOStreams
|
||||
return cmdutil.NewBrowser(os.Getenv("BROWSER"), io.Out, io.ErrOut)
|
||||
}
|
||||
|
||||
func executable() string {
|
||||
gh := "gh"
|
||||
if exe, err := os.Executable(); err == nil {
|
||||
gh = exe
|
||||
}
|
||||
return gh
|
||||
}
|
||||
|
||||
func configFunc() func() (config.Config, error) {
|
||||
var cachedConfig config.Config
|
||||
var configError error
|
||||
configFunc := func() (config.Config, error) {
|
||||
return func() (config.Config, error) {
|
||||
if cachedConfig != nil || configError != nil {
|
||||
return cachedConfig, configError
|
||||
}
|
||||
|
|
@ -30,45 +114,38 @@ func New(appVersion string) *cmdutil.Factory {
|
|||
cachedConfig = config.InheritEnv(cachedConfig)
|
||||
return cachedConfig, configError
|
||||
}
|
||||
}
|
||||
|
||||
rr := &remoteResolver{
|
||||
readRemotes: git.Remotes,
|
||||
getConfig: configFunc,
|
||||
}
|
||||
remotesFunc := rr.Resolver()
|
||||
|
||||
ghExecutable := "gh"
|
||||
if exe, err := os.Executable(); err == nil {
|
||||
ghExecutable = exe
|
||||
}
|
||||
|
||||
return &cmdutil.Factory{
|
||||
IOStreams: io,
|
||||
Config: configFunc,
|
||||
Remotes: remotesFunc,
|
||||
HttpClient: func() (*http.Client, error) {
|
||||
cfg, err := configFunc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewHTTPClient(io, cfg, appVersion, true), nil
|
||||
},
|
||||
BaseRepo: func() (ghrepo.Interface, error) {
|
||||
remotes, err := remotesFunc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return remotes[0], nil
|
||||
},
|
||||
Branch: func() (string, error) {
|
||||
currentBranch, err := git.CurrentBranch()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not determine current branch: %w", err)
|
||||
}
|
||||
return currentBranch, nil
|
||||
},
|
||||
Executable: ghExecutable,
|
||||
Browser: cmdutil.NewBrowser(os.Getenv("BROWSER"), io.Out, io.ErrOut),
|
||||
func branchFunc() func() (string, error) {
|
||||
return func() (string, error) {
|
||||
currentBranch, err := git.CurrentBranch()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not determine current branch: %w", err)
|
||||
}
|
||||
return currentBranch, nil
|
||||
}
|
||||
}
|
||||
|
||||
func ioStreams(f *cmdutil.Factory) *iostreams.IOStreams {
|
||||
io := iostreams.System()
|
||||
cfg, err := f.Config()
|
||||
if err != nil {
|
||||
return io
|
||||
}
|
||||
|
||||
if prompt, _ := cfg.Get("", "prompt"); prompt == "disabled" {
|
||||
io.SetNeverPrompt(true)
|
||||
}
|
||||
|
||||
// Pager precedence
|
||||
// 1. GH_PAGER
|
||||
// 2. pager from config
|
||||
// 3. PAGER
|
||||
if ghPager, ghPagerExists := os.LookupEnv("GH_PAGER"); ghPagerExists {
|
||||
io.SetPager(ghPager)
|
||||
} else if pager, _ := cfg.Get("", "pager"); pager != "" {
|
||||
io.SetPager(pager)
|
||||
}
|
||||
|
||||
return io
|
||||
}
|
||||
|
|
|
|||
391
pkg/cmd/factory/default_test.go
Normal file
391
pkg/cmd/factory/default_test.go
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
package factory
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/git"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_BaseRepo(t *testing.T) {
|
||||
orig_GH_HOST := os.Getenv("GH_HOST")
|
||||
t.Cleanup(func() {
|
||||
os.Setenv("GH_HOST", orig_GH_HOST)
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
remotes git.RemoteSet
|
||||
config config.Config
|
||||
override string
|
||||
wantsErr bool
|
||||
wantsName string
|
||||
wantsOwner string
|
||||
wantsHost string
|
||||
}{
|
||||
{
|
||||
name: "matching remote",
|
||||
remotes: git.RemoteSet{
|
||||
git.NewRemote("origin", "https://nonsense.com/owner/repo.git"),
|
||||
},
|
||||
config: defaultConfig(),
|
||||
wantsName: "repo",
|
||||
wantsOwner: "owner",
|
||||
wantsHost: "nonsense.com",
|
||||
},
|
||||
{
|
||||
name: "no matching remote",
|
||||
remotes: git.RemoteSet{
|
||||
git.NewRemote("origin", "https://test.com/owner/repo.git"),
|
||||
},
|
||||
config: defaultConfig(),
|
||||
wantsErr: true,
|
||||
},
|
||||
{
|
||||
name: "override with matching remote",
|
||||
remotes: git.RemoteSet{
|
||||
git.NewRemote("origin", "https://test.com/owner/repo.git"),
|
||||
},
|
||||
config: defaultConfig(),
|
||||
override: "test.com",
|
||||
wantsName: "repo",
|
||||
wantsOwner: "owner",
|
||||
wantsHost: "test.com",
|
||||
},
|
||||
{
|
||||
name: "override with no matching remote",
|
||||
remotes: git.RemoteSet{
|
||||
git.NewRemote("origin", "https://nonsense.com/owner/repo.git"),
|
||||
},
|
||||
config: defaultConfig(),
|
||||
override: "test.com",
|
||||
wantsErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.override != "" {
|
||||
os.Setenv("GH_HOST", tt.override)
|
||||
} else {
|
||||
os.Unsetenv("GH_HOST")
|
||||
}
|
||||
f := New("1")
|
||||
rr := &remoteResolver{
|
||||
readRemotes: func() (git.RemoteSet, error) {
|
||||
return tt.remotes, nil
|
||||
},
|
||||
getConfig: func() (config.Config, error) {
|
||||
return tt.config, nil
|
||||
},
|
||||
}
|
||||
f.Remotes = rr.Resolver()
|
||||
f.BaseRepo = BaseRepoFunc(f)
|
||||
repo, err := f.BaseRepo()
|
||||
if tt.wantsErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantsName, repo.RepoName())
|
||||
assert.Equal(t, tt.wantsOwner, repo.RepoOwner())
|
||||
assert.Equal(t, tt.wantsHost, repo.RepoHost())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_SmartBaseRepo(t *testing.T) {
|
||||
pu, _ := url.Parse("https://test.com/newowner/newrepo.git")
|
||||
orig_GH_HOST := os.Getenv("GH_HOST")
|
||||
t.Cleanup(func() {
|
||||
os.Setenv("GH_HOST", orig_GH_HOST)
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
remotes git.RemoteSet
|
||||
config config.Config
|
||||
override string
|
||||
wantsErr bool
|
||||
wantsName string
|
||||
wantsOwner string
|
||||
wantsHost string
|
||||
}{
|
||||
{
|
||||
name: "override with matching remote",
|
||||
remotes: git.RemoteSet{
|
||||
git.NewRemote("origin", "https://test.com/owner/repo.git"),
|
||||
},
|
||||
config: defaultConfig(),
|
||||
override: "test.com",
|
||||
wantsName: "repo",
|
||||
wantsOwner: "owner",
|
||||
wantsHost: "test.com",
|
||||
},
|
||||
{
|
||||
name: "override with matching remote and base resolution",
|
||||
remotes: git.RemoteSet{
|
||||
&git.Remote{Name: "origin",
|
||||
Resolved: "base",
|
||||
FetchURL: pu,
|
||||
PushURL: pu},
|
||||
},
|
||||
config: defaultConfig(),
|
||||
override: "test.com",
|
||||
wantsName: "newrepo",
|
||||
wantsOwner: "newowner",
|
||||
wantsHost: "test.com",
|
||||
},
|
||||
{
|
||||
name: "override with matching remote and nonbase resolution",
|
||||
remotes: git.RemoteSet{
|
||||
&git.Remote{Name: "origin",
|
||||
Resolved: "johnny/test",
|
||||
FetchURL: pu,
|
||||
PushURL: pu},
|
||||
},
|
||||
config: defaultConfig(),
|
||||
override: "test.com",
|
||||
wantsName: "test",
|
||||
wantsOwner: "johnny",
|
||||
wantsHost: "test.com",
|
||||
},
|
||||
{
|
||||
name: "override with no matching remote",
|
||||
remotes: git.RemoteSet{
|
||||
git.NewRemote("origin", "https://example.com/owner/repo.git"),
|
||||
},
|
||||
config: defaultConfig(),
|
||||
override: "test.com",
|
||||
wantsErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.override != "" {
|
||||
os.Setenv("GH_HOST", tt.override)
|
||||
} else {
|
||||
os.Unsetenv("GH_HOST")
|
||||
}
|
||||
f := New("1")
|
||||
rr := &remoteResolver{
|
||||
readRemotes: func() (git.RemoteSet, error) {
|
||||
return tt.remotes, nil
|
||||
},
|
||||
getConfig: func() (config.Config, error) {
|
||||
return tt.config, nil
|
||||
},
|
||||
}
|
||||
f.Remotes = rr.Resolver()
|
||||
f.BaseRepo = SmartBaseRepoFunc(f)
|
||||
repo, err := f.BaseRepo()
|
||||
if tt.wantsErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantsName, repo.RepoName())
|
||||
assert.Equal(t, tt.wantsOwner, repo.RepoOwner())
|
||||
assert.Equal(t, tt.wantsHost, repo.RepoHost())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Defined in pkg/cmdutil/repo_override.go but test it along with other BaseRepo functions
|
||||
func Test_OverrideBaseRepo(t *testing.T) {
|
||||
orig_GH_HOST := os.Getenv("GH_REPO")
|
||||
t.Cleanup(func() {
|
||||
os.Setenv("GH_REPO", orig_GH_HOST)
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
remotes git.RemoteSet
|
||||
config config.Config
|
||||
envOverride string
|
||||
argOverride string
|
||||
wantsErr bool
|
||||
wantsName string
|
||||
wantsOwner string
|
||||
wantsHost string
|
||||
}{
|
||||
{
|
||||
name: "override from argument",
|
||||
argOverride: "override/test",
|
||||
wantsHost: "github.com",
|
||||
wantsOwner: "override",
|
||||
wantsName: "test",
|
||||
},
|
||||
{
|
||||
name: "override from environment",
|
||||
envOverride: "somehost.com/override/test",
|
||||
wantsHost: "somehost.com",
|
||||
wantsOwner: "override",
|
||||
wantsName: "test",
|
||||
},
|
||||
{
|
||||
name: "no override",
|
||||
remotes: git.RemoteSet{
|
||||
git.NewRemote("origin", "https://nonsense.com/owner/repo.git"),
|
||||
},
|
||||
config: defaultConfig(),
|
||||
wantsHost: "nonsense.com",
|
||||
wantsOwner: "owner",
|
||||
wantsName: "repo",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.envOverride != "" {
|
||||
os.Setenv("GH_REPO", tt.envOverride)
|
||||
} else {
|
||||
os.Unsetenv("GH_REPO")
|
||||
}
|
||||
f := New("1")
|
||||
rr := &remoteResolver{
|
||||
readRemotes: func() (git.RemoteSet, error) {
|
||||
return tt.remotes, nil
|
||||
},
|
||||
getConfig: func() (config.Config, error) {
|
||||
return tt.config, nil
|
||||
},
|
||||
}
|
||||
f.Remotes = rr.Resolver()
|
||||
f.BaseRepo = cmdutil.OverrideBaseRepoFunc(f, tt.argOverride)
|
||||
repo, err := f.BaseRepo()
|
||||
if tt.wantsErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantsName, repo.RepoName())
|
||||
assert.Equal(t, tt.wantsOwner, repo.RepoOwner())
|
||||
assert.Equal(t, tt.wantsHost, repo.RepoHost())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ioStreams_pager(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
env map[string]string
|
||||
config config.Config
|
||||
wantPager string
|
||||
}{
|
||||
{
|
||||
name: "GH_PAGER and PAGER set",
|
||||
env: map[string]string{
|
||||
"GH_PAGER": "GH_PAGER",
|
||||
"PAGER": "PAGER",
|
||||
},
|
||||
wantPager: "GH_PAGER",
|
||||
},
|
||||
{
|
||||
name: "GH_PAGER and config pager set",
|
||||
env: map[string]string{
|
||||
"GH_PAGER": "GH_PAGER",
|
||||
},
|
||||
config: pagerConfig(),
|
||||
wantPager: "GH_PAGER",
|
||||
},
|
||||
{
|
||||
name: "config pager and PAGER set",
|
||||
env: map[string]string{
|
||||
"PAGER": "PAGER",
|
||||
},
|
||||
config: pagerConfig(),
|
||||
wantPager: "CONFIG_PAGER",
|
||||
},
|
||||
{
|
||||
name: "only PAGER set",
|
||||
env: map[string]string{
|
||||
"PAGER": "PAGER",
|
||||
},
|
||||
wantPager: "PAGER",
|
||||
},
|
||||
{
|
||||
name: "GH_PAGER set to blank string",
|
||||
env: map[string]string{
|
||||
"GH_PAGER": "",
|
||||
"PAGER": "PAGER",
|
||||
},
|
||||
wantPager: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.env != nil {
|
||||
for k, v := range tt.env {
|
||||
old := os.Getenv(k)
|
||||
os.Setenv(k, v)
|
||||
if k == "GH_PAGER" {
|
||||
defer os.Unsetenv(k)
|
||||
} else {
|
||||
defer os.Setenv(k, old)
|
||||
}
|
||||
}
|
||||
}
|
||||
f := New("1")
|
||||
if tt.config != nil {
|
||||
f.Config = func() (config.Config, error) {
|
||||
return tt.config, nil
|
||||
}
|
||||
}
|
||||
io := ioStreams(f)
|
||||
assert.Equal(t, tt.wantPager, io.GetPager())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ioStreams_prompt(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config config.Config
|
||||
promptDisabled bool
|
||||
}{
|
||||
{
|
||||
name: "default config",
|
||||
promptDisabled: false,
|
||||
},
|
||||
{
|
||||
name: "config with prompt disabled",
|
||||
config: disablePromptConfig(),
|
||||
promptDisabled: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f := New("1")
|
||||
if tt.config != nil {
|
||||
f.Config = func() (config.Config, error) {
|
||||
return tt.config, nil
|
||||
}
|
||||
}
|
||||
io := ioStreams(f)
|
||||
assert.Equal(t, tt.promptDisabled, io.GetNeverPrompt())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func defaultConfig() config.Config {
|
||||
return config.InheritEnv(config.NewFromString(heredoc.Doc(`
|
||||
hosts:
|
||||
nonsense.com:
|
||||
oauth_token: BLAH
|
||||
`)))
|
||||
}
|
||||
|
||||
func pagerConfig() config.Config {
|
||||
return config.NewFromString("pager: CONFIG_PAGER")
|
||||
}
|
||||
|
||||
func disablePromptConfig() config.Config {
|
||||
return config.NewFromString("prompt: disabled")
|
||||
}
|
||||
|
|
@ -4,9 +4,6 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/context"
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
actionsCmd "github.com/cli/cli/pkg/cmd/actions"
|
||||
aliasCmd "github.com/cli/cli/pkg/cmd/alias"
|
||||
apiCmd "github.com/cli/cli/pkg/cmd/api"
|
||||
|
|
@ -93,7 +90,7 @@ func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) *cobra.Command {
|
|||
|
||||
// below here at the commands that require the "intelligent" BaseRepo resolver
|
||||
repoResolvingCmdFactory := *f
|
||||
repoResolvingCmdFactory.BaseRepo = resolvedBaseRepo(f)
|
||||
repoResolvingCmdFactory.BaseRepo = factory.SmartBaseRepoFunc(f)
|
||||
|
||||
cmd.AddCommand(prCmd.NewCmdPR(&repoResolvingCmdFactory))
|
||||
cmd.AddCommand(issueCmd.NewCmdIssue(&repoResolvingCmdFactory))
|
||||
|
|
@ -126,29 +123,3 @@ func bareHTTPClient(f *cmdutil.Factory, version string) func() (*http.Client, er
|
|||
return factory.NewHTTPClient(f.IOStreams, cfg, version, false), nil
|
||||
}
|
||||
}
|
||||
|
||||
func resolvedBaseRepo(f *cmdutil.Factory) func() (ghrepo.Interface, error) {
|
||||
return func() (ghrepo.Interface, error) {
|
||||
httpClient, err := f.HttpClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiClient := api.NewClientFromHTTP(httpClient)
|
||||
|
||||
remotes, err := f.Remotes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repoContext, err := context.ResolveRemotesToRepos(remotes, apiClient, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baseRepo, err := repoContext.BaseRepo(f.IOStreams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return baseRepo, nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,24 @@
|
|||
package cmdutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func EnableRepoOverride(cmd *cobra.Command, f *Factory) {
|
||||
cmd.PersistentFlags().StringP("repo", "R", "", "Select another repository using the `[HOST/]OWNER/REPO` format")
|
||||
}
|
||||
|
||||
func OverrideBaseRepoFunc(f *Factory, override string) func() (ghrepo.Interface, error) {
|
||||
if override == "" {
|
||||
override = os.Getenv("GH_REPO")
|
||||
}
|
||||
if override != "" {
|
||||
return func() (ghrepo.Interface, error) {
|
||||
return ghrepo.FromFullName(override)
|
||||
}
|
||||
}
|
||||
return f.BaseRepo
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,6 +140,10 @@ func (s *IOStreams) SetPager(cmd string) {
|
|||
s.pagerCommand = cmd
|
||||
}
|
||||
|
||||
func (s *IOStreams) GetPager() string {
|
||||
return s.pagerCommand
|
||||
}
|
||||
|
||||
func (s *IOStreams) StartPager() error {
|
||||
if s.pagerCommand == "" || s.pagerCommand == "cat" || !s.IsStdoutTTY() {
|
||||
return nil
|
||||
|
|
@ -202,6 +206,10 @@ func (s *IOStreams) CanPrompt() bool {
|
|||
return s.IsStdinTTY() && s.IsStdoutTTY()
|
||||
}
|
||||
|
||||
func (s *IOStreams) GetNeverPrompt() bool {
|
||||
return s.neverPrompt
|
||||
}
|
||||
|
||||
func (s *IOStreams) SetNeverPrompt(v bool) {
|
||||
s.neverPrompt = v
|
||||
}
|
||||
|
|
@ -281,8 +289,6 @@ func System() *IOStreams {
|
|||
stdoutIsTTY := isTerminal(os.Stdout)
|
||||
stderrIsTTY := isTerminal(os.Stderr)
|
||||
|
||||
pagerCommand := os.Getenv("PAGER")
|
||||
|
||||
io := &IOStreams{
|
||||
In: os.Stdin,
|
||||
originalOut: os.Stdout,
|
||||
|
|
@ -290,7 +296,7 @@ func System() *IOStreams {
|
|||
ErrOut: colorable.NewColorable(os.Stderr),
|
||||
colorEnabled: EnvColorForced() || (!EnvColorDisabled() && stdoutIsTTY),
|
||||
is256enabled: Is256ColorSupported(),
|
||||
pagerCommand: pagerCommand,
|
||||
pagerCommand: os.Getenv("PAGER"),
|
||||
}
|
||||
|
||||
if stdoutIsTTY && stderrIsTTY {
|
||||
|
|
|
|||
1
script/build.bat
Normal file
1
script/build.bat
Normal file
|
|
@ -0,0 +1 @@
|
|||
go run script\build.go %*
|
||||
|
|
@ -23,6 +23,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
|
@ -136,8 +137,14 @@ func date() string {
|
|||
|
||||
func sourceFilesLaterThan(t time.Time) bool {
|
||||
foundLater := false
|
||||
_ = filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
|
||||
err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
// Ignore errors that occur when the project contains a symlink to a filesystem or volume that
|
||||
// Windows doesn't have access to.
|
||||
if path != "." && isAccessDenied(err) {
|
||||
fmt.Fprintf(os.Stderr, "%s: %v\n", path, err)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if foundLater {
|
||||
|
|
@ -151,6 +158,9 @@ func sourceFilesLaterThan(t time.Time) bool {
|
|||
}
|
||||
}
|
||||
if info.IsDir() {
|
||||
if name := filepath.Base(path); name == "vendor" || name == "node_modules" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if path == "go.mod" || path == "go.sum" || (strings.HasSuffix(path, ".go") && !strings.HasSuffix(path, "_test.go")) {
|
||||
|
|
@ -160,9 +170,18 @@ func sourceFilesLaterThan(t time.Time) bool {
|
|||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return foundLater
|
||||
}
|
||||
|
||||
func isAccessDenied(err error) bool {
|
||||
var pe *os.PathError
|
||||
// we would use `syscall.ERROR_ACCESS_DENIED` if this script supported build tags
|
||||
return errors.As(err, &pe) && strings.Contains(pe.Err.Error(), "Access is denied")
|
||||
}
|
||||
|
||||
func rmrf(targets ...string) error {
|
||||
args := append([]string{"rm", "-rf"}, targets...)
|
||||
announce(args...)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue