ExGix
This project provides high-level git operations through rustler bindings to the gitoxide library, which is a pure Rust implementation of Git. The goal of this project is to provide a high-performance, native Git library for Elixir applications.
Installation
If available in Hex, the package can be installed
by adding ex_gix to your list of dependencies in mix.exs:
def deps do
[
{:ex_gix, "~> 0.1.0"}
]
endDocumentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/ex_gix.
Usage Guide
ExGix provides a high-level API for interacting with Git repositories. Here are some examples of what you can do:
Opening and Initializing Repositories
# Open an existing repository
{:ok, repo} = ExGix.open(".")
# Or discover a repository from a subdirectory
{:ok, repo} = ExGix.discover("test/some/nested/dir")
# Initialize a new repository
{:ok, repo} = ExGix.init("/path/to/new/repo")
# Get repository properties
ExGix.is_bare(repo) #=> false
ExGix.path(repo) #=> "/path/to/repo/.git"
{:ok, head_name} = ExGix.head_name(repo) #=> {:ok, "refs/heads/main"}Reading Files and Objects
You can read the contents of files from the repository without needing a working tree:
{:ok, repo} = ExGix.open(".")
# Read a file directly from HEAD
{:ok, content} = ExGix.cat_file(repo, "HEAD:README.md")
# Resolve references to object IDs
{:ok, commit_id} = ExGix.rev_parse(repo, "HEAD")
{:ok, blob_id} = ExGix.rev_parse(repo, "HEAD:README.md")
# Read using an object ID
{:ok, content} = ExGix.cat_file(repo, blob_id)
Working with Object IDs (ExGix.ObjectId):
# Parse from hex
{:ok, oid} = ExGix.ObjectId.from_hex("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")
# Convert to hex
hex_string = ExGix.ObjectId.to_hex(oid)
# Check object properties
ExGix.ObjectId.is_empty_tree(oid)
ExGix.ObjectId.is_empty_blob(oid)Checking Repository Status
You can check the status of files in the working directory compared to the index and HEAD:
{:ok, repo} = ExGix.Repository.open(".")
{:ok, status_items} = ExGix.Repository.status(repo)
# Example output item:
# %ExGix.StatusItem{
# location: :index_worktree, # or :tree_index
# path: "lib/ex_gix.ex",
# status: :modified # :modified, :untracked, :added, etc.
# }Listing Tree Contents
You can list the contents of a tree (directory) at a specific revision:
{:ok, repo} = ExGix.Repository.open(".")
# List root directory of HEAD
{:ok, items} = ExGix.Repository.ls_tree(repo, "HEAD")
# List a specific subdirectory
{:ok, items} = ExGix.Repository.ls_tree(repo, "HEAD:lib")
# List recursively
{:ok, items} = ExGix.Repository.ls_tree(repo, "HEAD", recursive: true)
# Example output item:
# %ExGix.TreeItem{
# filename: "README.md",
# kind: :blob, # :blob, :tree, :exe, etc.
# mode: "100644",
# oid: <reference> # Object reference
# }Committing Changes
You can create new commits programmatically:
{:ok, repo} = ExGix.Repository.open(".")
# Define the author/committer signature
sig = %ExGix.Signature{
name: "Committer Name",
email: "committer@example.com",
time_seconds: System.os_time(:second),
time_offset: 0
}
# Commit changes (uses the current index/staged changes)
{:ok, commit_id} = ExGix.Repository.commit_as(
repo,
sig, # Author
sig, # Committer
"Test commit message" # Message
)Why ExGix?
Previous, the popular choice for Git bindings in Elixir was egit, which is a wrapper around the C library libgit2. However it has several limitations compared to a native Rust implementation:
- Performance: ExGix is designed to be fast, efficient and support multithreading access.
- Safety: ExGix's API is safer and closer to idiomatic Elixir, reducing the likelihood of bugs and memory safety issues that can arise with C bindings.
Architecture & Design
Opening a Git repository in ExGix creates a RepoResource that contains a gix::ThreadSafeRepository. This ThreadSafeRepository is a lightweight, thread-safe wrapper around the underlying Git repository data structures.
In every native function call, ExGix creates a lightweight, thread-local Repository instance from the shared ThreadSafeRepository using .to_thread_local(), ensuring that operations run concurrently without bottlenecking the Erlang schedulers.
Local Development
If you want to contribute to ex_gix or build the Rust NIF locally instead of using precompiled binaries, you need to set the EX_GIX_BUILD environment variable to 1:
export EX_GIX_BUILD=1
mix deps.get
mix compile
mix test