File Coverage

blib/lib/App/SCM/Digest.pm
Criterion Covered Total %
statement 95 229 41.4
branch 10 50 20.0
condition 0 13 0.0
subroutine 23 29 79.3
pod 3 3 100.0
total 131 324 40.4


line stmt bran cond sub pod time code
1             package App::SCM::Digest;
2              
3 4     4   87447 use strict;
  4         10  
  4         149  
4 4     4   20 use warnings;
  4         10  
  4         133  
5              
6 4     4   2194 use App::SCM::Digest::Utils qw(system_ad slurp);
  4         11  
  4         248  
7 4     4   3331 use App::SCM::Digest::SCM::Factory;
  4         11  
  4         122  
8              
9 4     4   29 use autodie;
  4         5  
  4         20  
10 4     4   23762 use DateTime;
  4         455168  
  4         154  
11 4     4   3894 use DateTime::Format::Strptime;
  4         72482  
  4         30  
12 4     4   5462 use Getopt::Long;
  4         45300  
  4         22  
13 4     4   4419 use Email::MIME;
  4         296881  
  4         141  
14 4     4   3167 use File::ReadBackwards;
  4         4891  
  4         129  
15 4     4   25 use File::Temp;
  4         9  
  4         374  
16 4     4   23 use List::Util qw(first);
  4         6  
  4         296  
17 4     4   24 use POSIX qw();
  4         7  
  4         78  
18              
19 4     4   20 use constant PATTERN => '%FT%T';
  4         8  
  4         312  
20 4         10984 use constant EMAIL_ATTRIBUTES => (
21             content_type => 'text/plain',
22             disposition => 'attachment',
23             charset => 'UTF-8',
24             encoding => 'quoted-printable',
25 4     4   20 );
  4         7  
