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 $search = $c->req->param('s') || '';
52 search_text => $search,
58 A summary of what's happening in the repo.
62 sub summary : Chained('base') Args(0) {
63 my ( $self, $c ) = @_;
64 my $repository = $c->stash->{Repository};
65 $c->detach('error_404') unless $repository;
66 my $commit = $self->_get_object($c);
67 my @heads = @{$repository->heads};
68 my $maxitems = Gitalist->config->{paging}{summary} || 10;
71 log_lines => [$repository->list_revs(
72 sha1 => $commit->sha1,
75 refs => $repository->references,
76 heads => [ @heads[0 .. ($#heads < $maxitems ? $#heads : $maxitems)] ],
82 The current list of heads (aka branches) in the repo.
86 sub heads : Chained('base') Args(0) {
87 my ( $self, $c ) = @_;
88 my $repository = $c->stash->{Repository};
90 commit => $self->_get_object($c),
91 heads => $repository->heads,
97 The current list of tags in the repo.
101 sub tags : Chained('base') Args(0) {
102 my ( $self, $c ) = @_;
103 my $repository = $c->stash->{Repository};
105 commit => $self->_get_object($c),
106 tags => $repository->tags,
110 sub blame : Chained('base') Args(0) {
113 my $repository = $c->stash->{Repository};
114 my $h = $c->req->param('h')
115 || $repository->hash_by_path($c->req->param('hb'), $c->req->param('f'))
116 || die "No file or sha1 provided.";
117 my $hb = $c->req->param('hb')
118 || $repository->head_hash
119 || die "Couldn't discern the corresponding head.";
120 my $filename = $c->req->param('f') || '';
122 my $blame = $repository->get_object($hb)->blame($filename, $h);
125 head => $repository->get_object($hb),
126 filename => $filename,
128 # XXX Hack hack hack, see View::SyntaxHighlight
129 language => ($filename =~ /\.p[lm]$/i ? 'Perl' : ''),
130 blob => join("\n", map $_->{line}, @$blame),
133 $c->forward('View::SyntaxHighlight')
134 unless $c->stash->{no_wrapper};
138 my ( $self, $c ) = @_;
139 my $repository = $c->stash->{Repository};
140 my $h = $c->req->param('h')
141 || $repository->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 || $repository->head_hash
145 || die "Couldn't discern the corresponding head.";
147 my $filename = $c->req->param('f') || '';
149 my $blob = $repository->get_object($h);
150 $blob = $repository->get_object(
151 $repository->hash_by_path($h || $hb, $filename)
152 ) if $blob->type ne 'blob';
154 return $blob, $repository->get_object($hb), $filename;
159 The blob action i.e the contents of a file.
163 sub blob : Chained('base') Args(0) {
164 my ( $self, $c ) = @_;
166 my($blob, $head, $filename) = $self->_blob_objs($c);
168 blob => $blob->content,
170 filename => $filename,
171 # XXX Hack hack hack, see View::SyntaxHighlight
172 language => ($filename =~ /\.p[lm]$/i ? 'Perl' : ''),
175 $c->forward('View::SyntaxHighlight')
176 unless $c->stash->{no_wrapper};
181 The plain text version of blob, where file is rendered as is.
185 sub blob_plain : Chained('base') Args(0) {
188 my($blob) = $self->_blob_objs($c);
189 $c->response->content_type('text/plain; charset=utf-8');
190 $c->response->body($blob->content);
191 $c->response->status(200);
194 =head2 blobdiff_plain
196 The plain text version of blobdiff.
200 sub blobdiff_plain : Chained('base') Args(0) {
203 $c->stash(no_wrapper => 1);
204 $c->response->content_type('text/plain; charset=utf-8');
206 $c->forward('blobdiff');
211 Exposes a given diff of a blob.
215 sub blobdiff : Chained('base') Args(0) {
216 my ( $self, $c ) = @_;
217 my $commit = $self->_get_object($c, $c->req->param('hb'));
218 my $filename = $c->req->param('f')
219 || croak("No file specified!");
220 my($tree, $patch) = $c->stash->{Repository}->diff(
223 parent => $c->req->param('hpb') || undef,
229 filename => $filename,
230 # XXX Hack hack hack, see View::SyntaxHighlight
231 blobs => [$patch->[0]->{diff}],
235 $c->forward('View::SyntaxHighlight')
236 unless $c->stash->{no_wrapper};
241 Exposes a given commit.
245 sub commit : Chained('base') Args(0) {
246 my ( $self, $c ) = @_;
247 my $repository = $c->stash->{Repository};
248 my $commit = $self->_get_object($c);
251 diff_tree => ($repository->diff(commit => $commit))[0],
252 refs => $repository->references,
258 Exposes a given diff of a commit.
262 sub commitdiff : Chained('base') Args(0) {
263 my ( $self, $c ) = @_;
264 my $commit = $self->_get_object($c);
265 my($tree, $patch) = $c->stash->{Repository}->diff(
267 parent => $c->req->param('hp') || undef,
274 # XXX Hack hack hack, see View::SyntaxHighlight
275 blobs => [map $_->{diff}, @$patch],
279 $c->forward('View::SyntaxHighlight')
280 unless $c->stash->{no_wrapper};
283 sub commitdiff_plain : Chained('base') Args(0) {
286 $c->stash(no_wrapper => 1);
287 $c->response->content_type('text/plain; charset=utf-8');
289 $c->forward('commitdiff');
294 Expose an abbreviated log of a given sha1.
298 sub shortlog : Chained('base') Args(0) {
299 my ( $self, $c ) = @_;
301 my $repository = $c->stash->{Repository};
302 my $commit = $self->_get_object($c, $c->req->param('hb'));
303 my $filename = $c->req->param('f') || '';
306 sha1 => $commit->sha1,
307 count => Gitalist->config->{paging}{log} || 25,
308 ($filename ? (file => $filename) : ())
311 my $page = $c->req->param('pg') || 0;
312 $logargs{skip} = $c->req->param('pg') * $logargs{count}
313 if $c->req->param('pg');
317 log_lines => [$repository->list_revs(%logargs)],
318 refs => $repository->references,
320 filename => $filename,
326 Calls shortlog internally. Perhaps that should be reversed ...
330 sub log : Chained('base') Args(0) {
331 $_[0]->shortlog($_[1]);
334 # For legacy support.
335 sub history : Chained('base') Args(0) {
336 my ( $self, $c ) = @_;
338 my $repository = $c->stash->{Repository};
339 my $file = $repository->get_object(
340 $repository->hash_by_path(
341 $repository->head_hash,
342 $c->stash->{filename}
346 filetype => $file->type,
352 The tree of a given commit.
356 sub tree : Chained('base') Args(0) {
357 my ( $self, $c ) = @_;
358 my $repository = $c->stash->{Repository};
359 my $commit = $self->_get_object($c, $c->req->param('hb'));
360 my $filename = $c->req->param('f') || '';
362 ? $repository->get_object($repository->hash_by_path($commit->sha1, $filename))
363 : $repository->get_object($commit->tree_sha1)
368 tree_list => [$repository->list_tree($tree->sha1)],
369 path => $c->req->param('f') || '',
375 Expose the local reflog. This may go away.
379 sub reflog : Chained('base') Args(0) {
380 my ( $self, $c ) = @_;
381 my @log = $c->stash->{Repository}->reflog(
392 The action for the search form.
396 sub search : Chained('base') Args(0) {
398 my $repository = $c->stash->{Repository};
399 my $commit = $self->_get_object($c);
400 # Lifted from /shortlog.
402 sha1 => $commit->sha1,
403 count => Gitalist->config->{paging}{log},
404 ($c->req->param('f') ? (file => $c->req->param('f')) : ()),
406 type => $c->req->param('type'),
407 text => $c->req->param('text'),
408 regexp => $c->req->param('regexp') || 0,
414 results => [$repository->list_revs(%logargs)],
415 # This could be added - page => $page,
421 Provides some help for the search form.
425 sub search_help : Chained('base') Args(0) {
427 $c->stash(template => 'search_help.tt2');
432 Provides an atom feed for a given repository.
436 sub atom : Chained('base') Args(0) {
439 my $feed = XML::Atom::Feed->new;
441 my $host = lc Sys::Hostname::hostname();
442 $feed->title($host . ' - ' . Gitalist->config->{name});
443 $feed->updated(~~DateTime->now);
445 my $repository = $c->stash->{Repository};
447 sha1 => $repository->head_hash,
448 count => Gitalist->config->{paging}{log} || 25,
449 ($c->req->param('f') ? (file => $c->req->param('f')) : ())
452 my $mk_title = $c->stash->{short_cmt};
453 for my $commit ($repository->list_revs(%logargs)) {
454 my $entry = XML::Atom::Entry->new;
455 $entry->title( $mk_title->($commit->comment) );
456 $entry->id($c->uri_for('commit', {h=>$commit->sha1}));
458 $entry->content($commit->comment);
459 $feed->add_entry($entry);
462 $c->response->body($feed->as_xml);
463 $c->response->content_type('application/atom+xml');
464 $c->response->status(200);
469 Provides an RSS feed for a given repository.
473 sub rss : Chained('base') Args(0) {
476 my $repository = $c->stash->{Repository};
478 my $rss = XML::RSS->new(version => '2.0');
480 title => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name},
481 link => $c->uri_for('summary', {p=>$repository->name}),
483 description => $repository->description,
484 pubDate => DateTime->now,
485 lastBuildDate => DateTime->now,
489 sha1 => $repository->head_hash,
490 count => Gitalist->config->{paging}{log} || 25,
491 ($c->req->param('f') ? (file => $c->req->param('f')) : ())
493 my $mk_title = $c->stash->{short_cmt};
494 for my $commit ($repository->list_revs(%logargs)) {
495 # XXX Needs work ....
497 title => $mk_title->($commit->comment),
498 permaLink => $c->uri_for(commit => {h=>$commit->sha1}),
499 description => $commit->comment,
503 $c->response->body($rss->as_string);
504 $c->response->content_type('application/rss+xml');
505 $c->response->status(200);
508 sub opml : Chained('base') Args(0) {
511 my $opml = XML::OPML::SimpleGen->new();
513 $opml->head(title => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name});
515 my @list = @{ $c->model()->repositories };
516 die 'No repositories found in '. $c->model->repo_dir
519 for my $proj ( @list ) {
520 $opml->insert_outline(
521 text => $proj->name. ' - '. $proj->description,
522 xmlUrl => $c->uri_for(rss => {p => $proj->name}),
526 $c->response->body($opml->as_string);
527 $c->response->content_type('application/rss');
528 $c->response->status(200);
533 A raw patch for a given commit.
537 sub patch : Chained('base') Args(0) {
539 $c->detach('patches', [1]);
544 The patcheset for a given commit ???
548 sub patches : Chained('base') Args(0) {
549 my ($self, $c, $count) = @_;
550 $count ||= Gitalist->config->{patches}{max};
551 my $commit = $self->_get_object($c);
552 my $parent = $c->req->param('hp') || undef;
553 my $patch = $commit->get_patch( $parent, $count );
554 $c->response->body($patch);
555 $c->response->content_type('text/plain');
556 $c->response->status(200);
561 Provides a snapshot of a given commit.
565 sub snapshot : Chained('base') Args(0) {
567 my $format = $c->req->param('sf') || 'tgz';
569 my $sha1 = $c->req->param('h') || $self->_get_object($c)->sha1;
570 my @snap = $c->stash->{Repository}->snapshot(
574 $c->response->status(200);
575 $c->response->headers->header( 'Content-Disposition' =>
576 "attachment; filename=$snap[0]");
577 $c->response->body($snap[1]);
581 sub base : Chained('/root') PathPart('') CaptureArgs(0) {
584 my $git_version = `git --version`;
587 git_version => $git_version,
588 version => $Gitalist::VERSION,
590 # XXX Move these to a plugin!
592 return 'never' unless $_[0];
593 return age_string(time - $_[0]->epoch);
597 my($line) = split /\n/, $cmt;
598 $line =~ s/^(.{70,80}\b).*/$1 \x{2026}/;
601 abridged_description => sub {
602 join(' ', grep { defined } (split / /, shift)[0..10]);
607 sub end : ActionClass('RenderView') {
609 # Give repository views the current HEAD.
610 if ($c->stash->{Repository}) {
611 $c->stash->{HEAD} = $c->stash->{Repository}->head_hash;
615 sub error_404 : Action {
617 $c->response->status(404);
618 $c->response->body('Page not found');
621 __PACKAGE__->meta->make_immutable;
627 Gitalist::Controller::Root - Root controller for the application
631 This controller handles all of the root level paths for the application
637 Root of chained actions
641 Populate the header and footer. Perhaps not the best location.
645 Provides the repository listing.
649 Attempt to render a view, if needed.
653 =head2 commitdiff_plain
661 =head2 repository_index
665 See L<Gitalist> for authors.
669 See L<Gitalist> for the license.