ExGitEngine. Git
(ex_git_engine v0.9.3)
Copy Markdown
Erlang NIF that exposes a subset of libgit2's library functions.
Most functions available in this module are implemented in C for performance reasons. These functions are compiled into a dynamic loadable, shared library. They are called like any other Elixir functions.
As a NIF library is dynamically linked into the emulator process, this is the fastest way of calling C-code from Erlang (alongside port drivers). Calling NIFs requires no context switches. But it is also the least safe, because a crash in a NIF brings the emulator down too.
Example
Let's start with a basic code example showing the last commit author and message:
alias ExGitEngine.Git
# load repository
{:ok, repo} = Git.repository_open("/tmp/my-repo.git")
# fetch commit pointed by master
{:ok, :commit, _oid, commit} = Git.reference_peel(repo, "refs/heads/master")
# fetch commit author & message
{:ok, name, email, time, _offset} = Git.commit_author(commit)
{:ok, message} = Git.commit_message(commit)
IO.puts "Last commit by #{name} <#{email}>:"
IO.puts messageFirst we open our repository using repository_open/1, passing the path of the Git repository. We can fetch
a commit by passing the exact reference path to reference_peel/2. In our example, this allows us to retrieve
the commit refs/heads/master is pointing to.
This is one of many ways to fetch a given revision, reference_lookup/2 and reference_glob/2 offer similar
functionalities. There are other related functions such as revparse_single/2 and revparse_ext/2 which
provide support for parsing revspecs.
Walk commit history
In order to walk the commit ancestry chain, we have a few functions at our disposal: revwalk_new/1,
revwalk_push/2, revwalk_next/1, revwalk_reset/1, etc.
# create revision walk iterator
{:ok, revwalk} = Git.revwalk_new(repo)
# set root commit for traversal
:ok = Git.revwalk_push(walk, commit_oid)
# create (lazy) stream of ancestors from iterator
{:ok, stream} = Git.revwalk_stream(walk)
for ancestor_oid <- stream do
# fetch commit object
{:ok, :commit, commit} = Git.object_lookup(ancestor_oid)
# fetch commit message
{:ok, message} = Git.commit_message(commit)
IO.puts "#{Git.oid_fmt_short(ancestor_oid)} - #{message}"
endIn this example revwalk_new/1 returns a revwalk/0, a mutable C-like iteratable object. This means
that revwalk_push/2 mutates the revwalk object instead of returning a new object.
The revwalk_stream/1 function converts the revwalk iterator to a Enumerable.t/0 we can then use to walk
the commit ancestry chain.
When iterating through a commit's history, revwalk_sorting/2 and revwalk_simplify_first_parent/1 provide
conveniences for sorting and filtering while revwalk_push/3 can be used to hide specific commits.
Retrieve blobs & trees
In order to access the actual files and directories of a repository, we have to retrieve the Git blob and tree objects of a given revision. Here we simply list files and folders at the root directory:
# fetch commit tree
{:ok, tree} = Git.commit_tree(commit)
# fetch tree entries at /
{:ok, tree_entries} = Git.tree_entries(tree)
for {mode, type, oid, name} <- tree_entries do
# fetch tree entry object by oid (blob or tree)
case Git.object_lookup(repo, oid) do
{:ok, :blob, blob} ->
# fetch blob size
{:ok, blob_size} = Git.blob_size(blob)
IO.puts "#{name} -- #{blob_size} bytes"
{:ok, :tree, tree} ->
# fetch number of sub entries
{:ok, count} <- Git.tree_count(tree)
IO.puts "#{name}/ -- #{count} items"
end
endNote that tree_entries/1 and tree_nth/2 return a tuple in the form of {mode, type, oid, name}. In order to
call blob and tree specific functions such as blob_size/1 and tree_count/1, we still need to lookup the Git object using object_lookup/2.
Here's an other example showing a convenient way to retrieve a tree entry by path:
# fetch commit tree
{:ok, tree} = Git.commit_tree(commit)
# fetch blob by path
{:ok, mode, :blob, oid, name} Git.tree_bypath(tree, "README.md")
# fetch blob object by oid
{:ok, :blob, blob} = Git.object_lookup(repo, oid)
# fetch blob content
{:ok, data} = Git.blob_content(blob)
IO.binwrite dataCompare revisions
Now that we know how to access files and directories, it might be interesting to determine the changes between two versions. In order to do so, we need to compare two tree objects from different revisions:
# fetch tree for tag v1.2
{:ok, v1_2, :commit, _oid} = Git.revparse_single(repo, "v1.2")
{:ok, v1_2_tree} = Git.commit_tree(v1_2)
# fetch tree for tag v1.3
{:ok, v1_3, :commit, _oid} = Git.revparse_single(repo, "v1.3")
{:ok, v1_3_tree} = Git.commit_tree(v1_3)
# fetch diff between v1.2 and v1.3
{:ok, diff} = Git.diff_tree(repo, v1_2_tree, v1_3_tree)
# format diff to string
{:ok, patch} = Git.diff_format(diff, :patch)
IO.puts patchNote that diff_tree/4 takes options such as :pathspec allowing to filter changes based on a given path.
For example, we might want to see the different between two revision for a specific file. In this case we could modify the above example as follow to print changes affecting README.md:
# fetch diff between v1.2 and v1.3 for README.md
{:ok, diff} = Git.diff_tree(repo, v1_2_tree, v1_3_tree, pathspec: "README.md")
# format diff to string
{:ok, patch} = Git.diff_format(diff, :patch)
IO.puts patchCommit changes
Committing changes to a repository is done in a serie of distinct steps.
First, we add a new blob object to the repository:
# Blob content
blob_content = "Hello world\n"
# Open repository ODB
{:ok, odb} = Git.repository_get_odb(repo)
# Write new blob object
{:ok, blob_oid} = Git.odb_write(odb, blob_content, :blob)We create an in-memory index to stage our modifications (an index is a list of path names, each with permissions and the SHA1 of a blob object). In order to create a new tree object reflecting our changes, we have to assign our new blob object at a given path in the index and write the index back to the repository:
# Blob path
blob_path = "README"
# Create new index
{:ok, index} = Git.index_new()
# Read last commit tree into index
:ok = Git.index_read_tree(index, tree)
# Add newly added blob object to index
:ok = Git.index_add(repo, index, blob_oid, blob_path, byte_size(blob_content), 0o100644)
# Write index
{:ok, tree_oid} = Git.index_write_tree(index)The repository now contains a new blob object and a new tree object reflecting our changes. We now have all the ingredients for creating a commit and update the master branch accordingly:
# Commit message
commit_message = "Add README"
# Fetch repository default signature for authoring and committing
{:ok, sig_name, sig_email, sig_ts, sig_tz} = Git.signature_default(repo)
commit_author = {sig_name, sig_email, sig_ts, sig_tz}
commit_committer = commit_author
# Fetch reference to update
{:ok, :commit, parent_oid, _parent} = Git.reference_peel(repo, "refs/heads/master")
# Create new commit
{:ok, commit_oid} = Git.commit_create(repo, :undefined, commit_author, commit_committer, :undefined, commit_message, tree_oid, [parent_oid])
# Update master branch to new commit
:ok = Git.reference_create(repo, "refs/heads/master", :oid, commit_oid)
IO.puts "File #{blob_path} added in commit #{Git.oid_fmt(commit_oid)}."We have created a commit pointing at the new tree object. The commit refers the newly created tree and requires two user signatures (author and committer), a commit message and the commit ancestor(s).
Finally we have updated the master branch to point at our new commit.
Thread safety
Accessing a repo/0 or any NIF allocated pointer (blob/0, commit/0, config/0, etc.) from multiple
processes simultaneously is not safe. These pointers should never be shared across processes.
In order to access a repository in a concurrent manner, each process has to initialize it's own repository
resource using repository_open/1. Alternatively, the ExGitEngine.GitAgent module provides a similar API but
can use a dedicated process, so that its access can be serialized.
Summary
Functions
Returns blame hunks for path in repo.
Returns the raw content of the given blob.
Returns the size in bytes of the given blob.
Returns the author of the given commit.
Returns the committer of the given commit.
Creates a new commit with the given params.
Returns an arbitrary header field of the given commit.
Returns the message for the given commit.
Looks for a parent commit of the given commit by its index.
Returns the number of parents for the given commit.
Returns parent commits of the given commit.
Returns the signed data of the given commit — the commit content with the
signature header stripped. This is the byte sequence that a commit signature
covers, suitable for passing to a signature verification routine.
Returns the full raw header of the given commit.
Returns the time of the given commit.
Returns the tree for the given commit.
Returns the tree id for the given commit.
Returns the value of the config entry with the given name.
Returns the value of the config entry with the given name.
Returns a config handle for the given path.
Sets the config entry with the given name to val.
Sets the config entry with the given name to val.
Returns the number of deltas in the given diff.
Returns a list of deltas for the given diff.
Returns a binary represention of the given diff.
Returns stats for the given diff.
Returns a diff with the difference between two tree objects.
Returns the number of unique commits between two commit objects.
Adds or updates the given entry.
Retrieves an entry contained in the index given its relative path.
Clears the contents (all the entries) of the given index.
Returns the number of entries in the given index.
Creates an new in-memory index object.
Looks for an entry by its position in the given index.
Reads the given tree into the given index file with stats.
Removes an entry from the given index.
Removes all entries from the given index under a given directory.
Writes the given index from memory back to disk using an atomic file lock.
Writes the given index as a tree.
Writes the given index as a tree.
Returns the libgit2 library version.
Finds the merge base (common ancestor) OID for two commits.
Merges two commits and returns the merged index.
Returns {:ok, index} on success, {:error, :conflict} if there are conflicts.
Returns the OID for the given obj.
Looks for an object with the given oid.
Returns the repository that owns the given obj.
Inflates the given data with zlib.
Returns an ODB write-pack for the given odb.
Returns true if the given oid exists in odb; elsewise returns false.
Returns the OID of an object type and raw data.
Return the uncompressed, raw data of an ODB object.
Writes the given object data with the given type into the odb.
Writes the given PACK data into the odb.
Appends the given data to the odb_writepack.
Commits the written data to the odb_writepack.
Returns the SHA hash for the given oid.
Returns the abbreviated SHA hash for the given oid.
Returns the OID for the given SHA hash.
Returns a PACK file for the given pack.
Inserts commit as well as the completed referenced tree.
Inserts objects as given by the walk.
Creates a new PACK object for the given repo.
Returns true if tree matches the given pathspec; otherwise returns false.
Creates a new reference name which points to an object or to an other reference.
Deletes an existing reference.
Looks for a reference by DWIMing its short_name.
Similar to reference_list/1 but allows glob patterns.
Returns an iterator for the references that match the specific glob pattern.
Returns all references for the given repo.
Returns true if a reflog exists for the given reference name.
Looks for a reference by name.
Returns the next reference.
Recursively peels the given reference name until an object of type type is found.
Resolves a symbolic reference to a direct reference.
Returns a stream for the references that match the specific glob pattern.
Looks for a reference by name and returns its id.
Reads the number of entry for the given reflog name.
Deletes the reflog for the given reference name.
Reads the reflog for the given reference name.
Returns true if repo is bare; elsewise returns false.
Clones a repository from url into local_path. Runs on a dirty IO scheduler.
Looks for a repository and returns its path.
Returns true if repo is empty; elsewise returns false.
Returns the config for the given repo.
Returns the index for the given repository.
Returns the ODB for the given repository.
Returns the absolute path for the given repo.
Returns the normalized path to the working directory for the given repo.
Initializes a new repository at the given path.
Returns a repository handle for the path.
Finds a single object and intermediate reference, as specified by the given revision.
Finds a single object, as specified by the given revision.
Creates a new revision walk object for the given repo.
Returns the next commit from the given revision walk.
Returns a PACK file for the given walk.
Adds a new root for the traversal.
Returns the repository on which the given walker is operating.
Resets the revision walk for reuse.
Simplifies the history by first-parent.
Changes the sorting mode when iterating through the repository's contents.
Returns a stream for the given revision walk.
Returns the default signature for the given repo.
Creates a new signature with the given name and email.
Creates a new signature with the given name, email and time.
Returns the author of the given tag.
Returns all tags for the given repo.
Returns the message of the given tag.
Returns the name of the given tag.
Recursively peels the given tag until a non tag object is found.
Retrieves a tree entry owned by the given tree, given its id.
Retrieves a tree entry contained in the given tree or in any of its subtrees, given its relative path.
Returns the number of entries listed in the given tree.
Returns all entries in the given tree.
Looks for a tree entry by its position in the given tree.
Adds a new working tree for the given repo
Prunes a working tree.
Types
@type blob() :: reference()
@type commit() :: reference()
@type config() :: reference()
@type diff() :: reference()
@type diff_delta() :: {diff_file(), diff_file(), non_neg_integer(), non_neg_integer()}
@type diff_file() :: {oid(), binary(), integer(), non_neg_integer()}
@type diff_format() :: :patch | :patch_header | :raw | :name_only | :name_status
@type index() :: reference()
@type index_entry() :: {integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), integer(), binary(), non_neg_integer(), non_neg_integer(), binary()}
@type indexer_progress() :: reference()
@type obj_type() :: :blob | :commit | :tree | :tag
@type odb() :: reference()
@type odb_type() :: atom()
@type odb_writepack() :: reference()
@type odb_writepack_progress() :: map()
@type oid() :: binary()
@type pack() :: reference()
@type ref_iter() :: reference()
@type ref_type() :: :oid | :symbolic
@type reflog_entry() :: {binary(), binary(), non_neg_integer(), non_neg_integer(), oid(), oid(), binary()}
@type repo() :: reference()
@type revwalk() :: reference()
@type revwalk_sort() :: :sort_topo | :sort_time | :sort_reverse
@type signature() :: {binary(), binary(), non_neg_integer(), non_neg_integer()}
@type tag() :: reference()
@type tree() :: reference()
@type worktree() :: reference()
Functions
@spec blame_file(repo(), Path.t(), binary() | nil) :: {:ok, [{binary(), pos_integer(), pos_integer(), binary(), binary(), integer()}]} | {:error, term()}
Returns blame hunks for path in repo.
Each hunk is a tuple {oid, start_line, line_count, author_name, author_email, timestamp} where
start_line is 1-based, and timestamp is Unix seconds.
Returns the raw content of the given blob.
Returns the size in bytes of the given blob.
@spec commit_author(commit()) :: {:ok, binary(), binary(), non_neg_integer(), non_neg_integer()} | {:error, term()}
Returns the author of the given commit.
@spec commit_committer(commit()) :: {:ok, binary(), binary(), non_neg_integer(), non_neg_integer()} | {:error, term()}
Returns the committer of the given commit.
@spec commit_create( repo(), binary() | :undefined, signature(), signature(), binary() | :undefined, binary(), oid(), [binary()] ) :: {:ok, oid()} | {:error, term()}
Creates a new commit with the given params.
Returns an arbitrary header field of the given commit.
Returns the message for the given commit.
@spec commit_parent(commit(), non_neg_integer()) :: {:ok, oid(), commit()} | {:error, term()}
Looks for a parent commit of the given commit by its index.
@spec commit_parent_count(commit()) :: {:ok, non_neg_integer()} | {:error, term()}
Returns the number of parents for the given commit.
@spec commit_parents(commit()) :: {:ok, Enumerable.t()} | {:error, term()}
Returns parent commits of the given commit.
Returns the signed data of the given commit — the commit content with the
signature header stripped. This is the byte sequence that a commit signature
covers, suitable for passing to a signature verification routine.
Returns the full raw header of the given commit.
@spec commit_time(commit()) :: {:ok, non_neg_integer(), non_neg_integer()} | {:error, term()}
Returns the time of the given commit.
Returns the tree for the given commit.
Returns the tree id for the given commit.
Returns the value of the config entry with the given name.
Returns the value of the config entry with the given name.
Returns a config handle for the given path.
Sets the config entry with the given name to val.
Sets the config entry with the given name to val.
@spec diff_delta_count(diff()) :: {:ok, non_neg_integer()} | {:error, term()}
Returns the number of deltas in the given diff.
@spec diff_deltas(diff()) :: {:ok, [{diff_delta(), [{diff_hunk(), [diff_line()]}]}]} | {:error, term()}
Returns a list of deltas for the given diff.
@spec diff_format(diff(), diff_format()) :: {:ok, binary()} | {:error, term()}
Returns a binary represention of the given diff.
@spec diff_stats(diff()) :: {:ok, non_neg_integer(), non_neg_integer(), non_neg_integer()} | {:error, term()}
Returns stats for the given diff.
Returns a diff with the difference between two tree objects.
@spec graph_ahead_behind(repo(), oid(), oid()) :: {:ok, non_neg_integer(), non_neg_integer()} | {:error, term()}
Returns the number of unique commits between two commit objects.
@spec index_add(index(), index_entry()) :: :ok | {:error, term()}
Adds or updates the given entry.
@spec index_bypath(index(), Path.t(), non_neg_integer()) :: {:ok, integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), integer(), binary(), non_neg_integer(), non_neg_integer(), binary()} | {:error, term()}
Retrieves an entry contained in the index given its relative path.
Clears the contents (all the entries) of the given index.
@spec index_count(index()) :: non_neg_integer()
Returns the number of entries in the given index.
Creates an new in-memory index object.
@spec index_nth(index(), non_neg_integer()) :: {:ok, integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), integer(), binary(), non_neg_integer(), non_neg_integer(), binary()} | {:error, term()}
Looks for an entry by its position in the given index.
Reads the given tree into the given index file with stats.
@spec index_remove(index(), Path.t(), non_neg_integer()) :: :ok | {:error, term()}
Removes an entry from the given index.
@spec index_remove_dir(index(), Path.t(), non_neg_integer()) :: :ok | {:error, term()}
Removes all entries from the given index under a given directory.
Writes the given index from memory back to disk using an atomic file lock.
Writes the given index as a tree.
Writes the given index as a tree.
Returns the libgit2 library version.
Finds the merge base (common ancestor) OID for two commits.
@spec merge_commits(repo(), obj(), obj()) :: {:ok, index()} | {:error, :conflict} | {:error, term()}
Merges two commits and returns the merged index.
Returns {:ok, index} on success, {:error, :conflict} if there are conflicts.
Returns the OID for the given obj.
Looks for an object with the given oid.
Returns the repository that owns the given obj.
@spec object_zlib_inflate(binary(), pos_integer()) :: {:ok, iodata(), non_neg_integer()} | {:error, term()}
Inflates the given data with zlib.
@spec odb_get_writepack(odb()) :: {:ok, odb_writepack()} | {:error, term()}
Returns an ODB write-pack for the given odb.
Returns true if the given oid exists in odb; elsewise returns false.
Returns the OID of an object type and raw data.
The resulting SHA-1 OID will be the identifier for the data buffer as if the data buffer it were to written to the ODB.
Return the uncompressed, raw data of an ODB object.
Writes the given object data with the given type into the odb.
Writes the given PACK data into the odb.
@spec odb_writepack_append(odb_writepack(), binary(), odb_writepack_progress()) :: {:ok, odb_writepack_progress()} | {:error, term()}
Appends the given data to the odb_writepack.
@spec odb_writepack_commit(odb_writepack(), odb_writepack_progress()) :: :ok | {:error, term()}
Commits the written data to the odb_writepack.
Returns the SHA hash for the given oid.
Returns the abbreviated SHA hash for the given oid.
Returns the OID for the given SHA hash.
Returns a PACK file for the given pack.
Inserts commit as well as the completed referenced tree.
Inserts objects as given by the walk.
Creates a new PACK object for the given repo.
Returns true if tree matches the given pathspec; otherwise returns false.
@spec reference_create(repo(), binary(), ref_type(), binary() | oid(), boolean()) :: :ok | {:error, term()}
Creates a new reference name which points to an object or to an other reference.
Deletes an existing reference.
Looks for a reference by DWIMing its short_name.
Similar to reference_list/1 but allows glob patterns.
Returns an iterator for the references that match the specific glob pattern.
Returns all references for the given repo.
Returns true if a reflog exists for the given reference name.
@spec reference_lookup(repo(), binary()) :: {:ok, binary(), ref_type(), binary()} | {:error, term()}
Looks for a reference by name.
@spec reference_next(ref_iter()) :: {:ok, binary(), binary(), ref_type(), binary()} | {:error, term()}
Returns the next reference.
@spec reference_peel(repo(), binary(), obj_type() | :undefined) :: {:ok, obj_type(), oid(), obj()} | {:error, term()}
Recursively peels the given reference name until an object of type type is found.
Resolves a symbolic reference to a direct reference.
@spec reference_stream(repo(), binary() | :undefined) :: {:ok, Enumerable.t()} | {:error, term()}
Returns a stream for the references that match the specific glob pattern.
Looks for a reference by name and returns its id.
@spec reflog_count(repo(), binary()) :: {:ok, pos_integer()} | {:error, term()}
Reads the number of entry for the given reflog name.
Deletes the reflog for the given reference name.
@spec reflog_read(repo(), binary()) :: {:ok, [reflog_entry()]} | {:error, term()}
Reads the reflog for the given reference name.
Returns true if repo is bare; elsewise returns false.
Clones a repository from url into local_path. Runs on a dirty IO scheduler.
Looks for a repository and returns its path.
Returns true if repo is empty; elsewise returns false.
Returns the config for the given repo.
Returns the index for the given repository.
Returns the ODB for the given repository.
Returns the absolute path for the given repo.
Returns the normalized path to the working directory for the given repo.
Initializes a new repository at the given path.
Returns a repository handle for the path.
@spec revparse_ext(repo(), binary()) :: {:ok, obj(), obj_type(), oid(), binary() | nil} | {:error, term()}
Finds a single object and intermediate reference, as specified by the given revision.
Finds a single object, as specified by the given revision.
Creates a new revision walk object for the given repo.
Returns the next commit from the given revision walk.
Returns a PACK file for the given walk.
Adds a new root for the traversal.
Returns the repository on which the given walker is operating.
Resets the revision walk for reuse.
Simplifies the history by first-parent.
@spec revwalk_sorting(revwalk(), [revwalk_sort()]) :: :ok | {:error, term()}
Changes the sorting mode when iterating through the repository's contents.
@spec revwalk_stream(revwalk()) :: {:ok, ExGitEngine.GitStream.t()} | {:error, term()}
Returns a stream for the given revision walk.
@spec signature_default(repo()) :: {:ok, binary(), binary(), non_neg_integer(), non_neg_integer()} | {:error, term()}
Returns the default signature for the given repo.
Creates a new signature with the given name and email.
@spec signature_new(binary(), binary(), non_neg_integer()) :: {:ok, binary(), binary(), non_neg_integer(), non_neg_integer()} | {:error, term()}
Creates a new signature with the given name, email and time.
@spec tag_author(tag()) :: {:ok, binary(), binary(), non_neg_integer(), non_neg_integer()} | {:error, term()}
Returns the author of the given tag.
Returns all tags for the given repo.
Returns the message of the given tag.
Returns the name of the given tag.
Recursively peels the given tag until a non tag object is found.
Retrieves a tree entry owned by the given tree, given its id.
@spec tree_bypath(tree(), Path.t()) :: {:ok, integer(), atom(), binary(), binary()} | {:error, term()}
Retrieves a tree entry contained in the given tree or in any of its subtrees, given its relative path.
@spec tree_count(tree()) :: {:ok, non_neg_integer()} | {:error, term()}
Returns the number of entries listed in the given tree.
@spec tree_entries(tree()) :: {:ok, Enumerable.t()} | {:error, term()}
Returns all entries in the given tree.
@spec tree_nth(tree(), non_neg_integer()) :: {:ok, integer(), atom(), binary(), binary()} | {:error, term()}
Looks for a tree entry by its position in the given tree.
@spec worktree_add(repo(), binary(), binary(), binary() | :undefined) :: {:ok, worktree()} | {:error, term()}
Adds a new working tree for the given repo
Prunes a working tree.