26              
27             our $VERSION = '0.08';
28              
29             sub new
30             {
31 2     2 1 4878 my ($class, $config) = @_;
32 2         24 my $self = { config => $config };
33 2         17 bless $self, $class;
34 2         16 return $self;
35             }
36              
37             sub _strftime
38             {
39 0     0   0 my ($time) = @_;
40              
41 0         0 return POSIX::strftime(PATTERN, gmtime($time));
42             }
43              
44             sub _impl
45             {
46 5     5   15 my ($name) = @_;
47              
48 5         122 return App::SCM::Digest::SCM::Factory->new($name);
49             }
50              
51             sub _load_repository
52             {
53 5     5   19 my ($repository) = @_;
54              
55 5         17 my ($name, $url, $type) = @{$repository}{qw(name url type)};
  5         39  
56 5         24 my $impl = _impl($type);
57              
58 5         109 return ($name, $impl);
59             }
60              
61             sub _load_and_open_repository
62             {
63 2     2   14 my ($repository) = @_;
64              
65 2         55 my ($name, $impl) = _load_repository($repository);
66 2         59 eval { $impl->open_repository($name) };
  2         138  
67 2 100       643513 if (my $error = $@) {
68 1         172 die "Unable to open repository '$name': $error";
69             }
70              
71 1         7 return ($name, $impl);
72             }
73              
74             sub _init_repository
75             {
76 3     3   18 my ($repo_path, $db_path, $repository) = @_;
77              
78 3         49 chdir $repo_path;
79 3         6543 my ($name, $impl) = _load_repository($repository);
80 3         121 my $pre_existing = (-e $name);
81 3 50       58 if (not $pre_existing) {
82 3 100       188 if (not -e "$db_path/$name") {
83 2         47 mkdir "$db_path/$name";
84             }
85 3         3809 $impl->clone($repository->{'url'}, $name);
86             }
87 1         21 $impl->open_repository($name);
88 1 50       9 if (not $impl->is_usable()) {
89 1         36 return;
90             }
91 0 0       0 if ($pre_existing) {
92 0         0 $impl->pull();
93             }
94 0         0 my @branches = @{$impl->branches()};
  0         0  
95 0         0 for my $branch (@branches) {
96 0         0 my ($branch_name, $commit) = @{$branch};
  0         0  
97 0         0 my $branch_db_path = "$db_path/$name/$branch_name";
98 0 0       0 if (-e $branch_db_path) {
99 0         0 next;
100             }
101 0         0 my @branch_db_segments = split /\//, $branch_db_path;
102 0         0 pop @branch_db_segments;
103 0         0 my $branch_db_parent = join '/', @branch_db_segments;
104 0 0       0 if (not -e $branch_db_parent) {
105 0         0 system_ad("mkdir -p $branch_db_parent");
106             }
107 0         0 open my $fh, '>', $branch_db_path;
108 0         0 print $fh _strftime(time()).".$commit\n";
109 0         0 close $fh;
110             }
111              
112 0         0 return 1;
113             }
114              
115             sub _update_repository
116             {
117 2     2   14 my ($repo_path, $db_path, $repository) = @_;
118              
119 2         41 chdir $repo_path;
120 2         383 my ($name, $impl) = _load_and_open_repository($repository);
121 1 50       10 if (not $impl->is_usable()) {
122 1         56 return;
123             }
124 0         0 $impl->pull();
125 0         0 my $current_branch = $impl->branch_name();
126 0         0 my @branches = @{$impl->branches()};
  0         0  
127 0         0 for my $branch (@branches) {
128 0         0 my ($branch_name, undef) = @{$branch};
  0         0  
129 0         0 my $branch_db_path = "$db_path/$name/$branch_name";
130 0 0       0 if (not -e $branch_db_path) {
131 0         0 die "Unable to find branch database ($branch_db_path).";
132             }
133 0 0       0 my $branch_db_file =
134             File::ReadBackwards->new($branch_db_path)
135             or die "Unable to load branch database ".
136             "($branch_db_path).";
137 0   0     0 my $last = $branch_db_file->readline() || '';
138 0         0 chomp $last;
139 0         0 my (undef, $commit) = split /\./, $last;
140 0 0       0 if (not $commit) {
141 0         0 die "Unable to find commit ID in database.";
142             }
143 0         0 my @new_commits = @{$impl->commits_from($branch_name, $commit)};
  0         0  
144 0         0 my $time = _strftime(time());
145 0         0 open my $fh, '>>', $branch_db_path;
146 0         0 for my $new_commit (@new_commits) {
147 0         0 print $fh "$time.$new_commit\n";
148             }
149 0         0 close $fh;
150             }
151 0         0 $impl->checkout($current_branch);
152              
153 0         0 return 1;
154             }
155              
156             sub _repository_map
157             {
158 5     5   24 my ($self, $method) = @_;
159              
160 5         23 my $config = $self->{'config'};
161              
162             my ($repo_path, $db_path, $repositories) =
163 5         20 @{$config}{qw(repository_path db_path repositories)};
  5         29  
164              
165 5         27 for my $repository (@{$repositories}) {
  5         28  
166 5 100       45 if ($config->{'ignore_errors'}) {
167 2         10 eval {
168 2         16 $method->($repo_path, $db_path, $repository);
169             };
170 2 50       856 if (my $error = $@) {
171 2         104 warn $error;
172             }
173             } else {
174 3         17 $method->($repo_path, $db_path, $repository);
175             }
176             }
177             }
178              
179             sub update
180             {
181 3     3 1 4587 my ($self) = @_;
182              
183 3         44 $self->_repository_map(\&_init_repository);
184 2         61 $self->_repository_map(\&_update_repository);
185              
186 2         49 return 1;
187             }
188              
189             sub _process_bounds
190             {
191 0     0     my ($self, $from, $to) = @_;
192              
193 0           my $config = $self->{'config'};
194 0   0       my $tz = $config->{'timezone'} || 'UTC';
195              
196 0 0 0       if (not defined $from and not defined $to) {
    0          
    0          
197 0           $from = DateTime->now(time_zone => $tz)
198             ->subtract(days => 1)
199             ->strftime(PATTERN);
200 0           $to = DateTime->now(time_zone => $tz)
201             ->strftime(PATTERN);
202             } elsif (not defined $from) {
203 0           $from = '0000-01-01T00:00:00';
204             } elsif (not defined $to) {
205 0           $to = '9999-12-31T23:59:59';
206             }
207              
208 0           my $strp =
209             DateTime::Format::Strptime->new(pattern => PATTERN,
210             time_zone => $tz);
211              
212             my ($from_dt, $to_dt) =
213 0           map { $strp->parse_datetime($_) }
  0            
214             ($from, $to);
215 0 0         if (not $from_dt) {
216 0           die "Invalid 'from' time provided.";
217             }
218 0 0         if (not $to_dt) {
219 0           die "Invalid 'to' time provided.";
220             }
221              
222             ($from, $to) =
223 0           map { $_->set_time_zone('UTC');
  0            
224 0           $_->strftime(PATTERN) }
225             ($from_dt, $to_dt);
226              
227 0           return ($from, $to);
228             }
229              
230             sub _utc_to_tz
231             {
232 0     0     my ($self, $datetime) = @_;
233              
234 0           my $config = $self->{'config'};
235 0           my $tz = $config->{'timezone'};
236 0 0 0       if ((not $tz) or ($tz eq 'UTC')) {
237 0           return $datetime;
238             }
239              
240 0           my $strp =
241             DateTime::Format::Strptime->new(pattern => PATTERN,
242             time_zone => 'UTC');
243              
244 0           my $dt = $strp->parse_datetime($datetime);
245 0           $dt->set_time_zone($tz);
246 0           return $dt->strftime(PATTERN);
247             }
248              
249             sub _load_commits
250             {
251 0     0     my ($branch_db_path, $from, $to) = @_;
252              
253 0 0         if (not -e $branch_db_path) {
254 0           die "Unable to find branch database ($branch_db_path).";
255             }
256 0           open my $fh, '<', $branch_db_path;
257 0           my @commits;
258 0           while (my $entry = <$fh>) {
259 0           chomp $entry;
260 0           my ($time, $id) = split /\./, $entry;
261 0 0 0       if (($time ge $from) and ($time le $to)) {
262 0           push @commits, [ $time, $id ];
263             }
264             }
265              
266 0           return @commits;
267             }
268              
269             sub _make_email_mime
270             {
271 0     0     my ($ft, $filename) = @_;
272              
273             return
274 0           Email::MIME->create(
275             attributes => { EMAIL_ATTRIBUTES,
276             filename => $filename },
277             body_str => slurp($ft)
278             );
279             }
280              
281             sub get_email
282             {
283 0     0 1   my ($self, $from, $to) = @_;
284              
285 0           my $config = $self->{'config'};
286              
287             my ($repo_path, $db_path, $repositories) =
288 0           @{$config}{qw(repository_path db_path repositories)};
  0            
289              
290 0           ($from, $to) = $self->_process_bounds($from, $to);
291              
292 0           my $output_ft = File::Temp->new();
293 0           my @commit_data;
294              
295 0           for my $repository (@{$repositories}) {
  0            
296 0           chdir $repo_path;
297 0           my ($name, $impl) = _load_and_open_repository($repository);
298 0 0         if (not $impl->is_usable()) {
299 0           next;
300             }
301 0           my $current_branch = $impl->branch_name();
302              
303 0           my @branches = @{$impl->branches()};
  0            
304 0           for my $branch (@branches) {
305 0           my ($branch_name, $commit) = @{$branch};
  0            
306 0           my $branch_db_path = "$db_path/$name/$branch_name";
307 0           my @commits = _load_commits($branch_db_path, $from, $to);
308 0 0         if (not @commits) {
309 0           next;
310             }
311 0           print $output_ft "Repository: $name\n".
312             "Branch: $branch_name\n\n";
313 0           for my $commit (@commits) {
314 0           my ($time, $id) = @{$commit};
  0            
315 0           $time = $self->_utc_to_tz($time);
316 0           $time =~ s/T/ /;
317             print $output_ft "Pulled at: $time\n".
318 0           (join '', @{$impl->show($id)}).
  0            
319             "\n";
320              
321 0           my $att_ft = File::Temp->new();
322 0           print $att_ft @{$impl->show_all($id)};
  0            
323 0           $att_ft->flush();
324              
325 0           push @commit_data, [$name, $branch_name, $id, $att_ft];
326             }
327 0           print $output_ft "\n";
328             }
329 0           $impl->checkout($current_branch);
330             }
331              
332 0           $output_ft->flush();
333              
334 0 0         if (not @commit_data) {
335 0           return;
336             }
337              
338             my $email = Email::MIME->create(
339 0 0         header_str => [ %{$config->{'headers'} || {}} ],
340             parts => [
341             _make_email_mime($output_ft, 'log.txt'),
342             map {
343 0           my ($name, $branch_name, $id, $att_ft) = @{$_};
  0            
  0            
344 0           _make_email_mime($att_ft,
345             "$name-$branch_name-$id.diff"),
346             } @commit_data
347             ]
348             );
349              
350 0           return $email;
351             }
352              
353             1;
354              
355             __END__