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"}
  ]
end

Documentation 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:

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