diff --git a/lib/Git/IssueManager.pm b/lib/Git/IssueManager.pm index 4cbaebf..a68cb0e 100644 --- a/lib/Git/IssueManager.pm +++ b/lib/Git/IssueManager.pm @@ -2,54 +2,61 @@ package Git::IssueManager; #ABSTRACT: Module for managing issues in a git branch within your repository use Moose; use MooseX::Privacy; -use Git::RepositoryHL; use DateTime; use DateTime::TimeZone; use Data::Dumper; +use Git::LowLevel; -our $VERSION = "0.1"; + +=attr gitcmd + +the path to the git command, default is using your path + +=cut +has 'gitcmd' => (is => 'ro', isa => 'Str', default=>"git"); =attr repository Git::Repository object on which to do the issue management =cut -has 'repository' => (is =>'ro', isa => 'Git::RepositoryHL', required => 1); +has 'repository' => (is =>'rw', isa => 'Git::LowLevel'); =attr _open B =cut -has '_open' => (is => 'rw', isa => 'ArrayRef', traits => [qw/Private/]); +has '_open' => (is => 'rw', isa => 'Git::LowLevel::Tree', traits => [qw/Private/]); =attr _assigned B =cut -has '_assigned' => (is => 'rw', isa => 'ArrayRef', traits => [qw/Private/]); +has '_assigned' => (is => 'rw', isa => 'Git::LowLevel::Tree', traits => [qw/Private/]); + =attr _inprogress B =cut -has '_inprogress' => (is => 'rw', isa => 'ArrayRef', traits => [qw/Private/]); +has '_inprogress' => (is => 'rw', isa => 'Git::LowLevel::Tree', traits => [qw/Private/]); =attr _closed B =cut -has '_closed' => (is => 'rw', isa => 'ArrayRef', traits => [qw/Private/]); +has '_closed' => (is => 'rw', isa => 'Git::LowLevel::Tree', traits => [qw/Private/]); =attr _root B =cut -has '_root' => (is => 'rw', isa => 'ArrayRef', traits => [qw/Private/]); +has '_root' => (is => 'rw', isa => 'Git::LowLevel::Tree', traits => [qw/Private/]); =method ready @@ -59,9 +66,14 @@ validates if everything is in place for issue management sub ready { my $self = shift; + my $ref = $self->repository->getReference('refs/heads/issues'); + return 0 unless $ref->exist; - # return false if issue branch does not exist - return 0 unless $self->repository->hasBranch('heads/issues'); + my $version = $ref->find('.version'); + return 0 unless defined($version) && ref($version) eq "Git::LowLevel::Blob"; + + my $tag = $ref->find('.tag'); + return 0 unless defined($tag) && ref($tag) eq "Git::LowLevel::Blob"; return 1; } @@ -78,19 +90,10 @@ sub version return unless $self->ready(); - my @tree = $self->repository->getTree("issues"); + my $ref = $self->repository->getReference('refs/heads/issues'); + my $version = $ref->find(".version"); - for my $t (@tree) - { - if ($t->{name} eq ".version") - { - my $version = $self->repository->getBlob($t->{ref}); - chomp($version); - return $version; - } - - } - return; + return $version->content; } =method tag @@ -104,19 +107,10 @@ sub tag return unless $self->ready(); - my @tree = $self->repository->getTree("issues"); + my $ref = $self->repository->getReference('refs/heads/issues'); + my $tag = $ref->find(".tag"); - for my $t (@tree) - { - if ($t->{name} eq ".tag") - { - my $tag = $self->repository->getBlob($t->{ref}); - chomp($tag); - return $tag; - } - - } - return; + return $tag->content; } @@ -132,629 +126,175 @@ sub init return unless ! $self->ready(); - die("no issue tag given") unless $issue_tag; + die("no issue tag given") unless defined($issue_tag) && length($issue_tag) > 0; - # create a version file in the issues branch, also creating the branch with this - my $filehash = $self->repository->createFileObject("0.1"); + my $ref = $self->repository->getReference('refs/heads/issues'); + my $root = $ref->getTree(); - # add the version file to a git tree - my $t = { - mode => "100644", - type => "blob", - ref => $filehash, - path => ".version" - }; - my @tree; - push(@tree,$t); + my $version = $root->newBlob(); + $version->path(".version"); + $version->_content("0.1"); + $root->add($version); - # create a tag file in the issues branch to identify the issue tag to prepend - $filehash = $self->repository->createFileObject($issue_tag); - my $t2 = { - mode => "100644", - type => "blob", - ref => $filehash, - path => ".tag" - }; - push(@tree,$t2); + my $tag = $root->newBlob(); + $tag->path(".tag"); + $tag->_content($issue_tag); + $root->add($tag); - # create the tree - my $tree_hash=$self->repository->createTree(\@tree); - - # commit the created tree - my $commit=$self->repository->createTreeCommit($tree_hash, "1", "initialized issue manager for this repository"); - - # now update/create the branch refs/heads/issues - - $self->repository->updateRef("refs/heads/issues",$commit); + $ref->commit("initialized issue manager"); } -=method _load - -B - -called to load all the git trees - -=cut -private_method _load => sub { - my $self = shift; - - my @root = $self->repository->getTree("issues"); - $self->_root(\@root); - for my $a (@root) - { - $a->{path} = $a->{name}; - } - - my @open = $self->repository->getTree("issues","open/"); - $self->_open(\@open); - for my $a (@open) - { - $a->{path} = $a->{name}; - } - - my @closed = $self->repository->getTree("issues","closed/"); - $self->_closed(\@closed); - for my $a (@closed) - { - $a->{path} = $a->{name}; - } - - my @assigned = $self->repository->getTree("issues","assigned/"); - $self->_assigned(\@assigned); - for my $a (@assigned) - { - $a->{path} = $a->{name}; - } - - my @inprogress = $self->repository->getTree("issues","inprogess/"); - $self->_inprogress(\@inprogress); - for my $a (@inprogress) - { - $a->{path} = $a->{name}; - } - - -}; - -=method _createTree - -B - -creates the root tree for the issues branch and returns its hash - -=cut -private_method _createTree => sub { - my $self = shift; - - # now recreate tree structure - my $openhash = $self->repository->createTree($self->_open); - my $closedhash = $self->repository->createTree($self->_closed); - my $assignedhash = $self->repository->createTree($self->_assigned); - my $inprogresshash= $self->repository->createTree($self->_inprogress); - - my $openfound=0; - my $closedfound=0; - my $assignedfound=0; - my $inprogressfound=0; - - # now recreate the root tree - my @fordelete; - my $i=0; - for my $r (@{$self->_root}) - { - if ($r->{name} eq "open") - { - if (defined($openhash) && length($openhash)==40) - { - $r->{ref}=$openhash; - $openfound=1; - } - else - { - push(@fordelete, $i); - } - } - - if ($r->{name} eq "closed") - { - if (defined($closedhash) && length($closedhash)==40) - { - $r->{ref}=$closedhash; - $closedfound=1; - } - else - { - push(@fordelete, $i); - } - } - - if ($r->{name} eq "assigned") - { - if (defined($assignedhash) && length($assignedhash)==40) - { - $r->{ref}=$assignedhash; - $assignedfound=1; - } - else - { - push(@fordelete, $i); - } - } - - if ($r->{name} eq "inprogess") - { - if (defined($inprogresshash) && length($inprogresshash)==40) - { - $r->{ref}=$inprogresshash; - $inprogressfound=1; - } - else - { - push(@fordelete, $i); - } - } - - $i++; - } - - for my $d (@fordelete) - { - splice @{$self->_root}, $d, 1; - } - - if (!$openfound && defined($openhash)) - { - my $t = { - path => "open", - ref => $openhash, - type => "tree", - mode => "040000" - }; - push(@{$self->_root}, $t); - } - - if (!$closedfound && defined($closedhash)) - { - my $t = { - path => "closed", - ref => $closedhash, - type => "tree", - mode => "040000" - }; - push(@{$self->_root}, $t); - } - - if (!$assignedfound && defined($assignedhash)) - { - my $t = { - path => "assigned", - ref => $assignedhash, - type => "tree", - mode => "040000" - }; - push(@{$self->_root}, $t); - } - - if (!$inprogressfound && defined($inprogresshash)) - { - my $t = { - path => "inprogress", - ref => $inprogresshash, - type => "tree", - mode => "040000" - }; - push(@{$self->_root}, $t); - } - - - return $self->repository->createTree($self->_root); - -}; - =method add -add an issue to the repository + add an issue to the repository + + first paramter is an GitIssueManager::Issue object =cut sub add { - my $self = shift; - my $issue = shift; + my $self = shift; + my $issue = shift; + die("IssueManager not initialized") unless $self->ready(); - die("no Git::IssueManager::Issue object given") unless ref($issue) eq "Git::IssueManager::Issue"; + die("no issue given") unless defined($issue) && ref($issue) eq "Git::IssueManager::Issue"; - $self->_load(); + my $ref = $self->repository->getReference('refs/heads/issues'); + my $root = $ref->getTree(); - my $issues; - if ($issue->status eq "open") + my $issueTree=$issue->createIssue($self->repository); + + my $base=$root->find($issue->status); + if (!defined($base)) { - $issues=$self->_open(); + $base = $root->newTree(); + $base->path($issue->status); + $root->add($base); } - elsif ($issue->status eq "closed") - { - $issues=$self->_closed(); - } - elsif ($issue->status eq "assigned") - { - $issues=$self->_assigned; - } - elsif ($issue->status eq "inprogress") - { - $issues=$self->_inprogress; - } - - # check if issues already exist, only checks the subject !!! - for my $i (@{$issues}) - { - if ($i->{name} eq $issue->subject) - { - die("issue already exist"); - } - } - - # append the issue to the tree - my $hash=$issue->createIssue($self->repository); - push (@{$issues},{ - path => $issue->subject, - ref => $hash, - type => "tree", - mode => "040000" - }); - - - my $roothash = $self->_createTree(); - - # commit the issue - my $commit=$self->repository->createTreeCommit($roothash,$self->repository->getBranchRef("heads/issues") || "start", "ADD: " . $issue->subject); - - #now update branch refs/heads/issues - $self->repository->updateRef("refs/heads/issues",$commit); - + $base->add($issueTree); + $ref->commit("added issue " . $issue->subject); } +=method parseIssue - - -=method _to_issue - internal method, do not call directly - -returns the issue converted from a tree hash entry + parsed the given Git::LowLevel::Tree object as an Issue =cut -sub _to_issue +sub parseIssue { my $self = shift; - my $i = shift; + my $d = shift; + my $tag = shift; - my $issue = Git::IssueManager::Issue->new(subject => $i->{name}); + my $subject = $d->mypath(); + my $description = $d->find("description"); + my $priority = $d->find("priority"); + my $severity = $d->find("severity"); + my $type = $d->find("type"); + my $worker = $d->find("worker"); + my $substatus = $d->find("substatus"); + my $comment = $d->find("comment"); + my $estimated = $d->find("estimated"); + my $working = $d->find("working"); + my $tags = $d->find("tags"); + my $id = $tag . "-" . substr($d->hash(),0,8); + my $cd = $d->timestamp_added(); + my $ld = $d->timestamp_last(); + my $author = $d->committer(); - $issue->priority($self->repository->getBlob($self->repository->getFileRef("issues",$i->{path}."/priority"))); - $issue->severity($self->repository->getBlob($self->repository->getFileRef("issues",$i->{path}."/severity"))); - $issue->type($self->repository->getBlob($self->repository->getFileRef("issues",$i->{path}."/type"))); - $issue->description($self->repository->getBlob($self->repository->getFileRef("issues",$i->{path}."/description"))); - $issue->comment($self->repository->getBlob($self->repository->getFileRef("issues",$i->{path}."/comment"))); - $issue->id($self->repository->getBlob($self->repository->getFileRef("issues",".tag")) . "-" . substr($i->{ref},0,8)); - $issue->estimated_time($self->repository->getBlob($self->repository->getFileRef("issues",$i->{path}."/estimated"))); - $issue->working_time($self->repository->getBlob($self->repository->getFileRef("issues",$i->{path}."/working"))); + # check for required attributes + die("description not available for issue " . $id) unless defined($description); + die("priority not available for issue " . $id) unless defined($priority); + die("severity not available for issue " . $id) unless defined($severity); + die("type not available for issue " . $id) unless defined($type); - my $worker = $self->repository->getBlob($self->repository->getFileRef("issues",$i->{path}."/worker")); - if (defined($worker)) - { - $worker=~/^(.*)<(.*)>$/; - $issue->worker($1 || ""); - $issue->worker_email($2 || ""); - } - - my $taglist= $self->repository->getBlob($self->repository->getFileRef("issues",$i->{path}."/tags")); - if (defined($taglist) && length($taglist)>0) - { - my @tags=split /\n/,$taglist; - $issue->tags(\@tags); - } my $tz=DateTime::TimeZone->new( name => 'local' ); - my @commit = $self->repository->getFileLog("issues",$i->{path}."/subject",1); - $issue->creation_date(DateTime->from_epoch(epoch => $commit[0]->{date},time_zone=>$tz )); - $issue->author($commit[0]->{author}->{name}); - $issue->author_email($commit[0]->{author}->{email}); + my $issue = Git::IssueManager::Issue->new(subject => $subject); + $issue->description($description->content()); + $issue->priority($priority->content()); + $issue->severity($severity->content()); + $issue->type($type->content()); + $issue->id($id); + $issue->creation_date(DateTime->from_epoch( epoch =>$cd, time_zone=>$tz)); + $issue->last_change_date(DateTime->from_epoch( epoch =>$ld, time_zone=>$tz)); - @commit = $self->repository->getFileLog("issues",$i->{path},1); - $issue->last_change_date(DateTime->from_epoch(epoch => $commit[0]->{date},time_zone=>$tz )); + if (defined($worker)) + { + $worker->content()=~/^(.*)\<(.*)\>$/; + $issue->worker($1); + $issue->worker_email($2); + } + + if (defined($author)) + { + $author=~/^(.*)\<(.*)\>$/; + $issue->author($1); + $issue->author_email($2); + } - $issue->closed_date(DateTime->from_epoch(epoch => $commit[0]->{date},time_zone=>$tz )); return $issue; } -=method get +sub list +{ + my $self = shift; + my @issues; -return the issue with the given id + die("IssueManager not initialized") unless $self->ready(); + my $ref = $self->repository->getReference('refs/heads/issues'); + my $root = $ref->getTree(); + my $open = $ref->find("open"); + my $closed = $ref->find("closed"); + my $assigned = $ref->find("assigned"); + my $inprogress = $ref->find("inprogess"); + my $tag = $ref->find(".tag")->content(); + my @all; -=cut -sub get + # merge all issues into one array + if (defined($open)) + { + push(@all,$open->get()); + } + + if (defined($closed)) + { + push(@all,$closed->get()); + } + + if (defined($assigned)) + { + push(@all,$assigned->get()); + } + + if (defined($inprogress)) + { + push(@all,$inprogress->get()); + } + + for my $d (@all) + { + my $issue = $self->parseIssue($d,$tag); + push(@issues,$issue); + } + + return @issues; +} + + +sub delete { my $self = shift; my $id = shift; - die("no id given") unless defined($id); - - $id=~/^[A-Z]+-(.*)$/; - my $hash=$1; - - # first search for the id - my @open = $self->repository->getTree("issues","open/"); - for my $i (@open) - { - if (substr($i->{ref},0,8) eq $hash) - { - return $self->_to_issue($i); - } - } - - my @assigned = $self->repository->getTree("issues","assigned/"); - for my $i (@assigned) - { - if (substr($i->{ref},0,8) eq $hash) - { - return $self->_to_issue($i); - } - } - - my @inprogress = $self->repository->getTree("issues","inprogress"); - for my $i (@inprogress) - { - if (substr($i->{ref},0,8) eq $hash) - { - return $self->_to_issue($i); - } - } - - my @closed = $self->repository->getTree("issues","closed/"); - for my $i (@closed) - { - if (substr($i->{ref},0,8) eq $hash) - { - return $self->_to_issue($i); - } - } - - return; + die("IssueManager not initialized") unless $self->ready(); + my $ref = $self->repository->getReference('refs/heads/issues'); + my $root = $ref->getTree(); + } - - -=method list - -list all issues of the repository - -=cut -sub list -{ - my $self = shift; - my $filter = shift; - my @issues =$self->repository->getTree("issues","open/"); - my @ret; - - if (!defined($filter) || $filter eq "open" ) - { - @issues=$self->repository->getTree("issues","open/"); - } - - for my $i (@issues) - { - my $issue = $self->_to_issue($i); - - push(@ret, $issue); - } - - # now sort by creation date - @ret = sort {DateTime->compare($a->creation_date, $b->creation_date)} @ret; - - return @ret; -} - -=method delete - -delete the issue with the given id - -=cut -sub delete -{ - my $self = shift; - my $id = shift; - - die("no id given") unless defined($id); - - $id=~/^[A-Z]+-(.*)$/; - my $hash=$1; - - $self->_load(); - - my $found=0; - my $index=0; - - # first search for the id - for my $i (@{$self->_open}) - { - if (substr($i->{ref},0,8) eq $hash) - { - $found=1; - last; - } - $index++; - } - if ($found) - { - splice @{$self->_open},$index,1; - } - - $index=0; - - if (!$found) - { - for my $i (@{$self->_assigned}) - { - if (substr($i->{ref},0,8) eq $hash) - { - $found=1; - last; - } - $index++; - } - if ($found) - { - splice @{$self->_assigned},$index,1; - } - } - - $index=0; - - if (!$found) - { - for my $i (@{$self->_inprogress}) - { - if (substr($i->{ref},0,8) eq $hash) - { - $found=1; - last; - } - $index++; - } - if ($found) - { - splice @{$self->_inprogress},$index,1; - } - } - - $index=0; - - if (!$found) - { - for my $i (@{$self->_closed}) - { - if (substr($i->{ref},0,8) eq $hash) - { - $found=1; - last; - } - $index++; - } - if ($found) - { - splice @{$self->_closed},$index,1; - } - } - - my $roothash = $self->_createTree(); - - # commit the issue - my $commit=$self->repository->createTreeCommit($roothash,$self->repository->getBranchRef("heads/issues") || "start", "RM: " . $id); - - #now update branch refs/heads/issues - $self->repository->updateRef("refs/heads/issues",$commit); -} - - - -=method close - -set the given issue as closed - -=cut -sub close -{ - my $self = shift; - - my $id = shift; - - die("no id given") unless defined($id); - - $id=~/^[A-Z]+-(.*)$/; - my $hash=$1; - - $self->_load(); - - my $found=0; - my $index=0; - - # check if issue is already closed - for my $i (@{$self->_closed}) - { - if (substr($i->{ref},0,8) eq $hash) - { - $found=1; - last; - } - $index++; - } - if ($found) - { - die("issue is already closed"); - } - - $index=0; - # search for the id in other status arrays - for my $i (@{$self->_open}) - { - if (substr($i->{ref},0,8) eq $hash) - { - $found=1; - last; - } - $index++; - } - if ($found) - { - push(@{$self->_closed}, $self->_open()->[$index]); - splice @{$self->_open},$index,1; - } - - $index=0; - - for my $i (@{$self->_assigned}) - { - if (substr($i->{ref},0,8) eq $hash) - { - $found=1; - last; - } - $index++; - } - if ($found) - { - push(@{$self->_closed}, $self->_assigned()->[$index]); - splice @{$self->_assigned},$index,1; - } - - $index=0; - - for my $i (@{$self->_inprogress}) - { - if (substr($i->{ref},0,8) eq $hash) - { - $found=1; - last; - } - $index++; - } - if ($found) - { - push(@{$self->_closed}, $self->_inprogress()->[$index]); - splice @{$self->_inprogress},$index,1; - } - - - my $roothash = $self->_createTree(); - - # commit the issue - my $commit=$self->repository->createTreeCommit($roothash,$self->repository->getBranchRef("heads/issues") || "start", "Close: " . $id); - - #now update branch refs/heads/issues - $self->repository->updateRef("refs/heads/issues",$commit); - -} - - 1;