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