repomatic.forge module

Read a repository’s metrics from whichever forge hosts it.

Answers one question for one repository: how many accounts follow it, when it was created, when it last shipped, and when it was last touched. GitHub, GitLab (on any instance) and Forgejo or Gitea (likewise) each expose that through a different API, and repo_metrics() picks the right one from the URL’s host.

One call per repository on every forge, which is what lets a single sampler collect every metric repomatic.metrics records rather than one call per metric family.

class repomatic.forge.Forge(*values)[source]

Bases: Enum

Forge software a host runs, which owns how to read its API.

Supporting a fourth forge means a new member plus its collector behind metrics(), which keeps the config-side validation and the dispatch from drifting: forge_of() resolves declared values through the member lookup, so a [tool.repomatic.metrics] forges entry naming anything else fails there rather than at sampling time.

FORGEJO = 'forgejo'
GITHUB = 'github'
GITLAB = 'gitlab'
metrics(host, path)[source]

Read one repository through this forge’s API.

Parameters:
  • host (str) – The instance’s hostname. Unused on GitHub, whose one host gh already addresses.

  • path (str) – The repository’s owner/name or namespace path.

Return type:

ForgeMetrics | None

Returns:

The repository’s metrics, or None when unreadable.

Raises:

RuntimeError – When a GitHub call fails.

repomatic.forge.FORGE_APIS: dict[str, Forge] = {'codeberg.org': Forge.FORGEJO, 'github.com': Forge.GITHUB, 'gitlab.com': Forge.GITLAB}

Forge software each known host runs, which is what selects the API to call.

Never guessed from the host name: an unknown host raises instead, so a subject landing on a fourth kind of forge has to declare how to read it rather than silently sampling nothing. Extend it through [tool.repomatic.metrics] forges, which a repository uses to name the self-hosted instances it tracks (salsa.debian.org runs GitLab, gitlab.archlinux.org too).

repomatic.forge.FORGE_USER_AGENT = 'repomatic forge metrics collector'

Sent to every forge API, where a browser identity backfires.

Several self-hosted GitLab instances answer a browser user-agent with a page rather than a payload, returning kilobytes of HTML where the same URL fetched under a plain agent returns a small JSON document. Nothing errors, so the symptom is a subject quietly missing from the readings rather than a failed run.

repomatic.forge.GITHUB_HOST = 'github.com'

The one host whose deep collectors exist.

An exact star reconstruction reads per-star timestamps, and an archive backfill mines github.com pages: both are GitHub-only, so a subject elsewhere is skipped by them rather than failed.

repomatic.forge.GITHUB_METRICS_QUERY = '\nquery($owner: String!, $name: String!) {\n  repository(owner: $owner, name: $name) {\n    createdAt\n    stargazerCount\n    latestRelease { publishedAt }\n    defaultBranchRef { target { ... on Commit { committedDate } } }\n    refs(refPrefix: "refs/tags/", first: 1,\n         orderBy: {field: TAG_COMMIT_DATE, direction: DESC}) {\n      nodes {\n        target {\n          ... on Commit { committedDate }\n          ... on Tag { target { ... on Commit { committedDate } } }\n        }\n      }\n    }\n  }\n}\n'

Reads a repository’s whole metric set in one call.

One call where REST needs four, and correct where REST is not: /tags answers in an order nobody should assume, so a fallback trusting it can date a live project a decade into the past. Ordering on TAG_COMMIT_DATE states the question instead of hoping the default matches it.

The commit date is read off the default branch rather than from the repository’s pushedAt, which any push to any branch bumps.

class repomatic.forge.ForgeMetrics(stars, created=None, release=None, release_source=None, commit=None)[source]

Bases: object

One repository’s metrics, as any forge reports them.

stars: int

Count of accounts following the repository on its own forge.

created: str | None = None

ISO date the repository was opened.

The one date a star count is known to be zero, which is what gives a history an origin and a by-age chart something to align on.

release: str | None = None

ISO date of the newest release or tag, None when a project has neither.

release_source: str | None = None

Where release came from: a release object, or a bare tag.

Recorded because the two are not the same claim. A release is something the project announced; a tag is only the newest thing it labelled, which is the closest available answer for the many projects that never cut a release.

commit: str | None = None

