CPAN-InGit

 view release on metacpan or  search on metacpan

lib/CPAN/InGit/MutableTree.pm  view on Meta::CPAN


use Carp;
use Moo;
use Git::Raw::Index;
use v5.36;


has parent            => ( is => 'ro', required => 1 );
has tree              => ( is => 'rw' );
has branch            => ( is => 'rw' );
has _changes          => ( is => 'rw' );
has has_changes       => ( is => 'rw' );
has use_workdir       => ( is => 'rw' );
sub git_repo             { shift->parent->git_repo }

sub BUILD($self, $args, @) {
   # branch supplied by name? look it up
   if (defined $self->{branch} && !ref $self->{branch}) {
      my $b= Git::Raw::Branch->lookup($self->git_repo, $self->{branch}, 1)
         or croak "No local branch named '$self->{branch}'";
      $self->{branch}= $b;
   }
   # If branch supplied and tree was not, look up the tree
   if ($self->{branch} && !$self->{tree}) {
      $self->{tree}= $self->{branch}->peel('tree');
   }
}


sub get_path($self, $path) {
   if ($self->has_changes) {
      if (keys $self->_changes->%*) {
         my $node= $self->_changes;
         my @path= split '/', $path;
         my $basename= pop @path;
         for (@path) {
            $node= $node->{$_} if defined $node;
         }
         return $node->{$basename} if ref $node eq 'HASH' && $node->{$basename};
      }
      if ($self->use_workdir) {
         my $ent= $self->git_repo->index->find($path);
         return [ $ent->blob, $ent->mode ]
            if $ent;
      }
   }
   if ($self->tree) {
      my $dirent= $self->tree->entry_bypath($path)
         or return undef;
      return [ $dirent->object, $dirent->file_mode ];
   }      
   return undef;
}


sub set_path($self, $path, $data, %opts) {
   # Two modes: we can be writing to the working directory and index, or be building a new tree
   # (which may or may not be connected to a branch)
   my $repo= $self->git_repo;
   my $mode= $opts{mode} // 0100644;
   my @path= split m{/+}, $path;
   my $basename= pop @path;
   if ($self->use_workdir) {
      my $fullpath= $self->git_repo->workdir;
      # create missing directories
      for (@path) {
         $fullpath .= '/'.$_;
         mkdir $fullpath || die "mkdir($fullpath): $!"
            unless -d $fullpath;
      }
      $fullpath .= '/'.$basename;
      if (!defined $data) {
         unlink($fullpath);
         $self->git_repo->index->remove($path);
      } else {
         # a shame there's no way to add the blob directly...
         $data= \$data->content if ref($data)->isa('Git::Raw::Blob');
         # Write file
         _mkfile($fullpath, $data, $mode);
         # Add to the index
         $self->git_repo->index->add_frombuffer($path, $data, $mode);
      }
   }
   else {
      my $node= ($self->{_changes} //= {});
      for (@path) {
         $node= ($node->{$_} //= {});
         ref $node eq 'HASH' or die "Can't set '$path'; '$_' is not a directory";
      }
      # Content may either be a Blob object or a scalar-ref of bytes
      if (ref $data eq 'SCALAR') {
         $data= Git::Raw::Blob->create($repo, $$data);
      }
      $node->{$basename}= defined $data? [ $data, $mode ] : undef;
   }
   $self->has_changes(1);
   $self->{_changes} //= {};
   $self;
}

sub _mkfile($path, $scalarref, $mode) {
   open my $fh, '>', $path or die "open($path): $!";
   $fh->print($$scalarref) or die "write($path): $!";
   $fh->close or die "close($path): $!";
   chmod($path, $mode) || die "chmod($path, $mode): $!"
      if defined $mode && $mode != 0100644;
}


sub update_tree($self) {
   # If using the Index, the index can write the new tree
   if ($self->use_workdir) {
      $self->tree($self->git_repo->index->write_tree);
   } else {
      $self->tree(_assemble_tree($self->git_repo, $self->tree, $self->_changes));
      $self->_changes({}); # reset the changes hash
   }
   # don't reset has_changes until it has been committed
}

# merge a hashref of changes into the previous Tree, and return the new Tree



( run in 0.385 second using v1.01-cache-2.11-cpan-71847e10f99 )