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