ISO date of the newest commit on the default branch.

The half of the activity reading that stays true for a rolling repository. A widely used package archive can go a decade without tagging a release while being committed to several times a day: a release date alone would report it as long dead.

readings()[source]

Yield each metric this reading carries, as (metric id, value).

The bridge between a typed forge answer and the metric store, which holds every value as text. A metric the forge did not answer yields nothing rather than an empty string, so a project with no release adds no row instead of a blank one.

created is deliberately absent: it is not a metric but the origin of one, recorded by the sampler as a stars reading of zero on that date.

Return type:

Iterator[tuple[str, str]]

repomatic.forge.split_repo_url(url)[source]

Split a repository URL into its host and its owner/name path.

Parameters:

url (str) – An https://host/owner/name repository URL.

Return type:

tuple[str, str]

Returns:

The (host, path) pair.

Raises:

ValueError – When the URL carries no host or no owner/name path.

repomatic.forge.canonical_url(subject)[source]

Normalize a configured subject into the URL the store keys on.

A bare owner/name is GitHub, which is what a repository declaring a handful of peers writes. Anything else is already a URL and only needs its trailing decoration removed. One spelling in the store keeps a subject addressable whichever way its configuration named it.

Parameters:

subject (str) – An owner/name slug or a full repository URL.

Return type:

str

Returns:

The canonical https://host/owner/name URL.

Raises:

ValueError – When neither shape parses.

repomatic.forge.forge_of(url, extra_forges=None)[source]

Name the forge software running the host of url.

Parameters:
  • url (str) – An https://host/owner/name repository URL.

  • extra_forges (Mapping[str, str] | None) – Host-to-forge entries a repository declared for the self-hosted instances it tracks, merged over FORGE_APIS.

Return type:

Forge

Returns:

The Forge the host runs.

Raises:

ValueError – When the host is not declared anywhere, which is deliberate: a silently unsampled subject is worse than a loud one.

repomatic.forge.newest_dated(release, tag)[source]

Pick whichever of a project’s newest release and newest tag is more recent.

Not a preference for releases: plenty of projects carry a tag newer than their latest release object, some by close to a year, so always reading the release would report them as idle. ISO dates compare as strings, which is the whole of the arithmetic here.

Parameters:
  • release (str | None) – ISO date of the newest release, or None.

  • tag (str | None) – ISO date of the newest tag, or None.

Return type:

tuple[str | None, str | None]

Returns:

The (date, source) pair, both None when a project has neither.

repomatic.forge.forge_json(url)[source]

Read one JSON document from a forge’s public API.

Covers every forge but GitHub, whose authentication gh already carries. The instances read here (GitLab and Forgejo) serve their project metadata to anonymous callers, so no token is involved and none is asked for.

Parameters:

url (str) – The API endpoint to read.

Return type:

Any | None

Returns:

The parsed payload, or None when the call or the parse failed.

repomatic.forge.github_metrics(path)[source]

Read a GitHub repository through GITHUB_METRICS_QUERY.

Parameters:

path (str) – The repository’s owner/name path.

Return type:

ForgeMetrics

Returns:

The repository’s metrics.

Raises:

RuntimeError – When the gh call fails.

repomatic.forge.gitlab_metrics(host, path)[source]

Read a GitLab project, on whichever instance hosts it.

Parameters:
  • host (str) – The instance’s hostname.

  • path (str) – The project’s namespace path.

Return type:

ForgeMetrics | None

Returns:

The project’s metrics, or None when unreadable.

repomatic.forge.forgejo_metrics(host, path)[source]

Read a Forgejo or Gitea repository, on whichever instance hosts it.

Parameters:
  • host (str) – The instance’s hostname.

  • path (str) – The repository’s owner/name path.

Return type:

ForgeMetrics | None

Returns:

The repository’s metrics, or None when unreadable.

repomatic.forge.repo_metrics(url, extra_forges=None)[source]

Read one repository, through whichever API its host speaks.

Parameters:
  • url (str) – An https://host/owner/name repository URL.

  • extra_forges (Mapping[str, str] | None) – Host-to-forge entries for self-hosted instances.

Return type:

ForgeMetrics | None

Returns:

The repository’s metrics, or None when the forge could not be read.

Raises: