repomatic.github.releases module

GitHub Releases API client.

The single home for reading GitHub Releases: raw cached API access (tags, versions, single bodies), tag-to-version extraction, tag-to-SHA resolution, and the range-to-release-notes fetch shared by the dependency updaters. The repomatic.release.version_sync adapters and repomatic.deps.dep_report release-notes helper build on top of these reads.

One write helper lives here too: edit_release_notes(), the shared gh release edit path behind the dev pre-release sync (repomatic.github.dev_release) and the changelog-to-release-notes sync (repomatic.github.release_sync), so both writers carry the same arguments and failure contract.

repomatic.github.releases.GITHUB_API_RELEASES_URL = 'https://api.github.com/repos/{owner}/{repo}/releases'

GitHub API URL for fetching all releases for a repository.

repomatic.github.releases.GITHUB_API_TAG_REF_URL = 'https://api.github.com/repos/{owner}/{repo}/git/ref/tags/{tag}'

GitHub API URL for resolving a tag name to its git object.

repomatic.github.releases.GITHUB_API_TAG_OBJECT_URL = 'https://api.github.com/repos/{owner}/{repo}/git/tags/{sha}'

GitHub API URL for dereferencing an annotated tag object to its commit.

repomatic.github.releases.GITHUB_API_RELEASE_BY_TAG_URL = 'https://api.github.com/repos/{owner}/{repo}/releases/tags/{tag}'

GitHub API URL for fetching a single release by tag name.

repomatic.github.releases.owner_repo(repo_url)[source]

Extract (owner, repo) from a GitHub repository URL.

Parameters:

