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,
162 blob => join("\n", map $_->{line}, @$blame),
165 $c->forward('Model::ContentMangler')
166 unless $c->stash->{no_wrapper};
170 my ( $self, $c ) = @_;
171 my $repository = $c->stash->{Repository};
172 my $h = $c->req->param('h')
173 || $repository->hash_by_path($c->req->param('hb'), $c->req->param('f'))
174 || die "No file or sha1 provided.";
175 my $hb = $c->req->param('hb')
176 || $repository->head_hash
177 || die "Couldn't discern the corresponding head.";
179 my $filename = $c->req->param('f') || '';
181 my $blob = $repository->get_object($h);
182 $blob = $repository->get_object(
183 $repository->hash_by_path($h || $hb, $filename)
184 ) if $blob->type ne 'blob';
186 return $blob, $repository->get_object($hb), $filename;
191 The blob action i.e the contents of a file.
195 sub blob : Chained('base') Args(0) {
196 my ( $self, $c ) = @_;
198 my($blob, $head, $filename) = $self->_blob_objs($c);
200 blob => $blob->content,
202 filename => $filename,
206 $c->forward('Model::ContentMangler')
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('Model::ContentMangler')
268 unless $c->stash->{no_wrapper};
273 Exposes a given commit.
277 sub commit : Chained('base') Args(0) {
278 my ( $self, $c ) = @_;
279 my $repository = $c->stash->{Repository};
280 my $commit = $self->_get_object($c);
283 diff_tree => ($repository->diff(commit => $commit))[0],
284 refs => $repository->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('Model::ContentMangler')
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 $repository = $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 => [$repository->list_revs(%logargs)],
352 refs => $repository->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 $repository = $c->stash->{Repository};
375 my $file = $repository->get_object(
376 $repository->hash_by_path(
377 $repository->head_hash,
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 $repository = $c->stash->{Repository};
395 my $commit = $self->_get_object($c, $c->req->param('hb'));
396 my $filename = $c->req->param('f') || '';
398 ? $repository->get_object($repository->hash_by_path($commit->sha1, $filename))
399 : $repository->get_object($commit->tree_sha1)
404 tree_list => [$repository->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 $repository = $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 => [$repository->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 repository.
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 $repository = $c->stash->{Repository};
487 sha1 => $repository->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 ($repository->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 repository.
513 sub rss : Chained('base') Args(0) {
516 my $repository = $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=>$repository->name}),
523 description => $repository->description,
524 pubDate => DateTime->now,
525 lastBuildDate => DateTime->now,
529 sha1 => $repository->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 ($repository->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()->repositories };
556 die 'No repositories 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 $repository = $c->req->param('p');
625 if (defined $repository) {
627 $c->stash(Repository => $c->model('GitRepos')->get_repository($repository));
630 $c->detach('/error_404');
634 my $a_repository = $c->stash->{Repository} || $c->model()->repositories->[0];
636 git_version => $a_repository->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 repository views the current HEAD.
659 if ($c->stash->{Repository}) {
660 $c->stash->{HEAD} = $c->stash->{Repository}->head_hash;
662 $c->stash(syntax_css => [$c->model('ContentMangler')->css]);
665 sub error_404 : Action {
667 $c->response->status(404);
668 $c->response->body('Page not found');
671 __PACKAGE__->meta->make_immutable;
677 Gitalist::Controller::Root - Root controller for the application
681 This controller handles all of the root level paths for the application
687 Root of chained actions
691 Populate the header and footer. Perhaps not the best location.
695 Provides the repository listing.
699 Attempt to render a view, if needed.
703 =head2 commitdiff_plain
711 =head2 repository_index
715 See L<Gitalist> for authors.
719 See L<Gitalist> for the license.