[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @task uri Repository URI Management 5 * @task autoclose Autoclose 6 */ 7 final class PhabricatorRepository extends PhabricatorRepositoryDAO 8 implements 9 PhabricatorPolicyInterface, 10 PhabricatorFlaggableInterface, 11 PhabricatorMarkupInterface, 12 PhabricatorDestructibleInterface, 13 PhabricatorProjectInterface { 14 15 /** 16 * Shortest hash we'll recognize in raw "a829f32" form. 17 */ 18 const MINIMUM_UNQUALIFIED_HASH = 7; 19 20 /** 21 * Shortest hash we'll recognize in qualified "rXab7ef2f8" form. 22 */ 23 const MINIMUM_QUALIFIED_HASH = 5; 24 25 const TABLE_PATH = 'repository_path'; 26 const TABLE_PATHCHANGE = 'repository_pathchange'; 27 const TABLE_FILESYSTEM = 'repository_filesystem'; 28 const TABLE_SUMMARY = 'repository_summary'; 29 const TABLE_BADCOMMIT = 'repository_badcommit'; 30 const TABLE_LINTMESSAGE = 'repository_lintmessage'; 31 const TABLE_PARENTS = 'repository_parents'; 32 const TABLE_COVERAGE = 'repository_coverage'; 33 34 const SERVE_OFF = 'off'; 35 const SERVE_READONLY = 'readonly'; 36 const SERVE_READWRITE = 'readwrite'; 37 38 const BECAUSE_REPOSITORY_IMPORTING = 'auto/importing'; 39 const BECAUSE_AUTOCLOSE_DISABLED = 'auto/disabled'; 40 const BECAUSE_NOT_ON_AUTOCLOSE_BRANCH = 'auto/nobranch'; 41 const BECAUSE_BRANCH_UNTRACKED = 'auto/notrack'; 42 const BECAUSE_BRANCH_NOT_AUTOCLOSE = 'auto/noclose'; 43 44 protected $name; 45 protected $callsign; 46 protected $uuid; 47 protected $viewPolicy; 48 protected $editPolicy; 49 protected $pushPolicy; 50 51 protected $versionControlSystem; 52 protected $details = array(); 53 protected $credentialPHID; 54 55 private $commitCount = self::ATTACHABLE; 56 private $mostRecentCommit = self::ATTACHABLE; 57 private $projectPHIDs = self::ATTACHABLE; 58 59 public static function initializeNewRepository(PhabricatorUser $actor) { 60 $app = id(new PhabricatorApplicationQuery()) 61 ->setViewer($actor) 62 ->withClasses(array('PhabricatorDiffusionApplication')) 63 ->executeOne(); 64 65 $view_policy = $app->getPolicy(DiffusionDefaultViewCapability::CAPABILITY); 66 $edit_policy = $app->getPolicy(DiffusionDefaultEditCapability::CAPABILITY); 67 $push_policy = $app->getPolicy(DiffusionDefaultPushCapability::CAPABILITY); 68 69 return id(new PhabricatorRepository()) 70 ->setViewPolicy($view_policy) 71 ->setEditPolicy($edit_policy) 72 ->setPushPolicy($push_policy); 73 } 74 75 public function getConfiguration() { 76 return array( 77 self::CONFIG_AUX_PHID => true, 78 self::CONFIG_SERIALIZATION => array( 79 'details' => self::SERIALIZATION_JSON, 80 ), 81 self::CONFIG_COLUMN_SCHEMA => array( 82 'name' => 'sort255', 83 'callsign' => 'sort32', 84 'versionControlSystem' => 'text32', 85 'uuid' => 'text64?', 86 'pushPolicy' => 'policy', 87 'credentialPHID' => 'phid?', 88 ), 89 self::CONFIG_KEY_SCHEMA => array( 90 'key_phid' => null, 91 'phid' => array( 92 'columns' => array('phid'), 93 'unique' => true, 94 ), 95 'callsign' => array( 96 'columns' => array('callsign'), 97 'unique' => true, 98 ), 99 'key_name' => array( 100 'columns' => array('name(128)'), 101 ), 102 'key_vcs' => array( 103 'columns' => array('versionControlSystem'), 104 ), 105 ), 106 ) + parent::getConfiguration(); 107 } 108 109 public function generatePHID() { 110 return PhabricatorPHID::generateNewPHID( 111 PhabricatorRepositoryRepositoryPHIDType::TYPECONST); 112 } 113 114 public function toDictionary() { 115 return array( 116 'id' => $this->getID(), 117 'name' => $this->getName(), 118 'phid' => $this->getPHID(), 119 'callsign' => $this->getCallsign(), 120 'monogram' => $this->getMonogram(), 121 'vcs' => $this->getVersionControlSystem(), 122 'uri' => PhabricatorEnv::getProductionURI($this->getURI()), 123 'remoteURI' => (string)$this->getRemoteURI(), 124 'description' => $this->getDetail('description'), 125 'isActive' => $this->isTracked(), 126 'isHosted' => $this->isHosted(), 127 'isImporting' => $this->isImporting(), 128 ); 129 } 130 131 public function getMonogram() { 132 return 'r'.$this->getCallsign(); 133 } 134 135 public function getDetail($key, $default = null) { 136 return idx($this->details, $key, $default); 137 } 138 139 public function getHumanReadableDetail($key, $default = null) { 140 $value = $this->getDetail($key, $default); 141 142 switch ($key) { 143 case 'branch-filter': 144 case 'close-commits-filter': 145 $value = array_keys($value); 146 $value = implode(', ', $value); 147 break; 148 } 149 150 return $value; 151 } 152 153 public function setDetail($key, $value) { 154 $this->details[$key] = $value; 155 return $this; 156 } 157 158 public function attachCommitCount($count) { 159 $this->commitCount = $count; 160 return $this; 161 } 162 163 public function getCommitCount() { 164 return $this->assertAttached($this->commitCount); 165 } 166 167 public function attachMostRecentCommit( 168 PhabricatorRepositoryCommit $commit = null) { 169 $this->mostRecentCommit = $commit; 170 return $this; 171 } 172 173 public function getMostRecentCommit() { 174 return $this->assertAttached($this->mostRecentCommit); 175 } 176 177 public function getDiffusionBrowseURIForPath( 178 PhabricatorUser $user, 179 $path, 180 $line = null, 181 $branch = null) { 182 183 $drequest = DiffusionRequest::newFromDictionary( 184 array( 185 'user' => $user, 186 'repository' => $this, 187 'path' => $path, 188 'branch' => $branch, 189 )); 190 191 return $drequest->generateURI( 192 array( 193 'action' => 'browse', 194 'line' => $line, 195 )); 196 } 197 198 public function getLocalPath() { 199 return $this->getDetail('local-path'); 200 } 201 202 public function getSubversionBaseURI($commit = null) { 203 $subpath = $this->getDetail('svn-subpath'); 204 if (!strlen($subpath)) { 205 $subpath = null; 206 } 207 return $this->getSubversionPathURI($subpath, $commit); 208 } 209 210 public function getSubversionPathURI($path = null, $commit = null) { 211 $vcs = $this->getVersionControlSystem(); 212 if ($vcs != PhabricatorRepositoryType::REPOSITORY_TYPE_SVN) { 213 throw new Exception('Not a subversion repository!'); 214 } 215 216 if ($this->isHosted()) { 217 $uri = 'file://'.$this->getLocalPath(); 218 } else { 219 $uri = $this->getDetail('remote-uri'); 220 } 221 222 $uri = rtrim($uri, '/'); 223 224 if (strlen($path)) { 225 $path = rawurlencode($path); 226 $path = str_replace('%2F', '/', $path); 227 $uri = $uri.'/'.ltrim($path, '/'); 228 } 229 230 if ($path !== null || $commit !== null) { 231 $uri .= '@'; 232 } 233 234 if ($commit !== null) { 235 $uri .= $commit; 236 } 237 238 return $uri; 239 } 240 241 public function attachProjectPHIDs(array $project_phids) { 242 $this->projectPHIDs = $project_phids; 243 return $this; 244 } 245 246 public function getProjectPHIDs() { 247 return $this->assertAttached($this->projectPHIDs); 248 } 249 250 251 /** 252 * Get the name of the directory this repository should clone or checkout 253 * into. For example, if the repository name is "Example Repository", a 254 * reasonable name might be "example-repository". This is used to help users 255 * get reasonable results when cloning repositories, since they generally do 256 * not want to clone into directories called "X/" or "Example Repository/". 257 * 258 * @return string 259 */ 260 public function getCloneName() { 261 $name = $this->getDetail('clone-name'); 262 263 // Make some reasonable effort to produce reasonable default directory 264 // names from repository names. 265 if (!strlen($name)) { 266 $name = $this->getName(); 267 $name = phutil_utf8_strtolower($name); 268 $name = preg_replace('@[/ -:]+@', '-', $name); 269 $name = trim($name, '-'); 270 if (!strlen($name)) { 271 $name = $this->getCallsign(); 272 } 273 } 274 275 return $name; 276 } 277 278 279 /* -( Remote Command Execution )------------------------------------------- */ 280 281 282 public function execRemoteCommand($pattern /* , $arg, ... */) { 283 $args = func_get_args(); 284 return $this->newRemoteCommandFuture($args)->resolve(); 285 } 286 287 public function execxRemoteCommand($pattern /* , $arg, ... */) { 288 $args = func_get_args(); 289 return $this->newRemoteCommandFuture($args)->resolvex(); 290 } 291 292 public function getRemoteCommandFuture($pattern /* , $arg, ... */) { 293 $args = func_get_args(); 294 return $this->newRemoteCommandFuture($args); 295 } 296 297 public function passthruRemoteCommand($pattern /* , $arg, ... */) { 298 $args = func_get_args(); 299 return $this->newRemoteCommandPassthru($args)->execute(); 300 } 301 302 private function newRemoteCommandFuture(array $argv) { 303 $argv = $this->formatRemoteCommand($argv); 304 $future = newv('ExecFuture', $argv); 305 $future->setEnv($this->getRemoteCommandEnvironment()); 306 return $future; 307 } 308 309 private function newRemoteCommandPassthru(array $argv) { 310 $argv = $this->formatRemoteCommand($argv); 311 $passthru = newv('PhutilExecPassthru', $argv); 312 $passthru->setEnv($this->getRemoteCommandEnvironment()); 313 return $passthru; 314 } 315 316 317 /* -( Local Command Execution )-------------------------------------------- */ 318 319 320 public function execLocalCommand($pattern /* , $arg, ... */) { 321 $args = func_get_args(); 322 return $this->newLocalCommandFuture($args)->resolve(); 323 } 324 325 public function execxLocalCommand($pattern /* , $arg, ... */) { 326 $args = func_get_args(); 327 return $this->newLocalCommandFuture($args)->resolvex(); 328 } 329 330 public function getLocalCommandFuture($pattern /* , $arg, ... */) { 331 $args = func_get_args(); 332 return $this->newLocalCommandFuture($args); 333 } 334 335 public function passthruLocalCommand($pattern /* , $arg, ... */) { 336 $args = func_get_args(); 337 return $this->newLocalCommandPassthru($args)->execute(); 338 } 339 340 private function newLocalCommandFuture(array $argv) { 341 $this->assertLocalExists(); 342 343 $argv = $this->formatLocalCommand($argv); 344 $future = newv('ExecFuture', $argv); 345 $future->setEnv($this->getLocalCommandEnvironment()); 346 347 if ($this->usesLocalWorkingCopy()) { 348 $future->setCWD($this->getLocalPath()); 349 } 350 351 return $future; 352 } 353 354 private function newLocalCommandPassthru(array $argv) { 355 $this->assertLocalExists(); 356 357 $argv = $this->formatLocalCommand($argv); 358 $future = newv('PhutilExecPassthru', $argv); 359 $future->setEnv($this->getLocalCommandEnvironment()); 360 361 if ($this->usesLocalWorkingCopy()) { 362 $future->setCWD($this->getLocalPath()); 363 } 364 365 return $future; 366 } 367 368 369 /* -( Command Infrastructure )--------------------------------------------- */ 370 371 372 private function getSSHWrapper() { 373 $root = dirname(phutil_get_library_root('phabricator')); 374 return $root.'/bin/ssh-connect'; 375 } 376 377 private function getCommonCommandEnvironment() { 378 $env = array( 379 // NOTE: Force the language to "en_US.UTF-8", which overrides locale 380 // settings. This makes stuff print in English instead of, e.g., French, 381 // so we can parse the output of some commands, error messages, etc. 382 'LANG' => 'en_US.UTF-8', 383 384 // Propagate PHABRICATOR_ENV explicitly. For discussion, see T4155. 385 'PHABRICATOR_ENV' => PhabricatorEnv::getSelectedEnvironmentName(), 386 ); 387 388 switch ($this->getVersionControlSystem()) { 389 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 390 break; 391 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 392 // NOTE: See T2965. Some time after Git 1.7.5.4, Git started fataling if 393 // it can not read $HOME. For many users, $HOME points at /root (this 394 // seems to be a default result of Apache setup). Instead, explicitly 395 // point $HOME at a readable, empty directory so that Git looks for the 396 // config file it's after, fails to locate it, and moves on. This is 397 // really silly, but seems like the least damaging approach to 398 // mitigating the issue. 399 400 $root = dirname(phutil_get_library_root('phabricator')); 401 $env['HOME'] = $root.'/support/empty/'; 402 break; 403 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 404 // NOTE: This overrides certain configuration, extensions, and settings 405 // which make Mercurial commands do random unusual things. 406 $env['HGPLAIN'] = 1; 407 break; 408 default: 409 throw new Exception('Unrecognized version control system.'); 410 } 411 412 return $env; 413 } 414 415 private function getLocalCommandEnvironment() { 416 return $this->getCommonCommandEnvironment(); 417 } 418 419 private function getRemoteCommandEnvironment() { 420 $env = $this->getCommonCommandEnvironment(); 421 422 if ($this->shouldUseSSH()) { 423 // NOTE: This is read by `bin/ssh-connect`, and tells it which credentials 424 // to use. 425 $env['PHABRICATOR_CREDENTIAL'] = $this->getCredentialPHID(); 426 switch ($this->getVersionControlSystem()) { 427 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 428 // Force SVN to use `bin/ssh-connect`. 429 $env['SVN_SSH'] = $this->getSSHWrapper(); 430 break; 431 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 432 // Force Git to use `bin/ssh-connect`. 433 $env['GIT_SSH'] = $this->getSSHWrapper(); 434 break; 435 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 436 // We force Mercurial through `bin/ssh-connect` too, but it uses a 437 // command-line flag instead of an environmental variable. 438 break; 439 default: 440 throw new Exception('Unrecognized version control system.'); 441 } 442 } 443 444 return $env; 445 } 446 447 private function formatRemoteCommand(array $args) { 448 $pattern = $args[0]; 449 $args = array_slice($args, 1); 450 451 switch ($this->getVersionControlSystem()) { 452 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 453 if ($this->shouldUseHTTP() || $this->shouldUseSVNProtocol()) { 454 $flags = array(); 455 $flag_args = array(); 456 $flags[] = '--non-interactive'; 457 $flags[] = '--no-auth-cache'; 458 if ($this->shouldUseHTTP()) { 459 $flags[] = '--trust-server-cert'; 460 } 461 462 $credential_phid = $this->getCredentialPHID(); 463 if ($credential_phid) { 464 $key = PassphrasePasswordKey::loadFromPHID( 465 $credential_phid, 466 PhabricatorUser::getOmnipotentUser()); 467 $flags[] = '--username %P'; 468 $flags[] = '--password %P'; 469 $flag_args[] = $key->getUsernameEnvelope(); 470 $flag_args[] = $key->getPasswordEnvelope(); 471 } 472 473 $flags = implode(' ', $flags); 474 $pattern = "svn {$flags} {$pattern}"; 475 $args = array_mergev(array($flag_args, $args)); 476 } else { 477 $pattern = "svn --non-interactive {$pattern}"; 478 } 479 break; 480 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 481 $pattern = "git {$pattern}"; 482 break; 483 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 484 if ($this->shouldUseSSH()) { 485 $pattern = "hg --config ui.ssh=%s {$pattern}"; 486 array_unshift( 487 $args, 488 $this->getSSHWrapper()); 489 } else { 490 $pattern = "hg {$pattern}"; 491 } 492 break; 493 default: 494 throw new Exception('Unrecognized version control system.'); 495 } 496 497 array_unshift($args, $pattern); 498 499 return $args; 500 } 501 502 private function formatLocalCommand(array $args) { 503 $pattern = $args[0]; 504 $args = array_slice($args, 1); 505 506 switch ($this->getVersionControlSystem()) { 507 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 508 $pattern = "svn --non-interactive {$pattern}"; 509 break; 510 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 511 $pattern = "git {$pattern}"; 512 break; 513 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 514 $pattern = "hg {$pattern}"; 515 break; 516 default: 517 throw new Exception('Unrecognized version control system.'); 518 } 519 520 array_unshift($args, $pattern); 521 522 return $args; 523 } 524 525 /** 526 * Sanitize output of an `hg` command invoked with the `--debug` flag to make 527 * it usable. 528 * 529 * @param string Output from `hg --debug ...` 530 * @return string Usable output. 531 */ 532 public static function filterMercurialDebugOutput($stdout) { 533 // When hg commands are run with `--debug` and some config file isn't 534 // trusted, Mercurial prints out a warning to stdout, twice, after Feb 2011. 535 // 536 // http://selenic.com/pipermail/mercurial-devel/2011-February/028541.html 537 538 $lines = preg_split('/(?<=\n)/', $stdout); 539 $regex = '/ignoring untrusted configuration option .*\n$/'; 540 541 foreach ($lines as $key => $line) { 542 $lines[$key] = preg_replace($regex, '', $line); 543 } 544 545 return implode('', $lines); 546 } 547 548 public function getURI() { 549 return '/diffusion/'.$this->getCallsign().'/'; 550 } 551 552 public function getNormalizedPath() { 553 $uri = (string)$this->getCloneURIObject(); 554 555 switch ($this->getVersionControlSystem()) { 556 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 557 $normalized_uri = new PhabricatorRepositoryURINormalizer( 558 PhabricatorRepositoryURINormalizer::TYPE_GIT, 559 $uri); 560 break; 561 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 562 $normalized_uri = new PhabricatorRepositoryURINormalizer( 563 PhabricatorRepositoryURINormalizer::TYPE_SVN, 564 $uri); 565 break; 566 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 567 $normalized_uri = new PhabricatorRepositoryURINormalizer( 568 PhabricatorRepositoryURINormalizer::TYPE_MERCURIAL, 569 $uri); 570 break; 571 default: 572 throw new Exception('Unrecognized version control system.'); 573 } 574 575 return $normalized_uri->getNormalizedPath(); 576 } 577 578 public function isTracked() { 579 return $this->getDetail('tracking-enabled', false); 580 } 581 582 public function getDefaultBranch() { 583 $default = $this->getDetail('default-branch'); 584 if (strlen($default)) { 585 return $default; 586 } 587 588 $default_branches = array( 589 PhabricatorRepositoryType::REPOSITORY_TYPE_GIT => 'master', 590 PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL => 'default', 591 ); 592 593 return idx($default_branches, $this->getVersionControlSystem()); 594 } 595 596 public function getDefaultArcanistBranch() { 597 return coalesce($this->getDefaultBranch(), 'svn'); 598 } 599 600 private function isBranchInFilter($branch, $filter_key) { 601 $vcs = $this->getVersionControlSystem(); 602 603 $is_git = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT); 604 605 $use_filter = ($is_git); 606 if (!$use_filter) { 607 // If this VCS doesn't use filters, pass everything through. 608 return true; 609 } 610 611 612 $filter = $this->getDetail($filter_key, array()); 613 614 // If there's no filter set, let everything through. 615 if (!$filter) { 616 return true; 617 } 618 619 // If this branch isn't literally named `regexp(...)`, and it's in the 620 // filter list, let it through. 621 if (isset($filter[$branch])) { 622 if (self::extractBranchRegexp($branch) === null) { 623 return true; 624 } 625 } 626 627 // If the branch matches a regexp, let it through. 628 foreach ($filter as $pattern => $ignored) { 629 $regexp = self::extractBranchRegexp($pattern); 630 if ($regexp !== null) { 631 if (preg_match($regexp, $branch)) { 632 return true; 633 } 634 } 635 } 636 637 // Nothing matched, so filter this branch out. 638 return false; 639 } 640 641 public static function extractBranchRegexp($pattern) { 642 $matches = null; 643 if (preg_match('/^regexp\\((.*)\\)\z/', $pattern, $matches)) { 644 return $matches[1]; 645 } 646 return null; 647 } 648 649 public function shouldTrackBranch($branch) { 650 return $this->isBranchInFilter($branch, 'branch-filter'); 651 } 652 653 public function formatCommitName($commit_identifier) { 654 $vcs = $this->getVersionControlSystem(); 655 656 $type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; 657 $type_hg = PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL; 658 659 $is_git = ($vcs == $type_git); 660 $is_hg = ($vcs == $type_hg); 661 if ($is_git || $is_hg) { 662 $short_identifier = substr($commit_identifier, 0, 12); 663 } else { 664 $short_identifier = $commit_identifier; 665 } 666 667 return 'r'.$this->getCallsign().$short_identifier; 668 } 669 670 public function isImporting() { 671 return (bool)$this->getDetail('importing', false); 672 } 673 674 675 /* -( Autoclose )---------------------------------------------------------- */ 676 677 678 /** 679 * Determine if autoclose is active for a branch. 680 * 681 * For more details about why, use @{method:shouldSkipAutocloseBranch}. 682 * 683 * @param string Branch name to check. 684 * @return bool True if autoclose is active for the branch. 685 * @task autoclose 686 */ 687 public function shouldAutocloseBranch($branch) { 688 return ($this->shouldSkipAutocloseBranch($branch) === null); 689 } 690 691 /** 692 * Determine if autoclose is active for a commit. 693 * 694 * For more details about why, use @{method:shouldSkipAutocloseCommit}. 695 * 696 * @param PhabricatorRepositoryCommit Commit to check. 697 * @return bool True if autoclose is active for the commit. 698 * @task autoclose 699 */ 700 public function shouldAutocloseCommit(PhabricatorRepositoryCommit $commit) { 701 return ($this->shouldSkipAutocloseCommit($commit) === null); 702 } 703 704 705 /** 706 * Determine why autoclose should be skipped for a branch. 707 * 708 * This method gives a detailed reason why autoclose will be skipped. To 709 * perform a simple test, use @{method:shouldAutocloseBranch}. 710 * 711 * @param string Branch name to check. 712 * @return const|null Constant identifying reason to skip this branch, or null 713 * if autoclose is active. 714 * @task autoclose 715 */ 716 public function shouldSkipAutocloseBranch($branch) { 717 $all_reason = $this->shouldSkipAllAutoclose(); 718 if ($all_reason) { 719 return $all_reason; 720 } 721 722 if (!$this->shouldTrackBranch($branch)) { 723 return self::BECAUSE_BRANCH_UNTRACKED; 724 } 725 726 if (!$this->isBranchInFilter($branch, 'close-commits-filter')) { 727 return self::BECAUSE_BRANCH_NOT_AUTOCLOSE; 728 } 729 730 return null; 731 } 732 733 734 /** 735 * Determine why autoclose should be skipped for a commit. 736 * 737 * This method gives a detailed reason why autoclose will be skipped. To 738 * perform a simple test, use @{method:shouldAutocloseCommit}. 739 * 740 * @param PhabricatorRepositoryCommit Commit to check. 741 * @return const|null Constant identifying reason to skip this commit, or null 742 * if autoclose is active. 743 * @task autoclose 744 */ 745 public function shouldSkipAutocloseCommit( 746 PhabricatorRepositoryCommit $commit) { 747 748 $all_reason = $this->shouldSkipAllAutoclose(); 749 if ($all_reason) { 750 return $all_reason; 751 } 752 753 switch ($this->getVersionControlSystem()) { 754 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 755 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 756 return null; 757 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 758 break; 759 default: 760 throw new Exception('Unrecognized version control system.'); 761 } 762 763 $closeable_flag = PhabricatorRepositoryCommit::IMPORTED_CLOSEABLE; 764 if (!$commit->isPartiallyImported($closeable_flag)) { 765 return self::BECAUSE_NOT_ON_AUTOCLOSE_BRANCH; 766 } 767 768 return null; 769 } 770 771 772 /** 773 * Determine why all autoclose operations should be skipped for this 774 * repository. 775 * 776 * @return const|null Constant identifying reason to skip all autoclose 777 * operations, or null if autoclose operations are not blocked at the 778 * repository level. 779 * @task autoclose 780 */ 781 private function shouldSkipAllAutoclose() { 782 if ($this->isImporting()) { 783 return self::BECAUSE_REPOSITORY_IMPORTING; 784 } 785 786 if ($this->getDetail('disable-autoclose', false)) { 787 return self::BECAUSE_AUTOCLOSE_DISABLED; 788 } 789 790 return null; 791 } 792 793 794 /* -( Repository URI Management )------------------------------------------ */ 795 796 797 /** 798 * Get the remote URI for this repository. 799 * 800 * @return string 801 * @task uri 802 */ 803 public function getRemoteURI() { 804 return (string)$this->getRemoteURIObject(); 805 } 806 807 808 /** 809 * Get the remote URI for this repository, including credentials if they're 810 * used by this repository. 811 * 812 * @return PhutilOpaqueEnvelope URI, possibly including credentials. 813 * @task uri 814 */ 815 public function getRemoteURIEnvelope() { 816 $uri = $this->getRemoteURIObject(); 817 818 $remote_protocol = $this->getRemoteProtocol(); 819 if ($remote_protocol == 'http' || $remote_protocol == 'https') { 820 // For SVN, we use `--username` and `--password` flags separately, so 821 // don't add any credentials here. 822 if (!$this->isSVN()) { 823 $credential_phid = $this->getCredentialPHID(); 824 if ($credential_phid) { 825 $key = PassphrasePasswordKey::loadFromPHID( 826 $credential_phid, 827 PhabricatorUser::getOmnipotentUser()); 828 829 $uri->setUser($key->getUsernameEnvelope()->openEnvelope()); 830 $uri->setPass($key->getPasswordEnvelope()->openEnvelope()); 831 } 832 } 833 } 834 835 return new PhutilOpaqueEnvelope((string)$uri); 836 } 837 838 839 /** 840 * Get the clone (or checkout) URI for this repository, without authentication 841 * information. 842 * 843 * @return string Repository URI. 844 * @task uri 845 */ 846 public function getPublicCloneURI() { 847 $uri = $this->getCloneURIObject(); 848 849 // Make sure we don't leak anything if this repo is using HTTP Basic Auth 850 // with the credentials in the URI or something zany like that. 851 852 // If repository is not accessed over SSH we remove both username and 853 // password. 854 if (!$this->isHosted()) { 855 if (!$this->shouldUseSSH()) { 856 $uri->setUser(null); 857 858 // This might be a Git URI or a normal URI. If it's Git, there's no 859 // password support. 860 if ($uri instanceof PhutilURI) { 861 $uri->setPass(null); 862 } 863 } 864 } 865 866 return (string)$uri; 867 } 868 869 870 /** 871 * Get the protocol for the repository's remote. 872 * 873 * @return string Protocol, like "ssh" or "git". 874 * @task uri 875 */ 876 public function getRemoteProtocol() { 877 $uri = $this->getRemoteURIObject(); 878 879 if ($uri instanceof PhutilGitURI) { 880 return 'ssh'; 881 } else { 882 return $uri->getProtocol(); 883 } 884 } 885 886 887 /** 888 * Get a parsed object representation of the repository's remote URI. This 889 * may be a normal URI (returned as a @{class@libphutil:PhutilURI}) or a git 890 * URI (returned as a @{class@libphutil:PhutilGitURI}). 891 * 892 * @return wild A @{class@libphutil:PhutilURI} or 893 * @{class@libphutil:PhutilGitURI}. 894 * @task uri 895 */ 896 public function getRemoteURIObject() { 897 $raw_uri = $this->getDetail('remote-uri'); 898 if (!$raw_uri) { 899 return new PhutilURI(''); 900 } 901 902 if (!strncmp($raw_uri, '/', 1)) { 903 return new PhutilURI('file://'.$raw_uri); 904 } 905 906 $uri = new PhutilURI($raw_uri); 907 if ($uri->getProtocol()) { 908 return $uri; 909 } 910 911 $uri = new PhutilGitURI($raw_uri); 912 if ($uri->getDomain()) { 913 return $uri; 914 } 915 916 throw new Exception("Remote URI '{$raw_uri}' could not be parsed!"); 917 } 918 919 920 /** 921 * Get the "best" clone/checkout URI for this repository, on any protocol. 922 */ 923 public function getCloneURIObject() { 924 if (!$this->isHosted()) { 925 if ($this->isSVN()) { 926 // Make sure we pick up the "Import Only" path for Subversion, so 927 // the user clones the repository starting at the correct path, not 928 // from the root. 929 $base_uri = $this->getSubversionBaseURI(); 930 $base_uri = new PhutilURI($base_uri); 931 $path = $base_uri->getPath(); 932 if (!$path) { 933 $path = '/'; 934 } 935 936 // If the trailing "@" is not required to escape the URI, strip it for 937 // readability. 938 if (!preg_match('/@.*@/', $path)) { 939 $path = rtrim($path, '@'); 940 } 941 942 $base_uri->setPath($path); 943 return $base_uri; 944 } else { 945 return $this->getRemoteURIObject(); 946 } 947 } 948 949 // Choose the best URI: pick a read/write URI over a URI which is not 950 // read/write, and SSH over HTTP. 951 952 $serve_ssh = $this->getServeOverSSH(); 953 $serve_http = $this->getServeOverHTTP(); 954 955 if ($serve_ssh === self::SERVE_READWRITE) { 956 return $this->getSSHCloneURIObject(); 957 } else if ($serve_http === self::SERVE_READWRITE) { 958 return $this->getHTTPCloneURIObject(); 959 } else if ($serve_ssh !== self::SERVE_OFF) { 960 return $this->getSSHCloneURIObject(); 961 } else if ($serve_http !== self::SERVE_OFF) { 962 return $this->getHTTPCloneURIObject(); 963 } else { 964 return null; 965 } 966 } 967 968 969 /** 970 * Get the repository's SSH clone/checkout URI, if one exists. 971 */ 972 public function getSSHCloneURIObject() { 973 if (!$this->isHosted()) { 974 if ($this->shouldUseSSH()) { 975 return $this->getRemoteURIObject(); 976 } else { 977 return null; 978 } 979 } 980 981 $serve_ssh = $this->getServeOverSSH(); 982 if ($serve_ssh === self::SERVE_OFF) { 983 return null; 984 } 985 986 $uri = new PhutilURI(PhabricatorEnv::getProductionURI($this->getURI())); 987 988 if ($this->isSVN()) { 989 $uri->setProtocol('svn+ssh'); 990 } else { 991 $uri->setProtocol('ssh'); 992 } 993 994 if ($this->isGit()) { 995 $uri->setPath($uri->getPath().$this->getCloneName().'.git'); 996 } else if ($this->isHg()) { 997 $uri->setPath($uri->getPath().$this->getCloneName().'/'); 998 } 999 1000 $ssh_user = PhabricatorEnv::getEnvConfig('diffusion.ssh-user'); 1001 if ($ssh_user) { 1002 $uri->setUser($ssh_user); 1003 } 1004 1005 $uri->setPort(PhabricatorEnv::getEnvConfig('diffusion.ssh-port')); 1006 1007 return $uri; 1008 } 1009 1010 1011 /** 1012 * Get the repository's HTTP clone/checkout URI, if one exists. 1013 */ 1014 public function getHTTPCloneURIObject() { 1015 if (!$this->isHosted()) { 1016 if ($this->shouldUseHTTP()) { 1017 return $this->getRemoteURIObject(); 1018 } else { 1019 return null; 1020 } 1021 } 1022 1023 $serve_http = $this->getServeOverHTTP(); 1024 if ($serve_http === self::SERVE_OFF) { 1025 return null; 1026 } 1027 1028 $uri = PhabricatorEnv::getProductionURI($this->getURI()); 1029 $uri = new PhutilURI($uri); 1030 1031 if ($this->isGit()) { 1032 $uri->setPath($uri->getPath().$this->getCloneName().'.git'); 1033 } else if ($this->isHg()) { 1034 $uri->setPath($uri->getPath().$this->getCloneName().'/'); 1035 } 1036 1037 return $uri; 1038 } 1039 1040 1041 /** 1042 * Determine if we should connect to the remote using SSH flags and 1043 * credentials. 1044 * 1045 * @return bool True to use the SSH protocol. 1046 * @task uri 1047 */ 1048 private function shouldUseSSH() { 1049 if ($this->isHosted()) { 1050 return false; 1051 } 1052 1053 $protocol = $this->getRemoteProtocol(); 1054 if ($this->isSSHProtocol($protocol)) { 1055 return true; 1056 } 1057 1058 return false; 1059 } 1060 1061 1062 /** 1063 * Determine if we should connect to the remote using HTTP flags and 1064 * credentials. 1065 * 1066 * @return bool True to use the HTTP protocol. 1067 * @task uri 1068 */ 1069 private function shouldUseHTTP() { 1070 if ($this->isHosted()) { 1071 return false; 1072 } 1073 1074 $protocol = $this->getRemoteProtocol(); 1075 return ($protocol == 'http' || $protocol == 'https'); 1076 } 1077 1078 1079 /** 1080 * Determine if we should connect to the remote using SVN flags and 1081 * credentials. 1082 * 1083 * @return bool True to use the SVN protocol. 1084 * @task uri 1085 */ 1086 private function shouldUseSVNProtocol() { 1087 if ($this->isHosted()) { 1088 return false; 1089 } 1090 1091 $protocol = $this->getRemoteProtocol(); 1092 return ($protocol == 'svn'); 1093 } 1094 1095 1096 /** 1097 * Determine if a protocol is SSH or SSH-like. 1098 * 1099 * @param string A protocol string, like "http" or "ssh". 1100 * @return bool True if the protocol is SSH-like. 1101 * @task uri 1102 */ 1103 private function isSSHProtocol($protocol) { 1104 return ($protocol == 'ssh' || $protocol == 'svn+ssh'); 1105 } 1106 1107 public function delete() { 1108 $this->openTransaction(); 1109 1110 $paths = id(new PhabricatorOwnersPath()) 1111 ->loadAllWhere('repositoryPHID = %s', $this->getPHID()); 1112 foreach ($paths as $path) { 1113 $path->delete(); 1114 } 1115 1116 $projects = id(new PhabricatorRepositoryArcanistProject()) 1117 ->loadAllWhere('repositoryID = %d', $this->getID()); 1118 foreach ($projects as $project) { 1119 // note each project deletes its PhabricatorRepositorySymbols 1120 $project->delete(); 1121 } 1122 1123 $commits = id(new PhabricatorRepositoryCommit()) 1124 ->loadAllWhere('repositoryID = %d', $this->getID()); 1125 foreach ($commits as $commit) { 1126 // note PhabricatorRepositoryAuditRequests and 1127 // PhabricatorRepositoryCommitData are deleted here too. 1128 $commit->delete(); 1129 } 1130 1131 $mirrors = id(new PhabricatorRepositoryMirror()) 1132 ->loadAllWhere('repositoryPHID = %s', $this->getPHID()); 1133 foreach ($mirrors as $mirror) { 1134 $mirror->delete(); 1135 } 1136 1137 $ref_cursors = id(new PhabricatorRepositoryRefCursor()) 1138 ->loadAllWhere('repositoryPHID = %s', $this->getPHID()); 1139 foreach ($ref_cursors as $cursor) { 1140 $cursor->delete(); 1141 } 1142 1143 $conn_w = $this->establishConnection('w'); 1144 1145 queryfx( 1146 $conn_w, 1147 'DELETE FROM %T WHERE repositoryID = %d', 1148 self::TABLE_FILESYSTEM, 1149 $this->getID()); 1150 1151 queryfx( 1152 $conn_w, 1153 'DELETE FROM %T WHERE repositoryID = %d', 1154 self::TABLE_PATHCHANGE, 1155 $this->getID()); 1156 1157 queryfx( 1158 $conn_w, 1159 'DELETE FROM %T WHERE repositoryID = %d', 1160 self::TABLE_SUMMARY, 1161 $this->getID()); 1162 1163 $result = parent::delete(); 1164 1165 $this->saveTransaction(); 1166 return $result; 1167 } 1168 1169 public function isGit() { 1170 $vcs = $this->getVersionControlSystem(); 1171 return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT); 1172 } 1173 1174 public function isSVN() { 1175 $vcs = $this->getVersionControlSystem(); 1176 return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_SVN); 1177 } 1178 1179 public function isHg() { 1180 $vcs = $this->getVersionControlSystem(); 1181 return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL); 1182 } 1183 1184 public function isHosted() { 1185 return (bool)$this->getDetail('hosting-enabled', false); 1186 } 1187 1188 public function setHosted($enabled) { 1189 return $this->setDetail('hosting-enabled', $enabled); 1190 } 1191 1192 public function getServeOverHTTP() { 1193 if ($this->isSVN()) { 1194 return self::SERVE_OFF; 1195 } 1196 $serve = $this->getDetail('serve-over-http', self::SERVE_OFF); 1197 return $this->normalizeServeConfigSetting($serve); 1198 } 1199 1200 public function setServeOverHTTP($mode) { 1201 return $this->setDetail('serve-over-http', $mode); 1202 } 1203 1204 public function getServeOverSSH() { 1205 $serve = $this->getDetail('serve-over-ssh', self::SERVE_OFF); 1206 return $this->normalizeServeConfigSetting($serve); 1207 } 1208 1209 public function setServeOverSSH($mode) { 1210 return $this->setDetail('serve-over-ssh', $mode); 1211 } 1212 1213 public static function getProtocolAvailabilityName($constant) { 1214 switch ($constant) { 1215 case self::SERVE_OFF: 1216 return pht('Off'); 1217 case self::SERVE_READONLY: 1218 return pht('Read Only'); 1219 case self::SERVE_READWRITE: 1220 return pht('Read/Write'); 1221 default: 1222 return pht('Unknown'); 1223 } 1224 } 1225 1226 private function normalizeServeConfigSetting($value) { 1227 switch ($value) { 1228 case self::SERVE_OFF: 1229 case self::SERVE_READONLY: 1230 return $value; 1231 case self::SERVE_READWRITE: 1232 if ($this->isHosted()) { 1233 return self::SERVE_READWRITE; 1234 } else { 1235 return self::SERVE_READONLY; 1236 } 1237 default: 1238 return self::SERVE_OFF; 1239 } 1240 } 1241 1242 1243 /** 1244 * Raise more useful errors when there are basic filesystem problems. 1245 */ 1246 private function assertLocalExists() { 1247 if (!$this->usesLocalWorkingCopy()) { 1248 return; 1249 } 1250 1251 $local = $this->getLocalPath(); 1252 Filesystem::assertExists($local); 1253 Filesystem::assertIsDirectory($local); 1254 Filesystem::assertReadable($local); 1255 } 1256 1257 /** 1258 * Determine if the working copy is bare or not. In Git, this corresponds 1259 * to `--bare`. In Mercurial, `--noupdate`. 1260 */ 1261 public function isWorkingCopyBare() { 1262 switch ($this->getVersionControlSystem()) { 1263 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 1264 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 1265 return false; 1266 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 1267 $local = $this->getLocalPath(); 1268 if (Filesystem::pathExists($local.'/.git')) { 1269 return false; 1270 } else { 1271 return true; 1272 } 1273 } 1274 } 1275 1276 public function usesLocalWorkingCopy() { 1277 switch ($this->getVersionControlSystem()) { 1278 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 1279 return $this->isHosted(); 1280 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 1281 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 1282 return true; 1283 } 1284 } 1285 1286 public function getHookDirectories() { 1287 $directories = array(); 1288 if (!$this->isHosted()) { 1289 return $directories; 1290 } 1291 1292 $root = $this->getLocalPath(); 1293 1294 switch ($this->getVersionControlSystem()) { 1295 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 1296 if ($this->isWorkingCopyBare()) { 1297 $directories[] = $root.'/hooks/pre-receive-phabricator.d/'; 1298 } else { 1299 $directories[] = $root.'/.git/hooks/pre-receive-phabricator.d/'; 1300 } 1301 break; 1302 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 1303 $directories[] = $root.'/hooks/pre-commit-phabricator.d/'; 1304 break; 1305 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 1306 // NOTE: We don't support custom Mercurial hooks for now because they're 1307 // messy and we can't easily just drop a `hooks.d/` directory next to 1308 // the hooks. 1309 break; 1310 } 1311 1312 return $directories; 1313 } 1314 1315 public function canDestroyWorkingCopy() { 1316 if ($this->isHosted()) { 1317 // Never destroy hosted working copies. 1318 return false; 1319 } 1320 1321 $default_path = PhabricatorEnv::getEnvConfig( 1322 'repository.default-local-path'); 1323 return Filesystem::isDescendant($this->getLocalPath(), $default_path); 1324 } 1325 1326 public function canUsePathTree() { 1327 return !$this->isSVN(); 1328 } 1329 1330 public function canMirror() { 1331 if ($this->isGit() || $this->isHg()) { 1332 return true; 1333 } 1334 1335 return false; 1336 } 1337 1338 public function canAllowDangerousChanges() { 1339 if (!$this->isHosted()) { 1340 return false; 1341 } 1342 1343 if ($this->isGit() || $this->isHg()) { 1344 return true; 1345 } 1346 1347 return false; 1348 } 1349 1350 public function shouldAllowDangerousChanges() { 1351 return (bool)$this->getDetail('allow-dangerous-changes'); 1352 } 1353 1354 public function writeStatusMessage( 1355 $status_type, 1356 $status_code, 1357 array $parameters = array()) { 1358 1359 $table = new PhabricatorRepositoryStatusMessage(); 1360 $conn_w = $table->establishConnection('w'); 1361 $table_name = $table->getTableName(); 1362 1363 if ($status_code === null) { 1364 queryfx( 1365 $conn_w, 1366 'DELETE FROM %T WHERE repositoryID = %d AND statusType = %s', 1367 $table_name, 1368 $this->getID(), 1369 $status_type); 1370 } else { 1371 queryfx( 1372 $conn_w, 1373 'INSERT INTO %T 1374 (repositoryID, statusType, statusCode, parameters, epoch) 1375 VALUES (%d, %s, %s, %s, %d) 1376 ON DUPLICATE KEY UPDATE 1377 statusCode = VALUES(statusCode), 1378 parameters = VALUES(parameters), 1379 epoch = VALUES(epoch)', 1380 $table_name, 1381 $this->getID(), 1382 $status_type, 1383 $status_code, 1384 json_encode($parameters), 1385 time()); 1386 } 1387 1388 return $this; 1389 } 1390 1391 public static function getRemoteURIProtocol($raw_uri) { 1392 $uri = new PhutilURI($raw_uri); 1393 if ($uri->getProtocol()) { 1394 return strtolower($uri->getProtocol()); 1395 } 1396 1397 $git_uri = new PhutilGitURI($raw_uri); 1398 if (strlen($git_uri->getDomain()) && strlen($git_uri->getPath())) { 1399 return 'ssh'; 1400 } 1401 1402 return null; 1403 } 1404 1405 public static function assertValidRemoteURI($uri) { 1406 if (trim($uri) != $uri) { 1407 throw new Exception( 1408 pht( 1409 'The remote URI has leading or trailing whitespace.')); 1410 } 1411 1412 $protocol = self::getRemoteURIProtocol($uri); 1413 1414 // Catch confusion between Git/SCP-style URIs and normal URIs. See T3619 1415 // for discussion. This is usually a user adding "ssh://" to an implicit 1416 // SSH Git URI. 1417 if ($protocol == 'ssh') { 1418 if (preg_match('(^[^:@]+://[^/:]+:[^\d])', $uri)) { 1419 throw new Exception( 1420 pht( 1421 "The remote URI is not formatted correctly. Remote URIs ". 1422 "with an explicit protocol should be in the form ". 1423 "'proto://domain/path', not 'proto://domain:/path'. ". 1424 "The ':/path' syntax is only valid in SCP-style URIs.")); 1425 } 1426 } 1427 1428 switch ($protocol) { 1429 case 'ssh': 1430 case 'http': 1431 case 'https': 1432 case 'git': 1433 case 'svn': 1434 case 'svn+ssh': 1435 break; 1436 default: 1437 // NOTE: We're explicitly rejecting 'file://' because it can be 1438 // used to clone from the working copy of another repository on disk 1439 // that you don't normally have permission to access. 1440 1441 throw new Exception( 1442 pht( 1443 "The URI protocol is unrecognized. It should begin ". 1444 "'ssh://', 'http://', 'https://', 'git://', 'svn://', ". 1445 "'svn+ssh://', or be in the form '[email protected]:path'.")); 1446 } 1447 1448 return true; 1449 } 1450 1451 1452 /** 1453 * Load the pull frequency for this repository, based on the time since the 1454 * last activity. 1455 * 1456 * We pull rarely used repositories less frequently. This finds the most 1457 * recent commit which is older than the current time (which prevents us from 1458 * spinning on repositories with a silly commit post-dated to some time in 1459 * 2037). We adjust the pull frequency based on when the most recent commit 1460 * occurred. 1461 * 1462 * @param int The minimum update interval to use, in seconds. 1463 * @return int Repository update interval, in seconds. 1464 */ 1465 public function loadUpdateInterval($minimum = 15) { 1466 // If a repository is still importing, always pull it as frequently as 1467 // possible. This prevents us from hanging for a long time at 99.9% when 1468 // importing an inactive repository. 1469 if ($this->isImporting()) { 1470 return $minimum; 1471 } 1472 1473 $window_start = (PhabricatorTime::getNow() + $minimum); 1474 1475 $table = id(new PhabricatorRepositoryCommit()); 1476 $last_commit = queryfx_one( 1477 $table->establishConnection('r'), 1478 'SELECT epoch FROM %T 1479 WHERE repositoryID = %d AND epoch <= %d 1480 ORDER BY epoch DESC LIMIT 1', 1481 $table->getTableName(), 1482 $this->getID(), 1483 $window_start); 1484 if ($last_commit) { 1485 $time_since_commit = ($window_start - $last_commit['epoch']); 1486 1487 $last_few_days = phutil_units('3 days in seconds'); 1488 1489 if ($time_since_commit <= $last_few_days) { 1490 // For repositories with activity in the recent past, we wait one 1491 // extra second for every 10 minutes since the last commit. This 1492 // shorter backoff is intended to handle weekends and other short 1493 // breaks from development. 1494 $smart_wait = ($time_since_commit / 600); 1495 } else { 1496 // For repositories without recent activity, we wait one extra second 1497 // for every 4 minutes since the last commit. This longer backoff 1498 // handles rarely used repositories, up to the maximum. 1499 $smart_wait = ($time_since_commit / 240); 1500 } 1501 1502 // We'll never wait more than 6 hours to pull a repository. 1503 $longest_wait = phutil_units('6 hours in seconds'); 1504 $smart_wait = min($smart_wait, $longest_wait); 1505 1506 $smart_wait = max($minimum, $smart_wait); 1507 } else { 1508 $smart_wait = $minimum; 1509 } 1510 1511 return $smart_wait; 1512 } 1513 1514 1515 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 1516 1517 1518 public function getCapabilities() { 1519 return array( 1520 PhabricatorPolicyCapability::CAN_VIEW, 1521 PhabricatorPolicyCapability::CAN_EDIT, 1522 DiffusionPushCapability::CAPABILITY, 1523 ); 1524 } 1525 1526 public function getPolicy($capability) { 1527 switch ($capability) { 1528 case PhabricatorPolicyCapability::CAN_VIEW: 1529 return $this->getViewPolicy(); 1530 case PhabricatorPolicyCapability::CAN_EDIT: 1531 return $this->getEditPolicy(); 1532 case DiffusionPushCapability::CAPABILITY: 1533 return $this->getPushPolicy(); 1534 } 1535 } 1536 1537 public function hasAutomaticCapability($capability, PhabricatorUser $user) { 1538 return false; 1539 } 1540 1541 public function describeAutomaticCapability($capability) { 1542 return null; 1543 } 1544 1545 1546 1547 /* -( PhabricatorMarkupInterface )----------------------------------------- */ 1548 1549 1550 public function getMarkupFieldKey($field) { 1551 $hash = PhabricatorHash::digestForIndex($this->getMarkupText($field)); 1552 return "repo:{$hash}"; 1553 } 1554 1555 public function newMarkupEngine($field) { 1556 return PhabricatorMarkupEngine::newMarkupEngine(array()); 1557 } 1558 1559 public function getMarkupText($field) { 1560 return $this->getDetail('description'); 1561 } 1562 1563 public function didMarkupText( 1564 $field, 1565 $output, 1566 PhutilMarkupEngine $engine) { 1567 require_celerity_resource('phabricator-remarkup-css'); 1568 return phutil_tag( 1569 'div', 1570 array( 1571 'class' => 'phabricator-remarkup', 1572 ), 1573 $output); 1574 } 1575 1576 public function shouldUseMarkupCache($field) { 1577 return true; 1578 } 1579 1580 1581 /* -( PhabricatorDestructibleInterface )----------------------------------- */ 1582 1583 public function destroyObjectPermanently( 1584 PhabricatorDestructionEngine $engine) { 1585 1586 $this->openTransaction(); 1587 $this->delete(); 1588 $this->saveTransaction(); 1589 } 1590 1591 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |