feat: add issue edit, close, and reopen (task 0005)
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.
This commit was merged in pull request #6.
This commit is contained in:
@@ -15,10 +15,25 @@ All three use the action-block pattern on success (`edited:`/`closed:`/`reopened
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] `issue edit` applies title, body, and milestone changes and outputs `edited: { number, status: "ok" }`
|
||||
- [ ] `--add-label` posts the name directly to the additive label endpoint; `--remove-label` resolves the ID first, yields `VALIDATION_ERROR` for a name not in the repo, and treats a 404 for an unapplied label as silent success
|
||||
- [ ] `--add-assignee`/`--remove-assignee` use fetch-then-patch, sending the full resulting assignee list in a single PATCH
|
||||
- [ ] `issue close <n>` outputs `closed: { number, status: "ok" }`; with `--comment` the comment is posted after the close, and a comment-post failure surfaces as an error even though the issue is closed
|
||||
- [ ] `issue close` on an already-closed issue and `issue reopen` on an already-open issue return early with `message: "Already closed"` / `message: "Already open"` and exit 0
|
||||
- [ ] `issue reopen <n>` outputs `reopened: { 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
|
||||
- [x] `issue edit` applies title, body, and milestone changes and outputs `edited: { number, status: "ok" }`
|
||||
- [x] `--add-label` posts the name directly to the additive label endpoint; `--remove-label` resolves the ID first, yields `VALIDATION_ERROR` for a name not in the repo, and treats a 404 for an unapplied label as silent success
|
||||
- [x] `--add-assignee`/`--remove-assignee` use fetch-then-patch, sending the full resulting assignee list in a single PATCH
|
||||
- [x] `issue close <n>` outputs `closed: { number, status: "ok" }`; with `--comment` the comment is posted after the close, and a comment-post failure surfaces as an error even though the issue is closed
|
||||
- [x] `issue close` on an already-closed issue and `issue reopen` on an already-open issue return early with `message: "Already closed"` / `message: "Already open"` and exit 0
|
||||
- [x] `issue reopen <n>` outputs `reopened: { number, status: "ok" }`
|
||||
- [x] 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`/`reopen` renders 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 a `GET` on the issue first, which also supplies the `state` reported in that block.
|
||||
- `--add-label`/`--remove-label`/`--add-assignee`/`--remove-assignee` are repeatable, matching `issue create`'s repeatable `--label`, rather than the single-valued form the spec text implies.
|
||||
- Added a `VALIDATION_ERROR` when `issue edit` is 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 `getIssue` helper (review finding) now used by `view`/`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.
|
||||
|
||||
Reference in New Issue
Block a user