Page MenuHomestyx hydra

No OneTemporary

diff --git a/resources/sql/patches/20130826.divinernode.sql b/resources/sql/patches/20130826.divinernode.sql
new file mode 100644
index 0000000000..265a7769b5
--- /dev/null
+++ b/resources/sql/patches/20130826.divinernode.sql
@@ -0,0 +1,5 @@
+ALTER TABLE {$NAMESPACE}_diviner.diviner_livesymbol
+ ADD nodeHash VARCHAR(64) COLLATE utf8_bin;
+
+ALTER TABLE {$NAMESPACE}_diviner.diviner_livesymbol
+ ADD UNIQUE KEY (nodeHash);
diff --git a/src/applications/diviner/atom/DivinerAtom.php b/src/applications/diviner/atom/DivinerAtom.php
index b4190ca7f9..22b0618162 100644
--- a/src/applications/diviner/atom/DivinerAtom.php
+++ b/src/applications/diviner/atom/DivinerAtom.php
@@ -1,365 +1,374 @@
<?php
final class DivinerAtom {
const TYPE_FILE = 'file';
const TYPE_ARTICLE = 'article';
+ const TYPE_METHOD = 'method';
private $type;
private $name;
private $file;
private $line;
private $hash;
private $contentRaw;
private $length;
private $language;
private $docblockRaw;
private $docblockText;
private $docblockMeta;
private $warnings = array();
private $parentHash;
private $childHashes = array();
private $context;
private $extends = array();
private $links = array();
private $book;
private $properties = array();
/**
* Returns a sorting key which imposes an unambiguous, stable order on atoms.
*/
public function getSortKey() {
return implode(
"\0",
array(
$this->getBook(),
$this->getType(),
$this->getContext(),
$this->getName(),
$this->getFile(),
sprintf('%08', $this->getLine()),
));
}
public function setBook($book) {
$this->book = $book;
return $this;
}
public function getBook() {
return $this->book;
}
public function setContext($context) {
$this->context = $context;
return $this;
}
public function getContext() {
return $this->context;
}
public static function getAtomSerializationVersion() {
return 2;
}
public function addWarning($warning) {
$this->warnings[] = $warning;
return $this;
}
public function getWarnings() {
return $this->warnings;
}
public function setDocblockRaw($docblock_raw) {
$this->docblockRaw = $docblock_raw;
$parser = new PhutilDocblockParser();
list($text, $meta) = $parser->parse($docblock_raw);
$this->docblockText = $text;
$this->docblockMeta = $meta;
return $this;
}
public function getDocblockRaw() {
return $this->docblockRaw;
}
public function getDocblockText() {
if ($this->docblockText === null) {
throw new Exception("Call setDocblockRaw() before getDocblockText()!");
}
return $this->docblockText;
}
public function getDocblockMeta() {
if ($this->docblockMeta === null) {
throw new Exception("Call setDocblockRaw() before getDocblockMeta()!");
}
return $this->docblockMeta;
}
public function getDocblockMetaValue($key, $default = null) {
$meta = $this->getDocblockMeta();
return idx($meta, $key, $default);
}
public function setDocblockMetaValue($key, $value) {
$meta = $this->getDocblockMeta();
$meta[$key] = $value;
$this->docblockMeta = $meta;
return $this;
}
public function setType($type) {
$this->type = $type;
return $this;
}
public function getType() {
return $this->type;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function setFile($file) {
$this->file = $file;
return $this;
}
public function getFile() {
return $this->file;
}
public function setLine($line) {
$this->line = $line;
return $this;
}
public function getLine() {
return $this->line;
}
public function setContentRaw($content_raw) {
$this->contentRaw = $content_raw;
return $this;
}
public function getContentRaw() {
return $this->contentRaw;
}
public function setHash($hash) {
$this->hash = $hash;
return $this;
}
public function addLink(DivinerAtomRef $ref) {
$this->links[] = $ref;
return $this;
}
public function addExtends(DivinerAtomRef $ref) {
$this->extends[] = $ref;
return $this;
}
public function getLinkDictionaries() {
return mpull($this->links, 'toDictionary');
}
public function getExtendsDictionaries() {
return mpull($this->extends, 'toDictionary');
}
+ public function getExtends() {
+ return $this->extends;
+ }
+
public function getHash() {
if ($this->hash) {
return $this->hash;
}
$parts = array(
$this->getType(),
$this->getName(),
$this->getFile(),
$this->getLine(),
$this->getLength(),
$this->getLanguage(),
$this->getContentRaw(),
$this->getDocblockRaw(),
$this->getProperties(),
mpull($this->extends, 'toHash'),
mpull($this->links, 'toHash'),
);
return md5(serialize($parts)).'N';
}
public function setLength($length) {
$this->length = $length;
return $this;
}
public function getLength() {
return $this->length;
}
public function setLanguage($language) {
$this->language = $language;
return $this;
}
public function getLanguage() {
return $this->language;
}
public function addChildHash($child_hash) {
$this->childHashes[] = $child_hash;
return $this;
}
public function getChildHashes() {
return $this->childHashes;
}
public function setParentHash($parent_hash) {
if ($this->parentHash) {
throw new Exception("Atom already has a parent!");
}
$this->parentHash = $parent_hash;
return $this;
}
public function getParentHash() {
return $this->parentHash;
}
public function addChild(DivinerAtom $atom) {
$atom->setParentHash($this->getHash());
$this->addChildHash($atom->getHash());
return $this;
}
public function getURI() {
$parts = array();
$parts[] = phutil_escape_uri_path_component($this->getType());
if ($this->getContext()) {
$parts[] = phutil_escape_uri_path_component($this->getContext());
}
$parts[] = phutil_escape_uri_path_component($this->getName());
$parts[] = null;
return implode('/', $parts);
}
public function toDictionary() {
// NOTE: If you change this format, bump the format version in
// getAtomSerializationVersion().
return array(
'book' => $this->getBook(),
'type' => $this->getType(),
'name' => $this->getName(),
'file' => $this->getFile(),
'line' => $this->getLine(),
'hash' => $this->getHash(),
'uri' => $this->getURI(),
'length' => $this->getLength(),
'context' => $this->getContext(),
'language' => $this->getLanguage(),
'docblockRaw' => $this->getDocblockRaw(),
'warnings' => $this->getWarnings(),
'parentHash' => $this->getParentHash(),
'childHashes' => $this->getChildHashes(),
'extends' => $this->getExtendsDictionaries(),
'links' => $this->getLinkDictionaries(),
'ref' => $this->getRef()->toDictionary(),
'properties' => $this->getProperties(),
);
}
public function getRef() {
$group = null;
$title = null;
if ($this->docblockMeta) {
$group = $this->getDocblockMetaValue('group');
$title = $this->getDocblockMetaValue('title');
}
return id(new DivinerAtomRef())
->setBook($this->getBook())
->setContext($this->getContext())
->setType($this->getType())
->setName($this->getName())
->setTitle($title)
->setGroup($group);
}
public static function newFromDictionary(array $dictionary) {
$atom = id(new DivinerAtom())
->setBook(idx($dictionary, 'book'))
->setType(idx($dictionary, 'type'))
->setName(idx($dictionary, 'name'))
->setFile(idx($dictionary, 'file'))
->setLine(idx($dictionary, 'line'))
->setHash(idx($dictionary, 'hash'))
->setLength(idx($dictionary, 'length'))
->setContext(idx($dictionary, 'context'))
->setLanguage(idx($dictionary, 'language'))
->setParentHash(idx($dictionary, 'parentHash'))
->setDocblockRaw(idx($dictionary, 'docblockRaw'))
->setProperties(idx($dictionary, 'properties'));
foreach (idx($dictionary, 'warnings', array()) as $warning) {
$atom->addWarning($warning);
}
foreach (idx($dictionary, 'childHashes', array()) as $child) {
$atom->addChildHash($child);
}
+ foreach (idx($dictionary, 'extends', array()) as $extends) {
+ $atom->addExtends(DivinerAtomRef::newFromDictionary($extends));
+ }
+
return $atom;
}
public function getProperty($key, $default = null) {
return idx($this->properties, $key, $default);
}
public function setProperty($key, $value) {
$this->properties[$key] = $value;
}
public function getProperties() {
return $this->properties;
}
public function setProperties(array $properties) {
$this->properties = $properties;
return $this;
}
public static function getThisAtomIsNotDocumentedString($type) {
switch ($type) {
case 'function':
return pht('This function is not documented.');
case 'class':
return pht('This class is not documented.');
case 'article':
return pht('This article is not documented.');
case 'method':
return pht('This method is not documented.');
default:
phlog("Need translation for '{$type}'.");
return pht('This %s is not documented.', $type);
}
}
}
diff --git a/src/applications/diviner/atomizer/DivinerPHPAtomizer.php b/src/applications/diviner/atomizer/DivinerPHPAtomizer.php
index e9161ac3f2..30928fe319 100644
--- a/src/applications/diviner/atomizer/DivinerPHPAtomizer.php
+++ b/src/applications/diviner/atomizer/DivinerPHPAtomizer.php
@@ -1,229 +1,313 @@
<?php
final class DivinerPHPAtomizer extends DivinerAtomizer {
protected function executeAtomize($file_name, $file_data) {
$future = xhpast_get_parser_future($file_data);
$tree = XHPASTTree::newFromDataAndResolvedExecFuture(
$file_data,
$future->resolve());
$atoms = array();
$root = $tree->getRootNode();
$func_decl = $root->selectDescendantsOfType('n_FUNCTION_DECLARATION');
foreach ($func_decl as $func) {
$name = $func->getChildByIndex(2);
$atom = id(new DivinerAtom())
->setType('function')
->setName($name->getConcreteString())
->setLine($func->getLineNumber())
->setFile($file_name);
$this->findAtomDocblock($atom, $func);
$this->parseParams($atom, $func);
$this->parseReturnType($atom, $func);
$atoms[] = $atom;
}
+ $class_types = array(
+ 'class' => 'n_CLASS_DECLARATION',
+ 'interface' => 'n_INTERFACE_DECLARATION',
+ );
+ foreach ($class_types as $atom_type => $node_type) {
+ $class_decls = $root->selectDescendantsOfType($node_type);
+ foreach ($class_decls as $class) {
+ $name = $class->getChildByIndex(1, 'n_CLASS_NAME');
+
+ $atom = id(new DivinerAtom())
+ ->setType($atom_type)
+ ->setName($name->getConcreteString())
+ ->setFile($file_name)
+ ->setLine($class->getLineNumber());
+
+ // If this exists, it is n_EXTENDS_LIST.
+ $extends = $class->getChildByIndex(2);
+ $extends_class = $extends->selectDescendantsOfType('n_CLASS_NAME');
+ foreach ($extends_class as $parent_class) {
+ $atom->addExtends(
+ DivinerAtomRef::newFromDictionary(
+ array(
+ 'type' => 'class',
+ 'name' => $parent_class->getConcreteString(),
+ )));
+ }
+
+ // If this exists, it is n_IMPLEMENTS_LIST.
+ $implements = $class->getChildByIndex(3);
+ $iface_names = $implements->selectDescendantsOfType('n_CLASS_NAME');
+ foreach ($iface_names as $iface_name) {
+ $atom->addExtends(
+ DivinerAtomRef::newFromDictionary(
+ array(
+ 'type' => 'interface',
+ 'name' => $iface_name->getConcreteString(),
+ )));
+ }
+
+ $this->findAtomDocblock($atom, $class);
+
+ $methods = $class->selectDescendantsOfType('n_METHOD_DECLARATION');
+ foreach ($methods as $method) {
+ $matom = id(new DivinerAtom())
+ ->setType('method');
+
+ $this->findAtomDocblock($matom, $method);
+
+ $attribute_list = $method->getChildByIndex(0);
+ $attributes = $attribute_list->selectDescendantsOfType('n_STRING');
+ if ($attributes) {
+ foreach ($attributes as $attribute) {
+ $attr = strtolower($attribute->getConcreteString());
+ switch ($attr) {
+ case 'static':
+ $matom->setProperty($attr, true);
+ break;
+ case 'public':
+ case 'protected':
+ case 'private':
+ $matom->setProperty('access', $attr);
+ break;
+ }
+ }
+ } else {
+ $matom->setProperty('access', 'public');
+ }
+
+ $this->parseParams($matom, $method);
+
+ $matom->setName($method->getChildByIndex(2)->getConcreteString());
+ $matom->setLine($method->getLineNumber());
+ $matom->setFile($file_name);
+
+ $this->parseReturnType($matom, $method);
+ $atom->addChild($matom);
+
+ $atoms[] = $matom;
+ }
+
+ $atoms[] = $atom;
+ }
+ }
+
return $atoms;
}
private function parseParams(DivinerAtom $atom, AASTNode $func) {
$params = $func
->getChildByIndex(3, 'n_DECLARATAION_PARAMETER_LIST')
->selectDescendantsOfType('n_DECLARATION_PARAMETER');
$param_spec = array();
if ($atom->getDocblockRaw()) {
$metadata = $atom->getDocblockMeta();
} else {
$metadata = array();
}
$docs = idx($metadata, 'param');
if ($docs) {
$docs = explode("\n", $docs);
$docs = array_filter($docs);
} else {
$docs = array();
}
if (count($docs)) {
if (count($docs) < count($params)) {
$atom->addWarning(
pht(
'This call takes %d parameters, but only %d are documented.',
count($params),
count($docs)));
}
}
foreach ($params as $param) {
$name = $param->getChildByIndex(1)->getConcreteString();
$dict = array(
'type' => $param->getChildByIndex(0)->getConcreteString(),
'default' => $param->getChildByIndex(2)->getConcreteString(),
);
if ($docs) {
$doc = array_shift($docs);
if ($doc) {
$dict += $this->parseParamDoc($atom, $doc, $name);
}
}
$param_spec[] = array(
'name' => $name,
) + $dict;
}
if ($docs) {
foreach ($docs as $doc) {
if ($doc) {
$param_spec[] = $this->parseParamDoc($atom, $doc, null);
}
}
}
// TODO: Find `assert_instances_of()` calls in the function body and
// add their type information here. See T1089.
$atom->setProperty('parameters', $param_spec);
}
private function findAtomDocblock(DivinerAtom $atom, XHPASTNode $node) {
$token = $node->getDocblockToken();
if ($token) {
$atom->setDocblockRaw($token->getValue());
return true;
} else {
$tokens = $node->getTokens();
if ($tokens) {
$prev = head($tokens);
while ($prev = $prev->getPrevToken()) {
if ($prev->isAnyWhitespace()) {
continue;
}
break;
}
if ($prev && $prev->isComment()) {
$value = $prev->getValue();
$matches = null;
if (preg_match('/@(return|param|task|author)/', $value, $matches)) {
$atom->addWarning(
pht(
'Atom "%s" is preceded by a comment containing "@%s", but the '.
'comment is not a documentation comment. Documentation '.
'comments must begin with "/**", followed by a newline. Did '.
'you mean to use a documentation comment? (As the comment is '.
'not a documentation comment, it will be ignored.)',
$atom->getName(),
$matches[1]));
}
}
}
$atom->setDocblockRaw('');
return false;
}
}
protected function parseParamDoc(DivinerAtom $atom, $doc, $name) {
$dict = array();
$split = preg_split('/\s+/', trim($doc), $limit = 2);
if (!empty($split[0])) {
$dict['doctype'] = $split[0];
}
if (!empty($split[1])) {
$docs = $split[1];
// If the parameter is documented like "@param int $num Blah blah ..",
// get rid of the `$num` part (which Diviner considers optional). If it
// is present and different from the declared name, raise a warning.
$matches = null;
if (preg_match('/^(\\$\S+)\s+/', $docs, $matches)) {
if ($name !== null) {
if ($matches[1] !== $name) {
$atom->addWarning(
pht(
'Parameter "%s" is named "%s" in the documentation. The '.
'documentation may be out of date.',
$name,
$matches[1]));
}
}
$docs = substr($docs, strlen($matches[0]));
}
$dict['docs'] = $docs;
}
return $dict;
}
private function parseReturnType(DivinerAtom $atom, XHPASTNode $decl) {
$return_spec = array();
$metadata = $atom->getDocblockMeta();
$return = idx($metadata, 'return');
if (!$return) {
$return = idx($metadata, 'returns');
if ($return) {
$atom->addWarning(
pht('Documentation uses `@returns`, but should use `@return`.'));
}
}
if ($atom->getName() == '__construct' && $atom->getType() == 'method') {
$return_spec = array(
'doctype' => 'this',
'docs' => '//Implicit.//',
);
if ($return) {
$atom->addWarning(
'Method __construct() has explicitly documented @return. The '.
'__construct() method always returns $this. Diviner documents '.
'this implicitly.');
}
} else if ($return) {
$split = preg_split('/\s+/', trim($return), $limit = 2);
if (!empty($split[0])) {
$type = $split[0];
}
if ($decl->getChildByIndex(1)->getTypeName() == 'n_REFERENCE') {
$type = $type.' &';
}
$docs = null;
if (!empty($split[1])) {
$docs = $split[1];
}
$return_spec = array(
'doctype' => $type,
'docs' => $docs,
);
} else {
$return_spec = array(
'type' => 'wild',
);
}
$atom->setProperty('return', $return_spec);
}
}
diff --git a/src/applications/diviner/controller/DivinerAtomController.php b/src/applications/diviner/controller/DivinerAtomController.php
index d5be8213c2..028800f28d 100644
--- a/src/applications/diviner/controller/DivinerAtomController.php
+++ b/src/applications/diviner/controller/DivinerAtomController.php
@@ -1,191 +1,284 @@
<?php
final class DivinerAtomController extends DivinerController {
private $bookName;
private $atomType;
private $atomName;
private $atomContext;
private $atomIndex;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->bookName = $data['book'];
$this->atomType = $data['type'];
$this->atomName = $data['name'];
$this->atomContext = nonempty(idx($data, 'context'), null);
$this->atomIndex = nonempty(idx($data, 'index'), null);
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$book = id(new DivinerBookQuery())
->setViewer($viewer)
->withNames(array($this->bookName))
->executeOne();
if (!$book) {
return new Aphront404Response();
}
// TODO: This query won't load ghosts, because they'll fail `needAtoms()`.
// Instead, we might want to load ghosts and render a message like
// "this thing existed in an older version, but no longer does", especially
// if we add content like comments.
$symbol = id(new DivinerAtomQuery())
->setViewer($viewer)
->withBookPHIDs(array($book->getPHID()))
->withTypes(array($this->atomType))
->withNames(array($this->atomName))
->withContexts(array($this->atomContext))
->withIndexes(array($this->atomIndex))
->needAtoms(true)
+ ->needExtends(true)
->executeOne();
if (!$symbol) {
return new Aphront404Response();
}
$atom = $symbol->getAtom();
+ $extends = $atom->getExtends();
+
+ $child_hashes = $atom->getChildHashes();
+ if ($child_hashes) {
+ $children = id(new DivinerAtomQuery())
+ ->setViewer($viewer)
+ ->withIncludeUndocumentable(true)
+ ->withNodeHashes($child_hashes)
+ ->execute();
+ } else {
+ $children = array();
+ }
+
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($book->getShortTitle())
->setHref('/book/'.$book->getName().'/'));
$atom_short_title = $atom->getDocblockMetaValue(
'short',
$symbol->getTitle());
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($atom_short_title));
$header = id(new PhabricatorHeaderView())
->setHeader($symbol->getTitle())
->addTag(
id(new PhabricatorTagView())
->setType(PhabricatorTagView::TYPE_STATE)
->setBackgroundColor(PhabricatorTagView::COLOR_BLUE)
->setName($this->renderAtomTypeName($atom->getType())));
$properties = id(new PhabricatorPropertyListView());
$group = $atom->getDocblockMetaValue('group');
if ($group) {
$group_name = $book->getGroupName($group);
} else {
$group_name = null;
}
$properties->addProperty(
pht('Defined'),
$atom->getFile().':'.$atom->getLine());
+
+ $this->buildExtendsAndImplements($properties, $symbol);
+
$warnings = $atom->getWarnings();
if ($warnings) {
$warnings = id(new AphrontErrorView())
->setErrors($warnings)
->setTitle(pht('Documentation Warnings'))
->setSeverity(AphrontErrorView::SEVERITY_WARNING);
}
$field = 'default';
$engine = id(new PhabricatorMarkupEngine())
->setViewer($viewer)
->addObject($symbol, $field)
->process();
$content = $engine->getOutput($symbol, $field);
if (strlen(trim($symbol->getMarkupText($field)))) {
$content = phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
array(
$content,
));
} else {
$undoc = DivinerAtom::getThisAtomIsNotDocumentedString($atom->getType());
$content = id(new AphrontErrorView())
->appendChild($undoc)
->setSeverity(AphrontErrorView::SEVERITY_NODATA);
}
$toc = $engine->getEngineMetadata(
$symbol,
$field,
PhutilRemarkupEngineRemarkupHeaderBlockRule::KEY_HEADER_TOC,
array());
$document = id(new PHUIDocumentView())
->setBook($book->getTitle(), $group_name)
->setHeader($header)
->appendChild($properties)
->appendChild($warnings)
->appendChild($content);
$parameters = $atom->getProperty('parameters');
if ($parameters !== null) {
$document->appendChild(
id(new PhabricatorHeaderView())
->setHeader(pht('Parameters')));
$document->appendChild(
id(new DivinerParameterTableView())
->setParameters($parameters));
}
$return = $atom->getProperty('return');
if ($return !== null) {
$document->appendChild(
id(new PhabricatorHeaderView())
->setHeader(pht('Return')));
$document->appendChild(
id(new DivinerReturnTableView())
->setReturn($return));
}
+ if ($children) {
+ $document->appendChild(
+ id(new PhabricatorHeaderView())
+ ->setHeader(pht('Methods')));
+ foreach ($children as $child) {
+ $document->appendChild(
+ id(new PhabricatorHeaderView())
+ ->setHeader($child->getName()));
+ }
+ }
+
if ($toc) {
$side = new PHUIListView();
$side->addMenuItem(
id(new PHUIListItemView())
->setName(pht('Contents'))
->setType(PHUIListItemView::TYPE_LABEL));
foreach ($toc as $key => $entry) {
$side->addMenuItem(
id(new PHUIListItemView())
->setName($entry[1])
->setHref('#'.$key));
}
$document->setSideNav($side, PHUIDocumentView::NAV_TOP);
}
return $this->buildApplicationPage(
array(
$crumbs,
$document,
),
array(
'title' => $symbol->getTitle(),
'device' => true,
));
}
private function renderAtomTypeName($name) {
return phutil_utf8_ucwords($name);
}
+ private function buildExtendsAndImplements(
+ PhabricatorPropertyListView $view,
+ DivinerLiveSymbol $symbol) {
+
+ $lineage = $this->getExtendsLineage($symbol);
+ if ($lineage) {
+ $lineage = mpull($lineage, 'getName');
+ $lineage = implode(' > ', $lineage);
+ $view->addProperty(pht('Extends'), $lineage);
+ }
+
+ $implements = $this->getImplementsLineage($symbol);
+ if ($implements) {
+ $items = array();
+ foreach ($implements as $spec) {
+ $via = $spec['via'];
+ $iface = $spec['interface'];
+ if ($via == $symbol) {
+ $items[] = $iface->getName();
+ } else {
+ $items[] = $iface->getName().' (via '.$via->getName().')';
+ }
+ }
+
+ $view->addProperty(
+ pht('Implements'),
+ phutil_implode_html(phutil_tag('br'), $items));
+ }
+
+ }
+
+ private function getExtendsLineage(DivinerLiveSymbol $symbol) {
+ foreach ($symbol->getExtends() as $extends) {
+ if ($extends->getType() == 'class') {
+ $lineage = $this->getExtendsLineage($extends);
+ $lineage[] = $extends;
+ return $lineage;
+ }
+ }
+ return array();
+ }
+
+ private function getImplementsLineage(DivinerLiveSymbol $symbol) {
+ $implements = array();
+
+ // Do these first so we get interfaces ordered from most to least specific.
+ foreach ($symbol->getExtends() as $extends) {
+ if ($extends->getType() == 'interface') {
+ $implements[$extends->getName()] = array(
+ 'interface' => $extends,
+ 'via' => $symbol,
+ );
+ }
+ }
+
+ // Now do parent interfaces.
+ foreach ($symbol->getExtends() as $extends) {
+ if ($extends->getType() == 'class') {
+ $implements += $this->getImplementsLineage($extends);
+ }
+ }
+
+ return $implements;
+ }
+
}
diff --git a/src/applications/diviner/publisher/DivinerLivePublisher.php b/src/applications/diviner/publisher/DivinerLivePublisher.php
index 6e2fc92170..164aa6f548 100644
--- a/src/applications/diviner/publisher/DivinerLivePublisher.php
+++ b/src/applications/diviner/publisher/DivinerLivePublisher.php
@@ -1,140 +1,144 @@
<?php
final class DivinerLivePublisher extends DivinerPublisher {
private $book;
private function loadBook() {
if (!$this->book) {
$book_name = $this->getConfig('name');
$book = id(new DivinerLiveBook())->loadOneWhere(
'name = %s',
$book_name);
if (!$book) {
$book = id(new DivinerLiveBook())
->setName($book_name)
->setViewPolicy(PhabricatorPolicies::POLICY_USER)
->save();
}
$book->setConfigurationData($this->getConfigurationData())->save();
$this->book = $book;
}
return $this->book;
}
private function loadSymbolForAtom(DivinerAtom $atom) {
$symbol = id(new DivinerAtomQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withBookPHIDs(array($this->loadBook()->getPHID()))
->withTypes(array($atom->getType()))
->withNames(array($atom->getName()))
->withContexts(array($atom->getContext()))
->withIndexes(array($this->getAtomSimilarIndex($atom)))
->withIncludeUndocumentable(true)
->withIncludeGhosts(true)
->executeOne();
if ($symbol) {
return $symbol;
}
return id(new DivinerLiveSymbol())
->setBookPHID($this->loadBook()->getPHID())
->setType($atom->getType())
->setName($atom->getName())
->setContext($atom->getContext())
->setAtomIndex($this->getAtomSimilarIndex($atom));
}
private function loadAtomStorageForSymbol(DivinerLiveSymbol $symbol) {
$storage = id(new DivinerLiveAtom())->loadOneWhere(
'symbolPHID = %s',
$symbol->getPHID());
if ($storage) {
return $storage;
}
return id(new DivinerLiveAtom())
->setSymbolPHID($symbol->getPHID());
}
protected function loadAllPublishedHashes() {
- $symbols = id(new DivinerLiveSymbol())->loadAllWhere(
- 'bookPHID = %s AND graphHash IS NOT NULL',
- $this->loadBook()->getPHID());
+ $symbols = id(new DivinerAtomQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withBookPHIDs(array($this->loadBook()->getPHID()))
+ ->withIncludeUndocumentable(true)
+ ->execute();
return mpull($symbols, 'getGraphHash');
}
protected function deleteDocumentsByHash(array $hashes) {
$atom_table = new DivinerLiveAtom();
$symbol_table = new DivinerLiveSymbol();
$conn_w = $symbol_table->establishConnection('w');
$strings = array();
foreach ($hashes as $hash) {
$strings[] = qsprintf($conn_w, '%s', $hash);
}
foreach (PhabricatorLiskDAO::chunkSQL($strings, ', ') as $chunk) {
queryfx(
$conn_w,
- 'UPDATE %T SET graphHash = NULL WHERE graphHash IN (%Q)',
+ 'UPDATE %T SET graphHash = NULL, nodeHash = NULL
+ WHERE graphHash IN (%Q)',
$symbol_table->getTableName(),
$chunk);
}
queryfx(
$conn_w,
'DELETE a FROM %T a LEFT JOIN %T s
ON a.symbolPHID = s.phid
WHERE s.graphHash IS NULL',
$atom_table->getTableName(),
$symbol_table->getTableName());
}
protected function createDocumentsByHash(array $hashes) {
foreach ($hashes as $hash) {
$atom = $this->getAtomFromGraphHash($hash);
$ref = $atom->getRef();
$symbol = $this->loadSymbolForAtom($atom);
$is_documentable = $this->shouldGenerateDocumentForAtom($atom);
$symbol
->setGraphHash($hash)
->setIsDocumentable((int)$is_documentable)
->setTitle($ref->getTitle())
- ->setGroupName($ref->getGroup());
+ ->setGroupName($ref->getGroup())
+ ->setNodeHash($atom->getHash());
if ($is_documentable) {
$renderer = $this->getRenderer();
$summary = $renderer->renderAtomSummary($atom);
$summary = (string)phutil_safe_html($summary);
$symbol->setSummary($summary);
}
$symbol->save();
if ($is_documentable) {
$storage = $this->loadAtomStorageForSymbol($symbol)
->setAtomData($atom->toDictionary())
->setContent(null)
->save();
}
}
}
public function findAtomByRef(DivinerAtomRef $ref) {
// TODO: Actually implement this.
return null;
}
}
diff --git a/src/applications/diviner/publisher/DivinerPublisher.php b/src/applications/diviner/publisher/DivinerPublisher.php
index 150cd42918..33109933a2 100644
--- a/src/applications/diviner/publisher/DivinerPublisher.php
+++ b/src/applications/diviner/publisher/DivinerPublisher.php
@@ -1,155 +1,156 @@
<?php
abstract class DivinerPublisher {
private $atomCache;
private $atomGraphHashToNodeHashMap;
private $atomMap = array();
private $renderer;
private $config;
private $symbolReverseMap;
private $dropCaches;
public function setDropCaches($drop_caches) {
$this->dropCaches = $drop_caches;
return $this;
}
public function setRenderer(DivinerRenderer $renderer) {
$renderer->setPublisher($this);
$this->renderer = $renderer;
return $this;
}
public function getRenderer() {
return $this->renderer;
}
public function setConfig(array $config) {
$this->config = $config;
return $this;
}
public function getConfig($key, $default = null) {
return idx($this->config, $key, $default);
}
public function getConfigurationData() {
return $this->config;
}
public function setAtomCache(DivinerAtomCache $cache) {
$this->atomCache = $cache;
$graph_map = $this->atomCache->getGraphMap();
$this->atomGraphHashToNodeHashMap = array_flip($graph_map);
}
protected function getAtomFromGraphHash($graph_hash) {
if (empty($this->atomGraphHashToNodeHashMap[$graph_hash])) {
throw new Exception("No such atom '{$graph_hash}'!");
}
return $this->getAtomFromNodeHash(
$this->atomGraphHashToNodeHashMap[$graph_hash]);
}
protected function getAtomFromNodeHash($node_hash) {
if (empty($this->atomMap[$node_hash])) {
$dict = $this->atomCache->getAtom($node_hash);
$this->atomMap[$node_hash] = DivinerAtom::newFromDictionary($dict);
}
return $this->atomMap[$node_hash];
}
protected function getSimilarAtoms(DivinerAtom $atom) {
if ($this->symbolReverseMap === null) {
$rmap = array();
$smap = $this->atomCache->getSymbolMap();
foreach ($smap as $nhash => $shash) {
$rmap[$shash][$nhash] = true;
}
$this->symbolReverseMap = $rmap;
}
$shash = $atom->getRef()->toHash();
if (empty($this->symbolReverseMap[$shash])) {
throw new Exception("Atom has no symbol map entry!");
}
$hashes = $this->symbolReverseMap[$shash];
$atoms = array();
foreach ($hashes as $hash => $ignored) {
$atoms[] = $this->getAtomFromNodeHash($hash);
}
$atoms = msort($atoms, 'getSortKey');
return $atoms;
}
/**
* If a book contains multiple definitions of some atom, like some function
* "f()", we assign them an arbitrary (but fairly stable) order and publish
* them as "function/f/1/", "function/f/2/", etc., or similar.
*/
protected function getAtomSimilarIndex(DivinerAtom $atom) {
$atoms = $this->getSimilarAtoms($atom);
if (count($atoms) == 1) {
return 0;
}
$index = 1;
foreach ($atoms as $similar_atom) {
if ($atom === $similar_atom) {
return $index;
}
$index++;
}
throw new Exception("Expected to find atom while disambiguating!");
}
abstract protected function loadAllPublishedHashes();
abstract protected function deleteDocumentsByHash(array $hashes);
abstract protected function createDocumentsByHash(array $hashes);
abstract public function findAtomByRef(DivinerAtomRef $ref);
final public function publishAtoms(array $hashes) {
$existing = $this->loadAllPublishedHashes();
if ($this->dropCaches) {
$deleted = $existing;
$created = $hashes;
} else {
$existing_map = array_fill_keys($existing, true);
$hashes_map = array_fill_keys($hashes, true);
$deleted = array_diff_key($existing_map, $hashes_map);
$created = array_diff_key($hashes_map, $existing_map);
$deleted = array_keys($deleted);
$created = array_keys($created);
}
echo pht('Deleting %d documents.', count($deleted))."\n";
$this->deleteDocumentsByHash($deleted);
echo pht('Creating %d documents.', count($created))."\n";
$this->createDocumentsByHash($created);
}
protected function shouldGenerateDocumentForAtom(DivinerAtom $atom) {
switch ($atom->getType()) {
+ case DivinerAtom::TYPE_METHOD:
case DivinerAtom::TYPE_FILE:
return false;
case DivinerAtom::TYPE_ARTICLE:
default:
break;
}
return true;
}
}
diff --git a/src/applications/diviner/query/DivinerAtomQuery.php b/src/applications/diviner/query/DivinerAtomQuery.php
index a461c40c51..06e6fda1e3 100644
--- a/src/applications/diviner/query/DivinerAtomQuery.php
+++ b/src/applications/diviner/query/DivinerAtomQuery.php
@@ -1,228 +1,325 @@
<?php
final class DivinerAtomQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $bookPHIDs;
private $names;
private $types;
private $contexts;
private $indexes;
private $includeUndocumentable;
private $includeGhosts;
+ private $nodeHashes;
private $needAtoms;
+ private $needExtends;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withBookPHIDs(array $phids) {
$this->bookPHIDs = $phids;
return $this;
}
public function withTypes(array $types) {
$this->types = $types;
return $this;
}
public function withNames(array $names) {
$this->names = $names;
return $this;
}
public function withContexts(array $contexts) {
$this->contexts = $contexts;
return $this;
}
public function withIndexes(array $indexes) {
$this->indexes = $indexes;
return $this;
}
+ public function withNodeHashes(array $hashes) {
+ $this->nodeHashes = $hashes;
+ return $this;
+ }
+
public function needAtoms($need) {
$this->needAtoms = $need;
return $this;
}
+
/**
* Include "ghosts", which are symbols which used to exist but do not exist
* currently (for example, a function which existed in an older version of
* the codebase but was deleted).
*
* These symbols had PHIDs assigned to them, and may have other sorts of
* metadata that we don't want to lose (like comments or flags), so we don't
* delete them outright. They might also come back in the future: the change
* which deleted the symbol might be reverted, or the documentation might
* have been generated incorrectly by accident. In these cases, we can
* restore the original data.
*
* However, most callers are not interested in these symbols, so they are
* excluded by default. You can use this method to include them in results.
*
* @param bool True to include ghosts.
* @return this
*/
public function withIncludeGhosts($include) {
$this->includeGhosts = $include;
return $this;
}
+
+ public function needExtends($need) {
+ $this->needExtends = $need;
+ return $this;
+ }
+
public function withIncludeUndocumentable($include) {
$this->includeUndocumentable = $include;
return $this;
}
protected function loadPage() {
$table = new DivinerLiveSymbol();
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT * FROM %T %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
return $table->loadAllFromArray($data);
}
protected function willFilterPage(array $atoms) {
$books = array_unique(mpull($atoms, 'getBookPHID'));
$books = id(new DivinerBookQuery())
->setViewer($this->getViewer())
->withPHIDs($books)
->execute();
$books = mpull($books, null, 'getPHID');
foreach ($atoms as $key => $atom) {
$book = idx($books, $atom->getBookPHID());
if (!$book) {
unset($atoms[$key]);
continue;
}
$atom->attachBook($book);
}
+ $need_atoms = $this->needAtoms;
+
if ($this->needAtoms) {
$atom_data = id(new DivinerLiveAtom())->loadAllWhere(
'symbolPHID IN (%Ls)',
mpull($atoms, 'getPHID'));
$atom_data = mpull($atom_data, null, 'getSymbolPHID');
foreach ($atoms as $key => $atom) {
$data = idx($atom_data, $atom->getPHID());
if (!$data) {
unset($atoms[$key]);
continue;
}
$atom->attachAtom($data);
}
}
+ // Load all of the symbols this symbol extends, recursively. Commonly,
+ // this means all the ancestor classes and interfaces it extends and
+ // implements.
+
+ if ($this->needExtends) {
+
+ // First, load all the matching symbols by name. This does 99% of the
+ // work in most cases, assuming things are named at all reasonably.
+
+ $names = array();
+ foreach ($atoms as $atom) {
+ foreach ($atom->getAtom()->getExtends() as $xref) {
+ $names[] = $xref->getName();
+ }
+ }
+
+ if ($names) {
+ $xatoms = id(new DivinerAtomQuery())
+ ->setViewer($this->getViewer())
+ ->withNames($names)
+ ->needExtends(true)
+ ->needAtoms(true)
+ ->execute();
+ $xatoms = mgroup($xatoms, 'getName', 'getType', 'getBookPHID');
+ } else {
+ $xatoms = array();
+ }
+
+ foreach ($atoms as $atom) {
+ $alang = $atom->getAtom()->getLanguage();
+ $extends = array();
+ foreach ($atom->getAtom()->getExtends() as $xref) {
+
+ // If there are no symbols of the matching name and type, we can't
+ // resolve this.
+ if (empty($xatoms[$xref->getName()][$xref->getType()])) {
+ continue;
+ }
+
+ // If we found matches in the same documentation book, prefer them
+ // over other matches. Otherwise, look at all the the matches.
+ $matches = $xatoms[$xref->getName()][$xref->getType()];
+ if (isset($matches[$atom->getBookPHID()])) {
+ $maybe = $matches[$atom->getBookPHID()];
+ } else {
+ $maybe = array_mergev($matches);
+ }
+
+ if (!$maybe) {
+ continue;
+ }
+
+ // Filter out matches in a different language, since, e.g., PHP
+ // classes can not implement JS classes.
+ $same_lang = array();
+ foreach ($maybe as $xatom) {
+ if ($xatom->getAtom()->getLanguage() == $alang) {
+ $same_lang[] = $xatom;
+ }
+ }
+
+ if (!$same_lang) {
+ continue;
+ }
+
+ // If we have duplicates remaining, just pick the first one. There's
+ // nothing more we can do to figure out which is the real one.
+ $extends[] = head($same_lang);
+ }
+
+ $atom->attachExtends($extends);
+ }
+ }
+
return $atoms;
}
private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
$where = array();
if ($this->ids) {
$where[] = qsprintf(
$conn_r,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids) {
$where[] = qsprintf(
$conn_r,
'phid IN (%Ls)',
$this->phids);
}
if ($this->bookPHIDs) {
$where[] = qsprintf(
$conn_r,
'bookPHID IN (%Ls)',
$this->bookPHIDs);
}
if ($this->types) {
$where[] = qsprintf(
$conn_r,
'type IN (%Ls)',
$this->types);
}
if ($this->names) {
$where[] = qsprintf(
$conn_r,
'name IN (%Ls)',
$this->names);
}
if ($this->contexts) {
$with_null = false;
$contexts = $this->contexts;
foreach ($contexts as $key => $value) {
if ($value === null) {
unset($contexts[$key]);
$with_null = true;
continue;
}
}
if ($contexts && $with_null) {
$where[] = qsprintf(
$conn_r,
'context IN (%Ls) OR context IS NULL',
$contexts);
} else if ($contexts) {
$where[] = qsprintf(
$conn_r,
'context IN (%Ls)',
$contexts);
} else if ($with_null) {
$where[] = qsprintf(
$conn_r,
'context IS NULL');
}
}
if ($this->indexes) {
$where[] = qsprintf(
$conn_r,
'atomIndex IN (%Ld)',
$this->indexes);
}
if (!$this->includeUndocumentable) {
$where[] = qsprintf(
$conn_r,
'isDocumentable = 1');
}
if (!$this->includeGhosts) {
$where[] = qsprintf(
$conn_r,
'graphHash IS NOT NULL');
}
+ if ($this->nodeHashes) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'nodeHash IN (%Ls)',
+ $this->nodeHashes);
+ }
+
$where[] = $this->buildPagingClause($conn_r);
return $this->formatWhereClause($where);
}
}
diff --git a/src/applications/diviner/storage/DivinerLiveSymbol.php b/src/applications/diviner/storage/DivinerLiveSymbol.php
index a4eef16282..8a8a4bb79f 100644
--- a/src/applications/diviner/storage/DivinerLiveSymbol.php
+++ b/src/applications/diviner/storage/DivinerLiveSymbol.php
@@ -1,166 +1,172 @@
<?php
final class DivinerLiveSymbol extends DivinerDAO
implements PhabricatorPolicyInterface, PhabricatorMarkupInterface {
protected $phid;
protected $bookPHID;
protected $context;
protected $type;
protected $name;
protected $atomIndex;
protected $graphHash;
protected $identityHash;
+ protected $nodeHash;
protected $title;
protected $groupName;
protected $summary;
protected $isDocumentable = 0;
- private $book;
- private $atom;
+ private $book = self::ATTACHABLE;
+ private $atom = self::ATTACHABLE;
+ private $extends = self::ATTACHABLE;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
DivinerPHIDTypeAtom::TYPECONST);
}
public function getBook() {
- if ($this->book === null) {
- throw new Exception("Call attachBook() before getBook()!");
- }
- return $this->book;
+ return $this->assertAttached($this->book);
}
public function attachBook(DivinerLiveBook $book) {
$this->book = $book;
return $this;
}
public function getAtom() {
- if ($this->atom === null) {
- throw new Exception("Call attachAtom() before getAtom()!");
- }
- return $this->atom;
+ return $this->assertAttached($this->atom);
}
public function attachAtom(DivinerLiveAtom $atom) {
$this->atom = DivinerAtom::newFromDictionary($atom->getAtomData());
return $this;
}
public function getURI() {
$parts = array(
'book',
$this->getBook()->getName(),
$this->getType(),
);
if ($this->getContext()) {
$parts[] = $this->getContext();
}
$parts[] = $this->getName();
if ($this->getAtomIndex()) {
$parts[] = $this->getAtomIndex();
}
return '/'.implode('/', $parts).'/';
}
public function getSortKey() {
return $this->getTitle();
}
public function save() {
// NOTE: The identity hash is just a sanity check because the unique tuple
// on this table is way way too long to fit into a normal UNIQUE KEY. We
// don't use it directly, but its existence prevents duplicate records.
if (!$this->identityHash) {
$this->identityHash = PhabricatorHash::digestForIndex(
serialize(
array(
'bookPHID' => $this->getBookPHID(),
'context' => $this->getContext(),
'type' => $this->getType(),
'name' => $this->getName(),
'index' => $this->getAtomIndex(),
)));
}
return parent::save();
}
public function getTitle() {
$title = parent::getTitle();
if (!strlen($title)) {
$title = $this->getName();
}
return $title;
}
+ public function attachExtends(array $extends) {
+ assert_instances_of($extends, 'DivinerLiveSymbol');
+ $this->extends = $extends;
+ return $this;
+ }
+
+ public function getExtends() {
+ return $this->assertAttached($this->extends);
+ }
+
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return $this->getBook()->getCapabilities();
}
public function getPolicy($capability) {
return $this->getBook()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getBook()->hasAutomaticCapability($capability, $viewer);
}
/* -( Markup Interface )--------------------------------------------------- */
public function getMarkupFieldKey($field) {
return $this->getPHID().':'.$field.':'.$this->getGraphHash();
}
public function newMarkupEngine($field) {
$engine = PhabricatorMarkupEngine::newMarkupEngine(array());
$engine->setConfig('preserve-linebreaks', false);
// $engine->setConfig('diviner.renderer', new DivinerDefaultRenderer());
$engine->setConfig('header.generate-toc', true);
return $engine;
}
public function getMarkupText($field) {
return $this->getAtom()->getDocblockText();
}
public function didMarkupText(
$field,
$output,
PhutilMarkupEngine $engine) {
return $output;
}
public function shouldUseMarkupCache($field) {
return false;
}
}
diff --git a/src/applications/diviner/workflow/DivinerAtomizeWorkflow.php b/src/applications/diviner/workflow/DivinerAtomizeWorkflow.php
index ace4b1dd15..1b1c272944 100644
--- a/src/applications/diviner/workflow/DivinerAtomizeWorkflow.php
+++ b/src/applications/diviner/workflow/DivinerAtomizeWorkflow.php
@@ -1,116 +1,118 @@
<?php
final class DivinerAtomizeWorkflow extends DivinerWorkflow {
public function didConstruct() {
$this
->setName('atomize')
->setSynopsis(pht('Build atoms from source.'))
->setArguments(
array(
array(
'name' => 'atomizer',
'param' => 'class',
'help' => pht('Specify a subclass of DivinerAtomizer.'),
),
array(
'name' => 'book',
'param' => 'path',
'help' => pht('Path to a Diviner book configuration.'),
),
array(
'name' => 'files',
'wildcard' => true,
),
array(
'name' => 'ugly',
'help' => pht('Produce ugly (but faster) output.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$this->readBookConfiguration($args);
$console = PhutilConsole::getConsole();
$atomizer_class = $args->getArg('atomizer');
if (!$atomizer_class) {
throw new Exception("Specify an atomizer class with --atomizer.");
}
$symbols = id(new PhutilSymbolLoader())
->setName($atomizer_class)
->setConcreteOnly(true)
->setAncestorClass('DivinerAtomizer')
->selectAndLoadSymbols();
if (!$symbols) {
throw new Exception(
"Atomizer class '{$atomizer_class}' must be a concrete subclass of ".
"DivinerAtomizer.");
}
$atomizer = newv($atomizer_class, array());
$files = $args->getArg('files');
if (!$files) {
throw new Exception("Specify one or more files to atomize.");
}
$file_atomizer = new DivinerFileAtomizer();
foreach (array($atomizer, $file_atomizer) as $configure) {
$configure->setBook($this->getConfig('name'));
}
$all_atoms = array();
foreach ($files as $file) {
$abs_path = Filesystem::resolvePath($file, $this->getConfig('root'));
$data = Filesystem::readFile($abs_path);
if (!$this->shouldAtomizeFile($file, $data)) {
$console->writeLog("Skipping %s...\n", $file);
continue;
} else {
$console->writeLog("Atomizing %s...\n", $file);
}
$file_atoms = $file_atomizer->atomize($file, $data);
$all_atoms[] = $file_atoms;
if (count($file_atoms) !== 1) {
throw new Exception("Expected exactly one atom from file atomizer.");
}
$file_atom = head($file_atoms);
$atoms = $atomizer->atomize($file, $data);
foreach ($atoms as $atom) {
- $file_atom->addChild($atom);
+ if (!$atom->getParentHash()) {
+ $file_atom->addChild($atom);
+ }
}
$all_atoms[] = $atoms;
}
$all_atoms = array_mergev($all_atoms);
$all_atoms = mpull($all_atoms, 'toDictionary');
$all_atoms = ipull($all_atoms, null, 'hash');
if ($args->getArg('ugly')) {
$json = json_encode($all_atoms);
} else {
$json_encoder = new PhutilJSON();
$json = $json_encoder->encodeFormatted($all_atoms);
}
$console->writeOut('%s', $json);
return 0;
}
private function shouldAtomizeFile($file_name, $file_data) {
return (strpos($file_data, '@'.'undivinable') === false);
}
}
diff --git a/src/applications/diviner/workflow/DivinerGenerateWorkflow.php b/src/applications/diviner/workflow/DivinerGenerateWorkflow.php
index afaaab8615..8f23257069 100644
--- a/src/applications/diviner/workflow/DivinerGenerateWorkflow.php
+++ b/src/applications/diviner/workflow/DivinerGenerateWorkflow.php
@@ -1,489 +1,506 @@
<?php
final class DivinerGenerateWorkflow extends DivinerWorkflow {
private $atomCache;
public function didConstruct() {
$this
->setName('generate')
->setSynopsis(pht('Generate documentation.'))
->setArguments(
array(
array(
'name' => 'clean',
'help' => 'Clear the caches before generating documentation.',
),
array(
'name' => 'book',
'param' => 'path',
'help' => 'Path to a Diviner book configuration.',
),
));
}
protected function getAtomCache() {
if (!$this->atomCache) {
$book_root = $this->getConfig('root');
$book_name = $this->getConfig('name');
$cache_directory = $book_root.'/.divinercache/'.$book_name;
$this->atomCache = new DivinerAtomCache($cache_directory);
}
return $this->atomCache;
}
protected function log($message) {
$console = PhutilConsole::getConsole();
- $console->getServer()->setEnableLog(true);
- $console->writeLog($message."\n");
+ $console->writeErr($message."\n");
}
public function execute(PhutilArgumentParser $args) {
$this->readBookConfiguration($args);
if ($args->getArg('clean')) {
$this->log(pht('CLEARING CACHES'));
$this->getAtomCache()->delete();
$this->log(pht('Done.')."\n");
}
// The major challenge of documentation generation is one of dependency
// management. When regenerating documentation, we want to do the smallest
// amount of work we can, so that regenerating documentation after minor
// changes is quick.
//
// ATOM CACHE
//
// In the first stage, we find all the direct changes to source code since
// the last run. This stage relies on two data structures:
//
// - File Hash Map: map<file_hash, node_hash>
// - Atom Map: map<node_hash, true>
//
// First, we hash all the source files in the project to detect any which
// have changed since the previous run (i.e., their hash is not present in
// the File Hash Map). If a file's content hash appears in the map, it has
// not changed, so we don't need to reparse it.
//
// We break the contents of each file into "atoms", which represent a unit
// of source code (like a function, method, class or file). Each atom has a
// "node hash" based on the content of the atom: if a function definition
// changes, the node hash of the atom changes too. The primary output of
// the atom cache is a list of node hashes which exist in the project. This
// is the Atom Map. The node hash depends only on the definition of the atom
// and the atomizer implementation. It ends with an "N", for "node".
//
// (We need the Atom Map in addition to the File Hash Map because each file
// may have several atoms in it (e.g., multiple functions, or a class and
// its methods). The File Hash Map contains an exhaustive list of all atoms
// with type "file", but not child atoms of those top-level atoms.)
//
// GRAPH CACHE
//
// We now know which atoms exist, and can compare the Atom Map to some
// existing cache to figure out what has changed. However, this isn't
// sufficient to figure out which documentation actually needs to be
// regnerated, because atoms depend on other atoms. For example, if "B
// extends A" and the definition for A changes, we need to regenerate the
// documentation in B. Similarly, if X links to Y and Y changes, we should
// regenerate X. (In both these cases, the documentation for the connected
// atom may not acutally change, but in some cases it will, and the extra
// work we need to do is generally very small compared to the size of the
// project.)
//
// To figure out which other nodes have changed, we compute a "graph hash"
// for each node. This hash combines the "node hash" with the node hashes
// of connected nodes. Our primary output is a list of graph hashes, which
// a documentation generator can use to easily determine what work needs
// to be done by comparing the list with a list of cached graph hashes,
// then generating documentation for new hashes and deleting documentation
// for missing hashes. The graph hash ends with a "G", for "graph".
//
// In this stage, we rely on three data structures:
//
// - Symbol Map: map<node_hash, symbol_hash>
// - Edge Map: map<node_hash, list<symbol_hash>>
// - Graph Map: map<node_hash, graph_hash>
//
// Calculating the graph hash requires several steps, because we need to
// figure out which nodes an atom is attached to. The atom contains symbolic
// references to other nodes by name (e.g., "extends SomeClass") in the form
// of DivinerAtomRefs. We can also build a symbolic reference for any atom
// from the atom itself. Each DivinerAtomRef generates a symbol hash,
// which ends with an "S", for "symbol".
//
// First, we update the symbol map. We remove (and mark dirty) any symbols
// associated with node hashes which no longer exist (e.g., old/dead nodes).
// Second, we add (and mark dirty) any symbols associated with new nodes.
// We also add edges defined by new nodes to the graph.
//
// We initialize a list of dirty nodes to the list of new nodes, then
// find all nodes connected to dirty symbols and add them to the dirty
// node list. This list now contains every node with a new or changed
// graph hash.
//
// We walk the dirty list and compute the new graph hashes, adding them
// to the graph hash map. This Graph Map can then be passed to an actual
// documentation generator, which can compare the graph hashes to a list
// of already-generated graph hashes and easily assess which documents need
// to be regenerated and which can be deleted.
$this->buildAtomCache();
$this->buildGraphCache();
$this->publishDocumentation($args->getArg('clean'));
}
/* -( Atom Cache )--------------------------------------------------------- */
private function buildAtomCache() {
$this->log(pht('BUILDING ATOM CACHE'));
$file_hashes = $this->findFilesInProject();
$this->log(pht('Found %d file(s) in project.', count($file_hashes)));
$this->deleteDeadAtoms($file_hashes);
$atomize = $this->getFilesToAtomize($file_hashes);
$this->log(pht('Found %d unatomized, uncached file(s).', count($atomize)));
$file_atomizers = $this->getAtomizersForFiles($atomize);
$this->log(pht('Found %d file(s) to atomize.', count($file_atomizers)));
$futures = $this->buildAtomizerFutures($file_atomizers);
+
+ $this->log(pht('Atomizing %d file(s).', count($file_atomizers)));
+
if ($futures) {
$this->resolveAtomizerFutures($futures, $file_hashes);
$this->log(pht("Atomization complete."));
} else {
$this->log(pht("Atom cache is up to date, no files to atomize."));
}
$this->log(pht("Writing atom cache."));
$this->getAtomCache()->saveAtoms();
$this->log(pht('Done.')."\n");
}
private function getAtomizersForFiles(array $files) {
$rules = $this->getRules();
$exclude = $this->getExclude();
$atomizers = array();
foreach ($files as $file) {
foreach ($exclude as $pattern) {
if (preg_match($pattern, $file)) {
continue 2;
}
}
foreach ($rules as $rule => $atomizer) {
$ok = preg_match($rule, $file);
if ($ok === false) {
throw new Exception(
"Rule '{$rule}' is not a valid regular expression.");
}
if ($ok) {
$atomizers[$file] = $atomizer;
continue;
}
}
}
return $atomizers;
}
private function getRules() {
$rules = $this->getConfig('rules', array(
'/\\.diviner$/' => 'DivinerArticleAtomizer',
'/\\.php$/' => 'DivinerPHPAtomizer',
));
foreach ($rules as $rule => $atomizer) {
if (@preg_match($rule, '') === false) {
throw new Exception(
"Rule '{$rule}' is not a valid regular expression!");
}
}
return $rules;
}
private function getExclude() {
$exclude = $this->getConfig('exclude', array());
foreach ($exclude as $rule) {
if (@preg_match($rule, '') === false) {
throw new Exception(
"Exclude rule '{$rule}' is not a valid regular expression!");
}
}
return $exclude;
}
private function findFilesInProject() {
$raw_hashes = id(new FileFinder($this->getConfig('root')))
->excludePath('*/.*')
->withType('f')
->setGenerateChecksums(true)
->find();
$version = $this->getDivinerAtomWorldVersion();
$file_hashes = array();
foreach ($raw_hashes as $file => $md5_hash) {
$rel_file = Filesystem::readablePath($file, $this->getConfig('root'));
// We want the hash to change if the file moves or Diviner gets updated,
// not just if the file content changes. Derive a hash from everything
// we care about.
$file_hashes[$rel_file] = md5("{$rel_file}\0{$md5_hash}\0{$version}").'F';
}
return $file_hashes;
}
private function deleteDeadAtoms(array $file_hashes) {
$atom_cache = $this->getAtomCache();
$hash_to_file = array_flip($file_hashes);
foreach ($atom_cache->getFileHashMap() as $hash => $atom) {
if (empty($hash_to_file[$hash])) {
$atom_cache->deleteFileHash($hash);
}
}
}
private function getFilesToAtomize(array $file_hashes) {
$atom_cache = $this->getAtomCache();
$atomize = array();
foreach ($file_hashes as $file => $hash) {
if (!$atom_cache->fileHashExists($hash)) {
$atomize[] = $file;
}
}
return $atomize;
}
private function buildAtomizerFutures(array $file_atomizers) {
$atomizers = array();
foreach ($file_atomizers as $file => $atomizer) {
$atomizers[$atomizer][] = $file;
}
+ $root = dirname(phutil_get_library_root('phabricator'));
+ $config_root = $this->getConfig('root');
+
+ $bar = id(new PhutilConsoleProgressBar())
+ ->setTotal(count($file_atomizers));
+
$futures = array();
foreach ($atomizers as $class => $files) {
foreach (array_chunk($files, 32) as $chunk) {
$future = new ExecFuture(
'%s atomize --ugly --book %s --atomizer %s -- %Ls',
- dirname(phutil_get_library_root('phabricator')).'/bin/diviner',
+ $root.'/bin/diviner',
$this->getBookConfigPath(),
$class,
$chunk);
- $future->setCWD($this->getConfig('root'));
+ $future->setCWD($config_root);
$futures[] = $future;
+
+ $bar->update(count($chunk));
}
}
+ $bar->done();
+
return $futures;
}
private function resolveAtomizerFutures(array $futures, array $file_hashes) {
assert_instances_of($futures, 'Future');
$atom_cache = $this->getAtomCache();
+ $bar = id(new PhutilConsoleProgressBar())
+ ->setTotal(count($futures));
foreach (Futures($futures)->limit(4) as $key => $future) {
$atoms = $future->resolveJSON();
foreach ($atoms as $atom) {
if ($atom['type'] == DivinerAtom::TYPE_FILE) {
$file_hash = $file_hashes[$atom['file']];
$atom_cache->addFileHash($file_hash, $atom['hash']);
}
$atom_cache->addAtom($atom);
}
+
+ $bar->update(1);
}
+ $bar->done();
}
/**
* Get a global version number, which changes whenever any atom or atomizer
* implementation changes in a way which is not backward-compatible.
*/
private function getDivinerAtomWorldVersion() {
$version = array();
$version['atom'] = DivinerAtom::getAtomSerializationVersion();
$version['rules'] = $this->getRules();
$atomizers = id(new PhutilSymbolLoader())
->setAncestorClass('DivinerAtomizer')
->setConcreteOnly(true)
->selectAndLoadSymbols();
$atomizer_versions = array();
foreach ($atomizers as $atomizer) {
$atomizer_versions[$atomizer['name']] = call_user_func(
array(
$atomizer['name'],
'getAtomizerVersion',
));
}
ksort($atomizer_versions);
$version['atomizers'] = $atomizer_versions;
return md5(serialize($version));
}
/* -( Graph Cache )-------------------------------------------------------- */
private function buildGraphCache() {
$this->log(pht('BUILDING GRAPH CACHE'));
$atom_cache = $this->getAtomCache();
$symbol_map = $atom_cache->getSymbolMap();
$atoms = $atom_cache->getAtomMap();
$dirty_symbols = array();
$dirty_nhashes = array();
$del_atoms = array_diff_key($symbol_map, $atoms);
$this->log(pht('Found %d obsolete atom(s) in graph.', count($del_atoms)));
foreach ($del_atoms as $nhash => $shash) {
$atom_cache->deleteSymbol($nhash);
$dirty_symbols[$shash] = true;
$atom_cache->deleteEdges($nhash);
$atom_cache->deleteGraph($nhash);
}
$new_atoms = array_diff_key($atoms, $symbol_map);
$this->log(pht('Found %d new atom(s) in graph.', count($new_atoms)));
foreach ($new_atoms as $nhash => $ignored) {
$shash = $this->computeSymbolHash($nhash);
$atom_cache->addSymbol($nhash, $shash);
$dirty_symbols[$shash] = true;
$atom_cache->addEdges(
$nhash,
$this->getEdges($nhash));
$dirty_nhashes[$nhash] = true;
}
$this->log(pht('Propagating changes through the graph.'));
// Find all the nodes which point at a dirty node, and dirty them. Then
// find all the nodes which point at those nodes and dirty them, and so
// on. (This is slightly overkill since we probably don't need to propagate
// dirtiness across documentation "links" between symbols, but we do want
// to propagate it across "extends", and we suffer only a little bit of
// collateral damage by over-dirtying as long as the documentation isn't
// too well-connected.)
$symbol_stack = array_keys($dirty_symbols);
while ($symbol_stack) {
$symbol_hash = array_pop($symbol_stack);
foreach ($atom_cache->getEdgesWithDestination($symbol_hash) as $edge) {
$dirty_nhashes[$edge] = true;
$src_hash = $this->computeSymbolHash($edge);
if (empty($dirty_symbols[$src_hash])) {
$dirty_symbols[$src_hash] = true;
$symbol_stack[] = $src_hash;
}
}
}
$this->log(pht('Found %d affected atoms.', count($dirty_nhashes)));
foreach ($dirty_nhashes as $nhash => $ignored) {
$atom_cache->addGraph($nhash, $this->computeGraphHash($nhash));
}
$this->log(pht('Writing graph cache.'));
$atom_cache->saveGraph();
$atom_cache->saveEdges();
$atom_cache->saveSymbols();
$this->log(pht('Done.')."\n");
}
private function computeSymbolHash($node_hash) {
$atom_cache = $this->getAtomCache();
$atom = $atom_cache->getAtom($node_hash);
if (!$atom) {
throw new Exception("No such atom with node hash '{$node_hash}'!");
}
$ref = DivinerAtomRef::newFromDictionary($atom['ref']);
return $ref->toHash();
}
private function getEdges($node_hash) {
$atom_cache = $this->getAtomCache();
$atom = $atom_cache->getAtom($node_hash);
$refs = array();
// Make the atom depend on its own symbol, so that all atoms with the same
// symbol are dirtied (e.g., if a codebase defines the function "f()"
// several times, all of them should be dirtied when one is dirtied).
$refs[DivinerAtomRef::newFromDictionary($atom)->toHash()] = true;
foreach (array_merge($atom['extends'], $atom['links']) as $ref_dict) {
$ref = DivinerAtomRef::newFromDictionary($ref_dict);
if ($ref->getBook() == $atom['book']) {
$refs[$ref->toHash()] = true;
}
}
return array_keys($refs);
}
private function computeGraphHash($node_hash) {
$atom_cache = $this->getAtomCache();
$atom = $atom_cache->getAtom($node_hash);
$edges = $this->getEdges($node_hash);
sort($edges);
$inputs = array(
'atomHash' => $atom['hash'],
'edges' => $edges,
);
return md5(serialize($inputs)).'G';
}
private function publishDocumentation($clean) {
$atom_cache = $this->getAtomCache();
$graph_map = $atom_cache->getGraphMap();
$this->log(pht('PUBLISHING DOCUMENTATION'));
$publisher = new DivinerLivePublisher();
$publisher->setDropCaches($clean);
$publisher->setConfig($this->getAllConfig());
$publisher->setAtomCache($atom_cache);
$publisher->setRenderer(new DivinerDefaultRenderer());
$publisher->publishAtoms(array_values($graph_map));
$this->log(pht('Done.'));
}
}
diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
index b1d5e98370..7fcd71c1cf 100644
--- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
+++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
@@ -1,1560 +1,1564 @@
<?php
final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
public function getNamespace() {
return 'phabricator';
}
private function getPatchPath($file) {
$root = dirname(phutil_get_library_root('phabricator'));
$path = $root.'/resources/sql/patches/'.$file;
// Make sure it exists.
Filesystem::readFile($path);
return $path;
}
public function getPatches() {
return array(
'db.audit' => array(
'type' => 'db',
'name' => 'audit',
'after' => array( /* First Patch */ ),
),
'db.calendar' => array(
'type' => 'db',
'name' => 'calendar',
),
'db.chatlog' => array(
'type' => 'db',
'name' => 'chatlog',
),
'db.conduit' => array(
'type' => 'db',
'name' => 'conduit',
),
'db.countdown' => array(
'type' => 'db',
'name' => 'countdown',
),
'db.daemon' => array(
'type' => 'db',
'name' => 'daemon',
),
'db.differential' => array(
'type' => 'db',
'name' => 'differential',
),
'db.draft' => array(
'type' => 'db',
'name' => 'draft',
),
'db.drydock' => array(
'type' => 'db',
'name' => 'drydock',
),
'db.feed' => array(
'type' => 'db',
'name' => 'feed',
),
'db.file' => array(
'type' => 'db',
'name' => 'file',
),
'db.flag' => array(
'type' => 'db',
'name' => 'flag',
),
'db.harbormaster' => array(
'type' => 'db',
'name' => 'harbormaster',
),
'db.herald' => array(
'type' => 'db',
'name' => 'herald',
),
'db.maniphest' => array(
'type' => 'db',
'name' => 'maniphest',
),
'db.meta_data' => array(
'type' => 'db',
'name' => 'meta_data',
),
'db.metamta' => array(
'type' => 'db',
'name' => 'metamta',
),
'db.oauth_server' => array(
'type' => 'db',
'name' => 'oauth_server',
),
'db.owners' => array(
'type' => 'db',
'name' => 'owners',
),
'db.pastebin' => array(
'type' => 'db',
'name' => 'pastebin',
),
'db.phame' => array(
'type' => 'db',
'name' => 'phame',
),
'db.phriction' => array(
'type' => 'db',
'name' => 'phriction',
),
'db.project' => array(
'type' => 'db',
'name' => 'project',
),
'db.repository' => array(
'type' => 'db',
'name' => 'repository',
),
'db.search' => array(
'type' => 'db',
'name' => 'search',
),
'db.slowvote' => array(
'type' => 'db',
'name' => 'slowvote',
),
'db.timeline' => array(
'type' => 'db',
'name' => 'timeline',
'dead' => true,
),
'db.user' => array(
'type' => 'db',
'name' => 'user',
),
'db.worker' => array(
'type' => 'db',
'name' => 'worker',
),
'db.xhpastview' => array(
'type' => 'db',
'name' => 'xhpastview',
),
'db.cache' => array(
'type' => 'db',
'name' => 'cache',
),
'db.fact' => array(
'type' => 'db',
'name' => 'fact',
),
'db.ponder' => array(
'type' => 'db',
'name' => 'ponder',
),
'db.xhprof' => array(
'type' => 'db',
'name' => 'xhprof',
),
'db.pholio' => array(
'type' => 'db',
'name' => 'pholio',
),
'db.conpherence' => array(
'type' => 'db',
'name' => 'conpherence',
),
'db.config' => array(
'type' => 'db',
'name' => 'config',
),
'db.token' => array(
'type' => 'db',
'name' => 'token',
),
'db.releeph' => array(
'type' => 'db',
'name' => 'releeph',
),
'db.phlux' => array(
'type' => 'db',
'name' => 'phlux',
),
'db.phortune' => array(
'type' => 'db',
'name' => 'phortune',
),
'db.phrequent' => array(
'type' => 'db',
'name' => 'phrequent',
),
'db.diviner' => array(
'type' => 'db',
'name' => 'diviner',
),
'db.auth' => array(
'type' => 'db',
'name' => 'auth',
),
'db.doorkeeper' => array(
'type' => 'db',
'name' => 'doorkeeper',
),
'db.legalpad' => array(
'type' => 'db',
'name' => 'legalpad',
),
'0000.legacy.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('0000.legacy.sql'),
'legacy' => 0,
),
'000.project.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('000.project.sql'),
'legacy' => 0,
),
'001.maniphest_projects.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('001.maniphest_projects.sql'),
'legacy' => 1,
),
'002.oauth.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('002.oauth.sql'),
'legacy' => 2,
),
'003.more_oauth.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('003.more_oauth.sql'),
'legacy' => 3,
),
'004.daemonrepos.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('004.daemonrepos.sql'),
'legacy' => 4,
),
'005.workers.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('005.workers.sql'),
'legacy' => 5,
),
'006.repository.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('006.repository.sql'),
'legacy' => 6,
),
'007.daemonlog.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('007.daemonlog.sql'),
'legacy' => 7,
),
'008.repoopt.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('008.repoopt.sql'),
'legacy' => 8,
),
'009.repo_summary.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('009.repo_summary.sql'),
'legacy' => 9,
),
'010.herald.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('010.herald.sql'),
'legacy' => 10,
),
'011.badcommit.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('011.badcommit.sql'),
'legacy' => 11,
),
'012.dropphidtype.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('012.dropphidtype.sql'),
'legacy' => 12,
),
'013.commitdetail.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('013.commitdetail.sql'),
'legacy' => 13,
),
'014.shortcuts.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('014.shortcuts.sql'),
'legacy' => 14,
),
'015.preferences.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('015.preferences.sql'),
'legacy' => 15,
),
'016.userrealnameindex.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('016.userrealnameindex.sql'),
'legacy' => 16,
),
'017.sessionkeys.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('017.sessionkeys.sql'),
'legacy' => 17,
),
'018.owners.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('018.owners.sql'),
'legacy' => 18,
),
'019.arcprojects.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('019.arcprojects.sql'),
'legacy' => 19,
),
'020.pathcapital.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('020.pathcapital.sql'),
'legacy' => 20,
),
'021.xhpastview.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('021.xhpastview.sql'),
'legacy' => 21,
),
'022.differentialcommit.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('022.differentialcommit.sql'),
'legacy' => 22,
),
'023.dxkeys.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('023.dxkeys.sql'),
'legacy' => 23,
),
'024.mlistkeys.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('024.mlistkeys.sql'),
'legacy' => 24,
),
'025.commentopt.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('025.commentopt.sql'),
'legacy' => 25,
),
'026.diffpropkey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('026.diffpropkey.sql'),
'legacy' => 26,
),
'027.metamtakeys.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('027.metamtakeys.sql'),
'legacy' => 27,
),
'028.systemagent.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('028.systemagent.sql'),
'legacy' => 28,
),
'029.cursors.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('029.cursors.sql'),
'legacy' => 29,
),
'030.imagemacro.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('030.imagemacro.sql'),
'legacy' => 30,
),
'031.workerrace.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('031.workerrace.sql'),
'legacy' => 31,
),
'032.viewtime.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('032.viewtime.sql'),
'legacy' => 32,
),
'033.privtest.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('033.privtest.sql'),
'legacy' => 33,
),
'034.savedheader.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('034.savedheader.sql'),
'legacy' => 34,
),
'035.proxyimage.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('035.proxyimage.sql'),
'legacy' => 35,
),
'036.mailkey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('036.mailkey.sql'),
'legacy' => 36,
),
'037.setuptest.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('037.setuptest.sql'),
'legacy' => 37,
),
'038.admin.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('038.admin.sql'),
'legacy' => 38,
),
'039.userlog.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('039.userlog.sql'),
'legacy' => 39,
),
'040.transform.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('040.transform.sql'),
'legacy' => 40,
),
'041.heraldrepetition.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('041.heraldrepetition.sql'),
'legacy' => 41,
),
'042.commentmetadata.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('042.commentmetadata.sql'),
'legacy' => 42,
),
'043.pastebin.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('043.pastebin.sql'),
'legacy' => 43,
),
'044.countdown.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('044.countdown.sql'),
'legacy' => 44,
),
'045.timezone.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('045.timezone.sql'),
'legacy' => 45,
),
'046.conduittoken.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('046.conduittoken.sql'),
'legacy' => 46,
),
'047.projectstatus.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('047.projectstatus.sql'),
'legacy' => 47,
),
'048.relationshipkeys.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('048.relationshipkeys.sql'),
'legacy' => 48,
),
'049.projectowner.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('049.projectowner.sql'),
'legacy' => 49,
),
'050.taskdenormal.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('050.taskdenormal.sql'),
'legacy' => 50,
),
'051.projectfilter.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('051.projectfilter.sql'),
'legacy' => 51,
),
'052.pastelanguage.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('052.pastelanguage.sql'),
'legacy' => 52,
),
'053.feed.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('053.feed.sql'),
'legacy' => 53,
),
'054.subscribers.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('054.subscribers.sql'),
'legacy' => 54,
),
'055.add_author_to_files.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('055.add_author_to_files.sql'),
'legacy' => 55,
),
'056.slowvote.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('056.slowvote.sql'),
'legacy' => 56,
),
'057.parsecache.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('057.parsecache.sql'),
'legacy' => 57,
),
'058.missingkeys.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('058.missingkeys.sql'),
'legacy' => 58,
),
'059.engines.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('059.engines.php'),
'legacy' => 59,
),
'060.phriction.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('060.phriction.sql'),
'legacy' => 60,
),
'061.phrictioncontent.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('061.phrictioncontent.sql'),
'legacy' => 61,
),
'062.phrictionmenu.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('062.phrictionmenu.sql'),
'legacy' => 62,
),
'063.pasteforks.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('063.pasteforks.sql'),
'legacy' => 63,
),
'064.subprojects.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('064.subprojects.sql'),
'legacy' => 64,
),
'065.sshkeys.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('065.sshkeys.sql'),
'legacy' => 65,
),
'066.phrictioncontent.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('066.phrictioncontent.sql'),
'legacy' => 66,
),
'067.preferences.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('067.preferences.sql'),
'legacy' => 67,
),
'068.maniphestauxiliarystorage.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('068.maniphestauxiliarystorage.sql'),
'legacy' => 68,
),
'069.heraldxscript.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('069.heraldxscript.sql'),
'legacy' => 69,
),
'070.differentialaux.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('070.differentialaux.sql'),
'legacy' => 70,
),
'071.contentsource.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('071.contentsource.sql'),
'legacy' => 71,
),
'072.blamerevert.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('072.blamerevert.sql'),
'legacy' => 72,
),
'073.reposymbols.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('073.reposymbols.sql'),
'legacy' => 73,
),
'074.affectedpath.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('074.affectedpath.sql'),
'legacy' => 74,
),
'075.revisionhash.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('075.revisionhash.sql'),
'legacy' => 75,
),
'076.indexedlanguages.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('076.indexedlanguages.sql'),
'legacy' => 76,
),
'077.originalemail.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('077.originalemail.sql'),
'legacy' => 77,
),
'078.nametoken.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('078.nametoken.sql'),
'legacy' => 78,
),
'079.nametokenindex.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('079.nametokenindex.php'),
'legacy' => 79,
),
'080.filekeys.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('080.filekeys.sql'),
'legacy' => 80,
),
'081.filekeys.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('081.filekeys.php'),
'legacy' => 81,
),
'082.xactionkey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('082.xactionkey.sql'),
'legacy' => 82,
),
'083.dxviewtime.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('083.dxviewtime.sql'),
'legacy' => 83,
),
'084.pasteauthorkey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('084.pasteauthorkey.sql'),
'legacy' => 84,
),
'085.packagecommitrelationship.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('085.packagecommitrelationship.sql'),
'legacy' => 85,
),
'086.formeraffil.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('086.formeraffil.sql'),
'legacy' => 86,
),
'087.phrictiondelete.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('087.phrictiondelete.sql'),
'legacy' => 87,
),
'088.audit.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('088.audit.sql'),
'legacy' => 88,
),
'089.projectwiki.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('089.projectwiki.sql'),
'legacy' => 89,
),
'090.forceuniqueprojectnames.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('090.forceuniqueprojectnames.php'),
'legacy' => 90,
),
'091.uniqueslugkey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('091.uniqueslugkey.sql'),
'legacy' => 91,
),
'092.dropgithubnotification.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('092.dropgithubnotification.sql'),
'legacy' => 92,
),
'093.gitremotes.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('093.gitremotes.php'),
'legacy' => 93,
),
'094.phrictioncolumn.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('094.phrictioncolumn.sql'),
'legacy' => 94,
),
'095.directory.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('095.directory.sql'),
'legacy' => 95,
),
'096.filename.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('096.filename.sql'),
'legacy' => 96,
),
'097.heraldruletypes.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('097.heraldruletypes.sql'),
'legacy' => 97,
),
'098.heraldruletypemigration.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('098.heraldruletypemigration.php'),
'legacy' => 98,
),
'099.drydock.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('099.drydock.sql'),
'legacy' => 99,
),
'100.projectxaction.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('100.projectxaction.sql'),
'legacy' => 100,
),
'101.heraldruleapplied.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('101.heraldruleapplied.sql'),
'legacy' => 101,
),
'102.heraldcleanup.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('102.heraldcleanup.php'),
'legacy' => 102,
),
'103.heraldedithistory.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('103.heraldedithistory.sql'),
'legacy' => 103,
),
'104.searchkey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('104.searchkey.sql'),
'legacy' => 104,
),
'105.mimetype.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('105.mimetype.sql'),
'legacy' => 105,
),
'106.chatlog.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('106.chatlog.sql'),
'legacy' => 106,
),
'107.oauthserver.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('107.oauthserver.sql'),
'legacy' => 107,
),
'108.oauthscope.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('108.oauthscope.sql'),
'legacy' => 108,
),
'109.oauthclientphidkey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('109.oauthclientphidkey.sql'),
'legacy' => 109,
),
'110.commitaudit.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('110.commitaudit.sql'),
'legacy' => 110,
),
'111.commitauditmigration.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('111.commitauditmigration.php'),
'legacy' => 111,
),
'112.oauthaccesscoderedirecturi.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('112.oauthaccesscoderedirecturi.sql'),
'legacy' => 112,
),
'113.lastreviewer.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('113.lastreviewer.sql'),
'legacy' => 113,
),
'114.auditrequest.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('114.auditrequest.sql'),
'legacy' => 114,
),
'115.prepareutf8.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('115.prepareutf8.sql'),
'legacy' => 115,
),
'116.utf8-backup-first-expect-wait.sql' => array(
'type' => 'sql',
'name' =>
$this->getPatchPath('116.utf8-backup-first-expect-wait.sql'),
'legacy' => 116,
),
'117.repositorydescription.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('117.repositorydescription.php'),
'legacy' => 117,
),
'118.auditinline.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('118.auditinline.sql'),
'legacy' => 118,
),
'119.filehash.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('119.filehash.sql'),
'legacy' => 119,
),
'120.noop.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('120.noop.sql'),
'legacy' => 120,
),
'121.drydocklog.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('121.drydocklog.sql'),
'legacy' => 121,
),
'122.flag.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('122.flag.sql'),
'legacy' => 122,
),
'123.heraldrulelog.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('123.heraldrulelog.sql'),
'legacy' => 123,
),
'124.subpriority.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('124.subpriority.sql'),
'legacy' => 124,
),
'125.ipv6.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('125.ipv6.sql'),
'legacy' => 125,
),
'126.edges.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('126.edges.sql'),
'legacy' => 126,
),
'127.userkeybody.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('127.userkeybody.sql'),
'legacy' => 127,
),
'128.phabricatorcom.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('128.phabricatorcom.sql'),
'legacy' => 128,
),
'129.savedquery.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('129.savedquery.sql'),
'legacy' => 129,
),
'130.denormalrevisionquery.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('130.denormalrevisionquery.sql'),
'legacy' => 130,
),
'131.migraterevisionquery.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('131.migraterevisionquery.php'),
'legacy' => 131,
),
'132.phame.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('132.phame.sql'),
'legacy' => 132,
),
'133.imagemacro.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('133.imagemacro.sql'),
'legacy' => 133,
),
'134.emptysearch.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('134.emptysearch.sql'),
'legacy' => 134,
),
'135.datecommitted.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('135.datecommitted.sql'),
'legacy' => 135,
),
'136.sex.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('136.sex.sql'),
'legacy' => 136,
),
'137.auditmetadata.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('137.auditmetadata.sql'),
'legacy' => 137,
),
'138.notification.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('138.notification.sql'),
),
'holidays.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('holidays.sql'),
),
'userstatus.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('userstatus.sql'),
),
'emailtable.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('emailtable.sql'),
),
'emailtableport.sql' => array(
'type' => 'php',
'name' => $this->getPatchPath('emailtableport.php'),
),
'emailtableremove.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('emailtableremove.sql'),
),
'phiddrop.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('phiddrop.sql'),
),
'testdatabase.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('testdatabase.sql'),
),
'ldapinfo.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('ldapinfo.sql'),
),
'threadtopic.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('threadtopic.sql'),
),
'usertranslation.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('usertranslation.sql'),
),
'differentialbookmarks.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('differentialbookmarks.sql'),
),
'harbormasterobject.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('harbormasterobject.sql'),
),
'markupcache.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('markupcache.sql'),
),
'maniphestxcache.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('maniphestxcache.sql'),
),
'migrate-maniphest-dependencies.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('migrate-maniphest-dependencies.php'),
),
'migrate-differential-dependencies.php' => array(
'type' => 'php',
'name' => $this->getPatchPath(
'migrate-differential-dependencies.php'),
),
'phameblog.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('phameblog.sql'),
),
'migrate-maniphest-revisions.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('migrate-maniphest-revisions.php'),
),
'daemonstatus.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('daemonstatus.sql'),
),
'symbolcontexts.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('symbolcontexts.sql'),
),
'migrate-project-edges.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('migrate-project-edges.php'),
),
'fact-raw.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('fact-raw.sql'),
),
'ponder.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('ponder.sql')
),
'policy-project.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('policy-project.sql'),
),
'daemonstatuskey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('daemonstatuskey.sql'),
),
'edgetype.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('edgetype.sql'),
),
'ponder-comments.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('ponder-comments.sql'),
),
'pastepolicy.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('pastepolicy.sql'),
),
'xhprof.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('xhprof.sql'),
),
'draft-metadata.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('draft-metadata.sql'),
),
'phamedomain.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('phamedomain.sql'),
),
'ponder-mailkey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('ponder-mailkey.sql'),
),
'ponder-mailkey-populate.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('ponder-mailkey-populate.php'),
),
'phamepolicy.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('phamepolicy.sql'),
),
'phameoneblog.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('phameoneblog.sql'),
),
'statustxt.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('statustxt.sql'),
),
'daemontaskarchive.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('daemontaskarchive.sql'),
),
'drydocktaskid.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('drydocktaskid.sql'),
),
'drydockresoucetype.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('drydockresourcetype.sql'),
),
'liskcounters.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('liskcounters.sql'),
),
'liskcounters.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('liskcounters.php'),
),
'dropfileproxyimage.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('dropfileproxyimage.sql'),
),
'repository-lint.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('repository-lint.sql'),
),
'liskcounters-task.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('liskcounters-task.sql'),
),
'pholio.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('pholio.sql'),
),
'owners-exclude.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('owners-exclude.sql'),
),
'20121209.pholioxactions.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20121209.pholioxactions.sql'),
),
'20121209.xmacroadd.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20121209.xmacroadd.sql'),
),
'20121209.xmacromigrate.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20121209.xmacromigrate.php'),
),
'20121209.xmacromigratekey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20121209.xmacromigratekey.sql'),
),
'20121220.generalcache.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20121220.generalcache.sql'),
),
'20121226.config.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20121226.config.sql'),
),
'20130101.confxaction.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130101.confxaction.sql'),
),
'20130102.metamtareceivedmailmessageidhash.sql' => array(
'type' => 'sql',
'name' =>
$this->getPatchPath('20130102.metamtareceivedmailmessageidhash.sql'),
),
'20130103.filemetadata.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130103.filemetadata.sql'),
),
'20130111.conpherence.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130111.conpherence.sql'),
),
'20130127.altheraldtranscript.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130127.altheraldtranscript.sql'),
),
'20130201.revisionunsubscribed.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130201.revisionunsubscribed.php'),
),
'20130201.revisionunsubscribed.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130201.revisionunsubscribed.sql'),
),
'20130131.conpherencepics.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130131.conpherencepics.sql'),
),
'20130214.chatlogchannel.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130214.chatlogchannel.sql'),
),
'20130214.chatlogchannelid.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130214.chatlogchannelid.sql'),
),
'20130214.token.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130214.token.sql'),
),
'20130215.phabricatorfileaddttl.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130215.phabricatorfileaddttl.sql'),
),
'20130217.cachettl.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130217.cachettl.sql'),
),
'20130218.updatechannelid.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130218.updatechannelid.php'),
),
'20130218.longdaemon.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130218.longdaemon.sql'),
),
'20130219.commitsummary.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130219.commitsummary.sql'),
),
'20130219.commitsummarymig.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130219.commitsummarymig.php'),
),
'20130222.dropchannel.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130222.dropchannel.sql'),
),
'20130226.commitkey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130226.commitkey.sql'),
),
'20131302.maniphestvalue.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20131302.maniphestvalue.sql'),
),
'20130304.lintauthor.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130304.lintauthor.sql'),
),
'releeph.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('releeph.sql'),
),
'20130319.phabricatorfileexplicitupload.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath(
'20130319.phabricatorfileexplicitupload.sql')
),
'20130319.conpherence.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130319.conpherence.sql'),
),
'20130320.phlux.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130320.phlux.sql'),
),
'20130317.phrictionedge.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130317.phrictionedge.sql'),
),
'20130321.token.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130321.token.sql'),
),
'20130310.xactionmeta.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130310.xactionmeta.sql'),
),
'20130322.phortune.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130322.phortune.sql'),
),
'20130323.phortunepayment.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130323.phortunepayment.sql'),
),
'20130324.phortuneproduct.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130324.phortuneproduct.sql'),
),
'20130330.phrequent.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130330.phrequent.sql'),
),
'20130403.conpherencecache.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130403.conpherencecache.sql'),
),
'20130403.conpherencecachemig.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130403.conpherencecachemig.php'),
),
'20130409.commitdrev.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130409.commitdrev.php'),
),
'20130417.externalaccount.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130417.externalaccount.sql'),
),
'20130423.updateexternalaccount.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130423.updateexternalaccount.sql'),
),
'20130423.phortunepaymentrevised.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130423.phortunepaymentrevised.sql'),
),
'20130423.conpherenceindices.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130423.conpherenceindices.sql'),
),
'20130426.search_savedquery.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130426.search_savedquery.sql'),
),
'20130502.countdownrevamp1.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130502.countdownrevamp1.sql'),
),
'20130502.countdownrevamp2.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130502.countdownrevamp2.php'),
),
'20130502.countdownrevamp3.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130502.countdownrevamp3.sql'),
),
'20130507.releephrqsimplifycols.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130507.releephrqsimplifycols.sql'),
),
'20130507.releephrqmailkey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130507.releephrqmailkey.sql'),
),
'20130507.releephrqmailkeypop.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130507.releephrqmailkeypop.php'),
),
'20130508.search_namedquery.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130508.search_namedquery.sql'),
),
'20130508.releephtransactions.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130508.releephtransactions.sql'),
),
'20130508.releephtransactionsmig.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130508.releephtransactionsmig.php'),
),
'20130513.receviedmailstatus.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130513.receviedmailstatus.sql'),
),
'20130519.diviner.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130519.diviner.sql'),
),
'20130521.dropconphimages.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130521.dropconphimages.sql'),
),
'20130523.maniphest_owners.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130523.maniphest_owners.sql'),
),
'20130524.repoxactions.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130524.repoxactions.sql'),
),
'20130529.macroauthor.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130529.macroauthor.sql'),
),
'20130529.macroauthormig.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130529.macroauthormig.php'),
),
'20130530.sessionhash.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130530.sessionhash.php'),
),
'20130530.macrodatekey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130530.macrodatekey.sql'),
),
'20130530.pastekeys.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130530.pastekeys.sql'),
),
'20130531.filekeys.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130531.filekeys.sql'),
),
'20130602.morediviner.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130602.morediviner.sql'),
),
'20130602.namedqueries.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130602.namedqueries.sql'),
),
'20130606.userxactions.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130606.userxactions.sql'),
),
'20130607.xaccount.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130607.xaccount.sql'),
),
'20130611.migrateoauth.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130611.migrateoauth.php'),
),
'20130611.nukeldap.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130611.nukeldap.php'),
),
'20130613.authdb.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130613.authdb.sql'),
),
'20130619.authconf.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130619.authconf.php'),
),
'20130620.diffxactions.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130620.diffxactions.sql'),
),
'20130621.diffcommentphid.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130621.diffcommentphid.sql'),
),
'20130621.diffcommentphidmig.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130621.diffcommentphidmig.php'),
),
'20130621.diffcommentunphid.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130621.diffcommentunphid.sql'),
),
'20130622.doorkeeper.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130622.doorkeeper.sql'),
),
'20130628.legalpadv0.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130628.legalpadv0.sql'),
),
'20130701.conduitlog.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130701.conduitlog.sql'),
),
'legalpad-mailkey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('legalpad-mailkey.sql'),
),
'legalpad-mailkey-populate.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('legalpad-mailkey-populate.php'),
),
'20130703.legalpaddocdenorm.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130703.legalpaddocdenorm.sql'),
),
'20130703.legalpaddocdenorm.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130703.legalpaddocdenorm.php'),
),
'20130709.legalpadsignature.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130709.legalpadsignature.sql'),
),
'20130709.droptimeline.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130709.droptimeline.sql'),
),
'20130711.trimrealnames.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130711.trimrealnames.php'),
),
'20130714.votexactions.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130714.votexactions.sql'),
),
'20130715.votecomments.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130715.votecomments.php'),
),
'20130715.voteedges.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130715.voteedges.sql'),
),
'20130711.pholioimageobsolete.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130711.pholioimageobsolete.sql'),
),
'20130711.pholioimageobsolete.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130711.pholioimageobsolete.php'),
),
'20130711.pholioimageobsolete2.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130711.pholioimageobsolete2.sql'),
),
'20130716.archivememberlessprojects.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130716.archivememberlessprojects.php'),
),
'20130722.pholioreplace.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130722.pholioreplace.sql'),
),
'20130723.taskstarttime.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130723.taskstarttime.sql'),
),
'20130727.ponderquestionstatus.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130727.ponderquestionstatus.sql'),
),
'20130726.ponderxactions.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130726.ponderxactions.sql'),
),
'20130728.ponderunique.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130728.ponderunique.php'),
),
'20130728.ponderuniquekey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130728.ponderuniquekey.sql'),
),
'20130728.ponderxcomment.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130728.ponderxcomment.php'),
),
'20130801.pastexactions.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130801.pastexactions.sql'),
),
'20130801.pastexactions.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130801.pastexactions.php'),
),
'20130805.pastemailkey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130805.pastemailkey.sql'),
),
'20130805.pasteedges.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130805.pasteedges.sql'),
),
'20130805.pastemailkeypop.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130805.pastemailkeypop.php'),
),
'20130802.heraldphid.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130802.heraldphid.sql'),
),
'20130802.heraldphids.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('20130802.heraldphids.php'),
),
'20130802.heraldphidukey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130802.heraldphidukey.sql'),
),
'20130802.heraldxactions.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130802.heraldxactions.sql'),
),
'20130731.releephrepoid.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130731.releephrepoid.sql'),
),
'20130731.releephproject.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130731.releephproject.sql'),
),
'20130731.releephcutpointidentifier.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130731.releephcutpointidentifier.sql'),
),
'20130814.usercustom.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130814.usercustom.sql'),
),
'20130820.releephxactions.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130820.releephxactions.sql'),
),
+ '20130826.divinernode.sql' => array(
+ 'type' => 'sql',
+ 'name' => $this->getPatchPath('20130826.divinernode.sql'),
+ ),
);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Mar 16, 10:58 PM (15 h, 31 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
963517
Default Alt Text
(123 KB)

Event Timeline