Gave ownership of URLs to Gitalist proper.
[catagits/Gitalist.git] / lib / Gitalist / Controller / Root.pm
1 package Gitalist::Controller::Root;
2 use Moose;
3 use namespace::autoclean;
4
5 BEGIN { extends 'Catalyst::Controller' }
6
7 #
8 # Sets the actions in this controller to be registered with no prefix
9 # so they function identically to actions created in MyApp.pm
10 #
11 __PACKAGE__->config->{namespace} = '';
12
13 =head1 NAME
14
15 Gitalist::Controller::Root - Root Controller for Gitalist
16
17 =head1 DESCRIPTION
18
19 [enter your description here]
20
21 =head1 METHODS
22
23 =cut
24
25 =head2 index
26
27 =cut
28
29 use IO::Capture::Stdout;
30
31 =head2 run_gitweb
32
33 The main shim around C<gitweb.pm>.
34
35 =cut
36
37 sub run_gitweb {
38   my ( $self, $c ) = @_;
39
40   # XXX A slippery slope to be sure.
41   if($c->req->param('a')) {
42     my $capture = IO::Capture::Stdout->new();
43     $capture->start();
44     eval {
45       my $action = gitweb::main($c);
46       $action->();
47     };
48     $capture->stop();
49     
50     use Data::Dumper;
51     die Dumper($@)
52       if $@;
53   
54     my $output = join '', $capture->read;
55     $c->stash->{gitweb_output} = $output;
56     $c->stash->{template} = 'gitweb.tt2';
57   }
58 }
59
60 sub _get_commit {
61   my($self, $c) = @_;
62
63   # Either use the provided h(ash) parameter, the f(ile) parameter or just use HEAD.
64   my $hash = $c->req->param('h')
65           || ($c->req->param('f') ? $c->model('Git')->hash_by_path($c->req->param('f')) : '')
66           || $c->model('Git')->head_hash
67           # XXX This could definitely use more context.
68           || Carp::croak("Couldn't find a hash for the commit object!");
69
70   my $commit = $c->model('Git')->get_object($hash)
71     or Carp::croak("Couldn't find a commit object for '$hash'!");
72
73   return $commit;
74 }
75
76 =head2 index
77
78 Provides the project listing.
79
80 =cut
81
82 sub index :Path :Args(0) {
83   my ( $self, $c ) = @_;
84
85   # Leave actions up to gitweb at this point.
86   return $self->run_gitweb($c)
87     if $c->req->param('a');
88
89   my $list = $c->model('Git')->list_projects;
90   unless(@$list) {
91     die "No projects found";
92   }
93
94   $c->stash(
95     searchtext => $c->req->param('searchtext') || '',
96     projects   => $list,
97     action     => 'index',
98   );
99 }
100
101 =head2 summary
102
103 A summary of what's happening in the repo.
104
105 =cut
106
107 sub summary : Local {
108   my ( $self, $c ) = @_;
109
110   my $commit = $self->_get_commit($c);
111   $c->stash(
112     commit    => $commit,
113     info      => $c->model('Git')->project_info($c->model('Git')->project),
114     log_lines => [$c->model('Git')->list_revs(rev => $commit->sha1, count => 17)],
115     heads     => [$c->model('Git')->heads],
116     action    => 'summary',
117   );
118 }
119
120 =head2 heads
121
122 The current list of heads (aka branches) in the repo.
123
124 =cut
125
126 sub heads : Local {
127   my ( $self, $c ) = @_;
128
129   $c->stash(
130     commit => $self->_get_commit($c),
131     heads  => [$c->model('Git')->heads],
132     action => 'heads',
133   );
134 }
135
136 =head2 blob
137
138 The blob action i.e the contents of a file.
139
140 =cut
141
142 sub blob : Local {
143   my ( $self, $c ) = @_;
144
145   my $h  = $c->req->param('h')
146        || $c->model('Git')->hash_by_path($c->req->param('f'))
147        || die "No file or sha1 provided.";
148   my $hb = $c->req->param('hb')
149        || $c->model('Git')->head_hash
150        || die "Couldn't discern the corresponding head.";
151
152   $c->stash(
153     blob     => $c->model('Git')->get_object($h)->content,
154     head     => $c->model('Git')->get_object($hb),
155     filename => $c->req->param('f') || '',
156     action   => 'blob',
157   );
158
159   $c->forward('View::SyntaxHighlight');
160 }
161
162 =head2 commit
163
164 Exposes a given commit.
165
166 =cut
167
168 sub commit : Local {
169   my ( $self, $c ) = @_;
170
171   my $commit = $self->_get_commit($c);
172   $c->stash(
173       commit      => $commit,
174       diff_tree   => [$c->model('Git')->diff_tree($commit)],
175       branches_on => [$c->model('Git')->refs_for($commit->sha1)],
176       action      => 'commit',
177   );
178 }
179
180 =head2 commitdiff
181
182 Exposes a given diff of a commit.
183
184 =cut
185
186 sub commitdiff : Local {
187   my ( $self, $c ) = @_;
188
189   my $commit = $self->_get_commit($c);
190   $c->stash(
191       commit      => $commit,
192       diff_tree   => [$c->model('Git')->diff_tree($commit)],
193       diff        => [$c->model('Git')->diff($commit->parent_sha1, $commit->sha1)],
194       action      => 'commitdiff',
195   );
196 }
197
198 =head2 shortlog
199
200 Expose an abbreviated log of a given sha1.
201
202 =cut
203
204 sub shortlog : Local {
205   my ( $self, $c ) = @_;
206
207   my $commit = $self->_get_commit($c);
208   # XXX Needs paging.
209   $c->stash(
210       commit    => $commit,
211       log_lines => [$c->model('Git')->list_revs(rev => $commit->sha1)],
212       action    => 'shortlog',
213   );
214 }
215
216 =head2 log
217
218 Calls shortlog internally. Perhaps that should be reversed ...
219
220 =cut
221 sub log : Local {
222     $_[0]->shortlog($_[1]);
223     $_[1]->stash->{action} = 'log';
224 }
225
226 =head2 tree
227
228 The tree of a given commit.
229
230 =cut
231
232 sub tree : Local {
233   my ( $self, $c ) = @_;
234
235   my $commit = $self->_get_commit($c);
236   $c->stash(
237       # XXX Useful defaults needed ...
238       commit    => $commit,
239       tree      => $c->model('Git')->get_object($c->req->param('hb')),
240       tree_list => [$c->model('Git')->list_tree($commit->sha1)],
241       action    => 'tree',
242   );
243 }
244
245 =head2 reflog
246
247 Expose the local reflog. This may go away.
248
249 =cut
250
251 sub reflog : Local {
252   my ( $self, $c ) = @_;
253
254   my @log = $c->model('Git')->reflog(
255       '--since=yesterday'
256   );
257
258   $c->stash(
259       log    => \@log,
260       action => 'reflog',
261   );
262 }
263
264 =head2 auto
265
266 Populate the header and footer. Perhaps not the best location.
267
268 =cut
269
270 sub auto : Private {
271     my($self, $c) = @_;
272
273     # XXX Temp hack until a decent solution is invented.
274     $c->model('Git')->project($c->req->param('p'))
275       if $c->req->param('p');
276
277     # Yes, this is hideous.
278     $self->header($c);
279     $self->footer($c);
280 }
281
282 # XXX This could probably be dropped altogether.
283 use Gitalist::Util qw(to_utf8);
284 # Formally git_header_html
285 sub header {
286   my($self, $c) = @_;
287
288   my $title = $c->config->{sitename};
289
290   my $project   = $c->req->param('project')  || $c->req->param('p');
291   my $action    = $c->req->param('action')   || $c->req->param('a');
292   my $file_name = $c->req->param('filename') || $c->req->param('f');
293   if(defined $project) {
294     $title .= " - " . to_utf8($project);
295     if (defined $action) {
296       $title .= "/$action";
297       if (defined $file_name) {
298         $title .= " - " . $file_name;
299         if ($action eq "tree" && $file_name !~ m|/$|) {
300           $title .= "/";
301         }
302       }
303     }
304   }
305
306   $c->stash->{version}     = $c->config->{version};
307   $c->stash->{git_version} = $c->model('Git')->run_cmd('--version');
308   $c->stash->{title}       = $title;
309
310   #$c->stash->{baseurl} = $ENV{PATH_INFO} && uri_escape($base_url);
311   $c->stash->{stylesheet} = $c->config->{stylesheet} || 'gitweb.css';
312
313   $c->stash->{project} = $project;
314   my @links;
315   if($project) {
316     my %href_params = $self->feed_info($c);
317     $href_params{'-title'} ||= 'log';
318
319     foreach my $format qw(RSS Atom) {
320       my $type = lc($format);
321       push @links, {
322         rel   => 'alternate',
323         title => "$project - $href_params{'-title'} - $format feed",
324
325         # XXX A bit hacky and could do with using gitweb::href() features
326         href  => "?a=$type;p=$project",
327         type  => "application/$type+xml"
328         }, {
329         rel   => 'alternate',
330
331         # XXX This duplication also feels a bit awkward
332         title => "$project - $href_params{'-title'} - $format feed (no merges)",
333         href  => "?a=$type;p=$project;opt=--no-merges",
334         type  => "application/$type+xml"
335         };
336     }
337   } else {
338     push @links, {
339       rel => "alternate",
340       title => $c->config->{sitename}." projects list",
341       href => '?a=project_index',
342       type => "text/plain; charset=utf-8"
343       }, {
344       rel => "alternate",
345       title => $c->config->{sitename}." projects feeds",
346       href => '?a=opml',
347       type => "text/plain; charset=utf-8"
348       };
349   }
350
351   $c->stash->{favicon} = $c->config->{favicon};
352
353   # </head><body>
354
355   $c->stash(
356     logo_url      => $c->config->{logo_url},
357     logo_label    => $c->config->{logo_label},
358     logo_img      => $c->config->{logo},
359     home_link     => $c->config->{home_link},
360     home_link_str => $c->config->{home_link_str},
361     );
362
363   if(defined $project) {
364     $c->stash(
365       search_text => ( $c->req->param('s') || $c->req->param('searchtext') ),
366       search_hash => ( $c->req->param('hb') || $c->req->param('hashbase')
367           || $c->req->param('h')  || $c->req->param('hash')
368           || 'HEAD' ),
369       );
370   }
371 }
372
373 # Formally git_footer_html
374 sub footer {
375   my($self, $c) = @_;
376
377   my $feed_class = 'rss_logo';
378
379   my @feeds;
380   my $project = $c->req->param('project')  || $c->req->param('p');
381   if(defined $project) {
382     (my $pstr = $project) =~ s[/?\.git$][];
383     my $descr = $c->model('Git')->project_info($project)->{description};
384     $c->stash->{project_description} = defined $descr
385       ? $descr
386       : '';
387
388     my %href_params = $self->feed_info($c);
389     if (!%href_params) {
390       $feed_class .= ' generic';
391     }
392     $href_params{'-title'} ||= 'log';
393
394     @feeds = [
395       map +{
396         class => $feed_class,
397         title => "$href_params{'-title'} $_ feed",
398         href  => "/?p=$project;a=\L$_",
399         name  => lc $_,
400         }, qw(RSS Atom)
401       ];
402   } else {
403     @feeds = [
404       map {
405         class => $feed_class,
406           title => '',
407           href  => "/?a=$_->[0]",
408           name  => $_->[1],
409         }, [opml=>'OPML'],[project_index=>'TXT'],
410       ];
411   }
412 }
413
414 # XXX This feels wrong here, should probably be refactored.
415 # returns hash to be passed to href to generate gitweb URL
416 # in -title key it returns description of link
417 sub feed_info {
418   my($self, $c) = @_;
419
420   my $format = shift || 'Atom';
421   my %res = (action => lc($format));
422
423   # feed links are possible only for project views
424   return unless $c->req->param('project');
425
426   # some views should link to OPML, or to generic project feed,
427   # or don't have specific feed yet (so they should use generic)
428   return if $c->req->param('action') =~ /^(?:tags|heads|forks|tag|search)$/x;
429
430   my $branch;
431   my $hash = $c->req->param('h')  || $c->req->param('hash');
432   my $hash_base = $c->req->param('hb') || $c->req->param('hashbase');
433
434   # branches refs uses 'refs/heads/' prefix (fullname) to differentiate
435   # from tag links; this also makes possible to detect branch links
436   if ((defined $hash_base && $hash_base =~ m!^refs/heads/(.*)$!) ||
437     (defined $hash      && $hash      =~ m!^refs/heads/(.*)$!)) {
438     $branch = $1;
439   }
440
441   # find log type for feed description (title)
442   my $type = 'log';
443   my $file_name = $c->req->param('f') || $c->req->param('filename');
444   if (defined $file_name) {
445     $type  = "history of $file_name";
446     $type .= "/" if $c->req->param('action') eq 'tree';
447     $type .= " on '$branch'" if (defined $branch);
448   } else {
449     $type = "log of $branch" if (defined $branch);
450   }
451
452   $res{-title} = $type;
453   $res{'hash'} = (defined $branch ? "refs/heads/$branch" : undef);
454   $res{'file_name'} = $file_name;
455
456   return %res;
457 }
458 =head2 end
459
460 Attempt to render a view, if needed.
461
462 =cut
463
464 sub end : ActionClass('RenderView') {}
465
466 =head1 AUTHOR
467
468 Dan Brook,,,
469
470 =head1 LICENSE
471
472 This library is free software. You can redistribute it and/or modify
473 it under the same terms as Perl itself.
474
475 =cut
476
477 __PACKAGE__->meta->make_immutable;