Patch check

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 project's bare clone. For a merge request URL, the merge request state is read too.

POST /v1/patch/check

{"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>"}
],
 "reroll": false,
 "target_core": "11.4.5"}

At most 50 items and 32 MB.

Field What it holds
project The drupal.org machine name. drupal or core for Drupal core.
version The installed composer version: 1.13.0, 8.x-1.13, 2.0.0-beta4, 11.4.5.
patch Unified diff text, up to 16 MB, or a URL on www.drupal.org/files/issues/, or on git.drupalcode.org under /project/<name>/-/merge_requests/<n> or /project/<name>/-/commit/<sha>, ending .patch or .diff.
title Optional. Echoed back.
base Optional. The release the site runs: a commit sha, or a composer version. A re-roll of a patch without index lines tries it first as the merge base, before the newest tags.
reroll Top-level. true also returns a 3-way re-roll for every item that ends conflicts. Each one costs a scratch merge and can be large.
target_core The core version the added code is checked against, 11.4.5. Defaults to the lock's drupal/core, else the version of a drupal item.

Or send the site's own files in place of items:

{"composer_json": "<composer.json text>",
 "composer_lock": "<composer.lock text>",
 "patch_files": {"patches/webform.patch": "<diff text>"}}

Every extra.patches entry is then checked at its installed version. patch_files holds the text of each local patch, keyed by its path. missing_files in the response lists the local patches the body did not include.

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 What it holds
tag The release tag matched, or unknown_version.
sha The commit the verdict was taken at. Present only when the version was a branch; a tag fixes its own commit.
applies_at The -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. 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.
hunks_shipped [{file, line, reason}]: the hunks the release already has verbatim, when only part of the patch landed. On a conflicts row, check whether the rest is still needed before re-rolling. Evidence, not a verdict.
files_shipped The files whose whole change is already in the release.
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 is. False for an open request. Null when nothing could be checked.
suggested The verdict: merged, applies, conflicts, or unknown.
reroll Only with reroll: true on a conflicts item: {status, level, patch, conflicts: [{file, regions}], truncated, error}. See below.
core_references What the added code references in core, at target_core. See below.
error Why a verdict is missing or partial.

reroll

Field What it holds
status clean: install patch as the new patch. conflicts: patch has `<<<<<<< release /
base The tag the merge ran from, when it was not the patch's own index lines. It is the item's base when the patch applies there, else the newest tag it applies to.
patch The re-rolled diff. Paths are relative to the repository root: contrib at -p1, core under core/ at -p2 from web/core. Cut at 16 MB.
syntax_errors PHP files that do not parse after the merge, path: message. verified is false then: the diff applies, the result is not valid PHP. Send resolutions whose text makes those files parse.

core_references

{target, checked, flagged, flagged_more, deprecated, unresolved, note}.

Field What it holds
flagged [{symbol, kind, via, file, line, reference, issue, site_arguments, target_parameters, since, change_record, replacement}].
flagged[].kind removed; moved (a live class has the name elsewhere); signature (the argument count is outside the declared parameters).
flagged[].via For parent::__construct and new: the walk to the nearest core ancestor whose constructor was checked.
deprecated [{fqn, deprecated_in, removal_in}].
note Why the references could not be checked, for a patch that does not apply.

Scope: 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.

Errors

400 for a bad batch. 413 above 32 MB. 503 with Retry-After when the service is busy. 502 when it is unreachable.