package Git::IssueManager; #ABSTRACT: Module for managing issues in a git branch within your repository use Moose; use Git::RepositoryHL; use DateTime; use DateTime::TimeZone; use Data::Dumper; our $VERSION = "0.1"; =attr repository Git::Repository object on which to do the issue management =cut has 'repository' => (is =>'ro', isa => 'Git::RepositoryHL', required => 1); =method ready validates if everything is in place for issue management =cut sub ready { my $self = shift; # return false if issue branch does not exist return 0 unless $self->repository->hasBranch('heads/issues'); return 1; } =method version returns the version number of the issue system within the issue branch =cut sub version { my $self = shift; return unless $self->ready(); my @tree = $self->repository->getTree("issues"); for my $t (@tree) { if ($t->{name} eq ".version") { my $version = $self->repository->getBlob($t->{ref}); chomp($version); return $version; } } return; } =method tag returns the issue tag to prepend in front of all issue ids =cut sub tag { my $self = shift; return unless $self->ready(); my @tree = $self->repository->getTree("issues"); for my $t (@tree) { if ($t->{name} eq ".tag") { my $tag = $self->repository->getBlob($t->{ref}); chomp($tag); return $tag; } } return; } =method init initialize the repository for managing issues =cut sub init { my $self = shift; my $issue_tag = shift; return unless ! $self->ready(); die("no issue tag given") unless $issue_tag; # create a version file in the issues branch, also creating the branch with this my $filehash = $self->repository->createFileObject("0.1"); # add the version file to a git tree my $t = { mode => "100644", type => "blob", ref => $filehash, path => ".version" }; my @tree; push(@tree,$t); # 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); # 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); } =method _recreate - internal method, do not call directly method used to recreate the tree structure received by getTree to something like createTree can use =cut sub recreate { my $self = shift; my $array = shift; for my $a (@{$array}) { $a->{path} = $a->{name}; } } =method add add an issue to the repository =cut sub add { 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"; my @root = $self->repository->getTree("issues"); my @open = $self->repository->getTree("issues","open/"); my @closed = $self->repository->getTree("issues","closed/"); my @assigned = $self->repository->getTree("issues","assigned/"); my @inprogress = $self->repository->getTree("issues","inprogress"); $self->recreate(\@open); $self->recreate(\@closed); $self->recreate(\@assigned); $self->recreate(\@inprogress); my $issues; if ($issue->status eq "open") { $issues=\@open; } elsif ($issue->status eq "closed") { $issues=\@closed; } elsif ($issue->status eq "assigned") { $issues=\@assigned; } elsif ($issue->status eq "inprogress") { $issues=\@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" }); # now recreate tree structure my $openhash = $self->repository->createTree(\@open); my $closedhash = $self->repository->createTree(\@closed); my $assignedhash = $self->repository->createTree(\@assigned); my $inprogresshash= $self->repository->createTree(\@inprogress); my $openfound=0; my $closedfound=0; my $assignedfound=0; my $inprogressfound=0; # now recreate the root tree for my $r (@root) { if ($r->{name} eq "open") { $r->{ref}=$openhash; $openfound=1; } if ($r->{name} eq "closed") { $r->{ref}=$closedhash; $closedfound=1; } if ($r->{name} eq "assigned") { $r->{ref}=$assignedhash; $assignedfound=1; } if ($r->{name} eq "inprogess") { $r->{ref}=$inprogresshash; $inprogressfound=1; } } if (!$openfound && defined($openhash)) { my $t = { path => "open", ref => $openhash, type => "tree", mode => "040000" }; push(@root, $t); } if (!$closedfound && defined($closedhash)) { my $t = { path => "closed", ref => $closedhash, type => "tree", mode => "040000" }; push(@root, $t); } if (!$assignedfound && defined($assignedhash)) { my $t = { path => "assigned", ref => $assignedhash, type => "tree", mode => "040000" }; push(@root, $t); } if (!$inprogressfound && defined($inprogresshash)) { my $t = { path => "inprogress", ref => $inprogresshash, type => "tree", mode => "040000" }; push(@root, $t); } my $roothash = $self->repository->createTree(\@root); # 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); } =method _to_issue - internal method, do not call directly returns the issue converted from a tree hash entry =cut sub _to_issue { my $self = shift; my $i = shift; my $issue = Git::IssueManager::Issue->new(subject => $i->{name}); $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"))); 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}); @commit = $self->repository->getFileLog("issues",$i->{path},1); $issue->last_change_date(DateTime->from_epoch(epoch => $commit[0]->{date},time_zone=>$tz )); $issue->closed_date(DateTime->from_epoch(epoch => $commit[0]->{date},time_zone=>$tz )); return $issue; } =method get return the issue with the given id =cut sub get { 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; } =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; } 1;