Gitility.RefDB (Gitility v0.4.0)

Copy Markdown View Source

Reference databases: mutable names for immutable objects.

Refs are deliberately separate from object storage. An ODB plus a commit ID answers every snapshot query; a RefDB exists only to turn names into object IDs at snapshot time.

Provider stores have the same two-shape lifecycle as Gitility.ODB: start_link/1 returns the provider supervisor and handle/1 obtains the immutable query handle bound to that provider process.

Summary

Types

t()

An opaque handle to a reference store.

Functions

Starts an asynchronous reference-listing job.

Starts an asynchronous reference resolution job.

Returns the query handle owned by a running provider supervisor.

Lists refs in deterministic byte order as one Gitility.Page.

Refreshes a provider backend. Missing optional refresh is a no-op.

Resolves a full raw-byte reference name. Symbolic chains are followed with a hard 16-hop limit. A missing name returns {:ok, :not_found}; it is not a backend error.

Starts a supervised provider-backed RefDB.

Types

t()

@opaque t()

An opaque handle to a reference store.

Functions

async_list(ref_db, query \\ %RefQuery{}, opts \\ [])

@spec async_list(t(), Gitility.RefQuery.t() | keyword(), keyword()) ::
  {:ok, Gitility.Job.t()} | {:error, Gitility.Error.t()}

Starts an asynchronous reference-listing job.

Query normalization, cursor validation, and limits match list/3.

async_resolve(ref_db, name, opts \\ [])

@spec async_resolve(t(), binary(), keyword()) ::
  {:ok, Gitility.Job.t()} | {:error, Gitility.Error.t()}

Starts an asynchronous reference resolution job.

This has the same per-hop provider semantics and options as resolve/3.

handle(pid_or_name)

@spec handle(pid() | GenServer.name()) :: {:ok, t()} | {:error, Gitility.Error.t()}

Returns the query handle owned by a running provider supervisor.

list(ref_db, query \\ %RefQuery{}, opts \\ [])

@spec list(t(), Gitility.RefQuery.t() | keyword(), keyword()) ::
  {:ok, Gitility.Page.t(Gitility.Ref.t())} | {:error, Gitility.Error.t()}

Lists refs in deterministic byte order as one Gitility.Page.

Local cursors store the raw full name of the last emitted ref and resume at names strictly greater in byte order. Ref mutation between pages therefore cannot tear either individual page: newly inserted earlier names are simply behind the continuation, while later names remain eligible. Provider backends own their continuation state and must provide the same page-level ordering guarantee.

Local cursors are intentionally repository-agnostic: their identity digest is all zeroes, so a cursor can resume the same prefix and hash algorithm in another repository. This is useful for caller-controlled repository swaps; Gitility does not claim the two namespaces contain the same refs.

A prefix uses the local store's prefixed iterator. Cursor resume still skips refs up to the last emitted name within that prefix on every page, so the per-page resume cost is linear in the already-consumed prefix.

Listing deliberately returns unfollowed targets: a symbolic ref remains symbolic. This differs from git for-each-ref, which resolves symbolic targets. Call resolve/3 when a resolved identity is required.

Full ref names are capped at 4096 bytes on both local and provider paths. Longer local entries are skipped with a warning. This deliberately differs from Git, which accepts larger names; names over 4 KiB are treated as hostile input.

Resolve-only providers return :unsupported_operation.

refresh(ref_db)

@spec refresh(t()) :: :ok | {:error, Gitility.Error.t()}

Refreshes a provider backend. Missing optional refresh is a no-op.

resolve(ref_db, name, opts \\ [])

@spec resolve(t(), binary(), keyword()) ::
  {:ok, Gitility.RefTarget.t() | :not_found} | {:error, Gitility.Error.t()}

Resolves a full raw-byte reference name. Symbolic chains are followed with a hard 16-hop limit. A missing name returns {:ok, :not_found}; it is not a backend error.

Local resolution is one logical store call and retains one packed-refs snapshot across the entire chain. Provider resolution is deliberately per-hop atomic: each symbolic hop is a separate backend resolve/2 call. A provider that requires a chain-coherent answer must resolve symbolics internally and return a direct target, which is also the recommended shape for API-backed providers. An optional provider resolve_following/2 callback may be considered after 1.0; it is not part of the 0.x contract.

start_link(opts)

@spec start_link(keyword()) :: Supervisor.on_start()

Starts a supervised provider-backed RefDB.

Backend init/1 runs in the caller before the supervision tree starts. Callbacks run concurrently under the configured cap, have independent request deadlines, and are sanitized before failures cross the boundary.

Options

  • :backend (required) — {module, init_arg}.
  • :name — optional provider-supervisor registered name.
  • :runtime — attached runtime (default: shared).
  • :concurrency — max concurrent callbacks (default 8).
  • :request_timeout — callback deadline in milliseconds (default 15_000).