README.md

# ExGix

This project provides high-level git operations through rustler bindings to the [gitoxide](https://github.com/GitoxideLabs/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](https://hex.pm/docs/publish), the package can be installed
by adding `ex_gix` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:ex_gix, "~> 0.1.0"}
  ]
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). 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

```elixir
# 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:

```elixir
{: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`):

```elixir
# 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:

```elixir
{: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:

```elixir
{: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:

```elixir
{: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](https://github.com/saleyn/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`:

```bash
export EX_GIX_BUILD=1
mix deps.get
mix compile
mix test
```