Commit 2147a76cd5

2147a76cd53e85dd5ce9c9bbb2149c2da5a69596

parent: 453e8dba28

Verified · cmc ci/build: success ci/test: success ci/vuln: success

cmc <hello@cleberg.net> · 2026-09-04 00:09 UTC

cli: a noun's --help is the server's reference

gitbay issue --help printed cobra's flagless subcommand list while
gitbay issue create --help asked the server: two help systems with
different shapes for adjacent commands. A group's help now runs the
server's help for the noun, which carries every flag, and falls back
to cobra's tree offline or for a noun the server does not know by that
name.

Closes #130

Layout: unified · split

cmd/gitbay/main.go +25
@@ -250,9 +250,34 @@ func usesStdin(args []string) bool {
250func group(use, short string, subs ...*cobra.Command) *cobra.Command { 250func group(use, short string, subs ...*cobra.Command) *cobra.Command {
251 c := &cobra.Command{Use: use, Short: short} 251 c := &cobra.Command{Use: use, Short: short}
252 c.AddCommand(subs...) 252 c.AddCommand(subs...)
253 // A noun's help is the server's, like a command's: the registry is
254 // the only place flags are written down, and cobra's subcommand list
255 // carried none (#130). Offline, or for a noun the server does not
256 // know by that name, cobra's own tree still prints.
257 local := c.HelpFunc()
258 c.SetHelpFunc(func(cmd *cobra.Command, args []string) {
259 if !serverHelp(use) {
260 local(cmd, args)
261 }
262 })
253 return c 263 return c
254} 264}
255 265
266// serverHelp prints the registry's usage for a prefix and reports whether
267// it did.
268func serverHelp(prefix string) bool {
269 t, err := resolveTarget()
270 if err != nil {
271 return false
272 }
273 out, code := sshCapture(t, []string{"help", prefix})
274 if code != 0 || out == "" {
275 return false
276 }
277 fmt.Print(out)
278 return true
279}
280
256// local wraps a locally-implemented command (git plumbing, config). 281// local wraps a locally-implemented command (git plumbing, config).
257func local(use, short string, fn func(args []string) int) *cobra.Command { 282func local(use, short string, fn func(args []string) int) *cobra.Command {
258 return &cobra.Command{ 283 return &cobra.Command{
e2e/cli_test.go +6
@@ -156,6 +156,12 @@ func TestCLI(t *testing.T) {
156 mustGit(t, dir, cliGitEnv, "add", ".") 156 mustGit(t, dir, cliGitEnv, "add", ".")
157 mustGit(t, dir, cliGitEnv, "commit", "-q", "-m", "feature work") 157 mustGit(t, dir, cliGitEnv, "commit", "-q", "-m", "feature work")
158 mustGit(t, dir, cliGitEnv, "push", "-q", "origin", "feature") 158 mustGit(t, dir, cliGitEnv, "push", "-q", "origin", "feature")
159 // A noun's --help is the server's reference for that noun, flags
160 // included, not cobra's flagless subcommand list (#130).
161 if out := c.must(t, dir, "", "issue", "--help"); !strings.Contains(out, "issue create <owner/name>") {
162 t.Fatalf("issue --help is not the server's reference:\n%s", out)
163 }
164
159 // Inside the clone on the branch, --source is the checked-out branch 165 // Inside the clone on the branch, --source is the checked-out branch
160 // and --target the default branch; neither needs typing (#101). 166 // and --target the default branch; neither needs typing (#101).
161 c.must(t, dir, "", "mr", "create", "--title", "via cli") 167 c.must(t, dir, "", "mr", "create", "--title", "via cli")