1 package Gitalist::Controller::Root;
3 use namespace::autoclean;
5 BEGIN { extends 'Catalyst::Controller' }
7 __PACKAGE__->config->{namespace} = '';
14 use XML::OPML::SimpleGen;
18 Gitalist::Controller::Root - Root Controller for Gitalist
22 [enter your description here]
29 my($self, $c, $haveh) = @_;
31 my $h = $haveh || $c->req->param('h') || '';
32 my $f = $c->req->param('f');
34 my $m = $c->stash->{Project};
37 # Either use the provided h(ash) parameter, the f(ile) parameter or just use HEAD.
38 my $hash = ($h =~ /[^a-f0-9]/ ? $m->head_hash($h) : $h)
39 || ($f && $m->hash_by_path($f))
41 # XXX This could definitely use more context.
42 || Carp::croak("Couldn't find a hash for the commit object!");
44 my $obj = $m->get_object($hash)
45 or Carp::croak("Couldn't find a object for '$hash' in '$pd'!");
52 Provides the project listing.
56 sub index :Path :Args(0) {
57 my ( $self, $c ) = @_;
59 $c->detach($c->req->param('a'))
60 if $c->req->param('a');
62 my @list = @{ $c->model()->projects };
63 die 'No projects found in '. $c->model->repo_dir
66 my $search = $c->req->param('s') || '';
69 index($_->name, $search) > -1
70 or ( $_->description !~ /^Unnamed repository/ and index($_->description, $search) > -1 )
75 search_text => $search,
81 sub project_index : Local {
82 my ( $self, $c ) = @_;
84 my @list = @{ $c->model()->projects };
85 die 'No projects found in '. $c->model->repo_dir
88 $c->response->content_type('text/plain');
90 join "\n", map $_->name, @list
92 $c->response->status(200);
97 A summary of what's happening in the repo.
101 sub summary : Local {
102 my ( $self, $c ) = @_;
103 my $project = $c->stash->{Project};
104 $c->detach('error_404') unless $project;
105 my $commit = $self->_get_object($c);
106 my @heads = @{$project->heads};
107 my $maxitems = Gitalist->config->{paging}{summary} || 10;
110 log_lines => [$project->list_revs(
111 sha1 => $commit->sha1,
114 refs => $project->references,
115 heads => [ @heads[0 .. ($#heads < $maxitems ? $#heads : $maxitems)] ],
122 The current list of heads (aka branches) in the repo.
127 my ( $self, $c ) = @_;
128 my $project = $c->stash->{Project};
130 commit => $self->_get_object($c),
131 heads => $project->heads,
138 The current list of tags in the repo.
143 my ( $self, $c ) = @_;
144 my $project = $c->stash->{Project};
146 commit => $self->_get_object($c),
147 tags => $project->tags,
155 my $project = $c->stash->{Project};
156 my $h = $c->req->param('h')
157 || $project->hash_by_path($c->req->param('hb'), $c->req->param('f'))
158 || die "No file or sha1 provided.";
159 my $hb = $c->req->param('hb')
160 || $project->head_hash
161 || die "Couldn't discern the corresponding head.";
162 my $filename = $c->req->param('f') || '';
164 my $blame = $project->get_object($hb)->blame($filename);
167 head => $project->get_object($hb),
168 filename => $filename,
170 # XXX Hack hack hack, see View::SyntaxHighlight
171 language => ($filename =~ /\.p[lm]$/i ? 'Perl' : ''),
172 blob => join("\n", map $_->{line}, @$blame),
175 $c->forward('View::SyntaxHighlight')
176 unless $c->stash->{no_wrapper};
180 my ( $self, $c ) = @_;
181 my $project = $c->stash->{Project};
182 my $h = $c->req->param('h')
183 || $project->hash_by_path($c->req->param('hb'), $c->req->param('f'))
184 || die "No file or sha1 provided.";
185 my $hb = $c->req->param('hb')
186 || $project->head_hash
187 || die "Couldn't discern the corresponding head.";
189 my $filename = $c->req->param('f') || '';
191 my $blob = $project->get_object($h);
192 $blob = $project->get_object(
193 $project->hash_by_path($h || $hb, $filename)
194 ) if $blob->type ne 'blob';
196 return $blob, $project->get_object($hb), $filename;
201 The blob action i.e the contents of a file.
206 my ( $self, $c ) = @_;
208 my($blob, $head, $filename) = $self->_blob_objs($c);
210 blob => $blob->content,
212 filename => $filename,
213 # XXX Hack hack hack, see View::SyntaxHighlight
214 language => ($filename =~ /\.p[lm]$/i ? 'Perl' : ''),
218 $c->forward('View::SyntaxHighlight')
219 unless $c->stash->{no_wrapper};
224 The plain text version of blob, where file is rendered as is.
228 sub blob_plain : Local {
231 my($blob) = $self->_blob_objs($c);
232 $c->response->content_type('text/plain; charset=utf-8');
233 $c->response->body($blob->content);
234 $c->response->status(200);
237 =head2 blobdiff_plain
239 The plain text version of blobdiff.
243 sub blobdiff_plain : Local {
246 $c->stash(no_wrapper => 1);
247 $c->response->content_type('text/plain; charset=utf-8');
249 $c->forward('blobdiff');
254 Exposes a given diff of a blob.
258 sub blobdiff : Local {
259 my ( $self, $c ) = @_;
260 my $commit = $self->_get_object($c, $c->req->param('hb'));
261 my $filename = $c->req->param('f')
262 || croak("No file specified!");
263 my($tree, $patch) = $c->stash->{Project}->diff(
266 parent => $c->req->param('hpb') || undef,
272 filename => $filename,
273 # XXX Hack hack hack, see View::SyntaxHighlight
274 blobs => [$patch->[0]->{diff}],
276 action => 'blobdiff',
279 $c->forward('View::SyntaxHighlight')
280 unless $c->stash->{no_wrapper};
285 Exposes a given commit.
290 my ( $self, $c ) = @_;
291 my $project = $c->stash->{Project};
292 my $commit = $self->_get_object($c);
295 diff_tree => ($project->diff(commit => $commit))[0],
296 refs => $project->references,
303 Exposes a given diff of a commit.
307 sub commitdiff : Local {
308 my ( $self, $c ) = @_;
309 my $commit = $self->_get_object($c);
310 my($tree, $patch) = $c->stash->{Project}->diff(
312 parent => $c->req->param('hp') || undef,
319 # XXX Hack hack hack, see View::SyntaxHighlight
320 blobs => [map $_->{diff}, @$patch],
322 action => 'commitdiff',
325 $c->forward('View::SyntaxHighlight')
326 unless $c->stash->{no_wrapper};
329 sub commitdiff_plain : Local {
332 $c->stash(no_wrapper => 1);
333 $c->response->content_type('text/plain; charset=utf-8');
335 $c->forward('commitdiff');
340 Expose an abbreviated log of a given sha1.
344 sub shortlog : Local {
345 my ( $self, $c ) = @_;
347 my $project = $c->stash->{Project};
348 my $commit = $self->_get_object($c, $c->req->param('hb'));
349 my $filename = $c->req->param('f') || '';
352 sha1 => $commit->sha1,
353 count => Gitalist->config->{paging}{log} || 25,
354 ($filename ? (file => $filename) : ())
357 my $page = $c->req->param('pg') || 0;
358 $logargs{skip} = $c->req->param('pg') * $logargs{count}
359 if $c->req->param('pg');
363 log_lines => [$project->list_revs(%logargs)],
364 refs => $project->references,
366 filename => $filename,
367 action => 'shortlog',
373 Calls shortlog internally. Perhaps that should be reversed ...
377 $_[0]->shortlog($_[1]);
378 $_[1]->stash->{action} = 'log';
381 # For legacy support.
382 sub history : Local {
383 my ( $self, $c ) = @_;
385 my $project = $c->stash->{Project};
386 my $file = $project->get_object(
387 $project->hash_by_path(
389 $c->stash->{filename}
392 $c->stash( action => 'history',
393 filetype => $file->type,
399 The tree of a given commit.
404 my ( $self, $c ) = @_;
405 my $project = $c->stash->{Project};
406 my $commit = $self->_get_object($c, $c->req->param('hb'));
407 my $filename = $c->req->param('f') || '';
409 ? $project->get_object($project->hash_by_path($commit->sha1, $filename))
410 : $project->get_object($commit->tree_sha1)
415 tree_list => [$project->list_tree($tree->sha1)],
416 path => $c->req->param('f') || '',
423 Expose the local reflog. This may go away.
428 my ( $self, $c ) = @_;
429 my @log = $c->stash->{Project}->reflog(
441 The action for the search form.
447 $c->stash(current_action => 'GitRepos');
448 my $project = $c->stash->{Project};
449 my $commit = $self->_get_object($c);
450 # Lifted from /shortlog.
452 sha1 => $commit->sha1,
453 count => Gitalist->config->{paging}{log},
454 ($c->req->param('f') ? (file => $c->req->param('f')) : ()),
456 type => $c->req->param('type'),
457 text => $c->req->param('text'),
458 regexp => $c->req->param('regexp') || 0,
464 results => [$project->list_revs(%logargs)],
466 # This could be added - page => $page,
472 Provides some help for the search form.
476 sub search_help : Local {
478 $c->stash(template => 'search_help.tt2');
483 Provides an atom feed for a given project.
490 my $feed = XML::Atom::Feed->new;
492 my $host = lc Sys::Hostname::hostname();
493 $feed->title($host . ' - ' . Gitalist->config->{name});
494 $feed->updated(~~DateTime->now);
496 my $project = $c->stash->{Project};
498 sha1 => $project->head_hash,
499 count => Gitalist->config->{paging}{log} || 25,
500 ($c->req->param('f') ? (file => $c->req->param('f')) : ())
503 my $mk_title = $c->stash->{short_cmt};
504 for my $commit ($project->list_revs(%logargs)) {
505 my $entry = XML::Atom::Entry->new;
506 $entry->title( $mk_title->($commit->comment) );
507 $entry->id($c->uri_for('commit', {h=>$commit->sha1}));
509 $entry->content($commit->comment);
510 $feed->add_entry($entry);
513 $c->response->body($feed->as_xml);
514 $c->response->content_type('application/atom+xml');
515 $c->response->status(200);
520 Provides an RSS feed for a given project.
527 my $project = $c->stash->{Project};
529 my $rss = XML::RSS->new(version => '2.0');
531 title => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name},
532 link => $c->uri_for('summary', {p=>$project->name}),
534 description => $project->description,
535 pubDate => DateTime->now,
536 lastBuildDate => DateTime->now,
540 sha1 => $project->head_hash,
541 count => Gitalist->config->{paging}{log} || 25,
542 ($c->req->param('f') ? (file => $c->req->param('f')) : ())
544 my $mk_title = $c->stash->{short_cmt};
545 for my $commit ($project->list_revs(%logargs)) {
546 # XXX Needs work ....
548 title => $mk_title->($commit->comment),
549 permaLink => $c->uri_for(commit => {h=>$commit->sha1}),
550 description => $commit->comment,
554 $c->response->body($rss->as_string);
555 $c->response->content_type('application/rss+xml');
556 $c->response->status(200);
562 my $opml = XML::OPML::SimpleGen->new();
564 $opml->head(title => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name});
566 my @list = @{ $c->model()->projects };
567 die 'No projects found in '. $c->model->repo_dir
570 for my $proj ( @list ) {
571 $opml->insert_outline(
572 text => $proj->name. ' - '. $proj->description,
573 xmlUrl => $c->uri_for(rss => {p => $proj->name}),
577 $c->response->body($opml->as_string);
578 $c->response->content_type('application/rss');
579 $c->response->status(200);
584 A raw patch for a given commit.
590 $c->detach('patches', [1]);
595 The patcheset for a given commit ???
599 sub patches : Local {
600 my ($self, $c, $count) = @_;
601 $count ||= Gitalist->config->{patches}{max};
602 my $commit = $self->_get_object($c);
603 my $parent = $c->req->param('hp') || undef;
604 my $patch = $commit->get_patch( $parent, $count );
605 $c->response->body($patch);
606 $c->response->content_type('text/plain');
607 $c->response->status(200);
612 Provides a snapshot of a given commit.
616 sub snapshot : Local {
618 my $format = $c->req->param('sf') || 'tgz';
620 my $sha1 = $c->req->param('h') || $self->_get_object($c)->sha1;
621 my @snap = $c->stash->{Project}->snapshot(
625 $c->response->status(200);
626 $c->response->headers->header( 'Content-Disposition' =>
627 "attachment; filename=$snap[0]");
628 $c->response->body($snap[1]);
633 Populate the header and footer. Perhaps not the best location.
640 my $project = $c->req->param('p');
641 if (defined $project) {
643 $c->stash(Project => $c->model('GitRepos')->project($project));
646 $c->detach('/error_404');
650 my $a_project = $c->stash->{Project} || $c->model()->projects->[0];
652 git_version => $a_project->run_cmd('--version'),
653 version => $Gitalist::VERSION,
655 # XXX Move these to a plugin!
657 return 'never' unless $_[0];
658 return age_string(time - $_[0]->epoch);
662 my($line) = split /\n/, $cmt;
663 $line =~ s/^(.{70,80}\b).*/$1 \x{2026}/;
666 abridged_description => sub {
667 join(' ', grep { defined } (split / /, shift)[0..10]);
674 Attempt to render a view, if needed.
678 sub end : ActionClass('RenderView') {
680 # Give project views the current HEAD.
681 if ($c->stash->{Project}) {
682 $c->stash->{HEAD} = $c->stash->{Project}->head_hash;
686 sub error_404 :Private {
688 $c->response->status(404);
689 $c->response->body('Page not found');
696 if ( $age > 60 * 60 * 24 * 365 * 2 ) {
697 $age_str = ( int $age / 60 / 60 / 24 / 365 );
698 $age_str .= " years ago";
700 elsif ( $age > 60 * 60 * 24 * ( 365 / 12 ) * 2 ) {
701 $age_str = int $age / 60 / 60 / 24 / ( 365 / 12 );
702 $age_str .= " months ago";
704 elsif ( $age > 60 * 60 * 24 * 7 * 2 ) {
705 $age_str = int $age / 60 / 60 / 24 / 7;
706 $age_str .= " weeks ago";
708 elsif ( $age > 60 * 60 * 24 * 2 ) {
709 $age_str = int $age / 60 / 60 / 24;
710 $age_str .= " days ago";
712 elsif ( $age > 60 * 60 * 2 ) {
713 $age_str = int $age / 60 / 60;
714 $age_str .= " hours ago";
716 elsif ( $age > 60 * 2 ) {
717 $age_str = int $age / 60;
718 $age_str .= " min ago";
722 $age_str .= " sec ago";
725 $age_str .= " right now";
730 __PACKAGE__->meta->make_immutable;
736 Gitalist::Controller::Root - Root controller for the application
740 This controller handles all of the root level paths for the application
748 =head2 commitdiff_plain
760 See L<Gitalist> for authors.
764 See L<Gitalist> for the license.