NAME
Git::Native::Error - Exception class for Git::Native
VERSION
version 0.005
SYNOPSIS
use Git::Native::Error;
Git::Native::Error->throw(
code => -3,
klass => 11,
message => 'object not found',
);
DESCRIPTION
Throwable exception used by Git::Native when libgit2 reports an error. Attributes mirror the C git_error struct plus the return code.
Every failure raised by the distribution arrives as one of these — no low-level Git::Libgit2::Error escapes the wrapper layer. Being a Throwable::Error, it carries a stack trace and stringifies to the message followed by that trace, so a bare die $err stays readable.
use Try::Tiny;
try { $repo->reference('refs/heads/nope') }
catch {
die $_ unless ref $_ && $_->isa('Git::Native::Error') && $_->is_not_found;
...
};
code
The libgit2 return code, always negative for a real failure — -3 for GIT_ENOTFOUND, -1 for the generic GIT_ERROR. This is the discriminator to branch on, through the is_* predicates below or against the GIT_E* constants exported by Git::Libgit2.
klass
The git_error_t category the failure came from (the klass field of libgit2's git_error), defaulting to 0. A secondary signal: it says which subsystem complained, not what went wrong. Branch on code.
message
The error text libgit2 produced, e.g. "cannot set OID on symbolic reference". Inherited from Throwable::Error.
check_rc
use Git::Native::Error qw( check_rc );
check_rc Git::Libgit2::FFI::some_call(...);
Pass-through for a non-negative return code; on a negative one it reads libgit2's thread-local error (a low-level Git::Libgit2::Error) and re-throws it as a Git::Native::Error. Every wrapper in the distribution routes its FFI int-returns through this so no raw libgit2 error object escapes the API.
is_not_found / is_exists / is_auth / is_certificate / is_conflict / is_not_fast_forward / is_unborn_branch / is_invalid_spec / is_not_matched / is_locked / is_bare_repo / is_ambiguous / is_owner_mismatch
if ( my $err = $@ ) {
return if $err->is_not_found; # treat "missing" as empty
die $err;
}
Predicates over code for the common failure kinds (GIT_ENOTFOUND, GIT_EEXISTS, GIT_EAUTH, GIT_ECERTIFICATE, GIT_ECONFLICT, GIT_ENONFASTFORWARD, GIT_EUNBORNBRANCH, GIT_EINVALIDSPEC, GIT_EMODIFIED, GIT_ELOCKED, GIT_EBAREREPO, GIT_EAMBIGUOUS, GIT_EOWNER). Each returns 1 or 0. For other codes compare $err->code against the GIT_E* constants exported by Git::Libgit2.
is_bare_repo covers the worktree-only operations: a bare repository has no checkout, so status and status_for_path fail with GIT_EBAREREPO (-8) rather than returning an empty result. Code that walks a mixed set of repositories should treat it as "not applicable here", not as a hard error.
is_ambiguous comes from the abbreviated-OID lookup "object_by_prefix" in Git::Native::Repository: the prefix matched more than one object, so the caller has to ask for more characters. It is not raised for a prefix that is merely too short — libgit2 answers that with GIT_EAMBIGUOUS as well, but object_by_prefix croaks on it before the lookup precisely so this predicate keeps meaning "genuinely ambiguous".
is_owner_mismatch is the container and CI case. libgit2 carries git's CVE-2022-24765 defence: opening a repository whose path belongs to a different uid fails with GIT_EOWNER (-36) and the message repository path '...' is not owned by current user, unless that path is listed in the safe.directory multivar of the system or global config. An image that bakes a checkout in as root and then builds it as an unprivileged user hits this on the first Git::Native->open. The path libgit2 stats is the repository's working directory, not each file under it, so a workdir on foreign-owned storage is enough on its own.
Two measured quirks of libgit2 1.5.1 come with it, both of which make the failure harder to recognise than the code suggests. With no safe.directory entry anywhere in the config — the usual state — the ownership check never reaches its own error: libgit2 asks the config for the multivar, gets GIT_ENOTFOUND back and returns that, so the open fails with code -3 and the message config value 'safe.directory' was not found. That is the shape most affected users actually see, a not-found naming a key they never set, and is_not_found is what answers for it. As soon as any safe.directory entry exists, matching or not, the real GIT_EOWNER arrives. The second quirk: safe.directory = *, the blanket escape hatch git honours, is not honoured by libgit2 1.5.1 — the path has to be listed literally.
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.