Starting to hook up "orphaned" actions.
[catagits/Gitalist.git] / lib / Gitalist / Controller / Root.pm
CommitLineData
89de6a33 1package Gitalist::Controller::Root;
42fe5d11 2use Moose;
3use namespace::autoclean;
89de6a33 4
42fe5d11 5BEGIN { extends 'Catalyst::Controller' }
89de6a33 6
89de6a33 7__PACKAGE__->config->{namespace} = '';
8
f796a861 9use Sys::Hostname ();
d17ce39c 10use XML::Atom::Feed;
11use XML::Atom::Entry;
f796a861 12use XML::RSS;
d17ce39c 13
89de6a33 14=head1 NAME
15
16Gitalist::Controller::Root - Root Controller for Gitalist
17
18=head1 DESCRIPTION
19
20[enter your description here]
21
22=head1 METHODS
23
24=cut
25
832cbc81 26sub _get_object {
b4b4d0fd 27 my($self, $c, $haveh) = @_;
9dc3b9a5 28
c1f608c8 29 my $h = $haveh || $c->req->param('h') || '';
0ee97fec 30 my $f = $c->req->param('f');
8dbe8024 31
1aad4e81 32 my $m = $c->stash->{Project};
33 my $pd = $m->path;
0ee97fec 34
9dc3b9a5 35 # Either use the provided h(ash) parameter, the f(ile) parameter or just use HEAD.
a7cc1ede 36 my $hash = ($h =~ /[^a-f0-9]/ ? $m->head_hash($h) : $h)
37 || ($f && $m->hash_by_path($f))
38 || $m->head_hash
9dc3b9a5 39 # XXX This could definitely use more context.
40 || Carp::croak("Couldn't find a hash for the commit object!");
41
a7cc1ede 42 my $commit = $m->get_object($hash)
43 or Carp::croak("Couldn't find a commit object for '$hash' in '$pd'!");
9dc3b9a5 44
45 return $commit;
46}
47
1625cd5f 48=head2 index
49
50Provides the project listing.
51
52=cut
53
86382b95 54sub index :Path :Args(0) {
7bc165b3 55 my ( $self, $c ) = @_;
56
57 $c->detach($c->req->param('a'))
58 if $c->req->param('a');
59
60 my @list = @{ $c->model()->projects };
61 die 'No projects found in '. $c->model->repo_dir
62 unless @list;
63
64 my $search = $c->req->param('s') || '';
65 if($search) {
66 @list = grep {
67 index($_->name, $search) > -1
68 or ( $_->description !~ /^Unnamed repository/ and index($_->description, $search) > -1 )
69 } @list
70 }
71
72 $c->stash(
73 search_text => $search,
74 projects => \@list,
75 action => 'index',
76 );
04d1d917 77}
78
790ce598 79=head2 summary
80
81A summary of what's happening in the repo.
82
83=cut
84
85sub summary : Local {
86 my ( $self, $c ) = @_;
b47ae2c0 87 my $project = $c->stash->{Project};
3bbb1202 88 $c->detach('error_404') unless $project;
832cbc81 89 my $commit = $self->_get_object($c);
c65cccc2 90 my @heads = @{$project->heads};
06281e11 91 my $maxitems = Gitalist->config->{paging}{summary} || 10;
790ce598 92 $c->stash(
93 commit => $commit,
4111e151 94 log_lines => [$project->list_revs(
95 sha1 => $commit->sha1,
06281e11 96 count => $maxitems,
fde5091f 97 )],
4111e151 98 refs => $project->references,
0969b411 99 heads => [ @heads[0 .. ($#heads < $maxitems ? $#heads : $maxitems)] ],
790ce598 100 action => 'summary',
06281e11 101 );
790ce598 102}
103
104=head2 heads
105
106The current list of heads (aka branches) in the repo.
107
108=cut
109
110sub heads : Local {
111 my ( $self, $c ) = @_;
b47ae2c0 112 my $project = $c->stash->{Project};
790ce598 113 $c->stash(
832cbc81 114 commit => $self->_get_object($c),
c65cccc2 115 heads => $project->heads,
790ce598 116 action => 'heads',
117 );
118}
119
ea19a20c 120=head2 tags
121
122The current list of tags in the repo.
123
124=cut
125
126sub tags : Local {
127 my ( $self, $c ) = @_;
128 my $project = $c->stash->{Project};
129 $c->stash(
130 commit => $self->_get_object($c),
131 tags => $project->tags,
132 action => 'tags',
133 );
134}
135
18a8059a 136sub blame : Local {
137 my($self, $c) = @_;
138
139 my $project = $c->stash->{Project};
140 my $h = $c->req->param('h')
141 || $project->hash_by_path($c->req->param('hb'), $c->req->param('f'))
142 || die "No file or sha1 provided.";
143 my $hb = $c->req->param('hb')
144 || $project->head_hash
145 || die "Couldn't discern the corresponding head.";
146 my $filename = $c->req->param('f') || '';
147
18a8059a 148 $c->stash(
6d4fe0d7 149 blame => $project->get_object($hb)->blame($filename),
18a8059a 150 head => $project->get_object($hb),
151 filename => $filename,
152 );
153
154}
155
1625cd5f 156=head2 blob
157
158The blob action i.e the contents of a file.
159
160=cut
161
295c9703 162sub blob : Local {
163 my ( $self, $c ) = @_;
8bb7649b 164 my $project = $c->stash->{Project};
c8870bd3 165 my $h = $c->req->param('h')
8bb7649b 166 || $project->hash_by_path($c->req->param('hb'), $c->req->param('f'))
c8870bd3 167 || die "No file or sha1 provided.";
168 my $hb = $c->req->param('hb')
8bb7649b 169 || $project->head_hash
c8870bd3 170 || die "Couldn't discern the corresponding head.";
171
f5da8e7a 172 my $filename = $c->req->param('f') || '';
173
295c9703 174 $c->stash(
77b5d22c 175 blob => $project->get_object($h)->content,
8bb7649b 176 head => $project->get_object($hb),
f5da8e7a 177 filename => $filename,
178 # XXX Hack hack hack, see View::SyntaxHighlight
179 language => ($filename =~ /\.p[lm]$/ ? 'Perl' : ''),
c8870bd3 180 action => 'blob',
295c9703 181 );
7e54e579 182
c8a42dd5 183 $c->forward('View::SyntaxHighlight')
184 unless $c->stash->{no_wrapper};
185}
186
187sub blob_plain : Local {
188 my($self, $c) = @_;
189
190 $c->stash(no_wrapper => 1);
191 $c->response->content_type('text/plain; charset=utf-8');
192
193 $c->forward('blob');
194}
195
196sub blobdiff_plain : Local {
197 my($self, $c) = @_;
198
199 $c->stash(no_wrapper => 1);
200 $c->response->content_type('text/plain; charset=utf-8');
201
202 $c->forward('blobdiff');
203
295c9703 204}
205
6cf4366a 206=head2 blobdiff
207
208Exposes a given diff of a blob.
209
210=cut
211
212sub blobdiff : Local {
213 my ( $self, $c ) = @_;
832cbc81 214 my $commit = $self->_get_object($c, $c->req->param('hb'));
6cf4366a 215 my $filename = $c->req->param('f')
216 || croak("No file specified!");
6cfcd548 217 my($tree, $patch) = $c->stash->{Project}->diff(
ad8884fc 218 commit => $commit,
ad8884fc 219 patch => 1,
c8a42dd5 220 parent => $c->req->param('hpb') || undef,
221 file => $filename,
6cf4366a 222 );
223 $c->stash(
224 commit => $commit,
ad8884fc 225 diff => $patch,
592b68ef 226 filename => $filename,
6cf4366a 227 # XXX Hack hack hack, see View::SyntaxHighlight
ad8884fc 228 blobs => [$patch->[0]->{diff}],
6cf4366a 229 language => 'Diff',
230 action => 'blobdiff',
231 );
232
c8a42dd5 233 $c->forward('View::SyntaxHighlight')
234 unless $c->stash->{no_wrapper};
6cf4366a 235}
236
1625cd5f 237=head2 commit
238
47495599 239Exposes a given commit.
1625cd5f 240
241=cut
242
1feb3d6b 243sub commit : Local {
d7c9a32f 244 my ( $self, $c ) = @_;
77edf882 245 my $project = $c->stash->{Project};
832cbc81 246 my $commit = $self->_get_object($c);
b7aca93a 247 $c->stash(
790ce598 248 commit => $commit,
77edf882 249 diff_tree => ($project->diff(commit => $commit))[0],
250 refs => $project->references,
790ce598 251 action => 'commit',
b7aca93a 252 );
d7c9a32f 253}
254
2247133f 255=head2 commitdiff
256
257Exposes a given diff of a commit.
258
259=cut
260
261sub commitdiff : Local {
262 my ( $self, $c ) = @_;
832cbc81 263 my $commit = $self->_get_object($c);
fc948aee 264 my($tree, $patch) = $c->stash->{Project}->diff(
ad8884fc 265 commit => $commit,
fc948aee 266 parent => $c->req->param('hp') || undef,
ad8884fc 267 patch => 1,
268 );
2247133f 269 $c->stash(
f5da8e7a 270 commit => $commit,
ad8884fc 271 diff_tree => $tree,
272 diff => $patch,
f5da8e7a 273 # XXX Hack hack hack, see View::SyntaxHighlight
ad8884fc 274 blobs => [map $_->{diff}, @$patch],
f5da8e7a 275 language => 'Diff',
276 action => 'commitdiff',
2247133f 277 );
f5da8e7a 278
c8a42dd5 279 $c->forward('View::SyntaxHighlight')
280 unless $c->stash->{no_wrapper};
281}
282
283sub commitdiff_plain : Local {
284 my($self, $c) = @_;
285
286 $c->stash(no_wrapper => 1);
287 $c->response->content_type('text/plain; charset=utf-8');
288
289 $c->forward('commitdiff');
2247133f 290}
291
47495599 292=head2 shortlog
293
294Expose an abbreviated log of a given sha1.
295
296=cut
297
298sub shortlog : Local {
299 my ( $self, $c ) = @_;
592b68ef 300
301 my $project = $c->stash->{Project};
302 my $commit = $self->_get_object($c);
303 my $filename = $c->req->param('f') || '';
304
fde5091f 305 my %logargs = (
306 sha1 => $commit->sha1,
f740f8a9 307 count => Gitalist->config->{paging}{log} || 25,
592b68ef 308 ($filename ? (file => $filename) : ())
63e220b9 309 );
fde5091f 310
311 my $page = $c->req->param('pg') || 0;
312 $logargs{skip} = $c->req->param('pg') * $logargs{count}
313 if $c->req->param('pg');
314
47495599 315 $c->stash(
790ce598 316 commit => $commit,
f740f8a9 317 log_lines => [$project->list_revs(%logargs)],
318 refs => $project->references,
b4b4d0fd 319 page => $page,
592b68ef 320 filename => $filename,
321 action => 'shortlog',
47495599 322 );
323}
324
790ce598 325=head2 log
326
327Calls shortlog internally. Perhaps that should be reversed ...
328
329=cut
330sub log : Local {
331 $_[0]->shortlog($_[1]);
332 $_[1]->stash->{action} = 'log';
333}
334
c8a42dd5 335# For legacy support.
336sub history : Local {
337 $_[0]->shortlog(@_[1 .. $#_]);
338}
339
b3fa97cd 340=head2 tree
341
342The tree of a given commit.
343
344=cut
345
346sub tree : Local {
347 my ( $self, $c ) = @_;
74af77a3 348 my $project = $c->stash->{Project};
832cbc81 349 my $commit = $self->_get_object($c, $c->req->param('hb'));
350 my $tree = $self->_get_object($c, $c->req->param('h') || $commit->tree_sha1);
b3fa97cd 351 $c->stash(
790ce598 352 commit => $commit,
b4b4d0fd 353 tree => $tree,
74af77a3 354 tree_list => [$project->list_tree($tree->sha1)],
832cbc81 355 path => $c->req->param('f') || '',
790ce598 356 action => 'tree',
b3fa97cd 357 );
358}
359
9dc3b9a5 360=head2 reflog
361
362Expose the local reflog. This may go away.
363
364=cut
365
366sub reflog : Local {
367 my ( $self, $c ) = @_;
d8abdf1c 368 my @log = $c->stash->{Project}->reflog(
9dc3b9a5 369 '--since=yesterday'
370 );
371
372 $c->stash(
373 log => \@log,
374 action => 'reflog',
375 );
376}
377
ea19a20c 378=head2 search
379
380The action for the search form.
381
382=cut
383
14664e1c 384sub search : Local {
4df2f62f 385 my($self, $c) = @_;
d8abdf1c 386 $c->stash(current_action => 'GitRepos');
387 my $project = $c->stash->{Project};
832cbc81 388 my $commit = $self->_get_object($c);
4df2f62f 389 # Lifted from /shortlog.
390 my %logargs = (
391 sha1 => $commit->sha1,
392 count => Gitalist->config->{paging}{log},
393 ($c->req->param('f') ? (file => $c->req->param('f')) : ()),
72d72d4d 394 search => {
395 type => $c->req->param('type'),
396 text => $c->req->param('text'),
397 regexp => $c->req->param('regexp') || 0,
398 },
4df2f62f 399 );
400
401 $c->stash(
402 commit => $commit,
d8abdf1c 403 results => [$project->list_revs(%logargs)],
4df2f62f 404 action => 'search',
405 # This could be added - page => $page,
406 );
14664e1c 407}
408
ea19a20c 409=head2 search_help
410
411Provides some help for the search form.
412
413=cut
414
14664e1c 415sub search_help : Local {
2646511e 416 my ($self, $c) = @_;
417 $c->stash(template => 'search_help.tt2');
6cfcd548 418}
419
ea19a20c 420=head2 atom
421
422Provides an atom feed for a given project.
423
424=cut
425
6cfcd548 426sub atom : Local {
d17ce39c 427 my($self, $c) = @_;
6cfcd548 428
d17ce39c 429 my $feed = XML::Atom::Feed->new;
6cfcd548 430
d17ce39c 431 my $host = lc Sys::Hostname::hostname();
432 $feed->title($host . ' - ' . Gitalist->config->{name});
433 $feed->updated(~~DateTime->now);
434
435 my $project = $c->stash->{Project};
436 my %logargs = (
437 sha1 => $project->head_hash,
438 count => Gitalist->config->{paging}{log} || 25,
439 ($c->req->param('f') ? (file => $c->req->param('f')) : ())
440 );
441
442 my $mk_title = $c->stash->{short_cmt};
443 for my $commit ($project->list_revs(%logargs)) {
444 my $entry = XML::Atom::Entry->new;
445 $entry->title( $mk_title->($commit->comment) );
446 $entry->id($c->uri_for('commit', {h=>$commit->sha1}));
447 # XXX Needs work ...
448 $entry->content($commit->comment);
449 $feed->add_entry($entry);
450 }
451
f796a861 452 $c->response->body($feed->as_xml);
e75df318 453 $c->response->content_type('application/atom+xml');
f796a861 454 $c->response->status(200);
6cfcd548 455}
456
ea19a20c 457=head2 rss
458
459Provides an RSS feed for a given project.
460
461=cut
462
6cfcd548 463sub rss : Local {
f796a861 464 my ($self, $c) = @_;
465
466 my $project = $c->stash->{Project};
467
468 my $rss = XML::RSS->new(version => '2.0');
469 $rss->channel(
470 title => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name},
471 link => $c->uri_for('summary', {p=>$project->name}),
472 language => 'en',
473 description => $project->description,
474 pubDate => DateTime->now,
475 lastBuildDate => DateTime->now,
476 );
477
478 my %logargs = (
479 sha1 => $project->head_hash,
480 count => Gitalist->config->{paging}{log} || 25,
481 ($c->req->param('f') ? (file => $c->req->param('f')) : ())
482 );
483 my $mk_title = $c->stash->{short_cmt};
484 for my $commit ($project->list_revs(%logargs)) {
485 # XXX Needs work ....
486 $rss->add_item(
487 title => $mk_title->($commit->comment),
488 permaLink => $c->uri_for(commit => {h=>$commit->sha1}),
489 description => $commit->comment,
490 );
491 }
492
493 $c->response->body($rss->as_string);
494 $c->response->content_type('application/rss+xml');
495 $c->response->status(200);
14664e1c 496}
497
ea19a20c 498=head2 patch
499
500A raw patch for a given commit.
501
502=cut
503
6cfcd548 504sub patch : Local {
377bf360 505 my ($self, $c) = @_;
61ba8635 506 $c->detach('patches', [1]);
507}
508
ea19a20c 509=head2 patches
510
511The patcheset for a given commit ???
512
513=cut
514
61ba8635 515sub patches : Local {
516 my ($self, $c, $count) = @_;
517 $count ||= Gitalist->config->{patches}{max};
377bf360 518 my $commit = $self->_get_object($c);
519 my $parent = $c->req->param('hp') || undef;
f707d264 520 my $patch = $commit->get_patch( $parent, $count );
377bf360 521 $c->response->body($patch);
522 $c->response->content_type('text/plain');
523 $c->response->status(200);
6cfcd548 524}
525
ea19a20c 526=head2 snapshot
527
528Provides a snapshot of a given commit.
529
530=cut
531
6cfcd548 532sub snapshot : Local {
30db8f5b 533 my ($self, $c) = @_;
63afe2db 534 my $format = $c->req->param('sf') || 'tgz';
30db8f5b 535 die unless $format;
2e79039a 536 my $sha1 = $c->req->param('h') || $self->_get_object($c)->sha1;
c0dbe239 537 my @snap = $c->stash->{Project}->snapshot(
538 sha1 => $sha1,
539 format => $format
540 );
30db8f5b 541 $c->response->status(200);
542 $c->response->headers->header( 'Content-Disposition' =>
c0dbe239 543 "attachment; filename=$snap[0]");
544 $c->response->body($snap[1]);
6cfcd548 545}
546
1625cd5f 547=head2 auto
548
549Populate the header and footer. Perhaps not the best location.
550
551=cut
552
04d1d917 553sub auto : Private {
4621ecf0 554 my($self, $c) = @_;
04d1d917 555
da8f4f82 556 my $project = $c->req->param('p');
557 if (defined $project) {
558 eval {
559 $c->stash(Project => $c->model('GitRepos')->project($project));
560 };
561 if ($@) {
562 $c->detach('error_404');
563 }
564 }
565
566 my $a_project = $c->stash->{Project} || $c->model()->projects->[0];
4621ecf0 567 $c->stash(
da8f4f82 568 git_version => $a_project->run_cmd('--version'),
569 version => $Gitalist::VERSION,
570
571 # XXX Move these to a plugin!
4621ecf0 572 time_since => sub {
ef11b09e 573 return 'never' unless $_[0];
4621ecf0 574 return age_string(time - $_[0]->epoch);
575 },
576 short_cmt => sub {
577 my $cmt = shift;
578 my($line) = split /\n/, $cmt;
579 $line =~ s/^(.{70,80}\b).*/$1 …/;
580 return $line;
581 },
5cd9b9f9 582 abridged_description => sub {
583 join(' ', grep { defined } (split / /, shift)[0..10]);
584 },
4621ecf0 585 );
04d1d917 586}
d9a9b56b 587
77730637 588sub project_index : Local {
589 # FIXME - implement snapshot
590 Carp::croak "Not implemented.";
591}
592sub opml : Local {
593 # FIXME - implement snapshot
594 Carp::croak "Not implemented.";
595}
77730637 596
89de6a33 597=head2 end
598
599Attempt to render a view, if needed.
600
601=cut
602
fde5091f 603sub end : ActionClass('RenderView') {
1aad4e81 604 my ($self, $c) = @_;
605 # Give project views the current HEAD.
606 if ($c->stash->{Project}) {
607 $c->stash->{HEAD} = $c->stash->{Project}->head_hash;
608 }
1fd8159c 609}
d9a9b56b 610
4111e151 611sub error_404 :Private {
612 my ($self, $c) = @_;
613 $c->response->status(404);
614 $c->stash(
615 title => 'Page not found',
616 content => 'Page not found',
1aad4e81 617 );
4111e151 618}
619
1fd8159c 620sub age_string {
da8f4f82 621 my $age = shift;
622 my $age_str;
623
624 if ( $age > 60 * 60 * 24 * 365 * 2 ) {
625 $age_str = ( int $age / 60 / 60 / 24 / 365 );
626 $age_str .= " years ago";
627 }
628 elsif ( $age > 60 * 60 * 24 * ( 365 / 12 ) * 2 ) {
629 $age_str = int $age / 60 / 60 / 24 / ( 365 / 12 );
630 $age_str .= " months ago";
631 }
632 elsif ( $age > 60 * 60 * 24 * 7 * 2 ) {
633 $age_str = int $age / 60 / 60 / 24 / 7;
634 $age_str .= " weeks ago";
635 }
636 elsif ( $age > 60 * 60 * 24 * 2 ) {
637 $age_str = int $age / 60 / 60 / 24;
638 $age_str .= " days ago";
639 }
640 elsif ( $age > 60 * 60 * 2 ) {
641 $age_str = int $age / 60 / 60;
642 $age_str .= " hours ago";
643 }
644 elsif ( $age > 60 * 2 ) {
645 $age_str = int $age / 60;
646 $age_str .= " min ago";
647 }
648 elsif ( $age > 2 ) {
649 $age_str = int $age;
650 $age_str .= " sec ago";
651 }
652 else {
653 $age_str .= " right now";
654 }
655 return $age_str;
7a88ffa4 656}
657
da8f4f82 658
89de6a33 659=head1 AUTHOR
660
f71a83ab 661Dan Brook
89de6a33 662
663=head1 LICENSE
664
665This library is free software. You can redistribute it and/or modify
666it under the same terms as Perl itself.
667
668=cut
669
42fe5d11 670__PACKAGE__->meta->make_immutable;