Skip to content
Open
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
3 changes: 3 additions & 0 deletions .changes/unreleased/Added-20260513-113740.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Added
body: Add --fill-from parameter to upstack, downstack, and stack commands to allow autofilling of CR titles/bodies when submitting whole stacks.
time: 2026-05-13T11:37:40.724695-07:00
3 changes: 3 additions & 0 deletions doc/includes/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ only if there are multiple CRs in the stack.
* `-r`, `--reviewer=REVIEWER,...`: Add reviewers to the change request. Pass multiple times or separate with commas. <span class="mdx-badge"><span class="mdx-badge__icon">:material-tag:{ title="Released in version" }</span><span class="mdx-badge__text">[v0.21.0](/changelog.md#v0.21.0)</span>
* `-a`, `--assign=ASSIGNEE,...`: Assign the change request to these users. Pass multiple times or separate with commas. <span class="mdx-badge"><span class="mdx-badge__icon">:material-tag:{ title="Released in version" }</span><span class="mdx-badge__text">[v0.21.0](/changelog.md#v0.21.0)</span>
* `--no-web`: Alias for --web=false.
* `--fill-from=FILE`: JSON file mapping branch names to {title, body} for CR creation. Use '-' for stdin.

**Configuration**: [spice.submit.assignees](/cli/config.md#spicesubmitassignees), [spice.submit.draft](/cli/config.md#spicesubmitdraft), [spice.submit.label](/cli/config.md#spicesubmitlabel), [spice.submit.label.addWhen](/cli/config.md#spicesubmitlabeladdwhen), [spice.submit.listTemplatesTimeout](/cli/config.md#spicesubmitlisttemplatestimeout), [spice.submit.navigationComment](/cli/config.md#spicesubmitnavigationcomment), [spice.submit.navigationComment.downstack](/cli/config.md#spicesubmitnavigationcommentdownstack), [spice.submit.navigationCommentStyle.marker](/cli/config.md#spicesubmitnavigationcommentstylemarker), [spice.submit.navigationCommentSync](/cli/config.md#spicesubmitnavigationcommentsync), [spice.submit.publish](/cli/config.md#spicesubmitpublish), [spice.submit.reviewers](/cli/config.md#spicesubmitreviewers), [spice.submit.reviewers.addWhen](/cli/config.md#spicesubmitreviewersaddwhen), [spice.submit.skipRestackCheck](/cli/config.md#spicesubmitskiprestackcheck), [spice.submit.template](/cli/config.md#spicesubmittemplate), [spice.submit.updateOnly](/cli/config.md#spicesubmitupdateonly), [spice.submit.web](/cli/config.md#spicesubmitweb)

Expand Down Expand Up @@ -400,6 +401,7 @@ only if there are multiple CRs in the stack.
* `-a`, `--assign=ASSIGNEE,...`: Assign the change request to these users. Pass multiple times or separate with commas. <span class="mdx-badge"><span class="mdx-badge__icon">:material-tag:{ title="Released in version" }</span><span class="mdx-badge__text">[v0.21.0](/changelog.md#v0.21.0)</span>
* `--no-web`: Alias for --web=false.
* `--branch=NAME`: Branch to start at
* `--fill-from=FILE`: JSON file mapping branch names to {title, body} for CR creation. Use '-' for stdin.

**Configuration**: [spice.submit.assignees](/cli/config.md#spicesubmitassignees), [spice.submit.draft](/cli/config.md#spicesubmitdraft), [spice.submit.label](/cli/config.md#spicesubmitlabel), [spice.submit.label.addWhen](/cli/config.md#spicesubmitlabeladdwhen), [spice.submit.listTemplatesTimeout](/cli/config.md#spicesubmitlisttemplatestimeout), [spice.submit.navigationComment](/cli/config.md#spicesubmitnavigationcomment), [spice.submit.navigationComment.downstack](/cli/config.md#spicesubmitnavigationcommentdownstack), [spice.submit.navigationCommentStyle.marker](/cli/config.md#spicesubmitnavigationcommentstylemarker), [spice.submit.navigationCommentSync](/cli/config.md#spicesubmitnavigationcommentsync), [spice.submit.publish](/cli/config.md#spicesubmitpublish), [spice.submit.reviewers](/cli/config.md#spicesubmitreviewers), [spice.submit.reviewers.addWhen](/cli/config.md#spicesubmitreviewersaddwhen), [spice.submit.skipRestackCheck](/cli/config.md#spicesubmitskiprestackcheck), [spice.submit.template](/cli/config.md#spicesubmittemplate), [spice.submit.updateOnly](/cli/config.md#spicesubmitupdateonly), [spice.submit.web](/cli/config.md#spicesubmitweb)

Expand Down Expand Up @@ -555,6 +557,7 @@ only if there are multiple CRs in the stack.
* `-a`, `--assign=ASSIGNEE,...`: Assign the change request to these users. Pass multiple times or separate with commas. <span class="mdx-badge"><span class="mdx-badge__icon">:material-tag:{ title="Released in version" }</span><span class="mdx-badge__text">[v0.21.0](/changelog.md#v0.21.0)</span>
* `--no-web`: Alias for --web=false.
* `--branch=NAME`: Branch to start at
* `--fill-from=FILE`: JSON file mapping branch names to {title, body} for CR creation. Use '-' for stdin.

**Configuration**: [spice.submit.assignees](/cli/config.md#spicesubmitassignees), [spice.submit.draft](/cli/config.md#spicesubmitdraft), [spice.submit.label](/cli/config.md#spicesubmitlabel), [spice.submit.label.addWhen](/cli/config.md#spicesubmitlabeladdwhen), [spice.submit.listTemplatesTimeout](/cli/config.md#spicesubmitlisttemplatestimeout), [spice.submit.navigationComment](/cli/config.md#spicesubmitnavigationcomment), [spice.submit.navigationComment.downstack](/cli/config.md#spicesubmitnavigationcommentdownstack), [spice.submit.navigationCommentStyle.marker](/cli/config.md#spicesubmitnavigationcommentstylemarker), [spice.submit.navigationCommentSync](/cli/config.md#spicesubmitnavigationcommentsync), [spice.submit.publish](/cli/config.md#spicesubmitpublish), [spice.submit.reviewers](/cli/config.md#spicesubmitreviewers), [spice.submit.reviewers.addWhen](/cli/config.md#spicesubmitreviewersaddwhen), [spice.submit.skipRestackCheck](/cli/config.md#spicesubmitskiprestackcheck), [spice.submit.template](/cli/config.md#spicesubmittemplate), [spice.submit.updateOnly](/cli/config.md#spicesubmitupdateonly), [spice.submit.web](/cli/config.md#spicesubmitweb)

Expand Down
10 changes: 10 additions & 0 deletions downstack_submit.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ type downstackSubmitCmd struct {
submit.BatchOptions

Branch string `placeholder:"NAME" help:"Branch to start at" predictor:"trackedBranches"`

FillFrom string `name:"fill-from" help:"JSON file mapping branch names to {title, body} for CR creation. Use '-' for stdin." placeholder:"FILE"`
}

func (*downstackSubmitCmd) Help() string {
Expand Down Expand Up @@ -57,6 +59,14 @@ func (cmd *downstackSubmitCmd) Run(

// TODO: separate preparation of the stack from submission

if cmd.FillFrom != "" {
meta, err := parseFillFrom(cmd.FillFrom)
if err != nil {
return fmt.Errorf("--fill-from: %w", err)
}
cmd.BranchMetadata = meta
}

return submitHandler.SubmitBatch(ctx, &submit.BatchRequest{
Branches: downstacks,
Options: &cmd.Options,
Expand Down
32 changes: 32 additions & 0 deletions fill_from.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package main

import (
"encoding/json"
"fmt"
"io"
"os"

"go.abhg.dev/gs/internal/handler/submit"
)

// parseFillFrom reads per-branch CR metadata from a file or stdin.
// If path is "-", it reads from stdin.
// The expected format is a JSON object mapping branch names
// to objects with "title" and "body" fields.
func parseFillFrom(path string) (map[string]submit.BranchMeta, error) {
var data []byte
var err error
if path == "-" {
data, err = io.ReadAll(os.Stdin)
} else {
data, err = os.ReadFile(path)
}
if err != nil {
return nil, fmt.Errorf("read: %w", err)
}
var meta map[string]submit.BranchMeta
if err := json.Unmarshal(data, &meta); err != nil {
return nil, fmt.Errorf("parse JSON: %w", err)
}
return meta, nil
}
25 changes: 24 additions & 1 deletion internal/handler/submit/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,20 @@ func (l *LabelsAddWhen) UnmarshalText(bs []byte) error {
// that are only available to batch submit operations.
type BatchOptions struct {
UpdateOnlyDefault bool `config:"submit.updateOnly" help:"Default value for --update-only in batch submit operations." hidden:"" default:"false"`

// BranchMetadata provides per-branch title and body overrides
// for CR creation. When a branch has an entry here,
// its title and body are used directly
// instead of deriving from commits.
BranchMetadata map[string]BranchMeta `kong:"-"`
}

// BranchMeta holds per-branch CR metadata for batch submissions.
// When provided via --fill-from, these values are used directly
// instead of deriving title and body from commit messages.
type BranchMeta struct {
Title string `json:"title"`
Body string `json:"body"`
}

// BatchRequest is a request to submit one or more change requests.
Expand All @@ -489,10 +503,19 @@ func (h *Handler) SubmitBatch(ctx context.Context, req *BatchRequest) error {
for _, branch := range req.Branches {
// Shallow copy the options because submitBranch may modify them.
opts := *opts
sopts := &submitOptions{Options: &opts}

// Use per-branch metadata if available.
if batchOpts.BranchMetadata != nil {
if meta, ok := batchOpts.BranchMetadata[branch]; ok {
sopts.Title = meta.Title
sopts.Body = meta.Body
}
}
status, err := h.submitBranch(
ctx,
branch,
&submitOptions{Options: &opts},
sopts,
)
if err != nil {
return fmt.Errorf("submit branch %s: %w", branch, err)
Expand Down
10 changes: 10 additions & 0 deletions stack_submit.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
type stackSubmitCmd struct {
submitOptions
submit.BatchOptions

FillFrom string `name:"fill-from" help:"JSON file mapping branch names to {title, body} for CR creation. Use '-' for stdin." placeholder:"FILE"`
}

func (*stackSubmitCmd) Help() string {
Expand Down Expand Up @@ -49,6 +51,14 @@ func (cmd *stackSubmitCmd) Run(

// TODO: separate preparation of the stack from submission

if cmd.FillFrom != "" {
meta, err := parseFillFrom(cmd.FillFrom)
if err != nil {
return fmt.Errorf("--fill-from: %w", err)
}
cmd.BranchMetadata = meta
}

return submitHandler.SubmitBatch(ctx, &submit.BatchRequest{
Branches: toSubmit,
Options: &cmd.Options,
Expand Down
2 changes: 2 additions & 0 deletions testdata/help/downstack_submit.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ Flags:
Pass multiple times or separate with commas.
--no-web Alias for --web=false.
--branch=NAME Branch to start at
--fill-from=FILE JSON file mapping branch names to {title,
body} for CR creation. Use '-' for stdin.

Global Flags:
-h, --help Show help for the command
Expand Down
2 changes: 2 additions & 0 deletions testdata/help/stack_submit.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ Flags:
-a, --assign=ASSIGNEE,... Assign the change request to these users.
Pass multiple times or separate with commas.
--no-web Alias for --web=false.
--fill-from=FILE JSON file mapping branch names to {title,
body} for CR creation. Use '-' for stdin.

Global Flags:
-h, --help Show help for the command
Expand Down
2 changes: 2 additions & 0 deletions testdata/help/upstack_submit.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ Flags:
Pass multiple times or separate with commas.
--no-web Alias for --web=false.
--branch=NAME Branch to start at
--fill-from=FILE JSON file mapping branch names to {title,
body} for CR creation. Use '-' for stdin.

Global Flags:
-h, --help Show help for the command
Expand Down
10 changes: 10 additions & 0 deletions upstack_submit.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ type upstackSubmitCmd struct {
submit.BatchOptions

Branch string `placeholder:"NAME" help:"Branch to start at" predictor:"trackedBranches"`

FillFrom string `name:"fill-from" help:"JSON file mapping branch names to {title, body} for CR creation. Use '-' for stdin." placeholder:"FILE"`
}

func (*upstackSubmitCmd) Help() string {
Expand Down Expand Up @@ -78,6 +80,14 @@ func (cmd *upstackSubmitCmd) Run(

// TODO: separate preparation of the stack from submission

if cmd.FillFrom != "" {
meta, err := parseFillFrom(cmd.FillFrom)
if err != nil {
return fmt.Errorf("--fill-from: %w", err)
}
cmd.BranchMetadata = meta
}

return submitHandler.SubmitBatch(ctx, &submit.BatchRequest{
Branches: upstacks,
Options: &cmd.Options,
Expand Down
Loading