]>
Commit | Line | Data |
---|---|---|
161332a5 KS |
1 | #!/usr/bin/perl |
2 | ||
22fafb99 KS |
3 | # gitweb.pl - simple web interface to track changes in git repositories |
4 | # | |
161332a5 KS |
5 | # (C) 2005, Kay Sievers <kay.sievers@vrfy.org> |
6 | # (C) 2005, Christian Gierke <ch@gierke.de> | |
823d5dc8 | 7 | # |
b87d78d6 | 8 | # This program is licensed under the GPL v2, or a later version |
161332a5 KS |
9 | |
10 | use strict; | |
11 | use warnings; | |
12 | use CGI qw(:standard :escapeHTML); | |
13 | use CGI::Carp qw(fatalsToBrowser); | |
b87d78d6 | 14 | use Fcntl ':mode'; |
161332a5 | 15 | |
3e029299 | 16 | my $cgi = new CGI; |
eb28240b | 17 | my $version = "121"; |
b87d78d6 KS |
18 | my $my_url = $cgi->url(); |
19 | my $my_uri = $cgi->url(-absolute => 1); | |
20 | my $rss_link = ""; | |
3e029299 | 21 | |
b87d78d6 | 22 | # absolute fs-path which will be prepended to the project path |
b51103f3 | 23 | my $projectroot = "/pub/scm"; |
b87d78d6 KS |
24 | |
25 | # location of the git-core binaries | |
b51103f3 | 26 | my $gitbin = "/usr/bin"; |
b87d78d6 KS |
27 | |
28 | # location for temporary files needed for diffs | |
b51103f3 | 29 | my $gittmp = "/tmp/gitweb"; |
034df39e | 30 | |
b87d78d6 | 31 | # target of the home link on top of all pages |
09bd7898 | 32 | my $home_link = "/git"; |
b87d78d6 | 33 | |
09bd7898 KS |
34 | # source of projects list |
35 | #my $projects_list = $projectroot; | |
36 | my $projects_list = "index/index.txt"; | |
b87d78d6 | 37 | |
09bd7898 KS |
38 | # input validation and dispatch |
39 | my $action = $cgi->param('a'); | |
40 | if (defined $action) { | |
41 | if ($action =~ m/[^0-9a-zA-Z\.\-]+/) { | |
42 | undef $action; | |
43 | die_error(undef, "Invalid action parameter."); | |
b87d78d6 | 44 | } |
09bd7898 KS |
45 | if ($action eq "git-logo.png") { |
46 | git_logo(); | |
47 | exit; | |
48 | } | |
49 | } else { | |
50 | $action = "log"; | |
b87d78d6 | 51 | } |
44ad2978 | 52 | |
022be3d0 | 53 | my $project = $cgi->param('p'); |
b87d78d6 KS |
54 | if (defined $project) { |
55 | if ($project =~ m/(^|\/)(|\.|\.\.)($|\/)/) { | |
56 | undef $project; | |
09bd7898 | 57 | die_error(undef, "Non-canonical project parameter."); |
b87d78d6 KS |
58 | } |
59 | if ($project =~ m/[^a-zA-Z0-9_\.\/\-\+\#\~]/) { | |
60 | undef $project; | |
09bd7898 | 61 | die_error(undef, "Invalid character in project parameter."); |
9cd3d988 KS |
62 | } |
63 | if (!(-d "$projectroot/$project")) { | |
b87d78d6 | 64 | undef $project; |
09bd7898 | 65 | die_error(undef, "No such directory."); |
b87d78d6 KS |
66 | } |
67 | if (!(-e "$projectroot/$project/HEAD")) { | |
68 | undef $project; | |
09bd7898 | 69 | die_error(undef, "No such project."); |
9cd3d988 | 70 | } |
6191f8e1 | 71 | $rss_link = "<link rel=\"alternate\" title=\"$project log\" href=\"$my_uri?p=$project;a=rss\" type=\"application/rss+xml\"/>"; |
034df39e | 72 | $ENV{'SHA1_FILE_DIRECTORY'} = "$projectroot/$project/objects"; |
09bd7898 KS |
73 | } else { |
74 | git_project_list($projects_list); | |
75 | exit; | |
a59d4afd | 76 | } |
6191f8e1 KS |
77 | |
78 | my $file_name = $cgi->param('f'); | |
b87d78d6 KS |
79 | if (defined $file_name) { |
80 | if ($file_name =~ m/(^|\/)(|\.|\.\.)($|\/)/) { | |
81 | undef $file_name; | |
09bd7898 | 82 | die_error(undef, "Non-canonical file parameter."); |
b87d78d6 | 83 | } |
09bd7898 | 84 | if ($file_name =~ m/[^a-zA-Z0-9_\.\/\-\+\#\~\:\!]/) { |
b87d78d6 | 85 | undef $file_name; |
09bd7898 | 86 | die_error(undef, "Invalid character in file parameter."); |
b87d78d6 | 87 | } |
a59d4afd | 88 | } |
6191f8e1 KS |
89 | |
90 | my $hash = $cgi->param('h'); | |
b87d78d6 KS |
91 | if (defined $hash && !($hash =~ m/^[0-9a-fA-F]{40}$/)) { |
92 | undef $hash; | |
09bd7898 | 93 | die_error(undef, "Invalid hash parameter."); |
a59d4afd | 94 | } |
6191f8e1 KS |
95 | |
96 | my $hash_parent = $cgi->param('hp'); | |
b87d78d6 KS |
97 | if (defined $hash_parent && !($hash_parent =~ m/^[0-9a-fA-F]{40}$/)) { |
98 | undef $hash_parent; | |
09bd7898 KS |
99 | die_error(undef, "Invalid hash_parent parameter."); |
100 | } | |
101 | ||
102 | my $hash_base = $cgi->param('hb'); | |
103 | if (defined $hash_base && !($hash_base =~ m/^[0-9a-fA-F]{40}$/)) { | |
104 | undef $hash_base; | |
105 | die_error(undef, "Invalid parent hash parameter."); | |
a59d4afd | 106 | } |
6191f8e1 KS |
107 | |
108 | my $time_back = $cgi->param('t'); | |
b87d78d6 KS |
109 | if (defined $time_back) { |
110 | if ($time_back =~ m/^[^0-9]+$/) { | |
111 | undef $time_back; | |
09bd7898 | 112 | die_error(undef, "Invalid time parameter."); |
b87d78d6 | 113 | } |
2ad9331e | 114 | } |
823d5dc8 | 115 | |
09bd7898 KS |
116 | if ($action eq "blob") { |
117 | git_blob(); | |
118 | exit; | |
119 | } elsif ($action eq "tree") { | |
120 | git_tree(); | |
121 | exit; | |
122 | } elsif ($action eq "rss") { | |
123 | git_rss(); | |
124 | exit; | |
125 | } elsif ($action eq "commit") { | |
126 | git_commit(); | |
127 | exit; | |
128 | } elsif ($action eq "log") { | |
129 | git_log(); | |
130 | exit; | |
131 | } elsif ($action eq "blobdiff") { | |
132 | git_blobdiff(); | |
133 | exit; | |
134 | } elsif ($action eq "commitdiff") { | |
135 | git_commitdiff(); | |
136 | exit; | |
137 | } elsif ($action eq "history") { | |
138 | git_history(); | |
139 | exit; | |
140 | } else { | |
141 | undef $action; | |
142 | die_error(undef, "Unknown action."); | |
143 | exit; | |
144 | } | |
145 | ||
12a88f2f | 146 | sub git_header_html { |
a59d4afd KS |
147 | my $status = shift || "200 OK"; |
148 | ||
b87d78d6 KS |
149 | my $title = "git"; |
150 | if (defined $project) { | |
151 | $title .= " - $project"; | |
152 | if (defined $action) { | |
153 | $title .= "/$action"; | |
154 | } | |
155 | } | |
a59d4afd KS |
156 | print $cgi->header(-type=>'text/html', -charset => 'utf-8', -status=> $status); |
157 | print <<EOF; | |
6191f8e1 | 158 | <?xml version="1.0" encoding="utf-8"?> |
161332a5 | 159 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> |
034df39e | 160 | <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US"> |
6191f8e1 | 161 | <!-- git web interface v$version, (C) 2005, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke <ch\@gierke.de> --> |
161332a5 | 162 | <head> |
b87d78d6 | 163 | <title>$title</title> |
6191f8e1 KS |
164 | $rss_link |
165 | <style type="text/css"> | |
166 | body { font-family: sans-serif; font-size: 12px; margin:0px; } | |
167 | a { color:#0000cc; } | |
168 | a:hover { color:#880000; } | |
169 | a:visited { color:#880000; } | |
170 | a:active { color:#880000; } | |
b87d78d6 KS |
171 | div.page_header { |
172 | margin:15px 15px 0px; height:25px; padding:8px; | |
6191f8e1 KS |
173 | font-size:18px; font-weight:bold; background-color:#d9d8d1; |
174 | } | |
175 | div.page_header a:visited { color:#0000cc; } | |
176 | div.page_header a:hover { color:#880000; } | |
b87d78d6 | 177 | div.page_nav { margin:0px 15px; padding:8px; border:solid #d9d8d1; border-width:0px 1px; } |
6191f8e1 | 178 | div.page_nav a:visited { color:#0000cc; } |
09bd7898 | 179 | div.page_path { font-weight:bold; margin:0px 15px; padding:8px; border:solid #d9d8d1; border-width:0px 1px 1px} |
b87d78d6 | 180 | div.page_footer { margin:0px 15px 15px; height:17px; padding:4px; padding-left:8px; background-color: #d9d8d1; } |
6191f8e1 | 181 | div.page_footer_text { float:left; color:#555555; font-style:italic; } |
b87d78d6 | 182 | div.page_body { margin:0px 15px; padding:8px; border:solid #d9d8d1; border-width:0px 1px; } |
6191f8e1 | 183 | div.title, a.title { |
b87d78d6 | 184 | display:block; margin:0px 15px; padding:6px 8px; |
6191f8e1 KS |
185 | font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000; |
186 | } | |
187 | a.title:hover { background-color: #d9d8d1; } | |
b87d78d6 KS |
188 | div.title_text { margin:0px 15px; padding:6px 8px; border: solid #d9d8d1; border-width:0px 1px 1px; } |
189 | div.log_body { margin:0px 15px; padding:8px; padding-left:150px; border:solid #d9d8d1; border-width:0px 1px; } | |
6191f8e1 KS |
190 | span.log_age { position:relative; float:left; width:142px; font-style:italic; } |
191 | div.log_link { font-size:10px; font-family:sans-serif; font-style:normal; position:relative; float:left; width:142px; } | |
192 | div.list { | |
09bd7898 | 193 | display:block; margin:0px 15px; padding:4px 6px 2px; border:solid #d9d8d1; border-width:0px 1px; |
6191f8e1 KS |
194 | font-weight:bold; |
195 | } | |
196 | div.list_head { | |
09bd7898 | 197 | display:block; margin:0px 15px; padding:6px 6px 4px; border:solid #d9d8d1; border-width:0px 1px 1px; |
6191f8e1 KS |
198 | font-style:italic; |
199 | } | |
200 | div.list a { text-decoration:none; color:#000000; } | |
201 | div.list a:hover { color:#880000; } | |
202 | div.link { | |
09bd7898 | 203 | margin:0px 15px; padding:0px 6px 8px; border:solid #d9d8d1; border-width:0px 1px 1px; |
6191f8e1 KS |
204 | font-family:sans-serif; font-size:10px; |
205 | } | |
b87d78d6 KS |
206 | td { padding:5px 15px 0px 0px; font-size:12px; } |
207 | th { padding-right:10px; font-size:12px; text-align:left; } | |
208 | span.diff_info { color:#000099; background-color:#edece6; font-style:italic; } | |
09bd7898 KS |
209 | a.rss_logo { float:right; padding:3px 0px; width:35px; line-height:10px; |
210 | border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e; | |
6191f8e1 | 211 | color:#ffffff; background-color:#ff6600; |
09bd7898 KS |
212 | font-weight:bold; font-family:sans-serif; font-size:10px; |
213 | text-align:center; text-decoration:none; | |
6191f8e1 | 214 | } |
2735983d | 215 | a.rss_logo:hover { background-color:#ee5500; } |
6191f8e1 | 216 | </style> |
161332a5 KS |
217 | </head> |
218 | <body> | |
219 | EOF | |
ff7669a5 | 220 | print "<div class=\"page_header\">\n" . |
b87d78d6 | 221 | "<a href=\"http://kernel.org/pub/software/scm/cogito\">" . |
022be3d0 | 222 | "<img src=\"$my_uri?a=git-logo.png\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/></a>"; |
b87d78d6 KS |
223 | print $cgi->a({-href => $home_link}, "projects") . " / "; |
224 | if (defined $project) { | |
d63577da | 225 | print $cgi->a({-href => "$my_uri?p=$project;a=log"}, escapeHTML($project)); |
b87d78d6 KS |
226 | if (defined $action) { |
227 | print " / $action"; | |
228 | } | |
4c02e3c5 KS |
229 | } |
230 | print "</div>\n"; | |
161332a5 KS |
231 | } |
232 | ||
12a88f2f | 233 | sub git_footer_html { |
6191f8e1 | 234 | print "<div class=\"page_footer\">\n"; |
b87d78d6 | 235 | if (defined $project) { |
09bd7898 | 236 | my $descr = git_read_description($project); |
b87d78d6 | 237 | if (defined $descr) { |
6191f8e1 | 238 | print "<div class=\"page_footer_text\">" . escapeHTML($descr) . "</div>\n"; |
6191f8e1 | 239 | } |
2735983d | 240 | print $cgi->a({-href => "$my_uri?p=$project;a=rss", -class => "rss_logo"}, "RSS") . "\n"; |
ff7669a5 | 241 | } |
6191f8e1 KS |
242 | print "</div>\n" . |
243 | "</body>\n" . | |
9cd3d988 | 244 | "</html>"; |
161332a5 KS |
245 | } |
246 | ||
061cc7cd KS |
247 | sub die_error { |
248 | my $status = shift || "403 Forbidden"; | |
a59d4afd | 249 | my $error = shift || "Malformed query, file missing or permission denied"; |
664f4cc5 | 250 | |
a59d4afd KS |
251 | git_header_html($status); |
252 | print "<div class=\"page_body\">\n" . | |
253 | "<br/><br/>\n"; | |
6191f8e1 | 254 | print "$status - $error\n"; |
a59d4afd KS |
255 | print "<br/></div>\n"; |
256 | git_footer_html(); | |
09bd7898 | 257 | exit; |
a59d4afd KS |
258 | } |
259 | ||
09bd7898 | 260 | sub git_read_head { |
54b0a43c | 261 | my $path = shift; |
09bd7898 | 262 | |
b87d78d6 | 263 | open my $fd, "$projectroot/$path/HEAD" || return undef; |
12a88f2f KS |
264 | my $head = <$fd>; |
265 | close $fd; | |
266 | chomp $head; | |
b87d78d6 KS |
267 | if ($head =~ m/^[0-9a-fA-F]{40}$/) { |
268 | return $head; | |
269 | } else { | |
270 | return undef; | |
271 | } | |
272 | } | |
273 | ||
09bd7898 | 274 | sub git_read_description { |
b87d78d6 | 275 | my $path = shift; |
09bd7898 | 276 | |
b87d78d6 KS |
277 | open my $fd, "$projectroot/$path/description" || return undef; |
278 | my $descr = <$fd>; | |
279 | close $fd; | |
280 | chomp $descr; | |
281 | return $descr; | |
12a88f2f KS |
282 | } |
283 | ||
09bd7898 | 284 | sub git_read_commit { |
703ac710 KS |
285 | my $commit = shift; |
286 | my %co; | |
287 | my @parents; | |
288 | ||
b87d78d6 | 289 | open my $fd, "-|", "$gitbin/git-cat-file commit $commit" || return; |
703ac710 | 290 | while (my $line = <$fd>) { |
b87d78d6 KS |
291 | last if $line eq "\n"; |
292 | chomp $line; | |
703ac710 KS |
293 | if ($line =~ m/^tree (.*)$/) { |
294 | $co{'tree'} = $1; | |
295 | } elsif ($line =~ m/^parent (.*)$/) { | |
296 | push @parents, $1; | |
022be3d0 | 297 | } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) { |
3f714537 | 298 | $co{'author'} = $1; |
185f09e5 KS |
299 | $co{'author_epoch'} = $2; |
300 | $co{'author_tz'} = $3; | |
991910a9 KS |
301 | $co{'author_name'} = $co{'author'}; |
302 | $co{'author_name'} =~ s/ <.*//; | |
86eed32d KS |
303 | } elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) { |
304 | $co{'committer'} = $1; | |
185f09e5 KS |
305 | $co{'committer_epoch'} = $2; |
306 | $co{'committer_tz'} = $3; | |
991910a9 KS |
307 | $co{'committer_name'} = $co{'committer'}; |
308 | $co{'committer_name'} =~ s/ <.*//; | |
703ac710 KS |
309 | } |
310 | } | |
3f714537 | 311 | $co{'parents'} = \@parents; |
3e029299 | 312 | $co{'parent'} = $parents[0]; |
3f714537 KS |
313 | my (@comment) = map { chomp; $_ } <$fd>; |
314 | $co{'comment'} = \@comment; | |
315 | $co{'title'} = $comment[0]; | |
b87d78d6 KS |
316 | close $fd || return; |
317 | if (!defined $co{'tree'}) { | |
318 | return undef | |
319 | }; | |
2ae100df KS |
320 | |
321 | my $age = time - $co{'committer_epoch'}; | |
322 | $co{'age'} = $age; | |
323 | if ($age > 60*60*24*365*2) { | |
324 | $co{'age_string'} = (int $age/60/60/24/365); | |
325 | $co{'age_string'} .= " years ago"; | |
326 | } elsif ($age > 60*60*24*365/12*2) { | |
327 | $co{'age_string'} = int $age/60/60/24/365/12; | |
328 | $co{'age_string'} .= " months ago"; | |
329 | } elsif ($age > 60*60*24*7*2) { | |
330 | $co{'age_string'} = int $age/60/60/24/7; | |
331 | $co{'age_string'} .= " weeks ago"; | |
332 | } elsif ($age > 60*60*24*2) { | |
333 | $co{'age_string'} = int $age/60/60/24; | |
334 | $co{'age_string'} .= " days ago"; | |
335 | } elsif ($age > 60*60*2) { | |
336 | $co{'age_string'} = int $age/60/60; | |
337 | $co{'age_string'} .= " hours ago"; | |
338 | } elsif ($age > 60*2) { | |
339 | $co{'age_string'} = int $age/60; | |
340 | $co{'age_string'} .= " minutes ago"; | |
b87d78d6 KS |
341 | } elsif ($age > 2) { |
342 | $co{'age_string'} = int $age; | |
343 | $co{'age_string'} .= " seconds ago"; | |
344 | } else { | |
345 | $co{'age_string'} .= " right now"; | |
2ae100df | 346 | } |
703ac710 KS |
347 | return %co; |
348 | } | |
349 | ||
8ed23e1b | 350 | sub git_diff_html { |
8ed23e1b | 351 | my $from = shift; |
2735983d | 352 | my $from_name = shift; |
8ed23e1b | 353 | my $to = shift; |
2735983d | 354 | my $to_name = shift; |
4c02e3c5 | 355 | |
8ed23e1b KS |
356 | my $from_tmp = "/dev/null"; |
357 | my $to_tmp = "/dev/null"; | |
8ed23e1b | 358 | my $pid = $$; |
4c02e3c5 | 359 | |
ff7669a5 | 360 | # create tmp from-file |
b87d78d6 | 361 | if (defined $from) { |
8ed23e1b | 362 | $from_tmp = "$gittmp/gitweb_" . $$ . "_from"; |
b87d78d6 | 363 | open my $fd2, "> $from_tmp"; |
034df39e | 364 | open my $fd, "-|", "$gitbin/git-cat-file blob $from"; |
8ed23e1b KS |
365 | my @file = <$fd>; |
366 | print $fd2 @file; | |
4c02e3c5 KS |
367 | close $fd2; |
368 | close $fd; | |
4c02e3c5 KS |
369 | } |
370 | ||
b531daf3 | 371 | # create tmp to-file |
b87d78d6 | 372 | if (defined $to) { |
8ed23e1b KS |
373 | $to_tmp = "$gittmp/gitweb_" . $$ . "_to"; |
374 | open my $fd2, "> $to_tmp"; | |
034df39e | 375 | open my $fd, "-|", "$gitbin/git-cat-file blob $to"; |
8ed23e1b KS |
376 | my @file = <$fd>; |
377 | print $fd2 @file; | |
4c02e3c5 KS |
378 | close $fd2; |
379 | close $fd; | |
4c02e3c5 KS |
380 | } |
381 | ||
2735983d | 382 | open my $fd, "-|", "/usr/bin/diff -u -p -L $from_name -L $to_name $from_tmp $to_tmp"; |
4c02e3c5 KS |
383 | while (my $line = <$fd>) { |
384 | my $char = substr($line,0,1); | |
2735983d KS |
385 | # skip errors |
386 | next if $char eq '\\'; | |
387 | # color the diff | |
54b0a43c KS |
388 | print '<span style="color: #008800;">' if $char eq '+'; |
389 | print '<span style="color: #CC0000;">' if $char eq '-'; | |
390 | print '<span style="color: #990099;">' if $char eq '@'; | |
4c02e3c5 | 391 | print escapeHTML($line); |
1b143380 | 392 | print '</span>' if $char eq '+' or $char eq '-' or $char eq '@'; |
4c02e3c5 KS |
393 | } |
394 | close $fd; | |
8ed23e1b | 395 | |
b87d78d6 | 396 | if (defined $from) { |
2735983d | 397 | unlink($from_tmp); |
8ed23e1b | 398 | } |
b87d78d6 | 399 | if (defined $to) { |
2735983d | 400 | unlink($to_tmp); |
8ed23e1b | 401 | } |
4c02e3c5 KS |
402 | } |
403 | ||
d767d59c | 404 | sub mode_str { |
2735983d KS |
405 | my $mode = oct shift; |
406 | ||
b87d78d6 KS |
407 | if (S_ISDIR($mode & S_IFMT)) { |
408 | return 'drwxr-xr-x'; | |
409 | } elsif (S_ISLNK($mode)) { | |
410 | return 'lrwxrwxrwx'; | |
411 | } elsif (S_ISREG($mode)) { | |
991910a9 | 412 | # git cares only about the executable bit |
b87d78d6 KS |
413 | if ($mode & S_IXUSR) { |
414 | return '-rwxr-xr-x'; | |
54b0a43c | 415 | } else { |
b87d78d6 | 416 | return '-rw-r--r--'; |
54b0a43c | 417 | }; |
b87d78d6 KS |
418 | } else { |
419 | return '----------'; | |
d767d59c | 420 | } |
d767d59c KS |
421 | } |
422 | ||
2735983d KS |
423 | sub file_type { |
424 | my $mode = oct shift; | |
425 | ||
b87d78d6 | 426 | if (S_ISDIR($mode & S_IFMT)) { |
2735983d | 427 | return "directory"; |
b87d78d6 | 428 | } elsif (S_ISLNK($mode)) { |
2735983d | 429 | return "symlink"; |
b87d78d6 KS |
430 | } elsif (S_ISREG($mode)) { |
431 | return "file"; | |
2735983d KS |
432 | } else { |
433 | return "unknown"; | |
434 | } | |
435 | } | |
436 | ||
86eed32d | 437 | sub date_str { |
991910a9 KS |
438 | my $epoch = shift; |
439 | my $tz = shift || "-0000"; | |
86eed32d | 440 | |
991910a9 | 441 | my %date; |
86eed32d KS |
442 | my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); |
443 | my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); | |
991910a9 KS |
444 | my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch); |
445 | $date{'hour'} = $hour; | |
ff7669a5 KS |
446 | $date{'minute'} = $min; |
447 | $date{'mday'} = $mday; | |
448 | $date{'day'} = $days[$wday]; | |
449 | $date{'month'} = $months[$mon]; | |
991910a9 KS |
450 | $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec; |
451 | $date{'mday-time'} = sprintf "%d %s %02d:%02d", $mday, $months[$mon], $hour ,$min; | |
452 | ||
034df39e KS |
453 | $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/; |
454 | my $local = $epoch + ((int $1 + ($2/60)) * 3600); | |
991910a9 | 455 | ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local); |
185f09e5 KS |
456 | $date{'hour_local'} = $hour; |
457 | $date{'minute_local'} = $min; | |
458 | $date{'tz_local'} = $tz; | |
991910a9 | 459 | return %date; |
86eed32d KS |
460 | } |
461 | ||
b87d78d6 | 462 | # git-logo (cached in browser for one day) |
eb28240b | 463 | sub git_logo { |
022be3d0 | 464 | print $cgi->header(-type => 'image/png', -expires => '+1d'); |
b87d78d6 KS |
465 | # cat git-logo.png | hexdump -e '16/1 " %02x" "\n"' | sed 's/ /\\x/g' |
466 | print "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" . | |
467 | "\x00\x00\x00\x48\x00\x00\x00\x1b\x04\x03\x00\x00\x00\x2d\xd9\xd4" . | |
468 | "\x2d\x00\x00\x00\x18\x50\x4c\x54\x45\xff\xff\xff\x60\x60\x5d\xb0" . | |
469 | "\xaf\xaa\x00\x80\x00\xce\xcd\xc7\xc0\x00\x00\xe8\xe8\xe6\xf7\xf7" . | |
470 | "\xf6\x95\x0c\xa7\x47\x00\x00\x00\x73\x49\x44\x41\x54\x28\xcf\x63" . | |
471 | "\x48\x67\x20\x04\x4a\x5c\x18\x0a\x08\x2a\x62\x53\x61\x20\x02\x08" . | |
472 | "\x0d\x69\x45\xac\xa1\xa1\x01\x30\x0c\x93\x60\x36\x26\x52\x91\xb1" . | |
473 | "\x01\x11\xd6\xe1\x55\x64\x6c\x6c\xcc\x6c\x6c\x0c\xa2\x0c\x70\x2a" . | |
474 | "\x62\x06\x2a\xc1\x62\x1d\xb3\x01\x02\x53\xa4\x08\xe8\x00\x03\x18" . | |
475 | "\x26\x56\x11\xd4\xe1\x20\x97\x1b\xe0\xb4\x0e\x35\x24\x71\x29\x82" . | |
476 | "\x99\x30\xb8\x93\x0a\x11\xb9\x45\x88\xc1\x8d\xa0\xa2\x44\x21\x06" . | |
477 | "\x27\x41\x82\x40\x85\xc1\x45\x89\x20\x70\x01\x00\xa4\x3d\x21\xc5" . | |
478 | "\x12\x1c\x9a\xfe\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82"; | |
022be3d0 KS |
479 | } |
480 | ||
09bd7898 KS |
481 | sub git_project_list { |
482 | my $project_list = shift; | |
483 | my @list; | |
484 | ||
485 | if (-d $project_list) { | |
486 | # search in directory | |
487 | my $dir = $project_list; | |
488 | opendir my $dh, $dir || return undef; | |
489 | while (my $dir = readdir($dh)) { | |
490 | if (-e "$projectroot/$dir/HEAD") { | |
491 | push @list, $dir; | |
492 | } | |
493 | } | |
494 | closedir($dh); | |
495 | } elsif (-e $project_list) { | |
496 | # read from file | |
497 | open my $fd , $project_list || return undef; | |
498 | while (my $line = <$fd>) { | |
499 | chomp $line; | |
500 | if (-e "$projectroot/$line/HEAD") { | |
501 | push @list, $line; | |
502 | } | |
503 | } | |
504 | close $fd; | |
505 | } | |
506 | ||
507 | if (!@list) { | |
508 | die_error(undef, "No project found."); | |
509 | } | |
510 | @list = sort @list; | |
511 | ||
b87d78d6 | 512 | git_header_html(); |
eb28240b | 513 | print "<div class=\"page_body\"><br/>\n"; |
b87d78d6 KS |
514 | print "<table cellspacing=\"0\">\n"; |
515 | print "<tr>\n" . | |
516 | "<th>Project</th>\n" . | |
517 | "<th>Description</th>\n" . | |
518 | "<th>Owner</th>\n" . | |
519 | "<th>last change</th>\n" . | |
eb28240b | 520 | "</tr>\n"; |
09bd7898 KS |
521 | foreach my $proj (@list) { |
522 | my $head = git_read_head($proj); | |
b87d78d6 KS |
523 | if (!defined $head) { |
524 | next; | |
525 | } | |
526 | $ENV{'SHA1_FILE_DIRECTORY'} = "$projectroot/$proj/objects"; | |
09bd7898 | 527 | my %co = git_read_commit($head); |
b87d78d6 KS |
528 | if (!%co) { |
529 | next; | |
530 | } | |
09bd7898 | 531 | my $descr = git_read_description($proj) || ""; |
b87d78d6 KS |
532 | my $owner = ""; |
533 | my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat("$projectroot/$proj"); | |
534 | my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid); | |
535 | if (defined $gcos) { | |
536 | $owner = $gcos; | |
537 | $owner =~ s/[,;].*$//; | |
538 | } | |
539 | print "<tr>\n" . | |
540 | "<td>" . $cgi->a({-href => "$my_uri?p=$proj;a=log"}, escapeHTML($proj)) . "</td>\n" . | |
541 | "<td>$descr</td>\n" . | |
542 | "<td><i>$owner</i></td>\n"; | |
543 | if ($co{'age'} < 60*60*2) { | |
544 | print "<td><span style =\"color: #009900;\"><b><i>" . $co{'age_string'} . "</i></b></span></td>\n"; | |
545 | } elsif ($co{'age'} < 60*60*24*2) { | |
546 | print "<td><span style =\"color: #009900;\"><i>" . $co{'age_string'} . "</i></span></td>\n"; | |
547 | } else { | |
548 | print "<td><i>" . $co{'age_string'} . "</i></td>\n"; | |
549 | } | |
550 | print "</tr>\n"; | |
b87d78d6 KS |
551 | } |
552 | print "</table>\n" . | |
553 | "<br/>\n" . | |
554 | "</div>\n"; | |
555 | git_footer_html(); | |
161332a5 KS |
556 | } |
557 | ||
09bd7898 KS |
558 | sub git_get_hash_by_path { |
559 | my $base = shift; | |
560 | my $path = shift; | |
561 | ||
562 | my $tree = $base; | |
563 | my @parts = split '/', $path; | |
564 | while (my $part = shift @parts) { | |
565 | open my $fd, "-|", "$gitbin/git-ls-tree $tree" || die_error(undef, "Open git-ls-tree failed."); | |
566 | my (@entries) = map { chomp; $_ } <$fd>; | |
567 | close $fd || die_error(undef, "Reading tree failed."); | |
568 | foreach my $line (@entries) { | |
569 | #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' | |
570 | $line =~ m/^([0-9]+)\t(.*)\t(.*)\t(.*)$/; | |
571 | my $t_mode = $1; | |
572 | my $t_type = $2; | |
573 | my $t_hash = $3; | |
574 | my $t_name = $4; | |
575 | if ($t_name eq $part) { | |
576 | if (!(@parts)) { | |
577 | return $t_hash; | |
578 | } | |
579 | if ($t_type eq "tree") { | |
580 | $tree = $t_hash; | |
581 | } | |
582 | last; | |
583 | } | |
584 | } | |
585 | } | |
586 | } | |
587 | ||
588 | sub git_blob { | |
589 | if (!defined $hash && defined $file_name) { | |
590 | my $base = $hash_base || git_read_head($project); | |
591 | $hash = git_get_hash_by_path($base, $file_name, "blob"); | |
592 | } | |
593 | open my $fd, "-|", "$gitbin/git-cat-file blob $hash" || die_error(undef, "Open failed."); | |
594 | my $base = $file_name || ""; | |
12a88f2f | 595 | git_header_html(); |
09bd7898 KS |
596 | if (defined $hash_base && (my %co = git_read_commit($hash_base))) { |
597 | print "<div class=\"page_nav\"> view\n" . | |
598 | $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$hash_base"}, "commit") . | |
599 | " | " . $cgi->a({-href => "$my_uri?p=$project;a=commitdiff;h=$hash_base"}, "diffs") . | |
600 | " | " . $cgi->a({-href => "$my_uri?p=$project;a=tree;h=" . $co{'tree'} . ";hb=$hash_base"}, "tree"); | |
601 | if (defined $file_name) { | |
602 | print " | " . $cgi->a({-href => "$my_uri?p=$project;a=history;h=$hash_base;f=$file_name"}, "history"); | |
603 | } | |
604 | print "<br/><br/>\n" . | |
605 | "</div>\n"; | |
606 | print "<div>\n" . | |
607 | $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$hash_base", -class => "title"}, escapeHTML($co{'title'})) . "\n"; | |
608 | } else { | |
609 | print "<div class=\"page_nav\">\n" . | |
610 | "<br/><br/></div>\n" . | |
611 | "<div class=\"title\">$hash</div>\n"; | |
612 | } | |
613 | if (defined $file_name) { | |
614 | print "<div class=\"page_path\">/$file_name</div>\n"; | |
615 | } | |
eb28240b | 616 | print "<div class=\"page_body\"><pre>\n"; |
161332a5 KS |
617 | my $nr; |
618 | while (my $line = <$fd>) { | |
619 | $nr++; | |
fbb592a9 | 620 | printf "<span style =\"color: #999999;\">%4i\t</span>%s", $nr, escapeHTML($line);; |
161332a5 | 621 | } |
b87d78d6 | 622 | close $fd || print "Reading blob failed.\n"; |
09bd7898 | 623 | print "</pre>\n"; |
fbb592a9 | 624 | print "</div>"; |
12a88f2f | 625 | git_footer_html(); |
09bd7898 KS |
626 | } |
627 | ||
628 | sub git_tree { | |
b87d78d6 | 629 | if (!defined $hash) { |
09bd7898 KS |
630 | $hash = git_read_head($project); |
631 | if (defined $file_name) { | |
632 | my $base = $hash_base || git_read_head($project); | |
633 | $hash = git_get_hash_by_path($base, $file_name, "tree"); | |
634 | } | |
161332a5 | 635 | } |
09bd7898 | 636 | open my $fd, "-|", "$gitbin/git-ls-tree $hash" || die_error(undef, "Open git-ls-tree failed."); |
161332a5 | 637 | my (@entries) = map { chomp; $_ } <$fd>; |
09bd7898 | 638 | close $fd || die_error(undef, "Reading tree failed."); |
d63577da | 639 | |
12a88f2f | 640 | git_header_html(); |
09bd7898 KS |
641 | my $base_key = ""; |
642 | my $file_key = ""; | |
643 | my $base = ""; | |
644 | if (defined $hash_base && (my %co = git_read_commit($hash_base))) { | |
645 | $base_key = ";hb=$hash_base"; | |
d63577da | 646 | print "<div class=\"page_nav\"> view\n" . |
09bd7898 KS |
647 | $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$hash_base"}, "commit") . " | " . |
648 | $cgi->a({-href => "$my_uri?p=$project;a=commitdiff;h=$hash_base"}, "diffs") . " | " . | |
649 | $cgi->a({-href => "$my_uri?p=$project;a=tree;h=" . $co{'tree'} . ";hb=$hash_base"}, "tree") . | |
6191f8e1 KS |
650 | "<br/><br/>\n" . |
651 | "</div>\n"; | |
d63577da | 652 | print "<div>\n" . |
09bd7898 | 653 | $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$hash_base", -class => "title"}, escapeHTML($co{'title'})) . "\n" . |
d63577da KS |
654 | "</div>\n"; |
655 | } else { | |
656 | print "<div class=\"page_nav\">\n"; | |
657 | print "<br/><br/></div>\n"; | |
658 | print "<div class=\"title\">$hash</div>\n"; | |
659 | } | |
09bd7898 KS |
660 | if (defined $file_name) { |
661 | $base = "$file_name/"; | |
662 | print "<div class=\"page_path\">/$file_name</div>\n"; | |
663 | } else { | |
664 | print "<div class=\"page_path\">/</div>\n"; | |
665 | } | |
fbb592a9 | 666 | print "<div class=\"page_body\">\n"; |
09bd7898 | 667 | print "<pre>\n"; |
161332a5 | 668 | foreach my $line (@entries) { |
c068cff1 | 669 | #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' |
161332a5 | 670 | $line =~ m/^([0-9]+)\t(.*)\t(.*)\t(.*)$/; |
d767d59c | 671 | my $t_mode = $1; |
161332a5 KS |
672 | my $t_type = $2; |
673 | my $t_hash = $3; | |
674 | my $t_name = $4; | |
09bd7898 | 675 | $file_key = ";f=$base$t_name"; |
161332a5 | 676 | if ($t_type eq "blob") { |
09bd7898 | 677 | print mode_str($t_mode). " " . $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$t_hash" . $base_key . $file_key}, $t_name); |
b87d78d6 | 678 | if (S_ISLNK(oct $t_mode)) { |
2735983d KS |
679 | open my $fd, "-|", "$gitbin/git-cat-file blob $t_hash"; |
680 | my $target = <$fd>; | |
681 | close $fd; | |
682 | print "\t -> $target"; | |
683 | } | |
684 | print "\n"; | |
161332a5 | 685 | } elsif ($t_type eq "tree") { |
09bd7898 | 686 | print mode_str($t_mode). " " . $cgi->a({-href => "$my_uri?p=$project;a=tree;h=$t_hash" . $base_key . $file_key}, $t_name) . "\n"; |
161332a5 KS |
687 | } |
688 | } | |
689 | print "</pre>\n"; | |
09bd7898 | 690 | print "</div>"; |
12a88f2f | 691 | git_footer_html(); |
09bd7898 KS |
692 | } |
693 | ||
694 | sub git_rss { | |
695 | open my $fd, "-|", "$gitbin/git-rev-list --max-count=20 " . git_read_head($project) || die_error(undef, "Open failed."); | |
d51e902a | 696 | my (@revlist) = map { chomp; $_ } <$fd>; |
09bd7898 | 697 | close $fd || die_error(undef, "Reading rev-list failed."); |
e334d18c | 698 | |
034df39e KS |
699 | print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); |
700 | print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n". | |
701 | "<rss version=\"0.91\">\n"; | |
702 | print "<channel>\n"; | |
703 | print "<title>$project</title>\n". | |
704 | "<link> " . $my_url . "/$project/log</link>\n". | |
705 | "<description>$project log</description>\n". | |
706 | "<language>en</language>\n"; | |
707 | ||
708 | foreach my $commit (@revlist) { | |
09bd7898 | 709 | my %co = git_read_commit($commit); |
185f09e5 | 710 | my %ad = date_str($co{'author_epoch'}); |
034df39e | 711 | print "<item>\n" . |
eb28240b | 712 | "\t<title>" . sprintf("%d %s %02d:%02d", $ad{'mday'}, $ad{'month'}, $ad{'hour'}, $ad{'minute'}) . " - " . escapeHTML($co{'title'}) . "</title>\n" . |
034df39e KS |
713 | "\t<link> " . $my_url . "?p=$project;a=commit;h=$commit</link>\n" . |
714 | "\t<description>"; | |
715 | my $comment = $co{'comment'}; | |
716 | foreach my $line (@$comment) { | |
717 | print escapeHTML($line) . "<br/>\n"; | |
161332a5 | 718 | } |
034df39e KS |
719 | print "\t</description>\n" . |
720 | "</item>\n"; | |
721 | } | |
722 | print "</channel></rss>"; | |
09bd7898 KS |
723 | } |
724 | ||
725 | sub git_log { | |
726 | my $head = git_read_head($project); | |
b87d78d6 KS |
727 | my $limit_option = ""; |
728 | if (!defined $time_back) { | |
729 | $limit_option = "--max-count=10"; | |
730 | } elsif ($time_back > 0) { | |
731 | my $date = time - $time_back*24*60*60; | |
732 | $limit_option = "--max-age=$date"; | |
733 | } | |
09bd7898 | 734 | open my $fd, "-|", "$gitbin/git-rev-list $limit_option $head" || die_error(undef, "Open failed."); |
034df39e | 735 | my (@revlist) = map { chomp; $_ } <$fd>; |
09bd7898 | 736 | close $fd || die_error(undef, "Reading rev-list failed."); |
034df39e KS |
737 | |
738 | git_header_html(); | |
739 | print "<div class=\"page_nav\">\n"; | |
740 | print "view "; | |
b87d78d6 KS |
741 | print $cgi->a({-href => "$my_uri?p=$project;a=log"}, "last 10") . " | " . |
742 | $cgi->a({-href => "$my_uri?p=$project;a=log;t=1"}, "day") . " | " . | |
034df39e KS |
743 | $cgi->a({-href => "$my_uri?p=$project;a=log;t=7"}, "week") . " | " . |
744 | $cgi->a({-href => "$my_uri?p=$project;a=log;t=31"}, "month") . " | " . | |
745 | $cgi->a({-href => "$my_uri?p=$project;a=log;t=365"}, "year") . " | " . | |
746 | $cgi->a({-href => "$my_uri?p=$project;a=log;t=0"}, "all") . "<br/>\n"; | |
747 | print "<br/><br/>\n" . | |
748 | "</div>\n"; | |
749 | ||
b87d78d6 | 750 | if (!@revlist) { |
09bd7898 | 751 | my %co = git_read_commit($head); |
034df39e | 752 | print "<div class=\"page_body\"> Last change " . $co{'age_string'} . ".<br/><br/></div>\n"; |
161332a5 | 753 | } |
034df39e KS |
754 | |
755 | foreach my $commit (@revlist) { | |
09bd7898 | 756 | my %co = git_read_commit($commit); |
b87d78d6 | 757 | next if !%co; |
034df39e KS |
758 | my %ad = date_str($co{'author_epoch'}); |
759 | print "<div>\n" . | |
09bd7898 | 760 | $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$commit", -class => "title"}, |
034df39e KS |
761 | "<span class=\"log_age\">" . $co{'age_string'} . "</span>" . escapeHTML($co{'title'})) . "\n" . |
762 | "</div>\n"; | |
763 | print "<div class=\"title_text\">\n" . | |
764 | "<div class=\"log_link\">\n" . | |
eb28240b KS |
765 | "view " . $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$commit"}, "commit") . |
766 | " | " . $cgi->a({-href => "$my_uri?p=$project;a=commitdiff;h=$commit"}, "diff") . | |
767 | "<br/>\n" . | |
034df39e KS |
768 | "</div>\n" . |
769 | "<i>" . escapeHTML($co{'author_name'}) . " [" . $ad{'rfc2822'} . "]</i><br/>\n" . | |
770 | "</div>\n" . | |
771 | "<div class=\"log_body\">\n"; | |
772 | my $comment = $co{'comment'}; | |
09bd7898 | 773 | my $empty = 0; |
034df39e | 774 | foreach my $line (@$comment) { |
09bd7898 KS |
775 | if ($line =~ m/^(signed.off|acked).by/i) { |
776 | next; | |
777 | } | |
778 | if ($line eq "") { | |
779 | if ($empty) { | |
780 | next; | |
781 | } | |
782 | $empty = 1; | |
783 | } else { | |
784 | $empty = 0; | |
785 | } | |
786 | print escapeHTML($line) . "<br/>\n"; | |
034df39e | 787 | } |
09bd7898 KS |
788 | if (!$empty) { |
789 | print "<br/>\n"; | |
790 | } | |
791 | print "</div>\n"; | |
e334d18c | 792 | } |
034df39e | 793 | git_footer_html(); |
09bd7898 KS |
794 | } |
795 | ||
796 | sub git_commit { | |
797 | my %co = git_read_commit($hash); | |
034df39e | 798 | if (!%co) { |
09bd7898 | 799 | die_error(undef, "Unknown commit object."); |
d63577da | 800 | } |
185f09e5 KS |
801 | my %ad = date_str($co{'author_epoch'}, $co{'author_tz'}); |
802 | my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'}); | |
161332a5 | 803 | |
6191f8e1 | 804 | my @difftree; |
b87d78d6 | 805 | if (defined $co{'parent'}) { |
09bd7898 | 806 | open my $fd, "-|", "$gitbin/git-diff-tree -r " . $co{'parent'} . " $hash" || die_error(undef, "Open failed."); |
6191f8e1 | 807 | @difftree = map { chomp; $_ } <$fd>; |
09bd7898 | 808 | close $fd || die_error(undef, "Reading diff-tree failed."); |
6191f8e1 KS |
809 | } else { |
810 | # fake git-diff-tree output for initial revision | |
09bd7898 | 811 | open my $fd, "-|", "$gitbin/git-ls-tree -r $hash" || die_error(undef, "Open failed."); |
6191f8e1 | 812 | @difftree = map { chomp; "+" . $_ } <$fd>; |
09bd7898 | 813 | close $fd || die_error(undef, "Reading ls-tree failed."); |
6191f8e1 | 814 | } |
12a88f2f | 815 | git_header_html(); |
ff7669a5 KS |
816 | print "<div class=\"page_nav\"> view\n" . |
817 | $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$hash"}, "commit") . " | \n" . | |
1207151d | 818 | $cgi->a({-href => "$my_uri?p=$project;a=commitdiff;h=$hash"}, "diffs") . " | \n" . |
09bd7898 | 819 | $cgi->a({-href => "$my_uri?p=$project;a=tree;h=" . $co{'tree'} . ";hb=$hash"}, "tree") . "\n" . |
ff7669a5 | 820 | "<br/><br/></div>\n"; |
b87d78d6 KS |
821 | if (defined $co{'parent'}) { |
822 | print "<div>\n" . | |
823 | $cgi->a({-href => "$my_uri?p=$project;a=commitdiff;h=$hash", -class => "title"}, escapeHTML($co{'title'})) . "\n" . | |
824 | "</div>\n"; | |
825 | } else { | |
826 | print "<div>\n" . | |
09bd7898 | 827 | $cgi->a({-href => "$my_uri?p=$project;a=tree;h=" . $co{'tree'} . ";hb=$hash", -class => "title"}, escapeHTML($co{'title'})) . "\n" . |
b87d78d6 KS |
828 | "</div>\n"; |
829 | } | |
6191f8e1 | 830 | print "<div class=\"title_text\">\n" . |
b87d78d6 KS |
831 | "<table cellspacing=\"0\">\n"; |
832 | print "<tr><td>author</td><td>" . escapeHTML($co{'author'}) . "</td></tr>\n". | |
833 | "<tr><td></td><td> " . $ad{'rfc2822'}; | |
927dcec4 | 834 | if ($ad{'hour_local'} < 6) { |
b87d78d6 KS |
835 | printf(" (<span style=\"color: #cc0000;\">%02d:%02d</span> %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}); |
836 | } else { | |
837 | printf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}); | |
838 | } | |
839 | print "</td></tr>\n"; | |
840 | print "<tr><td>committer</td><td>" . escapeHTML($co{'committer'}) . "</td></tr>\n"; | |
841 | print "<tr><td></td><td> " . $cd{'rfc2822'} . | |
842 | sprintf(" (%02d:%02d %s)", $cd{'hour_local'}, $cd{'minute_local'}, $cd{'tz_local'}) . "</td></tr>\n"; | |
843 | print "<tr><td>commit</td><td style=\"font-family: monospace;\">$hash</td></tr>\n"; | |
844 | print "<tr><td>tree</td><td style=\"font-family: monospace;\">" . | |
09bd7898 | 845 | $cgi->a({-href => "$my_uri?p=$project;a=tree;h=" . $co{'tree'} . ";hb=" . $hash}, $co{'tree'}) . "</td></tr>\n"; |
3e029299 KS |
846 | my $parents = $co{'parents'}; |
847 | foreach my $par (@$parents) { | |
b87d78d6 KS |
848 | print "<tr><td>parent</td><td style=\"font-family: monospace;\">" . |
849 | $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$par"}, $par) . "</td></tr>\n"; | |
3e029299 | 850 | } |
b87d78d6 KS |
851 | print "</table>". |
852 | "</div>\n"; | |
fbb592a9 | 853 | print "<div class=\"page_body\">\n"; |
3e029299 | 854 | my $comment = $co{'comment'}; |
09bd7898 KS |
855 | my $empty = 0; |
856 | my $signed = 0; | |
3e029299 | 857 | foreach my $line (@$comment) { |
09bd7898 KS |
858 | # print only one empty line |
859 | if ($line eq "") { | |
860 | if ($empty || $signed) { | |
861 | next; | |
862 | } | |
863 | $empty = 1; | |
864 | } else { | |
865 | $empty = 0; | |
866 | } | |
867 | if ($line =~ m/(signed.off|acked).by/i) { | |
868 | $signed = 1; | |
927dcec4 | 869 | print "<span style=\"color: #888888\">" . escapeHTML($line) . "</span><br/>\n"; |
3e029299 | 870 | } else { |
09bd7898 | 871 | $signed = 0; |
3e029299 KS |
872 | print escapeHTML($line) . "<br/>\n"; |
873 | } | |
874 | } | |
927dcec4 | 875 | print "</div>\n"; |
09bd7898 | 876 | print "<div class=\"list_head\">\n"; |
6191f8e1 | 877 | if ($#difftree > 10) { |
09bd7898 | 878 | print(($#difftree + 1) . " files changed:\n"); |
6191f8e1 | 879 | } |
09bd7898 | 880 | print "</div>\n"; |
161332a5 | 881 | foreach my $line (@difftree) { |
c068cff1 KS |
882 | # '*100644->100644 blob 9f91a116d91926df3ba936a80f020a6ab1084d2b->bb90a0c3a91eb52020d0db0e8b4f94d30e02d596 net/ipv4/route.c' |
883 | # '+100644 blob 4a83ab6cd565d21ab0385bac6643826b83c2fcd4 arch/arm/lib/bitops.h' | |
9cd3d988 KS |
884 | # '*100664->100644 blob b1a8e3dd5556b61dd771d32307c6ee5d7150fa43->b1a8e3dd5556b61dd771d32307c6ee5d7150fa43 show-files.c' |
885 | # '*100664->100644 blob d08e895238bac36d8220586fdc28c27e1a7a76d3->d08e895238bac36d8220586fdc28c27e1a7a76d3 update-cache.c' | |
161332a5 KS |
886 | $line =~ m/^(.)(.*)\t(.*)\t(.*)\t(.*)$/; |
887 | my $op = $1; | |
888 | my $mode = $2; | |
889 | my $type = $3; | |
890 | my $id = $4; | |
891 | my $file = $5; | |
892 | if ($type eq "blob") { | |
893 | if ($op eq "+") { | |
b87d78d6 KS |
894 | my $mode_chng = ""; |
895 | if (S_ISREG(oct $mode)) { | |
896 | $mode_chng = sprintf(" with mode: %04o", (oct $mode) & 0777); | |
897 | } | |
927dcec4 | 898 | print "<div class=\"list\">\n" . |
09bd7898 | 899 | $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$id;hb=$hash;f=$file"}, |
b87d78d6 | 900 | escapeHTML($file) . " <span style=\"color: #008000;\">[new " . file_type($mode) . $mode_chng . "]</span>") . "\n" . |
9cd3d988 KS |
901 | "</div>"; |
902 | print "<div class=\"link\">\n" . | |
1207151d | 903 | "view " . |
09bd7898 | 904 | $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$id;hb=$hash;f=$file"}, "file") . "<br/>\n" . |
9cd3d988 | 905 | "</div>\n"; |
161332a5 | 906 | } elsif ($op eq "-") { |
927dcec4 | 907 | print "<div class=\"list\">\n" . |
09bd7898 | 908 | $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$id;hb=$hash;f=$file"}, |
2735983d | 909 | escapeHTML($file) . " <span style=\"color: #c00000;\">[deleted " . file_type($mode) . "]</span>") . "\n" . |
9cd3d988 KS |
910 | "</div>"; |
911 | print "<div class=\"link\">\n" . | |
1207151d | 912 | "view " . |
09bd7898 | 913 | $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$id;hb=$hash;f=$file"}, "file") . " | " . |
6191f8e1 | 914 | $cgi->a({-href => "$my_uri?p=$project;a=history;h=$hash;f=$file"}, "history") . "<br/>\n" . |
9cd3d988 | 915 | "</div>\n"; |
161332a5 KS |
916 | } elsif ($op eq "*") { |
917 | $id =~ m/([0-9a-fA-F]+)->([0-9a-fA-F]+)/; | |
9cd3d988 KS |
918 | my $from_id = $1; |
919 | my $to_id = $2; | |
920 | $mode =~ m/^([0-7]{6})->([0-7]{6})$/; | |
921 | my $from_mode = $1; | |
922 | my $to_mode = $2; | |
d63577da | 923 | my $mode_chnge = ""; |
b87d78d6 KS |
924 | if ($from_mode != $to_mode) { |
925 | $mode_chnge = " <span style=\"color: #888888;\">[changed"; | |
926 | if (((oct $from_mode) & S_IFMT) != ((oct $to_mode) & S_IFMT)) { | |
927 | $mode_chnge .= " from " . file_type($from_mode) . " to " . file_type($to_mode); | |
928 | } | |
929 | if (((oct $from_mode) & 0777) != ((oct $to_mode) & 0777)) { | |
930 | if (S_ISREG($from_mode) && S_ISREG($to_mode)) { | |
931 | $mode_chnge .= sprintf(" mode: %04o->%04o", (oct $from_mode) & 0777, (oct $to_mode) & 0777); | |
932 | } elsif (S_ISREG($to_mode)) { | |
933 | $mode_chnge .= sprintf(" mode: %04o", (oct $to_mode) & 0777); | |
934 | } | |
935 | } | |
936 | $mode_chnge .= "]</span>\n"; | |
9cd3d988 | 937 | } |
2bb7c6d4 KS |
938 | print "<div class=\"list\">\n"; |
939 | if ($to_id ne $from_id) { | |
09bd7898 | 940 | print $cgi->a({-href => "$my_uri?p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file"}, |
2bb7c6d4 KS |
941 | escapeHTML($file) . $mode_chnge) . "\n" . |
942 | "</div>\n"; | |
943 | } else { | |
09bd7898 | 944 | print $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$to_id;hb=$hash;f=$file"}, |
2bb7c6d4 KS |
945 | escapeHTML($file) . $mode_chnge) . "\n" . |
946 | "</div>\n"; | |
947 | } | |
9cd3d988 | 948 | print "<div class=\"link\">\n" . |
1207151d | 949 | "view "; |
9cd3d988 | 950 | if ($to_id ne $from_id) { |
09bd7898 | 951 | print $cgi->a({-href => "$my_uri?p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file"}, "diff") . " | "; |
9cd3d988 | 952 | } |
09bd7898 | 953 | print $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$to_id;hb=$hash;f=$file"}, "file") . " | " . |
6191f8e1 | 954 | $cgi->a({-href => "$my_uri?p=$project;a=history;h=$hash;f=$file"}, "history") . "<br/>\n" . |
9cd3d988 | 955 | "</div>\n"; |
161332a5 KS |
956 | } |
957 | } | |
958 | } | |
12a88f2f | 959 | git_footer_html(); |
09bd7898 KS |
960 | } |
961 | ||
962 | sub git_blobdiff { | |
b87d78d6 | 963 | mkdir($gittmp, 0700); |
12a88f2f | 964 | git_header_html(); |
09bd7898 KS |
965 | if (defined $hash_base && (my %co = git_read_commit($hash_base))) { |
966 | print "<div class=\"page_nav\"> view\n" . | |
967 | $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$hash_base"}, "commit") . | |
968 | " | " . $cgi->a({-href => "$my_uri?p=$project;a=commitdiff;h=$hash_base"}, "diffs") . | |
969 | " | " . $cgi->a({-href => "$my_uri?p=$project;a=tree;h=" . $co{'tree'} . ";hb=$hash_base"}, "tree"); | |
970 | if (defined $file_name) { | |
971 | print " | " . $cgi->a({-href => "$my_uri?p=$project;a=history;h=$hash_base;f=$file_name"}, "history"); | |
972 | } | |
973 | print "<br/><br/>\n" . | |
974 | "</div>\n"; | |
975 | print "<div>\n" . | |
976 | $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$hash_base", -class => "title"}, escapeHTML($co{'title'})) . "\n" . | |
977 | "</div>\n"; | |
978 | } else { | |
979 | print "<div class=\"page_nav\">\n" . | |
980 | "<br/><br/></div>\n" . | |
981 | "<div class=\"title\">$hash vs $hash_parent</div>\n"; | |
982 | } | |
983 | if (defined $file_name) { | |
984 | print "<div class=\"page_path\">\n" . | |
985 | "/$file_name\n" . | |
986 | "</div>\n"; | |
987 | } | |
9cd3d988 | 988 | print "<div class=\"page_body\">\n" . |
ff7669a5 | 989 | "<pre>\n"; |
2735983d | 990 | print "<span class=\"diff_info\">blob:" . |
09bd7898 | 991 | $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$hash_parent;hb=$hash_base;f=$file_name"}, $hash_parent) . |
2735983d | 992 | " -> blob:" . |
09bd7898 | 993 | $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name"}, $hash) . |
2735983d | 994 | "</span>\n"; |
09bd7898 | 995 | git_diff_html($hash_parent, $file_name || $hash_parent, $hash, $file_name || $hash); |
ff7669a5 | 996 | print "</pre>\n" . |
09bd7898 | 997 | "</div>"; |
12a88f2f | 998 | git_footer_html(); |
09bd7898 KS |
999 | } |
1000 | ||
1001 | sub git_commitdiff { | |
b87d78d6 | 1002 | mkdir($gittmp, 0700); |
09bd7898 | 1003 | my %co = git_read_commit($hash); |
034df39e | 1004 | if (!%co) { |
09bd7898 | 1005 | die_error(undef, "Unknown commit object."); |
d63577da | 1006 | } |
09bd7898 | 1007 | open my $fd, "-|", "$gitbin/git-diff-tree -r " . $co{'parent'} . " $hash" || die_error(undef, "Open failed."); |
4c02e3c5 | 1008 | my (@difftree) = map { chomp; $_ } <$fd>; |
09bd7898 | 1009 | close $fd || die_error(undef, "Reading diff-tree failed."); |
161332a5 | 1010 | |
12a88f2f | 1011 | git_header_html(); |
ff7669a5 KS |
1012 | print "<div class=\"page_nav\"> view\n" . |
1013 | $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$hash"}, "commit") . " | \n" . | |
1207151d | 1014 | $cgi->a({-href => "$my_uri?p=$project;a=commitdiff;h=$hash"}, "diffs") . " | \n" . |
09bd7898 | 1015 | $cgi->a({-href => "$my_uri?p=$project;a=tree;h=" . $co{'tree'} . ";hb=$hash"}, "tree") . "\n" . |
ff7669a5 | 1016 | "<br/><br/></div>\n"; |
9cd3d988 | 1017 | print "<div>\n" . |
927dcec4 | 1018 | $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$hash", -class => "title"}, escapeHTML($co{'title'})) . "\n" . |
9cd3d988 | 1019 | "</div>\n"; |
ff7669a5 KS |
1020 | print "<div class=\"page_body\">\n" . |
1021 | "<pre>\n"; | |
4c02e3c5 | 1022 | foreach my $line (@difftree) { |
c068cff1 | 1023 | # '*100644->100644 blob 8e5f9bbdf4de94a1bc4b4da8cb06677ce0a57716->8da3a306d0c0c070d87048d14a033df02f40a154 Makefile' |
4c02e3c5 KS |
1024 | $line =~ m/^(.)(.*)\t(.*)\t(.*)\t(.*)$/; |
1025 | my $op = $1; | |
1026 | my $mode = $2; | |
1027 | my $type = $3; | |
1028 | my $id = $4; | |
1029 | my $file = $5; | |
1030 | if ($type eq "blob") { | |
1031 | if ($op eq "+") { | |
b87d78d6 | 1032 | print "<span class=\"diff_info\">" . file_type($mode) . ":" . |
09bd7898 | 1033 | $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$id;hb=$hash;f=$file"}, $id) . "(new)" . |
2735983d | 1034 | "</span>\n"; |
b87d78d6 | 1035 | git_diff_html(undef, "/dev/null", $id, "b/$file"); |
4c02e3c5 | 1036 | } elsif ($op eq "-") { |
b87d78d6 | 1037 | print "<span class=\"diff_info\">" . file_type($mode) . ":" . |
09bd7898 | 1038 | $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$id;hb=$hash;f=$file"}, $id) . "(deleted)" . |
2735983d | 1039 | "</span>\n"; |
b87d78d6 | 1040 | git_diff_html($id, "a/$file", undef, "/dev/null"); |
4c02e3c5 KS |
1041 | } elsif ($op eq "*") { |
1042 | $id =~ m/([0-9a-fA-F]+)->([0-9a-fA-F]+)/; | |
2735983d KS |
1043 | my $from_id = $1; |
1044 | my $to_id = $2; | |
1045 | $mode =~ m/([0-7]+)->([0-7]+)/; | |
1046 | my $from_mode = $1; | |
1047 | my $to_mode = $2; | |
1048 | if ($from_id ne $to_id) { | |
1049 | print "<span class=\"diff_info\">" . | |
09bd7898 | 1050 | file_type($from_mode) . ":" . $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$from_id;hb=$hash;f=$file"}, $from_id) . |
2735983d | 1051 | " -> " . |
09bd7898 | 1052 | file_type($to_mode) . ":" . $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$to_id;hb=$hash;f=$file"}, $to_id); |
2735983d KS |
1053 | print "</span>\n"; |
1054 | git_diff_html($from_id, "a/$file", $to_id, "b/$file"); | |
9cd3d988 | 1055 | } |
4c02e3c5 KS |
1056 | } |
1057 | } | |
161332a5 | 1058 | } |
b87d78d6 | 1059 | print "</pre><br/>\n"; |
fbb592a9 | 1060 | print "</div>"; |
12a88f2f | 1061 | git_footer_html(); |
09bd7898 KS |
1062 | } |
1063 | ||
1064 | sub git_history { | |
b87d78d6 | 1065 | if (!defined $hash) { |
09bd7898 KS |
1066 | $hash = git_read_head($project); |
1067 | } | |
1068 | my %co = git_read_commit($hash); | |
1069 | if (!%co) { | |
1070 | die_error(undef, "Unknown commit object."); | |
2ae100df | 1071 | } |
d51e902a | 1072 | git_header_html(); |
09bd7898 KS |
1073 | print "<div class=\"page_nav\"> view\n" . |
1074 | $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$hash"}, "commit") . " | " . | |
1075 | $cgi->a({-href => "$my_uri?p=$project;a=commitdiff;h=$hash"}, "diffs") . " | " . | |
1076 | $cgi->a({-href => "$my_uri?p=$project;a=tree;h=" . $co{'tree'} . ";hb=$hash"}, "tree") . | |
1077 | "<br/><br/>\n" . | |
1078 | "</div>\n"; | |
d63577da | 1079 | print "<div>\n" . |
09bd7898 | 1080 | $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$hash", -class => "title"}, escapeHTML($co{'title'})) . "\n" . |
820e4f6b | 1081 | "</div>\n"; |
09bd7898 KS |
1082 | print "<div class=\"page_path\">\n" . |
1083 | "/$file_name<br/>\n"; | |
1084 | print "</div>\n"; | |
b87d78d6 KS |
1085 | open my $fd, "-|", "$gitbin/git-rev-list $hash | $gitbin/git-diff-tree -r --stdin $file_name"; |
1086 | my $commit; | |
1087 | while (my $line = <$fd>) { | |
1088 | if ($line =~ m/^([0-9a-fA-F]{40}) /){ | |
1089 | $commit = $1; | |
1090 | next; | |
d51e902a | 1091 | } |
b87d78d6 KS |
1092 | if ($line =~ m/^(.)(.*)\t(.*)\t(.*)\t(.*)$/ && (defined $commit)) { |
1093 | my $type = $3; | |
1094 | my $file = $5; | |
1095 | if ($file ne $file_name || $type ne "blob") { | |
1096 | next; | |
1097 | } | |
09bd7898 | 1098 | my %co = git_read_commit($commit); |
b87d78d6 KS |
1099 | if (!%co) { |
1100 | next; | |
1101 | } | |
927dcec4 | 1102 | print "<div class=\"list\">\n" . |
b87d78d6 | 1103 | $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$commit"}, |
1207151d | 1104 | "<span class=\"log_age\">" . $co{'age_string'} . "</span>" . escapeHTML($co{'title'})) . "\n" . |
334538f1 KS |
1105 | "</div>\n"; |
1106 | print "<div class=\"link\">\n" . | |
820e4f6b | 1107 | "view " . |
eb28240b KS |
1108 | $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$commit"}, "commit") . |
1109 | " | " . $cgi->a({-href => "$my_uri?p=$project;a=tree;h=" . $co{'tree'} . ";hb=$commit"}, "tree") . | |
1110 | " | " . $cgi->a({-href => "$my_uri?p=$project;a=blob;hb=$commit;f=" . $file}, "file") . | |
1111 | "<br/><br/>\n" . | |
820e4f6b | 1112 | "</div>\n"; |
b87d78d6 | 1113 | undef $commit; |
2ae100df | 1114 | } |
d51e902a | 1115 | } |
b87d78d6 | 1116 | close $fd; |
d51e902a | 1117 | git_footer_html(); |
161332a5 | 1118 | } |