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