isHead = $is_head; return $this; } public function getIsHead() { return $this->isHead; } public function setIsTail($is_tail) { $this->isTail = $is_tail; return $this; } public function getIsTail() { return $this->isTail; } public function setHeight($height) { $this->height = $height; return $this; } public function getHeight() { return $this->height; } public function renderRawGraph(array $parents) { // This keeps our accumulated information about each line of the // merge/branch graph. $graph = array(); // This holds the next commit we're looking for in each column of the // graph. $threads = array(); // This is the largest number of columns any row has, i.e. the width of // the graph. $count = 0; foreach ($parents as $cursor => $parent_list) { $joins = array(); $splits = array(); // Look for some thread which has this commit as the next commit. If // we find one, this commit goes on that thread. Otherwise, this commit // goes on a new thread. $line = ''; $found = false; $pos = count($threads); $thread_count = $pos; for ($n = 0; $n < $thread_count; $n++) { if (empty($threads[$n])) { $line .= ' '; continue; } if ($threads[$n] == $cursor) { if ($found) { $line .= ' '; $joins[] = $n; $threads[$n] = false; } else { $line .= 'o'; $found = true; $pos = $n; } } else { // We render a "|" for any threads which have a commit that we haven't // seen yet, this is later drawn as a vertical line. $line .= '|'; } } // If we didn't find the thread this commit goes on, start a new thread. // We use "o" to mark the commit for the rendering engine, or "^" to // indicate that there's nothing after it so the line from the commit // upward should not be drawn. if (!$found) { if ($this->getIsHead()) { $line .= '^'; } else { $line .= 'o'; foreach ($graph as $k => $meta) { // Go back across all the lines we've already drawn and add a // "|" to the end, since this is connected to some future commit // we don't know about. for ($jj = strlen($meta['line']); $jj <= $count; $jj++) { $graph[$k]['line'] .= '|'; } } } } // Update the next commit on this thread to the commit's first parent. // This might have the effect of making a new thread. $threads[$pos] = head($parent_list); // If we made a new thread, increase the thread count. $count = max($pos + 1, $count); // Now, deal with splits (merges). I picked this terms opposite to the // underlying repository term to confuse you. foreach (array_slice($parent_list, 1) as $parent) { $found = false; // Try to find the other parent(s) in our existing threads. If we find // them, split to that thread. foreach ($threads as $idx => $thread_commit) { if ($thread_commit == $parent) { $found = true; $splits[] = $idx; break; } } // If we didn't find the parent, we don't know about it yet. Find the // first free thread and add it as the "next" commit in that thread. // This might create a new thread. if (!$found) { for ($n = 0; $n < $count; $n++) { if (empty($threads[$n])) { break; } } $threads[$n] = $parent; $splits[] = $n; $count = max($n + 1, $count); } } $graph[] = array( 'line' => $line, 'split' => $splits, 'join' => $joins, ); } // If this is the last page in history, replace any "o" characters at the // bottom of columns with "x" characters so we do not draw a connecting // line downward, and replace "^" with an "X" for repositories with // exactly one commit. if ($this->getIsTail() && $graph) { $terminated = array(); foreach (array_reverse(array_keys($graph)) as $key) { $line = $graph[$key]['line']; $len = strlen($line); for ($ii = 0; $ii < $len; $ii++) { $c = $line[$ii]; if ($c == 'o') { // If we've already terminated this thread, we don't need to add // a terminator. if (isset($terminated[$ii])) { continue; } $terminated[$ii] = true; // If this thread is joining some other node here, we don't want // to terminate it. if (isset($graph[$key + 1])) { $joins = $graph[$key + 1]['join']; if (in_array($ii, $joins)) { continue; } } $graph[$key]['line'][$ii] = 'x'; } else if ($c != ' ') { $terminated[$ii] = true; } else { unset($terminated[$ii]); } } } $last = array_pop($graph); $last['line'] = str_replace('^', 'X', $last['line']); $graph[] = $last; } return array($graph, $count); } /** * @return array */ public function renderGraph(array $parents) { list($graph, $count) = $this->renderRawGraph($parents); // Render into tags for the behavior. foreach ($graph as $k => $meta) { $graph[$k] = javelin_tag( 'div', array( 'sigil' => 'commit-graph', 'meta' => $meta, ), ''); } Javelin::initBehavior( 'diffusion-commit-graph', array( 'count' => $count, 'height' => $this->getHeight(), )); return $graph; } }