NAME
Git::Native - Native Git for Perl via libgit2 (FFI, no fork/exec)
VERSION
version 0.005
SYNOPSIS
use Git::Native;
my $repo = Git::Native->open('/path/to/.git');
my $main = $repo->reference('refs/heads/main');
say $main->target; # commit OID
# Build a commit without forking git
my $blob_oid = $repo->blob_create_frombuffer("hello\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
Git::Native is a Moo wrapper around Git::Libgit2 (which binds libgit2 via FFI::Platypus). Use it instead of Git::Wrapper or Git::Repository when you want to do Git work without forking the git binary on every operation.
Contrast: - Git::Wrapper, Git::Repository: shell out to git - Git::Raw: XS bindings, unmaintained since 2022, known segfaults - Git::PurePerl: pure-Perl read-only, no push/pull
open
my $repo = Git::Native->open($path);
Open an existing repository at $path. Returns a Git::Native::Repository.
Opening can fail on ownership rather than on anything about the repository: libgit2 carries git's dubious-ownership check and refuses a path owned by another uid unless it is listed in safe.directory. Containers and CI hit this when an image is baked as root and then used as an unprivileged user — see is_owner_mismatch in Git::Native::Error.
On libgit2 1.5.1 that failure does not identify itself. With no safe.directory entry anywhere, which is the usual state, the open dies with GIT_ENOTFOUND and config value 'safe.directory' was not found — a not-found naming a key that was never set, and is_not_found is the predicate that answers, not is_owner_mismatch. safe.directory = * is not honoured either; the path has to be listed literally.
open_ext
my $repo = Git::Native->open_ext($start_path, %opts);
Same as git_repository_open_ext — walks up from $start_path. flags and ceiling_dirs are forwarded.
The ownership check described under open applies here as well, to the repository the walk lands on rather than to $start_path.
init
my $repo = Git::Native->init($path, bare => 1);
my $repo = Git::Native->init($path, initial_branch => 'main');
Initialise a new repository at $path, creating the directory if needed, and return a Git::Native::Repository. bare => 1 creates a bare repo.
initial_branch points HEAD at that branch instead of whatever default libgit2 was compiled with (Debian patches it to main, upstream still uses master) — worth setting whenever the branch name matters, since the branch is unborn until the first commit either way. A plain name is taken as refs/heads/$name.
clone
my $repo = Git::Native->clone('https://github.com/Getty/p5-git-native.git', $path);
Clone $url into $path and return the Git::Native::Repository.
Two limits today, both down to field offsets inside git_clone_options that shift between libgit2 versions: bare => 1 is refused (use init(bare => 1) plus a remote and a fetch), and there is no credentials callback, so the URL has to be one that needs no authentication — public HTTPS, git:// or file://. For an authenticated clone, init then "fetch" in Git::Native::Remote with credentials.
reference_name_is_valid
Git::Native->reference_name_is_valid('refs/heads/main'); # 1
Git::Native->reference_name_is_valid('refs/bad..name'); # 0
Class method. Returns true if libgit2 considers $name a valid reference name. No repository handle required.
set_config_search_path
# keep a test suite out of the developer's real git config
Git::Native->set_config_search_path(
system => $tmp, global => $tmp, xdg => $tmp, programdata => $tmp,
);
# prepend a directory, keeping what the level already searches
Git::Native->set_config_search_path( global => "$dir" . ':$PATH' );
# back to libgit2's compiled-in default
Git::Native->set_config_search_path( system => undef );
Class method. Tells libgit2 which directories to search for the non-repository config levels, i.e. where /etc/gitconfig, ~/.gitconfig and $XDG_CONFIG_HOME/git/config are looked up. Levels are given by name — system, global, xdg, programdata — and any combination may be passed in one call. Returns true.
Each argument is the level's search path, not the config file: libgit2 appends the filename it expects at that level (gitconfig for system and programdata, .gitconfig for global, git/config for xdg). Several directories may be given in one string, separated by :; the first one that actually holds that file wins — they are searched, not merged. A literal $PATH in the string (single-quote it in Perl) stands for what the level searches right now, so a path can be extended instead of replaced. undef resets the level to libgit2's compiled-in default; an empty string blanks it, so nothing is read at that level at all.
This is the only supported way to keep a process away from the system config: libgit2 compiles the /etc/gitconfig path in and ignores GIT_CONFIG_SYSTEM and GIT_CONFIG_NOSYSTEM alike (those reach the git CLI only). For global and xdg it is the more dependable half of the same job as pointing HOME elsewhere, and unlike HOME it still works after libgit2 has been initialised — the HOME guess happens once, inside git_libgit2_init.
Two properties worth knowing before calling this outside a test suite:
It is process-global. Every repository opened afterwards — including ones opened by unrelated code in the same process — uses the new paths. That is why this is a class method on Git::Native and not something on Git::Native::Repository.
It does not reach back. A repository that is already open keeps the config it resolved when it was opened; only the next
git_repository_opensees the change.
Needs Git::Libgit2 0.006 or newer, which is where git_libgit2_opts and the GIT_CONFIG_LEVEL_* constants arrived.
SEE ALSO
Alien::Libgit2, Git::Libgit2, FFI::Platypus, libgit2
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.