Git-Native

 view release on metacpan or  search on metacpan

t/71-object-prefix.t  view on Meta::CPAN

# Repository->object_by_prefix($hex) - the `git rev-parse abc1234` lookup.
#
# Three things are worth pinning here, none of which t/47-object.t can cover
# because object() only ever sees a complete OID:
#
#   * the length libgit2 wants counts hex characters (nibbles), NOT bytes.
#     Getting that wrong is silent for even-length prefixes that happen to
#     match anyway, so the odd-length and minimum-length cases below are the
#     ones that would catch it.
#   * an ambiguous prefix must arrive as a Git::Native::Error with
#     is_ambiguous true - the whole reason that predicate exists.
#   * a prefix below GIT_OID_MINPREFIXLEN must NOT arrive that way. libgit2
#     answers it with GIT_EAMBIGUOUS too ("OID prefix is too short"), which
#     would make is_ambiguous mean two different things; the wrapper croaks
#     first so it keeps meaning one.

# ---------------------------------------------------------------------------
# Typed dispatch: object_by_prefix returns what object() returns.
# ---------------------------------------------------------------------------
{
  my ( $repo, $tmp ) = TestRepo::new_repo();

  my $blob_oid = $repo->blob_create_frombuffer("payload\n");
  my $tb       = $repo->tree_builder;
  $tb->insert( name => 'f', oid => $blob_oid, mode => 0100644 );
  my $tree_oid   = $tb->write;
  my $commit_oid = $repo->commit_create(
    tree => $tree_oid, parents => [], message => 'c',
  );
  my $tag_oid = $repo->tag_create(
    'v1.0.0', $commit_oid,
    message => "rel\n",
    tagger  => Git::Native::Signature->new( name => 'T', email => 't@e' ),
  );

  my $blob = $repo->object_by_prefix( substr "$blob_oid", 0, 7 );
  isa_ok $blob, ['Git::Native::Blob'], 'short blob prefix -> Blob';
  is $blob->content, "payload\n", 'and it is the right blob';
  is "" . $blob->oid, "$blob_oid", 'the resolved object carries the full OID';

  isa_ok $repo->object_by_prefix( substr "$tree_oid", 0, 7 ),
    ['Git::Native::Tree'], 'short tree prefix -> Tree';
  isa_ok $repo->object_by_prefix( substr "$commit_oid", 0, 7 ),
    ['Git::Native::Commit'], 'short commit prefix -> Commit';
  isa_ok $repo->object_by_prefix( substr "$tag_oid", 0, 7 ),
    ['Git::Native::Tag'], 'short annotated tag prefix -> Tag';

  # An odd length is the interesting one: a byte-count reading of the length
  # would halve 5 to 2 and libgit2 would reject it as too short.
  is $repo->object_by_prefix( substr "$blob_oid", 0, 5 )->oid . "", "$blob_oid",
    'an odd-length prefix resolves (length is hex characters, not bytes)';

  # Degenerate ends of the accepted range.
  is $repo->object_by_prefix("$blob_oid")->oid . "", "$blob_oid",
    'a full 40-character hex string is accepted';
  is $repo->object_by_prefix($blob_oid)->oid . "", "$blob_oid",
    'a Git::Native::Oid is accepted and behaves like object()';
  is $repo->object_by_prefix( uc( substr "$blob_oid", 0, 7 ) )->oid . "", "$blob_oid",
    'prefix matching is case-insensitive';

  # The child holds its parent alive (memory ownership contract).
  ok $blob->_owner == $repo, 'the returned wrapper owns the repository';
}

# ---------------------------------------------------------------------------
# Boundary: GIT_OID_MINPREFIXLEN characters is enough.
#
# Its own repository, holding exactly one object, so "4 characters resolve"
# cannot be spoiled by a chance collision with a second object.
# ---------------------------------------------------------------------------
{
  my ( $repo, $tmp ) = TestRepo::new_repo();
  my $oid = $repo->blob_create_frombuffer("only object\n");

  is GIT_OID_MINPREFIXLEN, 4, 'libgit2 minimum prefix length is 4';

  my $obj = $repo->object_by_prefix( substr "$oid", 0, GIT_OID_MINPREFIXLEN );
  is $obj->oid . "", "$oid", 'a prefix of exactly GIT_OID_MINPREFIXLEN resolves';

  # ---- nothing matches ----
  my $err = dies { $repo->object_by_prefix('deadbeef') };
  isa_ok $err, ['Git::Native::Error'], 'an unmatched prefix throws';
  is $err->is_not_found, 1, 'and it is a not-found, like a full OID lookup';
  is $err->is_ambiguous, 0, 'not-found does not answer is_ambiguous';

  # ---- too short: a croak, deliberately not a Git::Native::Error ----
  for my $short ( '', 'a', 'ab', 'abc' ) {
    my $e = dies { $repo->object_by_prefix($short) };
    ok !ref $e,
      "prefix '$short' croaks rather than throwing a Git::Native::Error";
  }
  like dies { $repo->object_by_prefix('abc') },
    qr/shorter than the 4 characters/,
    'the too-short croak says what is wrong';

  # ---- other rejected shapes ----
  like dies { $repo->object_by_prefix( 'a' x 41 ) },
    qr/at most 40 characters/, 'more than 40 characters croaks';
  like dies { $repo->object_by_prefix('zzzzzz') },
    qr/hex OID prefix/, 'a non-hex prefix croaks';
  like dies { $repo->object_by_prefix(undef) },
    qr/hex OID prefix/, 'undef croaks';
}

# ---------------------------------------------------------------------------
# Ambiguity.
#
# Two objects sharing their first 4 hex characters. The blob contents are
# fixed, so their OIDs are fixed and the collision is reproducible; the loop
# only has to find it (261 blobs, measured).
# ---------------------------------------------------------------------------
{
  my ( $repo, $tmp ) = TestRepo::new_repo();

  my ( %seen, $first, $second, $prefix );
  for my $i ( 1 .. 5000 ) {
    my $oid = "" . $repo->blob_create_frombuffer("blob $i\n");
    my $p   = substr $oid, 0, 4;
    if ( $seen{$p} && $seen{$p} ne $oid ) {
      ( $first, $second, $prefix ) = ( $seen{$p}, $oid, $p );
      last;



( run in 3.021 seconds using v1.01-cache-2.11-cpan-14f38c9f855 )