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