Exgit (exgit v0.1.0)

Copy Markdown View Source

Pure-Elixir git client — clone, fetch, push, plus a path-oriented filesystem API for reading blobs by path.

No git binary, no libgit2, no shelling out.

Top-level entry points

  • init/1 — create an empty repo (memory or on-disk).
  • open/2 — open an existing on-disk repo.
  • clone/2 — full, lazy (lazy: true), or partial (filter: {:blob, :none}) clone.
  • fetch/3 — fetch updates into an existing repo.
  • push/3 — push objects + ref updates to a remote.

See individual function docs for options and semantics. See also Exgit.FS, Exgit.Walk, Exgit.Diff, Exgit.Repository, and the Exgit.Transport protocol.

Summary

Types

A remote endpoint for clone/2, fetch/3, and push/3.

Functions

Clone a git repository.

Fetch updates from a remote into an existing repository.

Create an empty bare repository.

Open an existing on-disk repository.

Push local refs to a remote.

Types

remote()

A remote endpoint for clone/2, fetch/3, and push/3.

Either a URL string ("https://..." or "file://..."), one of the built-in transport structs (Exgit.Transport.File, Exgit.Transport.HTTP), or any struct implementing the Exgit.Transport protocol.

Functions

clone(source, opts \\ [])

@spec clone(
  remote(),
  keyword()
) :: {:ok, Exgit.Repository.t()} | {:error, term()}

Clone a git repository.

{:ok, repo} = Exgit.clone("https://github.com/user/repo")

Options

  • :path — persistent on-disk clone. When set, the clone is backed by ObjectStore.Disk/RefStore.Disk and survives process death. Default: in-memory (lost on process exit).

  • :lazy — when true, defer object fetching. Returns a %Repository{mode: :lazy} whose object store is an ObjectStore.Promisor that fetches on demand. The initial clone pulls refs only — any subsequent object read triggers a want <sha> fetch against the transport. Great for agent loops that read a handful of files from large repos. Default: false (full clone).

  • :filter — partial-clone filter spec. Valid values: {:blob, :none}, {:blob, {:limit, bytes}}, {:tree, depth}, {:raw, "filter=..."}. When set, the server ships a pack that omits blobs (or trees) matching the filter; omitted objects are fetched on demand. Implies :lazy-like semantics for omitted objects but keeps the default branch's commit+tree history resident.

  • :if_unsupported:error (default) or :ignore. When :filter is set but the server doesn't advertise filter capability, :error fails the clone and :ignore proceeds as a full clone.

  • :remote — remote name to record. Default: "origin".

Matrix of modes

OptionsClone-time fetchOn read
clone(url)refs + all objectslocal always
clone(url, lazy: true)refs onlyper-object fetch
clone(url, filter: {:blob, :none})refs + commits + treesblobs fetched on read
clone(url, filter: ..., lazy: true)refs onlyeverything on read

:path + :lazy / :filter

On-disk partial clones are not yet supported; :path combined with :lazy or :filter returns {:error, :disk_partial_clone_unsupported}.

Returns

{:ok, %Repository{}} with :mode => :eager for full clones and :mode => :lazy for any form of partial/lazy clone. Use Exgit.Repository.materialize/2 to convert :lazy:eager after reading what you need.

fetch(repo, source, opts \\ [])

@spec fetch(Exgit.Repository.t(), remote(), keyword()) ::
  {:ok, Exgit.Repository.t()} | {:error, term()}

Fetch updates from a remote into an existing repository.

{:ok, repo} = Exgit.fetch(repo, "https://github.com/user/repo")

Lists the remote's refs, fetches any objects reachable from them, and records remote-tracking refs under refs/remotes/<remote>/ (for branches) and refs/tags/ (for tags). Local branches and HEAD are never moved.

Options

  • :remote — remote name to record tracking refs under. Default: "origin".

  • :prefix — ref prefixes to list on the remote. Default: ["refs/heads/", "refs/tags/"].

Returns

{:ok, %Repository{}} with the updated object and ref stores, or {:error, reason} when listing refs or fetching the pack fails.

init(opts \\ [])

@spec init(keyword()) :: {:ok, Exgit.Repository.t()} | {:error, term()}

Create an empty bare repository.

{:ok, repo} = Exgit.init()
{:ok, repo} = Exgit.init(path: "/tmp/repo.git")

Options

  • :path — create the repo on disk at this path, backed by ObjectStore.Disk/RefStore.Disk. Default: in-memory (lost on process exit).

Returns

{:ok, %Repository{}} with HEAD pointing at refs/heads/main. When :path is set and any directory or file write fails, returns {:error, {:init_failed, reason}} with the underlying posix reason.

open(path, opts \\ [])

@spec open(
  Path.t(),
  keyword()
) :: {:ok, Exgit.Repository.t()} | {:error, term()}

Open an existing on-disk repository.

{:ok, repo} = Exgit.open("/tmp/repo.git")

path must point at a git directory — a bare repo or a .git directory — containing a HEAD file and an objects directory. The repo's config file is read when present; a missing or unparseable config falls back to an empty Exgit.Config.

Returns

{:ok, %Repository{}} backed by ObjectStore.Disk/RefStore.Disk, or {:error, {:not_a_repository, reason}} when the path doesn't look like a git directory.

push(repo, dest, opts \\ [])

@spec push(Exgit.Repository.t(), remote(), keyword()) ::
  {:ok, map()} | {:error, term()}

Push local refs to a remote.

The :refspecs keyword takes a list where each entry is one of:

  • "refs/heads/main" — a ref name; pushes the local sha for that ref to the same name on the remote (creating it if absent).
  • {:delete, "refs/heads/branch"} — delete the ref on the remote.

When :refspecs is omitted, the repo's current branch — HEAD's symref target — is pushed. If HEAD is not a symbolic ref (e.g. detached or unset), the push fails with {:error, :no_refspecs}.

Errors

  • {:error, :no_refspecs}:refspecs omitted and HEAD doesn't point at a branch.
  • {:error, {:local_ref_not_found, name}} — a named refspec doesn't resolve in the local ref store. The whole push fails; nothing is sent to the remote. (Deleting a ref the remote doesn't have remains a silent no-op.)

Returns {:ok, %{ref_results: [...]}} on success.