diff --git a/src/applications/differential/landing/DifferentialLandingStrategy.php b/src/applications/differential/landing/DifferentialLandingStrategy.php index 96d948f608..c62ca2ce23 100644 --- a/src/applications/differential/landing/DifferentialLandingStrategy.php +++ b/src/applications/differential/landing/DifferentialLandingStrategy.php @@ -1,83 +1,83 @@ getId(); return id(new PhabricatorActionView()) ->setRenderAsForm(true) ->setWorkflow(true) ->setName($name) ->setHref("/differential/revision/land/{$revision_id}/{$strategy}/"); } /** * Check if this action should be disabled, and explain why. * * By default, this method checks for push permissions, and for the * revision being Accepted. * * @return False for "not disabled"; human-readable text explaining why, if * it is disabled. */ public function isActionDisabled( PhabricatorUser $viewer, DifferentialRevision $revision, PhabricatorRepository $repository) { $status = $revision->getStatus(); if ($status != ArcanistDifferentialRevisionStatus::ACCEPTED) { return pht('Only Accepted revisions can be landed.'); } if (!PhabricatorPolicyFilter::hasCapability( $viewer, $repository, DiffusionPushCapability::CAPABILITY)) { return pht('You do not have permissions to push to this repository.'); } return false; } /** * Might break if repository is not Git. */ protected function getGitWorkspace(PhabricatorRepository $repository) { try { return DifferentialGetWorkingCopy::getCleanGitWorkspace($repository); } catch (Exception $e) { throw new PhutilProxyException('Failed to allocate a workspace', $e); } } /** * Might break if repository is not Mercurial. */ protected function getMercurialWorkspace(PhabricatorRepository $repository) { try { return DifferentialGetWorkingCopy::getCleanMercurialWorkspace( $repository); } catch (Exception $e) { throw new PhutilProxyException('Failed to allocate a workspace', $e); } } } diff --git a/src/applications/drydock/controller/DrydockController.php b/src/applications/drydock/controller/DrydockController.php index 41f82b6bfe..f8ee24f18e 100644 --- a/src/applications/drydock/controller/DrydockController.php +++ b/src/applications/drydock/controller/DrydockController.php @@ -1,11 +1,11 @@ buildSideNavView()->getMenu(); } } diff --git a/src/applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php b/src/applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php index 39b4dd959b..fbe70f6789 100644 --- a/src/applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php +++ b/src/applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php @@ -1,34 +1,34 @@ 1, 'excluded' => 0, 'path' => 'src/'), array('id' => 1, 'excluded' => 1, 'path' => 'src/releeph/'), array('id' => 2, 'excluded' => 0, 'path' => 'src/releeph/'), ); $paths = array( 'src/' => array('src/a.php' => true, 'src/releeph/b.php' => true), 'src/releeph/' => array('src/releeph/b.php' => true), ); $this->assertEqual( array( 1 => strlen('src/'), 2 => strlen('src/releeph/'), ), PhabricatorOwnersPackage::findLongestPathsPerPackage($rows, $paths)); $paths = array( 'src/' => array('src/releeph/b.php' => true), 'src/releeph/' => array('src/releeph/b.php' => true), ); $this->assertEqual( array( 2 => strlen('src/releeph/'), ), PhabricatorOwnersPackage::findLongestPathsPerPackage($rows, $paths)); } } diff --git a/src/applications/phragment/controller/PhragmentController.php b/src/applications/phragment/controller/PhragmentController.php index 25ebd6a5ae..254444928b 100644 --- a/src/applications/phragment/controller/PhragmentController.php +++ b/src/applications/phragment/controller/PhragmentController.php @@ -1,233 +1,233 @@ setViewer($this->getRequest()->getUser()) ->needLatestVersion(true) ->withPaths($combinations) ->execute(); foreach ($combinations as $combination) { $found = false; foreach ($results as $fragment) { if ($fragment->getPath() === $combination) { $fragments[] = $fragment; $found = true; break; } } if (!$found) { return null; } } return $fragments; } protected function buildApplicationCrumbsWithPath(array $fragments) { $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb('/', '/phragment/'); foreach ($fragments as $parent) { $crumbs->addTextCrumb( $parent->getName(), '/phragment/browse/'.$parent->getPath()); } return $crumbs; } protected function createCurrentFragmentView($fragment, $is_history_view) { if ($fragment === null) { return null; } $viewer = $this->getRequest()->getUser(); $phids = array(); $phids[] = $fragment->getLatestVersionPHID(); $snapshot_phids = array(); $snapshots = id(new PhragmentSnapshotQuery()) ->setViewer($viewer) ->withPrimaryFragmentPHIDs(array($fragment->getPHID())) ->execute(); foreach ($snapshots as $snapshot) { $phids[] = $snapshot->getPHID(); $snapshot_phids[] = $snapshot->getPHID(); } $this->loadHandles($phids); $file = null; $file_uri = null; if (!$fragment->isDirectory()) { $file = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withPHIDs(array($fragment->getLatestVersion()->getFilePHID())) ->executeOne(); if ($file !== null) { $file_uri = $file->getDownloadURI(); } } $header = id(new PHUIHeaderView()) ->setHeader($fragment->getName()) ->setPolicyObject($fragment) ->setUser($viewer); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $fragment, PhabricatorPolicyCapability::CAN_EDIT); $zip_uri = $this->getApplicationURI('zip/'.$fragment->getPath()); $actions = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObject($fragment) ->setObjectURI($fragment->getURI()); $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Download Fragment')) ->setHref($this->isCorrectlyConfigured() ? $file_uri : null) ->setDisabled($file === null || !$this->isCorrectlyConfigured()) ->setIcon('fa-download')); $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Download Contents as ZIP')) ->setHref($this->isCorrectlyConfigured() ? $zip_uri : null) ->setDisabled(!$this->isCorrectlyConfigured()) ->setIcon('fa-floppy-o')); if (!$fragment->isDirectory()) { $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Update Fragment')) ->setHref($this->getApplicationURI('update/'.$fragment->getPath())) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit) ->setIcon('fa-refresh')); } else { $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Convert to File')) ->setHref($this->getApplicationURI('update/'.$fragment->getPath())) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit) ->setIcon('fa-file-o')); } $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Set Fragment Policies')) ->setHref($this->getApplicationURI('policy/'.$fragment->getPath())) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit) ->setIcon('fa-asterisk')); if ($is_history_view) { $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('View Child Fragments')) ->setHref($this->getApplicationURI('browse/'.$fragment->getPath())) ->setIcon('fa-search-plus')); } else { $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('View History')) ->setHref($this->getApplicationURI('history/'.$fragment->getPath())) ->setIcon('fa-list')); } $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Create Snapshot')) ->setHref($this->getApplicationURI( 'snapshot/create/'.$fragment->getPath())) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit) ->setIcon('fa-files-o')); $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Promote Snapshot to Here')) ->setHref($this->getApplicationURI( 'snapshot/promote/latest/'.$fragment->getPath())) ->setWorkflow(true) ->setDisabled(!$can_edit) ->setIcon('fa-arrow-circle-up')); $properties = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($fragment) ->setActionList($actions); if (!$fragment->isDirectory()) { if ($fragment->isDeleted()) { $properties->addProperty( pht('Type'), pht('File (Deleted)')); } else { $properties->addProperty( pht('Type'), pht('File')); } $properties->addProperty( pht('Latest Version'), $this->renderHandlesForPHIDs(array($fragment->getLatestVersionPHID()))); } else { $properties->addProperty( pht('Type'), pht('Directory')); } if (count($snapshot_phids) > 0) { $properties->addProperty( pht('Snapshots'), $this->renderHandlesForPHIDs($snapshot_phids)); } return id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); } - function renderConfigurationWarningIfRequired() { + public function renderConfigurationWarningIfRequired() { $alt = PhabricatorEnv::getEnvConfig('security.alternate-file-domain'); if ($alt === null) { return id(new AphrontErrorView()) ->setTitle(pht('security.alternate-file-domain must be configured!')) ->setSeverity(AphrontErrorView::SEVERITY_ERROR) ->appendChild(phutil_tag('p', array(), pht( 'Because Phragment generates files (such as ZIP archives and '. 'patches) as they are requested, it requires that you configure '. 'the `security.alternate-file-domain` option. This option on it\'s '. 'own will also provide additional security when serving files '. 'across Phabricator.'))); } return null; } /** * We use this to disable the download links if the alternate domain is * not configured correctly. Although the download links will mostly work * for logged in users without an alternate domain, the behaviour is * reasonably non-consistent and will deny public users, even if policies * are configured otherwise (because the Files app does not support showing * the info page to viewers who are not logged in). */ - function isCorrectlyConfigured() { + public function isCorrectlyConfigured() { $alt = PhabricatorEnv::getEnvConfig('security.alternate-file-domain'); return $alt !== null; } } diff --git a/src/applications/phragment/controller/PhragmentRevertController.php b/src/applications/phragment/controller/PhragmentRevertController.php index 9b13a79277..84e8cbb71e 100644 --- a/src/applications/phragment/controller/PhragmentRevertController.php +++ b/src/applications/phragment/controller/PhragmentRevertController.php @@ -1,87 +1,87 @@ dblob = $data['dblob']; $this->id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); $fragment = id(new PhragmentFragmentQuery()) ->setViewer($viewer) ->withPaths(array($this->dblob)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if ($fragment === null) { return new Aphront404Response(); } $version = id(new PhragmentFragmentVersionQuery()) ->setViewer($viewer) ->withFragmentPHIDs(array($fragment->getPHID())) ->withIDs(array($this->id)) ->executeOne(); if ($version === null) { return new Aphront404Response(); } if ($request->isDialogFormPost()) { $file_phid = $version->getFilePHID(); $file = null; if ($file_phid !== null) { $file = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withPHIDs(array($file_phid)) ->executeOne(); if ($file === null) { throw new Exception( 'The file associated with this version was not found.'); } } if ($file === null) { $fragment->deleteFile($viewer); } else { $fragment->updateFromFile($viewer, $file); } return id(new AphrontRedirectResponse()) ->setURI($this->getApplicationURI('/history/'.$this->dblob)); } return $this->createDialog($fragment, $version); } - function createDialog( + public function createDialog( PhragmentFragment $fragment, PhragmentFragmentVersion $version) { $request = $this->getRequest(); $viewer = $request->getUser(); $dialog = id(new AphrontDialogView()) ->setTitle(pht('Really revert this fragment?')) ->setUser($request->getUser()) ->addSubmitButton(pht('Revert')) ->addCancelButton(pht('Cancel')) ->appendParagraph(pht( 'Reverting this fragment to version %d will create a new version of '. 'the fragment. It will not delete any version history.', $version->getSequence(), $version->getSequence())); return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php b/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php index 26591d4aef..715280a2c2 100644 --- a/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php +++ b/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php @@ -1,54 +1,54 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); $snapshot = id(new PhragmentSnapshotQuery()) ->setViewer($viewer) ->requireCapabilities(array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->withIDs(array($this->id)) ->executeOne(); if ($snapshot === null) { return new Aphront404Response(); } if ($request->isDialogFormPost()) { $fragment_uri = $snapshot->getPrimaryFragment()->getURI(); $snapshot->delete(); return id(new AphrontRedirectResponse()) ->setURI($fragment_uri); } return $this->createDialog(); } - function createDialog() { + public function createDialog() { $request = $this->getRequest(); $viewer = $request->getUser(); $dialog = id(new AphrontDialogView()) ->setTitle(pht('Really delete this snapshot?')) ->setUser($request->getUser()) ->addSubmitButton(pht('Delete')) ->addCancelButton(pht('Cancel')) ->appendParagraph(pht( 'Deleting this snapshot is a permanent operation. You can not '. 'recover the state of the snapshot.')); return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php b/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php index 5d45842ebe..18d7df6a5a 100644 --- a/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php +++ b/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php @@ -1,195 +1,195 @@ dblob = idx($data, 'dblob', null); $this->id = idx($data, 'id', null); } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); // When the user is promoting a snapshot to the latest version, the // identifier is a fragment path. if ($this->dblob !== null) { $this->targetFragment = id(new PhragmentFragmentQuery()) ->setViewer($viewer) ->requireCapabilities(array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->withPaths(array($this->dblob)) ->executeOne(); if ($this->targetFragment === null) { return new Aphront404Response(); } $this->snapshots = id(new PhragmentSnapshotQuery()) ->setViewer($viewer) ->withPrimaryFragmentPHIDs(array($this->targetFragment->getPHID())) ->execute(); } // When the user is promoting a snapshot to another snapshot, the // identifier is another snapshot ID. if ($this->id !== null) { $this->targetSnapshot = id(new PhragmentSnapshotQuery()) ->setViewer($viewer) ->requireCapabilities(array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->withIDs(array($this->id)) ->executeOne(); if ($this->targetSnapshot === null) { return new Aphront404Response(); } $this->snapshots = id(new PhragmentSnapshotQuery()) ->setViewer($viewer) ->withPrimaryFragmentPHIDs(array( $this->targetSnapshot->getPrimaryFragmentPHID(), )) ->execute(); } // If there's no identifier, just 404. if ($this->snapshots === null) { return new Aphront404Response(); } // Work out what options the user has. $this->options = mpull( $this->snapshots, 'getName', 'getID'); if ($this->id !== null) { unset($this->options[$this->id]); } // If there's no options, show a dialog telling the // user there are no snapshots to promote. if (count($this->options) === 0) { return id(new AphrontDialogResponse())->setDialog( id(new AphrontDialogView()) ->setTitle(pht('No snapshots to promote')) ->appendParagraph(pht( 'There are no snapshots available to promote.')) ->setUser($request->getUser()) ->addCancelButton(pht('Cancel'))); } // Handle snapshot promotion. if ($request->isDialogFormPost()) { $snapshot = id(new PhragmentSnapshotQuery()) ->setViewer($viewer) ->withIDs(array($request->getStr('snapshot'))) ->executeOne(); if ($snapshot === null) { return new Aphront404Response(); } $snapshot->openTransaction(); // Delete all existing child entries. $children = id(new PhragmentSnapshotChildQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withSnapshotPHIDs(array($snapshot->getPHID())) ->execute(); foreach ($children as $child) { $child->delete(); } if ($this->id === null) { // The user is promoting the snapshot to the latest version. $children = id(new PhragmentFragmentQuery()) ->setViewer($viewer) ->needLatestVersion(true) ->withLeadingPath($this->targetFragment->getPath().'/') ->execute(); // Add the primary fragment. id(new PhragmentSnapshotChild()) ->setSnapshotPHID($snapshot->getPHID()) ->setFragmentPHID($this->targetFragment->getPHID()) ->setFragmentVersionPHID( $this->targetFragment->getLatestVersionPHID()) ->save(); // Add all of the child fragments. foreach ($children as $child) { id(new PhragmentSnapshotChild()) ->setSnapshotPHID($snapshot->getPHID()) ->setFragmentPHID($child->getPHID()) ->setFragmentVersionPHID($child->getLatestVersionPHID()) ->save(); } } else { // The user is promoting the snapshot to another snapshot. We just // copy the other snapshot's child entries and change the snapshot // PHID to make it identical. $children = id(new PhragmentSnapshotChildQuery()) ->setViewer($viewer) ->withSnapshotPHIDs(array($this->targetSnapshot->getPHID())) ->execute(); foreach ($children as $child) { id(new PhragmentSnapshotChild()) ->setSnapshotPHID($snapshot->getPHID()) ->setFragmentPHID($child->getFragmentPHID()) ->setFragmentVersionPHID($child->getFragmentVersionPHID()) ->save(); } } $snapshot->saveTransaction(); if ($this->id === null) { return id(new AphrontRedirectResponse()) ->setURI($this->targetFragment->getURI()); } else { return id(new AphrontRedirectResponse()) ->setURI($this->targetSnapshot->getURI()); } } return $this->createDialog(); } - function createDialog() { + public function createDialog() { $request = $this->getRequest(); $viewer = $request->getUser(); $dialog = id(new AphrontDialogView()) ->setTitle(pht('Promote which snapshot?')) ->setUser($request->getUser()) ->addSubmitButton(pht('Promote')) ->addCancelButton(pht('Cancel')); if ($this->id === null) { // The user is promoting a snapshot to the latest version. $dialog->appendParagraph(pht( 'Select the snapshot you want to promote to the latest version:')); } else { // The user is promoting a snapshot to another snapshot. $dialog->appendParagraph(pht( "Select the snapshot you want to promote to '%s':", $this->targetSnapshot->getName())); } $dialog->appendChild( id(new AphrontFormSelectControl()) ->setUser($viewer) ->setName('snapshot') ->setOptions($this->options)); return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/infrastructure/log/PhabricatorAccessLog.php b/src/infrastructure/log/PhabricatorAccessLog.php index 2b77d144cd..5b6d37d74f 100644 --- a/src/infrastructure/log/PhabricatorAccessLog.php +++ b/src/infrastructure/log/PhabricatorAccessLog.php @@ -1,41 +1,41 @@ setFailQuietly(true) ->setData( array( 'D' => date('r'), 'h' => php_uname('n'), 'p' => getmypid(), 'e' => time(), )); self::$log = $log; } return self::$log; } } diff --git a/src/infrastructure/log/PhabricatorSSHLog.php b/src/infrastructure/log/PhabricatorSSHLog.php index 12b8001b5a..9f9f75b41b 100644 --- a/src/infrastructure/log/PhabricatorSSHLog.php +++ b/src/infrastructure/log/PhabricatorSSHLog.php @@ -1,52 +1,52 @@ date('r'), 'h' => php_uname('n'), 'p' => getmypid(), 'e' => time(), ); $sudo_user = PhabricatorEnv::getEnvConfig('phd.user'); if (strlen($sudo_user)) { $data['S'] = $sudo_user; } if (function_exists('posix_geteuid')) { $system_uid = posix_geteuid(); $system_info = posix_getpwuid($system_uid); $data['s'] = idx($system_info, 'name'); } $client = getenv('SSH_CLIENT'); if (strlen($client)) { $remote_address = head(explode(' ', $client)); $data['r'] = $remote_address; } $log = id(new PhutilDeferredLog($path, $format)) ->setFailQuietly(true) ->setData($data); self::$log = $log; } return self::$log; } } diff --git a/src/infrastructure/storage/patch/PhabricatorSQLPatchList.php b/src/infrastructure/storage/patch/PhabricatorSQLPatchList.php index 282cbbd822..0eefe68069 100644 --- a/src/infrastructure/storage/patch/PhabricatorSQLPatchList.php +++ b/src/infrastructure/storage/patch/PhabricatorSQLPatchList.php @@ -1,181 +1,181 @@ $matches[1], 'name' => rtrim($directory, '/').'/'.$patch, ); } return $patches; } final public static function buildAllPatches() { $patch_lists = id(new PhutilSymbolLoader()) ->setAncestorClass('PhabricatorSQLPatchList') ->setConcreteOnly(true) ->selectAndLoadSymbols(); $specs = array(); $seen_namespaces = array(); foreach ($patch_lists as $patch_class) { $patch_class = $patch_class['name']; $patch_list = newv($patch_class, array()); $namespace = $patch_list->getNamespace(); if (isset($seen_namespaces[$namespace])) { $prior = $seen_namespaces[$namespace]; throw new Exception( "PatchList '{$patch_class}' has the same namespace, '{$namespace}', ". "as another patch list class, '{$prior}'. Each patch list MUST have ". "a unique namespace."); } $last_key = null; foreach ($patch_list->getPatches() as $key => $patch) { if (!is_array($patch)) { throw new Exception( "PatchList '{$patch_class}' has a patch '{$key}' which is not ". "an array."); } $valid = array( 'type' => true, 'name' => true, 'after' => true, 'legacy' => true, 'dead' => true, ); foreach ($patch as $pkey => $pval) { if (empty($valid[$pkey])) { throw new Exception( "PatchList '{$patch_class}' has a patch, '{$key}', with an ". "unknown property, '{$pkey}'. Patches must have only valid ". "keys: ".implode(', ', array_keys($valid)).'.'); } } if (is_numeric($key)) { throw new Exception( "PatchList '{$patch_class}' has a patch with a numeric key, ". "'{$key}'. Patches must use string keys."); } if (strpos($key, ':') !== false) { throw new Exception( "PatchList '{$patch_class}' has a patch with a colon in the ". "key name, '{$key}'. Patch keys may not contain colons."); } $full_key = "{$namespace}:{$key}"; if (isset($specs[$full_key])) { throw new Exception( "PatchList '{$patch_class}' has a patch '{$key}' which ". "duplicates an existing patch key."); } $patch['key'] = $key; $patch['fullKey'] = $full_key; $patch['dead'] = (bool)idx($patch, 'dead', false); if (isset($patch['legacy'])) { if ($namespace != 'phabricator') { throw new Exception( "Only patches in the 'phabricator' namespace may contain ". "'legacy' keys."); } } else { $patch['legacy'] = false; } if (!array_key_exists('after', $patch)) { if ($last_key === null) { throw new Exception( "Patch '{$full_key}' is missing key 'after', and is the first ". "patch in the patch list '{$patch_class}', so its application ". "order can not be determined implicitly. The first patch in a ". "patch list must list the patch or patches it depends on ". "explicitly."); } else { $patch['after'] = array($last_key); } } $last_key = $full_key; foreach ($patch['after'] as $after_key => $after) { if (strpos($after, ':') === false) { $patch['after'][$after_key] = $namespace.':'.$after; } } $type = idx($patch, 'type'); if (!$type) { throw new Exception( "Patch '{$namespace}:{$key}' is missing key 'type'. Every patch ". "must have a type."); } switch ($type) { case 'db': case 'sql': case 'php': break; default: throw new Exception( "Patch '{$namespace}:{$key}' has unknown patch type '{$type}'."); } $specs[$full_key] = $patch; } } foreach ($specs as $key => $patch) { foreach ($patch['after'] as $after) { if (empty($specs[$after])) { throw new Exception( "Patch '{$key}' references nonexistent dependency, '{$after}'. ". "Patches may only depend on patches which actually exist."); } } } $patches = array(); foreach ($specs as $full_key => $spec) { $patches[$full_key] = new PhabricatorStoragePatch($spec); } // TODO: Detect cycles? return $patches; } }