Git-Native
view release on metacpan or search on metacpan
# Git::Native
High-level Moo wrapper over L<Git::Libgit2>. This is the API CPAN
consumers see. Name contrasts deliberately with `Git::Wrapper` and
`Git::Repository` (both shell out to the `git` binary).
## Stack
`Git::Native` (Moo) -> `Git::Libgit2` (FFI) -> `Alien::Libgit2` (libgit2 C lib).
## Class Layout
```
Git::Native ->open / ->init($path, bare =>?, initial_branch =>?) / ->clone($url, $path)
Git::Native::Repository workdir, gitdir, is_bare
->config, ->reference($name), ->reference_names(glob =>)
->reference_create / ->reference_delete / ->reference_exists
->reference_symbolic_create($name, $target, force =>?, message =>?)
->head -> Reference|undef / ->head_unborn / ->head_detached
->set_head($refname)
->remote($name) / ->remote_create / ->remote_anonymous / ->has_remote
->revwalker
->branch($name, type =>) / ->branches(type =>)
->branch_create($name, $target) / ->has_branch
->tag($name) / ->tag_names(pattern =>)
->tag_create($name, $target, message =>?, tagger =>?)
->tag_delete($name)
->status -> { path => flags, ... }
->status_for_path($path)
->signature_default
->commit_create(tree =>, parents =>, message =>, ...)
->blob_create_frombuffer($scalar)
->object($oid), ->tree($oid), ->tree_builder
DESTROY: git_repository_free
Git::Native::Reference name, shorthand, target -> Oid, symbolic_target, is_symbolic
is_branch / is_remote / is_tag
->resolve -> Reference (follows symbolic to direct)
->set_target($oid, message =>?) (direct refs)
->symbolic_set_target($refname, message =>?) (symbolic refs)
->delete
Git::Native::Config ->get_string / ->get_bool / ->set_string / ->snapshot
Git::Native::Blob ->content, ->size, ->oid
Git::Native::Tree ->entries, ->entry_by_name
Git::Native::TreeBuilder ->insert(name =>, oid =>, mode => 0100644) / ->write
Git::Native::Commit ->oid, ->message, ->summary, ->time (epoch), ->time_offset (min)
->tree, ->tree_oid, ->parent_count, ->parent_oids
Git::Native::Remote ->url, ->name
->fetch(refspecs =>, credentials =>, prune =>)
->push(refspecs =>, credentials =>, prune =>)
->list_refs(credentials =>)
Git::Native::Credential ->userpass / ->ssh_key / ->ssh_agent / ->default / ->username
Git::Native::Revwalker ->push_head / ->push_ref / ->push_oid / ->push_glob / ->push_range
->hide_head / ->hide_ref / ->hide_oid / ->hide_glob
->sorting / ->reset / ->simplify_first_parent
->next -> Oid | undef ->all -> [Oid, ...]
Git::Native::Branch ->name / ->refname / ->target / ->is_head / ->is_local / ->is_remote
->rename($new) / ->delete
Git::Native::Tag ->name / ->message / ->target_id (annotated only)
Git::Native::Signature name, email, when
Git::Native::Oid stringify hex, ->raw (20B), ->short(7)
Git::Native::Error isa Throwable::Error; code, klass, message
```
## Memory Ownership
Each Moo wrapper holds one opaque libgit2 handle. `DESTROY` calls the
matching `git_*_free`. Child objects (e.g. a `Tree` returned from a
`Commit`) hold a strong ref to their parent in `_owner` so the parent
outlives the child - no use-after-free.
## Error Handling
Every FFI call with an `int` return code goes through `_check($rc)` in
`Git::Libgit2`. On negative rc, the C error string is fetched via
`Git::Libgit2::Error->last`, then re-thrown as a `Git::Native::Error`
(Throwable). No raw libgit2 codes leak above this layer.
## Phase 4 - Network + Auth
`Git::Native::Remote` is the hard layer. Two libgit2 quirks worth knowing:
- **Push wildcards are not expanded by libgit2.** `git_remote_push` rejects
`+refs/karr/*:refs/karr/*` with "not a valid reference". `->push` expands
patterns client-side via `_owner->reference_names(glob => ...)` and emits
one concrete refspec per matching local ref. Fetch is unaffected (server
side enumerates).
- **No native `--prune` on push.** Implemented by `_connect(DIRECTION_PUSH)`
+ `git_remote_ls` + diffing remote heads against the expanded local set,
then prepending `:refs/...` delete refspecs to the push call. `_connect`
uses the credential callback too, so prune works against authenticated
remotes.
The credential callback (`git_credential_acquire_cb`) is a
`FFI::Platypus::Closure`. The C signature has a `git_credential **out`
out-param â FFI::Platypus closures only accept native types + strings, so
it's declared as plain `opaque` (the pointer value). The Perl closure
calls the user's coderef, calls `_disown` on the returned
`Git::Native::Credential` to hand ownership to libgit2, then `memcpy`s
the pointer into the out address. Returning `undef` from the user
coderef maps to `GIT_PASSTHROUGH (-30)`, letting libgit2 try the next
auth type.
The closure must outlive the C call â `Remote` stashes it in
`$self->{_fetch_keep}` / `_push_keep` / `_connect_keep` for the duration
of the operation. Out-of-scope mid-call = segv.
Struct sizes for `git_remote_callbacks` / `git_fetch_options` /
`git_push_options` are over-allocated (256 / 384 / 384) vs probed sizes
on libgit2 1.5 (120 / 208 / 192) â leaves headroom for newer libgit2
( run in 0.643 second using v1.01-cache-2.11-cpan-140bd7fdf52 )