44 "errors"
55 "fmt"
66 "io"
7- "net/http"
87 "os"
98 "os/exec"
109 "path/filepath"
@@ -13,27 +12,17 @@ import (
1312 "runtime/debug"
1413 "strings"
1514
16- "github.com/MakeNowJust/heredoc"
1715 "github.com/cli/cli/api"
1816 "github.com/cli/cli/context"
19- "github.com/cli/cli/git"
2017 "github.com/cli/cli/internal/config"
2118 "github.com/cli/cli/internal/ghinstance"
22- "github.com/cli/cli/internal/ghrepo"
2319 "github.com/cli/cli/internal/run"
24- apiCmd "github.com/cli/cli/pkg/cmd/api"
25- gistCmd "github.com/cli/cli/pkg/cmd/gist"
26- issueCmd "github.com/cli/cli/pkg/cmd/issue"
27- prCmd "github.com/cli/cli/pkg/cmd/pr"
28- repoCmd "github.com/cli/cli/pkg/cmd/repo"
29- creditsCmd "github.com/cli/cli/pkg/cmd/repo/credits"
30- "github.com/cli/cli/pkg/cmdutil"
31- "github.com/cli/cli/pkg/iostreams"
20+ "github.com/cli/cli/pkg/cmd/factory"
21+ "github.com/cli/cli/pkg/cmd/root"
3222 "github.com/cli/cli/utils"
3323 "github.com/google/shlex"
3424
3525 "github.com/spf13/cobra"
36- "github.com/spf13/pflag"
3726)
3827
3928// Version is dynamically set by the toolchain or overridden by the Makefile.
@@ -42,169 +31,20 @@ var Version = "DEV"
4231// BuildDate is dynamically set at build time in the Makefile.
4332var BuildDate = "" // YYYY-MM-DD
4433
45- var versionOutput = ""
46-
47- var defaultStreams * iostreams.IOStreams
34+ var RootCmd * cobra.Command
4835
4936func init () {
5037 if Version == "DEV" {
5138 if info , ok := debug .ReadBuildInfo (); ok && info .Main .Version != "(devel)" {
5239 Version = info .Main .Version
5340 }
5441 }
55- Version = strings .TrimPrefix (Version , "v" )
56- if BuildDate == "" {
57- RootCmd .Version = Version
58- } else {
59- RootCmd .Version = fmt .Sprintf ("%s (%s)" , Version , BuildDate )
60- }
61- versionOutput = fmt .Sprintf ("gh version %s\n %s\n " , RootCmd .Version , changelogURL (Version ))
62- RootCmd .AddCommand (versionCmd )
63- RootCmd .SetVersionTemplate (versionOutput )
64-
65- RootCmd .PersistentFlags ().Bool ("help" , false , "Show help for command" )
66- RootCmd .Flags ().Bool ("version" , false , "Show gh version" )
67- // TODO:
68- // RootCmd.PersistentFlags().BoolP("verbose", "V", false, "enable verbose output")
69-
70- RootCmd .SetHelpFunc (rootHelpFunc )
71- RootCmd .SetUsageFunc (rootUsageFunc )
72-
73- RootCmd .SetFlagErrorFunc (func (cmd * cobra.Command , err error ) error {
74- if err == pflag .ErrHelp {
75- return err
76- }
77- return & cmdutil.FlagError {Err : err }
78- })
79-
80- defaultStreams = iostreams .System ()
81-
82- // TODO: iron out how a factory incorporates context
83- cmdFactory := & cmdutil.Factory {
84- IOStreams : defaultStreams ,
85- HttpClient : func () (* http.Client , error ) {
86- // TODO: decouple from `context`
87- ctx := context .New ()
88- cfg , err := ctx .Config ()
89- if err != nil {
90- return nil , err
91- }
92-
93- // TODO: avoid setting Accept header for `api` command
94- return httpClient (defaultStreams , cfg , true ), nil
95- },
96- BaseRepo : func () (ghrepo.Interface , error ) {
97- // TODO: decouple from `context`
98- ctx := context .New ()
99- return ctx .BaseRepo ()
100- },
101- Remotes : func () (context.Remotes , error ) {
102- ctx := context .New ()
103- return ctx .Remotes ()
104- },
105- Config : func () (config.Config , error ) {
106- cfg , err := config .ParseDefaultConfig ()
107- if errors .Is (err , os .ErrNotExist ) {
108- cfg = config .NewBlankConfig ()
109- } else if err != nil {
110- return nil , err
111- }
112- return cfg , nil
113- },
114- Branch : func () (string , error ) {
115- currentBranch , err := git .CurrentBranch ()
116- if err != nil {
117- return "" , fmt .Errorf ("could not determine current branch: %w" , err )
118- }
119- return currentBranch , nil
120- },
121- }
122-
123- RootCmd .AddCommand (apiCmd .NewCmdApi (cmdFactory , nil ))
124- RootCmd .AddCommand (gistCmd .NewCmdGist (cmdFactory ))
125-
126- resolvedBaseRepo := func () (ghrepo.Interface , error ) {
127- httpClient , err := cmdFactory .HttpClient ()
128- if err != nil {
129- return nil , err
130- }
131-
132- apiClient := api .NewClientFromHTTP (httpClient )
133-
134- ctx := context .New ()
135- remotes , err := ctx .Remotes ()
136- if err != nil {
137- return nil , err
138- }
139- repoContext , err := context .ResolveRemotesToRepos (remotes , apiClient , "" )
140- if err != nil {
141- return nil , err
142- }
143- baseRepo , err := repoContext .BaseRepo ()
144- if err != nil {
145- return nil , err
146- }
147-
148- return baseRepo , nil
149- }
150-
151- repoResolvingCmdFactory := * cmdFactory
152-
153- repoResolvingCmdFactory .BaseRepo = resolvedBaseRepo
154-
155- RootCmd .AddCommand (prCmd .NewCmdPR (& repoResolvingCmdFactory ))
156- RootCmd .AddCommand (issueCmd .NewCmdIssue (& repoResolvingCmdFactory ))
157- RootCmd .AddCommand (repoCmd .NewCmdRepo (& repoResolvingCmdFactory ))
158- RootCmd .AddCommand (creditsCmd .NewCmdCredits (cmdFactory , nil ))
159- }
160-
161- // RootCmd is the entry point of command-line execution
162- var RootCmd = & cobra.Command {
163- Use : "gh <command> <subcommand> [flags]" ,
164- Short : "GitHub CLI" ,
165- Long : `Work seamlessly with GitHub from the command line.` ,
166-
167- SilenceErrors : true ,
168- SilenceUsage : true ,
169- Example : heredoc .Doc (`
170- $ gh issue create
171- $ gh repo clone cli/cli
172- $ gh pr checkout 321
173- ` ),
174- Annotations : map [string ]string {
175- "help:feedback" : heredoc .Doc (`
176- Fill out our feedback form https://forms.gle/umxd3h31c7aMQFKG7
177- Open an issue using “gh issue create -R cli/cli”
178- ` ),
179- "help:environment" : heredoc .Doc (`
180- GITHUB_TOKEN: an authentication token for API requests. Setting this avoids being
181- prompted to authenticate and overrides any previously stored credentials.
182-
183- GH_REPO: specify the GitHub repository in "OWNER/REPO" format for commands that
184- otherwise operate on a local repository.
185-
186- GH_EDITOR, GIT_EDITOR, VISUAL, EDITOR (in order of precedence): the editor tool to use
187- for authoring text.
188-
189- BROWSER: the web browser to use for opening links.
190-
191- DEBUG: set to any value to enable verbose output to standard error. Include values "api"
192- or "oauth" to print detailed information about HTTP requests or authentication flow.
193-
194- GLAMOUR_STYLE: the style to use for rendering Markdown. See
195- https://github.com/charmbracelet/glamour#styles
196-
197- NO_COLOR: avoid printing ANSI escape sequences for color output.
198- ` ),
199- },
200- }
20142
202- var versionCmd = & cobra.Command {
203- Use : "version" ,
204- Hidden : true ,
205- Run : func (cmd * cobra.Command , args []string ) {
206- fmt .Print (versionOutput )
207- },
43+ cmdFactory := factory .New (Version )
44+ RootCmd = root .NewCmdRoot (cmdFactory , Version , BuildDate )
45+ RootCmd .AddCommand (aliasCmd )
46+ RootCmd .AddCommand (completionCmd )
47+ RootCmd .AddCommand (configCmd )
20848}
20949
21050// overridden in tests
@@ -245,62 +85,6 @@ func contextForCommand(cmd *cobra.Command) context.Context {
24585 return ctx
24686}
24787
248- // generic authenticated HTTP client for commands
249- func httpClient (io * iostreams.IOStreams , cfg config.Config , setAccept bool ) * http.Client {
250- var opts []api.ClientOption
251- if verbose := os .Getenv ("DEBUG" ); verbose != "" {
252- opts = append (opts , apiVerboseLog ())
253- }
254-
255- opts = append (opts ,
256- api .AddHeader ("User-Agent" , fmt .Sprintf ("GitHub CLI %s" , Version )),
257- // antiope-preview: Checks
258- // FIXME: avoid setting this header for `api` command
259- api .AddHeader ("Accept" , "application/vnd.github.antiope-preview+json" ),
260- api .AddHeaderFunc ("Authorization" , func (req * http.Request ) (string , error ) {
261- if token := os .Getenv ("GITHUB_TOKEN" ); token != "" {
262- return fmt .Sprintf ("token %s" , token ), nil
263- }
264-
265- hostname := ghinstance .NormalizeHostname (req .URL .Hostname ())
266- token , err := cfg .Get (hostname , "oauth_token" )
267- if token == "" {
268- var notFound * config.NotFoundError
269- // TODO: check if stdout is TTY too
270- if errors .As (err , & notFound ) && io .IsStdinTTY () {
271- // interactive OAuth flow
272- token , err = config .AuthFlowWithConfig (cfg , hostname , "Notice: authentication required" )
273- }
274- if err != nil {
275- return "" , err
276- }
277- if token == "" {
278- // TODO: instruct user how to manually authenticate
279- return "" , fmt .Errorf ("authentication required for %s" , hostname )
280- }
281- }
282-
283- return fmt .Sprintf ("token %s" , token ), nil
284- }),
285- )
286-
287- if setAccept {
288- opts = append (opts ,
289- api .AddHeaderFunc ("Accept" , func (req * http.Request ) (string , error ) {
290- // antiope-preview: Checks
291- accept := "application/vnd.github.antiope-preview+json"
292- if ghinstance .IsEnterprise (req .URL .Hostname ()) {
293- // shadow-cat-preview: Draft pull requests
294- accept += ", application/vnd.github.shadow-cat-preview"
295- }
296- return accept , nil
297- }),
298- )
299- }
300-
301- return api .NewHTTPClient (opts ... )
302- }
303-
30488func apiVerboseLog () api.ClientOption {
30589 logTraffic := strings .Contains (os .Getenv ("DEBUG" ), "api" )
30690 colorize := utils .IsTerminal (os .Stderr )
@@ -323,17 +107,6 @@ func colorableErr(cmd *cobra.Command) io.Writer {
323107 return err
324108}
325109
326- func changelogURL (version string ) string {
327- path := "https://github.com/cli/cli"
328- r := regexp .MustCompile (`^v?\d+\.\d+\.\d+(-[\w.]+)?$` )
329- if ! r .MatchString (version ) {
330- return fmt .Sprintf ("%s/releases/latest" , path )
331- }
332-
333- url := fmt .Sprintf ("%s/releases/tag/v%s" , path , strings .TrimPrefix (version , "v" ))
334- return url
335- }
336-
337110func ExecuteShellAlias (args []string ) error {
338111 externalCmd := exec .Command (args [0 ], args [1 :]... )
339112 externalCmd .Stderr = os .Stderr
0 commit comments