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