@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Move symbols to be repository-based

Summary: Fixes T7220. Ref T7977. Changes symbols from being bound to an Arcanist project to being bound to a repository.

Test Plan:
- Added symbols and then applied migrations, symbols seemed to be migrated successfully.
- Tested the `/diffusion/symbol/$SYMBOL_NAME` endpoint.
- Tested the `/diffusion/symbol/$SYMBOL_NAME` endpoint with the `?repositories=$REPOSITORY_PHID` parameter.

Reviewers: #blessed_reviewers, epriestley

Reviewed By: #blessed_reviewers, epriestley

Subscribers: avivey, Korvin, epriestley

Maniphest Tasks: T7977, T7220

Differential Revision: https://secure.phabricator.com/D12608

+419 -401
+2
resources/sql/autopatches/20150503.repositorysymbols.1.sql
··· 1 + ALTER TABLE {$NAMESPACE}_repository.repository_symbol 2 + ADD repositoryPHID varbinary(64) NOT NULL AFTER arcanistProjectID;
+26
resources/sql/autopatches/20150503.repositorysymbols.2.php
··· 1 + <?php 2 + 3 + $projects = id(new PhabricatorRepositoryArcanistProjectQuery()) 4 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 5 + ->needRepositories(true) 6 + ->execute(); 7 + 8 + $table = new PhabricatorRepositorySymbol(); 9 + $conn_w = $table->establishConnection('w'); 10 + 11 + foreach ($projects as $project) { 12 + $repo = $project->getRepository(); 13 + 14 + if (!$repo) { 15 + continue; 16 + } 17 + 18 + echo pht("Migrating symbols for '%s' project...\n", $project->getName()); 19 + 20 + queryfx( 21 + $conn_w, 22 + 'UPDATE %T SET repositoryPHID = %s WHERE arcanistProjectID = %d', 23 + $table->getTableName(), 24 + $repo->getPHID(), 25 + $project->getID()); 26 + }
+2
resources/sql/autopatches/20150503.repositorysymbols.3.sql
··· 1 + ALTER TABLE {$NAMESPACE}_repository.repository_symbol 2 + DROP COLUMN arcanistProjectID;
-32
scripts/symbols/clear_project_symbols.php
··· 1 - #!/usr/bin/env php 2 - <?php 3 - 4 - $root = dirname(dirname(dirname(__FILE__))); 5 - require_once $root.'/scripts/__init_script__.php'; 6 - 7 - $project = id(new PhabricatorRepositoryArcanistProject())->loadOneWhere( 8 - 'name = %s', $argv[1]); 9 - if (!$project) { 10 - throw new Exception('No such arcanist project.'); 11 - } 12 - 13 - $input = file_get_contents('php://stdin'); 14 - $normalized = array(); 15 - foreach (explode("\n", trim($input)) as $path) { 16 - // emulate the behavior of the symbol generation scripts 17 - $normalized[] = '/'.ltrim($path, './'); 18 - } 19 - $paths = PhabricatorRepositoryCommitChangeParserWorker::lookupOrCreatePaths( 20 - $normalized); 21 - 22 - $symbol = new PhabricatorRepositorySymbol(); 23 - $conn_w = $symbol->establishConnection('w'); 24 - 25 - foreach (array_chunk(array_values($paths), 128) as $chunk) { 26 - queryfx( 27 - $conn_w, 28 - 'DELETE FROM %T WHERE arcanistProjectID = %d AND pathID IN (%Ld)', 29 - $symbol->getTableName(), 30 - $project->getID(), 31 - $chunk); 32 - }
+58
scripts/symbols/clear_repository_symbols.php
··· 1 + #!/usr/bin/env php 2 + <?php 3 + 4 + $root = dirname(dirname(dirname(__FILE__))); 5 + require_once $root.'/scripts/__init_script__.php'; 6 + 7 + $args = new PhutilArgumentParser($argv); 8 + $args->setSynopsis(<<<EOSYNOPSIS 9 + **clear_repository_symbols.php** [__options__] __callsign__ 10 + 11 + Clear repository symbols. 12 + EOSYNOPSIS 13 + ); 14 + $args->parseStandardArguments(); 15 + $args->parse( 16 + array( 17 + array( 18 + 'name' => 'callsign', 19 + 'wildcard' => true, 20 + ), 21 + )); 22 + 23 + $callsigns = $args->getArg('callsign'); 24 + if (count($callsigns) !== 1) { 25 + $args->printHelpAndExit(); 26 + } 27 + 28 + $callsign = head($callsigns); 29 + $repository = id(new PhabricatorRepositoryQuery()) 30 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 31 + ->withCallsigns($callsigns) 32 + ->executeOne(); 33 + 34 + if (!$repository) { 35 + echo pht("Repository '%s' does not exist.", $callsign); 36 + exit(1); 37 + } 38 + 39 + $input = file_get_contents('php://stdin'); 40 + $normalized = array(); 41 + foreach (explode("\n", trim($input)) as $path) { 42 + // Emulate the behavior of the symbol generation scripts. 43 + $normalized[] = '/'.ltrim($path, './'); 44 + } 45 + $paths = PhabricatorRepositoryCommitChangeParserWorker::lookupOrCreatePaths( 46 + $normalized); 47 + 48 + $symbol = new PhabricatorRepositorySymbol(); 49 + $conn_w = $symbol->establishConnection('w'); 50 + 51 + foreach (array_chunk(array_values($paths), 128) as $chunk) { 52 + queryfx( 53 + $conn_w, 54 + 'DELETE FROM %T WHERE repositoryPHID = %s AND pathID IN (%Ld)', 55 + $symbol->getTableName(), 56 + $repository->getPHID(), 57 + $chunk); 58 + }
+41 -33
scripts/symbols/generate_ctags_symbols.php
··· 4 4 $root = dirname(dirname(dirname(__FILE__))); 5 5 require_once $root.'/scripts/__init_script__.php'; 6 6 7 + $args = new PhutilArgumentParser($argv); 8 + $args->setSynopsis(<<<EOSYNOPSIS 9 + **generate_ctags_symbols.php** [__options__] 10 + 11 + Generate repository symbols using Exuberant Ctags. Paths are read from stdin. 12 + EOSYNOPSIS 13 + ); 14 + $args->parseStandardArguments(); 15 + 7 16 if (ctags_check_executable() == false) { 8 17 echo phutil_console_format( 9 - "Could not find Exuberant ctags. Make sure it is installed and\n". 10 - "available in executable path.\n\n". 11 - "Exuberant ctags project page: http://ctags.sourceforge.net/\n"); 18 + "%s\n\n%s\n", 19 + pht( 20 + 'Could not find Exuberant Ctags. Make sure it is installed and '. 21 + 'available in executable path.'), 22 + pht( 23 + 'Exuberant Ctags project page: %s', 24 + 'http://ctags.sourceforge.net/')); 12 25 exit(1); 13 26 } 14 27 15 - if ($argc !== 1 || posix_isatty(STDIN)) { 28 + if (posix_isatty(STDIN)) { 16 29 echo phutil_console_format( 17 - "usage: find . -type f -name '*.py' | ./generate_ctags_symbols.php\n"); 30 + "%s\n", 31 + pht( 32 + 'Usage: %s', 33 + "find . -type f -name '*.py' | ./generate_ctags_symbols.php")); 18 34 exit(1); 19 35 } 20 36 21 37 $input = file_get_contents('php://stdin'); 22 - $input = trim($input); 23 - $input = explode("\n", $input); 24 - 25 38 $data = array(); 26 39 $futures = array(); 27 40 28 - foreach ($input as $file) { 41 + foreach (explode("\n", trim($input)) as $file) { 29 42 $file = Filesystem::readablePath($file); 30 43 $futures[$file] = ctags_get_parser_future($file); 31 44 } 32 45 33 - $futures = id(new FutureIterator($futures)) 34 - ->limit(8); 35 - foreach ($futures as $file => $future) { 46 + $futures = new FutureIterator($futures); 47 + foreach ($futures->limit(8) as $file => $future) { 36 48 $tags = $future->resolve(); 37 49 $tags = explode("\n", $tags[1]); 38 50 39 51 foreach ($tags as $tag) { 40 52 $parts = explode(';', $tag); 41 - // skip lines that we can not parse 53 + 54 + // Skip lines that we can not parse. 42 55 if (count($parts) < 2) { 43 56 continue; 44 57 } 45 58 46 - // split ctags information 59 + // Split ctags information. 47 60 $tag_info = explode("\t", $parts[0]); 48 - // split exuberant ctags "extension fields" (additional information) 61 + 62 + // Split exuberant ctags "extension fields" (additional information). 49 63 $parts[1] = trim($parts[1], "\t \""); 50 64 $extension_fields = explode("\t", $parts[1]); 51 65 52 - // skip lines that we can not parse 66 + // Skip lines that we can not parse. 53 67 if (count($tag_info) < 3 || count($extension_fields) < 2) { 54 68 continue; 55 69 } 56 70 57 - // default $context to empty 71 + // Default context to empty. 58 72 $extension_fields[] = ''; 59 73 list($token, $file_path, $line_num) = $tag_info; 60 74 list($type, $language, $context) = $extension_fields; 61 75 62 - // skip lines with tokens containing a space 76 + // Skip lines with tokens containing a space. 63 77 if (strpos($token, ' ') !== false) { 64 78 continue; 65 79 } 66 80 67 - // strip "language:" 81 + // Strip "language:" 68 82 $language = substr($language, 9); 69 83 70 84 // To keep consistent with "Separate with commas, for example: php, py" 71 85 // in Arcanist Project edit form. 72 86 $language = str_ireplace('python', 'py', $language); 73 87 74 - // also, "normalize" c++ and c# 88 + // Also, "normalize" C++ and C#. 75 89 $language = str_ireplace('c++', 'cpp', $language); 76 90 $language = str_ireplace('c#', 'cs', $language); 77 91 78 - // Ruby has "singleton method", for example 92 + // Ruby has "singleton method", for example. 79 93 $type = substr(str_replace(' ', '_', $type), 0, 12); 94 + 80 95 // class:foo, struct:foo, union:foo, enum:foo, ... 81 96 $context = last(explode(':', $context, 2)); 82 97 ··· 89 104 } 90 105 } 91 106 92 - function ctags_get_parser_future($file_path) { 93 - $future = new ExecFuture('ctags -n --fields=Kls -o - %s', 94 - $file_path); 107 + function ctags_get_parser_future($path) { 108 + $future = new ExecFuture('ctags -n --fields=Kls -o - %s', $path); 95 109 return $future; 96 110 } 97 111 98 112 function ctags_check_executable() { 99 - $future = new ExecFuture('ctags --version'); 100 - $result = $future->resolve(); 101 - 102 - if (empty($result[1])) { 103 - return false; 104 - } 105 - 106 - return true; 113 + $result = exec_manual('ctags --version'); 114 + return !empty($result[1]); 107 115 } 108 116 109 117 function print_symbol($file, $line_num, $type, $token, $context, $language) { 110 - // get rid of relative path 118 + // Get rid of relative path. 111 119 $file = explode('/', $file); 112 120 if ($file[0] == '.' || $file[0] == '..') { 113 121 array_shift($file);
+24 -16
scripts/symbols/generate_php_symbols.php
··· 4 4 $root = dirname(dirname(dirname(__FILE__))); 5 5 require_once $root.'/scripts/__init_script__.php'; 6 6 7 - if ($argc !== 1 || posix_isatty(STDIN)) { 7 + $args = new PhutilArgumentParser($argv); 8 + $args->setSynopsis(<<<EOSYNOPSIS 9 + **generate_php_symbols.php** [__options__] 10 + 11 + Generate repository symbols using XHPAST. Paths are read from stdin. 12 + EOSYNOPSIS 13 + ); 14 + $args->parseStandardArguments(); 15 + 16 + if (posix_isatty(STDIN)) { 8 17 echo phutil_console_format( 9 - "usage: find . -type f -name '*.php' | ./generate_php_symbols.php\n"); 18 + "%s\n", 19 + pht( 20 + 'Usage: %s', 21 + "find . -type f -name '*.php' | ./generate_php_symbols.php")); 10 22 exit(1); 11 23 } 12 24 13 25 $input = file_get_contents('php://stdin'); 14 - $input = trim($input); 15 - $input = explode("\n", $input); 16 - 17 26 $data = array(); 18 27 $futures = array(); 19 28 20 - foreach ($input as $file) { 29 + foreach (explode("\n", trim($input)) as $file) { 21 30 $file = Filesystem::readablePath($file); 22 31 $data[$file] = Filesystem::readFile($file); 23 32 $futures[$file] = PhutilXHPASTBinary::getParserFuture($data[$file]); 24 33 } 25 34 26 - $futures = id(new FutureIterator($futures)) 27 - ->limit(8); 28 - foreach ($futures as $file => $future) { 35 + $futures = new FutureIterator($futures); 36 + foreach ($futures->limit(8) as $file => $future) { 29 37 $tree = XHPASTTree::newFromDataAndResolvedExecFuture( 30 38 $data[$file], 31 39 $future->resolve()); ··· 36 44 $functions = $root->selectDescendantsOfType('n_FUNCTION_DECLARATION'); 37 45 foreach ($functions as $function) { 38 46 $name = $function->getChildByIndex(2); 39 - // Skip anonymous functions 47 + // Skip anonymous functions. 40 48 if (!$name->getConcreteString()) { 41 49 continue; 42 50 } ··· 67 75 } 68 76 69 77 foreach ($scopes as $scope) { 70 - // this prints duplicate symbols in the case of nested classes 71 - // luckily, PHP doesn't allow those 78 + // This prints duplicate symbols in the case of nested classes. 79 + // Luckily, PHP doesn't allow those. 72 80 list($class, $class_name) = $scope; 73 81 74 82 $consts = $class->selectDescendantsOfType( ··· 100 108 } 101 109 } 102 110 103 - function print_symbol($file, $type, $token, $context = null) { 111 + function print_symbol($file, $type, XHPASTNode $node, $context = null) { 104 112 $parts = array( 105 113 $context ? $context->getConcreteString() : '', 106 - // variable tokens are `$name`, not just `name`, so strip the $ off of 114 + // Variable tokens are `$name`, not just `name`, so strip the "$"" off of 107 115 // class field names 108 - ltrim($token->getConcreteString(), '$'), 116 + ltrim($node->getConcreteString(), '$'), 109 117 $type, 110 118 'php', 111 - $token->getLineNumber(), 119 + $node->getLineNumber(), 112 120 '/'.ltrim($file, './'), 113 121 ); 114 122 echo implode(' ', $parts)."\n";
-205
scripts/symbols/import_project_symbols.php
··· 1 - #!/usr/bin/env php 2 - <?php 3 - 4 - 5 - $root = dirname(dirname(dirname(__FILE__))); 6 - require_once $root.'/scripts/__init_script__.php'; 7 - 8 - $args = new PhutilArgumentParser($argv); 9 - $args->setSynopsis(<<<EOSYNOPSIS 10 - **import_project_symbols.php** [__options__] __project_name__ < symbols 11 - 12 - Import project symbols (symbols are read from stdin). 13 - EOSYNOPSIS 14 - ); 15 - $args->parseStandardArguments(); 16 - $args->parse( 17 - array( 18 - array( 19 - 'name' => 'no-purge', 20 - 'help' => 'Do not clear all symbols for this project before '. 21 - 'uploading new symbols. Useful for incremental updating.', 22 - ), 23 - array( 24 - 'name' => 'ignore-errors', 25 - 'help' => 'If a line can\'t be parsed, ignore that line and '. 26 - 'continue instead of exiting.', 27 - ), 28 - array( 29 - 'name' => 'max-transaction', 30 - 'param' => 'num-syms', 31 - 'default' => '100000', 32 - 'help' => 'Maximum number of symbols that should '. 33 - 'be part of a single transaction', 34 - ), 35 - array( 36 - 'name' => 'more', 37 - 'wildcard' => true, 38 - ), 39 - )); 40 - 41 - $more = $args->getArg('more'); 42 - if (count($more) !== 1) { 43 - $args->printHelpAndExit(); 44 - } 45 - 46 - $project_name = head($more); 47 - $project = id(new PhabricatorRepositoryArcanistProject())->loadOneWhere( 48 - 'name = %s', 49 - $project_name); 50 - 51 - if (!$project) { 52 - // TODO: Provide a less silly way to do this explicitly, or just do it right 53 - // here. 54 - echo "Project '{$project_name}' is unknown. Upload a diff to implicitly ". 55 - "create it.\n"; 56 - exit(1); 57 - } 58 - 59 - echo "Parsing input from stdin...\n"; 60 - $input = file_get_contents('php://stdin'); 61 - $input = trim($input); 62 - $input = explode("\n", $input); 63 - 64 - 65 - function commit_symbols ($syms, $project, $no_purge) { 66 - echo "Looking up path IDs...\n"; 67 - $path_map = 68 - PhabricatorRepositoryCommitChangeParserWorker::lookupOrCreatePaths( 69 - ipull($syms, 'path')); 70 - 71 - $symbol = new PhabricatorRepositorySymbol(); 72 - $conn_w = $symbol->establishConnection('w'); 73 - 74 - echo "Preparing queries...\n"; 75 - $sql = array(); 76 - foreach ($syms as $dict) { 77 - $sql[] = qsprintf( 78 - $conn_w, 79 - '(%d, %s, %s, %s, %s, %d, %d)', 80 - $project->getID(), 81 - $dict['ctxt'], 82 - $dict['name'], 83 - $dict['type'], 84 - $dict['lang'], 85 - $dict['line'], 86 - $path_map[$dict['path']]); 87 - } 88 - 89 - if (!$no_purge) { 90 - echo "Purging old syms...\n"; 91 - queryfx($conn_w, 92 - 'DELETE FROM %T WHERE arcanistProjectID = %d', 93 - $symbol->getTableName(), 94 - $project->getID()); 95 - } 96 - 97 - echo "Loading ".number_format(count($sql))." syms...\n"; 98 - foreach (array_chunk($sql, 128) as $chunk) { 99 - queryfx($conn_w, 100 - 'INSERT INTO %T 101 - (arcanistProjectID, symbolContext, symbolName, symbolType, 102 - symbolLanguage, lineNumber, pathID) VALUES %Q', 103 - $symbol->getTableName(), 104 - implode(', ', $chunk)); 105 - } 106 - 107 - } 108 - 109 - function check_string_value($value, $field_name, $line_no, $max_length) { 110 - if (strlen($value) > $max_length) { 111 - throw new Exception( 112 - "{$field_name} '{$value}' defined on line #{$line_no} is too long, ". 113 - "maximum {$field_name} length is {$max_length} characters."); 114 - } 115 - 116 - if (!phutil_is_utf8_with_only_bmp_characters($value)) { 117 - throw new Exception( 118 - "{$field_name} '{$value}' defined on line #{$line_no} is not a valid ". 119 - "UTF-8 string, ". 120 - "it should contain only UTF-8 characters."); 121 - } 122 - } 123 - 124 - $no_purge = $args->getArg('no-purge'); 125 - $symbols = array(); 126 - foreach ($input as $key => $line) { 127 - try { 128 - $line_no = $key + 1; 129 - $matches = null; 130 - $ok = preg_match( 131 - '/^((?P<context>[^ ]+)? )?(?P<name>[^ ]+) (?P<type>[^ ]+) '. 132 - '(?P<lang>[^ ]+) (?P<line>\d+) (?P<path>.*)$/', 133 - $line, 134 - $matches); 135 - if (!$ok) { 136 - throw new Exception( 137 - "Line #{$line_no} of input is invalid. Expected five or six ". 138 - "space-delimited fields: maybe symbol context, symbol name, symbol ". 139 - "type, symbol language, line number, path. ". 140 - "For example:\n\n". 141 - "idx function php 13 /path/to/some/file.php\n\n". 142 - "Actual line was:\n\n". 143 - "{$line}"); 144 - } 145 - if (empty($matches['context'])) { 146 - $matches['context'] = ''; 147 - } 148 - $context = $matches['context']; 149 - $name = $matches['name']; 150 - $type = $matches['type']; 151 - $lang = $matches['lang']; 152 - $line_number = $matches['line']; 153 - $path = $matches['path']; 154 - 155 - check_string_value($context, 'Symbol context', $line_no, 128); 156 - check_string_value($name, 'Symbol name', $line_no, 128); 157 - check_string_value($type, 'Symbol type', $line_no, 12); 158 - check_string_value($lang, 'Symbol language', $line_no, 32); 159 - check_string_value($path, 'Path', $line_no, 512); 160 - 161 - if (!strlen($path) || $path[0] != '/') { 162 - throw new Exception( 163 - "Path '{$path}' defined on line #{$line_no} is invalid. Paths should ". 164 - "begin with '/' and specify a path from the root of the project, like ". 165 - "'/src/utils/utils.php'."); 166 - } 167 - 168 - $symbols[] = array( 169 - 'ctxt' => $context, 170 - 'name' => $name, 171 - 'type' => $type, 172 - 'lang' => $lang, 173 - 'line' => $line_number, 174 - 'path' => $path, 175 - ); 176 - } catch (Exception $e) { 177 - if ($args->getArg('ignore-errors')) { 178 - continue; 179 - } else { 180 - throw $e; 181 - } 182 - } 183 - 184 - if (count ($symbols) >= $args->getArg('max-transaction')) { 185 - try { 186 - echo "Committing {$args->getArg('max-transaction')} symbols....\n"; 187 - commit_symbols($symbols, $project, $no_purge); 188 - $no_purge = true; 189 - unset($symbols); 190 - $symbols = array(); 191 - } catch (Exception $e) { 192 - if ($args->getArg('ignore-errors')) { 193 - continue; 194 - } else { 195 - throw $e; 196 - } 197 - } 198 - } 199 - } 200 - 201 - if (count($symbols)) { 202 - commit_symbols($symbols, $project, $no_purge); 203 - } 204 - 205 - echo "Done.\n";
+228
scripts/symbols/import_repository_symbols.php
··· 1 + #!/usr/bin/env php 2 + <?php 3 + 4 + $root = dirname(dirname(dirname(__FILE__))); 5 + require_once $root.'/scripts/__init_script__.php'; 6 + 7 + $args = new PhutilArgumentParser($argv); 8 + $args->setSynopsis(<<<EOSYNOPSIS 9 + **import_repository_symbols.php** [__options__] __callsign__ < symbols 10 + 11 + Import repository symbols (symbols are read from stdin). 12 + EOSYNOPSIS 13 + ); 14 + $args->parseStandardArguments(); 15 + $args->parse( 16 + array( 17 + array( 18 + 'name' => 'no-purge', 19 + 'help' => pht( 20 + 'Do not clear all symbols for this repository before '. 21 + 'uploading new symbols. Useful for incremental updating.'), 22 + ), 23 + array( 24 + 'name' => 'ignore-errors', 25 + 'help' => pht( 26 + "If a line can't be parsed, ignore that line and ". 27 + "continue instead of exiting."), 28 + ), 29 + array( 30 + 'name' => 'max-transaction', 31 + 'param' => 'num-syms', 32 + 'default' => '100000', 33 + 'help' => pht( 34 + 'Maximum number of symbols that should '. 35 + 'be part of a single transaction.'), 36 + ), 37 + array( 38 + 'name' => 'more', 39 + 'wildcard' => true, 40 + ), 41 + )); 42 + 43 + $more = $args->getArg('more'); 44 + if (count($more) !== 1) { 45 + $args->printHelpAndExit(); 46 + } 47 + 48 + $callsign = head($more); 49 + $repository = id(new PhabricatorRepository())->loadOneWhere( 50 + 'callsign = %s', 51 + $callsign); 52 + 53 + if (!$repository) { 54 + echo pht("Repository '%s' does not exist.", $callsign); 55 + exit(1); 56 + } 57 + 58 + if (!function_exists('posix_isatty') || posix_isatty(STDIN)) { 59 + echo pht('Parsing input from stdin...'), "\n"; 60 + } 61 + 62 + $input = file_get_contents('php://stdin'); 63 + $input = trim($input); 64 + $input = explode("\n", $input); 65 + 66 + 67 + function commit_symbols( 68 + array $symbols, 69 + PhabricatorRepository $repository, 70 + $no_purge) { 71 + 72 + echo pht('Looking up path IDs...'), "\n"; 73 + $path_map = 74 + PhabricatorRepositoryCommitChangeParserWorker::lookupOrCreatePaths( 75 + ipull($symbols, 'path')); 76 + 77 + $symbol = new PhabricatorRepositorySymbol(); 78 + $conn_w = $symbol->establishConnection('w'); 79 + 80 + echo pht('Preparing queries...'), "\n"; 81 + $sql = array(); 82 + foreach ($symbols as $dict) { 83 + $sql[] = qsprintf( 84 + $conn_w, 85 + '(%s, %s, %s, %s, %s, %d, %d)', 86 + $repository->getPHID(), 87 + $dict['ctxt'], 88 + $dict['name'], 89 + $dict['type'], 90 + $dict['lang'], 91 + $dict['line'], 92 + $path_map[$dict['path']]); 93 + } 94 + 95 + if (!$no_purge) { 96 + echo pht('Purging old symbols...'), "\n"; 97 + queryfx( 98 + $conn_w, 99 + 'DELETE FROM %T WHERE repositoryPHID = %s', 100 + $symbol->getTableName(), 101 + $repository->getPHID()); 102 + } 103 + 104 + echo pht('Loading %s symbols...', new PhutilNumber(count($sql))), "\n"; 105 + foreach (array_chunk($sql, 128) as $chunk) { 106 + queryfx( 107 + $conn_w, 108 + 'INSERT INTO %T 109 + (repositoryPHID, symbolContext, symbolName, symbolType, 110 + symbolLanguage, lineNumber, pathID) VALUES %Q', 111 + $symbol->getTableName(), 112 + implode(', ', $chunk)); 113 + } 114 + } 115 + 116 + function check_string_value($value, $field_name, $line_no, $max_length) { 117 + if (strlen($value) > $max_length) { 118 + throw new Exception( 119 + pht( 120 + "%s '%s' defined on line #%d is too long, ". 121 + "maximum %s length is %d characters.", 122 + $field_name, 123 + $value, 124 + $line_no, 125 + $field_name, 126 + $max_length)); 127 + } 128 + 129 + if (!phutil_is_utf8_with_only_bmp_characters($value)) { 130 + throw new Exception( 131 + pht( 132 + "%s '%s' defined on line #%d is not a valid ". 133 + "UTF-8 string, it should contain only UTF-8 characters.", 134 + $field_name, 135 + $value, 136 + $line_no)); 137 + } 138 + } 139 + 140 + $no_purge = $args->getArg('no-purge'); 141 + $symbols = array(); 142 + foreach ($input as $key => $line) { 143 + try { 144 + $line_no = $key + 1; 145 + $matches = null; 146 + $ok = preg_match( 147 + '/^((?P<context>[^ ]+)? )?(?P<name>[^ ]+) (?P<type>[^ ]+) '. 148 + '(?P<lang>[^ ]+) (?P<line>\d+) (?P<path>.*)$/', 149 + $line, 150 + $matches); 151 + if (!$ok) { 152 + throw new Exception( 153 + pht( 154 + "Line #%d of input is invalid. Expected five or six space-delimited ". 155 + "fields: maybe symbol context, symbol name, symbol type, symbol ". 156 + "language, line number, path. For example:\n\n%s\n\n". 157 + "Actual line was:\n\n%s", 158 + $line_no, 159 + 'idx function php 13 /path/to/some/file.php', 160 + $line)); 161 + } 162 + if (empty($matches['context'])) { 163 + $matches['context'] = ''; 164 + } 165 + $context = $matches['context']; 166 + $name = $matches['name']; 167 + $type = $matches['type']; 168 + $lang = $matches['lang']; 169 + $line_number = $matches['line']; 170 + $path = $matches['path']; 171 + 172 + check_string_value($context, 'Symbol context', $line_no, 128); 173 + check_string_value($name, 'Symbol name', $line_no, 128); 174 + check_string_value($type, 'Symbol type', $line_no, 12); 175 + check_string_value($lang, 'Symbol language', $line_no, 32); 176 + check_string_value($path, 'Path', $line_no, 512); 177 + 178 + if (!strlen($path) || $path[0] != '/') { 179 + throw new Exception( 180 + pht( 181 + "Path '%s' defined on line #%d is invalid. Paths should begin with ". 182 + "'%s' and specify a path from the root of the project, like '%s'.", 183 + $path, 184 + $line_no, 185 + '/', 186 + '/src/utils/utils.php')); 187 + } 188 + 189 + $symbols[] = array( 190 + 'ctxt' => $context, 191 + 'name' => $name, 192 + 'type' => $type, 193 + 'lang' => $lang, 194 + 'line' => $line_number, 195 + 'path' => $path, 196 + ); 197 + } catch (Exception $e) { 198 + if ($args->getArg('ignore-errors')) { 199 + continue; 200 + } else { 201 + throw $e; 202 + } 203 + } 204 + 205 + if (count ($symbols) >= $args->getArg('max-transaction')) { 206 + try { 207 + echo pht( 208 + "Committing %s symbols...\n", 209 + new PhutilNumber($args->getArg('max-transaction'))); 210 + commit_symbols($symbols, $repository, $no_purge); 211 + $no_purge = true; 212 + unset($symbols); 213 + $symbols = array(); 214 + } catch (Exception $e) { 215 + if ($args->getArg('ignore-errors')) { 216 + continue; 217 + } else { 218 + throw $e; 219 + } 220 + } 221 + } 222 + } 223 + 224 + if (count($symbols)) { 225 + commit_symbols($symbols, $repository, $no_purge); 226 + } 227 + 228 + echo pht('Done.'), "\n";
+1 -2
src/applications/diffusion/conduit/DiffusionFindSymbolsConduitAPIMethod.php
··· 8 8 } 9 9 10 10 public function getMethodDescription() { 11 - return 'Retrieve Diffusion symbol information.'; 11 + return pht('Retrieve Diffusion symbol information.'); 12 12 } 13 13 14 14 protected function defineParamTypes() { ··· 51 51 } 52 52 53 53 $query->needPaths(true); 54 - $query->needArcanistProjects(true); 55 54 $query->needRepositories(true); 56 55 57 56 $results = $query->execute();
+12 -19
src/applications/diffusion/controller/DiffusionSymbolController.php
··· 24 24 $query->setLanguage($request->getStr('lang')); 25 25 } 26 26 27 - if ($request->getStr('projects')) { 28 - $phids = $request->getStr('projects'); 27 + if ($request->getStr('repositories')) { 28 + $phids = $request->getStr('repositories'); 29 29 $phids = explode(',', $phids); 30 30 $phids = array_filter($phids); 31 31 32 32 if ($phids) { 33 - $projects = id(new PhabricatorRepositoryArcanistProject()) 34 - ->loadAllWhere( 35 - 'phid IN (%Ls)', 36 - $phids); 37 - $projects = mpull($projects, 'getID'); 38 - if ($projects) { 39 - $query->setProjectIDs($projects); 33 + $repos = id(new PhabricatorRepositoryQuery()) 34 + ->setViewer($request->getUser()) 35 + ->withPHIDs($phids) 36 + ->execute(); 37 + 38 + $repos = mpull($repos, 'getPHID'); 39 + if ($repos) { 40 + $query->withRepositoryPHIDs($repos); 40 41 } 41 42 } 42 43 } 43 44 44 45 $query->needPaths(true); 45 - $query->needArcanistProjects(true); 46 46 $query->needRepositories(true); 47 47 48 48 $symbols = $query->execute(); ··· 73 73 74 74 $rows = array(); 75 75 foreach ($symbols as $symbol) { 76 - $project = $symbol->getArcanistProject(); 77 - if ($project) { 78 - $project_name = $project->getName(); 79 - } else { 80 - $project_name = '-'; 81 - } 82 - 83 76 $file = $symbol->getPath(); 84 77 $line = $symbol->getLineNumber(); 85 78 ··· 110 103 $symbol->getSymbolContext(), 111 104 $symbol->getSymbolName(), 112 105 $symbol->getSymbolLanguage(), 113 - $project_name, 106 + $repo->getMonogram(), 114 107 $location, 115 108 ); 116 109 } ··· 122 115 pht('Context'), 123 116 pht('Name'), 124 117 pht('Language'), 125 - pht('Project'), 118 + pht('Repository'), 126 119 pht('File'), 127 120 )); 128 121 $table->setColumnClasses(
+15 -56
src/applications/diffusion/query/DiffusionSymbolQuery.php
··· 16 16 private $namePrefix; 17 17 private $name; 18 18 19 - private $projectIDs; 19 + private $repositoryPHIDs; 20 20 private $language; 21 21 private $type; 22 22 23 23 private $needPaths; 24 - private $needArcanistProject; 25 24 private $needRepositories; 26 25 27 26 ··· 72 71 /** 73 72 * @task config 74 73 */ 75 - public function setProjectIDs(array $project_ids) { 76 - $this->projectIDs = $project_ids; 74 + public function withRepositoryPHIDs(array $repository_phids) { 75 + $this->repositoryPHIDs = $repository_phids; 77 76 return $this; 78 77 } 79 78 ··· 108 107 /** 109 108 * @task config 110 109 */ 111 - public function needArcanistProjects($need_arcanist_projects) { 112 - $this->needArcanistProjects = $need_arcanist_projects; 113 - return $this; 114 - } 115 - 116 - 117 - /** 118 - * @task config 119 - */ 120 110 public function needRepositories($need_repositories) { 121 111 $this->needRepositories = $need_repositories; 122 112 return $this; ··· 132 122 public function execute() { 133 123 if ($this->name && $this->namePrefix) { 134 124 throw new Exception( 135 - 'You can not set both a name and a name prefix!'); 125 + pht('You can not set both a name and a name prefix!')); 136 126 } else if (!$this->name && !$this->namePrefix) { 137 127 throw new Exception( 138 - 'You must set a name or a name prefix!'); 128 + pht('You must set a name or a name prefix!')); 139 129 } 140 130 141 131 $symbol = new PhabricatorRepositorySymbol(); ··· 154 144 if ($symbols) { 155 145 if ($this->needPaths) { 156 146 $this->loadPaths($symbols); 157 - } 158 - if ($this->needArcanistProjects || $this->needRepositories) { 159 - $this->loadArcanistProjects($symbols); 160 147 } 161 148 if ($this->needRepositories) { 162 149 $this->loadRepositories($symbols); ··· 208 195 $this->namePrefix); 209 196 } 210 197 211 - if ($this->projectIDs) { 198 + if ($this->repositoryPHIDs) { 212 199 $where[] = qsprintf( 213 200 $conn_r, 214 - 'arcanistProjectID IN (%Ld)', 215 - $this->projectIDs); 201 + 'repositoryPHID IN (%Ls)', 202 + $this->repositoryPHIDs); 216 203 } 217 204 218 205 if ($this->language) { ··· 253 240 /** 254 241 * @task internal 255 242 */ 256 - private function loadArcanistProjects(array $symbols) { 257 - assert_instances_of($symbols, 'PhabricatorRepositorySymbol'); 258 - $projects = id(new PhabricatorRepositoryArcanistProject())->loadAllWhere( 259 - 'id IN (%Ld)', 260 - mpull($symbols, 'getArcanistProjectID')); 261 - foreach ($symbols as $symbol) { 262 - $project = idx($projects, $symbol->getArcanistProjectID()); 263 - $symbol->attachArcanistProject($project); 264 - } 265 - } 266 - 267 - 268 - /** 269 - * @task internal 270 - */ 271 243 private function loadRepositories(array $symbols) { 272 244 assert_instances_of($symbols, 'PhabricatorRepositorySymbol'); 273 245 274 - $projects = mpull($symbols, 'getArcanistProject'); 275 - $projects = array_filter($projects); 276 - 277 - $repo_ids = mpull($projects, 'getRepositoryID'); 278 - $repo_ids = array_filter($repo_ids); 279 - 280 - if ($repo_ids) { 281 - $repos = id(new PhabricatorRepositoryQuery()) 282 - ->setViewer($this->getViewer()) 283 - ->withIDs($repo_ids) 284 - ->execute(); 285 - } else { 286 - $repos = array(); 287 - } 246 + $repos = id(new PhabricatorRepositoryQuery()) 247 + ->setViewer($this->viewer) 248 + ->withPHIDs(mpull($symbols, 'getRepositoryPHID')) 249 + ->execute(); 250 + $repos = mpull($repos, null, 'getPHID'); 288 251 289 252 foreach ($symbols as $symbol) { 290 - $proj = $symbol->getArcanistProject(); 291 - if ($proj) { 292 - $symbol->attachRepository(idx($repos, $proj->getRepositoryID())); 293 - } else { 294 - $symbol->attachRepository(null); 295 - } 253 + $repository = idx($repos, $symbol->getRepositoryPHID()); 254 + $symbol->attachRepository($repository); 296 255 } 297 256 } 298 257
+2 -3
src/applications/diffusion/typeahead/DiffusionSymbolDatasource.php
··· 32 32 ->setViewer($viewer) 33 33 ->setNamePrefix($raw_query) 34 34 ->setLimit(15) 35 - ->needArcanistProjects(true) 36 35 ->needRepositories(true) 37 36 ->needPaths(true) 38 37 ->execute(); ··· 40 39 $lang = $symbol->getSymbolLanguage(); 41 40 $name = $symbol->getSymbolName(); 42 41 $type = $symbol->getSymbolType(); 43 - $proj = $symbol->getArcanistProject()->getName(); 42 + $repo = $symbol->getRepository()->getName(); 44 43 45 44 $results[] = id(new PhabricatorTypeaheadResult()) 46 45 ->setName($name) 47 46 ->setURI($symbol->getURI()) 48 47 ->setPHID(md5($symbol->getURI())) // Just needs to be unique. 49 48 ->setDisplayName($name) 50 - ->setDisplayType(strtoupper($lang).' '.ucwords($type).' ('.$proj.')') 49 + ->setDisplayType(strtoupper($lang).' '.ucwords($type).' ('.$repo.')') 51 50 ->setPriorityType('symb'); 52 51 } 53 52 }
+6 -1
src/applications/repository/storage/PhabricatorRepository.php
··· 1163 1163 $projects = id(new PhabricatorRepositoryArcanistProject()) 1164 1164 ->loadAllWhere('repositoryID = %d', $this->getID()); 1165 1165 foreach ($projects as $project) { 1166 - // note each project deletes its PhabricatorRepositorySymbols 1167 1166 $project->delete(); 1168 1167 } 1168 + 1169 + queryfx( 1170 + $this->establishConnection('w'), 1171 + 'DELETE FROM %T WHERE repositoryPHID = %s', 1172 + id(new PhabricatorRepositorySymbol())->getTableName(), 1173 + $this->getPHID()); 1169 1174 1170 1175 $commits = id(new PhabricatorRepositoryCommit()) 1171 1176 ->loadAllWhere('repositoryID = %d', $this->getID());
-14
src/applications/repository/storage/PhabricatorRepositoryArcanistProject.php
··· 45 45 PhabricatorRepositoryArcanistProjectPHIDType::TYPECONST); 46 46 } 47 47 48 - public function delete() { 49 - $this->openTransaction(); 50 - 51 - queryfx( 52 - $this->establishConnection('w'), 53 - 'DELETE FROM %T WHERE arcanistProjectID = %d', 54 - id(new PhabricatorRepositorySymbol())->getTableName(), 55 - $this->getID()); 56 - 57 - $result = parent::delete(); 58 - $this->saveTransaction(); 59 - return $result; 60 - } 61 - 62 48 public function getRepository() { 63 49 return $this->assertAttached($this->repository); 64 50 }
+2 -20
src/applications/repository/storage/PhabricatorRepositorySymbol.php
··· 8 8 */ 9 9 final class PhabricatorRepositorySymbol extends PhabricatorRepositoryDAO { 10 10 11 - protected $arcanistProjectID; 11 + protected $repositoryPHID; 12 12 protected $symbolContext; 13 13 protected $symbolName; 14 14 protected $symbolType; ··· 17 17 protected $lineNumber; 18 18 19 19 private $path = self::ATTACHABLE; 20 - private $arcanistProject = self::ATTACHABLE; 21 20 private $repository = self::ATTACHABLE; 22 21 23 22 protected function getConfiguration() { 24 23 return array( 25 - self::CONFIG_IDS => self::IDS_MANUAL, 26 24 self::CONFIG_TIMESTAMPS => false, 27 25 self::CONFIG_COLUMN_SCHEMA => array( 28 26 'id' => null, ··· 42 40 } 43 41 44 42 public function getURI() { 45 - if (!$this->repository) { 46 - // This symbol is in the index, but we don't know which Repository it's 47 - // part of. Usually this means the Arcanist Project hasn't been linked 48 - // to a Repository. We can't generate a URI, so just fail. 49 - return null; 50 - } 51 - 52 43 $request = DiffusionRequest::newFromDictionary( 53 44 array( 54 45 'user' => PhabricatorUser::getOmnipotentUser(), ··· 75 66 return $this->assertAttached($this->repository); 76 67 } 77 68 78 - public function attachRepository($repository) { 69 + public function attachRepository(PhabricatorRepository $repository) { 79 70 $this->repository = $repository; 80 - return $this; 81 - } 82 - 83 - public function getArcanistProject() { 84 - return $this->assertAttached($this->arcanistProject); 85 - } 86 - 87 - public function attachArcanistProject($project) { 88 - $this->arcanistProject = $project; 89 71 return $this; 90 72 } 91 73