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