Commit ea552d4cb3

ea552d4cb38d818eb56648db09e49bd8939559ac

parent: e3ba4251c5

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-28 07:59 UTC

mr show: pluralize commits/checks/reviews section headings

Ref #268

Layout: unified · split

CHANGELOG.org +2
@@ -92,6 +92,8 @@ for the eighteen commands whose CLI path differs from the registry's
9292- =issue create= takes =--label= (repeatable), =--milestone= and
9393 =--assignee= (repeatable), setting them in the same call instead of
9494 a separate one per field (#268).
95- =mr show= pluralizes multi-row section headings with counts: =commits (7):=,
96 =checks (2):=, =reviews (3):= (#268).
9597
9698* v1.36.0 — 2026-09-23
9799
internal/control/mr.go +3 −3
@@ -790,7 +790,7 @@ func runMRShow(c *Ctx, args []string) int {
790790 v.body(d.Body, d.BodyFormat)
791791
792792 if len(commits) > 1 {
793 v.section("commit")
793 v.section(fmt.Sprintf("commits (%d)", len(commits)))
794794 tb := c.table(w, "SHA", "SUBJECT")
795795 for _, cm := range commits {
796796 tb.row(cRef(fmt.Sprintf("%.10s", cm.SHA)), cFlex(cm.Subject))
@@ -799,7 +799,7 @@ func runMRShow(c *Ctx, args []string) int {
799799 }
800800
801801 if len(checks) > 1 {
802 v.section("check")
802 v.section(fmt.Sprintf("checks (%d)", len(checks)))
803803 tb := c.table(w, "CHECK", "STATE", "DURATION", "UPDATED")
804804 for _, x := range checks {
805805 tb.row(cText(x.Context), cState(x.State), cText(x.Duration), cText(c.when(x.UpdatedAt)))
@@ -808,7 +808,7 @@ func runMRShow(c *Ctx, args []string) int {
808808 }
809809
810810 if len(rs) > 1 {
811 v.section("review")
811 v.section(fmt.Sprintf("reviews (%d)", len(rs)))
812812 tb := c.table(w, "REVIEWER", "VERDICT", "WHEN")
813813 for _, r := range rs {
814814 verdict := r.Verdict
internal/control/mr_test.go +85
@@ -3,6 +3,8 @@ package control
33import (
44 "bytes"
55 "encoding/json"
6 "os"
7 "path/filepath"
68 "strconv"
79 "strings"
810 "testing"
@@ -177,3 +179,86 @@ func TestMREditSupersededByOnOpenMRRefused(t *testing.T) {
177179 t.Fatalf("stderr = %q, want the closed-only refusal", errOut.String())
178180 }
179181}
182
183// mr show pluralizes multi-row section headings with counts.
184func TestMRShowPluralizesMultiRowSections(t *testing.T) {
185 st, repo, uid := newQueueTestRepo(t)
186 owner := store.User{ID: uid, Username: "alice"}
187
188 // Create git commits for the MR
189 git := gitRunner(t)
190 root := t.TempDir()
191
192 // Create a temporary repository to set up commits
193 src := filepath.Join(root, "src")
194 os.MkdirAll(src, 0o755)
195 git(root, "init", "-q", "-b", "main", "src")
196
197 // Create base commit on main
198 os.WriteFile(filepath.Join(src, "file.txt"), []byte("content"), 0o644)
199 git(src, "add", ".")
200 git(src, "commit", "-q", "-m", "initial")
201
202 // Create feature branch with 2 commits
203 git(src, "checkout", "-q", "-b", "feature")
204 os.WriteFile(filepath.Join(src, "file.txt"), []byte("content1"), 0o644)
205 git(src, "add", ".")
206 git(src, "commit", "-q", "-m", "commit1")
207
208 os.WriteFile(filepath.Join(src, "file.txt"), []byte("content2"), 0o644)
209 git(src, "add", ".")
210 git(src, "commit", "-q", "-m", "commit2")
211 headSHA := strings.TrimSpace(git(src, "rev-parse", "HEAD"))
212
213 // Clone as a bare repository to the gitbay path
214 dir := RepoDir(root, repo.OwnerName, repo.Name)
215 os.MkdirAll(filepath.Dir(dir), 0o755)
216 git(root, "clone", "-q", "--bare", "src", dir)
217
218 // Create the MR head ref in the bare repository
219 git(dir, "update-ref", "refs/merge-requests/1/head", headSHA)
220
221 // Create an MR
222 _, err := st.CreateMR(repo.ID, uid, repo.ID, "feature", "main", "Feature", "", headSHA, "md", false)
223 if err != nil {
224 t.Fatalf("CreateMR: %v", err)
225 }
226
227 // Add 2 checks
228 if err := st.SetCommitStatus(repo.ID, headSHA, "check1", "success", "", "", 0); err != nil {
229 t.Fatal(err)
230 }
231 if err := st.SetCommitStatus(repo.ID, headSHA, "check2", "success", "", "", 0); err != nil {
232 t.Fatal(err)
233 }
234
235 // Add 2 reviews
236 bob, err := st.CreateUser("bob", false)
237 if err != nil {
238 t.Fatal(err)
239 }
240 if err := st.AddMRReview(1, bob, "approve", headSHA); err != nil {
241 t.Fatal(err)
242 }
243 charlie, err := st.CreateUser("charlie", false)
244 if err != nil {
245 t.Fatal(err)
246 }
247 if err := st.AddMRReview(1, charlie, "approve", headSHA); err != nil {
248 t.Fatal(err)
249 }
250
251 // Call mr show in plain text mode
252 c, out, errOut := mrTestCtx(st, owner)
253 c.Cfg.Server.Root = root
254 if code := Dispatch(c, []string{"mr", "show", repo.Path(), "1"}); code != protocol.ExitOK {
255 t.Fatalf("mr show: exit %d, %s", code, errOut.String())
256 }
257
258 outStr := out.String()
259 for _, want := range []string{"commits (2):", "checks (2):", "reviews (2):"} {
260 if !strings.Contains(outStr, want) {
261 t.Errorf("missing %q in:\n%s", want, outStr)
262 }
263 }
264}