443 lines
9.4 KiB
Perl
443 lines
9.4 KiB
Perl
|
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));
|
||
|
|
||
|
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;
|