NAME
Git::Native::Repository - A libgit2 repository handle
VERSION
version 0.005
SYNOPSIS
my $repo = Git::Native->open('/path/to/.git');
my $main = $repo->reference('refs/heads/main');
say $main->target;
my $blob_oid = $repo->blob_create_frombuffer("hi\n");
my $tb = $repo->tree_builder;
$tb->insert(name => 'hi.txt', oid => $blob_oid, mode => 0100644);
my $tree_oid = $tb->write;
my $commit_oid = $repo->commit_create(
update_ref => 'HEAD',
tree => $tree_oid,
parents => [$main->target],
message => 'add greeting',
);
DESCRIPTION
The main entry point for working with a Git repository through Git::Native. Wraps git_repository*; freed automatically.
workdir
my $dir = $repo->workdir; # '/path/to/checkout/'
Absolute path of the working directory, with a trailing slash. undef for a bare repository, so check is_bare before using it as a path.
gitdir
my $dir = $repo->gitdir; # '/path/to/checkout/.git/'
Absolute path of the repository directory — the .git directory, or the repository itself when bare — with a trailing slash.
is_bare
Returns 1 for a bare repository, 0 otherwise. A bare repository has no checkout: workdir is undef and the worktree-only operations status / status_for_path fail with GIT_EBAREREPO.
reference
my $ref = $repo->reference('refs/heads/main');
Look up a reference by its full name. Returns a Git::Native::Reference, or throws a Git::Native::Error for which is_not_found is true when there is no such reference. Use reference_exists to test without an exception.
reference_exists
if ( $repo->reference_exists('refs/heads/main') ) { ... }
Returns 1 or 0 for the full reference name. Never throws for a missing reference.
reference_names
my $all = $repo->reference_names;
my $tags = $repo->reference_names( glob => 'refs/tags/*' );
Arrayref of full reference names. glob filters libgit2-side, which is cheaper than listing everything and grepping in Perl.
reference_create
my $ref = $repo->reference_create(
'refs/heads/main', $new_oid,
expected_old => $old_oid,
);
Create or update a direct reference. force and message retain their usual meaning for unconditional calls. Supplying expected_old makes the update compare-and-swap: the write succeeds only while the reference points to that OID, otherwise it throws a Git::Native::Error for which is_not_matched is true. An explicit expected_old => undef requires the reference to be absent and creates it atomically.
A correct retry loop has to cover two failure kinds, both normal under contention and both retryable: is_not_matched (another writer moved the reference in the meantime) and is_locked (a concurrent writer currently holds the refs/<name>.lock file). Retrying only on is_not_matched silently loses updates.
reference_set_target
my $ref = $repo->reference_set_target(
'refs/heads/main', $new_oid,
expected_old => $old_oid,
);
Atomically update an existing direct reference by name. expected_old is required. A stale expected OID throws a Git::Native::Error for which is_not_matched is true and leaves the reference unchanged.
As with reference_create, a correct retry loop covers is_not_matched (the reference moved under us) as well as is_locked (a concurrent writer holds refs/<name>.lock); both are expected under contention and both are retryable.
libgit2 does not provide a git_reference_set_target_matching function. This method looks up the reference, checks the caller's expected OID, then uses git_reference_set_target, which atomically guards its write against the OID in that looked-up reference.
reference_symbolic_create
$repo->reference_symbolic_create('refs/heads/current', 'refs/heads/main');
Create a symbolic reference — one that points at another reference's name rather than at an OID. force overwrites an existing reference, message goes into the reflog. Returns the new Git::Native::Reference.
reference_delete
$repo->reference_delete('refs/heads/topic');
Delete a reference by full name and return the repository. Idempotent: deleting a reference that does not exist succeeds, exactly like git update-ref -d. That is deliberately unlike reference and tag, which throw is_not_found for something missing — so a successful reference_delete is no evidence the reference was ever there.
head
my $head = $repo->head or say 'no commits yet';
The resolved HEAD reference as a Git::Native::Reference, or undef when HEAD is unborn (a freshly initialised repository, before its first commit) or missing altogether. Those two cases do not throw, so no eval is needed; use head_unborn to tell them apart.
head_unborn
Returns 1 when HEAD points at a branch that has no commit yet, 0 otherwise. This is the normal state directly after Git::Native->init.
head_detached
Returns 1 when HEAD points straight at a commit instead of at a branch, 0 otherwise.
set_head
$repo->set_head('refs/heads/main');
Point HEAD at $refname and return the repository. The branch may be unborn — this is how a freshly initialised repository gets its default branch pinned. A refname outside refs/heads/ (a tag, say) leaves HEAD detached at that reference's target instead. An invalid reference name throws a Git::Native::Error for which is_invalid_spec is true.
object
my $obj = $repo->object($oid); # Blob / Tree / Commit / Tag
Look up an object of unknown kind and return the wrapper matching its actual type: Git::Native::Blob, Git::Native::Tree, Git::Native::Commit or Git::Native::Tag. Croaks for an object type this distribution has no wrapper for. $oid may be a Git::Native::Oid or a hex string, as everywhere below.
The OID has to be complete — 40 hex characters. Resolving an abbreviation is a separate operation, because it can fail in a way an exact lookup cannot: see object_by_prefix.
object_by_prefix
my $obj = $repo->object_by_prefix('c2981a9'); # git rev-parse c2981a9
Resolve an abbreviated OID against this repository's object database and return the same typed wrapper object would. This is what git rev-parse does with a short SHA; object deliberately does not accept one, since only this path can come back ambiguous.
$prefix is 4 to 40 hex characters (a full Git::Native::Oid is accepted too, and then behaves exactly like object). Three ways it can go wrong:
More than one object matches — a Git::Native::Error with
is_ambiguoustrue. The fix is more characters, so this is worth catching and reporting rather than dying on.Nothing matches — a Git::Native::Error with
is_not_foundtrue, exactly as for a full OID.Fewer than 4 characters, more than 40, or not hex at all — a croak, before libgit2 is called. libgit2's own answer to a too-short prefix is
GIT_EAMBIGUOUS, indistinguishable from a real ambiguity; a prefix that short is a bug in the calling code, not a property of the repository, so it is rejected here instead. Four is libgit2'sGIT_OID_MINPREFIXLEN.
"from_hex" in Git::Native::Oid stays strict about the full 40 characters: an Oid is a value with no repository behind it, and an abbreviation cannot be expanded without one.
blob
my $blob = $repo->blob($oid);
Look up a blob and return a Git::Native::Blob. Type-asserting: an OID naming an object of another kind throws a Git::Native::Error ("the requested type does not match the type in the ODB") rather than quietly returning something else — use object when the kind is not known up front.
libgit2 reports that mismatch as GIT_ENOTFOUND, so is_not_found is true on the error even though the object does exist; is_not_found alone cannot tell "no such object" from "wrong type". The same applies to tree and commit.
tree
my $tree = $repo->tree($oid);
Look up a tree and return a Git::Native::Tree. Type-asserting in the same way as blob.
commit
my $commit = $repo->commit($oid);
Look up a commit and return a Git::Native::Commit. Type-asserting in the same way as blob.
blob_create_frombuffer
my $oid = $repo->blob_create_frombuffer("hello\n");
Write a blob straight from a Perl scalar into the object database and return its Git::Native::Oid. Index and working directory are untouched.
tree_builder
my $tb = $repo->tree_builder;
$tb->insert( name => 'hi.txt', oid => $blob_oid, mode => 0100644 );
my $tree_oid = $tb->write;
A fresh, empty Git::Native::TreeBuilder for composing a tree object.
commit_create
my $oid = $repo->commit_create(
update_ref => 'HEAD',
tree => $tree_oid,
parents => [ $head->target ],
message => "add greeting\n",
);
Write a commit object and return its Git::Native::Oid. tree and message are required.
parents is an arrayref of OIDs: [] (or omitted) makes a root commit, one entry the ordinary case, two or more a merge commit. update_ref names a reference to move to the new commit — usually 'HEAD', which follows the symbolic HEAD to its branch and creates that branch if it is still unborn. Omitting update_ref writes the commit without pointing any reference at it.
author and committer take a Git::Native::Signature; author defaults to signature_default and committer to author. message_encoding defaults to UTF-8.
branch
my $b = $repo->branch('main');
my $r = $repo->branch( 'origin/main',
type => Git::Native::Branch::GIT_BRANCH_REMOTE );
Look up a branch by its short name and return a Git::Native::Branch. type defaults to Git::Native::Branch::GIT_BRANCH_LOCAL. Throws a Git::Native::Error for which is_not_found is true when there is no such branch.
has_branch
The non-throwing form of branch: returns 1 or 0, and takes the same type option.
branch_create
my $b = $repo->branch_create('topic', $commit_oid);
Create a local branch pointing at $target, which must name a commit. force moves an existing branch of that name; without it a duplicate throws is_exists. Returns the new Git::Native::Branch.
branches
my $all = $repo->branches;
my $locals = $repo->branches( type => Git::Native::Branch::GIT_BRANCH_LOCAL );
Arrayref of Git::Native::Branch objects. type defaults to Git::Native::Branch::GIT_BRANCH_ALL — local plus remote-tracking.
tag
my $tag = $repo->tag('v1.0');
Look up an annotated tag by short or full (refs/tags/...) name and return a Git::Native::Tag.
Returns undef for a lightweight tag: those are plain references under refs/tags/* with no tag object behind them, so there is nothing to wrap — read them through reference instead. A name with no reference at all throws is_not_found, so undef specifically means "exists, but lightweight".
tag_create
my $oid = $repo->tag_create('v1.0', $commit_oid, message => "release\n");
my $oid = $repo->tag_create('v1.0-lw', $commit_oid); # lightweight
Tag any object — commit, tree or blob. With message this creates an annotated tag and returns the new tag object's OID; without one it creates a lightweight tag and returns the target's OID. tagger takes a Git::Native::Signature and defaults to signature_default. force replaces an existing tag of that name; otherwise a duplicate throws is_exists.
tag_delete
$repo->tag_delete('v1.0');
Delete a tag by short name and return the repository. Unlike reference_delete this is not idempotent: deleting a tag that does not exist throws a Git::Native::Error for which is_not_found is true.
tag_names
my $names = $repo->tag_names;
my $v1 = $repo->tag_names( pattern => 'v1.*' );
Arrayref of short tag names (no refs/tags/ prefix), annotated and lightweight alike. pattern is an fnmatch-style glob applied libgit2-side.
remote
my $origin = $repo->remote('origin');
Look up a configured remote by name and return a Git::Native::Remote. Throws a Git::Native::Error for which is_not_found is true when the remote is not configured.
has_remote
The non-throwing form of remote: returns 1 or 0.
remote_create
my $r = $repo->remote_create('origin', 'https://example.invalid/repo.git');
Create a named remote with the default fetch refspec and persist it to the repository config. Returns a Git::Native::Remote.
remote_anonymous
my $r = $repo->remote_anonymous('file:///srv/git/repo.git');
An in-memory remote for a one-off fetch or ref listing. Nothing is written to the config, so has_remote stays false afterwards and $r->name is undef.
config
$repo->config->set_string('user.name', 'Ada');
The repository's live, writable Git::Native::Config. Writes go here; reads do not — libgit2 refuses get_string on a live config. Use config_snapshot, config_string or config_bool to read.
config_snapshot
my $snap = $repo->config_snapshot;
say $snap->get_string('user.email');
A read-only, point-in-time Git::Native::Config. Values written through config afterwards are not visible in an existing snapshot; take a fresh one.
config_string
my $name = $repo->config_string('user.name');
One string value, read off a freshly taken snapshot. undef when the key is unset anywhere in the config chain.
config_bool
my $bare = $repo->config_bool('core.bare'); # 1 / 0 / undef
One value parsed by git's boolean rules (true / yes / on / non-zero numbers against false / no / off / 0), read off a freshly taken snapshot. undef when the key is unset; a value that is set but not a boolean throws a Git::Native::Error.
revwalker
my $walk = $repo->revwalker;
$walk->push_head;
say $_->hex for @{ $walk->all };
A fresh Git::Native::Revwalker for this repository. It yields nothing until seeded with one of its push_* methods.
status
my $st = $repo->status;
say "$_ $st->{$_}" for sort keys %$st;
Hashref of path => flags for every path that is not clean, where flags is libgit2's GIT_STATUS_* bitmask combining the index-side and worktree-side bits. Clean paths are absent, so an empty hashref means a clean tree.
On a bare repository this throws a Git::Native::Error for which is_bare_repo is true — it does not return an empty result. Code walking a mixed set of repositories has to handle that explicitly.
status_for_path
my $flags = $repo->status_for_path('README.md');
The same GIT_STATUS_* bitmask for a single path, relative to the working directory. A path git knows nothing about — neither tracked nor present on disk — throws is_not_found; a bare repository throws is_bare_repo.
index
my $index = $repo->index;
if ( $index->is_tracked_under('tasks') ) { ... }
The repository's index as a Git::Native::Index — the tracked-path list git ls-files prints, queryable without shelling out.
Every call re-reads the index file from disk, so a fresh $repo->index always reflects what is on disk right now. That is not free of consequences and is worth knowing in two directions:
An Index object you hold on to does not update itself, which is what this accessor is for. What it also is not is a snapshot of when you took it: libgit2 keeps one cached git_index* per repository and every call here hands back that same object, so the re-read this one does is felt by Index objects handed out earlier. Neither direction is a guarantee — take a fresh one from here, or call "reload" in Git::Native::Index, whenever the answer has to be current.
And because this re-reads, any in-memory modification of the index would be discarded by the next call. Nothing in Git::Native can make one today — Git::Native::Index is read-only — but that is the reason the accessor is free to re-read rather than a promise it will keep if that changes.
signature_default
my $sig = $repo->signature_default;
A Git::Native::Signature built from the repository's effective user.name / user.email configuration, stamped with the current time.
When neither is configured this falls back to Git::Native <unconfigured@example.invalid> rather than failing, so commit_create and tag_create still work in an unconfigured environment. Pass an explicit author / tagger where that placeholder would be wrong.
SUPPORT
Issues
Please report bugs and feature requests on GitHub at https://github.com/Getty/p5-git-native/issues.
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHOR
Torsten Raudssus <getty@cpan.org>
COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.