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 $c->stash(current_action => 'GitRepos');
442 my $repository = $c->stash->{Repository};
443 my $commit = $self->_get_object($c);
444 # Lifted from /shortlog.
446 sha1 => $commit->sha1,
447 count => Gitalist->config->{paging}{log},
448 ($c->req->param('f') ? (file => $c->req->param('f')) : ()),
450 type => $c->req->param('type'),
451 text => $c->req->param('text'),
452 regexp => $c->req->param('regexp') || 0,
458 results => [$repository->list_revs(%logargs)],
460 # This could be added - page => $page,
466 Provides some help for the search form.
470 sub search_help : Chained('base') Args(0) {
472 $c->stash(template => 'search_help.tt2');
477 Provides an atom feed for a given repository.
481 sub atom : Chained('base') Args(0) {
484 my $feed = XML::Atom::Feed->new;
486 my $host = lc Sys::Hostname::hostname();
487 $feed->title($host . ' - ' . Gitalist->config->{name});
488 $feed->updated(~~DateTime->now);
490 my $repository = $c->stash->{Repository};
492 sha1 => $repository->head_hash,
493 count => Gitalist->config->{paging}{log} || 25,
494 ($c->req->param('f') ? (file => $c->req->param('f')) : ())
497 my $mk_title = $c->stash->{short_cmt};
498 for my $commit ($repository->list_revs(%logargs)) {
499 my $entry = XML::Atom::Entry->new;
500 $entry->title( $mk_title->($commit->comment) );
501 $entry->id($c->uri_for('commit', {h=>$commit->sha1}));
503 $entry->content($commit->comment);
504 $feed->add_entry($entry);
507 $c->response->body($feed->as_xml);
508 $c->response->content_type('application/atom+xml');
509 $c->response->status(200);
514 Provides an RSS feed for a given repository.
518 sub rss : Chained('base') Args(0) {
521 my $repository = $c->stash->{Repository};
523 my $rss = XML::RSS->new(version => '2.0');
525 title => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name},
526 link => $c->uri_for('summary', {p=>$repository->name}),
528 description => $repository->description,
529 pubDate => DateTime->now,
530 lastBuildDate => DateTime->now,
534 sha1 => $repository->head_hash,
535 count => Gitalist->config->{paging}{log} || 25,
536 ($c->req->param('f') ? (file => $c->req->param('f')) : ())
538 my $mk_title = $c->stash->{short_cmt};
539 for my $commit ($repository->list_revs(%logargs)) {
540 # XXX Needs work ....
542 title => $mk_title->($commit->comment),
543 permaLink => $c->uri_for(commit => {h=>$commit->sha1}),
544 description => $commit->comment,
548 $c->response->body($rss->as_string);
549 $c->response->content_type('application/rss+xml');
550 $c->response->status(200);
553 sub opml : Chained('base') Args(0) {
556 my $opml = XML::OPML::SimpleGen->new();
558 $opml->head(title => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name});
560 my @list = @{ $c->model()->repositories };
561 die 'No repositories found in '. $c->model->repo_dir
564 for my $proj ( @list ) {
565 $opml->insert_outline(
566 text => $proj->name. ' - '. $proj->description,
567 xmlUrl => $c->uri_for(rss => {p => $proj->name}),
571 $c->response->body($opml->as_string);
572 $c->response->content_type('application/rss');
573 $c->response->status(200);
578 A raw patch for a given commit.
582 sub patch : Chained('base') Args(0) {
584 $c->detach('patches', [1]);
589 The patcheset for a given commit ???
593 sub patches : Chained('base') Args(0) {
594 my ($self, $c, $count) = @_;
595 $count ||= Gitalist->config->{patches}{max};
596 my $commit = $self->_get_object($c);
597 my $parent = $c->req->param('hp') || undef;
598 my $patch = $commit->get_patch( $parent, $count );
599 $c->response->body($patch);
600 $c->response->content_type('text/plain');
601 $c->response->status(200);
606 Provides a snapshot of a given commit.
610 sub snapshot : Chained('base') Args(0) {
612 my $format = $c->req->param('sf') || 'tgz';
614 my $sha1 = $c->req->param('h') || $self->_get_object($c)->sha1;
615 my @snap = $c->stash->{Repository}->snapshot(
619 $c->response->status(200);
620 $c->response->headers->header( 'Content-Disposition' =>
621 "attachment; filename=$snap[0]");
622 $c->response->body($snap[1]);
626 sub base : Chained('/root') PathPart('') CaptureArgs(0) {
629 my $repository = $c->req->param('p');
630 if (defined $repository) {
632 $c->stash(Repository => $c->model('GitRepos')->get_repository($repository));
635 $c->detach('/error_404');
639 my $a_repository = $c->stash->{Repository} || $c->model()->repositories->[0];
641 git_version => $a_repository->run_cmd('--version'),
642 version => $Gitalist::VERSION,
644 # XXX Move these to a plugin!
646 return 'never' unless $_[0];
647 return age_string(time - $_[0]->epoch);
651 my($line) = split /\n/, $cmt;
652 $line =~ s/^(.{70,80}\b).*/$1 \x{2026}/;
655 abridged_description => sub {
656 join(' ', grep { defined } (split / /, shift)[0..10]);
661 sub end : ActionClass('RenderView') {
663 # Give repository views the current HEAD.
664 if ($c->stash->{Repository}) {
665 $c->stash->{HEAD} = $c->stash->{Repository}->head_hash;
669 sub error_404 : Action {
671 $c->response->status(404);
672 $c->response->body('Page not found');
675 __PACKAGE__->meta->make_immutable;
681 Gitalist::Controller::Root - Root controller for the application
685 This controller handles all of the root level paths for the application
691 Root of chained actions
695 Populate the header and footer. Perhaps not the best location.
699 Provides the repository listing.
703 Attempt to render a view, if needed.
707 =head2 commitdiff_plain
715 =head2 repository_index
719 See L<Gitalist> for authors.
723 See L<Gitalist> for the license.