package factory import ( "context" "fmt" "net/http" "os" "regexp" "time" "github.com/cli/cli/v2/api" ghContext "github.com/cli/cli/v2/context" "github.com/cli/cli/v2/git" "github.com/cli/cli/v2/internal/browser" "github.com/cli/cli/v2/internal/config" "github.com/cli/cli/v2/internal/ghrepo" "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/pkg/cmd/extension" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" ) var ssoHeader string var ssoURLRE = regexp.MustCompile(`\burl=([^;]+)`) func New(appVersion string) *cmdutil.Factory { f := &cmdutil.Factory{ AppVersion: appVersion, Config: configFunc(), // No factory dependencies ExecutableName: "gh", } f.IOStreams = ioStreams(f) // Depends on Config f.HttpClient = httpClientFunc(f, appVersion) // Depends on Config, IOStreams, and appVersion f.GitClient = newGitClient(f) // Depends on IOStreams, and Executable f.Remotes = remotesFunc(f) // Depends on Config, and GitClient f.BaseRepo = BaseRepoFunc(f) // Depends on Remotes f.Prompter = newPrompter(f) // Depends on Config and IOStreams f.Browser = newBrowser(f) // Depends on Config, and IOStreams f.ExtensionManager = extensionManager(f) // Depends on Config, HttpClient, and IOStreams f.Branch = branchFunc(f) // Depends on GitClient 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 := ghContext.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() (ghContext.Remotes, error) { rr := &remoteResolver{ readRemotes: func() (git.RemoteSet, error) { return f.GitClient.Remotes(context.Background()) }, 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 } opts := api.HTTPClientOptions{ Config: cfg.Authentication(), Log: io.ErrOut, LogColorize: io.ColorEnabled(), AppVersion: appVersion, } client, err := api.NewHTTPClient(opts) if err != nil { return nil, err } client.Transport = api.ExtractHeader("X-GitHub-SSO", &ssoHeader)(client.Transport) return client, nil } } func newGitClient(f *cmdutil.Factory) *git.Client { io := f.IOStreams ghPath := f.Executable() client := &git.Client{ GhPath: ghPath, Stderr: io.ErrOut, Stdin: io.In, Stdout: io.Out, } return client } func newBrowser(f *cmdutil.Factory) browser.Browser { io := f.IOStreams return browser.New("", io.Out, io.ErrOut) } func newPrompter(f *cmdutil.Factory) prompter.Prompter { editor, _ := cmdutil.DetermineEditor(f.Config) io := f.IOStreams return prompter.New(editor, io.In, io.Out, io.ErrOut) } func configFunc() func() (config.Config, error) { var cachedConfig config.Config var configError error return func() (config.Config, error) { if cachedConfig != nil || configError != nil { return cachedConfig, configError } cachedConfig, configError = config.NewConfig() return cachedConfig, configError } } func branchFunc(f *cmdutil.Factory) func() (string, error) { return func() (string, error) { currentBranch, err := f.GitClient.CurrentBranch(context.Background()) if err != nil { return "", fmt.Errorf("could not determine current branch: %w", err) } return currentBranch, nil } } func extensionManager(f *cmdutil.Factory) *extension.Manager { em := extension.NewManager(f.IOStreams, f.GitClient) cfg, err := f.Config() if err != nil { return em } em.SetConfig(cfg) client, err := f.HttpClient() if err != nil { return em } em.SetClient(api.NewCachedHTTPClient(client, time.Second*30)) return em } func ioStreams(f *cmdutil.Factory) *iostreams.IOStreams { io := iostreams.System() cfg, err := f.Config() if err != nil { return io } if _, ghPromptDisabled := os.LookupEnv("GH_PROMPT_DISABLED"); ghPromptDisabled { io.SetNeverPrompt(true) } else if prompt := cfg.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.Pager(""); pager != "" { io.SetPager(pager) } return io } // SSOURL returns the URL of a SAML SSO challenge received by the server for clients that use ExtractHeader // to extract the value of the "X-GitHub-SSO" response header. func SSOURL() string { if ssoHeader == "" { return "" } m := ssoURLRE.FindStringSubmatch(ssoHeader) if m == nil { return "" } return m[1] }