Make tea pulls create honor --output json (#1111)

## Problem

`tea pulls create` accepts `--output` (the flag parses successfully) but never reads it — the action always prints glamour-rendered markdown regardless of the requested format. That is a trap for anyone scripting the CLI: `tea pr create --output json | jq .url` quietly feeds jq a markdown document.

The current output is also hostile to URL-scraping consumers even without `--output`: glamour autolinks the bare PR URL into an OSC 8 terminal hyperlink, so piped stdout contains

```
\x1b]8;;https://host/owner/repo/pulls/33\x1b\\https://host/owner/repo/pulls/33\x1b]8;;\x1b\\
```

instead of a plain URL (repro: `tea pr create ... | cat -v`).

## Why the flag parses but does nothing

`create` itself does not declare `--output`: its flag set (`IssuePRCreateFlags`) carries no `OutputFlag`. The flag parses anyway because urfave/cli v3 resolves flags through `Command.lookupAppliedFlag`, which searches `appliedFlags` — "local flags for current command **or persistent flags from ancestors**". The parent `pulls` command carries `--output` via `AllDefaultFlags`, so the flag reaches the subcommand's parser while being absent from `create --help` — and was never consulted by the action.

## What this changes

- `task.CreatePull` now returns the created `*gitea.PullRequest` instead of printing it.
- `runPullsCreate` switches on `--output`, mirroring the existing detail-command precedent (`RunPullsDetails` in `cmd/pulls.go`): `--output json` emits a lean JSON object; any other value (or no flag at all) falls through to the previous `print.PullDetails` rendering, byte-identical to before.
- Lean JSON shape, since a freshly created PR has no reviews/comments/CI yet: `index`, `title`, `url`, `state`, `base`, `head`.
- `--agit` combined with `--output` now fails fast with an explicit error before any API call or `git push`: the agit flow creates the PR server-side via push and returns no object to print.
- The interactive path is untouched — it only triggers when zero flags are set, so `--output` can never be active there.

Example:

```
$ tea pr create --output json --title "fix: thing" | jq -r .url
https://gitea.example.com/owner/repo/pulls/33
```

---------

Co-authored-by: Danilo Sousa <code@danilosousa.net>
Reviewed-on: https://gitea.com/gitea/tea/pulls/1111
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: ongolk <238961+ongolk@noreply.gitea.com>
This commit is contained in:
ongolk
2026-09-08 19:20:40 +00:00
committed by Lunny Xiao
co-authored by Danilo Sousa
parent 58931b5d17
commit b2bab268d7
5 changed files with 162 additions and 15 deletions
+48 -1
View File
@@ -5,6 +5,9 @@ package pulls
import (
stdctx "context"
"encoding/json"
"fmt"
"io"
gitea "gitea.dev/sdk"
"github.com/urfave/cli/v3"
@@ -12,6 +15,7 @@ import (
"gitea.dev/tea/cmd/flags"
"gitea.dev/tea/modules/context"
"gitea.dev/tea/modules/interact"
"gitea.dev/tea/modules/print"
"gitea.dev/tea/modules/task"
"gitea.dev/tea/modules/utils"
)
@@ -80,6 +84,12 @@ func runPullsCreate(requestCtx stdctx.Context, cmd *cli.Command) error {
return nil
}
// agit flow creates the PR via git push and returns no PR object, so
// --output cannot be honored there; fail fast before any API calls
if ctx.Bool("agit") && ctx.IsSet("output") {
return fmt.Errorf("--output cannot be combined with --agit: the PR is created via git push, so no pull request object is available to print")
}
// else use args to create PR
opts, err := flags.GetIssuePRCreateFlags(requestCtx, ctx)
if err != nil {
@@ -108,7 +118,7 @@ func runPullsCreate(requestCtx stdctx.Context, cmd *cli.Command) error {
allowMaintainerEdits = gitea.OptionalBool(ctx.Bool("allow-maintainer-edits"))
}
return task.CreatePull(
pr, err := task.CreatePull(
requestCtx,
ctx,
ctx.String("base"),
@@ -116,4 +126,41 @@ func runPullsCreate(requestCtx stdctx.Context, cmd *cli.Command) error {
allowMaintainerEdits,
opts,
)
if err != nil {
return err
}
if ctx.IsSet("output") {
switch ctx.String("output") {
case "json":
return writeCreatedPullAsJSON(ctx.Writer, pr)
}
}
print.PullDetails(pr, nil, nil)
return nil
}
// createdPullJSON is the machine-readable representation of a freshly
// created pull request. A new PR has no reviews, comments or CI yet, so
// this is intentionally leaner than the detail view's pullData (cmd/pulls.go).
type createdPullJSON struct {
Index int64 `json:"index"`
Title string `json:"title"`
URL string `json:"url"`
State gitea.StateType `json:"state"`
Base string `json:"base"`
Head string `json:"head"`
}
func writeCreatedPullAsJSON(w io.Writer, pr *gitea.PullRequest) error {
return json.NewEncoder(w).Encode(createdPullJSON{
Index: pr.Index,
Title: pr.Title,
URL: pr.HTMLURL,
State: pr.State,
Base: pr.Base.Ref,
Head: pr.Head.Ref,
})
}