Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,12 +303,17 @@ Flags:
> Aliases: `git submit`, `git ss`

Pushes every branch in the stack and creates or updates a PR for each one, each based on the branch
below it. New PRs are ready for review by default.
below it. `--down` stops at the current branch, and `git stack submit <branch>` at the named one,
leaving the branches above alone; handy for getting the bottom of a stack reviewed while you keep
working on the top. New PRs are ready for review by default.

Unless you pass `-n` / `--no-edit`, `--ai` or `--no-interactive`, gh stack opens its editor for the
title and body of each new PR. `--draft`, `--publish`, `--ai` and the editor only apply to new PRs.
gh stack can only submit a whole stack, so with `--down` git-stack pushes those branches itself,
creates their missing PRs and has `gh stack link` add them to the stack on GitHub; the editor isn't
available there, so new PRs take their titles from their commits unless you pass `--ai`.

gh stack does the push, with `--force-with-lease`, so after a restack your branches are force
Branches are pushed with `--force-with-lease`, so after a restack your branches are force
pushed. If a branch has commits on the remote that aren't in your local one (someone else pushed
to it), submit refuses rather than overwrite them. `git sync` pulls them in when the remote is
simply ahead; otherwise merge or cherry-pick them by hand, or pass `--force` if you really do want
Expand All @@ -324,6 +329,7 @@ Flags:
- `--no-ai` don't use AI, even if `stack.ai.auto` is set
- `-f` / `--force` push even if it overwrites commits someone else pushed
- `--dry-run` show what would be submitted without pushing anything
- `--down` only submit the current branch (or the one you name) and the ones below it

### `git stack up` / `git stack down` / `git stack top` / `git stack bottom`

