Commit a8523b10b1
Verified · cmc
Layout: unified · split
cmd/gitbay/main.go +19 −14
| @@ -253,12 +253,13 @@ func pass(use string, o passOpts) *cobra.Command { | ||
| 253 | 253 | // The registry is the only place flags are written down, so |
| 254 | 254 | // --help asks the server rather than reprinting the one-line |
| 255 | 255 | // summary cobra holds. |
| 256 | cliPath := cliPathOf(cmd) | |
| 256 | 257 | for _, a := range args { |
| 257 | 258 | if a == "--help" || a == "-h" { |
| 258 | os.Exit(runServerHelp(o)) | |
| 259 | os.Exit(runServerHelp(o, cliPath)) | |
| 259 | 260 | } |
| 260 | 261 | } |
| 261 | os.Exit(runPass(o, args)) | |
| 262 | os.Exit(runPass(o, cliPath, args)) | |
| 262 | 263 | return nil |
| 263 | 264 | }, |
| 264 | 265 | } |
| @@ -276,16 +277,17 @@ func withShort(cmd *cobra.Command, short string) *cobra.Command { | ||
| 276 | 277 | } |
| 277 | 278 | |
| 278 | 279 | // runServerHelp prints the registry's usage for one command. |
| 279 | func runServerHelp(o passOpts) int { | |
| 280 | func runServerHelp(o passOpts, cliPath string) int { | |
| 280 | 281 | t, err := resolveTarget() |
| 281 | 282 | if err != nil { |
| 282 | 283 | fmt.Fprintln(os.Stderr, "gitbay:", err) |
| 283 | 284 | return protocol.ExitFailure |
| 284 | 285 | } |
| 285 | return runSSH(t, append([]string{"help"}, o.server...), strings.NewReader("")) | |
| 286 | argv := withCLIPath(cliPath, strings.Join(o.server, " "), append([]string{"help"}, o.server...)) | |
| 287 | return runSSH(t, argv, strings.NewReader("")) | |
| 286 | 288 | } |
| 287 | 289 | |
| 288 | func runPass(o passOpts, args []string) int { | |
| 290 | func runPass(o passOpts, cliPath string, args []string) int { | |
| 289 | 291 | t, err := resolveTarget() |
| 290 | 292 | if err != nil { |
| 291 | 293 | fmt.Fprintln(os.Stderr, "gitbay:", err) |
| @@ -343,7 +345,8 @@ func runPass(o passOpts, args []string) int { | ||
| 343 | 345 | stdin = r |
| 344 | 346 | } |
| 345 | 347 | } |
| 346 | return runSSHPaged(t, append(o.server, args...), stdin, pages(o.server, args)) | |
| 348 | argv := withCLIPath(cliPath, strings.Join(o.server, " "), append(o.server, args...)) | |
| 349 | return runSSHPaged(t, argv, stdin, pages(o.server, args)) | |
| 347 | 350 | } |
| 348 | 351 | |
| 349 | 352 | func isEmptyReader(r io.Reader) bool { |
| @@ -380,7 +383,7 @@ func group(use, short string, subs ...*cobra.Command) *cobra.Command { | ||
| 380 | 383 | // know by that name, cobra's own tree still prints. |
| 381 | 384 | local := c.HelpFunc() |
| 382 | 385 | c.SetHelpFunc(func(cmd *cobra.Command, args []string) { |
| 383 | if !serverHelp(use) { | |
| 386 | if !serverHelp(use, cliPathOf(c)) { | |
| 384 | 387 | local(cmd, args) |
| 385 | 388 | } |
| 386 | 389 | }) |
| @@ -388,19 +391,21 @@ func group(use, short string, subs ...*cobra.Command) *cobra.Command { | ||
| 388 | 391 | } |
| 389 | 392 | |
| 390 | 393 | // serverHelp prints the registry's usage for a prefix and reports whether |
| 391 | // it did. At a terminal it goes through the terminal-aware path, so it | |
| 392 | // gets the same --term=<cols>[,color] treatment (and layout) as any other | |
| 393 | // command; piped, it stays a quiet capture, so a network or lookup | |
| 394 | // failure falls back to cobra's local help without noise. | |
| 395 | func serverHelp(prefix string) bool { | |
| 394 | // it did. cliPath is the group's own path in the CLI. At a terminal it | |
| 395 | // goes through the terminal-aware path, so it gets the same | |
| 396 | // --term=<cols>[,color] treatment (and layout) as any other command; | |
| 397 | // piped, it stays a quiet capture, so a network or lookup failure falls | |
| 398 | // back to cobra's local help without noise. | |
| 399 | func serverHelp(prefix, cliPath string) bool { | |
| 396 | 400 | t, err := resolveTarget() |
| 397 | 401 | if err != nil { |
| 398 | 402 | return false |
| 399 | 403 | } |
| 404 | argv := withCLIPath(cliPath, prefix, []string{"help", prefix}) | |
| 400 | 405 | if term.IsTerminal(int(os.Stdout.Fd())) { |
| 401 | return runSSH(t, []string{"help", prefix}, strings.NewReader("")) == 0 | |
| 406 | return runSSH(t, argv, strings.NewReader("")) == 0 | |
| 402 | 407 | } |
| 403 | out, code := sshCapture(t, []string{"help", prefix}) | |
| 408 | out, code := sshCapture(t, argv) | |
| 404 | 409 | if code != 0 || out == "" { |
| 405 | 410 | return false |
| 406 | 411 | } |
cmd/gitbay/serverpath_test.go added +45
| @@ -0,0 +1,45 @@ | ||
| 1 | package main | |
| 2 | ||
| 3 | import ( | |
| 4 | "slices" | |
| 5 | "testing" | |
| 6 | ||
| 7 | "github.com/spf13/cobra" | |
| 8 | ) | |
| 9 | ||
| 10 | // TestServerPathMismatches pins the commands whose CLI path differs from | |
| 11 | // the server path they dispatch, the ones that send --path= so usage and | |
| 12 | // help print the CLI's path (#267). A change to this list is deliberate. | |
| 13 | func TestServerPathMismatches(t *testing.T) { | |
| 14 | want := []string{ | |
| 15 | "auth email add", "auth email list", "auth email primary", | |
| 16 | "auth email remove", "auth email verify", | |
| 17 | "auth export", | |
| 18 | "auth keys add", "auth keys label", "auth keys list", "auth keys remove", | |
| 19 | "auth pgp add", "auth pgp list", "auth pgp remove", | |
| 20 | "auth token create", "auth token list", "auth token revoke", | |
| 21 | "auth whoami", | |
| 22 | "repo topics list", | |
| 23 | } | |
| 24 | ||
| 25 | var got []string | |
| 26 | var walk func(*cobra.Command) | |
| 27 | walk = func(c *cobra.Command) { | |
| 28 | if p := c.Annotations[serverPath]; p != "" { | |
| 29 | if cli := cliPathOf(c); cli != p { | |
| 30 | got = append(got, cli) | |
| 31 | } | |
| 32 | } | |
| 33 | for _, sub := range c.Commands() { | |
| 34 | walk(sub) | |
| 35 | } | |
| 36 | } | |
| 37 | root := newRoot() | |
| 38 | root.InitDefaultHelpCmd() | |
| 39 | walk(root) | |
| 40 | slices.Sort(got) | |
| 41 | ||
| 42 | if !slices.Equal(got, want) { | |
| 43 | t.Errorf("mismatched CLI paths = %v\nwant %v", got, want) | |
| 44 | } | |
| 45 | } | |
cmd/gitbay/ssh.go +18
| @@ -17,6 +17,7 @@ import ( | ||
| 17 | 17 | "gitbay.org/gitbay/internal/cliconfig" |
| 18 | 18 | "gitbay.org/gitbay/internal/protocol" |
| 19 | 19 | "gitbay.org/gitbay/internal/toolpath" |
| 20 | "github.com/spf13/cobra" | |
| 20 | 21 | ) |
| 21 | 22 | |
| 22 | 23 | // context is the resolved target for a command: which instance to talk to |
| @@ -166,6 +167,23 @@ func pages(server, args []string) bool { | ||
| 166 | 167 | return false |
| 167 | 168 | } |
| 168 | 169 | |
| 170 | // cliPathOf is the path a cobra command was reached by, without the | |
| 171 | // root's name: "auth keys remove". | |
| 172 | func cliPathOf(cmd *cobra.Command) string { | |
| 173 | return strings.TrimPrefix(cmd.CommandPath(), "gitbay ") | |
| 174 | } | |
| 175 | ||
| 176 | // withCLIPath prepends --path=<cliPath> to a server command line when the | |
| 177 | // CLI path differs from the server path it dispatches, so usage and help | |
| 178 | // print a command the caller can run (#267). Dispatch strips it before | |
| 179 | // Lookup, as it does --term=. | |
| 180 | func withCLIPath(cliPath, server string, argv []string) []string { | |
| 181 | if cliPath == server { | |
| 182 | return argv | |
| 183 | } | |
| 184 | return append([]string{"--path=" + cliPath}, argv...) | |
| 185 | } | |
| 186 | ||
| 169 | 187 | // runSSH executes the server command over the system ssh binary, wiring |
| 170 | 188 | // stdio through, with no pager. It returns the remote exit code. |
| 171 | 189 | func runSSH(t target, serverArgv []string, stdin io.Reader) int { |
cmd/gitbay/term_test.go +28
| @@ -1,8 +1,11 @@ | ||
| 1 | 1 | package main |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | "slices" | |
| 4 | 5 | "strings" |
| 5 | 6 | "testing" |
| 7 | ||
| 8 | "github.com/spf13/cobra" | |
| 6 | 9 | ) |
| 7 | 10 | |
| 8 | 11 | func TestTermValue(t *testing.T) { |
| @@ -77,3 +80,28 @@ func TestPages(t *testing.T) { | ||
| 77 | 80 | t.Error("list must not page") |
| 78 | 81 | } |
| 79 | 82 | } |
| 83 | ||
| 84 | func TestCLIPathOf(t *testing.T) { | |
| 85 | root := &cobra.Command{Use: "gitbay"} | |
| 86 | auth := &cobra.Command{Use: "auth"} | |
| 87 | keys := &cobra.Command{Use: "keys"} | |
| 88 | remove := &cobra.Command{Use: "remove"} | |
| 89 | keys.AddCommand(remove) | |
| 90 | auth.AddCommand(keys) | |
| 91 | root.AddCommand(auth) | |
| 92 | if got := cliPathOf(remove); got != "auth keys remove" { | |
| 93 | t.Errorf("cliPathOf = %q", got) | |
| 94 | } | |
| 95 | } | |
| 96 | ||
| 97 | func TestWithCLIPath(t *testing.T) { | |
| 98 | argv := []string{"keys", "remove", "abc"} | |
| 99 | if got := withCLIPath("keys remove", "keys remove", argv); !slices.Equal(got, argv) { | |
| 100 | t.Errorf("matching path: %v", got) | |
| 101 | } | |
| 102 | got := withCLIPath("auth keys remove", "keys remove", argv) | |
| 103 | want := []string{"--path=auth keys remove", "keys", "remove", "abc"} | |
| 104 | if !slices.Equal(got, want) { | |
| 105 | t.Errorf("got %v, want %v", got, want) | |
| 106 | } | |
| 107 | } | |