@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 403 lines 11 kB view raw
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}