@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
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}