Commit f050e964f5

f050e964f52bd6b5e6416d9811029e7abc0027f6

parent: f2c5e7302d

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

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

help: examples take the CLI's path for regrouped commands

Closes #304

Layout: unified · split

CHANGELOG.org +3
@@ -8,6 +8,9 @@ anything beyond "replace the binary and restart" is needed.
88
99- Marking notifications read from the web dispatches =notifications
1010 read=, so it counts against the write budget and is audited (#261).
11- =gitbay <cli path> --help= for commands the CLI regroups (=auth keys
12 add=, =auth email list=, =repo topics list=) prints EXAMPLES under the
13 CLI's path instead of the registered one (#304).
1114- =gitbayd admin backup --verify= also checks every release asset the
1215 database names against its recorded size and sha256, and every
1316 archived LFS object against its name (#259).
cmd/gitbay/serverpath_test.go +52
@@ -1,10 +1,15 @@
11package main
22
33import (
4 "bytes"
45 "slices"
6 "strings"
57 "testing"
68
79 "github.com/spf13/cobra"
10
11 "gitbay.org/gitbay/internal/control"
12 "gitbay.org/gitbay/internal/protocol"
813)
914
1015// TestServerPathMismatches pins the commands whose CLI path differs from
@@ -79,3 +84,50 @@ func TestHelpArgvSendsThePathForAMismatchedGroup(t *testing.T) {
7984 t.Errorf("helpArgv(keys, auth keys) = %v, want %v", got, want)
8085 }
8186}
87
88// TestRenamedCommandHelpNamesTheCLIPath runs the server's help for every
89// command whose CLI path differs from its registered path, sent as the
90// CLI sends it, and fails if the text names the registered path where the
91// CLI's belongs (#304).
92func TestRenamedCommandHelpNamesTheCLIPath(t *testing.T) {
93 type renamed struct{ cli, server string }
94 var cmds []renamed
95 var walk func(*cobra.Command)
96 walk = func(c *cobra.Command) {
97 if p := c.Annotations[serverPath]; p != "" && p != "help" {
98 if cli := cliPathOf(c); cli != p {
99 cmds = append(cmds, renamed{cli, p})
100 }
101 }
102 for _, sub := range c.Commands() {
103 walk(sub)
104 }
105 }
106 root := newRoot()
107 root.InitDefaultHelpCmd()
108 walk(root)
109 if len(cmds) == 0 {
110 t.Fatal("no renamed commands found")
111 }
112 for _, rc := range cmds {
113 argv := withCLIPath(rc.cli, rc.server, append([]string{"help"}, strings.Fields(rc.server)...))
114 var out, errOut bytes.Buffer
115 c := &control.Ctx{Stdout: &out, Stderr: &errOut, Scope: "full"}
116 c.Cfg.Server.SiteURL = "https://forge.test"
117 if code := control.Dispatch(c, argv); code != protocol.ExitOK {
118 t.Errorf("%v: exit %d: %s", argv, code, errOut.String())
119 continue
120 }
121 // What the CLI path itself contains (repo topics list holds the
122 // registered repo topics) is not a mismatch.
123 // SEE ALSO names sibling commands, which keep their own paths.
124 text, _, _ := strings.Cut(out.String(), "SEE ALSO")
125 text = strings.ReplaceAll(text, "gitbay "+rc.cli, "")
126 for _, line := range strings.Split(text, "\n") {
127 if strings.Contains(line, "gitbay "+rc.server+" ") || strings.HasSuffix(line, "gitbay "+rc.server) {
128 t.Errorf("%s: help names the registered path %q:\n%s", rc.cli, rc.server, out.String())
129 break
130 }
131 }
132 }
133}
internal/control/help.go +1 −1
@@ -310,7 +310,7 @@ func (c *Ctx) helpVerb(w io.Writer, cmd Command, below []Command) {
310310 fmt.Fprintln(w)
311311 c.heading(w, "EXAMPLES")
312312 for _, ex := range cmd.Examples {
313 c.wrapLine(w, " "+c.program()+" ", ex)
313 c.wrapLine(w, " "+c.program()+" ", c.shownAs(registered, ex))
314314 }
315315 }
316316 if len(below) > 0 {