@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 recaptime-dev/main 528 lines 15 kB view raw
1<?php 2 3final class DiffusionRepositoryController extends DiffusionController { 4 5 private $browseFuture; 6 private $branchButton = null; 7 private $branchFuture; 8 9 public function shouldAllowPublic() { 10 return true; 11 } 12 13 public function handleRequest(AphrontRequest $request) { 14 $response = $this->loadDiffusionContext(); 15 if ($response) { 16 return $response; 17 } 18 19 require_celerity_resource('diffusion-css'); 20 21 $viewer = $this->getViewer(); 22 $drequest = $this->getDiffusionRequest(); 23 $repository = $drequest->getRepository(); 24 25 $crumbs = $this->buildCrumbs(); 26 $crumbs->setBorder(true); 27 28 $header = $this->buildHeaderView($repository); 29 $actions = $this->buildActionList($repository); 30 $description = $this->buildDescriptionView($repository); 31 $locate_file = $this->buildLocateFile(); 32 33 // Before we do any work, make sure we're looking at a some content: we're 34 // on a valid branch, and the repository is not empty. 35 $page_has_content = false; 36 $empty_title = null; 37 $empty_message = null; 38 39 // If this VCS supports branches, check that the selected branch actually 40 // exists. 41 if ($drequest->supportsBranches()) { 42 // NOTE: Mercurial may have multiple branch heads with the same name. 43 $ref_cursors = id(new PhabricatorRepositoryRefCursorQuery()) 44 ->setViewer($viewer) 45 ->withRepositoryPHIDs(array($repository->getPHID())) 46 ->withRefTypes(array(PhabricatorRepositoryRefCursor::TYPE_BRANCH)) 47 ->withRefNames(array($drequest->getBranch())) 48 ->needPositions(true) 49 ->execute(); 50 51 // It's possible that this branch previously existed, but has been 52 // deleted. Make sure we have valid cursor positions, not just cursors. 53 $any_positions = false; 54 foreach ($ref_cursors as $ref_cursor) { 55 if ($ref_cursor->getPositions()) { 56 $any_positions = true; 57 break; 58 } 59 } 60 61 if ($any_positions) { 62 // This is a valid branch, so we necessarily have some content. 63 $page_has_content = true; 64 } else { 65 $default = $repository->getDefaultBranch(); 66 if ($default != $drequest->getBranch()) { 67 $empty_title = pht('No Such Branch'); 68 $empty_message = pht( 69 'There is no branch named "%s" in this repository.', 70 $drequest->getBranch()); 71 } else { 72 $empty_title = pht('No Default Branch'); 73 $empty_message = pht( 74 'This repository is configured with default branch "%s" but '. 75 'there is no branch with that name in this repository.', 76 $default); 77 $branch_setup_uri = $this->getApplicationURI( 78 'edit/'.$repository->getID().'/page/branches/'); 79 phlog('Repository "'.$repository->getName().'" is configured with '. 80 'default branch "'.$default.'" but there is no such branch in '. 81 'this repository. Correct the settings at '.$branch_setup_uri); 82 } 83 } 84 } 85 86 // If we didn't find any branches, check if there are any commits at all. 87 // This can tailor the message for empty repositories. 88 $any_commit = null; 89 if (!$page_has_content) { 90 $any_commit = id(new DiffusionCommitQuery()) 91 ->setViewer($viewer) 92 ->withRepository($repository) 93 ->setLimit(1) 94 ->execute(); 95 if ($any_commit) { 96 if (!$drequest->supportsBranches()) { 97 $page_has_content = true; 98 } 99 } else { 100 $empty_title = pht('Empty Repository'); 101 $empty_message = pht('This repository does not have any commits yet.'); 102 } 103 } 104 105 if ($page_has_content) { 106 $content = $this->buildNormalContent($drequest); 107 } else { 108 // If we have a commit somewhere, find branches. 109 // TODO: Evan will replace 110 // $this->buildNormalContent($drequest); 111 $content = id(new PHUIInfoView()) 112 ->setTitle($empty_title) 113 ->setSeverity(PHUIInfoView::SEVERITY_WARNING) 114 ->setErrors(array($empty_message)); 115 } 116 117 $tabs = $this->buildTabsView('home'); 118 119 $clone_uri = $drequest->generateURI( 120 array( 121 'action' => 'clone', 122 )); 123 124 if ($repository->isSVN()) { 125 $clone_text = pht('Checkout'); 126 } else { 127 $clone_text = pht('Clone'); 128 } 129 130 $actions_button = id(new PHUIButtonView()) 131 ->setTag('a') 132 ->setText(pht('Actions')) 133 ->setIcon('fa-bars') 134 ->addClass('mmr') 135 ->setColor(PHUIButtonView::GREY) 136 ->setDropdown(true) 137 ->setDropdownMenu($actions); 138 139 $clone_button = id(new PHUIButtonView()) 140 ->setTag('a') 141 ->setText($clone_text) 142 ->setColor(PHUIButtonView::GREEN) 143 ->setIcon('fa-download') 144 ->setWorkflow(true) 145 ->setHref($clone_uri); 146 147 $bar = id(new PHUILeftRightView()) 148 ->setLeft($locate_file) 149 ->setRight(array($this->branchButton, $actions_button, $clone_button)) 150 ->addClass('diffusion-action-bar'); 151 152 $status_view = null; 153 if ($repository->isReadOnly()) { 154 $status_view = id(new PHUIInfoView()) 155 ->setSeverity(PHUIInfoView::SEVERITY_WARNING) 156 ->setErrors( 157 array( 158 phutil_escape_html_newlines( 159 $repository->getReadOnlyMessageForDisplay()), 160 )); 161 } 162 163 $view = id(new PHUITwoColumnView()) 164 ->setHeader($header) 165 ->setFooter( 166 array( 167 $status_view, 168 $bar, 169 $description, 170 $content, 171 )); 172 173 if ($page_has_content) { 174 $view->setTabs($tabs); 175 } 176 177 return $this->newPage() 178 ->setTitle( 179 array( 180 $repository->getName(), 181 $repository->getDisplayName(), 182 )) 183 ->setCrumbs($crumbs) 184 ->appendChild(array( 185 $view, 186 )); 187 } 188 189 190 private function buildNormalContent(DiffusionRequest $drequest) { 191 $request = $this->getRequest(); 192 $repository = $drequest->getRepository(); 193 194 $commit = $drequest->getCommit(); 195 $path = $drequest->getPath(); 196 197 $futures = array(); 198 199 $browse_pager = id(new PHUIPagerView()) 200 ->readFromRequest($request); 201 202 $this->browseFuture = $this->callConduitMethod( 203 'diffusion.browsequery', 204 array( 205 'commit' => $commit, 206 'path' => $path, 207 'limit' => $browse_pager->getPageSize() + 1, 208 )); 209 $futures[] = $this->browseFuture; 210 211 if ($this->needBranchFuture()) { 212 $branch_limit = $this->getBranchLimit(); 213 $this->branchFuture = $this->callConduitMethod( 214 'diffusion.branchquery', 215 array( 216 'closed' => false, 217 'limit' => $branch_limit + 1, 218 )); 219 $futures[] = $this->branchFuture; 220 } 221 222 $futures = array_filter($futures); 223 $futures = new FutureIterator($futures); 224 foreach ($futures as $future) { 225 // Just resolve all the futures before continuing. 226 } 227 228 $content = array(); 229 230 try { 231 $browse_results = $this->browseFuture->resolve(); 232 $browse_results = DiffusionBrowseResultSet::newFromConduit( 233 $browse_results); 234 235 $browse_paths = $browse_results->getPaths(); 236 $browse_paths = $browse_pager->sliceResults($browse_paths); 237 238 $browse_exception = null; 239 } catch (Exception $ex) { 240 $browse_results = null; 241 $browse_paths = null; 242 $browse_exception = $ex; 243 } 244 245 if ($browse_results) { 246 $readme = $this->renderDirectoryReadme($browse_results); 247 } else { 248 $readme = null; 249 } 250 251 if ($readme) { 252 $content[] = $readme; 253 } 254 255 $content[] = $this->buildBrowseTable( 256 $browse_results, 257 $browse_paths, 258 $browse_exception, 259 $browse_pager); 260 261 try { 262 $branch_button = $this->buildBranchList($drequest); 263 $this->branchButton = $branch_button; 264 } catch (Exception $ex) { 265 if (!$repository->isImporting()) { 266 $content[] = $this->renderStatusMessage( 267 pht('Unable to Load Branches'), 268 $ex->getMessage()); 269 } 270 } 271 272 return $content; 273 } 274 275 private function buildHeaderView(PhabricatorRepository $repository) { 276 $viewer = $this->getViewer(); 277 $drequest = $this->getDiffusionRequest(); 278 $search = $this->renderSearchForm(); 279 280 $header = id(new PHUIHeaderView()) 281 ->setHeader($repository->getName()) 282 ->setViewer($viewer) 283 ->setPolicyObject($repository) 284 ->setProfileHeader(true) 285 ->setImage($repository->getProfileImageURI()) 286 ->setImageEditURL('/diffusion/picture/'.$repository->getID().'/') 287 ->addActionItem($search) 288 ->addClass('diffusion-profile-header'); 289 290 if (!$repository->isTracked()) { 291 $header->setStatus('fa-ban', 'dark', pht('Inactive')); 292 } else if ($repository->isReadOnly()) { 293 $header->setStatus('fa-wrench', 'indigo', pht('Under Maintenance')); 294 } else if ($repository->isImporting()) { 295 $ratio = $repository->loadImportProgress(); 296 $percentage = sprintf('%.2f%%', 100 * $ratio); 297 $header->setStatus( 298 'fa-clock-o', 299 'indigo', 300 pht('Importing (%s)...', $percentage)); 301 } else if ($repository->isPublishingDisabled()) { 302 $header->setStatus('fa-minus', 'bluegrey', pht('Publishing Disabled')); 303 } else { 304 $header->setStatus('fa-check', 'bluegrey', pht('Active')); 305 } 306 307 if (!$repository->isSVN()) { 308 $default = $repository->getDefaultBranch(); 309 if ($default != $drequest->getBranch()) { 310 $branch_tag = $this->renderBranchTag($drequest); 311 $header->addTag($branch_tag); 312 } 313 } 314 315 // Display associated projects as tags in repository browsing header 316 $project_phids = $repository->getProjectPHIDs(); 317 if ($project_phids) { 318 $handles = id(new PhabricatorHandleQuery()) 319 ->setViewer($viewer) 320 ->withPHIDs($project_phids) 321 ->execute(); 322 $project_handles = array_select_keys( 323 $handles, 324 $project_phids); 325 foreach ($project_handles as $handle) { 326 $header->addTag($handle->renderTag()); 327 } 328 } 329 330 return $header; 331 } 332 333 private function buildActionList(PhabricatorRepository $repository) { 334 $viewer = $this->getViewer(); 335 336 $edit_uri = $repository->getPathURI('manage/'); 337 $action_view = id(new PhabricatorActionListView()) 338 ->setViewer($viewer) 339 ->setObject($repository); 340 341 $action_view->addAction( 342 id(new PhabricatorActionView()) 343 ->setName(pht('Manage Repository')) 344 ->setIcon('fa-cogs') 345 ->setHref($edit_uri)); 346 347 if ($repository->isHosted()) { 348 $push_uri = $this->getApplicationURI( 349 'pushlog/?repositories='.$repository->getPHID()); 350 351 $action_view->addAction( 352 id(new PhabricatorActionView()) 353 ->setName(pht('View Push Logs')) 354 ->setIcon('fa-upload') 355 ->setHref($push_uri)); 356 357 $pull_uri = $this->getApplicationURI( 358 'synclog/?repositories='.$repository->getPHID()); 359 360 $action_view->addAction( 361 id(new PhabricatorActionView()) 362 ->setName(pht('View Sync Logs')) 363 ->setIcon('fa-exchange') 364 ->setHref($pull_uri)); 365 } 366 367 $pull_uri = $this->getApplicationURI( 368 'pulllog/?repositories='.$repository->getPHID()); 369 370 $action_view->addAction( 371 id(new PhabricatorActionView()) 372 ->setName(pht('View Pull Logs')) 373 ->setIcon('fa-download') 374 ->setHref($pull_uri)); 375 376 return $action_view; 377 } 378 379 private function buildDescriptionView(PhabricatorRepository $repository) { 380 $viewer = $this->getViewer(); 381 $view = id(new PHUIPropertyListView()) 382 ->setViewer($viewer); 383 384 $description = $repository->getDetail('description'); 385 if (strlen($description)) { 386 $description = new PHUIRemarkupView($viewer, $description); 387 $view->addTextContent($description); 388 return id(new PHUIObjectBoxView()) 389 ->appendChild($view) 390 ->addClass('diffusion-profile-description'); 391 } 392 return null; 393 } 394 395 private function buildBranchList(DiffusionRequest $drequest) { 396 $viewer = $this->getViewer(); 397 398 if (!$this->needBranchFuture()) { 399 return null; 400 } 401 402 $branches = $this->branchFuture->resolve(); 403 if (!$branches) { 404 return null; 405 } 406 407 $limit = $this->getBranchLimit(); 408 $more_branches = (count($branches) > $limit); 409 $branches = array_slice($branches, 0, $limit); 410 411 $branches = DiffusionRepositoryRef::loadAllFromDictionaries($branches); 412 413 $actions = id(new PhabricatorActionListView()) 414 ->setViewer($viewer); 415 416 foreach ($branches as $branch) { 417 $branch_uri = $drequest->generateURI( 418 array( 419 'action' => 'branch', 420 'branch' => $branch->getShortname(), 421 )); 422 $actions->addAction( 423 id(new PhabricatorActionView()) 424 ->setName($branch->getShortname()) 425 ->setIcon('fa-code-fork') 426 ->setHref($branch_uri)); 427 } 428 429 if ($more_branches) { 430 $more_uri = $drequest->generateURI( 431 array( 432 'action' => 'branches', 433 )); 434 $actions->addAction( 435 id(new PhabricatorActionView()) 436 ->setType(PhabricatorActionView::TYPE_DIVIDER)); 437 $actions->addAction( 438 id(new PhabricatorActionView()) 439 ->setName(pht('See More Branches')) 440 ->setIcon('fa-external-link') 441 ->setHref($more_uri)); 442 } 443 444 $button = id(new PHUIButtonView()) 445 ->setText(pht('Branch: %s', $drequest->getBranch())) 446 ->setTag('a') 447 ->addClass('mmr') 448 ->setIcon('fa-code-fork') 449 ->setColor(PHUIButtonView::GREY) 450 ->setDropdown(true) 451 ->setDropdownMenu($actions); 452 453 return $button; 454 } 455 456 private function buildBrowseTable( 457 $browse_results, 458 $browse_paths, 459 $browse_exception, 460 PHUIPagerView $pager) { 461 462 require_celerity_resource('diffusion-icons-css'); 463 464 $request = $this->getRequest(); 465 $viewer = $request->getUser(); 466 $drequest = $this->getDiffusionRequest(); 467 $repository = $drequest->getRepository(); 468 469 if ($browse_exception) { 470 if ($repository->isImporting()) { 471 // The history table renders a useful message. 472 return null; 473 } else { 474 return $this->renderStatusMessage( 475 pht('Unable to Retrieve Paths'), 476 $browse_exception->getMessage()); 477 } 478 } 479 480 $browse_table = id(new DiffusionBrowseTableView()) 481 ->setUser($viewer) 482 ->setDiffusionRequest($drequest); 483 if ($browse_paths) { 484 $browse_table->setPaths($browse_paths); 485 } else { 486 $browse_table->setPaths(array()); 487 } 488 489 $browse_uri = $drequest->generateURI(array('action' => 'browse')); 490 $pager->setURI($browse_uri, 'offset'); 491 492 $repository_name = $repository->getName(); 493 $branch_name = $drequest->getBranch(); 494 if (phutil_nonempty_string($branch_name)) { 495 $repository_name .= ' ('.$branch_name.')'; 496 } 497 498 $header = phutil_tag( 499 'a', 500 array( 501 'href' => $browse_uri, 502 'class' => 'diffusion-view-browse-header', 503 ), 504 $repository_name); 505 506 return id(new PHUIObjectBoxView()) 507 ->setHeaderText($header) 508 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 509 ->setTable($browse_table) 510 ->addClass('diffusion-mobile-view') 511 ->setPager($pager); 512 } 513 514 private function needBranchFuture() { 515 $drequest = $this->getDiffusionRequest(); 516 517 if ($drequest->getBranch() === null) { 518 return false; 519 } 520 521 return true; 522 } 523 524 private function getBranchLimit() { 525 return 15; 526 } 527 528}