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.

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_ambiguous true. 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_found true, 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's GIT_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.