Expand Down
37 changes: 27 additions & 10 deletions cmd/submit.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,35 +18,46 @@ func newSubmitCmd(c *cli) *cobra.Command {
useAI bool
noAI bool
force bool
down bool
)
cmd := &cobra.Command{
Use: "submit",
Use: "submit [branch]",
Aliases: []string{"ss"},
Short: "Push the stack and create or update its pull requests",
Long: `Pushes every branch in the stack and creates or updates a PR for each one, each
based on the branch below it. New PRs are ready for review by default; pass
--draft, or set git config stack.submit.default to draft (or ask, to be asked
each time).
based on the branch below it. With --down, or a branch name, it stops at the
current (or named) branch and leaves the ones above it alone. New PRs are ready
for review by default; pass --draft, or set git config stack.submit.default to
draft (or ask, to be asked each time).

Unless you pass --no-edit, --ai or --no-interactive, gh stack opens its editor
for the title and body of each new PR. --draft, --publish, --ai and the editor
only apply to new PRs.
only apply to new PRs. gh stack can only submit a whole stack, so with --down
we push those branches ourselves and have gh stack link add their PRs to the
stack; the editor isn't available there, so new PRs take their titles from
their commits unless you pass --ai.

gh stack does the push, with --force-with-lease, so after a restack your
Branches are pushed with --force-with-lease, so after a restack your
branches are force pushed. If a branch has commits on the remote that aren't in
your local one (someone else pushed to it), submit refuses rather than
overwrite them. Run git stack sync to pull them in, or pass --force if you
really do want them gone.`,
Example: ` # push the stack and open PRs for any new branches
git ss

# push only the current branch and the ones below it
git ss --down

# push feat-b and the ones below it, whichever branch is checked out
git ss feat-b

# open the new PRs as drafts, with titles taken from the commits
git ss -d -n

# see what would be created without pushing anything
git stack submit --dry-run`,
Args: cobra.NoArgs,
ValidArgsFunction: completeNothing,
Args: cobra.MaximumNArgs(1),
ValidArgsFunction: c.completeBranches,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
a, repo, err := c.app(ctx)
Expand All @@ -58,14 +69,19 @@ really do want them gone.`,
if err != nil {
return err
}
res, err := a.Submit(ctx, repo, app.SubmitOptions{
opts := app.SubmitOptions{
Draft: draft,
Publish: publish,
NoEdit: noEdit || (!edit && c.globals.NoInteractive),
DryRun: dryRun,
UseAI: ai,
Force: force,
})
Down: down,
}
if len(args) == 1 {
opts.Branch = args[0]
}
res, err := a.Submit(ctx, repo, opts)
if err != nil {
return err
}
Expand Down Expand Up @@ -118,6 +134,7 @@ really do want them gone.`,
must(cmd.Flags().MarkHidden("stack"))
cmd.Flags().BoolVarP(&force, "force", "f", false, "push even if it overwrites commits someone else pushed")
cmd.Flags().BoolVar(&dryRun, "dry-run", false, "report what would be submitted without pushing")
cmd.Flags().BoolVar(&down, "down", false, "only submit the current branch and the ones below it")
cmd.Flags().BoolVar(&useAI, "ai", false, "have Claude Code write titles and bodies for new PRs (see stack.ai.auto)")
cmd.Flags().BoolVar(&noAI, "no-ai", false, "never use AI; takes precedence over --ai and stack.ai.auto")
return cmd
Expand Down
13 changes: 13 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,19 @@ absorbed hunks simply drop out of the staged diff. Everything above the current
through modify's upstack restack, conflict handling included. There is no absorb specific abort;
the old tips are printed and in the reflog.

**`submit --down` pushes and creates PRs itself, then links them.** `--down`, or naming a branch,
submits that branch and the ones below it, so the bottom of a stack can go for review while the
top is still moving. gh stack submit always pushes every branch, so when there is a branch above
the one asked for, submit does the work itself: it pushes those branches in one atomic
`git push`, each with `--force-with-lease` against its remote tracking tip, creates any missing PRs
through the forge (with the supplied or drafted text, otherwise a single commit's subject and body,
the way gh stack fills them in), and runs `gh stack link` with the PR numbers so they join the
stack on GitHub. Numbers resolve to PRs rather than branches, so link pushes nothing. With no stack
on GitHub yet and a single PR there is nothing to link. A failed link is only a notice: the PRs
exist, and gh stack submit finds open PRs by branch and adopts a stack that already exists, so a
later whole stack submit tidies up. gh stack's editor only exists inside its submit, so with
`--down` the titles come from the commits and we say so.

**`submit --ai` drafts first, then lets gh stack submit, then fixes the PRs up.** The
alternative was to push and create PRs ourselves and only use gh stack to link them. We
didn't, because gh stack owns the push (`--force-with-lease`), the base calculation, the
Expand Down
2 changes: 1 addition & 1 deletion docs/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ path is resolved with `git rev-parse`; anything outside a repository returns `no
| `stack_abort` | Give up an interrupted restack and put every moved branch back | | destructive |
| `stack_restack` | Local rebase of the stack onto its parents, bottom branch onto the local trunk included; only checks out a branch to resolve a conflict through one git rebase | `scope` = `all`/`upstack`/`downstack`/`only`, `branch`, `continue`, `abort`, `stage_all` | destructive |
| `stack_navigate` | `up`/`down`/`top`/`bottom` with `steps`, or `branch` | `direction`, `steps`, `branch` | idempotent |
| `stack_submit` | Push and create/update chained PRs; ready for review by default | `draft`, `publish`, `dry_run`, `pull_requests` `{branch: {title, body}}`, `use_ai`, `force` | destructive, open-world |
| `stack_submit` | Push and create/update chained PRs (with `down`, only up to the current branch); ready for review by default | `down`, `branch`, `draft`, `publish`, `dry_run`, `pull_requests` `{branch: {title, body}}`, `use_ai`, `force` | destructive, open-world |
| `stack_sync` | Fetch, update trunk and restack every stack (checked out or not, all worktrees), never pushing; merged branches deleted per `stack.sync.prune` | `prune` (force deletion) | destructive, open-world |
| `stack_merge` | Merge the stack's PRs up to a branch into trunk, all or nothing, then sync; refuses while a check on any of them failed or is still running | `branch` (default current), `method` = `merge`/`squash`/`rebase`, `no_sync`, `force` (skip the checks check; only with the user's say so) | destructive, open-world |

Expand Down
Loading
Loading