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,
56 sub blame : Chained('base') Args(0) {
59 my $repository = $c->stash->{Repository};
60 my $h = $c->req->param('h')
61 || $repository->hash_by_path($c->req->param('hb'), $c->req->param('f'))
62 || die "No file or sha1 provided.";
63 my $hb = $c->req->param('hb')
64 || $repository->head_hash
65 || die "Couldn't discern the corresponding head.";
66 my $filename = $c->req->param('f') || '';
68 my $blame = $repository->get_object($hb)->blame($filename, $h);
71 head => $repository->get_object($hb),
72 filename => $filename,
74 # XXX Hack hack hack, see View::SyntaxHighlight
75 language => ($filename =~ /\.p[lm]$/i ? 'Perl' : ''),
76 blob => join("\n", map $_->{line}, @$blame),
79 $c->forward('View::SyntaxHighlight')
80 unless $c->stash->{no_wrapper};
84 my ( $self, $c ) = @_;
85 my $repository = $c->stash->{Repository};
86 my $h = $c->req->param('h')
87 || $repository->hash_by_path($c->req->param('hb'), $c->req->param('f'))
88 || die "No file or sha1 provided.";
89 my $hb = $c->req->param('hb')
90 || $repository->head_hash
91 || die "Couldn't discern the corresponding head.";
93 my $filename = $c->req->param('f') || '';
95 my $blob = $repository->get_object($h);
96 $blob = $repository->get_object(
97 $repository->hash_by_path($h || $hb, $filename)
98 ) if $blob->type ne 'blob';
100 return $blob, $repository->get_object($hb), $filename;
105 The blob action i.e the contents of a file.
109 sub blob : Chained('base') Args(0) {
110 my ( $self, $c ) = @_;
112 my($blob, $head, $filename) = $self->_blob_objs($c);
114 blob => $blob->content,
116 filename => $filename,
117 # XXX Hack hack hack, see View::SyntaxHighlight
118 language => ($filename =~ /\.p[lm]$/i ? 'Perl' : ''),
121 $c->forward('View::SyntaxHighlight')
122 unless $c->stash->{no_wrapper};
127 The plain text version of blob, where file is rendered as is.
131 sub blob_plain : Chained('base') Args(0) {
134 my($blob) = $self->_blob_objs($c);
135 $c->response->content_type('text/plain; charset=utf-8');
136 $c->response->body($blob->content);
137 $c->response->status(200);
140 =head2 blobdiff_plain
142 The plain text version of blobdiff.
146 sub blobdiff_plain : Chained('base') Args(0) {
149 $c->stash(no_wrapper => 1);
150 $c->response->content_type('text/plain; charset=utf-8');
152 $c->forward('blobdiff');
157 Exposes a given diff of a blob.
161 sub blobdiff : Chained('base') Args(0) {
162 my ( $self, $c ) = @_;
163 my $commit = $self->_get_object($c, $c->req->param('hb'));
164 my $filename = $c->req->param('f')
165 || croak("No file specified!");
166 my($tree, $patch) = $c->stash->{Repository}->diff(
169 parent => $c->req->param('hpb') || undef,
175 filename => $filename,
176 # XXX Hack hack hack, see View::SyntaxHighlight
177 blobs => [$patch->[0]->{diff}],
181 $c->forward('View::SyntaxHighlight')
182 unless $c->stash->{no_wrapper};
187 Exposes a given commit.
191 sub commit : Chained('base') Args(0) {
192 my ( $self, $c ) = @_;
193 my $repository = $c->stash->{Repository};
194 my $commit = $self->_get_object($c);
197 diff_tree => ($repository->diff(commit => $commit))[0],
198 refs => $repository->references,
204 Exposes a given diff of a commit.
208 sub commitdiff : Chained('base') Args(0) {
209 my ( $self, $c ) = @_;
210 my $commit = $self->_get_object($c);
211 my($tree, $patch) = $c->stash->{Repository}->diff(
213 parent => $c->req->param('hp') || undef,
220 # XXX Hack hack hack, see View::SyntaxHighlight
221 blobs => [map $_->{diff}, @$patch],
225 $c->forward('View::SyntaxHighlight')
226 unless $c->stash->{no_wrapper};
229 sub commitdiff_plain : Chained('base') Args(0) {
232 $c->stash(no_wrapper => 1);
233 $c->response->content_type('text/plain; charset=utf-8');
235 $c->forward('commitdiff');
238 # For legacy support.
239 sub history : Chained('base') Args(0) {
240 my ( $self, $c ) = @_;
242 my $repository = $c->stash->{Repository};
243 my $file = $repository->get_object(
244 $repository->hash_by_path(
245 $repository->head_hash,
246 $c->stash->{filename}
250 filetype => $file->type,
256 The tree of a given commit.
260 sub tree : Chained('base') Args(0) {
261 my ( $self, $c ) = @_;
262 my $repository = $c->stash->{Repository};
263 my $commit = $self->_get_object($c, $c->req->param('hb'));
264 my $filename = $c->req->param('f') || '';
266 ? $repository->get_object($repository->hash_by_path($commit->sha1, $filename))
267 : $repository->get_object($commit->tree_sha1)
272 tree_list => [$repository->list_tree($tree->sha1)],
273 path => $c->req->param('f') || '',
279 Expose the local reflog. This may go away.
283 sub reflog : Chained('base') Args(0) {
284 my ( $self, $c ) = @_;
285 my @log = $c->stash->{Repository}->reflog(
296 The action for the search form.
300 sub search : Chained('base') Args(0) {
302 my $repository = $c->stash->{Repository};
303 my $commit = $self->_get_object($c);
304 # Lifted from /shortlog.
306 sha1 => $commit->sha1,
307 count => Gitalist->config->{paging}{log},
308 ($c->req->param('f') ? (file => $c->req->param('f')) : ()),
310 type => $c->req->param('type'),
311 text => $c->req->param('text'),
312 regexp => $c->req->param('regexp') || 0,
318 results => [$repository->list_revs(%logargs)],
319 # This could be added - page => $page,
325 Provides some help for the search form.
329 sub search_help : Chained('base') Args(0) {
331 $c->stash(template => 'search_help.tt2');
336 Provides an atom feed for a given repository.
340 sub atom : Chained('base') Args(0) {
343 my $feed = XML::Atom::Feed->new;
345 my $host = lc Sys::Hostname::hostname();
346 $feed->title($host . ' - ' . Gitalist->config->{name});
347 $feed->updated(~~DateTime->now);
349 my $repository = $c->stash->{Repository};
351 sha1 => $repository->head_hash,
352 count => Gitalist->config->{paging}{log} || 25,
353 ($c->req->param('f') ? (file => $c->req->param('f')) : ())
356 my $mk_title = $c->stash->{short_cmt};
357 for my $commit ($repository->list_revs(%logargs)) {
358 my $entry = XML::Atom::Entry->new;
359 $entry->title( $mk_title->($commit->comment) );
360 $entry->id($c->uri_for('commit', {h=>$commit->sha1}));
362 $entry->content($commit->comment);
363 $feed->add_entry($entry);
366 $c->response->body($feed->as_xml);
367 $c->response->content_type('application/atom+xml');
368 $c->response->status(200);
373 Provides an RSS feed for a given repository.
377 sub rss : Chained('base') Args(0) {
380 my $repository = $c->stash->{Repository};
382 my $rss = XML::RSS->new(version => '2.0');
384 title => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name},
385 link => $c->uri_for('summary', {p=>$repository->name}),
387 description => $repository->description,
388 pubDate => DateTime->now,
389 lastBuildDate => DateTime->now,
393 sha1 => $repository->head_hash,
394 count => Gitalist->config->{paging}{log} || 25,
395 ($c->req->param('f') ? (file => $c->req->param('f')) : ())
397 my $mk_title = $c->stash->{short_cmt};
398 for my $commit ($repository->list_revs(%logargs)) {
399 # XXX Needs work ....
401 title => $mk_title->($commit->comment),
402 permaLink => $c->uri_for(commit => {h=>$commit->sha1}),
403 description => $commit->comment,
407 $c->response->body($rss->as_string);
408 $c->response->content_type('application/rss+xml');
409 $c->response->status(200);
412 sub opml : Chained('base') Args(0) {
415 my $opml = XML::OPML::SimpleGen->new();
417 $opml->head(title => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name});
419 my @list = @{ $c->model()->repositories };
420 die 'No repositories found in '. $c->model->repo_dir
423 for my $proj ( @list ) {
424 $opml->insert_outline(
425 text => $proj->name. ' - '. $proj->description,
426 xmlUrl => $c->uri_for(rss => {p => $proj->name}),
430 $c->response->body($opml->as_string);
431 $c->response->content_type('application/rss');
432 $c->response->status(200);
437 A raw patch for a given commit.
441 sub patch : Chained('base') Args(0) {
443 $c->detach('patches', [1]);
448 The patcheset for a given commit ???
452 sub patches : Chained('base') Args(0) {
453 my ($self, $c, $count) = @_;
454 $count ||= Gitalist->config->{patches}{max};
455 my $commit = $self->_get_object($c);
456 my $parent = $c->req->param('hp') || undef;
457 my $patch = $commit->get_patch( $parent, $count );
458 $c->response->body($patch);
459 $c->response->content_type('text/plain');
460 $c->response->status(200);
465 Provides a snapshot of a given commit.
469 sub snapshot : Chained('base') Args(0) {
471 my $format = $c->req->param('sf') || 'tgz';
473 my $sha1 = $c->req->param('h') || $self->_get_object($c)->sha1;
474 my @snap = $c->stash->{Repository}->snapshot(
478 $c->response->status(200);
479 $c->response->headers->header( 'Content-Disposition' =>
480 "attachment; filename=$snap[0]");
481 $c->response->body($snap[1]);
485 sub base : Chained('/root') PathPart('') CaptureArgs(0) {
488 my $git_version = `git --version`;
491 git_version => $git_version,
492 version => $Gitalist::VERSION,
494 # XXX Move these to a plugin!
496 return 'never' unless $_[0];
497 return age_string(time - $_[0]->epoch);
501 my($line) = split /\n/, $cmt;
502 $line =~ s/^(.{70,80}\b).*/$1 \x{2026}/;
505 abridged_description => sub {
506 join(' ', grep { defined } (split / /, shift)[0..10]);
511 sub end : ActionClass('RenderView') {
513 # Give repository views the current HEAD.
514 if ($c->stash->{Repository}) {
515 $c->stash->{HEAD} = $c->stash->{Repository}->head_hash;
519 sub error_404 : Action {
521 $c->response->status(404);
522 $c->response->body('Page not found');
525 __PACKAGE__->meta->make_immutable;
531 Gitalist::Controller::Root - Root controller for the application
535 This controller handles all of the root level paths for the application
541 Root of chained actions
545 Populate the header and footer. Perhaps not the best location.
549 Provides the repository listing.
553 Attempt to render a view, if needed.
557 =head2 commitdiff_plain
565 =head2 repository_index
569 See L<Gitalist> for authors.
573 See L<Gitalist> for the license.