Make Object aware of its containing repository, and able to return its contents.
[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 => NonEmptySimpleStr,
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->path,
39         );
40         return $util;
41     }
42     
43     method _build_description {
44         my $description = $self->path->file('description')->slurp;
45         chomp $description;
46         return $description;
47     }
48
49     method _build_owner {
50         my $owner = (getpwuid $self->path->stat->uid)[6];
51         $owner =~ s/,+$//;
52         return $owner;
53     }
54     
55     method _build_last_change {
56         my $last_change;
57         my $output = $self->run_cmd(
58             qw{ for-each-ref --format=%(committer)
59                 --sort=-committerdate --count=1 refs/heads
60           });
61         if (my ($epoch, $tz) = $output =~ /\s(\d+)\s+([+-]\d+)$/) {
62             my $dt = DateTime->from_epoch(epoch => $epoch);
63             $dt->set_time_zone($tz);
64             $last_change = $dt;
65         }
66         return $last_change;
67     }
68
69 =head2 head_hash
70
71 Find the hash of a given head (defaults to HEAD).
72
73 =cut
74
75     method head_hash (Str $head?) {
76         my $output = $self->run_cmd(qw/rev-parse --verify/, $head || 'HEAD' );
77         return unless defined $output;
78
79         my($sha1) = $output =~ /^($SHA1RE)$/;
80         return $sha1;
81     }
82
83 =head2 list_tree
84
85 Return an array of contents for a given tree.
86 The tree is specified by sha1, and defaults to HEAD.
87 The keys for each item will be:
88
89         mode
90         type
91         object
92         file
93
94 =cut
95
96     method list_tree (Str $sha1?) {
97         $sha1 ||= $self->head_hash;
98
99         my $output = $self->run_cmd(qw/ls-tree -z/, $sha1);
100         return unless defined $output;
101
102         my @ret;
103         for my $line (split /\0/, $output) {
104             my ($mode, $type, $object, $file) = split /\s+/, $line, 4;
105             push @ret, Object->new( mode => oct $mode,
106                                     type => $type,
107                                     sha1 => $object,
108                                     file => $file,
109                                     project => $self,
110                                   );
111         }
112         return @ret;
113     }
114
115
116     method project_dir {
117         my $dir = $self->path->stringify;
118         $dir .= '/.git'
119             if -f dir($dir)->file('.git/HEAD');
120         return $dir;
121     }
122
123     # Compatibility
124
125 =head2 info
126
127 Returns a hash containing properties of this project. The keys will
128 be:
129
130         name
131         description (empty if .git/description is empty/unnamed)
132         owner
133         last_change
134
135 =cut
136
137     method info {
138         return {
139             name => $self->name,
140             description => $self->description,
141             owner => $self->owner,
142             last_change => $self->last_change,
143         };
144     };
145     
146 } # end class