Project description can be an empty string.
[catagits/Gitalist.git] / lib / Gitalist / Git / Project.pm
1 use MooseX::Declare;
2
3 class Gitalist::Git::Project {
4     # FIXME, use Types::Path::Class and coerce
5     use MooseX::Types::Common::String qw/NonEmptySimpleStr/;
6     use DateTime;
7     use Path::Class;
8     use Gitalist::Git::Util;
9     use aliased 'Gitalist::Git::Object';
10
11     our $SHA1RE = qr/[0-9a-fA-F]{40}/;
12     
13     has name => ( isa => NonEmptySimpleStr,
14                   is => 'ro' );
15     has path => ( isa => "Path::Class::Dir",
16                   is => 'ro');
17
18     has description => ( isa => 'Str',
19                          is => 'ro',
20                          lazy_build => 1,
21                      );
22     has owner => ( isa => NonEmptySimpleStr,
23                    is => 'ro',
24                    lazy_build => 1,
25                );
26     has last_change => ( isa => 'DateTime',
27                          is => 'ro',
28                          lazy_build => 1,
29                      );
30     has _util => ( isa => 'Gitalist::Git::Util',
31                    is => 'ro',
32                    lazy_build => 1,
33                    handles => [ 'run_cmd' ],
34                );
35
36     method _build__util {
37         my $util = Gitalist::Git::Util->new(
38             gitdir => $self->project_dir($self->path),
39         );
40         return $util;
41     }
42     
43     method _build_description {
44         my $description = "";
45         eval {
46             $description = $self->path->file('description')->slurp;
47             chomp $description;
48         };
49         return $description;
50     }
51
52     method _build_owner {
53         my $owner = (getpwuid $self->path->stat->uid)[6];
54         $owner =~ s/,+$//;
55         return $owner;
56     }
57     
58     method _build_last_change {
59         my $last_change;
60         my $output = $self->run_cmd(
61             qw{ for-each-ref --format=%(committer)
62                 --sort=-committerdate --count=1 refs/heads
63           });
64         if (my ($epoch, $tz) = $output =~ /\s(\d+)\s+([+-]\d+)$/) {
65             my $dt = DateTime->from_epoch(epoch => $epoch);
66             $dt->set_time_zone($tz);
67             $last_change = $dt;
68         }
69         return $last_change;
70     }
71
72 =head2 head_hash
73
74 Find the hash of a given head (defaults to HEAD).
75
76 =cut
77
78     method head_hash (Str $head?) {
79         my $output = $self->run_cmd(qw/rev-parse --verify/, $head || 'HEAD' );
80         return unless defined $output;
81
82         my($sha1) = $output =~ /^($SHA1RE)$/;
83         return $sha1;
84     }
85
86 =head2 list_tree
87
88 Return an array of contents for a given tree.
89 The tree is specified by sha1, and defaults to HEAD.
90 The keys for each item will be:
91
92         mode
93         type
94         object
95         file
96
97 =cut
98
99     method list_tree (Str $sha1?) {
100         $sha1 ||= $self->head_hash;
101
102         my $output = $self->run_cmd(qw/ls-tree -z/, $sha1);
103         return unless defined $output;
104
105         my @ret;
106         for my $line (split /\0/, $output) {
107             my ($mode, $type, $object, $file) = split /\s+/, $line, 4;
108             push @ret, Object->new( mode => oct $mode,
109                                     type => $type,
110                                     sha1 => $object,
111                                     file => $file,
112                                     project => $self,
113                                   );
114         }
115         return @ret;
116     }
117
118
119     method project_dir {
120         my $dir = $self->path->stringify;
121         $dir .= '/.git'
122             if -f dir($dir)->file('.git/HEAD');
123         return $dir;
124     }
125
126     # Compatibility
127
128 =head2 info
129
130 Returns a hash containing properties of this project. The keys will
131 be:
132
133         name
134         description (empty if .git/description is empty/unnamed)
135         owner
136         last_change
137
138 =cut
139
140     method info {
141         return {
142             name => $self->name,
143             description => $self->description,
144             owner => $self->owner,
145             last_change => $self->last_change,
146         };
147     };
148     
149 } # end class