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
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
@type remote() :: String.t() | Exgit.Transport.File.t() | Exgit.Transport.HTTP.t() | struct()
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
@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 byObjectStore.Disk/RefStore.Diskand survives process death. Default: in-memory (lost on process exit).:lazy— whentrue, defer object fetching. Returns a%Repository{mode: :lazy}whose object store is anObjectStore.Promisorthat fetches on demand. The initial clone pulls refs only — any subsequent object read triggers awant <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:filteris set but the server doesn't advertisefiltercapability,:errorfails the clone and:ignoreproceeds as a full clone.:remote— remote name to record. Default:"origin".
Matrix of modes
| Options | Clone-time fetch | On read |
|---|---|---|
clone(url) | refs + all objects | local always |
clone(url, lazy: true) | refs only | per-object fetch |
clone(url, filter: {:blob, :none}) | refs + commits + trees | blobs fetched on read |
clone(url, filter: ..., lazy: true) | refs only | everything 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.
@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.
@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 byObjectStore.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.
@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.
@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}—:refspecsomitted andHEADdoesn'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.