Something went wrong. Try again.
@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
Something went wrong. Try again.
11 kB · 368 lines
PHP
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369<?php
abstract class PhabricatorSQLPatchList extends Phobject {
abstract public function getNamespace(); abstract public function getPatches();
/** * Examine a directory for `.php` and `.sql` files and build patch * specifications for them. */ protected function buildPatchesFromDirectory($directory) { $patch_list = Filesystem::listDirectory( $directory, $include_hidden = false);
sort($patch_list); $patches = array();
foreach ($patch_list as $patch) { $matches = null; if (!preg_match('/\.(sql|php|db)$/', $patch, $matches)) { throw new Exception( pht( 'Unknown patch "%s" in "%s", '. 'expected ".php" or ".sql" or ".db" suffix.', $patch, $directory)); }
$patch_type = $matches[1]; $patch_full_path = rtrim($directory, '/').'/'.$patch;
$attributes = array(); switch ($patch_type) { case 'php': $attributes = $this->getPHPPatchAttributes( $patch, $patch_full_path); $patch_name = $patch_full_path; break;
case 'db': $attributes = $this->getDBPatchAttributes( $patch, $patch_full_path); $patch_name = $attributes['name']; break;
default: $patch_name = $patch_full_path; break; }
$patches[$patch] = array( 'type' => $patch_type, 'name' => $patch_name, ) + $attributes; }
return $patches; }
final public static function buildAllPatches() { $patch_lists = id(new PhutilClassMapQuery()) ->setAncestorClass(self::class) ->setUniqueMethod('getNamespace') ->execute();
$specs = array(); $seen_namespaces = array();
$phases = PhabricatorStoragePatch::getPhaseList(); $phases = array_fuse($phases);
$default_phase = PhabricatorStoragePatch::getDefaultPhase();
foreach ($patch_lists as $patch_list) { $last_keys = array_fill_keys( array_keys($phases), null);
foreach ($patch_list->getPatches() as $key => $patch) { if (!is_array($patch)) { throw new Exception( pht( "%s '%s' has a patch '%s' which is not an array.", self::class, get_class($patch_list), $key)); }
$valid = array( 'type' => true, 'name' => true, 'after' => true, 'legacy' => true, 'dead' => true, 'phase' => true, );
foreach ($patch as $pkey => $pval) { if (empty($valid[$pkey])) { throw new Exception( pht( "%s '%s' has a patch, '%s', with an unknown property, '%s'.". "Patches must have only valid keys: %s.", self::class, get_class($patch_list), $key, $pkey, implode(', ', array_keys($valid)))); } }
if (is_numeric($key)) { throw new Exception( pht( "%s '%s' has a patch with a numeric key, '%s'. ". "Patches must use string keys.", self::class, get_class($patch_list), $key)); }
if (strpos($key, ':') !== false) { throw new Exception( pht( "%s '%s' has a patch with a colon in the key name, '%s'. ". "Patch keys may not contain colons.", self::class, get_class($patch_list), $key)); }
$namespace = $patch_list->getNamespace(); $full_key = "{$namespace}:{$key}";
if (isset($specs[$full_key])) { throw new Exception( pht( "%s '%s' has a patch '%s' which duplicates an ". "existing patch key.", self::class, get_class($patch_list), $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( pht( "Only patches in the '%s' namespace may contain '%s' keys.", 'phabricator', 'legacy')); } } else { $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 ($last_key === null && $patch_phase === $default_phase) { throw new Exception( pht( "Patch '%s' is missing key 'after', and is the first patch ". "in the patch list '%s', 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.", $full_key, get_class($patch_list))); } else { if ($last_key === null) { $patch['after'] = array(); } else { $patch['after'] = array($last_key); } } } $last_keys[$patch_phase] = $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( pht( "Patch '%s' is missing key '%s'. Every patch must have a type.", "{$namespace}:{$key}", 'type')); }
switch ($type) { case 'db': case 'sql': case 'php': break; default: throw new Exception( pht( "Patch '%s' has unknown patch type '%s'.", "{$namespace}:{$key}", $type)); }
$specs[$full_key] = $patch; } }
foreach ($specs as $key => $patch) { foreach ($patch['after'] as $after) { if (empty($specs[$after])) { throw new Exception( pht( "Patch '%s' references nonexistent dependency, '%s'. ". "Patches may only depend on patches which actually exist.", $key, $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(); foreach ($specs as $full_key => $spec) { $patches[$full_key] = new PhabricatorStoragePatch($spec); }
// TODO: Detect cycles?
$patches = msortv($patches, 'newSortVector');
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; }
private function getDBPatchAttributes($patch_name, $full_path) { $content = Filesystem::readFile($full_path); // Should be a valid JSON $data = phutil_json_decode($content);
return array_select_keys($data, array('name', 'after', 'dead')); }}