Implement the issue state-transition mutations:
- issue edit: --title, --body/--body-file, --add-label/--remove-label,
--add-assignee/--remove-assignee, --milestone. Label mutations use
Gitea's dedicated additive/removal endpoints (names added directly,
removals resolved to IDs, unapplied-label 404 as silent success);
assignees use fetch-then-patch (ADR 0007); title/body/milestone and the
recomputed assignee list travel in one PATCH. Outputs edited: {number,
status: ok}.
- issue close: PATCHes state closed, optional --comment posted after with
its failure surfaced; already-closed short-circuits with Already closed.
- issue reopen: PATCHes state open; already-open short-circuits with
Already open.
Extract a shared getIssue helper used by view/edit/close/reopen.
3.7 KiB
spec, blocked-by
| spec | blocked-by |
|---|---|
| gitea-axi | 0004-issue-create-and-comment |
What to build
The issue state-transition mutations: issue edit, issue close, issue reopen.
issue edit <n> supports --title, --body/--body-file, --add-label, --remove-label, --add-assignee, --remove-assignee, and --milestone (name-resolved).
Label mutations use Gitea's dedicated additive/removal label endpoints: --add-label passes the name directly (no lookup); --remove-label resolves the name to an ID case-insensitively, erroring if the label does not exist in the repo, and treating Gitea's 404 for a label not applied to the issue as silent success.
Assignee mutations introduce fetch-then-patch: read the current assignee list, apply the change in-process, send the full resulting list in one PATCH (see ADR 0007).
issue close <n> PATCHes state: "closed", with optional --comment as a second API call whose failure is surfaced rather than swallowed; --reason is excluded.
issue reopen <n> PATCHes state: "open".
All three use the action-block pattern on success (edited:/closed:/reopened: with { number, status: "ok" }) — a deliberate departure from gh-axi's entity block on issue edit — and close/reopen return early with an Already closed/Already open message when a no-op.
Acceptance criteria
issue editapplies title, body, and milestone changes and outputsedited: { number, status: "ok" }--add-labelposts the name directly to the additive label endpoint;--remove-labelresolves the ID first, yieldsVALIDATION_ERRORfor a name not in the repo, and treats a 404 for an unapplied label as silent success--add-assignee/--remove-assigneeuse fetch-then-patch, sending the full resulting assignee list in a single PATCHissue close <n>outputsclosed: { number, status: "ok" }; with--commentthe comment is posted after the close, and a comment-post failure surfaces as an error even though the issue is closedissue closeon an already-closed issue andissue reopenon an already-open issue return early withmessage: "Already closed"/message: "Already open"and exit 0issue reopen <n>outputsreopened: { number, status: "ok" }- Fixture-server tests cover each mutation path, both idempotent no-ops, the unapplied-label silent success, and the close-comment partial failure
Implementation Notes
No criteria were dropped or altered; all seven are satisfied.
Decisions made mid-implementation:
- The idempotent no-op for
close/reopenrenders an entity block —issue: { number, state, message }— mirroring gh-axi, since the spec's deliberate action-block departure is scoped to the success path only. Determining the no-op requires aGETon the issue first, which also supplies thestatereported in that block. --add-label/--remove-label/--add-assignee/--remove-assigneeare repeatable, matchingissue create's repeatable--label, rather than the single-valued form the spec text implies.- Added a
VALIDATION_ERRORwhenissue editis invoked with no changes (documented in--help). The spec never specified the no-change case; this is a small justified extension, not scope creep. - Title/body/milestone and the recomputed assignee list travel in a single PATCH; label mutations use Gitea's dedicated endpoints afterward. Name resolution (milestone, remove-label IDs) runs before any mutation so a typo is reported before a change lands.
- Extracted a shared
getIssuehelper (review finding) now used byview/edit/close/reopen.
Follow-up worth flagging: issue close/reopen do not type-guard against a PR number (unlike issue view), consistent with the spec, which does not require it.