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};
185 # For legacy support.
186 sub history : Chained('base') Args(0) {
187 my ( $self, $c ) = @_;
189 my $repository = $c->stash->{Repository};
190 my $file = $repository->get_object(
191 $repository->hash_by_path(
192 $repository->head_hash,
193 $c->stash->{filename}
197 filetype => $file->type,
203 Expose the local reflog. This may go away.
207 sub reflog : Chained('base') Args(0) {
208 my ( $self, $c ) = @_;
209 my @log = $c->stash->{Repository}->reflog(
220 The action for the search form.
224 sub search : Chained('base') Args(0) {
226 my $repository = $c->stash->{Repository};
227 my $commit = $self->_get_object($c);
228 # Lifted from /shortlog.
230 sha1 => $commit->sha1,
231 count => Gitalist->config->{paging}{log},
232 ($c->req->param('f') ? (file => $c->req->param('f')) : ()),
234 type => $c->req->param('type'),
235 text => $c->req->param('text'),
236 regexp => $c->req->param('regexp') || 0,
242 results => [$repository->list_revs(%logargs)],
243 # This could be added - page => $page,
249 Provides some help for the search form.
253 sub search_help : Chained('base') Args(0) {
255 $c->stash(template => 'search_help.tt2');
260 Provides an atom feed for a given repository.
264 sub atom : Chained('base') Args(0) {
267 my $feed = XML::Atom::Feed->new;
269 my $host = lc Sys::Hostname::hostname();
270 $feed->title($host . ' - ' . Gitalist->config->{name});
271 $feed->updated(~~DateTime->now);
273 my $repository = $c->stash->{Repository};
275 sha1 => $repository->head_hash,
276 count => Gitalist->config->{paging}{log} || 25,
277 ($c->req->param('f') ? (file => $c->req->param('f')) : ())
280 my $mk_title = $c->stash->{short_cmt};
281 for my $commit ($repository->list_revs(%logargs)) {
282 my $entry = XML::Atom::Entry->new;
283 $entry->title( $mk_title->($commit->comment) );
284 $entry->id($c->uri_for('commit', {h=>$commit->sha1}));
286 $entry->content($commit->comment);
287 $feed->add_entry($entry);
290 $c->response->body($feed->as_xml);
291 $c->response->content_type('application/atom+xml');
292 $c->response->status(200);
297 Provides an RSS feed for a given repository.
301 sub rss : Chained('base') Args(0) {
304 my $repository = $c->stash->{Repository};
306 my $rss = XML::RSS->new(version => '2.0');
308 title => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name},
309 link => $c->uri_for('summary', {p=>$repository->name}),
311 description => $repository->description,
312 pubDate => DateTime->now,
313 lastBuildDate => DateTime->now,
317 sha1 => $repository->head_hash,
318 count => Gitalist->config->{paging}{log} || 25,
319 ($c->req->param('f') ? (file => $c->req->param('f')) : ())
321 my $mk_title = $c->stash->{short_cmt};
322 for my $commit ($repository->list_revs(%logargs)) {
323 # XXX Needs work ....
325 title => $mk_title->($commit->comment),
326 permaLink => $c->uri_for(commit => {h=>$commit->sha1}),
327 description => $commit->comment,
331 $c->response->body($rss->as_string);
332 $c->response->content_type('application/rss+xml');
333 $c->response->status(200);
336 sub opml : Chained('base') Args(0) {
339 my $opml = XML::OPML::SimpleGen->new();
341 $opml->head(title => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name});
343 my @list = @{ $c->model()->repositories };
344 die 'No repositories found in '. $c->model->repo_dir
347 for my $proj ( @list ) {
348 $opml->insert_outline(
349 text => $proj->name. ' - '. $proj->description,
350 xmlUrl => $c->uri_for(rss => {p => $proj->name}),
354 $c->response->body($opml->as_string);
355 $c->response->content_type('application/rss');
356 $c->response->status(200);
361 A raw patch for a given commit.
365 sub patch : Chained('base') Args(0) {
367 $c->detach('patches', [1]);
372 The patcheset for a given commit ???
376 sub patches : Chained('base') Args(0) {
377 my ($self, $c, $count) = @_;
378 $count ||= Gitalist->config->{patches}{max};
379 my $commit = $self->_get_object($c);
380 my $parent = $c->req->param('hp') || undef;
381 my $patch = $commit->get_patch( $parent, $count );
382 $c->response->body($patch);
383 $c->response->content_type('text/plain');
384 $c->response->status(200);
389 Provides a snapshot of a given commit.
393 sub snapshot : Chained('base') Args(0) {
395 my $format = $c->req->param('sf') || 'tgz';
397 my $sha1 = $c->req->param('h') || $self->_get_object($c)->sha1;
398 my @snap = $c->stash->{Repository}->snapshot(
402 $c->response->status(200);
403 $c->response->headers->header( 'Content-Disposition' =>
404 "attachment; filename=$snap[0]");
405 $c->response->body($snap[1]);
409 sub base : Chained('/root') PathPart('') CaptureArgs(0) {
412 my $git_version = `git --version`;
415 git_version => $git_version,
416 version => $Gitalist::VERSION,
418 # XXX Move these to a plugin!
420 return 'never' unless $_[0];
421 return age_string(time - $_[0]->epoch);
425 my($line) = split /\n/, $cmt;
426 $line =~ s/^(.{70,80}\b).*/$1 \x{2026}/;
429 abridged_description => sub {
430 join(' ', grep { defined } (split / /, shift)[0..10]);
435 sub end : ActionClass('RenderView') {
437 # Give repository views the current HEAD.
438 if ($c->stash->{Repository}) {
439 $c->stash->{HEAD} = $c->stash->{Repository}->head_hash;
443 sub error_404 : Action {
445 $c->response->status(404);
446 $c->response->body('Page not found');
449 __PACKAGE__->meta->make_immutable;
455 Gitalist::Controller::Root - Root controller for the application
459 This controller handles all of the root level paths for the application
465 Root of chained actions
469 Populate the header and footer. Perhaps not the best location.
473 Provides the repository listing.
477 Attempt to render a view, if needed.
487 =head2 repository_index
491 See L<Gitalist> for authors.
495 See L<Gitalist> for the license.