# Patch Check API Reference

Does each composer patch still apply to the release a site installed, and is
its fix already in that release? Verdicts come from `git apply --check`
against the release tag in the bare clone of the project, plus the merge
request state when the patch is a merge-request URL.

## POST /v1/patch/check

Body, at most 50 items and 4 MB:

```json
{"items": [
  {"project": "redirect", "version": "1.13.0",
   "patch": "https://git.drupalcode.org/project/redirect/-/merge_requests/45.patch",
   "title": "Guide users who enter path aliases"},
  {"project": "drupal", "version": "11.4.5", "patch": "<inline unified diff>"}
]}
```

- `project` — drupal.org machine name; `drupal` or `core` for Drupal core.
- `version` — the composer version installed (`1.13.0`, `8.x-1.13`, `2.0.0-beta4`, `11.4.5`).
- `patch` — unified diff text (256 KB), or a URL on `www.drupal.org/files/issues/` or `git.drupalcode.org/project/<name>/-/merge_requests/<n>.patch|.diff`.
- `title` — optional, echoed back.
- `composer_json` + `composer_lock` (+ `patch_files`: text of each local patch keyed by its path) — instead of `items`: every `extra.patches` entry is checked at its installed version; `missing_files` lists local patches the body did not carry.
- `reroll` — top-level boolean beside `items`: also return a 3-way re-roll for every item that ends `needs-reroll` (see `reroll` below). Costs a scratch merge per item and can return large diffs.
- `target_core` — core version the code each patch adds is checked against (`11.4.5`); defaults to the lock's `drupal/core`, else the version of a `drupal` item.

## GET /v1/patch/check

One URL item: `?project=redis&version=1.11.0&patch=https://www.drupal.org/files/issues/...patch`; add `&reroll=1` for the re-roll. Cached for one hour.

## Response

`{count, results: [...]}`, one result per item in input order:

| Field | Meaning |
|---|---|
| `tag` | release tag matched, or `unknown_version` |
| `applies_at` | `-p` level at which `git apply --check` passes (1, 0, 2, 4 tried in that order), or null |
| `fuzzy` | true when it applied only with one context line, whitespace ignored and hunk counts recomputed (what GNU patch fuzz accepts); review the result |
| `hunks_failed` | `[{file, line, reason}]` at the most plausible level, at most 10 |
| `reverse_applies` | true when the patched lines are already in the tag |
| `mr` | `{state, target_branch, merged_at, merge_commit_sha, squash_commit_sha, head_sha, detailed_merge_status}` for a merge-request URL |
| `merged_in_version` | true when the merged request's squash or merge commit is an ancestor of the tag (or every `From <sha>` of a format-patch series); false for an open request; null when nothing could be checked |
| `suggested` | `shipped` · `still-needed` · `needs-reroll` · `unknown` |
| `reroll` | only with `reroll: true` and `needs-reroll`: `{status, level, patch, conflicts: [{file, regions}], truncated, error}`. `status` is `clean` (install `patch` as the new patch), `conflicts` (`patch` carries `<<<<<<< release / ||||||| patch base / >>>>>>> patch` markers in the listed files) or `unavailable` (no index lines to merge from). Paths are relative to the repository root: contrib at `-p1`, core carries `core/` (`-p2` from `web/core`). Cut at 256 KB. |
| `core_references` | what the code the patch adds references in core, at `target_core`: `{target, checked, flagged: [{symbol, kind, via, file, line, reference, issue, site_arguments, target_parameters, since, change_record, replacement}], flagged_more, deprecated: [{fqn, deprecated_in, removal_in}], unresolved, note}`. `kind` is `removed`, `moved` (a live class carries the name elsewhere) or `signature` (argument count outside the declared parameters; `parent::__construct` and `new` are checked against the constructor the nearest core ancestor declares, `via` lists the walk). Added lines and direct references only: extends, implements, trait use, new, static calls, parent::__construct. Read from the re-rolled diff when a clean re-roll produced one. |
| `error` | why a verdict is missing or partial |

Errors: 400 for a bad batch, 413 above 4 MB, 503 with `Retry-After` when the service is busy, 502 when it is unreachable.
