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()->projects };
50 die 'No projects 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,
68 # FIXME - WTF is this for?
69 sub project_index : Chained('base') Args(0) {
70 my ( $self, $c ) = @_;
72 my @list = @{ $c->model()->projects };
73 die 'No projects found in '. $c->model->repo_dir
76 $c->response->content_type('text/plain');
78 join "\n", map $_->name, @list
80 $c->response->status(200);
85 A summary of what's happening in the repo.
89 sub summary : Chained('base') Args(0) {
90 my ( $self, $c ) = @_;
91 my $project = $c->stash->{Repository};
92 $c->detach('error_404') unless $project;
93 my $commit = $self->_get_object($c);
94 my @heads = @{$project->heads};
95 my $maxitems = Gitalist->config->{paging}{summary} || 10;
98 log_lines => [$project->list_revs(
99 sha1 => $commit->sha1,
102 refs => $project->references,
103 heads => [ @heads[0 .. ($#heads < $maxitems ? $#heads : $maxitems)] ],
110 The current list of heads (aka branches) in the repo.
114 sub heads : Chained('base') Args(0) {
115 my ( $self, $c ) = @_;
116 my $project = $c->stash->{Repository};
118 commit => $self->_get_object($c),
119 heads => $project->heads,
126 The current list of tags in the repo.
130 sub tags : Chained('base') Args(0) {
131 my ( $self, $c ) = @_;
132 my $project = $c->stash->{Repository};
134 commit => $self->_get_object($c),
135 tags => $project->tags,
140 sub blame : Chained('base') Args(0) {
143 my $project = $c->stash->{Repository};
144 my $h = $c->req->param('h')
145 || $project->hash_by_path($c->req->param('hb'), $c->req->param('f'))
146 || die "No file or sha1 provided.";
147 my $hb = $c->req->param('hb')
148 || $project->head_hash
149 || die "Couldn't discern the corresponding head.";
150 my $filename = $c->req->param('f') || '';
152 my $blame = $project->get_object($hb)->blame($filename);
155 head => $project->get_object($hb),
156 filename => $filename,
158 # XXX Hack hack hack, see View::SyntaxHighlight
159 language => ($filename =~ /\.p[lm]$/i ? 'Perl' : ''),
160 blob => join("\n", map $_->{line}, @$blame),
163 $c->forward('View::SyntaxHighlight')
164 unless $c->stash->{no_wrapper};
168 my ( $self, $c ) = @_;
169 my $project = $c->stash->{Repository};
170 my $h = $c->req->param('h')
171 || $project->hash_by_path($c->req->param('hb'), $c->req->param('f'))
172 || die "No file or sha1 provided.";
173 my $hb = $c->req->param('hb')
174 || $project->head_hash
175 || die "Couldn't discern the corresponding head.";
177 my $filename = $c->req->param('f') || '';
179 my $blob = $project->get_object($h);
180 $blob = $project->get_object(
181 $project->hash_by_path($h || $hb, $filename)
182 ) if $blob->type ne 'blob';
184 return $blob, $project->get_object($hb), $filename;
189 The blob action i.e the contents of a file.
193 sub blob : Chained('base') Args(0) {
194 my ( $self, $c ) = @_;
196 my($blob, $head, $filename) = $self->_blob_objs($c);
198 blob => $blob->content,
200 filename => $filename,
201 # XXX Hack hack hack, see View::SyntaxHighlight
202 language => ($filename =~ /\.p[lm]$/i ? 'Perl' : ''),
206 $c->forward('View::SyntaxHighlight')
207 unless $c->stash->{no_wrapper};
212 The plain text version of blob, where file is rendered as is.
216 sub blob_plain : Chained('base') Args(0) {
219 my($blob) = $self->_blob_objs($c);
220 $c->response->content_type('text/plain; charset=utf-8');
221 $c->response->body($blob->content);
222 $c->response->status(200);
225 =head2 blobdiff_plain
227 The plain text version of blobdiff.
231 sub blobdiff_plain : Chained('base') Args(0) {
234 $c->stash(no_wrapper => 1);
235 $c->response->content_type('text/plain; charset=utf-8');
237 $c->forward('blobdiff');
242 Exposes a given diff of a blob.
246 sub blobdiff : Chained('base') Args(0) {
247 my ( $self, $c ) = @_;
248 my $commit = $self->_get_object($c, $c->req->param('hb'));
249 my $filename = $c->req->param('f')
250 || croak("No file specified!");
251 my($tree, $patch) = $c->stash->{Repository}->diff(
254 parent => $c->req->param('hpb') || undef,
260 filename => $filename,
261 # XXX Hack hack hack, see View::SyntaxHighlight
262 blobs => [$patch->[0]->{diff}],
264 action => 'blobdiff',
267 $c->forward('View::SyntaxHighlight')
268 unless $c->stash->{no_wrapper};
273 Exposes a given commit.
277 sub commit : Chained('base') Args(0) {
278 my ( $self, $c ) = @_;
279 my $project = $c->stash->{Repository};
280 my $commit = $self->_get_object($c);
283 diff_tree => ($project->diff(commit => $commit))[0],
284 refs => $project->references,
291 Exposes a given diff of a commit.
295 sub commitdiff : Chained('base') Args(0) {
296 my ( $self, $c ) = @_;
297 my $commit = $self->_get_object($c);
298 my($tree, $patch) = $c->stash->{Repository}->diff(
300 parent => $c->req->param('hp') || undef,
307 # XXX Hack hack hack, see View::SyntaxHighlight
308 blobs => [map $_->{diff}, @$patch],
310 action => 'commitdiff',
313 $c->forward('View::SyntaxHighlight')
314 unless $c->stash->{no_wrapper};
317 sub commitdiff_plain : Chained('base') Args(0) {
320 $c->stash(no_wrapper => 1);
321 $c->response->content_type('text/plain; charset=utf-8');
323 $c->forward('commitdiff');
328 Expose an abbreviated log of a given sha1.
332 sub shortlog : Chained('base') Args(0) {
333 my ( $self, $c ) = @_;
335 my $project = $c->stash->{Repository};
336 my $commit = $self->_get_object($c, $c->req->param('hb'));
337 my $filename = $c->req->param('f') || '';
340 sha1 => $commit->sha1,
341 count => Gitalist->config->{paging}{log} || 25,
342 ($filename ? (file => $filename) : ())
345 my $page = $c->req->param('pg') || 0;
346 $logargs{skip} = $c->req->param('pg') * $logargs{count}
347 if $c->req->param('pg');
351 log_lines => [$project->list_revs(%logargs)],
352 refs => $project->references,
354 filename => $filename,
355 action => 'shortlog',
361 Calls shortlog internally. Perhaps that should be reversed ...
365 sub log : Chained('base') Args(0) {
366 $_[0]->shortlog($_[1]);
367 $_[1]->stash->{action} = 'log';
370 # For legacy support.
371 sub history : Chained('base') Args(0) {
372 my ( $self, $c ) = @_;
374 my $project = $c->stash->{Repository};
375 my $file = $project->get_object(
376 $project->hash_by_path(
378 $c->stash->{filename}
381 $c->stash( action => 'history',
382 filetype => $file->type,
388 The tree of a given commit.
392 sub tree : Chained('base') Args(0) {
393 my ( $self, $c ) = @_;
394 my $project = $c->stash->{Repository};
395 my $commit = $self->_get_object($c, $c->req->param('hb'));
396 my $filename = $c->req->param('f') || '';
398 ? $project->get_object($project->hash_by_path($commit->sha1, $filename))
399 : $project->get_object($commit->tree_sha1)
404 tree_list => [$project->list_tree($tree->sha1)],
405 path => $c->req->param('f') || '',
412 Expose the local reflog. This may go away.
416 sub reflog : Chained('base') Args(0) {
417 my ( $self, $c ) = @_;
418 my @log = $c->stash->{Repository}->reflog(
430 The action for the search form.
434 sub search : Chained('base') Args(0) {
436 $c->stash(current_action => 'GitRepos');
437 my $project = $c->stash->{Repository};
438 my $commit = $self->_get_object($c);
439 # Lifted from /shortlog.
441 sha1 => $commit->sha1,
442 count => Gitalist->config->{paging}{log},
443 ($c->req->param('f') ? (file => $c->req->param('f')) : ()),
445 type => $c->req->param('type'),
446 text => $c->req->param('text'),
447 regexp => $c->req->param('regexp') || 0,
453 results => [$project->list_revs(%logargs)],
455 # This could be added - page => $page,
461 Provides some help for the search form.
465 sub search_help : Chained('base') Args(0) {
467 $c->stash(template => 'search_help.tt2');
472 Provides an atom feed for a given project.
476 sub atom : Chained('base') Args(0) {
479 my $feed = XML::Atom::Feed->new;
481 my $host = lc Sys::Hostname::hostname();
482 $feed->title($host . ' - ' . Gitalist->config->{name});
483 $feed->updated(~~DateTime->now);
485 my $project = $c->stash->{Repository};
487 sha1 => $project->head_hash,
488 count => Gitalist->config->{paging}{log} || 25,
489 ($c->req->param('f') ? (file => $c->req->param('f')) : ())
492 my $mk_title = $c->stash->{short_cmt};
493 for my $commit ($project->list_revs(%logargs)) {
494 my $entry = XML::Atom::Entry->new;
495 $entry->title( $mk_title->($commit->comment) );
496 $entry->id($c->uri_for('commit', {h=>$commit->sha1}));
498 $entry->content($commit->comment);
499 $feed->add_entry($entry);
502 $c->response->body($feed->as_xml);
503 $c->response->content_type('application/atom+xml');
504 $c->response->status(200);
509 Provides an RSS feed for a given project.
513 sub rss : Chained('base') Args(0) {
516 my $project = $c->stash->{Repository};
518 my $rss = XML::RSS->new(version => '2.0');
520 title => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name},
521 link => $c->uri_for('summary', {p=>$project->name}),
523 description => $project->description,
524 pubDate => DateTime->now,
525 lastBuildDate => DateTime->now,
529 sha1 => $project->head_hash,
530 count => Gitalist->config->{paging}{log} || 25,
531 ($c->req->param('f') ? (file => $c->req->param('f')) : ())
533 my $mk_title = $c->stash->{short_cmt};
534 for my $commit ($project->list_revs(%logargs)) {
535 # XXX Needs work ....
537 title => $mk_title->($commit->comment),
538 permaLink => $c->uri_for(commit => {h=>$commit->sha1}),
539 description => $commit->comment,
543 $c->response->body($rss->as_string);
544 $c->response->content_type('application/rss+xml');
545 $c->response->status(200);
548 sub opml : Chained('base') Args(0) {
551 my $opml = XML::OPML::SimpleGen->new();
553 $opml->head(title => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name});
555 my @list = @{ $c->model()->projects };
556 die 'No projects found in '. $c->model->repo_dir
559 for my $proj ( @list ) {
560 $opml->insert_outline(
561 text => $proj->name. ' - '. $proj->description,
562 xmlUrl => $c->uri_for(rss => {p => $proj->name}),
566 $c->response->body($opml->as_string);
567 $c->response->content_type('application/rss');
568 $c->response->status(200);
573 A raw patch for a given commit.
577 sub patch : Chained('base') Args(0) {
579 $c->detach('patches', [1]);
584 The patcheset for a given commit ???
588 sub patches : Chained('base') Args(0) {
589 my ($self, $c, $count) = @_;
590 $count ||= Gitalist->config->{patches}{max};
591 my $commit = $self->_get_object($c);
592 my $parent = $c->req->param('hp') || undef;
593 my $patch = $commit->get_patch( $parent, $count );
594 $c->response->body($patch);
595 $c->response->content_type('text/plain');
596 $c->response->status(200);
601 Provides a snapshot of a given commit.
605 sub snapshot : Chained('base') Args(0) {
607 my $format = $c->req->param('sf') || 'tgz';
609 my $sha1 = $c->req->param('h') || $self->_get_object($c)->sha1;
610 my @snap = $c->stash->{Repository}->snapshot(
614 $c->response->status(200);
615 $c->response->headers->header( 'Content-Disposition' =>
616 "attachment; filename=$snap[0]");
617 $c->response->body($snap[1]);
621 sub base : Chained('/root') PathPart('') CaptureArgs(0) {
624 my $project = $c->req->param('p');
625 if (defined $project) {
627 $c->stash(Repository => $c->model('GitRepos')->get_repository($project));
630 $c->detach('/error_404');
634 my $a_project = $c->stash->{Repository} || $c->model()->projects->[0];
636 git_version => $a_project->run_cmd('--version'),
637 version => $Gitalist::VERSION,
639 # XXX Move these to a plugin!
641 return 'never' unless $_[0];
642 return age_string(time - $_[0]->epoch);
646 my($line) = split /\n/, $cmt;
647 $line =~ s/^(.{70,80}\b).*/$1 \x{2026}/;
650 abridged_description => sub {
651 join(' ', grep { defined } (split / /, shift)[0..10]);
656 sub end : ActionClass('RenderView') {
658 # Give project views the current HEAD.
659 if ($c->stash->{Repository}) {
660 $c->stash->{HEAD} = $c->stash->{Repository}->head_hash;
664 sub error_404 : Action {
666 $c->response->status(404);
667 $c->response->body('Page not found');
670 __PACKAGE__->meta->make_immutable;
676 Gitalist::Controller::Root - Root controller for the application
680 This controller handles all of the root level paths for the application
686 Root of chained actions
690 Populate the header and footer. Perhaps not the best location.
694 Provides the project listing.
698 Attempt to render a view, if needed.
702 =head2 commitdiff_plain
714 See L<Gitalist> for authors.
718 See L<Gitalist> for the license.