update gitweb to 2.22.0.214.g8dca754b1e
[gitweb.git] / git_graph.php
1 <?php
2
3 /* CONFIG */
4 $repo_path = "/home/git/";
5 $project_list = null;
6
7 $size = 20;
8 $header_height = false; //false = dynamic
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),
18 array(array(0, 255, 0), array(0, 192, 0)),
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;
127 $branch_uid = 1;
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;
142 $branch['sticky'] = true;
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);
150 $name = explode("/", $head[1]);
151 $name = $name[count($name)-1];
152 if(!$head[0])
153 continue;
154 $existing = false;
155 foreach($data['branches'] as &$branch) {
156 if($branch['next'] == $head[0]) {
157 $existing = true;
158 $branch['name'][] = $name;
159 break;
160 }
161 }
162 unset($branch);
163 if($existing)
164 continue;
165 $data['branches'][count($data['branches'])] = array(
166 "id" => $brach_id++,
167 "uid" => $branch_uid++,
168 "active" => true,
169 "sticky" => true,
170 "name" => array($name),
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);
180 $name = explode("/", $head[1]);
181 $name = $name[count($name)-2];
182 if(!$head[0])
183 continue;
184 $existing = false;
185 foreach($data['branches'] as &$branch) {
186 if($branch['next'] == $head[0]) {
187 $existing = true;
188 $branch['name'][] = $name;
189 break;
190 }
191 }
192 unset($branch);
193 if($existing)
194 continue;
195 $data['branches'][count($data['branches'])] = array(
196 "id" => $brach_id++,
197 "uid" => $branch_uid++,
198 "active" => true,
199 "sticky" => true,
200 "name" => array($name),
201 "next" => $head[0]
202 );
203 }
204 }
205 }
206
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
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
344 //DEV Breakpoint
345 //print_r($data);
346 //die();
347
348 //generate image
349
350 if($graph_commit == NULL)
351 die("ERROR 0x07");
352
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
385 function get_color($id, $text = false) {
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 }
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);
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 ?>