Chop out theoretically dead stuff.
[catagits/Gitalist.git] / lib / Gitalist / Controller / Root.pm
1 package Gitalist::Controller::Root;
2
3 use Moose;
4 use Moose::Autobox;
5 use Sys::Hostname ();
6 use XML::Atom::Feed;
7 use XML::Atom::Entry;
8 use XML::RSS;
9 use XML::OPML::SimpleGen;
10
11 use Gitalist::Utils qw/ age_string /;
12
13 use namespace::autoclean;
14
15 BEGIN { extends 'Catalyst::Controller' }
16
17 __PACKAGE__->config->{namespace} = '';
18
19 sub root : Chained('/') PathPart('') CaptureArgs(0) {}
20
21 sub _get_object {
22   my($self, $c, $haveh) = @_;
23
24   my $h = $haveh || $c->req->param('h') || '';
25   my $f = $c->req->param('f');
26
27   my $m = $c->stash->{Repository};
28   my $pd = $m->path;
29
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))
33           || $m->head_hash
34           # XXX This could definitely use more context.
35           || Carp::croak("Couldn't find a hash for the commit object!");
36
37   my $obj = $m->get_object($hash)
38     or Carp::croak("Couldn't find a object for '$hash' in '$pd'!");
39
40   return $obj;
41 }
42
43 sub index : Chained('base') PathPart('') Args(0) {
44   my ( $self, $c ) = @_;
45
46   $c->detach($c->req->param('a'))
47     if $c->req->param('a');
48
49   my $search = $c->req->param('s') || '';
50
51   $c->stash(
52     search_text => $search,
53   );
54 }
55
56 sub blame : Chained('base') Args(0) {
57   my($self, $c) = @_;
58
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') || '';
67
68   my $blame = $repository->get_object($hb)->blame($filename, $h);
69   $c->stash(
70     blame    => $blame,
71     head     => $repository->get_object($hb),
72     filename => $filename,
73
74     # XXX Hack hack hack, see View::SyntaxHighlight
75     language => ($filename =~ /\.p[lm]$/i ? 'Perl' : ''),
76     blob     => join("\n", map $_->{line}, @$blame),
77   );
78
79   $c->forward('View::SyntaxHighlight')
80     unless $c->stash->{no_wrapper};
81 }
82
83 sub _blob_objs {
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.";
92
93   my $filename = $c->req->param('f') || '';
94
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';
99
100   return $blob, $repository->get_object($hb), $filename;
101 }
102
103 =head2 blob
104
105 The blob action i.e the contents of a file.
106
107 =cut
108
109 sub blob : Chained('base') Args(0) {
110   my ( $self, $c ) = @_;
111
112   my($blob, $head, $filename) = $self->_blob_objs($c);
113   $c->stash(
114     blob     => $blob->content,
115     head     => $head,
116     filename => $filename,
117     # XXX Hack hack hack, see View::SyntaxHighlight
118     language => ($filename =~ /\.p[lm]$/i ? 'Perl' : ''),
119   );
120
121   $c->forward('View::SyntaxHighlight')
122     unless $c->stash->{no_wrapper};
123 }
124
125 =head2 blob_plain
126
127 The plain text version of blob, where file is rendered as is.
128
129 =cut
130
131 sub blob_plain : Chained('base') Args(0) {
132   my($self, $c) = @_;
133
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);
138 }
139
140 =head2 blobdiff_plain
141
142 The plain text version of blobdiff.
143
144 =cut
145
146 sub blobdiff_plain : Chained('base') Args(0) {
147   my($self, $c) = @_;
148
149   $c->stash(no_wrapper => 1);
150   $c->response->content_type('text/plain; charset=utf-8');
151
152   $c->forward('blobdiff');
153 }
154
155 =head2 blobdiff
156
157 Exposes a given diff of a blob.
158
159 =cut
160
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(
167     commit => $commit,
168     patch  => 1,
169     parent => $c->req->param('hpb') || undef,
170     file   => $filename,
171   );
172   $c->stash(
173     commit    => $commit,
174     diff      => $patch,
175     filename  => $filename,
176     # XXX Hack hack hack, see View::SyntaxHighlight
177     blobs     => [$patch->[0]->{diff}],
178     language  => 'Diff',
179   );
180
181   $c->forward('View::SyntaxHighlight')
182     unless $c->stash->{no_wrapper};
183 }
184
185 # For legacy support.
186 sub history : Chained('base') Args(0) {
187     my ( $self, $c ) = @_;
188     $self->shortlog($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}
194         )
195     );
196      $c->stash(
197                filetype => $file->type,
198            );
199 }
200
201 =head2 reflog
202
203 Expose the local reflog. This may go away.
204
205 =cut
206
207 sub reflog : Chained('base') Args(0) {
208   my ( $self, $c ) = @_;
209   my @log = $c->stash->{Repository}->reflog(
210       '--since=yesterday'
211   );
212
213   $c->stash(
214       log    => \@log,
215   );
216 }
217
218 =head2 search
219
220 The action for the search form.
221
222 =cut
223
224 sub search : Chained('base') Args(0) {
225   my($self, $c) = @_;
226   my $repository = $c->stash->{Repository};
227   my $commit  = $self->_get_object($c);
228   # Lifted from /shortlog.
229   my %logargs = (
230     sha1   => $commit->sha1,
231     count  => Gitalist->config->{paging}{log},
232     ($c->req->param('f') ? (file => $c->req->param('f')) : ()),
233     search => {
234       type   => $c->req->param('type'),
235       text   => $c->req->param('text'),
236       regexp => $c->req->param('regexp') || 0,
237     },
238   );
239
240   $c->stash(
241       commit  => $commit,
242       results => [$repository->list_revs(%logargs)],
243           # This could be added - page      => $page,
244   );
245 }
246
247 =head2 search_help
248
249 Provides some help for the search form.
250
251 =cut
252
253 sub search_help : Chained('base') Args(0) {
254     my ($self, $c) = @_;
255     $c->stash(template => 'search_help.tt2');
256 }
257
258 =head2 atom
259
260 Provides an atom feed for a given repository.
261
262 =cut
263
264 sub atom : Chained('base') Args(0) {
265   my($self, $c) = @_;
266
267   my $feed = XML::Atom::Feed->new;
268
269   my $host = lc Sys::Hostname::hostname();
270   $feed->title($host . ' - ' . Gitalist->config->{name});
271   $feed->updated(~~DateTime->now);
272
273   my $repository = $c->stash->{Repository};
274   my %logargs = (
275       sha1   => $repository->head_hash,
276       count  => Gitalist->config->{paging}{log} || 25,
277       ($c->req->param('f') ? (file => $c->req->param('f')) : ())
278   );
279
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}));
285     # XXX Needs work ...
286     $entry->content($commit->comment);
287     $feed->add_entry($entry);
288   }
289
290   $c->response->body($feed->as_xml);
291   $c->response->content_type('application/atom+xml');
292   $c->response->status(200);
293 }
294
295 =head2 rss
296
297 Provides an RSS feed for a given repository.
298
299 =cut
300
301 sub rss : Chained('base') Args(0) {
302   my ($self, $c) = @_;
303
304   my $repository = $c->stash->{Repository};
305
306   my $rss = XML::RSS->new(version => '2.0');
307   $rss->channel(
308     title          => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name},
309     link           => $c->uri_for('summary', {p=>$repository->name}),
310     language       => 'en',
311     description    => $repository->description,
312     pubDate        => DateTime->now,
313     lastBuildDate  => DateTime->now,
314   );
315
316   my %logargs = (
317       sha1   => $repository->head_hash,
318       count  => Gitalist->config->{paging}{log} || 25,
319       ($c->req->param('f') ? (file => $c->req->param('f')) : ())
320   );
321   my $mk_title = $c->stash->{short_cmt};
322   for my $commit ($repository->list_revs(%logargs)) {
323     # XXX Needs work ....
324     $rss->add_item(
325         title       => $mk_title->($commit->comment),
326         permaLink   => $c->uri_for(commit => {h=>$commit->sha1}),
327         description => $commit->comment,
328     );
329   }
330
331   $c->response->body($rss->as_string);
332   $c->response->content_type('application/rss+xml');
333   $c->response->status(200);
334 }
335
336 sub opml : Chained('base') Args(0) {
337   my($self, $c) = @_;
338
339   my $opml = XML::OPML::SimpleGen->new();
340
341   $opml->head(title => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name});
342
343   my @list = @{ $c->model()->repositories };
344   die 'No repositories found in '. $c->model->repo_dir
345     unless @list;
346
347   for my $proj ( @list ) {
348     $opml->insert_outline(
349       text   => $proj->name. ' - '. $proj->description,
350       xmlUrl => $c->uri_for(rss => {p => $proj->name}),
351     );
352   }
353
354   $c->response->body($opml->as_string);
355   $c->response->content_type('application/rss');
356   $c->response->status(200);
357 }
358
359 =head2 patch
360
361 A raw patch for a given commit.
362
363 =cut
364
365 sub patch : Chained('base') Args(0) {
366     my ($self, $c) = @_;
367     $c->detach('patches', [1]);
368 }
369
370 =head2 patches
371
372 The patcheset for a given commit ???
373
374 =cut
375
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);
385 }
386
387 =head2 snapshot
388
389 Provides a snapshot of a given commit.
390
391 =cut
392
393 sub snapshot : Chained('base') Args(0) {
394     my ($self, $c) = @_;
395     my $format = $c->req->param('sf') || 'tgz';
396     die unless $format;
397     my $sha1 = $c->req->param('h') || $self->_get_object($c)->sha1;
398     my @snap = $c->stash->{Repository}->snapshot(
399         sha1 => $sha1,
400         format => $format
401     );
402     $c->response->status(200);
403     $c->response->headers->header( 'Content-Disposition' =>
404                                        "attachment; filename=$snap[0]");
405     $c->response->body($snap[1]);
406 }
407
408
409 sub base : Chained('/root') PathPart('') CaptureArgs(0) {
410   my($self, $c) = @_;
411
412   my $git_version = `git --version`;
413   chomp($git_version);
414   $c->stash(
415     git_version => $git_version,
416     version     => $Gitalist::VERSION,
417
418     # XXX Move these to a plugin!
419     time_since => sub {
420       return 'never' unless $_[0];
421       return age_string(time - $_[0]->epoch);
422     },
423     short_cmt => sub {
424       my $cmt = shift;
425       my($line) = split /\n/, $cmt;
426       $line =~ s/^(.{70,80}\b).*/$1 \x{2026}/;
427       return $line;
428     },
429     abridged_description => sub {
430         join(' ', grep { defined } (split / /, shift)[0..10]);
431     },
432   );
433 }
434
435 sub end : ActionClass('RenderView') {
436     my ($self, $c) = @_;
437     # Give repository views the current HEAD.
438     if ($c->stash->{Repository}) {
439         $c->stash->{HEAD} = $c->stash->{Repository}->head_hash;
440     }
441 }
442
443 sub error_404 : Action {
444     my ($self, $c) = @_;
445     $c->response->status(404);
446     $c->response->body('Page not found');
447 }
448
449 __PACKAGE__->meta->make_immutable;
450
451 __END__
452
453 =head1 NAME
454
455 Gitalist::Controller::Root - Root controller for the application
456
457 =head1 DESCRIPTION
458
459 This controller handles all of the root level paths for the application
460
461 =head1 METHODS
462
463 =head2 root
464
465 Root of chained actions
466
467 =head2 base
468
469 Populate the header and footer. Perhaps not the best location.
470
471 =head2 index
472
473 Provides the repository listing.
474
475 =head2 end
476
477 Attempt to render a view, if needed.
478
479 =head2 blame
480
481 =head2 error_404
482
483 =head2 history
484
485 =head2 opml
486
487 =head2 repository_index
488
489 =head1 AUTHORS
490
491 See L<Gitalist> for authors.
492
493 =head1 LICENSE
494
495 See L<Gitalist> for the license.
496
497 =cut