Skip to content

Commit Sync Capability

CommitSyncCapability is the contract base for a capability that ingests a project's commit (and tag) history into ClickHouse. Bind it with a Capability(kind='commit-sync', handler=...) in the plugin's manifest.

The host addresses this capability directly (manual sync endpoints, availability checks) and permissions it independently (project:commits:write). Incremental sync from inbound webhook deliveries still flows through the plugin's webhook-actions catalog; this kind exists so the host can resolve, enable, and assign commit-sync on its own.

Surfaces: api, webhook.

See Authoring Plugins for the manifest, capabilities, context, credential decryption, and error conventions shared by every plugin.

from imbi.common.plugins import (
    CommitSyncCapability,
    PluginContext,
)


class GitHubCommitSync(CommitSyncCapability):
    async def sync_all_history(
        self,
        *,
        ctx: PluginContext,
        credentials: dict[str, str],
    ) -> tuple[int, int]:
        ...

    async def check_available(
        self,
        *,
        ctx: PluginContext,
        credentials: dict[str, str],
    ) -> bool:
        ...

Method contracts

  • sync_all_history (required) — record the project's full commit and tag history. Host-invoked with no webhook payload; both arguments are keyword-only. Returns (commits_recorded, tags_recorded). Re-running is safe: the ClickHouse commits / tags tables are ReplacingMergeTree and dedupe against rows the webhook already recorded.
  • check_available (optional, default True) — whether an on-demand sync can run for ctx right now. Override to report False when the remote / repository cannot be resolved so the host can hide the affordance. Both arguments are keyword-only.

To attribute commit authors to Imbi users, use ctx.resolve_user_by_identity (see Plugin Context); cache results, as a full-history sync would otherwise repeat the lookup for every commit.

Hints

  • cacheable — the host may cache reads from this capability.

API reference

CommitSyncCapability

Bases: CapabilityHandler

Ingest a project's commit (and tag) history into ClickHouse.

Addressed directly by the host (manual sync endpoints, availability checks) and independently permissioned (project:commits:write). The gateway-side webhook delivery for incremental sync still flows through the plugin's webhook-actions catalog; this kind exists so the host can resolve/enable/assign commit-sync on its own.

check_available async

check_available(
    *, ctx: PluginContext, credentials: dict[str, str]
) -> bool

Whether an on-demand sync can run for ctx right now.

Default True; override to report False when the remote / repository can't be resolved so the host can hide the affordance.

Source code in libraries/common/src/imbi/common/plugins/base.py
async def check_available(
    self,
    *,
    ctx: PluginContext,
    credentials: dict[str, str],
) -> bool:
    """Whether an on-demand sync can run for ``ctx`` right now.

    Default ``True``; override to report ``False`` when the remote /
    repository can't be resolved so the host can hide the affordance.
    """
    del ctx, credentials
    return True

sync_all_history abstractmethod async

sync_all_history(
    *, ctx: PluginContext, credentials: dict[str, str]
) -> tuple[int, int]

Record the project's full commit and tag history.

Host-invoked (no webhook payload). Returns (commits_recorded, tags_recorded). Re-running is safe: the ClickHouse commits / tags tables are ReplacingMergeTree and dedupe against rows the webhook already recorded.

Source code in libraries/common/src/imbi/common/plugins/base.py
@abc.abstractmethod
async def sync_all_history(
    self,
    *,
    ctx: PluginContext,
    credentials: dict[str, str],
) -> tuple[int, int]:
    """Record the project's full commit and tag history.

    Host-invoked (no webhook payload). Returns
    ``(commits_recorded, tags_recorded)``. Re-running is safe: the
    ClickHouse ``commits`` / ``tags`` tables are ``ReplacingMergeTree``
    and dedupe against rows the webhook already recorded.
    """

sync_new_commits async

sync_new_commits(
    *,
    ctx: PluginContext,
    credentials: dict[str, str],
    since: datetime | None = None,
) -> int

Record commits not yet stored. Returns rows written.

The bounded counterpart to :meth:sync_all_history for commits -- for picking up, say, the version-bump commits a release build pushed. Implementations should bound the walk by what they already hold (the newest stored commit) and fall back to since only when they hold nothing, so cost tracks new commits rather than total history.

Re-running is safe (ReplacingMergeTree).

Source code in libraries/common/src/imbi/common/plugins/base.py
async def sync_new_commits(
    self,
    *,
    ctx: PluginContext,
    credentials: dict[str, str],
    since: datetime.datetime | None = None,
) -> int:
    """Record commits not yet stored. Returns rows written.

    The bounded counterpart to :meth:`sync_all_history` for commits --
    for picking up, say, the version-bump commits a release build
    pushed.  Implementations should bound the walk by what they
    already hold (the newest stored commit) and fall back to *since*
    only when they hold nothing, so cost tracks new commits rather
    than total history.

    Re-running is safe (``ReplacingMergeTree``).
    """
    del ctx, credentials, since
    raise NotImplementedError

sync_tag async

sync_tag(
    *,
    ctx: PluginContext,
    credentials: dict[str, str],
    tag: str,
) -> int

Record the single tag named tag. Returns rows written (0/1).

The bounded counterpart to :meth:sync_all_history, for the case where the host already knows which tag appeared -- it supplied the version to the build that created it. Cost is constant in the repo's age, where a full resync grows with it, so this is what a per-release sync should call.

Returns 0 rather than raising when the tag does not exist on the remote: a build that failed before tagging is an expected outcome, not an error. Re-running is safe (ReplacingMergeTree).

Source code in libraries/common/src/imbi/common/plugins/base.py
async def sync_tag(
    self,
    *,
    ctx: PluginContext,
    credentials: dict[str, str],
    tag: str,
) -> int:
    """Record the single tag named *tag*. Returns rows written (0/1).

    The bounded counterpart to :meth:`sync_all_history`, for the case
    where the host already knows which tag appeared -- it supplied the
    version to the build that created it.  Cost is constant in the
    repo's age, where a full resync grows with it, so this is what a
    per-release sync should call.

    Returns ``0`` rather than raising when the tag does not exist on
    the remote: a build that failed before tagging is an expected
    outcome, not an error.  Re-running is safe (``ReplacingMergeTree``).
    """
    del ctx, credentials, tag
    raise NotImplementedError