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,
202 # For legacy support.
203 sub history : Chained('base') Args(0) {
204 my ( $self, $c ) = @_;
206 my $repository = $c->stash->{Repository};
207 my $file = $repository->get_object(
208 $repository->hash_by_path(
209 $repository->head_hash,
210 $c->stash->{filename}
214 filetype => $file->type,
220 The tree of a given commit.
224 sub tree : Chained('base') Args(0) {
225 my ( $self, $c ) = @_;
226 my $repository = $c->stash->{Repository};
227 my $commit = $self->_get_object($c, $c->req->param('hb'));
228 my $filename = $c->req->param('f') || '';
230 ? $repository->get_object($repository->hash_by_path($commit->sha1, $filename))
231 : $repository->get_object($commit->tree_sha1)
236 tree_list => [$repository->list_tree($tree->sha1)],
237 path => $c->req->param('f') || '',
243 Expose the local reflog. This may go away.
247 sub reflog : Chained('base') Args(0) {
248 my ( $self, $c ) = @_;
249 my @log = $c->stash->{Repository}->reflog(
260 The action for the search form.
264 sub search : Chained('base') Args(0) {
266 my $repository = $c->stash->{Repository};
267 my $commit = $self->_get_object($c);
268 # Lifted from /shortlog.
270 sha1 => $commit->sha1,
271 count => Gitalist->config->{paging}{log},
272 ($c->req->param('f') ? (file => $c->req->param('f')) : ()),
274 type => $c->req->param('type'),
275 text => $c->req->param('text'),
276 regexp => $c->req->param('regexp') || 0,
282 results => [$repository->list_revs(%logargs)],
283 # This could be added - page => $page,
289 Provides some help for the search form.
293 sub search_help : Chained('base') Args(0) {
295 $c->stash(template => 'search_help.tt2');
300 Provides an atom feed for a given repository.
304 sub atom : Chained('base') Args(0) {
307 my $feed = XML::Atom::Feed->new;
309 my $host = lc Sys::Hostname::hostname();
310 $feed->title($host . ' - ' . Gitalist->config->{name});
311 $feed->updated(~~DateTime->now);
313 my $repository = $c->stash->{Repository};
315 sha1 => $repository->head_hash,
316 count => Gitalist->config->{paging}{log} || 25,
317 ($c->req->param('f') ? (file => $c->req->param('f')) : ())
320 my $mk_title = $c->stash->{short_cmt};
321 for my $commit ($repository->list_revs(%logargs)) {
322 my $entry = XML::Atom::Entry->new;
323 $entry->title( $mk_title->($commit->comment) );
324 $entry->id($c->uri_for('commit', {h=>$commit->sha1}));
326 $entry->content($commit->comment);
327 $feed->add_entry($entry);
330 $c->response->body($feed->as_xml);
331 $c->response->content_type('application/atom+xml');
332 $c->response->status(200);
337 Provides an RSS feed for a given repository.
341 sub rss : Chained('base') Args(0) {
344 my $repository = $c->stash->{Repository};
346 my $rss = XML::RSS->new(version => '2.0');
348 title => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name},
349 link => $c->uri_for('summary', {p=>$repository->name}),
351 description => $repository->description,
352 pubDate => DateTime->now,
353 lastBuildDate => DateTime->now,
357 sha1 => $repository->head_hash,
358 count => Gitalist->config->{paging}{log} || 25,
359 ($c->req->param('f') ? (file => $c->req->param('f')) : ())
361 my $mk_title = $c->stash->{short_cmt};
362 for my $commit ($repository->list_revs(%logargs)) {
363 # XXX Needs work ....
365 title => $mk_title->($commit->comment),
366 permaLink => $c->uri_for(commit => {h=>$commit->sha1}),
367 description => $commit->comment,
371 $c->response->body($rss->as_string);
372 $c->response->content_type('application/rss+xml');
373 $c->response->status(200);
376 sub opml : Chained('base') Args(0) {
379 my $opml = XML::OPML::SimpleGen->new();
381 $opml->head(title => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name});
383 my @list = @{ $c->model()->repositories };
384 die 'No repositories found in '. $c->model->repo_dir
387 for my $proj ( @list ) {
388 $opml->insert_outline(
389 text => $proj->name. ' - '. $proj->description,
390 xmlUrl => $c->uri_for(rss => {p => $proj->name}),
394 $c->response->body($opml->as_string);
395 $c->response->content_type('application/rss');
396 $c->response->status(200);
401 A raw patch for a given commit.
405 sub patch : Chained('base') Args(0) {
407 $c->detach('patches', [1]);
412 The patcheset for a given commit ???
416 sub patches : Chained('base') Args(0) {
417 my ($self, $c, $count) = @_;
418 $count ||= Gitalist->config->{patches}{max};
419 my $commit = $self->_get_object($c);
420 my $parent = $c->req->param('hp') || undef;
421 my $patch = $commit->get_patch( $parent, $count );
422 $c->response->body($patch);
423 $c->response->content_type('text/plain');
424 $c->response->status(200);
429 Provides a snapshot of a given commit.
433 sub snapshot : Chained('base') Args(0) {
435 my $format = $c->req->param('sf') || 'tgz';
437 my $sha1 = $c->req->param('h') || $self->_get_object($c)->sha1;
438 my @snap = $c->stash->{Repository}->snapshot(
442 $c->response->status(200);
443 $c->response->headers->header( 'Content-Disposition' =>
444 "attachment; filename=$snap[0]");
445 $c->response->body($snap[1]);
449 sub base : Chained('/root') PathPart('') CaptureArgs(0) {
452 my $git_version = `git --version`;
455 git_version => $git_version,
456 version => $Gitalist::VERSION,
458 # XXX Move these to a plugin!
460 return 'never' unless $_[0];
461 return age_string(time - $_[0]->epoch);
465 my($line) = split /\n/, $cmt;
466 $line =~ s/^(.{70,80}\b).*/$1 \x{2026}/;
469 abridged_description => sub {
470 join(' ', grep { defined } (split / /, shift)[0..10]);
475 sub end : ActionClass('RenderView') {
477 # Give repository views the current HEAD.
478 if ($c->stash->{Repository}) {
479 $c->stash->{HEAD} = $c->stash->{Repository}->head_hash;
483 sub error_404 : Action {
485 $c->response->status(404);
486 $c->response->body('Page not found');
489 __PACKAGE__->meta->make_immutable;
495 Gitalist::Controller::Root - Root controller for the application
499 This controller handles all of the root level paths for the application
505 Root of chained actions
509 Populate the header and footer. Perhaps not the best location.
513 Provides the repository listing.
517 Attempt to render a view, if needed.
527 =head2 repository_index
531 See L<Gitalist> for authors.
535 See L<Gitalist> for the license.