@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 PhabricatorConfigConsoleController
4 extends PhabricatorConfigController {
5
6 public function handleRequest(AphrontRequest $request) {
7 $viewer = $request->getViewer();
8
9 $menu = id(new PHUIObjectItemListView())
10 ->setViewer($viewer)
11 ->setBig(true);
12
13 $menu->addItem(
14 id(new PHUIObjectItemView())
15 ->setHeader(pht('Settings'))
16 ->setHref($this->getApplicationURI('settings/'))
17 ->setImageIcon('fa-wrench')
18 ->setClickable(true)
19 ->addAttribute(
20 pht(
21 'Review and modify configuration settings.')));
22
23 $menu->addItem(
24 id(new PHUIObjectItemView())
25 ->setHeader(pht('Setup Issues'))
26 ->setHref($this->getApplicationURI('issue/'))
27 ->setImageIcon('fa-exclamation-triangle')
28 ->setClickable(true)
29 ->addAttribute(
30 pht(
31 'Show unresolved issues with setup and configuration.')));
32
33 $menu->addItem(
34 id(new PHUIObjectItemView())
35 ->setHeader(pht('Services'))
36 ->setHref($this->getApplicationURI('cluster/databases/'))
37 ->setImageIcon('fa-server')
38 ->setClickable(true)
39 ->addAttribute(
40 pht(
41 'View status information for databases, caches, repositories, '.
42 'and other services.')));
43
44 $menu->addItem(
45 id(new PHUIObjectItemView())
46 ->setHeader(pht('Extensions/Modules'))
47 ->setHref($this->getApplicationURI('module/'))
48 ->setImageIcon('fa-gear')
49 ->setClickable(true)
50 ->addAttribute(
51 pht(
52 'Show installed extensions and modules.')));
53
54 $crumbs = $this->buildApplicationCrumbs()
55 ->addTextCrumb(pht('Console'))
56 ->setBorder(true);
57
58 $box = id(new PHUIObjectBoxView())
59 ->setHeaderText(pht('Configuration'))
60 ->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
61 ->setObjectList($menu);
62
63 $versions = $this->newLibraryVersionTable();
64 $binary_versions = $this->newBinaryVersionTable();
65
66 $launcher_view = id(new PHUILauncherView())
67 ->appendChild($box)
68 ->appendChild($versions)
69 ->appendChild($binary_versions);
70
71 $view = id(new PHUITwoColumnView())
72 ->setFooter($launcher_view);
73
74 return $this->newPage()
75 ->setTitle(pht('Configuration'))
76 ->setCrumbs($crumbs)
77 ->appendChild($view);
78 }
79
80 public function newLibraryVersionTable() {
81 $viewer = $this->getViewer();
82
83 $versions = $this->loadVersions($viewer);
84
85 $rows = array();
86 foreach ($versions as $name => $info) {
87 $branchpoint = $info['branchpoint'];
88 if (phutil_nonempty_string($branchpoint)) {
89 $branchpoint = substr($branchpoint, 0, 12);
90 } else {
91 $branchpoint = null;
92 }
93
94 $version = $info['hash'];
95 if (phutil_nonempty_string($version)) {
96 $version = substr($version, 0, 12);
97 } else {
98 $version = pht('Unknown');
99 }
100
101
102 $epoch = $info['epoch'];
103 if ($epoch) {
104 $epoch = phabricator_date($epoch, $viewer);
105 } else {
106 $epoch = null;
107 }
108
109 $rows[] = array(
110 $name,
111 $version,
112 $epoch,
113 $branchpoint,
114 );
115 }
116
117 $table_view = id(new AphrontTableView($rows))
118 ->setHeaders(
119 array(
120 pht('Library'),
121 pht('Version'),
122 pht('Date'),
123 pht('Branchpoint'),
124 ))
125 ->setColumnClasses(
126 array(
127 'pri',
128 null,
129 null,
130 'wide',
131 ));
132
133 return id(new PHUIObjectBoxView())
134 ->setHeaderText(pht('Version Information'))
135 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
136 ->appendChild($table_view);
137 }
138
139 private function loadVersions(PhabricatorUser $viewer) {
140 $specs = array(
141 'phorge',
142 'arcanist',
143 );
144
145 $all_libraries = PhutilBootloader::getInstance()->getAllLibraries();
146 // This puts the core libraries at the top:
147 $other_libraries = array_diff($all_libraries, $specs);
148 $specs = array_merge($specs, $other_libraries);
149
150 $log_futures = array();
151 $remote_futures = array();
152
153 foreach ($specs as $lib) {
154 $root = dirname(phutil_get_library_root($lib));
155
156 if (Filesystem::isPharPath($root)) {
157 continue;
158 }
159
160 $log_command = csprintf(
161 'git log --format=%s -n 1 --',
162 '%H %ct');
163
164 $remote_command = csprintf(
165 'git remote -v');
166
167 $log_futures[$lib] = id(new ExecFuture('%C', $log_command))
168 ->setCWD($root);
169
170 $remote_futures[$lib] = id(new ExecFuture('%C', $remote_command))
171 ->setCWD($root);
172 }
173
174 $all_futures = array_merge($log_futures, $remote_futures);
175
176 id(new FutureIterator($all_futures))
177 ->resolveAll();
178
179 // A repository may have a bunch of remotes, but we're only going to look
180 // for remotes we host to try to figure out where this repository branched.
181 $upstream_pattern =
182 '('.
183 implode('|', array(
184 'we\.phorge\.it/',
185 'github\.com/phorgeit/',
186 'github\.com/phacility/',
187 'secure\.phabricator\.com/',
188 )).
189 ')';
190
191 $upstream_futures = array();
192 $lib_upstreams = array();
193 foreach ($specs as $lib) {
194 if (!array_key_exists($lib, $remote_futures)) {
195 continue;
196 }
197 $remote_future = $remote_futures[$lib];
198
199 try {
200 list($stdout, $err) = $remote_future->resolvex();
201 } catch (CommandException $e) {
202 $this->logGitErrorWithPotentialTips($e, $lib);
203 continue;
204 }
205
206 // These look like this, with a tab separating the first two fields:
207 // remote-name http://remote.uri/ (push)
208
209 $upstreams = array();
210
211 $remotes = phutil_split_lines($stdout, false);
212 foreach ($remotes as $remote) {
213 $remote_pattern = '/^([^\t]+)\t([^ ]+) \(([^)]+)\)\z/';
214 $matches = null;
215 if (!preg_match($remote_pattern, $remote, $matches)) {
216 continue;
217 }
218
219 // Remote URIs are either "push" or "fetch": we only care about "fetch"
220 // URIs.
221 $type = $matches[3];
222 if ($type != 'fetch') {
223 continue;
224 }
225
226 $uri = $matches[2];
227 $is_upstream = preg_match($upstream_pattern, $uri);
228 if (!$is_upstream) {
229 continue;
230 }
231
232 $name = $matches[1];
233 $upstreams[$name] = $name;
234 }
235
236 // If we have several suitable upstreams, try to pick the one named
237 // "origin", if it exists. Otherwise, just pick the first one.
238 if (isset($upstreams['origin'])) {
239 $upstream = $upstreams['origin'];
240 } else if ($upstreams) {
241 $upstream = head($upstreams);
242 } else {
243 $upstream = null;
244 }
245
246 if (!$upstream) {
247 continue;
248 }
249
250 $lib_upstreams[$lib] = $upstream;
251
252 $merge_base_command = csprintf(
253 'git merge-base HEAD %s/master --',
254 $upstream);
255
256 $root = dirname(phutil_get_library_root($lib));
257
258 $upstream_futures[$lib] = id(new ExecFuture('%C', $merge_base_command))
259 ->setCWD($root);
260 }
261
262 if ($upstream_futures) {
263 id(new FutureIterator($upstream_futures))
264 ->resolveAll();
265 }
266
267 $results = array();
268 foreach ($log_futures as $lib => $future) {
269 try {
270 list($stdout, $err) = $future->resolvex();
271 list($hash, $epoch) = explode(' ', $stdout);
272 } catch (CommandException $e) {
273 $hash = null;
274 $epoch = null;
275 $this->logGitErrorWithPotentialTips($e, $lib);
276 }
277
278 $result = array(
279 'hash' => $hash,
280 'epoch' => $epoch,
281 'upstream' => null,
282 'branchpoint' => null,
283 );
284
285 $upstream_future = idx($upstream_futures, $lib);
286 if ($upstream_future) {
287 list($stdout, $err) = $upstream_future->resolvex();
288 if (!$err) {
289 $branchpoint = trim($stdout);
290 if (strlen($branchpoint)) {
291 // We only list a branchpoint if it differs from HEAD.
292 if ($branchpoint != $hash) {
293 $result['upstream'] = $lib_upstreams[$lib];
294 $result['branchpoint'] = trim($stdout);
295 }
296 }
297 }
298 }
299
300 $results[$lib] = $result;
301 }
302
303 foreach ($specs as $lib) {
304 if (!array_key_exists($lib, $results)) {
305 $results[$lib] = array(
306 'hash' => null,
307 'epoch' => null,
308 'upstream' => null,
309 'branchpoint' => null,
310 );
311 }
312 }
313
314 return $results;
315 }
316
317 private function newBinaryVersionTable() {
318 $rows = array();
319
320 $rows[] = array(
321 'php',
322 phpversion(),
323 php_sapi_name(),
324 );
325
326 $binaries = PhutilBinaryAnalyzer::getAllBinaries();
327 foreach ($binaries as $binary) {
328 if (!$binary->isBinaryAvailable()) {
329 $binary_version = pht('Not Available');
330 $binary_path = null;
331 } else {
332 $binary_version = $binary->getBinaryVersion();
333 $binary_path = $binary->getBinaryPath();
334 }
335
336 $rows[] = array(
337 $binary->getBinaryName(),
338 $binary_version,
339 $binary_path,
340 );
341 }
342
343 $table_view = id(new AphrontTableView($rows))
344 ->setHeaders(
345 array(
346 pht('Binary'),
347 pht('Version'),
348 pht('Path'),
349 ))
350 ->setColumnClasses(
351 array(
352 'pri',
353 null,
354 'wide',
355 ));
356
357 return id(new PHUIObjectBoxView())
358 ->setHeaderText(pht('Other Version Information'))
359 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
360 ->appendChild($table_view);
361 }
362
363 /**
364 * Help in better troubleshooting git errors.
365 * @param CommandException $e Exception
366 * @param string $lib Library name involved
367 */
368 private function logGitErrorWithPotentialTips($e, $lib) {
369
370 // First, detect this specific error message related to [safe] stuff.
371 $expected_error_msg_part = 'detected dubious ownership in repository';
372 $stderr = $e->getStderr();
373 if (strpos($stderr, $expected_error_msg_part) !== false) {
374
375 // Found! Let's show a nice resolution tip.
376
377 // Complete path of the problematic repository.
378 $lib_root = dirname(phutil_get_library_root($lib));
379
380 phlog(pht(
381 "Cannot identify the version of the %s repository because ".
382 "the webserver does not trust it (more info on Task %s).\n".
383 "Try this system resolution:\n".
384 "sudo git config --system --add safe.directory %s",
385 $lib,
386 'https://we.phorge.it/T15282',
387 $lib_root));
388 return;
389 }
390
391 // Second, detect if .git does not exist
392 // https://we.phorge.it/T16023
393 $expected_error_msg_part = 'fatal: not a git repository '.
394 '(or any of the parent directories): .git';
395 if (strpos($stderr, $expected_error_msg_part) !== false) {
396 return;
397 }
398
399 // Otherwise show a generic error message
400 phlog($e);
401 }
402
403}