Trivial tidyups.
[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
5bb401c6 56sub blame : Chained('base') Args(0) {
18a8059a 57 my($self, $c) = @_;
58
82bc0f05 59 my $repository = $c->stash->{Repository};
18a8059a 60 my $h = $c->req->param('h')
82bc0f05 61 || $repository->hash_by_path($c->req->param('hb'), $c->req->param('f'))
18a8059a 62 || die "No file or sha1 provided.";
63 my $hb = $c->req->param('hb')
82bc0f05 64 || $repository->head_hash
18a8059a 65 || die "Couldn't discern the corresponding head.";
66 my $filename = $c->req->param('f') || '';
67
61f14672 68 my $blame = $repository->get_object($hb)->blame($filename, $h);
18a8059a 69 $c->stash(
6bd8b34b 70 blame => $blame,
82bc0f05 71 head => $repository->get_object($hb),
18a8059a 72 filename => $filename,
6bd8b34b 73
74 # XXX Hack hack hack, see View::SyntaxHighlight
75 language => ($filename =~ /\.p[lm]$/i ? 'Perl' : ''),
76 blob => join("\n", map $_->{line}, @$blame),
18a8059a 77 );
6bd8b34b 78
79 $c->forward('View::SyntaxHighlight')
80 unless $c->stash->{no_wrapper};
18a8059a 81}
82
b5f3d3e7 83sub _blob_objs {
295c9703 84 my ( $self, $c ) = @_;
82bc0f05 85 my $repository = $c->stash->{Repository};
c8870bd3 86 my $h = $c->req->param('h')
82bc0f05 87 || $repository->hash_by_path($c->req->param('hb'), $c->req->param('f'))
c8870bd3 88 || die "No file or sha1 provided.";
89 my $hb = $c->req->param('hb')
82bc0f05 90 || $repository->head_hash
c8870bd3 91 || die "Couldn't discern the corresponding head.";
92
f5da8e7a 93 my $filename = $c->req->param('f') || '';
94
82bc0f05 95 my $blob = $repository->get_object($h);
96 $blob = $repository->get_object(
97 $repository->hash_by_path($h || $hb, $filename)
b5f3d3e7 98 ) if $blob->type ne 'blob';
99
82bc0f05 100 return $blob, $repository->get_object($hb), $filename;
b5f3d3e7 101}
102
103=head2 blob
104
105The blob action i.e the contents of a file.
106
107=cut
108
5bb401c6 109sub blob : Chained('base') Args(0) {
b5f3d3e7 110 my ( $self, $c ) = @_;
111
112 my($blob, $head, $filename) = $self->_blob_objs($c);
295c9703 113 $c->stash(
b5f3d3e7 114 blob => $blob->content,
115 head => $head,
f5da8e7a 116 filename => $filename,
117 # XXX Hack hack hack, see View::SyntaxHighlight
6bd8b34b 118 language => ($filename =~ /\.p[lm]$/i ? 'Perl' : ''),
295c9703 119 );
7e54e579 120
c8a42dd5 121 $c->forward('View::SyntaxHighlight')
122 unless $c->stash->{no_wrapper};
123}
124
b5f3d3e7 125=head2 blob_plain
126
127The plain text version of blob, where file is rendered as is.
128
129=cut
130
5bb401c6 131sub blob_plain : Chained('base') Args(0) {
c8a42dd5 132 my($self, $c) = @_;
133
b5f3d3e7 134 my($blob) = $self->_blob_objs($c);
c8a42dd5 135 $c->response->content_type('text/plain; charset=utf-8');
b5f3d3e7 136 $c->response->body($blob->content);
137 $c->response->status(200);
c8a42dd5 138}
139
b5f3d3e7 140=head2 blobdiff_plain
141
142The plain text version of blobdiff.
143
144=cut
145
5bb401c6 146sub blobdiff_plain : Chained('base') Args(0) {
c8a42dd5 147 my($self, $c) = @_;
148
149 $c->stash(no_wrapper => 1);
150 $c->response->content_type('text/plain; charset=utf-8');
151
152 $c->forward('blobdiff');
295c9703 153}
154
6cf4366a 155=head2 blobdiff
156
157Exposes a given diff of a blob.
158
159=cut
160
5bb401c6 161sub blobdiff : Chained('base') Args(0) {
6cf4366a 162 my ( $self, $c ) = @_;
832cbc81 163 my $commit = $self->_get_object($c, $c->req->param('hb'));
6cf4366a 164 my $filename = $c->req->param('f')
165 || croak("No file specified!");
87581f05 166 my($tree, $patch) = $c->stash->{Repository}->diff(
ad8884fc 167 commit => $commit,
ad8884fc 168 patch => 1,
c8a42dd5 169 parent => $c->req->param('hpb') || undef,
170 file => $filename,
6cf4366a 171 );
172 $c->stash(
173 commit => $commit,
ad8884fc 174 diff => $patch,
592b68ef 175 filename => $filename,
6cf4366a 176 # XXX Hack hack hack, see View::SyntaxHighlight
ad8884fc 177 blobs => [$patch->[0]->{diff}],
6cf4366a 178 language => 'Diff',
6cf4366a 179 );
180
c8a42dd5 181 $c->forward('View::SyntaxHighlight')
182 unless $c->stash->{no_wrapper};
6cf4366a 183}
184
1625cd5f 185=head2 commit
186
47495599 187Exposes a given commit.
1625cd5f 188
189=cut
190
5bb401c6 191sub commit : Chained('base') Args(0) {
d7c9a32f 192 my ( $self, $c ) = @_;
82bc0f05 193 my $repository = $c->stash->{Repository};
832cbc81 194 my $commit = $self->_get_object($c);
b7aca93a 195 $c->stash(
790ce598 196 commit => $commit,
82bc0f05 197 diff_tree => ($repository->diff(commit => $commit))[0],
198 refs => $repository->references,
b7aca93a 199 );
d7c9a32f 200}
201
c8a42dd5 202# For legacy support.
5bb401c6 203sub history : Chained('base') Args(0) {
cce8c9b6 204 my ( $self, $c ) = @_;
205 $self->shortlog($c);
82bc0f05 206 my $repository = $c->stash->{Repository};
207 my $file = $repository->get_object(
208 $repository->hash_by_path(
209 $repository->head_hash,
cce8c9b6 210 $c->stash->{filename}
211 )
212 );
066e9aa4 213 $c->stash(
cce8c9b6 214 filetype => $file->type,
215 );
c8a42dd5 216}
217
b3fa97cd 218=head2 tree
219
220The tree of a given commit.
221
222=cut
223
5bb401c6 224sub tree : Chained('base') Args(0) {
b3fa97cd 225 my ( $self, $c ) = @_;
82bc0f05 226 my $repository = $c->stash->{Repository};
832cbc81 227 my $commit = $self->_get_object($c, $c->req->param('hb'));
c046a52f 228 my $filename = $c->req->param('f') || '';
229 my $tree = $filename
82bc0f05 230 ? $repository->get_object($repository->hash_by_path($commit->sha1, $filename))
231 : $repository->get_object($commit->tree_sha1)
c046a52f 232 ;
b3fa97cd 233 $c->stash(
790ce598 234 commit => $commit,
b4b4d0fd 235 tree => $tree,
82bc0f05 236 tree_list => [$repository->list_tree($tree->sha1)],
832cbc81 237 path => $c->req->param('f') || '',
b3fa97cd 238 );
239}
240
9dc3b9a5 241=head2 reflog
242
243Expose the local reflog. This may go away.
244
245=cut
246
5bb401c6 247sub reflog : Chained('base') Args(0) {
9dc3b9a5 248 my ( $self, $c ) = @_;
87581f05 249 my @log = $c->stash->{Repository}->reflog(
9dc3b9a5 250 '--since=yesterday'
251 );
252
253 $c->stash(
254 log => \@log,
9dc3b9a5 255 );
256}
257
ea19a20c 258=head2 search
259
260The action for the search form.
261
262=cut
263
5bb401c6 264sub search : Chained('base') Args(0) {
4df2f62f 265 my($self, $c) = @_;
82bc0f05 266 my $repository = $c->stash->{Repository};
832cbc81 267 my $commit = $self->_get_object($c);
4df2f62f 268 # Lifted from /shortlog.
269 my %logargs = (
270 sha1 => $commit->sha1,
271 count => Gitalist->config->{paging}{log},
272 ($c->req->param('f') ? (file => $c->req->param('f')) : ()),
72d72d4d 273 search => {
274 type => $c->req->param('type'),
275 text => $c->req->param('text'),
276 regexp => $c->req->param('regexp') || 0,
277 },
4df2f62f 278 );
279
280 $c->stash(
281 commit => $commit,
82bc0f05 282 results => [$repository->list_revs(%logargs)],
4df2f62f 283 # This could be added - page => $page,
284 );
14664e1c 285}
286
ea19a20c 287=head2 search_help
288
289Provides some help for the search form.
290
291=cut
292
5bb401c6 293sub search_help : Chained('base') Args(0) {
2646511e 294 my ($self, $c) = @_;
295 $c->stash(template => 'search_help.tt2');
6cfcd548 296}
297
ea19a20c 298=head2 atom
299
82bc0f05 300Provides an atom feed for a given repository.
ea19a20c 301
302=cut
303
5bb401c6 304sub atom : Chained('base') Args(0) {
d17ce39c 305 my($self, $c) = @_;
6cfcd548 306
d17ce39c 307 my $feed = XML::Atom::Feed->new;
6cfcd548 308
d17ce39c 309 my $host = lc Sys::Hostname::hostname();
310 $feed->title($host . ' - ' . Gitalist->config->{name});
311 $feed->updated(~~DateTime->now);
312
82bc0f05 313 my $repository = $c->stash->{Repository};
d17ce39c 314 my %logargs = (
82bc0f05 315 sha1 => $repository->head_hash,
d17ce39c 316 count => Gitalist->config->{paging}{log} || 25,
317 ($c->req->param('f') ? (file => $c->req->param('f')) : ())
318 );
319
320 my $mk_title = $c->stash->{short_cmt};
82bc0f05 321 for my $commit ($repository->list_revs(%logargs)) {
d17ce39c 322 my $entry = XML::Atom::Entry->new;
323 $entry->title( $mk_title->($commit->comment) );
324 $entry->id($c->uri_for('commit', {h=>$commit->sha1}));
325 # XXX Needs work ...
326 $entry->content($commit->comment);
327 $feed->add_entry($entry);
328 }
329
f796a861 330 $c->response->body($feed->as_xml);
e75df318 331 $c->response->content_type('application/atom+xml');
f796a861 332 $c->response->status(200);
6cfcd548 333}
334
ea19a20c 335=head2 rss
336
82bc0f05 337Provides an RSS feed for a given repository.
ea19a20c 338
339=cut
340
5bb401c6 341sub rss : Chained('base') Args(0) {
f796a861 342 my ($self, $c) = @_;
343
82bc0f05 344 my $repository = $c->stash->{Repository};
f796a861 345
346 my $rss = XML::RSS->new(version => '2.0');
347 $rss->channel(
348 title => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name},
82bc0f05 349 link => $c->uri_for('summary', {p=>$repository->name}),
f796a861 350 language => 'en',
82bc0f05 351 description => $repository->description,
f796a861 352 pubDate => DateTime->now,
353 lastBuildDate => DateTime->now,
354 );
355
356 my %logargs = (
82bc0f05 357 sha1 => $repository->head_hash,
f796a861 358 count => Gitalist->config->{paging}{log} || 25,
359 ($c->req->param('f') ? (file => $c->req->param('f')) : ())
360 );
361 my $mk_title = $c->stash->{short_cmt};
82bc0f05 362 for my $commit ($repository->list_revs(%logargs)) {
f796a861 363 # XXX Needs work ....
364 $rss->add_item(
365 title => $mk_title->($commit->comment),
366 permaLink => $c->uri_for(commit => {h=>$commit->sha1}),
367 description => $commit->comment,
368 );
369 }
370
371 $c->response->body($rss->as_string);
372 $c->response->content_type('application/rss+xml');
373 $c->response->status(200);
14664e1c 374}
375
5bb401c6 376sub opml : Chained('base') Args(0) {
286cea09 377 my($self, $c) = @_;
378
379 my $opml = XML::OPML::SimpleGen->new();
380
381 $opml->head(title => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name});
382
82bc0f05 383 my @list = @{ $c->model()->repositories };
384 die 'No repositories found in '. $c->model->repo_dir
286cea09 385 unless @list;
386
387 for my $proj ( @list ) {
388 $opml->insert_outline(
389 text => $proj->name. ' - '. $proj->description,
390 xmlUrl => $c->uri_for(rss => {p => $proj->name}),
391 );
392 }
393
394 $c->response->body($opml->as_string);
395 $c->response->content_type('application/rss');
396 $c->response->status(200);
397}
398
ea19a20c 399=head2 patch
400
401A raw patch for a given commit.
402
403=cut
404
5bb401c6 405sub patch : Chained('base') Args(0) {
377bf360 406 my ($self, $c) = @_;
61ba8635 407 $c->detach('patches', [1]);
408}
409
ea19a20c 410=head2 patches
411
412The patcheset for a given commit ???
413
414=cut
415
5bb401c6 416sub patches : Chained('base') Args(0) {
61ba8635 417 my ($self, $c, $count) = @_;
418 $count ||= Gitalist->config->{patches}{max};
377bf360 419 my $commit = $self->_get_object($c);
420 my $parent = $c->req->param('hp') || undef;
f707d264 421 my $patch = $commit->get_patch( $parent, $count );
377bf360 422 $c->response->body($patch);
423 $c->response->content_type('text/plain');
424 $c->response->status(200);
6cfcd548 425}
426
ea19a20c 427=head2 snapshot
428
429Provides a snapshot of a given commit.
430
431=cut
432
5bb401c6 433sub snapshot : Chained('base') Args(0) {
30db8f5b 434 my ($self, $c) = @_;
63afe2db 435 my $format = $c->req->param('sf') || 'tgz';
30db8f5b 436 die unless $format;
2e79039a 437 my $sha1 = $c->req->param('h') || $self->_get_object($c)->sha1;
87581f05 438 my @snap = $c->stash->{Repository}->snapshot(
c0dbe239 439 sha1 => $sha1,
440 format => $format
441 );
30db8f5b 442 $c->response->status(200);
443 $c->response->headers->header( 'Content-Disposition' =>
c0dbe239 444 "attachment; filename=$snap[0]");
445 $c->response->body($snap[1]);
6cfcd548 446}
447
1625cd5f 448
5bb401c6 449sub base : Chained('/root') PathPart('') CaptureArgs(0) {
4621ecf0 450 my($self, $c) = @_;
04d1d917 451
066e9aa4 452 my $git_version = `git --version`;
453 chomp($git_version);
4621ecf0 454 $c->stash(
066e9aa4 455 git_version => $git_version,
da8f4f82 456 version => $Gitalist::VERSION,
457
458 # XXX Move these to a plugin!
4621ecf0 459 time_since => sub {
ef11b09e 460 return 'never' unless $_[0];
4621ecf0 461 return age_string(time - $_[0]->epoch);
462 },
463 short_cmt => sub {
464 my $cmt = shift;
465 my($line) = split /\n/, $cmt;
741e110e 466 $line =~ s/^(.{70,80}\b).*/$1 \x{2026}/;
4621ecf0 467 return $line;
468 },
5cd9b9f9 469 abridged_description => sub {
470 join(' ', grep { defined } (split / /, shift)[0..10]);
471 },
4621ecf0 472 );
04d1d917 473}
d9a9b56b 474
fde5091f 475sub end : ActionClass('RenderView') {
1aad4e81 476 my ($self, $c) = @_;
82bc0f05 477 # Give repository views the current HEAD.
87581f05 478 if ($c->stash->{Repository}) {
479 $c->stash->{HEAD} = $c->stash->{Repository}->head_hash;
1aad4e81 480 }
1fd8159c 481}
d9a9b56b 482
c113db92 483sub error_404 : Action {
4111e151 484 my ($self, $c) = @_;
485 $c->response->status(404);
5232dbdd 486 $c->response->body('Page not found');
4111e151 487}
488
775e96e0 489__PACKAGE__->meta->make_immutable;
490
491__END__
492
d137f7d5 493=head1 NAME
494
495Gitalist::Controller::Root - Root controller for the application
496
497=head1 DESCRIPTION
498
499This controller handles all of the root level paths for the application
500
501=head1 METHODS
502
5bb401c6 503=head2 root
504
505Root of chained actions
506
507=head2 base
508
509Populate the header and footer. Perhaps not the best location.
510
511=head2 index
512
82bc0f05 513Provides the repository listing.
5bb401c6 514
515=head2 end
516
517Attempt to render a view, if needed.
d137f7d5 518
519=head2 blame
520
d137f7d5 521=head2 error_404
522
523=head2 history
524
525=head2 opml
526
82bc0f05 527=head2 repository_index
da8f4f82 528
775e96e0 529=head1 AUTHORS
89de6a33 530
775e96e0 531See L<Gitalist> for authors.
89de6a33 532
533=head1 LICENSE
534
775e96e0 535See L<Gitalist> for the license.
89de6a33 536
537=cut