mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-25 00:02:41 +01:00
20abf37b88
Summary: Fixes T4477. Sort of winging this but it's probably the right fix? One error in T4477. One error via email: ``` [2014-04-15 17:44:34] ERROR 8: Undefined index: /some_index/ at [/phab_path/phabricator/src/applications/owners/storage/PhabricatorOwnersPackage.php:213] #0 PhabricatorOwnersPackage::findLongestPathsPerPackage(Array of size 3 starting with: { 0 => Array of size 3 starting with: { id => 5 } }, Array of size 8 starting with: { / => Array of size 2 starting with: { /some_index/some_file.py => true } }) called at [/phab_path/phabricator/src/applications/owners/storage/PhabricatorOwnersPackage.php:170] #1 PhabricatorOwnersPackage::loadPackagesForPaths(Object PhabricatorRepository, Array of size 2 starting with: { 0 => /some_index/some_file.py }) called at [/phab_path/phabricator/src/applications/owners/storage/PhabricatorOwnersPackage.php:119] ... ``` Test Plan: Will make @zeeg do it. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley, zeeg Maniphest Tasks: T4477 Differential Revision: https://secure.phabricator.com/D8779
421 lines
11 KiB
PHP
421 lines
11 KiB
PHP
<?php
|
|
|
|
final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO
|
|
implements PhabricatorPolicyInterface {
|
|
|
|
protected $name;
|
|
protected $originalName;
|
|
protected $auditingEnabled;
|
|
protected $description;
|
|
protected $primaryOwnerPHID;
|
|
|
|
private $unsavedOwners;
|
|
private $unsavedPaths;
|
|
private $actorPHID;
|
|
private $oldPrimaryOwnerPHID;
|
|
private $oldAuditingEnabled;
|
|
|
|
public function getCapabilities() {
|
|
return array(
|
|
PhabricatorPolicyCapability::CAN_VIEW,
|
|
);
|
|
}
|
|
|
|
public function getPolicy($capability) {
|
|
return PhabricatorPolicies::POLICY_USER;
|
|
}
|
|
|
|
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
|
return false;
|
|
}
|
|
|
|
public function describeAutomaticCapability($capability) {
|
|
return null;
|
|
}
|
|
|
|
public function getConfiguration() {
|
|
return array(
|
|
// This information is better available from the history table.
|
|
self::CONFIG_TIMESTAMPS => false,
|
|
self::CONFIG_AUX_PHID => true,
|
|
) + parent::getConfiguration();
|
|
}
|
|
|
|
public function generatePHID() {
|
|
return PhabricatorPHID::generateNewPHID('OPKG');
|
|
}
|
|
|
|
public function attachUnsavedOwners(array $owners) {
|
|
$this->unsavedOwners = $owners;
|
|
return $this;
|
|
}
|
|
|
|
public function attachUnsavedPaths(array $paths) {
|
|
$this->unsavedPaths = $paths;
|
|
return $this;
|
|
}
|
|
|
|
public function attachActorPHID($actor_phid) {
|
|
$this->actorPHID = $actor_phid;
|
|
return $this;
|
|
}
|
|
|
|
public function getActorPHID() {
|
|
return $this->actorPHID;
|
|
}
|
|
|
|
public function attachOldPrimaryOwnerPHID($old_primary) {
|
|
$this->oldPrimaryOwnerPHID = $old_primary;
|
|
return $this;
|
|
}
|
|
|
|
public function getOldPrimaryOwnerPHID() {
|
|
return $this->oldPrimaryOwnerPHID;
|
|
}
|
|
|
|
public function attachOldAuditingEnabled($auditing_enabled) {
|
|
$this->oldAuditingEnabled = $auditing_enabled;
|
|
return $this;
|
|
}
|
|
|
|
public function getOldAuditingEnabled() {
|
|
return $this->oldAuditingEnabled;
|
|
}
|
|
|
|
public function setName($name) {
|
|
$this->name = $name;
|
|
if (!$this->getID()) {
|
|
$this->originalName = $name;
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
public function loadOwners() {
|
|
if (!$this->getID()) {
|
|
return array();
|
|
}
|
|
return id(new PhabricatorOwnersOwner())->loadAllWhere(
|
|
'packageID = %d',
|
|
$this->getID());
|
|
}
|
|
|
|
public function loadPaths() {
|
|
if (!$this->getID()) {
|
|
return array();
|
|
}
|
|
return id(new PhabricatorOwnersPath())->loadAllWhere(
|
|
'packageID = %d',
|
|
$this->getID());
|
|
}
|
|
|
|
public static function loadAffectedPackages(
|
|
PhabricatorRepository $repository,
|
|
array $paths) {
|
|
|
|
if (!$paths) {
|
|
return array();
|
|
}
|
|
|
|
return self::loadPackagesForPaths($repository, $paths);
|
|
}
|
|
|
|
public static function loadOwningPackages($repository, $path) {
|
|
if (empty($path)) {
|
|
return array();
|
|
}
|
|
|
|
return self::loadPackagesForPaths($repository, array($path), 1);
|
|
}
|
|
|
|
private static function loadPackagesForPaths(
|
|
PhabricatorRepository $repository,
|
|
array $paths,
|
|
$limit = 0) {
|
|
|
|
$fragments = array();
|
|
foreach ($paths as $path) {
|
|
foreach (self::splitPath($path) as $fragment) {
|
|
$fragments[$fragment][$path] = true;
|
|
}
|
|
}
|
|
|
|
$package = new PhabricatorOwnersPackage();
|
|
$path = new PhabricatorOwnersPath();
|
|
$conn = $package->establishConnection('r');
|
|
|
|
$repository_clause = qsprintf(
|
|
$conn,
|
|
'AND p.repositoryPHID = %s',
|
|
$repository->getPHID());
|
|
|
|
// NOTE: The list of $paths may be very large if we're coming from
|
|
// the OwnersWorker and processing, e.g., an SVN commit which created a new
|
|
// branch. Break it apart so that it will fit within 'max_allowed_packet',
|
|
// and then merge results in PHP.
|
|
|
|
$rows = array();
|
|
foreach (array_chunk(array_keys($fragments), 128) as $chunk) {
|
|
$rows[] = queryfx_all(
|
|
$conn,
|
|
'SELECT pkg.id, p.excluded, p.path
|
|
FROM %T pkg JOIN %T p ON p.packageID = pkg.id
|
|
WHERE p.path IN (%Ls) %Q',
|
|
$package->getTableName(),
|
|
$path->getTableName(),
|
|
$chunk,
|
|
$repository_clause);
|
|
}
|
|
$rows = array_mergev($rows);
|
|
|
|
$ids = self::findLongestPathsPerPackage($rows, $fragments);
|
|
|
|
if (!$ids) {
|
|
return array();
|
|
}
|
|
|
|
arsort($ids);
|
|
if ($limit) {
|
|
$ids = array_slice($ids, 0, $limit, $preserve_keys = true);
|
|
}
|
|
$ids = array_keys($ids);
|
|
|
|
$packages = $package->loadAllWhere('id in (%Ld)', $ids);
|
|
$packages = array_select_keys($packages, $ids);
|
|
|
|
return $packages;
|
|
}
|
|
|
|
public static function loadPackagesForRepository($repository) {
|
|
$package = new PhabricatorOwnersPackage();
|
|
$ids = ipull(
|
|
queryfx_all(
|
|
$package->establishConnection('r'),
|
|
'SELECT DISTINCT packageID FROM %T WHERE repositoryPHID = %s',
|
|
id(new PhabricatorOwnersPath())->getTableName(),
|
|
$repository->getPHID()),
|
|
'packageID');
|
|
|
|
return $package->loadAllWhere('id in (%Ld)', $ids);
|
|
}
|
|
|
|
public static function findLongestPathsPerPackage(array $rows, array $paths) {
|
|
$ids = array();
|
|
|
|
foreach (igroup($rows, 'id') as $id => $package_paths) {
|
|
$relevant_paths = array_select_keys(
|
|
$paths,
|
|
ipull($package_paths, 'path'));
|
|
|
|
// For every package, remove all excluded paths.
|
|
$remove = array();
|
|
foreach ($package_paths as $package_path) {
|
|
if ($package_path['excluded']) {
|
|
$remove += idx($relevant_paths, $package_path['path'], array());
|
|
unset($relevant_paths[$package_path['path']]);
|
|
}
|
|
}
|
|
|
|
if ($remove) {
|
|
foreach ($relevant_paths as $fragment => $fragment_paths) {
|
|
$relevant_paths[$fragment] = array_diff_key($fragment_paths, $remove);
|
|
}
|
|
}
|
|
|
|
$relevant_paths = array_filter($relevant_paths);
|
|
if ($relevant_paths) {
|
|
$ids[$id] = max(array_map('strlen', array_keys($relevant_paths)));
|
|
}
|
|
}
|
|
|
|
return $ids;
|
|
}
|
|
|
|
private function getActor() {
|
|
// TODO: This should be cleaner, but we'd likely need to move the whole
|
|
// thing to an Editor (T603).
|
|
return PhabricatorUser::getOmnipotentUser();
|
|
}
|
|
|
|
public function save() {
|
|
|
|
if ($this->getID()) {
|
|
$is_new = false;
|
|
} else {
|
|
$is_new = true;
|
|
}
|
|
|
|
$this->openTransaction();
|
|
|
|
$ret = parent::save();
|
|
|
|
$add_owners = array();
|
|
$remove_owners = array();
|
|
$all_owners = array();
|
|
if ($this->unsavedOwners) {
|
|
$new_owners = array_fill_keys($this->unsavedOwners, true);
|
|
$cur_owners = array();
|
|
foreach ($this->loadOwners() as $owner) {
|
|
if (empty($new_owners[$owner->getUserPHID()])) {
|
|
$remove_owners[$owner->getUserPHID()] = true;
|
|
$owner->delete();
|
|
continue;
|
|
}
|
|
$cur_owners[$owner->getUserPHID()] = true;
|
|
}
|
|
|
|
$add_owners = array_diff_key($new_owners, $cur_owners);
|
|
$all_owners = array_merge(
|
|
array($this->getPrimaryOwnerPHID() => true),
|
|
$new_owners,
|
|
$remove_owners);
|
|
foreach ($add_owners as $phid => $ignored) {
|
|
$owner = new PhabricatorOwnersOwner();
|
|
$owner->setPackageID($this->getID());
|
|
$owner->setUserPHID($phid);
|
|
$owner->save();
|
|
}
|
|
unset($this->unsavedOwners);
|
|
}
|
|
|
|
$add_paths = array();
|
|
$remove_paths = array();
|
|
$touched_repos = array();
|
|
if ($this->unsavedPaths) {
|
|
$new_paths = igroup($this->unsavedPaths, 'repositoryPHID', 'path');
|
|
$cur_paths = $this->loadPaths();
|
|
foreach ($cur_paths as $key => $path) {
|
|
$repository_phid = $path->getRepositoryPHID();
|
|
$new_path = head(idx(
|
|
idx($new_paths, $repository_phid, array()),
|
|
$path->getPath(),
|
|
array()));
|
|
$excluded = $path->getExcluded();
|
|
if (!$new_path || idx($new_path, 'excluded') != $excluded) {
|
|
$touched_repos[$repository_phid] = true;
|
|
$remove_paths[$repository_phid][$path->getPath()] = $excluded;
|
|
$path->delete();
|
|
unset($cur_paths[$key]);
|
|
}
|
|
}
|
|
|
|
$cur_paths = mgroup($cur_paths, 'getRepositoryPHID', 'getPath');
|
|
foreach ($new_paths as $repository_phid => $paths) {
|
|
// TODO: (T603) Thread policy stuff in here.
|
|
|
|
// get repository object for path validation
|
|
$repository = id(new PhabricatorRepository())->loadOneWhere(
|
|
'phid = %s',
|
|
$repository_phid);
|
|
if (!$repository) {
|
|
continue;
|
|
}
|
|
foreach ($paths as $path => $dicts) {
|
|
$path = ltrim($path, '/');
|
|
// build query to validate path
|
|
$drequest = DiffusionRequest::newFromDictionary(
|
|
array(
|
|
'user' => $this->getActor(),
|
|
'repository' => $repository,
|
|
'path' => $path,
|
|
));
|
|
$results = DiffusionBrowseResultSet::newFromConduit(
|
|
DiffusionQuery::callConduitWithDiffusionRequest(
|
|
$this->getActor(),
|
|
$drequest,
|
|
'diffusion.browsequery',
|
|
array(
|
|
'commit' => $drequest->getCommit(),
|
|
'path' => $path,
|
|
'needValidityOnly' => true)));
|
|
$valid = $results->isValidResults();
|
|
$is_directory = true;
|
|
if (!$valid) {
|
|
switch ($results->getReasonForEmptyResultSet()) {
|
|
case DiffusionBrowseResultSet::REASON_IS_FILE:
|
|
$valid = true;
|
|
$is_directory = false;
|
|
break;
|
|
case DiffusionBrowseResultSet::REASON_IS_EMPTY:
|
|
$valid = true;
|
|
break;
|
|
}
|
|
}
|
|
if ($is_directory && substr($path, -1) != '/') {
|
|
$path .= '/';
|
|
}
|
|
if (substr($path, 0, 1) != '/') {
|
|
$path = '/'.$path;
|
|
}
|
|
if (empty($cur_paths[$repository_phid][$path]) && $valid) {
|
|
$touched_repos[$repository_phid] = true;
|
|
$excluded = idx(reset($dicts), 'excluded', 0);
|
|
$add_paths[$repository_phid][$path] = $excluded;
|
|
$obj = new PhabricatorOwnersPath();
|
|
$obj->setPackageID($this->getID());
|
|
$obj->setRepositoryPHID($repository_phid);
|
|
$obj->setPath($path);
|
|
$obj->setExcluded($excluded);
|
|
$obj->save();
|
|
}
|
|
}
|
|
}
|
|
unset($this->unsavedPaths);
|
|
}
|
|
|
|
$this->saveTransaction();
|
|
|
|
if ($is_new) {
|
|
$mail = new PackageCreateMail($this);
|
|
} else {
|
|
$mail = new PackageModifyMail(
|
|
$this,
|
|
array_keys($add_owners),
|
|
array_keys($remove_owners),
|
|
array_keys($all_owners),
|
|
array_keys($touched_repos),
|
|
$add_paths,
|
|
$remove_paths);
|
|
}
|
|
$mail->setActor($this->getActor());
|
|
$mail->send();
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public function delete() {
|
|
$mails = id(new PackageDeleteMail($this))
|
|
->setActor($this->getActor())
|
|
->prepareMails();
|
|
|
|
$this->openTransaction();
|
|
foreach ($this->loadOwners() as $owner) {
|
|
$owner->delete();
|
|
}
|
|
foreach ($this->loadPaths() as $path) {
|
|
$path->delete();
|
|
}
|
|
|
|
$ret = parent::delete();
|
|
|
|
$this->saveTransaction();
|
|
|
|
foreach ($mails as $mail) {
|
|
$mail->saveAndSend();
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
private static function splitPath($path) {
|
|
$result = array('/');
|
|
$trailing_slash = preg_match('@/$@', $path) ? '/' : '';
|
|
$path = trim($path, '/');
|
|
$parts = explode('/', $path);
|
|
while (count($parts)) {
|
|
$result[] = '/'.implode('/', $parts).$trailing_slash;
|
|
$trailing_slash = '/';
|
|
array_pop($parts);
|
|
}
|
|
return $result;
|
|
}
|
|
}
|