feat(mr): respect --push=false with --fill
Description
glab mr create --fill always pushes the source branch, and an explicit --push=false is ignored: the autofill branch of run() sets ShouldPush = true after the flags are parsed, so the value given on the command line never reaches the push. That leaves no way to use --fill when the push itself fails, which is the case in the issue: a Jujutsu bookmark pushed by an earlier step, where pushing HEAD again conflicts with the remote and aborts the command.
This MR follows the plan in the issue, in four commits:
test(mr): pin that mr create --fill pushes the source branchaddsTestNewCmdCreate_FillPush. It drives the command throughcmdtest.SetupCmdForTestwith--filland with--fill --push, and asserts on thegitcommands the command stubber records: onegit push origin HEAD:feat-new-mreach. Nothing ran--fillthrough to the push before. Both cases pass onmain.feat(mr): respect --push=false with --fillis the fix.complete()records whether--pushwas given, withcmd.Flags().Changed("push"), after the handling of--allow-collaboration,--remove-source-branchand--squash-before-merge, and the autofill branch turns the push on only when it was not. Bare--filland--fill --pushpush as before, and--fill --push=falsecreates the merge request without pushing. The commit adds that third case to the test, which fails on the first commit and passes on this one.docs(mr): describe --push=false with --fill in mr create help. The--filldescription wrappedpushin backticks, which pflag reads as the flag's value name, so the help anddocs/source/mr/create.mdshowed-f, --fill push. It now says the branch is pushed unless--push=falseis set. The--pushdescription says it defaults to true with--filland that--push=falseskips the push, and there is an example for--fill --push=false.docs(mr): say that mr create --push pushes before creating. The--pushdescription said the push happens after the merge request is created, whilerun()pushes first and creates the merge request afterwards.
Commits 3 and 4 carry docs/source/mr/create.md exactly as make gen-docs writes it.
Step 5 of the plan, the error when the branch is not on the remote: GitLab refuses the create with 400 {"message":{"source_branch":["does not exist"]}} (the spec "returns 400 when source_branch does not exist in the repository" in spec/requests/api/merge_requests_spec.rb pins that answer), and against the stub described below, glab prints:
Post http://127.0.0.1:8099/api/v4/projects/OWNER%2FREPO/merge_requests: 400 {message: {source_branch: [does not exist]}}.It names the field and what is wrong with it, and it is the error glab mr create already gives today without --fill for a branch that was never pushed, so I left it as it is. If you would rather have a hint that points at --push there, I can add it to this MR.
Related Issues
Closes #8224 (closed)
How has this been tested?
With Go 1.27.2 and golangci-lint 2.14.0, on each of the four commits:
TestNewCmdCreate_FillPushpasses. On the first commit, the--push=falsecase added by the second fails withexpected: [][]string(nil),actual: [][]string{[]string{"git", "push", "origin", "HEAD:feat-new-mr"}}, and the other two pass.go testpasses on the packagesmake test-changedselects for this change, the changed package and every package that imports it:cmd/gen-docs,cmd/glab,internal/commands,internal/commands/mr,internal/commands/mr/create,internal/commands/stackandinternal/commands/stack/sync.golangci-lint run(make lint): 0 issues.golangci-lint fmt --diff,go vet ./internal/commands/mr/...,make check-embed check-argsandgo run ./scripts/comment-overlap -threshold 1.0 -max-findings 0 ./cmd/... ./internal/...are clean.go run ./cmd/gen-docs/docs.go, which is whatmake gen-docsruns, leavesdocs/andREADME.mdunchanged.- On commits 3 and 4, in the docs lint image CI uses,
vale --minAlertLevel error,markdownlint-cli2andlychee --offline --include-fragmentsreport nothing ondocs/source/mr/create.md. - Every commit message and the title pass commitlint with
scripts/commit-lint/.commitlintrc.js.
End to end, I built glab from main and from this branch and ran both in a scratch repository whose origin is a small stub of the GitLab API. The stub logs every request, git's smart HTTP requests included, so a push attempt shows up as GET /OWNER/REPO.git/info/refs?service=git-receive-pack. feat-on-remote is a branch the stub reports as present, feat-local-only one it does not.
Runs and the stub's request log
### main on feat-on-remote: glab mr create --fill --push=false --yes
fatal: repository 'http://127.0.0.1:8099/OWNER/REPO.git/' not found
Failed to create merge request. Created recovery file: .../recover/OWNER/REPO/mr.json
Run the command again with the '--recover' option to retry.
ERROR
Exit status 128.
### this branch on feat-on-remote: glab mr create --fill --push=false --yes
Creating merge request for feat-on-remote into main in OWNER/REPO
http://127.0.0.1:8099/OWNER/REPO/-/merge_requests/1
### this branch on feat-local-only: glab mr create --fill --push=false --yes
Creating merge request for feat-local-only into main in OWNER/REPO
Failed to create merge request. Created recovery file: .../recover/OWNER/REPO/mr.json
Run the command again with the '--recover' option to retry.
ERROR
Post http://127.0.0.1:8099/api/v4/projects/OWNER%2FREPO/merge_requests: 400 {message: {source_branch: [does not
exist]}}.
### this branch on feat-local-only: glab mr create --fill --yes
fatal: repository 'http://127.0.0.1:8099/OWNER/REPO.git/' not found
Failed to create merge request. Created recovery file: .../recover/OWNER/REPO/mr.json
Run the command again with the '--recover' option to retry.
ERROR
Exit status 128.
### main on feat-local-only, without --fill: glab mr create --title Title --description Body --yes
Creating merge request for feat-local-only into main in OWNER/REPO
Failed to create merge request. Created recovery file: .../recover/OWNER/REPO/mr.json
Run the command again with the '--recover' option to retry.
ERROR
Post http://127.0.0.1:8099/api/v4/projects/OWNER%2FREPO/merge_requests: 400 {message: {source_branch: [does not
exist]}}.---- main on feat-on-remote: glab mr create --fill --push=false --yes
GET /api/v4/projects/OWNER%2FREPO?license=true&with_custom_attributes=true
GET /api/v4/projects/OWNER%2FREPO?license=true&with_custom_attributes=true
POST /api/graphql
GET /api/v4/projects/OWNER%2FREPO/repository/commits/main
GET /OWNER/REPO.git/info/refs?service=git-receive-pack
---- this branch on feat-on-remote: glab mr create --fill --push=false --yes
GET /api/v4/projects/OWNER%2FREPO?license=true&with_custom_attributes=true
POST /api/graphql
GET /api/v4/projects/OWNER%2FREPO/repository/commits/main
POST /api/v4/projects/OWNER%2FREPO/merge_requests
source_branch="feat-on-remote" target_branch="main" title="feat: add the feature"
---- this branch on feat-local-only: glab mr create --fill --push=false --yes
GET /api/v4/projects/OWNER%2FREPO?license=true&with_custom_attributes=true
POST /api/graphql
GET /api/v4/projects/OWNER%2FREPO/repository/commits/main
POST /api/v4/projects/OWNER%2FREPO/merge_requests
source_branch="feat-local-only" target_branch="main" title="fix: a change that was never pushed"
---- this branch on feat-local-only: glab mr create --fill --yes
GET /api/v4/projects/OWNER%2FREPO?license=true&with_custom_attributes=true
POST /api/graphql
GET /api/v4/projects/OWNER%2FREPO/repository/commits/main
GET /OWNER/REPO.git/info/refs?service=git-receive-pack
---- main on feat-local-only, without --fill: glab mr create --title Title --description Body --yes
GET /api/v4/projects/OWNER%2FREPO?license=true&with_custom_attributes=true
POST /api/graphql
POST /api/v4/projects/OWNER%2FREPO/merge_requests
source_branch="feat-local-only" target_branch="main" title="Title"