--- /dev/null
+<?php
+/* graph.class.php - phpgitweb
+ * Copyright (C) 2011-2012 Philipp Kreil (pk910)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class graph_data_generator {
+ const DOT_TYPE_NORMAL = 'a';
+ const DOT_TYPE_MERGE = 'b';
+ const DOT_TYPE_INIT = 'c';
+
+ private $data = array();
+ private $graph = array();
+ private $max_branches, $brach_id = 1, $branch_id = 1;
+
+ public function __construct() {
+ $this->max_branches = 10;
+ $this->data['branches'] = array();
+ $this->data['ubranches'] = array();
+ }
+
+ public function add_branch($first_id, $name) {
+ $existing = false;
+ foreach($this->data['branches'] as &$branch) {
+ if($branch['next'] == $first_id) {
+ $existing = true;
+ $branch['name'][] = $name;
+ break;
+ }
+ }
+ unset($branch);
+ if($existing)
+ continue;
+ $this->data['branches'][count($this->data['branches'])] = array(
+ "id" => $this->brach_id++,
+ "uid" => $this->branch_uid++,
+ "active" => true,
+ "sticky" => true,
+ "name" => array($name),
+ "next" => $first_id,
+ "pre_merge" => false
+ );
+ }
+
+ public function parse($commits) {
+ $brach_id = $this->brach_id;
+ $branch_uid = $this->branch_id;
+ $first_commit = (count($this->data['branches']) == 0 ? true : false);
+ foreach($commits as $commit) {
+ //get current branch
+ $commit['merge'] = array();
+ $commit['dot_type'] = self::DOT_TYPE_NORMAL;
+ if($first_commit) {
+ $first_commit = false;
+ $this->data['branches'][0] = array();
+ $branch = &$this->data['branches'][0];
+ $branch['id'] = $brach_id++;
+ $branch['uid'] = $branch_uid++;
+ $branch['active'] = true;
+ } else {
+ $first = true;
+ foreach($this->data['branches'] as $id => &$cbranch) {
+ if($cbranch['next'] == $commit['id']) {
+ if($first && !$cbranch['pre_merge']) {
+ $branch = &$this->data['branches'][$id];
+ $first = false;
+ }
+ }
+ }
+ foreach($this->data['branches'] as $id => &$cbranch) {
+ if($cbranch['next'] == $commit['id']) {
+ if($first) {
+ $branch = &$this->data['branches'][$id];
+ $first = false;
+ } else if($cbranch['id'] == $branch['id']) {
+ } else {
+ $commit['merge'][] = array("point" => $cbranch['id'], "start" => true, "end" => false);
+ $cbranch['active'] = false;
+ if($cbranch['pre_merge']) {
+ $cbranch['pre_merge_start'] = true;
+ $cbranch['pre_merge_id'] = $branch['id'];
+ $this->data['ubranches'][$cbranch['uid']] = $this->data['branches'][$cbranch['id']-1];
+ }
+ }
+ }
+ }
+ unset($cbranch);
+ if($first) {
+ $this->data['branches'][count($this->data['branches'])] = array();
+ $branch = &$this->data['branches'][count($this->data['branches'])-1];
+ $branch['id'] = $brach_id++;
+ $branch['uid'] = $branch_uid++;
+ $branch['active'] = true;
+ $branch['pre_merge'] = false;
+
+ }
+ }
+
+ if(array_key_exists('parent', $commit) && count($commit['parent']) > 1) {
+ //merge(s)
+ for($j = 1; $j < count($commit['parent']); $j++) {
+ $add = true;
+ foreach($this->data['branches'] as $cbranch) {
+ if($cbranch['next'] == $commit['parent'][$j]) {
+ $add = false;
+ break;
+ }
+ }
+ if($add) {
+ $cadd = true;
+ foreach($this->data['branches'] as $bid => &$cbranch) {
+ if(!$cbranch['active']) {
+ $cadd = false;
+ break;
+ }
+ }
+ if($cadd) {
+ $this->data['branches'][count($this->data['branches'])] = array();
+ $cbranch = &$this->data['branches'][count($this->data['branches'])-1];
+ $cbranch['id'] = $brach_id++;
+ }
+ $cbranch['uid'] = $branch_uid++;
+ $cbranch['active'] = true;
+ $cbranch['pre_merge'] = true;
+ $cbranch['next'] = $commit['parent'][$j];
+ }
+ $commit['merge'][] = array("point" => $cbranch['id'], "start" => false, "end" => $add);
+ $commit['dot_type'] = self::DOT_TYPE_MERGE;
+ $this->data['ubranches'][$cbranch['uid']] = $this->data['branches'][$cbranch['id']-1];
+ unset($cbranch);
+ }
+ } else if(!array_key_exists('parent', $commit) || count($commit['parent']) == 0) {
+ $branch['active'] = false;
+ $commit['dot_type'] = self::DOT_TYPE_INIT;
+ }
+ $branch['next'] = (array_key_exists('parent', $commit) ? $commit['parent'][0] : null);
+ $branch['pre_merge'] = false;
+ $this->data['ubranches'][$branch['uid']] = $this->data['branches'][$branch['id']-1];
+
+ $commit['dot'] = $branch['id'];
+
+ foreach($this->data['branches'] as $id => $cbranch) {
+ $commit['branches'][$id] = $cbranch;
+ }
+
+ $this->graph[$commit['id']] = $this->graph_data($commit);
+ //echo$commit['id']." ".$this->get_graph($commit['id'])."\n";
+ }
+ }
+
+ private function graph_data($commit) {
+ $data = array();
+ $data['d'] = array();
+ $data['d']['p'] = $commit['dot']; //dot position
+ $data['d']['type'] = 'a';
+ $data['l'] = array(); //lines
+ $data['br'] = array(); //branches for color check
+ foreach($commit['branches'] as $branch) {
+ if($branch['pre_merge'] || $commit['merge']) {
+ $data['br'][] = $branch['uid'];
+ }
+ if($branch['active']) {
+ if($commit['dot'] == $branch['id']) continue;
+ $show = true;
+ if($commit['merge']) {
+ foreach($commit['merge'] as $merge) {
+ if($merge['point'] == $branch['id']) {
+ $show = false;
+ break;
+ }
+ }
+ }
+ if(!$show) continue;
+ if($branch['id'] > $this->max_branches) continue;
+ $data['l'][] = $branch['id'];
+ }
+ }
+ $data['m'] = array(); //merges
+ if($commit['merge']) {
+ foreach($commit['merge'] as $merge) {
+ $mergepoint = array();
+ $mergepoint['hl'] = array();
+ $mergepoint['p'] = $merge['point'];
+
+ if($commit['dot'] <= $this->max_branches)
+ $mergepoint['dd'] = ($commit['dot'] < $merge['point'] ? 'r' : 'l');
+ else
+ $mergepoint['dd'] = 'n';
+
+ $mergepoint['ml'] = ($merge['start'] ? 1 : 0) + ($merge['end'] ? 2 : 0);
+ if($merge['point'] <= $this->max_branches)
+ $mergepoint['md']=($commit['dot'] < $merge['point'] ? 'l' : 'r');
+ else
+ $mergepoint['md'] = 'n';
+ $min = ($commit['dot'] < $merge['point'] ? $commit['dot'] : $merge['point']) + 1;
+ $max = ($commit['dot'] < $merge['point'] ? $merge['point'] : $commit['dot']);
+ for($i = $min; $i < $max; $i++) {
+ if($i > $this->max_branches) continue;
+ $mergepoint['hl'][] = $i;
+ }
+ $data['m'][] = $mergepoint;
+ }
+ }
+ $data['d']['type'] = $commit['dot_type'];
+ return $data;
+ }
+
+ public function get_graph($id) {
+ $graph = $this->graph[$id];
+ $data = $graph['d']['p'].$graph['d']['type'].count($this->data['branches']).'('.implode(',', $graph['l']).')';
+ $first_merge = true;
+ foreach($graph['m'] as $merge) {
+ if(!$first_merge)
+ $data .= '|';
+ $first_merge = false;
+ $data.=$merge['p'].$merge['dd'].$merge['md'].$merge['ml'];
+ foreach($merge['hl'] as $hline)
+ $data.=','.$hline;
+ }
+ $graph['cs'] = array();
+ foreach($graph['br'] as $buid) {
+ $branch = $this->data['ubranches'][$buid];
+ if($branch['pre_merge'] && $branch['pre_merge_start'])
+ $graph['cs'][] = $branch['id']."=".$branch['pre_merge_id'];
+ }
+ if(count($graph['cs'])) {
+ $data .= '('.implode(',', $graph['cs']).')';
+ }
+ return $data;
+ }
+
+}
+
+class graph_image_generator {
+ private $max_branches = 10;
+ private $image;
+ private $size = 20;
+ private $tile_size = 20;
+ private $colors, $color_swap = array();
+
+ public function generate($data) {
+ $data = $this->parse_data($data);
+ if(!$data)
+ return;
+
+ $this->colors = array(
+ NULL,
+ array(255, 0, 0),
+ array(array(0, 255, 0), array(0, 192, 0)),
+ array(0, 0, 255),
+ array(128, 128, 128),
+ array(128, 128, 0),
+ array(0, 128, 128),
+ array(128, 0, 128)
+ );
+
+ $count = $data['count'];
+ if($count > $this->max_branches)
+ $count = $this->max_branches;
+ $this->image = imagecreatetruecolor($count * $this->size, $this->size);
+ $transparentIndex = imagecolorallocate($this->image, 0xFF, 0xFF, 0xFF);
+ imagefill($this->image, 0, 0, $transparentIndex);
+
+ $this->apply_data($data);
+
+ imagecolortransparent($this->image, $transparentIndex);
+
+ header('Content-Type: image/png');
+ imagepng($this->image);
+ imagedestroy($this->image);
+ }
+
+ private function parse_data($data) {
+ //$data = array();
+ if(!preg_match("/^([0-9]+)([abc]{1})([0-9]+)\(([^\)]*)\)([a-z0-9,\|]*)(\(([^\)]*)\)|)/i", $data, $matches))
+ return null;
+
+ $data = array();
+ $data['dot'] = array();
+ $data['dot']['pos'] = $matches[1];
+ $data['dot']['type'] = $matches[2];
+ $data['count'] = $matches[3];
+
+ if($matches[4] != '')
+ $data['l'] = explode(',', $matches[4]);
+ else
+ $data['l'] = array();
+
+ $data['m'] = array();
+ if($matches[5] != '') {
+ foreach(explode('|', $matches[5]) as $m) {
+ $merge = array();
+
+ if(!preg_match("/^([0-9]+)([rln]{1})([rln]{1})([0123]{1})(.*)/i", $m, $sm))
+ return null;
+ $merge['hl'] = array();
+ $merge['pos'] = $sm[1];
+ $merge['dd'] = $sm[2];
+ $merge['md'] = $sm[3];
+ $merge['ml'] = $sm[4];
+ if($sm[5] != '') {
+ $merge['hl'] = explode(',', $sm[5]);
+ }
+ $data['m'][] = $merge;
+ }
+ }
+ if($matches[6] != '' && $matches[7] != '') {
+ foreach(explode(',', $matches[7]) as $cswap) {
+ $cswap = explode("=", $cswap);
+ $this->color_swap[$cswap[0]] = $cswap[1];
+ }
+ }
+ return $data;
+ }
+
+ function image_set_color($src, $color) {
+ imagesavealpha($src, true);
+ imagealphablending($src, false);
+ // scan image pixels
+ for ($x = 0; $x < $this->size; $x++) {
+ for ($y = 0; $y < $this->size; $y++) {
+ $src_pix = imagecolorat($src,$x,$y);
+ $src_pix_array = imagecolorsforindex($src, $src_pix);
+
+ imagesetpixel($src, $x, $y, imagecolorallocatealpha($src, $color[0], $color[1], $color[2], $src_pix_array['alpha']));
+ }
+ }
+ }
+
+ function overlay_image($name, $left, $color = false) {
+ $image2 = imagecreatefrompng($name);
+
+ if($color) {
+ $this->image_set_color($image2, $color);
+ }
+ imagecopyresampled($this->image, $image2, $left, 0, 0, 0, $this->size, $this->size, $this->tile_size, $this->tile_size);
+ }
+
+ function get_color($id, $text = false) {
+ if(array_key_exists($id, $this->color_swap))
+ $id = $this->color_swap[$id];
+ $color_array = $this->colors[($id - 1) % count($this->colors)];
+ if($text && is_array($color_array[0]) && $color_array[1])
+ return $color_array[1];
+ return (is_array($color_array[0]) ? $color_array[0] : $color_array);
+ }
+
+ private function apply_data($data) {
+ foreach($data['l'] as $l)
+ $this->overlay_image("img/line.png", ($l-1) * $this->size, $this->get_color($l));
+ foreach($data['m'] as $m) {
+ if($m['dd'] == 'r')
+ $this->overlay_image("img/dot_merge_right.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($m['pos']));
+ else if($m['dd'] == 'l')
+ $this->overlay_image("img/dot_merge_left.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($m['pos']));
+ if($m['md'] == 'r')
+ $this->overlay_image("img/".(($m['ml'] & 1) ? "branch" : "merge")."_right.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos']));
+ else if($m['md'] == 'l')
+ $this->overlay_image("img/".(($m['ml'] & 1) ? "branch" : "merge")."_left.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos']));
+ if($m['ml'] == 0)
+ $this->overlay_image("img/line.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos']));
+ foreach($m['hl'] as $hl) {
+ $this->overlay_image("img/line_h.png", ($hl - 1) * $this->size, $this->get_color($m['pos']));
+ }
+ }
+ if($data['dot']['type'] == 'a')
+ $this->overlay_image("img/dot.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos']));
+ else if($data['dot']['type'] == 'b')
+ $this->overlay_image("img/dot_merge.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos']));
+ else if($data['dot']['type'] == 'c')
+ $this->overlay_image("img/dot_init.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos']));
+
+ }
+
+}
+
+?>
\ No newline at end of file