Commit 453e8dba28

453e8dba280dc46afde86cb5630099825031d813

parent: 467699a1ed

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

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

cli: mr create takes the checked-out branch as --source

Inside a clone, gitbay mr create --title x failed with usage until
--source was typed; the branch you are on is the one you mean. When
the repository was inferred from the clone and no --source is given,
the CLI adds the checked-out branch. Naming another repository
explicitly leaves --source to the caller, and --target already
defaults to the repository's default branch on the server.

Closes #101

Layout: unified · split

cmd/gitbay/local.go +19
@@ -297,3 +297,22 @@ func cmdRemoteList() int {
297297 }
298298 return 0
299299}
300
301// currentBranch is the checked-out branch of the working directory's
302// clone, or "" outside a clone or on a detached HEAD.
303func currentBranch() string {
304 out, err := exec.Command("git", "symbolic-ref", "--quiet", "--short", "HEAD").Output()
305 if err != nil {
306 return ""
307 }
308 return strings.TrimSpace(string(out))
309}
310
311func hasFlag(args []string, flag string) bool {
312 for _, a := range args {
313 if a == flag || strings.HasPrefix(a, flag+"=") {
314 return true
315 }
316 }
317 return false
318}
cmd/gitbay/main.go +11 −1
@@ -125,6 +125,7 @@ type passOpts struct {
125125 stdinOK bool // wire local stdin through when --file - asks for it
126126 alwaysStdin bool // stdin is the payload, named by no flag: a bare redirect
127127 editor string // open $EDITOR for a body when none given
128 inferSource bool // --source defaults to the checked-out branch inside a clone
128129}
129130
130131// pass builds a passthrough command. Flags are parsed by the server, which
@@ -182,6 +183,7 @@ func runPass(o passOpts, args []string) int {
182183 fmt.Fprintln(os.Stderr, "gitbay:", err)
183184 return protocol.ExitFailure
184185 }
186 explicitRepo := len(args) > 0 && !strings.HasPrefix(args[0], "-") && strings.Contains(args[0], "/")
185187 if o.needsRepo {
186188 args, err = withRepo(t, args)
187189 if err != nil {
@@ -189,6 +191,14 @@ func runPass(o passOpts, args []string) int {
189191 return protocol.ExitUsage
190192 }
191193 }
194 // Inside a clone, the branch you are on is the one you mean (#101).
195 // Only when the repository was inferred from the clone too: naming
196 // another repository and meaning this checkout's branch is unlikely.
197 if o.inferSource && !explicitRepo && !hasFlag(args, "--source") {
198 if branch := currentBranch(); branch != "" {
199 args = append(args, "--source", branch)
200 }
201 }
192202
193203 var stdin io.Reader = strings.NewReader("")
194204 if o.editor != "" {
@@ -447,7 +457,7 @@ func milestoneCmd() *cobra.Command {
447457func mrCmd() *cobra.Command {
448458 return group("mr", "merge requests",
449459 pass("create", "open a merge request: --source <branch> --target <branch> --title <t>",
450 passOpts{server: []string{"mr", "create"}, needsRepo: true, stdinOK: true, editor: "merge request"}),
460 passOpts{server: []string{"mr", "create"}, needsRepo: true, stdinOK: true, editor: "merge request", inferSource: true}),
451461 pass("list", "list merge requests [--state ...] [--author u] [--milestone m|none] [--limit n] [--cursor c]", passOpts{server: []string{"mr", "list"}, needsRepo: true}),
452462 pass("show", "show a merge request", passOpts{server: []string{"mr", "show"}, needsRepo: true}),
453463 pass("diff", "show the diff", passOpts{server: []string{"mr", "diff"}, needsRepo: true}),
e2e/cli_test.go +6 −1
@@ -156,7 +156,12 @@ func TestCLI(t *testing.T) {
156156 mustGit(t, dir, cliGitEnv, "add", ".")
157157 mustGit(t, dir, cliGitEnv, "commit", "-q", "-m", "feature work")
158158 mustGit(t, dir, cliGitEnv, "push", "-q", "origin", "feature")
159 c.must(t, dir, "", "mr", "create", "--source", "feature", "--target", "main", "--title", "via cli")
159 // Inside the clone on the branch, --source is the checked-out branch
160 // and --target the default branch; neither needs typing (#101).
161 c.must(t, dir, "", "mr", "create", "--title", "via cli")
162 if out := c.must(t, dir, "", "mr", "show", "1", "--json"); !strings.Contains(out, `"source":"feature"`) || !strings.Contains(out, `"target_ref":"main"`) {
163 t.Fatalf("mr create did not infer source and target:\n%s", out)
164 }
160165
161166 // mr checkout uses the clone's own git; the MR ref comes from origin.
162167 mustGit(t, dir, cliGitEnv, "checkout", "-q", "main")