feat: add --description-file to issue, MR, and work item commands

Description

--description had no file-based counterpart on any of the issue, merge request, or work item commands. Anything longer than a line required --description "$(cat file.md)", which strips trailing newlines, hits ARG_MAX on large descriptions, and offers no non-interactive path at all: --description - opens an editor, so it fails in CI.

Only glab mr note create / mr note update accepted a body from stdin, and only glab release create --notes-file had a file flag. This brings the same capability to descriptions.

Adds --description-file to:

Command Notes
glab issue create also mutually exclusive with --template
glab issue update
glab mr create also mutually exclusive with --template
glab mr update
glab work-items create
glab work-items update

A path of - reads from standard input, matching the wording and behaviour of the existing release create --notes-file.

Design notes

Both flags are wired through one shared helper, internal/cmdutils/description_file.go:

  • AddDescriptionFileFlag(cmd, resource) registers the flag and marks it mutually exclusive with --description via MarkFlagsMutuallyExclusive.
  • ResolveDescriptionFile(ios, cmd) resolves the file (or stdin) into the --description flag value.

Resolving into --description rather than into each command's own field means the six call sites keep reading a single flag, and their existing Changed("description") checks keep working whether the description came from a flag or a file. That matters for the create commands, where Changed("description") drives whether the interactive prompt is skipped.

Two deliberate choices:

  • No shorthand. -F is the canonical shorthand for --output via cmdutils.EnableJSONOutput, which work-items create and work-items update both call, so -F would have collided there.
  • A file containing only - is rejected rather than passed through, since it would otherwise be indistinguishable from the --description - editor sentinel and would silently launch an editor.

Not changed

  • project create and snippet create also take a free-form --description but are left for a follow-up; long-form descriptions there are rare.
  • An empty description file is a no-op rather than clearing the description, because all six commands gate on if description != "". Same as --description "" today.

No tracking issue — this was raised in review discussion. Happy to link one if it exists.

How has this been tested?

New unit tests for the helper in internal/cmdutils/description_file_test.go cover reading a multi-line file, an empty file, stdin, a missing file, the --only file rejection, the no-op case, and the mutual-exclusion error.

End-to-end command tests were added to work-items update and mr update specifically, because those two exercise the different consumption shapes — a bound opts field versus cmd.Flags().GetString — and both assert the file content reaches the API call options.

make lint              # 0 issues
make test              # 3113 tests, 8 skipped
lefthook run pre-push  # build, lint, test, check-generated, markdownlint, vale, lychee all pass

Manually verified against the built binary:

# reads the file
glab issue update 42 --description-file description.md

# reads stdin
cat description.md | glab issue update 42 --description-file -

# rejects the conflict
$ glab issue update 42 --description x --description-file description.md
If any flags in the group [description description-file] are set none of the others can be

# reports a missing file clearly
$ glab issue update 42 --description-file nope.md
Failed to read description file: open nope.md: no such file or directory.

Merge request reports

Loading
Loading