1 package Gitalist::Controller::Root;
9 use XML::OPML::SimpleGen;
11 use Gitalist::Utils qw/ age_string /;
13 use namespace::autoclean;
15 BEGIN { extends 'Catalyst::Controller' }
17 __PACKAGE__->config->{namespace} = '';
19 sub root : Chained('/') PathPart('') CaptureArgs(0) {}
22 my($self, $c, $haveh) = @_;
24 my $h = $haveh || $c->req->param('h') || '';
25 my $f = $c->req->param('f');
27 my $m = $c->stash->{Repository};
30 # Either use the provided h(ash) parameter, the f(ile) parameter or just use HEAD.
31 my $hash = ($h =~ /[^a-f0-9]/ ? $m->head_hash($h) : $h)
32 || ($f && $m->hash_by_path($f))
34 # XXX This could definitely use more context.
35 || Carp::croak("Couldn't find a hash for the commit object!");
37 my $obj = $m->get_object($hash)
38 or Carp::croak("Couldn't find a object for '$hash' in '$pd'!");
43 sub index : Chained('base') PathPart('') Args(0) {
44 my ( $self, $c ) = @_;
46 $c->detach($c->req->param('a'))
47 if $c->req->param('a');
49 my @list = @{ $c->model()->repositories };
50 die 'No repositories found in '. $c->model->repo_dir
53 my $search = $c->req->param('s') || '';
56 index($_->name, $search) > -1
57 or ( $_->description !~ /^Unnamed repository/ and index($_->description, $search) > -1 )
62 search_text => $search,
63 repositories => \@list,
68 # FIXME - WTF is this for?
69 sub repository_index : Chained('base') Args(0) {
70 my ( $self, $c ) = @_;
72 my @list = @{ $c->model()->repositories };
73 die 'No repositories found in '. $c->model->repo_dir
76 $c->response->content_type('text/plain');
78 join "\n", map $_->name, @list
80 $c->response->status(200);
82 # FIXME - maintain compatibility with previous URI
83 sub project_index : Chained('base') Args(0) {
85 $c->detach('repository_index');
90 A summary of what's happening in the repo.
94 sub summary : Chained('base') Args(0) {
95 my ( $self, $c ) = @_;
96 my $repository = $c->stash->{Repository};
97 $c->detach('error_404') unless $repository;
98 my $commit = $self->_get_object($c);
99 my @heads = @{$repository->heads};
100 my $maxitems = Gitalist->config->{paging}{summary} || 10;
103 log_lines => [$repository->list_revs(
104 sha1 => $commit->sha1,
107 refs => $repository->references,
108 heads => [ @heads[0 .. ($#heads < $maxitems ? $#heads : $maxitems)] ],
115 The current list of heads (aka branches) in the repo.
119 sub heads : Chained('base') Args(0) {
120 my ( $self, $c ) = @_;
121 my $repository = $c->stash->{Repository};
123 commit => $self->_get_object($c),
124 heads => $repository->heads,
131 The current list of tags in the repo.
135 sub tags : Chained('base') Args(0) {
136 my ( $self, $c ) = @_;
137 my $repository = $c->stash->{Repository};
139 commit => $self->_get_object($c),
140 tags => $repository->tags,
145 sub blame : Chained('base') Args(0) {
148 my $repository = $c->stash->{Repository};
149 my $h = $c->req->param('h')
150 || $repository->hash_by_path($c->req->param('hb'), $c->req->param('f'))
151 || die "No file or sha1 provided.";
152 my $hb = $c->req->param('hb')
153 || $repository->head_hash
154 || die "Couldn't discern the corresponding head.";
155 my $filename = $c->req->param('f') || '';
157 my $blame = $repository->get_object($hb)->blame($filename, $h);
160 head => $repository->get_object($hb),
161 filename => $filename,
163 # XXX Hack hack hack, see View::SyntaxHighlight
164 language => ($filename =~ /\.p[lm]$/i ? 'Perl' : ''),
165 blob => join("\n", map $_->{line}, @$blame),
168 $c->forward('View::SyntaxHighlight')
169 unless $c->stash->{no_wrapper};
173 my ( $self, $c ) = @_;
174 my $repository = $c->stash->{Repository};
175 my $h = $c->req->param('h')
176 || $repository->hash_by_path($c->req->param('hb'), $c->req->param('f'))
177 || die "No file or sha1 provided.";
178 my $hb = $c->req->param('hb')
179 || $repository->head_hash
180 || die "Couldn't discern the corresponding head.";
182 my $filename = $c->req->param('f') || '';
184 my $blob = $repository->get_object($h);
185 $blob = $repository->get_object(
186 $repository->hash_by_path($h || $hb, $filename)
187 ) if $blob->type ne 'blob';
189 return $blob, $repository->get_object($hb), $filename;
194 The blob action i.e the contents of a file.
198 sub blob : Chained('base') Args(0) {
199 my ( $self, $c ) = @_;
201 my($blob, $head, $filename) = $self->_blob_objs($c);
203 blob => $blob->content,
205 filename => $filename,
206 # XXX Hack hack hack, see View::SyntaxHighlight
207 language => ($filename =~ /\.p[lm]$/i ? 'Perl' : ''),
211 $c->forward('View::SyntaxHighlight')
212 unless $c->stash->{no_wrapper};
217 The plain text version of blob, where file is rendered as is.
221 sub blob_plain : Chained('base') Args(0) {
224 my($blob) = $self->_blob_objs($c);
225 $c->response->content_type('text/plain; charset=utf-8');
226 $c->response->body($blob->content);
227 $c->response->status(200);
230 =head2 blobdiff_plain
232 The plain text version of blobdiff.
236 sub blobdiff_plain : Chained('base') Args(0) {
239 $c->stash(no_wrapper => 1);
240 $c->response->content_type('text/plain; charset=utf-8');
242 $c->forward('blobdiff');
247 Exposes a given diff of a blob.
251 sub blobdiff : Chained('base') Args(0) {
252 my ( $self, $c ) = @_;
253 my $commit = $self->_get_object($c, $c->req->param('hb'));
254 my $filename = $c->req->param('f')
255 || croak("No file specified!");
256 my($tree, $patch) = $c->stash->{Repository}->diff(
259 parent => $c->req->param('hpb') || undef,
265 filename => $filename,
266 # XXX Hack hack hack, see View::SyntaxHighlight
267 blobs => [$patch->[0]->{diff}],
269 action => 'blobdiff',
272 $c->forward('View::SyntaxHighlight')
273 unless $c->stash->{no_wrapper};
278 Exposes a given commit.
282 sub commit : Chained('base') Args(0) {
283 my ( $self, $c ) = @_;
284 my $repository = $c->stash->{Repository};
285 my $commit = $self->_get_object($c);
288 diff_tree => ($repository->diff(commit => $commit))[0],
289 refs => $repository->references,
296 Exposes a given diff of a commit.
300 sub commitdiff : Chained('base') Args(0) {
301 my ( $self, $c ) = @_;
302 my $commit = $self->_get_object($c);
303 my($tree, $patch) = $c->stash->{Repository}->diff(
305 parent => $c->req->param('hp') || undef,
312 # XXX Hack hack hack, see View::SyntaxHighlight
313 blobs => [map $_->{diff}, @$patch],
315 action => 'commitdiff',
318 $c->forward('View::SyntaxHighlight')
319 unless $c->stash->{no_wrapper};
322 sub commitdiff_plain : Chained('base') Args(0) {
325 $c->stash(no_wrapper => 1);
326 $c->response->content_type('text/plain; charset=utf-8');
328 $c->forward('commitdiff');
333 Expose an abbreviated log of a given sha1.
337 sub shortlog : Chained('base') Args(0) {
338 my ( $self, $c ) = @_;
340 my $repository = $c->stash->{Repository};
341 my $commit = $self->_get_object($c, $c->req->param('hb'));
342 my $filename = $c->req->param('f') || '';
345 sha1 => $commit->sha1,
346 count => Gitalist->config->{paging}{log} || 25,
347 ($filename ? (file => $filename) : ())
350 my $page = $c->req->param('pg') || 0;
351 $logargs{skip} = $c->req->param('pg') * $logargs{count}
352 if $c->req->param('pg');
356 log_lines => [$repository->list_revs(%logargs)],
357 refs => $repository->references,
359 filename => $filename,
360 action => 'shortlog',
366 Calls shortlog internally. Perhaps that should be reversed ...
370 sub log : Chained('base') Args(0) {
371 $_[0]->shortlog($_[1]);
372 $_[1]->stash->{action} = 'log';
375 # For legacy support.
376 sub history : Chained('base') Args(0) {
377 my ( $self, $c ) = @_;
379 my $repository = $c->stash->{Repository};
380 my $file = $repository->get_object(
381 $repository->hash_by_path(
382 $repository->head_hash,
383 $c->stash->{filename}
386 $c->stash( action => 'history',
387 filetype => $file->type,
393 The tree of a given commit.
397 sub tree : Chained('base') Args(0) {
398 my ( $self, $c ) = @_;
399 my $repository = $c->stash->{Repository};
400 my $commit = $self->_get_object($c, $c->req->param('hb'));
401 my $filename = $c->req->param('f') || '';
403 ? $repository->get_object($repository->hash_by_path($commit->sha1, $filename))
404 : $repository->get_object($commit->tree_sha1)
409 tree_list => [$repository->list_tree($tree->sha1)],
410 path => $c->req->param('f') || '',
417 Expose the local reflog. This may go away.
421 sub reflog : Chained('base') Args(0) {
422 my ( $self, $c ) = @_;
423 my @log = $c->stash->{Repository}->reflog(
435 The action for the search form.
439 sub search : Chained('base') Args(0) {
441 my $repository = $c->stash->{Repository};
442 my $commit = $self->_get_object($c);
443 # Lifted from /shortlog.
445 sha1 => $commit->sha1,
446 count => Gitalist->config->{paging}{log},
447 ($c->req->param('f') ? (file => $c->req->param('f')) : ()),
449 type => $c->req->param('type'),
450 text => $c->req->param('text'),
451 regexp => $c->req->param('regexp') || 0,
457 results => [$repository->list_revs(%logargs)],
459 # This could be added - page => $page,
465 Provides some help for the search form.
469 sub search_help : Chained('base') Args(0) {
471 $c->stash(template => 'search_help.tt2');
476 Provides an atom feed for a given repository.
480 sub atom : Chained('base') Args(0) {
483 my $feed = XML::Atom::Feed->new;
485 my $host = lc Sys::Hostname::hostname();
486 $feed->title($host . ' - ' . Gitalist->config->{name});
487 $feed->updated(~~DateTime->now);
489 my $repository = $c->stash->{Repository};
491 sha1 => $repository->head_hash,
492 count => Gitalist->config->{paging}{log} || 25,
493 ($c->req->param('f') ? (file => $c->req->param('f')) : ())
496 my $mk_title = $c->stash->{short_cmt};
497 for my $commit ($repository->list_revs(%logargs)) {
498 my $entry = XML::Atom::Entry->new;
499 $entry->title( $mk_title->($commit->comment) );
500 $entry->id($c->uri_for('commit', {h=>$commit->sha1}));
502 $entry->content($commit->comment);
503 $feed->add_entry($entry);
506 $c->response->body($feed->as_xml);
507 $c->response->content_type('application/atom+xml');
508 $c->response->status(200);
513 Provides an RSS feed for a given repository.
517 sub rss : Chained('base') Args(0) {
520 my $repository = $c->stash->{Repository};
522 my $rss = XML::RSS->new(version => '2.0');
524 title => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name},
525 link => $c->uri_for('summary', {p=>$repository->name}),
527 description => $repository->description,
528 pubDate => DateTime->now,
529 lastBuildDate => DateTime->now,
533 sha1 => $repository->head_hash,
534 count => Gitalist->config->{paging}{log} || 25,
535 ($c->req->param('f') ? (file => $c->req->param('f')) : ())
537 my $mk_title = $c->stash->{short_cmt};
538 for my $commit ($repository->list_revs(%logargs)) {
539 # XXX Needs work ....
541 title => $mk_title->($commit->comment),
542 permaLink => $c->uri_for(commit => {h=>$commit->sha1}),
543 description => $commit->comment,
547 $c->response->body($rss->as_string);
548 $c->response->content_type('application/rss+xml');
549 $c->response->status(200);
552 sub opml : Chained('base') Args(0) {
555 my $opml = XML::OPML::SimpleGen->new();
557 $opml->head(title => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name});
559 my @list = @{ $c->model()->repositories };
560 die 'No repositories found in '. $c->model->repo_dir
563 for my $proj ( @list ) {
564 $opml->insert_outline(
565 text => $proj->name. ' - '. $proj->description,
566 xmlUrl => $c->uri_for(rss => {p => $proj->name}),
570 $c->response->body($opml->as_string);
571 $c->response->content_type('application/rss');
572 $c->response->status(200);
577 A raw patch for a given commit.
581 sub patch : Chained('base') Args(0) {
583 $c->detach('patches', [1]);
588 The patcheset for a given commit ???
592 sub patches : Chained('base') Args(0) {
593 my ($self, $c, $count) = @_;
594 $count ||= Gitalist->config->{patches}{max};
595 my $commit = $self->_get_object($c);
596 my $parent = $c->req->param('hp') || undef;
597 my $patch = $commit->get_patch( $parent, $count );
598 $c->response->body($patch);
599 $c->response->content_type('text/plain');
600 $c->response->status(200);
605 Provides a snapshot of a given commit.
609 sub snapshot : Chained('base') Args(0) {
611 my $format = $c->req->param('sf') || 'tgz';
613 my $sha1 = $c->req->param('h') || $self->_get_object($c)->sha1;
614 my @snap = $c->stash->{Repository}->snapshot(
618 $c->response->status(200);
619 $c->response->headers->header( 'Content-Disposition' =>
620 "attachment; filename=$snap[0]");
621 $c->response->body($snap[1]);
625 sub base : Chained('/root') PathPart('') CaptureArgs(0) {
628 my $repository = $c->req->param('p');
629 if (defined $repository) {
631 $c->stash(Repository => $c->model()->get_repository($repository));
634 $c->detach('/error_404');
638 my $a_repository = $c->stash->{Repository} || $c->model()->repositories->[0];
640 git_version => $a_repository->run_cmd('--version'),
641 version => $Gitalist::VERSION,
643 # XXX Move these to a plugin!
645 return 'never' unless $_[0];
646 return age_string(time - $_[0]->epoch);
650 my($line) = split /\n/, $cmt;
651 $line =~ s/^(.{70,80}\b).*/$1 \x{2026}/;
654 abridged_description => sub {
655 join(' ', grep { defined } (split / /, shift)[0..10]);
660 sub end : ActionClass('RenderView') {
662 # Give repository views the current HEAD.
663 if ($c->stash->{Repository}) {
664 $c->stash->{HEAD} = $c->stash->{Repository}->head_hash;
668 sub error_404 : Action {
670 $c->response->status(404);
671 $c->response->body('Page not found');
674 __PACKAGE__->meta->make_immutable;
680 Gitalist::Controller::Root - Root controller for the application
684 This controller handles all of the root level paths for the application
690 Root of chained actions
694 Populate the header and footer. Perhaps not the best location.
698 Provides the repository listing.
702 Attempt to render a view, if needed.
706 =head2 commitdiff_plain
714 =head2 repository_index
718 See L<Gitalist> for authors.
722 See L<Gitalist> for the license.