Commit e56e8ca1bb

e56e8ca1bb20b1546231e63c75601742f6dafd77

parent: 2faceb4c41

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-29 05:23 UTC

cli: mr apply-suggestion takes --json on both paths; a local apply whose thread stays open warns and exits 0

Ref #288

Layout: unified · split

cmd/gitbay/local.go +34 −6
@@ -471,12 +471,21 @@ func cmdMRApplySuggestion(args []string) int {
471 fmt.Fprintln(os.Stderr, "gitbay:", err) 471 fmt.Fprintln(os.Stderr, "gitbay:", err)
472 return protocol.ExitFailure 472 return protocol.ExitFailure
473 } 473 }
474 if args, err = withRepo(t, args); err != nil { 474 asJSON := false
475 var rest []string
476 for _, a := range args {
477 if a == "--json" {
478 asJSON = true
479 continue
480 }
481 rest = append(rest, a)
482 }
483 if args, err = withRepo(t, rest); err != nil {
475 fmt.Fprintln(os.Stderr, "gitbay:", err) 484 fmt.Fprintln(os.Stderr, "gitbay:", err)
476 return protocol.ExitUsage 485 return protocol.ExitUsage
477 } 486 }
478 if len(args) != 3 { 487 if len(args) != 3 {
479 fmt.Fprintln(os.Stderr, "usage: gitbay mr apply-suggestion [<owner/name>] <n> <thread-id>") 488 fmt.Fprintln(os.Stderr, "usage: gitbay mr apply-suggestion [<owner/name>] <n> <thread-id> [--json]")
480 return protocol.ExitUsage 489 return protocol.ExitUsage
481 } 490 }
482 repo, n, thread := args[0], args[1], args[2] 491 repo, n, thread := args[0], args[1], args[2]
@@ -516,7 +525,11 @@ func cmdMRApplySuggestion(args []string) int {
516 fmt.Fprintf(os.Stderr, "gitbay: thread %s carries no suggestion\n", thread) 525 fmt.Fprintf(os.Stderr, "gitbay: thread %s carries no suggestion\n", thread)
517 return protocol.ExitUsage 526 return protocol.ExitUsage
518 case s.Apply != "local": 527 case s.Apply != "local":
519 return runSSH(t, []string{"mr", "apply-suggestion", repo, n, thread}, strings.NewReader("")) 528 argv := []string{"mr", "apply-suggestion", repo, n, thread}
529 if asJSON {
530 argv = append(argv, "--json")
531 }
532 return runSSH(t, argv, strings.NewReader(""))
520 case s.Outdated: 533 case s.Outdated:
521 fmt.Fprintf(os.Stderr, "gitbay: suggestion in thread %s is outdated: %s\n", thread, s.Reason) 534 fmt.Fprintf(os.Stderr, "gitbay: suggestion in thread %s is outdated: %s\n", thread, s.Reason)
522 return protocol.ExitUsage 535 return protocol.ExitUsage
@@ -611,10 +624,25 @@ func cmdMRApplySuggestion(args []string) int {
611 if code := runGitLocal("push", "--quiet", url, sha+":refs/heads/"+branch); code != 0 { 624 if code := runGitLocal("push", "--quiet", url, sha+":refs/heads/"+branch); code != 0 {
612 return code 625 return code
613 } 626 }
614 if _, code := captureSSH(t, []string{"mr", "resolve", repo, n, thread}); code != 0 { 627 // The commit has landed, so a thread that cannot be resolved (the
615 return code 628 // server's resolve rule is narrower than who can push) is a warning,
629 // not a failure; captureSSH has already printed the server's reason.
630 _, code = captureSSH(t, []string{"mr", "resolve", repo, n, thread})
631 resolved := code == 0
632 if asJSON {
633 out, _ := json.Marshal(protocol.Envelope{ProtocolVersion: protocol.Version, Data: map[string]any{
634 "thread": tn, "sha": sha, "source": mr.Data.Source, "branch": branch, "resolved": resolved}})
635 fmt.Println(string(out))
636 } else {
637 fmt.Printf("applied thread %s to %s at %.10s", thread, mr.Data.Source, sha)
638 if resolved {
639 fmt.Print("; thread resolved")
640 }
641 fmt.Println()
642 }
643 if !resolved {
644 fmt.Fprintf(os.Stderr, "gitbay: warning: thread %s is still open\n", thread)
616 } 645 }
617 fmt.Printf("applied thread %s to %s at %.10s; thread resolved\n", thread, mr.Data.Source, sha)
618 return 0 646 return 0
619} 647}
620 648
e2e/suggestion_test.go +18 −2
@@ -112,10 +112,13 @@ func TestSuggestionAppliedByServer(t *testing.T) {
112 t.Fatalf("reader applied a suggestion: exit %d %s", code, errOut) 112 t.Fatalf("reader applied a suggestion: exit %d %s", code, errOut)
113 } 113 }
114 114
115 out, errOut, code := c.run(t, dir, "", "mr", "apply-suggestion", "1", thread) 115 out, errOut, code := c.run(t, dir, "", "mr", "apply-suggestion", "1", thread, "--json")
116 if code != 0 { 116 if code != 0 {
117 t.Fatalf("apply-suggestion: exit %d\n%s\n%s", code, out, errOut) 117 t.Fatalf("apply-suggestion: exit %d\n%s\n%s", code, out, errOut)
118 } 118 }
119 if !strings.Contains(out, `"resolved":true`) {
120 t.Errorf("apply-suggestion --json = %s", out)
121 }
119 mustGit(t, dir, env, "fetch", "-q", "origin", "feat") 122 mustGit(t, dir, env, "fetch", "-q", "origin", "feat")
120 if got := mustGit(t, dir, env, "show", "FETCH_HEAD:lib.txt"); got != "one\nTWO\nTWO AND A HALF\nthree\n" { 123 if got := mustGit(t, dir, env, "show", "FETCH_HEAD:lib.txt"); got != "one\nTWO\nTWO AND A HALF\nthree\n" {
121 t.Fatalf("lib.txt = %q", got) 124 t.Fatalf("lib.txt = %q", got)
@@ -174,11 +177,24 @@ func TestSuggestionAppliedLocallyWhenSigned(t *testing.T) {
174 t.Fatalf("server-side apply on a require-signed repository: exit %d %s", code, errOut) 177 t.Fatalf("server-side apply on a require-signed repository: exit %d %s", code, errOut)
175 } 178 }
176 179
177 out, errOut, code := c.run(t, dir, "", "mr", "apply-suggestion", "1", thread) 180 out, errOut, code := c.run(t, dir, "", "mr", "apply-suggestion", "1", thread, "--json")
178 if code != 0 { 181 if code != 0 {
179 t.Fatalf("local apply-suggestion: exit %d\n%s\n%s", code, out, errOut) 182 t.Fatalf("local apply-suggestion: exit %d\n%s\n%s", code, out, errOut)
180 } 183 }
184 var res struct {
185 Data struct {
186 SHA string `json:"sha"`
187 Branch string `json:"branch"`
188 Resolved bool `json:"resolved"`
189 } `json:"data"`
190 }
191 if err := json.Unmarshal([]byte(out), &res); err != nil || res.Data.Branch != "feat" || !res.Data.Resolved {
192 t.Fatalf("local apply-suggestion --json = %s (%v)", out, err)
193 }
181 mustGit(t, dir, env, "fetch", "-q", "origin", "feat") 194 mustGit(t, dir, env, "fetch", "-q", "origin", "feat")
195 if tip := strings.TrimSpace(mustGit(t, dir, env, "rev-parse", "FETCH_HEAD")); tip != res.Data.SHA {
196 t.Errorf("reported sha %s, branch at %s", res.Data.SHA, tip)
197 }
182 if got := mustGit(t, dir, env, "show", "FETCH_HEAD:lib.txt"); got != "one\nTWO\nTWO AND A HALF\nthree\n" { 198 if got := mustGit(t, dir, env, "show", "FETCH_HEAD:lib.txt"); got != "one\nTWO\nTWO AND A HALF\nthree\n" {
183 t.Fatalf("lib.txt = %q", got) 199 t.Fatalf("lib.txt = %q", got)
184 } 200 }