@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
at upstream/main 227 lines 6.2 kB view raw
1<?php 2 3final class PHUIDiffGraphView extends Phobject { 4 5 private $isHead = true; 6 private $isTail = true; 7 private $height; 8 9 public function setIsHead($is_head) { 10 $this->isHead = $is_head; 11 return $this; 12 } 13 14 public function getIsHead() { 15 return $this->isHead; 16 } 17 18 public function setIsTail($is_tail) { 19 $this->isTail = $is_tail; 20 return $this; 21 } 22 23 public function getIsTail() { 24 return $this->isTail; 25 } 26 27 public function setHeight($height) { 28 $this->height = $height; 29 return $this; 30 } 31 32 public function getHeight() { 33 return $this->height; 34 } 35 36 public function renderRawGraph(array $parents) { 37 // This keeps our accumulated information about each line of the 38 // merge/branch graph. 39 $graph = array(); 40 41 // This holds the next commit we're looking for in each column of the 42 // graph. 43 $threads = array(); 44 45 // This is the largest number of columns any row has, i.e. the width of 46 // the graph. 47 $count = 0; 48 49 foreach ($parents as $cursor => $parent_list) { 50 $joins = array(); 51 $splits = array(); 52 53 // Look for some thread which has this commit as the next commit. If 54 // we find one, this commit goes on that thread. Otherwise, this commit 55 // goes on a new thread. 56 57 $line = ''; 58 $found = false; 59 $pos = count($threads); 60 61 $thread_count = $pos; 62 for ($n = 0; $n < $thread_count; $n++) { 63 if (empty($threads[$n])) { 64 $line .= ' '; 65 continue; 66 } 67 68 if ($threads[$n] == $cursor) { 69 if ($found) { 70 $line .= ' '; 71 $joins[] = $n; 72 $threads[$n] = false; 73 } else { 74 $line .= 'o'; 75 $found = true; 76 $pos = $n; 77 } 78 } else { 79 80 // We render a "|" for any threads which have a commit that we haven't 81 // seen yet, this is later drawn as a vertical line. 82 $line .= '|'; 83 } 84 } 85 86 // If we didn't find the thread this commit goes on, start a new thread. 87 // We use "o" to mark the commit for the rendering engine, or "^" to 88 // indicate that there's nothing after it so the line from the commit 89 // upward should not be drawn. 90 91 if (!$found) { 92 if ($this->getIsHead()) { 93 $line .= '^'; 94 } else { 95 $line .= 'o'; 96 foreach ($graph as $k => $meta) { 97 // Go back across all the lines we've already drawn and add a 98 // "|" to the end, since this is connected to some future commit 99 // we don't know about. 100 for ($jj = strlen($meta['line']); $jj <= $count; $jj++) { 101 $graph[$k]['line'] .= '|'; 102 } 103 } 104 } 105 } 106 107 // Update the next commit on this thread to the commit's first parent. 108 // This might have the effect of making a new thread. 109 $threads[$pos] = head($parent_list); 110 111 // If we made a new thread, increase the thread count. 112 $count = max($pos + 1, $count); 113 114 // Now, deal with splits (merges). I picked this terms opposite to the 115 // underlying repository term to confuse you. 116 foreach (array_slice($parent_list, 1) as $parent) { 117 $found = false; 118 119 // Try to find the other parent(s) in our existing threads. If we find 120 // them, split to that thread. 121 122 foreach ($threads as $idx => $thread_commit) { 123 if ($thread_commit == $parent) { 124 $found = true; 125 $splits[] = $idx; 126 break; 127 } 128 } 129 130 // If we didn't find the parent, we don't know about it yet. Find the 131 // first free thread and add it as the "next" commit in that thread. 132 // This might create a new thread. 133 134 if (!$found) { 135 for ($n = 0; $n < $count; $n++) { 136 if (empty($threads[$n])) { 137 break; 138 } 139 } 140 $threads[$n] = $parent; 141 $splits[] = $n; 142 $count = max($n + 1, $count); 143 } 144 } 145 146 $graph[] = array( 147 'line' => $line, 148 'split' => $splits, 149 'join' => $joins, 150 ); 151 } 152 153 // If this is the last page in history, replace any "o" characters at the 154 // bottom of columns with "x" characters so we do not draw a connecting 155 // line downward, and replace "^" with an "X" for repositories with 156 // exactly one commit. 157 if ($this->getIsTail() && $graph) { 158 $terminated = array(); 159 foreach (array_reverse(array_keys($graph)) as $key) { 160 $line = $graph[$key]['line']; 161 $len = strlen($line); 162 for ($ii = 0; $ii < $len; $ii++) { 163 $c = $line[$ii]; 164 if ($c == 'o') { 165 // If we've already terminated this thread, we don't need to add 166 // a terminator. 167 if (isset($terminated[$ii])) { 168 continue; 169 } 170 171 $terminated[$ii] = true; 172 173 // If this thread is joining some other node here, we don't want 174 // to terminate it. 175 if (isset($graph[$key + 1])) { 176 $joins = $graph[$key + 1]['join']; 177 if (in_array($ii, $joins)) { 178 continue; 179 } 180 } 181 182 $graph[$key]['line'][$ii] = 'x'; 183 } else if ($c != ' ') { 184 $terminated[$ii] = true; 185 } else { 186 unset($terminated[$ii]); 187 } 188 } 189 } 190 191 $last = array_pop($graph); 192 $last['line'] = str_replace('^', 'X', $last['line']); 193 $graph[] = $last; 194 } 195 196 return array($graph, $count); 197 } 198 199 /** 200 * @return array<PhutilSafeHTML> 201 */ 202 public function renderGraph(array $parents) { 203 list($graph, $count) = $this->renderRawGraph($parents); 204 205 // Render into tags for the behavior. 206 207 foreach ($graph as $k => $meta) { 208 $graph[$k] = javelin_tag( 209 'div', 210 array( 211 'sigil' => 'commit-graph', 212 'meta' => $meta, 213 ), 214 ''); 215 } 216 217 Javelin::initBehavior( 218 'diffusion-commit-graph', 219 array( 220 'count' => $count, 221 'height' => $this->getHeight(), 222 )); 223 224 return $graph; 225 } 226 227}