4 $repo_path = "/home/git/";
8 $header_height = false; //false = dynamic
9 $tile_size = 20; /* do not edit */
12 $use_local_branches = true;
13 $use_remote_branches = true;
18 array(array(0, 255, 0), array(0, 192, 0)),
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];
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.");
43 //check if project is in $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];
56 if(!preg_match('#^[a-z0-9.-_]*$#i', $_GET['p']))
58 if(file_exists($repo_path.$_GET['p']))
59 $project = $_GET['p'];
65 if(!preg_match('#^[a-z0-9/.-_]*$#i', $_GET['h']))
67 if(!preg_match('#^[a-z0-9]*$#i', $_GET['c']))
69 if(!preg_match('#^[0-9]*$#i', $_GET['from']))
71 if(!preg_match('#^[0-9]*$#i', $_GET['to']))
76 if($_SESSION['git_graph']) {
77 $old_data = unserialize($_SESSION['git_graph']);
78 if($old_data['r'] == $_GET['r'])
82 function parse_commit($commit_data) {
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) == " ") {
89 $commit['text'] .= "\n";
90 $commit['text'] .= substr($rev_line, 4);
92 $opt = explode(" ", $rev_line);
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];
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);
121 $_SESSION['git_graph'] = serialize($data);
123 //generate graph data
124 $data['branches'] = array();
125 $data['ubranches'] = array();
128 $graph_commit = NULL;
129 $first_commit = true;
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'];
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];
155 foreach($data['branches'] as &$branch) {
156 if($branch['next'] == $head[0]) {
158 $branch['name'][] = $name;
165 $data['branches'][count($data['branches'])] = array(
167 "uid" => $branch_uid++
,
170 "name" => array($name),
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];
185 foreach($data['branches'] as &$branch) {
186 if($branch['next'] == $head[0]) {
188 $branch['name'][] = $name;
195 $data['branches'][count($data['branches'])] = array(
197 "uid" => $branch_uid++
,
200 "name" => array($name),
207 if($_GET['c'] == "header") {
208 $count = count($data['branches']);
209 if($count > $max_branches)
210 $count = $max_branches;
211 if(!$header_height) {
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]);
219 $header_height = $maxlen * 2 +
15;
221 $image = imagecreatetruecolor($count * $size +
60, $header_height);
222 $transparentIndex = imagecolorallocate($image, 217, 216, 209);
223 imagefill($image, 0, 0, $transparentIndex);
225 foreach($data['branches'] as $branch) {
226 if($branch['sticky'] && count($branch['name'])) {
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]);
233 if(!$branches) die();
234 imagecolortransparent($image, $transparentIndex);
235 header('Content-Type: image/png');
237 imagedestroy($image);
241 for($i = 0; $i <= intval($_GET['to']); $i++
) {
242 //for($i = 0; $i < count($data['commits']); $i++) {
243 $commit = &$data['commits'][$i];
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;
254 foreach($data['branches'] as $id => &$cbranch) {
255 if($cbranch['next'] == $commit['id']) {
256 if($first && !$cbranch['pre_merge']) {
257 $branch = &$data['branches'][$id];
262 foreach($data['branches'] as $id => &$cbranch) {
263 if($cbranch['next'] == $commit['id']) {
265 $branch = &$data['branches'][$id];
267 } else if($cbranch['id'] == $branch['id']) {
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];
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;
289 if(count($commit['parent']) > 1) {
291 for($j = 1; $j < count($commit['parent']); $j++
) {
293 foreach($data['branches'] as $cbranch) {
294 if($cbranch['next'] == $commit['parent'][$j]) {
301 foreach($data['branches'] as $bid => &$cbranch) {
302 if(!$cbranch['active']) {
308 $data['branches'][count($data['branches'])] = array();
309 $cbranch = &$data['branches'][count($data['branches'])-1];
310 $cbranch['id'] = $brach_id++
;
312 $cbranch['uid'] = $branch_uid++
;
313 $cbranch['active'] = true;
314 $cbranch['pre_merge'] = true;
315 $cbranch['next'] = $commit['parent'][$j];
317 $commit['merge'][] = array("point" => $cbranch['id'], "end" => $add);
318 $commit['dot_merge'] = true;
319 $data['ubranches'][$cbranch['uid']] = $data['branches'][$cbranch['id']-1];
322 } else if(count($commit['parent']) == 0) {
323 $branch['active'] = false;
324 $commit['dot_init'] = true;
326 $branch['next'] = $commit['parent'][0];
327 $branch['pre_merge'] = false;
328 $data['ubranches'][$branch['uid']] = $data['branches'][$branch['id']-1];
330 $commit['dot'] = $branch['id'];
332 foreach($data['branches'] as $id => $cbranch) {
333 $commit['branches'][$id] = $cbranch;
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;
350 if($graph_commit == NULL)
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);
360 function image_set_color($src, $color) {
362 imagesavealpha($src, true);
363 imagealphablending($src, false);
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);
370 imagesetpixel($src, $x, $y, imagecolorallocatealpha($src, $color[0], $color[1], $color[2], $src_pix_array['alpha']));
375 function overlay_image($name, $left, $color = false) {
376 global $image, $size, $tile_size;
377 $image2 = imagecreatefrompng($name);
380 image_set_color($image2, $color);
382 imagecopyresampled($image, $image2, $left, 0, 0, 0, $size, $size, $tile_size, $tile_size);
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'];
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);
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;
403 if($graph_commit['merge']) {
404 foreach($graph_commit['merge'] as $merge) {
405 if($merge['point'] == $branch['id']) {
412 if($branch['id'] > $max_branches) continue;
413 overlay_image("images/line.png", $left, get_color($branch['id']));
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");
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']));
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']));
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']));
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']));
441 overlay_image("images/dot.png", ($graph_commit['dot'] - 1) * $size, get_color($graph_commit['dot']));
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']));
451 imagecolortransparent($image, $transparentIndex);
453 header('Content-Type: image/png');
455 imagedestroy($image);