Changeset View
Changeset View
Standalone View
Standalone View
src/applications/differential/storage/DifferentialRevision.php
| Show First 20 Lines • Show All 42 Lines • ▼ Show 20 Lines | final class DifferentialRevision extends DifferentialDAO | ||||
| private $diffIDs = self::ATTACHABLE; | private $diffIDs = self::ATTACHABLE; | ||||
| private $hashes = self::ATTACHABLE; | private $hashes = self::ATTACHABLE; | ||||
| private $repository = self::ATTACHABLE; | private $repository = self::ATTACHABLE; | ||||
| private $reviewerStatus = self::ATTACHABLE; | private $reviewerStatus = self::ATTACHABLE; | ||||
| private $customFields = self::ATTACHABLE; | private $customFields = self::ATTACHABLE; | ||||
| private $drafts = array(); | private $drafts = array(); | ||||
| private $flags = array(); | private $flags = array(); | ||||
| private $forceMap = array(); | |||||
| const TABLE_COMMIT = 'differential_commit'; | const TABLE_COMMIT = 'differential_commit'; | ||||
| const RELATION_REVIEWER = 'revw'; | const RELATION_REVIEWER = 'revw'; | ||||
| const RELATION_SUBSCRIBED = 'subd'; | const RELATION_SUBSCRIBED = 'subd'; | ||||
| const PROPERTY_CLOSED_FROM_ACCEPTED = 'wasAcceptedBeforeClose'; | const PROPERTY_CLOSED_FROM_ACCEPTED = 'wasAcceptedBeforeClose'; | ||||
| ▲ Show 20 Lines • Show All 181 Lines • ▼ Show 20 Lines | public function getHashes() { | ||||
| return $this->assertAttached($this->hashes); | return $this->assertAttached($this->hashes); | ||||
| } | } | ||||
| public function attachHashes(array $hashes) { | public function attachHashes(array $hashes) { | ||||
| $this->hashes = $hashes; | $this->hashes = $hashes; | ||||
| return $this; | return $this; | ||||
| } | } | ||||
| public function canReviewerForceAccept( | |||||
| PhabricatorUser $viewer, | |||||
| DifferentialReviewer $reviewer) { | |||||
| if (!$reviewer->isPackage()) { | |||||
| return false; | |||||
| } | |||||
| $map = $this->getReviewerForceAcceptMap($viewer); | |||||
| if (!$map) { | |||||
| return false; | |||||
| } | |||||
| if (isset($map[$reviewer->getReviewerPHID()])) { | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| private function getReviewerForceAcceptMap(PhabricatorUser $viewer) { | |||||
| $fragment = $viewer->getCacheFragment(); | |||||
| if (!array_key_exists($fragment, $this->forceMap)) { | |||||
| $map = $this->newReviewerForceAcceptMap($viewer); | |||||
| $this->forceMap[$fragment] = $map; | |||||
| } | |||||
| return $this->forceMap[$fragment]; | |||||
| } | |||||
| private function newReviewerForceAcceptMap(PhabricatorUser $viewer) { | |||||
| $diff = $this->getActiveDiff(); | |||||
| if (!$diff) { | |||||
| return null; | |||||
| } | |||||
| $repository_phid = $diff->getRepositoryPHID(); | |||||
| if (!$repository_phid) { | |||||
| return null; | |||||
| } | |||||
| $paths = array(); | |||||
| try { | |||||
| $changesets = $diff->getChangesets(); | |||||
| } catch (Exception $ex) { | |||||
| $changesets = id(new DifferentialChangesetQuery()) | |||||
| ->setViewer($viewer) | |||||
| ->withDiffs(array($diff)) | |||||
| ->execute(); | |||||
| } | |||||
| foreach ($changesets as $changeset) { | |||||
| $paths[] = $changeset->getOwnersFilename(); | |||||
| } | |||||
| if (!$paths) { | |||||
| return null; | |||||
| } | |||||
| $reviewer_phids = array(); | |||||
| foreach ($this->getReviewers() as $reviewer) { | |||||
| if (!$reviewer->isPackage()) { | |||||
| continue; | |||||
| } | |||||
| $reviewer_phids[] = $reviewer->getReviewerPHID(); | |||||
| } | |||||
| if (!$reviewer_phids) { | |||||
| return null; | |||||
| } | |||||
| // Load all the reviewing packages which have control over some of the | |||||
| // paths in the change. These are packages which the actor may be able | |||||
| // to force-accept on behalf of. | |||||
| $control_query = id(new PhabricatorOwnersPackageQuery()) | |||||
| ->setViewer($viewer) | |||||
| ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) | |||||
| ->withPHIDs($reviewer_phids) | |||||
| ->withControl($repository_phid, $paths); | |||||
| $control_packages = $control_query->execute(); | |||||
| if (!$control_packages) { | |||||
| return null; | |||||
| } | |||||
| // Load all the packages which have potential control over some of the | |||||
| // paths in the change and are owned by the actor. These are packages | |||||
| // which the actor may be able to use their authority over to gain the | |||||
| // ability to force-accept for other packages. This query doesn't apply | |||||
| // dominion rules yet, and we'll bypass those rules later on. | |||||
| $authority_query = id(new PhabricatorOwnersPackageQuery()) | |||||
| ->setViewer($viewer) | |||||
| ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) | |||||
| ->withAuthorityPHIDs(array($viewer->getPHID())) | |||||
| ->withControl($repository_phid, $paths); | |||||
| $authority_packages = $authority_query->execute(); | |||||
| if (!$authority_packages) { | |||||
| return null; | |||||
| } | |||||
| $authority_packages = mpull($authority_packages, null, 'getPHID'); | |||||
| // Build a map from each path in the revision to the reviewer packages | |||||
| // which control it. | |||||
| $control_map = array(); | |||||
| foreach ($paths as $path) { | |||||
| $control_packages = $control_query->getControllingPackagesForPath( | |||||
| $repository_phid, | |||||
| $path); | |||||
| // Remove packages which the viewer has authority over. We don't need | |||||
| // to check these for force-accept because they can just accept them | |||||
| // normally. | |||||
| $control_packages = mpull($control_packages, null, 'getPHID'); | |||||
| foreach ($control_packages as $phid => $control_package) { | |||||
| if (isset($authority_packages[$phid])) { | |||||
| unset($control_packages[$phid]); | |||||
| } | |||||
| } | |||||
| if (!$control_packages) { | |||||
| continue; | |||||
| } | |||||
| $control_map[$path] = $control_packages; | |||||
| } | |||||
| if (!$control_map) { | |||||
| return null; | |||||
| } | |||||
| // From here on out, we only care about paths which we have at least one | |||||
| // controlling package for. | |||||
| $paths = array_keys($control_map); | |||||
| // Now, build a map from each path to the packages which would control it | |||||
| // if there were no dominion rules. | |||||
| $authority_map = array(); | |||||
| foreach ($paths as $path) { | |||||
| $authority_packages = $authority_query->getControllingPackagesForPath( | |||||
| $repository_phid, | |||||
| $path, | |||||
| $ignore_dominion = true); | |||||
| $authority_map[$path] = mpull($authority_packages, null, 'getPHID'); | |||||
| } | |||||
| // For each path, find the most general package that the viewer has | |||||
| // authority over. For example, we'll prefer a package that owns "/" to a | |||||
| // package that owns "/src/". | |||||
| $force_map = array(); | |||||
| foreach ($authority_map as $path => $package_map) { | |||||
| $path_fragments = PhabricatorOwnersPackage::splitPath($path); | |||||
| $fragment_count = count($path_fragments); | |||||
| // Find the package that we have authority over which has the most | |||||
| // general match for this path. | |||||
| $best_match = null; | |||||
| $best_package = null; | |||||
| foreach ($package_map as $package_phid => $package) { | |||||
| $package_paths = $package->getPathsForRepository($repository_phid); | |||||
| foreach ($package_paths as $package_path) { | |||||
| // NOTE: A strength of 0 means "no match". A strength of 1 means | |||||
| // that we matched "/", so we can not possibly find another stronger | |||||
| // match. | |||||
| $strength = $package_path->getPathMatchStrength( | |||||
| $path_fragments, | |||||
| $fragment_count); | |||||
| if (!$strength) { | |||||
| continue; | |||||
| } | |||||
| if ($strength < $best_match || !$best_package) { | |||||
| $best_match = $strength; | |||||
| $best_package = $package; | |||||
| if ($strength == 1) { | |||||
| break 2; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| if ($best_package) { | |||||
| $force_map[$path] = array( | |||||
| 'strength' => $best_match, | |||||
| 'package' => $best_package, | |||||
| ); | |||||
| } | |||||
| } | |||||
| // For each path which the viewer owns a package for, find other packages | |||||
| // which that authority can be used to force-accept. Once we find a way to | |||||
| // force-accept a package, we don't need to keep loooking. | |||||
| $has_control = array(); | |||||
| foreach ($force_map as $path => $spec) { | |||||
| $path_fragments = PhabricatorOwnersPackage::splitPath($path); | |||||
| $fragment_count = count($path_fragments); | |||||
| $authority_strength = $spec['strength']; | |||||
| $control_packages = $control_map[$path]; | |||||
| foreach ($control_packages as $control_phid => $control_package) { | |||||
| if (isset($has_control[$control_phid])) { | |||||
| continue; | |||||
| } | |||||
| $control_paths = $control_package->getPathsForRepository( | |||||
| $repository_phid); | |||||
| foreach ($control_paths as $control_path) { | |||||
| $strength = $control_path->getPathMatchStrength( | |||||
| $path_fragments, | |||||
| $fragment_count); | |||||
| if (!$strength) { | |||||
| continue; | |||||
| } | |||||
| if ($strength > $authority_strength) { | |||||
| $authority = $spec['package']; | |||||
| $has_control[$control_phid] = array( | |||||
| 'authority' => $authority, | |||||
| 'phid' => $authority->getPHID(), | |||||
| ); | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| // Return a map from packages which may be force accepted to the packages | |||||
| // which permit that forced acceptance. | |||||
| return ipull($has_control, 'phid'); | |||||
| } | |||||
| /* -( PhabricatorPolicyInterface )----------------------------------------- */ | /* -( PhabricatorPolicyInterface )----------------------------------------- */ | ||||
| public function getCapabilities() { | public function getCapabilities() { | ||||
| return array( | return array( | ||||
| PhabricatorPolicyCapability::CAN_VIEW, | PhabricatorPolicyCapability::CAN_VIEW, | ||||
| PhabricatorPolicyCapability::CAN_EDIT, | PhabricatorPolicyCapability::CAN_EDIT, | ||||
| ▲ Show 20 Lines • Show All 428 Lines • Show Last 20 Lines | |||||