repo_url (str) – Repository URL (e.g. https://github.com/user/repo).

Return type:

tuple[str, str] | None

Returns:

An (owner, repo) pair, or None when the URL does not parse.

exception repomatic.github.releases.GitHubReleasesUnavailable[source]

Bases: RuntimeError

Raised when the GitHub Releases API call could not complete cleanly.

Signals a transient failure (network error, timeout, JSON parse error, or pagination breaking mid-stream) where the result cannot safely be treated as “no releases.”

Callers that drive destructive operations (rewriting the changelog, deleting tags, etc.) must catch this and refuse to act, rather than silently rewriting state with a corrupted or empty view of release history.

class repomatic.github.releases.GitHubRelease(date: str, body: str)[source]

Bases: NamedTuple

Release metadata for a single version from GitHub.

Create new instance of GitHubRelease(date, body)

date: str

Publication date in YYYY-MM-DD format.

body: str

Release description body (markdown).

class repomatic.github.releases.ReleaseAsset(name: str, size: int, sha256: str, download_url: str)[source]

Bases: NamedTuple

A single downloadable asset attached to a GitHub release.

Create new instance of ReleaseAsset(name, size, sha256, download_url)

name: str

Asset filename.

size: int

Asset size in bytes.

sha256: str

SHA-256 hex digest from the API’s digest field.

Empty for assets uploaded before GitHub started recording digests (mid-2025), where the API returns digest: null.

download_url: str

Public browser download URL.

class repomatic.github.releases.ReleaseWithAssets(tag: str, date: str, draft: bool, prerelease: bool, assets: tuple[ReleaseAsset, ...], body: str = '', html_url: str = '')[source]

Bases: NamedTuple

Full release metadata including its assets and visibility flags.

Create new instance of ReleaseWithAssets(tag, date, draft, prerelease, assets, body, html_url)

tag: str

Raw tag name (e.g. v1.2.3).

date: str

Publication date in YYYY-MM-DD format.

draft: bool

True for draft releases, which are only visible to maintainers.

prerelease: bool

True for releases marked as pre-release.

assets: tuple[ReleaseAsset, ...]

Assets attached to the release, in API order.

body: str

Release notes body (markdown).

Carried so sync-binaries --backfill-records can recover detection snapshots from the legacy VirusTotal tables that release notes held before the scan history file existed.

html_url: str

Browser URL of the release page.

For a draft release this is the only resolvable link: drafts have no public releases/tag/<tag> URL, so GitHub serves them at an unguessable releases/tag/untagged-<hash> path exposed only in this field. The prepare-release PR body links the rolling dev pre-release through it (see dev_release_url_and_previous_version()).

repomatic.github.releases.get_github_releases(repo_url, *, force_refresh=False)[source]

Get versions and dates for all GitHub releases.

Fetches all releases via the GitHub API with pagination. Extracts version numbers by stripping the v prefix from tag names. Uses published_at (falling back to created_at) for the date.

Parameters:
  • repo_url (str) – Repository URL (e.g. https://github.com/user/repo).

  • force_refresh (bool) – Ignore any cached map and re-fetch. A cached map predating a release reports it as absent, so callers acting on an absence around release time should re-confirm live.

Return type:

dict[str, GitHubRelease]

Returns:

Dict mapping version strings to GitHubRelease tuples. Empty dict only when the repository genuinely has no releases (the API returned an empty page) or when repo_url does not parse to an owner/repo pair.

Raises:

GitHubReleasesUnavailable – When any page fetch fails or returns unparsable JSON. An empty return value from this function means “the repo has no releases”; a raised exception means “the answer is unknown.”

repomatic.github.releases.get_release_tags(repo_url)[source]

Get all releases keyed by their raw, unstripped tag name.

get_github_releases() keeps only v-prefixed tags (and strips the v), which drops tools whose release tags use another scheme (lychee’s lychee-v…, biome’s @biomejs/biome@…). sync-tool-versions and sync-action-pins need every tag, so the version can be extracted with a per-tool pattern.

Parameters:

repo_url (str) – Repository URL.

Return type:

dict[str, GitHubRelease]

Returns:

Dict mapping raw tag names to GitHubRelease tuples. Empty only when the repository has no releases or repo_url does not parse to an owner/repo pair.

Raises:

GitHubReleasesUnavailable – When any page fetch fails or returns unparsable JSON.

repomatic.github.releases.get_releases_with_assets(repo_url)[source]

Get every release with its assets, visibility flags, and digests.

Deliberately uncached, unlike get_github_releases(): the main consumer is sync-binaries, which runs minutes after a release is published and must see the assets that were just uploaded. A cached view would regenerate the binaries page from a pre-release snapshot.

Parameters:

repo_url (str) – Repository URL (e.g. https://github.com/user/repo).

Return type:

list[ReleaseWithAssets]

Returns:

One ReleaseWithAssets per release (drafts and pre-releases included, for the caller to filter), in API order (newest first). Empty when the repository has no releases or repo_url does not parse to an owner/repo pair.

Raises:

GitHubReleasesUnavailable – When any page fetch fails or returns unparsable JSON.

repomatic.github.releases.parse_release_version(tag)[source]

Parse a release tag as a version, or None for foreign tag schemes.

Return type:

Version | None

repomatic.github.releases.dev_release_url_and_previous_version(repo_url, version)[source]

Look up the two release references the prepare-release PR body links to.

A single get_releases_with_assets() fetch yields both:

  • Dev pre-release URL: the html_url of the draft pre-release whose version shares version’s release segment (the rolling v{version}.dev0 draft). Drafts are visible only to authenticated maintainers, so an unauthenticated or token-less caller gets None here even when the draft exists.

  • Previous version: the highest final release (draft, pre-release, and .dev tags excluded) already published. At prepare-release time the tag for version does not exist yet, so this is the release the new one supersedes, used for the v{previous}...main comparison link.

Parameters:
  • repo_url (str) – Repository URL (e.g. https://github.com/user/repo).

  • version (str) – The release version being prepared (e.g. 1.2.3), with the .dev suffix already stripped.

Return type:

tuple[str | None, str | None]

Returns:

An (dev_release_url, previous_version) pair. Either element is None when its release cannot be found or the API is unavailable, so the caller degrades each list item independently.

repomatic.github.releases.resolve_tag_to_sha(repo_url, tag)[source]

Resolve a release tag to its 40-character commit SHA.

Reads the tag’s git reference. An annotated tag points at an intermediate tag object, dereferenced one hop to the commit it targets; a lightweight tag points straight at the commit.

Parameters:
  • repo_url (str) – Repository URL.

  • tag (str) – The tag name to resolve (e.g. v1.2.3).

Return type:

str | None

Returns:

The commit SHA, or None when the tag cannot be resolved (network error, missing tag, or unexpected payload).

repomatic.github.releases.extract_version(tag, tag_pattern)[source]

Extract a version from a GitHub release tag.

Parameters:
  • tag (str) – The raw tag name.

  • tag_pattern (str | None) – A regex with a version named group, or None to strip a leading v (the common vX.Y.Z scheme).

Return type:

str | None

Returns:

The version string, or None when tag_pattern does not match.

repomatic.github.releases.edit_release_notes(tag, repository, body, *, title='')[source]

Edit a release’s notes (and optionally its title) in place.

The one gh release edit path shared by every release writer, so the dev pre-release sync and the changelog-to-release-notes sync carry the same arguments and failure contract. Assets are never touched.

Parameters:
  • tag (str) – Git tag name of the release (e.g. v1.2.3).

  • repository (str) – GitHub repository in owner/name form.

  • body (str) – The new release body text.

  • title (str) – When non-empty, also replace the release title.

Return type:

bool

Returns:

True when the edit landed, False when the release does not exist or the edit failed.

repomatic.github.releases.get_github_release_body(repo_url, version)[source]

Fetch the release notes body for a specific version from GitHub.

Tries v{version} first (most common for Python packages), then the bare {version} tag.

Parameters:
  • repo_url (str) – GitHub repository URL.

  • version (str) – The version string (e.g. 7.13.5).

Return type:

tuple[str, str]

Returns:

A tuple of (tag, body) where tag is the matched tag name and body is the release notes markdown. Both are empty strings if no release is found.

repomatic.github.releases.fetch_github_release_notes(items)[source]

Fetch GitHub release notes for a batch of version bumps.

For each item, lists the repository’s releases (a cached call, already warm from a prior candidate sweep) and keeps those whose extracted version lands in the half-open range (old, new], oldest first. Non-GitHub datasources (npm, PyPI workflow literals) contribute no item here and render no notes.

Parameters:

items (list[tuple[str, str, str, str, str | None]]) – One (name, repo_url, old, new, tag_pattern) tuple per bumped pin, where old and new are bare versions and tag_pattern is the per-tool extraction regex (or None for the vX.Y.Z scheme).

Return type:

dict[str, tuple[str, list[tuple[str, str]]]]

Returns:

A dict mapping names to (repo_url, versions) tuples, the same shape repomatic.deps.dep_report.fetch_release_notes() returns, so repomatic.deps.dep_report.format_release_notes() renders it unchanged. Only entries with at least one non-empty release body are included.