Changeset View
Changeset View
Standalone View
Standalone View
src/infrastructure/storage/patch/PhabricatorSQLPatchList.php
| Show All 21 Lines | foreach ($patch_list as $patch) { | ||||
| if (!preg_match('/\.(sql|php)$/', $patch, $matches)) { | if (!preg_match('/\.(sql|php)$/', $patch, $matches)) { | ||||
| throw new Exception( | throw new Exception( | ||||
| pht( | pht( | ||||
| 'Unknown patch "%s" in "%s", expected ".php" or ".sql" suffix.', | 'Unknown patch "%s" in "%s", expected ".php" or ".sql" suffix.', | ||||
| $patch, | $patch, | ||||
| $directory)); | $directory)); | ||||
| } | } | ||||
| $patch_type = $matches[1]; | |||||
| $patch_full_path = rtrim($directory, '/').'/'.$patch; | |||||
| $attributes = array(); | |||||
| if ($patch_type === 'php') { | |||||
| $attributes = $this->getPHPPatchAttributes( | |||||
| $patch, | |||||
| $patch_full_path); | |||||
| } | |||||
| $patches[$patch] = array( | $patches[$patch] = array( | ||||
| 'type' => $matches[1], | 'type' => $patch_type, | ||||
| 'name' => rtrim($directory, '/').'/'.$patch, | 'name' => $patch_full_path, | ||||
| ); | ) + $attributes; | ||||
| } | } | ||||
| return $patches; | return $patches; | ||||
| } | } | ||||
| final public static function buildAllPatches() { | final public static function buildAllPatches() { | ||||
| $patch_lists = id(new PhutilClassMapQuery()) | $patch_lists = id(new PhutilClassMapQuery()) | ||||
| ->setAncestorClass(__CLASS__) | ->setAncestorClass(__CLASS__) | ||||
| ->setUniqueMethod('getNamespace') | ->setUniqueMethod('getNamespace') | ||||
| ->execute(); | ->execute(); | ||||
| $specs = array(); | $specs = array(); | ||||
| $seen_namespaces = array(); | $seen_namespaces = array(); | ||||
| $phases = PhabricatorStoragePatch::getPhaseList(); | |||||
| $phases = array_fuse($phases); | |||||
| $default_phase = PhabricatorStoragePatch::getDefaultPhase(); | |||||
| foreach ($patch_lists as $patch_list) { | foreach ($patch_lists as $patch_list) { | ||||
| $last_key = null; | $last_keys = array_fill_keys( | ||||
| array_keys($phases), | |||||
| null); | |||||
| foreach ($patch_list->getPatches() as $key => $patch) { | foreach ($patch_list->getPatches() as $key => $patch) { | ||||
| if (!is_array($patch)) { | if (!is_array($patch)) { | ||||
| throw new Exception( | throw new Exception( | ||||
| pht( | pht( | ||||
| "%s '%s' has a patch '%s' which is not an array.", | "%s '%s' has a patch '%s' which is not an array.", | ||||
| __CLASS__, | __CLASS__, | ||||
| get_class($patch_list), | get_class($patch_list), | ||||
| $key)); | $key)); | ||||
| } | } | ||||
| $valid = array( | $valid = array( | ||||
| 'type' => true, | 'type' => true, | ||||
| 'name' => true, | 'name' => true, | ||||
| 'after' => true, | 'after' => true, | ||||
| 'legacy' => true, | 'legacy' => true, | ||||
| 'dead' => true, | 'dead' => true, | ||||
| 'phase' => true, | |||||
| ); | ); | ||||
| foreach ($patch as $pkey => $pval) { | foreach ($patch as $pkey => $pval) { | ||||
| if (empty($valid[$pkey])) { | if (empty($valid[$pkey])) { | ||||
| throw new Exception( | throw new Exception( | ||||
| pht( | pht( | ||||
| "%s '%s' has a patch, '%s', with an unknown property, '%s'.". | "%s '%s' has a patch, '%s', with an unknown property, '%s'.". | ||||
| "Patches must have only valid keys: %s.", | "Patches must have only valid keys: %s.", | ||||
| ▲ Show 20 Lines • Show All 49 Lines • ▼ Show 20 Lines | foreach ($patch_lists as $patch_list) { | ||||
| "Only patches in the '%s' namespace may contain '%s' keys.", | "Only patches in the '%s' namespace may contain '%s' keys.", | ||||
| 'phabricator', | 'phabricator', | ||||
| 'legacy')); | 'legacy')); | ||||
| } | } | ||||
| } else { | } else { | ||||
| $patch['legacy'] = false; | $patch['legacy'] = false; | ||||
| } | } | ||||
| if (!array_key_exists('phase', $patch)) { | |||||
| $patch['phase'] = $default_phase; | |||||
| } | |||||
| $patch_phase = $patch['phase']; | |||||
| if (!isset($phases[$patch_phase])) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Storage patch "%s" specifies it should apply in phase "%s", '. | |||||
| 'but this phase is unrecognized. Valid phases are: %s.', | |||||
| $full_key, | |||||
| $patch_phase, | |||||
| implode(', ', array_keys($phases)))); | |||||
| } | |||||
| $last_key = $last_keys[$patch_phase]; | |||||
| if (!array_key_exists('after', $patch)) { | if (!array_key_exists('after', $patch)) { | ||||
| if ($last_key === null) { | if ($last_key === null && $patch_phase === $default_phase) { | ||||
| throw new Exception( | throw new Exception( | ||||
| pht( | pht( | ||||
| "Patch '%s' is missing key 'after', and is the first patch ". | "Patch '%s' is missing key 'after', and is the first patch ". | ||||
| "in the patch list '%s', so its application order can not be ". | "in the patch list '%s', so its application order can not be ". | ||||
| "determined implicitly. The first patch in a patch list must ". | "determined implicitly. The first patch in a patch list must ". | ||||
| "list the patch or patches it depends on explicitly.", | "list the patch or patches it depends on explicitly.", | ||||
| $full_key, | $full_key, | ||||
| get_class($patch_list))); | get_class($patch_list))); | ||||
| } else { | } else { | ||||
| if ($last_key === null) { | |||||
| $patch['after'] = array(); | |||||
| } else { | |||||
| $patch['after'] = array($last_key); | $patch['after'] = array($last_key); | ||||
| } | } | ||||
| } | } | ||||
| $last_key = $full_key; | } | ||||
| $last_keys[$patch_phase] = $full_key; | |||||
| foreach ($patch['after'] as $after_key => $after) { | foreach ($patch['after'] as $after_key => $after) { | ||||
| if (strpos($after, ':') === false) { | if (strpos($after, ':') === false) { | ||||
| $patch['after'][$after_key] = $namespace.':'.$after; | $patch['after'][$after_key] = $namespace.':'.$after; | ||||
| } | } | ||||
| } | } | ||||
| $type = idx($patch, 'type'); | $type = idx($patch, 'type'); | ||||
| Show All 27 Lines | foreach ($specs as $key => $patch) { | ||||
| if (empty($specs[$after])) { | if (empty($specs[$after])) { | ||||
| throw new Exception( | throw new Exception( | ||||
| pht( | pht( | ||||
| "Patch '%s' references nonexistent dependency, '%s'. ". | "Patch '%s' references nonexistent dependency, '%s'. ". | ||||
| "Patches may only depend on patches which actually exist.", | "Patches may only depend on patches which actually exist.", | ||||
| $key, | $key, | ||||
| $after)); | $after)); | ||||
| } | } | ||||
| $patch_phase = $patch['phase']; | |||||
| $after_phase = $specs[$after]['phase']; | |||||
| if ($patch_phase !== $after_phase) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Storage patch "%s" executes in phase "%s", but depends on '. | |||||
| 'patch "%s" which is in a different phase ("%s"). Patches '. | |||||
| 'may not have dependencies across phases.', | |||||
| $key, | |||||
| $patch_phase, | |||||
| $after, | |||||
| $after_phase)); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| $patches = array(); | $patches = array(); | ||||
| foreach ($specs as $full_key => $spec) { | foreach ($specs as $full_key => $spec) { | ||||
| $patches[$full_key] = new PhabricatorStoragePatch($spec); | $patches[$full_key] = new PhabricatorStoragePatch($spec); | ||||
| } | } | ||||
| // TODO: Detect cycles? | // TODO: Detect cycles? | ||||
| $patches = msortv($patches, 'newSortVector'); | |||||
| return $patches; | return $patches; | ||||
| } | } | ||||
| private function getPHPPatchAttributes($patch_name, $full_path) { | |||||
| $data = Filesystem::readFile($full_path); | |||||
| $phase_list = PhabricatorStoragePatch::getPhaseList(); | |||||
| $phase_map = array_fuse($phase_list); | |||||
| $attributes = array(); | |||||
| $lines = phutil_split_lines($data, false); | |||||
| foreach ($lines as $line) { | |||||
| // Skip over the "PHP" line. | |||||
| if (preg_match('(^<\?)', $line)) { | |||||
| continue; | |||||
| } | |||||
| // Skip over blank lines. | |||||
| if (!strlen(trim($line))) { | |||||
| continue; | |||||
| } | |||||
| // If this is a "//" comment... | |||||
| if (preg_match('(^\s*//)', $line)) { | |||||
| $matches = null; | |||||
| if (preg_match('(^\s*//\s*@(\S+)(?:\s+(.*))?\z)', $line, $matches)) { | |||||
| $attr_key = $matches[1]; | |||||
| $attr_value = trim(idx($matches, 2)); | |||||
| switch ($attr_key) { | |||||
| case 'phase': | |||||
| $phase_name = $attr_value; | |||||
| if (!strlen($phase_name)) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Storage patch "%s" specifies a "@phase" attribute with '. | |||||
| 'no phase value. Phase attributes must specify a value, '. | |||||
| 'like "@phase default".', | |||||
| $patch_name)); | |||||
| } | |||||
| if (!isset($phase_map[$phase_name])) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Storage patch "%s" specifies a "@phase" value ("%s"), '. | |||||
| 'but this is not a recognized phase. Valid phases '. | |||||
| 'are: %s.', | |||||
| $patch_name, | |||||
| $phase_name, | |||||
| implode(', ', $phase_list))); | |||||
| } | |||||
| if (isset($attributes['phase'])) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Storage patch "%s" specifies a "@phase" value ("%s"), '. | |||||
| 'but it already has a specified phase ("%s"). Patches '. | |||||
| 'may not specify multiple phases.', | |||||
| $patch_name, | |||||
| $phase_name, | |||||
| $attributes['phase'])); | |||||
| } | |||||
| $attributes[$attr_key] = $phase_name; | |||||
| break; | |||||
| default: | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Storage patch "%s" specifies attribute "%s", but this '. | |||||
| 'attribute is unknown.', | |||||
| $patch_name, | |||||
| $attr_key)); | |||||
| } | |||||
| } | |||||
| continue; | |||||
| } | |||||
| // If this is anything else, we're all done. Attributes must be marked | |||||
| // in the header of the file. | |||||
| break; | |||||
| } | |||||
| return $attributes; | |||||
| } | |||||
| } | } | ||||