Commit | Line | Data |
---|---|---|
8a1b4b56 S |
1 | <?php |
2 | ||
3 | /* CONFIG */ | |
4 | $repo_path = "/home/git/"; | |
5 | $project_list = null; | |
6 | ||
7 | $size = 20; | |
b7ac5aae | 8 | $header_height = false; //false = dynamic |
8a1b4b56 S |
9 | $tile_size = 20; /* do not edit */ |
10 | ||
11 | $max_branches = 30; | |
12 | $use_local_branches = true; | |
13 | $use_remote_branches = true; | |
14 | ||
15 | $colors = array( | |
16 | NULL, | |
17 | array(255, 0, 0), | |
b7ac5aae | 18 | array(array(0, 255, 0), array(0, 192, 0)), |
8a1b4b56 S |
19 | array(0, 0, 255), |
20 | array(128, 128, 128), | |
21 | array(128, 128, 0), | |
22 | array(0, 128, 128), | |
23 | array(128, 0, 128) | |
24 | ); | |
25 | /* END CONFIG */ | |
26 | ||
27 | session_start(); | |
28 | ||
29 | //parse ; seprated query args | |
30 | $args = explode(";", $_SERVER["QUERY_STRING"]); | |
31 | foreach($args as $arg) { | |
32 | $arg = explode("=", $arg, 2); | |
33 | $_GET[$arg[0]] = $arg[1]; | |
34 | } | |
35 | ||
36 | if($_SESSION['gitgraph_maxbranch']) | |
37 | $max_branches = $_SESSION['gitgraph_maxbranch']; | |
38 | if($_GET['maxbranch']) { | |
39 | $_SESSION['gitgraph_maxbranch'] = $_GET['maxbranch']; | |
40 | die("maxbranch set."); | |
41 | } | |
42 | ||
43 | //check if project is in $project_list | |
44 | $project = NULL; | |
45 | if($project_list) { | |
46 | $projects = file_get_contents($project_list); | |
47 | foreach(explode("\n", $projects) as $cproject) { | |
48 | $cproject = explode(" ", $cproject); | |
49 | if(strtolower($_GET['p']) == strtolower($cproject[0])) { | |
50 | if(file_exists($repo_path.$cproject[0])) | |
51 | $project = $cproject[0]; | |
52 | break; | |
53 | } | |
54 | } | |
55 | } else { | |
56 | if(!preg_match('#^[a-z0-9.-_]*$#i', $_GET['p'])) | |
57 | die("ERROR 0x01"); | |
58 | if(file_exists($repo_path.$_GET['p'])) | |
59 | $project = $_GET['p']; | |
60 | } | |
61 | if(!$project) | |
62 | die("ERROR 0x02"); | |
63 | ||
64 | //check other args | |
65 | if(!preg_match('#^[a-z0-9/.-_]*$#i', $_GET['h'])) | |
66 | die("ERROR 0x03"); | |
67 | if(!preg_match('#^[a-z0-9]*$#i', $_GET['c'])) | |
68 | die("ERROR 0x04"); | |
69 | if(!preg_match('#^[0-9]*$#i', $_GET['from'])) | |
70 | die("ERROR 0x05"); | |
71 | if(!preg_match('#^[0-9]*$#i', $_GET['to'])) | |
72 | die("ERROR 0x06"); | |
73 | ||
74 | //load old cache | |
75 | $data = array(); | |
76 | if($_SESSION['git_graph']) { | |
77 | $old_data = unserialize($_SESSION['git_graph']); | |
78 | if($old_data['r'] == $_GET['r']) | |
79 | $data = $old_data; | |
80 | } | |
81 | ||
82 | function parse_commit($commit_data) { | |
83 | $commit = array(); | |
84 | $rev_lines = explode("\n", str_replace("\r", "", $commit_data)); | |
85 | $commit['id'] = $rev_lines[0]; | |
86 | foreach($rev_lines as $rev_line) { | |
87 | if(substr($rev_line, 0, 4) == " ") { | |
88 | if($commit['text']) | |
89 | $commit['text'] .= "\n"; | |
90 | $commit['text'] .= substr($rev_line, 4); | |
91 | } else { | |
92 | $opt = explode(" ", $rev_line); | |
93 | if($opt[0] == "tree") | |
94 | $commit['tree'] = $opt[1]; | |
95 | else if($opt[0] == "parent") | |
96 | $commit['parent'][] = $opt[1]; | |
97 | else if($opt[0] == "author") { | |
98 | $commit['author'] = $opt[1]; | |
99 | $commit['author_mail'] = $opt[2]; | |
100 | $commit['author_time'] = $opt[3]; | |
101 | } else if($opt[0] == "committer") { | |
102 | $commit['committer'] = $opt[1]; | |
103 | $commit['committer_mail'] = $opt[2]; | |
104 | $commit['committer_time'] = $opt[3]; | |
105 | } | |
106 | } | |
107 | } | |
108 | return $commit; | |
109 | } | |
110 | ||
111 | //load rev-list | |
112 | if(!$data['commits']) { | |
113 | $cmd = "git --git-dir=".$repo_path.$project." rev-list --header --max-count=".($_GET['to'] + 1)." ".($_GET['h'] =="all" ? "--all" : $_GET['h']); | |
114 | $rev_list = shell_exec($cmd); | |
115 | foreach(explode("\000", $rev_list) as $rev) { | |
116 | $data['commits'][] = parse_commit($rev); | |
117 | } | |
118 | } | |
119 | ||
120 | //save cache | |
121 | $_SESSION['git_graph'] = serialize($data); | |
122 | ||
123 | //generate graph data | |
124 | $data['branches'] = array(); | |
125 | $data['ubranches'] = array(); | |
126 | $brach_id = 1; | |
b7ac5aae | 127 | $branch_uid = 1; |
8a1b4b56 S |
128 | $graph_commit = NULL; |
129 | $first_commit = true; | |
130 | ||
131 | if($_GET['h'] == "all") { | |
132 | //set master branch as first branch | |
133 | $cmd = "git --git-dir=".$repo_path.$project." rev-list --header --max-count=1 HEAD"; | |
134 | $head_list = shell_exec($cmd); | |
135 | $head = parse_commit($head_list); | |
136 | $first_commit = false; | |
137 | $data['branches'][count($data['branches'])] = array(); | |
138 | $branch = &$data['branches'][count($data['branches'])-1]; | |
139 | $branch['id'] = $brach_id++; | |
140 | $branch['uid'] = $branch_uid++; | |
141 | $branch['active'] = true; | |
b7ac5aae | 142 | $branch['sticky'] = true; |
8a1b4b56 S |
143 | $branch['next'] = $head['id']; |
144 | unset($branch); | |
145 | if($use_local_branches) { | |
146 | $cmd = "git --git-dir=".$repo_path.$project." for-each-ref --sort=-committerdate refs/heads/"; | |
147 | $head_list = shell_exec($cmd); | |
148 | foreach(explode("\n", str_replace("\r", "", $head_list)) as $head) { | |
149 | $head = explode(" ", $head); | |
b7ac5aae | 150 | $name = explode("/", $head[1]); |
151 | $name = $name[count($name)-1]; | |
8a1b4b56 S |
152 | if(!$head[0]) |
153 | continue; | |
154 | $existing = false; | |
b7ac5aae | 155 | foreach($data['branches'] as &$branch) { |
8a1b4b56 S |
156 | if($branch['next'] == $head[0]) { |
157 | $existing = true; | |
b7ac5aae | 158 | $branch['name'][] = $name; |
8a1b4b56 S |
159 | break; |
160 | } | |
161 | } | |
b7ac5aae | 162 | unset($branch); |
8a1b4b56 S |
163 | if($existing) |
164 | continue; | |
165 | $data['branches'][count($data['branches'])] = array( | |
166 | "id" => $brach_id++, | |
b7ac5aae | 167 | "uid" => $branch_uid++, |
8a1b4b56 | 168 | "active" => true, |
b7ac5aae | 169 | "sticky" => true, |
170 | "name" => array($name), | |
8a1b4b56 S |
171 | "next" => $head[0] |
172 | ); | |
173 | } | |
174 | } | |
175 | if($use_remote_branches) { | |
176 | $cmd = "git --git-dir=".$repo_path.$project." for-each-ref --sort=-committerdate refs/remotes/"; | |
177 | $head_list = shell_exec($cmd); | |
178 | foreach(explode("\n", str_replace("\r", "", $head_list)) as $head) { | |
179 | $head = explode(" ", $head); | |
b7ac5aae | 180 | $name = explode("/", $head[1]); |
181 | $name = $name[count($name)-2]; | |
8a1b4b56 S |
182 | if(!$head[0]) |
183 | continue; | |
184 | $existing = false; | |
b7ac5aae | 185 | foreach($data['branches'] as &$branch) { |
8a1b4b56 S |
186 | if($branch['next'] == $head[0]) { |
187 | $existing = true; | |
b7ac5aae | 188 | $branch['name'][] = $name; |
8a1b4b56 S |
189 | break; |
190 | } | |
191 | } | |
b7ac5aae | 192 | unset($branch); |
8a1b4b56 S |
193 | if($existing) |
194 | continue; | |
195 | $data['branches'][count($data['branches'])] = array( | |
196 | "id" => $brach_id++, | |
b7ac5aae | 197 | "uid" => $branch_uid++, |
8a1b4b56 | 198 | "active" => true, |
b7ac5aae | 199 | "sticky" => true, |
200 | "name" => array($name), | |
8a1b4b56 S |
201 | "next" => $head[0] |
202 | ); | |
203 | } | |
204 | } | |
205 | } | |
206 | ||
b7ac5aae | 207 | if($_GET['c'] == "header") { |
208 | $count = count($data['branches']); | |
209 | if($count > $max_branches) | |
210 | $count = $max_branches; | |
211 | if(!$header_height) { | |
212 | $maxlen = 0; | |
213 | foreach($data['branches'] as $branch) { | |
214 | if($branch['sticky'] && count($branch['name'])) { | |
215 | if(strlen($branch['name'][0]) > $maxlen) | |
216 | $maxlen = strlen($branch['name'][0]); | |
217 | } | |
218 | } | |
219 | $header_height = $maxlen * 2 + 15; | |
220 | } | |
221 | $image = imagecreatetruecolor($count * $size + 60, $header_height); | |
222 | $transparentIndex = imagecolorallocate($image, 217, 216, 209); | |
223 | imagefill($image, 0, 0, $transparentIndex); | |
224 | $branches = 0; | |
225 | foreach($data['branches'] as $branch) { | |
226 | if($branch['sticky'] && count($branch['name'])) { | |
227 | $branches++; | |
228 | $color = get_color($branch['id'], true); | |
229 | $color = imagecolorallocatealpha($image, $color[0], $color[1], $color[2], 0); | |
230 | imagettftext($image, 8, 28, ($branch['id']-1) * $size + 10, $header_height-2, $color, $_SERVER['DOCUMENT_ROOT']."/arial.ttf", $branch['name'][0]); | |
231 | } | |
232 | } | |
233 | if(!$branches) die(); | |
234 | imagecolortransparent($image, $transparentIndex); | |
235 | header('Content-Type: image/png'); | |
236 | imagepng($image); | |
237 | imagedestroy($image); | |
238 | die(); | |
239 | } | |
240 | ||
8a1b4b56 S |
241 | for($i = 0; $i <= intval($_GET['to']); $i++) { |
242 | //for($i = 0; $i < count($data['commits']); $i++) { | |
243 | $commit = &$data['commits'][$i]; | |
244 | //get current branch | |
245 | if($first_commit) { | |
246 | $first_commit = false; | |
247 | $data['branches'][0] = array(); | |
248 | $branch = &$data['branches'][0]; | |
249 | $branch['id'] = $brach_id++; | |
250 | $branch['uid'] = $branch_uid++; | |
251 | $branch['active'] = true; | |
252 | } else { | |
253 | $first = true; | |
254 | foreach($data['branches'] as $id => &$cbranch) { | |
255 | if($cbranch['next'] == $commit['id']) { | |
256 | if($first && !$cbranch['pre_merge']) { | |
257 | $branch = &$data['branches'][$id]; | |
258 | $first = false; | |
259 | } | |
260 | } | |
261 | } | |
262 | foreach($data['branches'] as $id => &$cbranch) { | |
263 | if($cbranch['next'] == $commit['id']) { | |
264 | if($first) { | |
265 | $branch = &$data['branches'][$id]; | |
266 | $first = false; | |
267 | } else if($cbranch['id'] == $branch['id']) { | |
268 | } else { | |
269 | $commit['merge'][] = array("point" => $cbranch['id'], "start" => true); | |
270 | $cbranch['active'] = false; | |
271 | if($cbranch['pre_merge']) { | |
272 | $cbranch['pre_merge_start'] = true; | |
273 | $cbranch['pre_merge_id'] = $branch['id']; | |
274 | $data['ubranches'][$cbranch['uid']] = $data['branches'][$cbranch['id']-1]; | |
275 | } | |
276 | } | |
277 | } | |
278 | } | |
279 | unset($cbranch); | |
280 | if($first) { | |
281 | $data['branches'][count($data['branches'])] = array(); | |
282 | $branch = &$data['branches'][count($data['branches'])-1]; | |
283 | $branch['id'] = $brach_id++; | |
284 | $branch['uid'] = $branch_uid++; | |
285 | $branch['active'] = true; | |
286 | } | |
287 | } | |
288 | ||
289 | if(count($commit['parent']) > 1) { | |
290 | //merge(s) | |
291 | for($j = 1; $j < count($commit['parent']); $j++) { | |
292 | $add = true; | |
293 | foreach($data['branches'] as $cbranch) { | |
294 | if($cbranch['next'] == $commit['parent'][$j]) { | |
295 | $add = false; | |
296 | break; | |
297 | } | |
298 | } | |
299 | if($add) { | |
300 | $cadd = true; | |
301 | foreach($data['branches'] as $bid => &$cbranch) { | |
302 | if(!$cbranch['active']) { | |
303 | $cadd = false; | |
304 | break; | |
305 | } | |
306 | } | |
307 | if($cadd) { | |
308 | $data['branches'][count($data['branches'])] = array(); | |
309 | $cbranch = &$data['branches'][count($data['branches'])-1]; | |
310 | $cbranch['id'] = $brach_id++; | |
311 | } | |
312 | $cbranch['uid'] = $branch_uid++; | |
313 | $cbranch['active'] = true; | |
314 | $cbranch['pre_merge'] = true; | |
315 | $cbranch['next'] = $commit['parent'][$j]; | |
316 | } | |
317 | $commit['merge'][] = array("point" => $cbranch['id'], "end" => $add); | |
318 | $commit['dot_merge'] = true; | |
319 | $data['ubranches'][$cbranch['uid']] = $data['branches'][$cbranch['id']-1]; | |
320 | unset($cbranch); | |
321 | } | |
322 | } else if(count($commit['parent']) == 0) { | |
323 | $branch['active'] = false; | |
324 | $commit['dot_init'] = true; | |
325 | } | |
326 | $branch['next'] = $commit['parent'][0]; | |
327 | $branch['pre_merge'] = false; | |
328 | $data['ubranches'][$branch['uid']] = $data['branches'][$branch['id']-1]; | |
329 | ||
330 | $commit['dot'] = $branch['id']; | |
331 | ||
332 | foreach($data['branches'] as $id => $cbranch) { | |
333 | $commit['branches'][$id] = $cbranch; | |
334 | } | |
335 | ||
336 | if($commit['id'] == $_GET['c']) { | |
337 | //yeaaa i've found a PHP Bug :D | |
338 | //$graph_commit['branches'] still gets updated.. (damn references) | |
339 | //we need a fully independent copy of the array.... | |
340 | $graph_commit = $commit; | |
341 | } | |
342 | } | |
343 | ||
8a1b4b56 S |
344 | //DEV Breakpoint |
345 | //print_r($data); | |
346 | //die(); | |
347 | ||
348 | //generate image | |
b7ac5aae | 349 | |
350 | if($graph_commit == NULL) | |
351 | die("ERROR 0x07"); | |
352 | ||
8a1b4b56 S |
353 | $count = count($data['branches']); |
354 | if($count > $max_branches) | |
355 | $count = $max_branches; | |
356 | $image = imagecreatetruecolor($count * $size, $size); | |
357 | $transparentIndex = imagecolorallocate($image, 0xFF, 0xFF, 0xFF); | |
358 | imagefill($image, 0, 0, $transparentIndex); | |
359 | ||
360 | function image_set_color($src, $color) { | |
361 | global $size; | |
362 | imagesavealpha($src, true); | |
363 | imagealphablending($src, false); | |
364 | // scan image pixels | |
365 | for ($x = 0; $x < $size; $x++) { | |
366 | for ($y = 0; $y < $size; $y++) { | |
367 | $src_pix = imagecolorat($src,$x,$y); | |
368 | $src_pix_array = imagecolorsforindex($src, $src_pix); | |
369 | ||
370 | imagesetpixel($src, $x, $y, imagecolorallocatealpha($src, $color[0], $color[1], $color[2], $src_pix_array['alpha'])); | |
371 | } | |
372 | } | |
373 | } | |
374 | ||
375 | function overlay_image($name, $left, $color = false) { | |
376 | global $image, $size, $tile_size; | |
377 | $image2 = imagecreatefrompng($name); | |
378 | ||
379 | if($color) { | |
380 | image_set_color($image2, $color); | |
381 | } | |
382 | imagecopyresampled($image, $image2, $left, 0, 0, 0, $size, $size, $tile_size, $tile_size); | |
383 | } | |
384 | ||
b7ac5aae | 385 | function get_color($id, $text = false) { |
8a1b4b56 S |
386 | global $colors, $graph_commit, $data; |
387 | if($graph_commit['branches'][($id-1)]['pre_merge']) { | |
388 | $branch = $data['ubranches'][$graph_commit['branches'][($id-1)]['uid']]; | |
389 | if($branch['pre_merge'] && $branch['pre_merge_start']) | |
390 | $id = $branch['pre_merge_id']; | |
391 | } | |
b7ac5aae | 392 | $color_array = $colors[($id - 1) % count($colors)]; |
393 | if($text && is_array($color_array[0]) && $color_array[1]) | |
394 | return $color_array[1]; | |
395 | return (is_array($color_array[0]) ? $color_array[0] : $color_array); | |
8a1b4b56 S |
396 | } |
397 | ||
398 | foreach($graph_commit['branches'] as $branch) { | |
399 | $left = ($branch['id']-1) * $size; | |
400 | if($branch['active']) { | |
401 | if($graph_commit['dot'] == $branch['id']) continue; | |
402 | $show = true; | |
403 | if($graph_commit['merge']) { | |
404 | foreach($graph_commit['merge'] as $merge) { | |
405 | if($merge['point'] == $branch['id']) { | |
406 | $show = false; | |
407 | break; | |
408 | } | |
409 | } | |
410 | } | |
411 | if(!$show) continue; | |
412 | if($branch['id'] > $max_branches) continue; | |
413 | overlay_image("images/line.png", $left, get_color($branch['id'])); | |
414 | } | |
415 | } | |
416 | ||
417 | if($graph_commit['merge']) { | |
418 | foreach($graph_commit['merge'] as $merge) { | |
419 | $dot_dir = ($graph_commit['dot'] < $merge['point'] ? "right" : "left"); | |
420 | $merge_dir = ($graph_commit['dot'] < $merge['point'] ? "left" : "right"); | |
421 | ||
422 | if($graph_commit['dot'] <= $max_branches) | |
423 | overlay_image("images/dot_merge_".$dot_dir.".png", ($graph_commit['dot'] - 1) * $size, get_color($merge['point'])); | |
424 | ||
425 | if($merge['point'] <= $max_branches) { | |
426 | overlay_image("images/".($merge['start'] ? "branch" : "merge")."_".$merge_dir.".png", ($merge['point'] - 1) * $size, get_color($merge['point'])); | |
427 | if(!$merge['start'] && !$merge['end']) | |
428 | overlay_image("images/line.png", ($merge['point'] - 1) * $size, get_color($merge['point'])); | |
429 | } | |
430 | $min = ($graph_commit['dot'] < $merge['point'] ? $graph_commit['dot'] : $merge['point']) + 1; | |
431 | $max = ($graph_commit['dot'] < $merge['point'] ? $merge['point'] : $graph_commit['dot']); | |
432 | for($i = $min; $i < $max; $i++) { | |
433 | if($i > $max_branches) continue; | |
434 | overlay_image("images/line_h.png", ($i - 1) * $size, get_color($merge['point'])); | |
435 | } | |
436 | } | |
437 | if($graph_commit['dot'] <= $max_branches) { | |
438 | if($graph_commit['dot_merge']) | |
439 | overlay_image("images/dot_merge.png", ($graph_commit['dot'] - 1) * $size, get_color($graph_commit['dot'])); | |
440 | else | |
441 | overlay_image("images/dot.png", ($graph_commit['dot'] - 1) * $size, get_color($graph_commit['dot'])); | |
442 | } | |
443 | } else if($graph_commit['dot_init']) { | |
444 | if($graph_commit['dot'] <= $max_branches) | |
445 | overlay_image("images/dot_init.png", ($graph_commit['dot'] - 1) * $size, get_color($graph_commit['dot'])); | |
446 | } else if($graph_commit['dot']) { | |
447 | if($graph_commit['dot'] <= $max_branches) | |
448 | overlay_image("images/dot.png", ($graph_commit['dot'] - 1) * $size, get_color($graph_commit['dot'])); | |
449 | } | |
450 | ||
451 | imagecolortransparent($image, $transparentIndex); | |
452 | ||
453 | header('Content-Type: image/png'); | |
454 | imagepng($image); | |
455 | imagedestroy($image); | |
456 | ||
457 | ?> |