@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.

Diffusion - move DiffusionBrowseQuery => Conduit

Summary: see title. Ref T2784.

Test Plan:
In diffusion, for each of SVN, Mercurial, and Git, I loaded up /diffusion/CALLSIGN/. I verified the README was displayed and things looked good. Next I clicked on "browse" on the top-most commit and verified things looked correct. Also clicked through to a file for a good measure and things looked good.
In owners, for each of SVN, Mercurial, and Git, I played around with the path typeahead / validator. It worked correctly!

Reviewers: epriestley

Reviewed By: epriestley

CC: chad, aran, Korvin

Maniphest Tasks: T2784

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

+812 -649
+3 -7
src/__phutil_library_map__.php
··· 146 146 'ConduitAPI_diffusion_Method' => 'applications/diffusion/conduit/ConduitAPI_diffusion_Method.php', 147 147 'ConduitAPI_diffusion_abstractquery_Method' => 'applications/diffusion/conduit/ConduitAPI_diffusion_abstractquery_Method.php', 148 148 'ConduitAPI_diffusion_branchquery_Method' => 'applications/diffusion/conduit/ConduitAPI_diffusion_branchquery_Method.php', 149 + 'ConduitAPI_diffusion_browsequery_Method' => 'applications/diffusion/conduit/ConduitAPI_diffusion_browsequery_Method.php', 149 150 'ConduitAPI_diffusion_existsquery_Method' => 'applications/diffusion/conduit/ConduitAPI_diffusion_existsquery_Method.php', 150 151 'ConduitAPI_diffusion_filecontentquery_Method' => 'applications/diffusion/conduit/ConduitAPI_diffusion_filecontentquery_Method.php', 151 152 'ConduitAPI_diffusion_findsymbols_Method' => 'applications/diffusion/conduit/ConduitAPI_diffusion_findsymbols_Method.php', ··· 404 405 'DiffusionBranchTableView' => 'applications/diffusion/view/DiffusionBranchTableView.php', 405 406 'DiffusionBrowseController' => 'applications/diffusion/controller/DiffusionBrowseController.php', 406 407 'DiffusionBrowseFileController' => 'applications/diffusion/controller/DiffusionBrowseFileController.php', 407 - 'DiffusionBrowseQuery' => 'applications/diffusion/query/browse/DiffusionBrowseQuery.php', 408 + 'DiffusionBrowseResultSet' => 'applications/diffusion/data/DiffusionBrowseResultSet.php', 408 409 'DiffusionBrowseTableView' => 'applications/diffusion/view/DiffusionBrowseTableView.php', 409 410 'DiffusionChangeController' => 'applications/diffusion/controller/DiffusionChangeController.php', 410 411 'DiffusionCommentListView' => 'applications/diffusion/view/DiffusionCommentListView.php', ··· 427 428 'DiffusionFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionFileContentQuery.php', 428 429 'DiffusionGitBranch' => 'applications/diffusion/data/DiffusionGitBranch.php', 429 430 'DiffusionGitBranchTestCase' => 'applications/diffusion/data/__tests__/DiffusionGitBranchTestCase.php', 430 - 'DiffusionGitBrowseQuery' => 'applications/diffusion/query/browse/DiffusionGitBrowseQuery.php', 431 431 'DiffusionGitCommitParentsQuery' => 'applications/diffusion/query/parents/DiffusionGitCommitParentsQuery.php', 432 432 'DiffusionGitCommitTagsQuery' => 'applications/diffusion/query/committags/DiffusionGitCommitTagsQuery.php', 433 433 'DiffusionGitContainsQuery' => 'applications/diffusion/query/contains/DiffusionGitContainsQuery.php', ··· 451 451 'DiffusionLintController' => 'applications/diffusion/controller/DiffusionLintController.php', 452 452 'DiffusionLintDetailsController' => 'applications/diffusion/controller/DiffusionLintDetailsController.php', 453 453 'DiffusionLintSaveRunner' => 'applications/diffusion/DiffusionLintSaveRunner.php', 454 - 'DiffusionMercurialBrowseQuery' => 'applications/diffusion/query/browse/DiffusionMercurialBrowseQuery.php', 455 454 'DiffusionMercurialCommitParentsQuery' => 'applications/diffusion/query/parents/DiffusionMercurialCommitParentsQuery.php', 456 455 'DiffusionMercurialCommitTagsQuery' => 'applications/diffusion/query/committags/DiffusionMercurialCommitTagsQuery.php', 457 456 'DiffusionMercurialContainsQuery' => 'applications/diffusion/query/contains/DiffusionMercurialContainsQuery.php', ··· 481 480 'DiffusionRepositoryTag' => 'applications/diffusion/data/DiffusionRepositoryTag.php', 482 481 'DiffusionRequest' => 'applications/diffusion/request/DiffusionRequest.php', 483 482 'DiffusionSetupException' => 'applications/diffusion/exception/DiffusionSetupException.php', 484 - 'DiffusionSvnBrowseQuery' => 'applications/diffusion/query/browse/DiffusionSvnBrowseQuery.php', 485 483 'DiffusionSvnCommitParentsQuery' => 'applications/diffusion/query/parents/DiffusionSvnCommitParentsQuery.php', 486 484 'DiffusionSvnCommitTagsQuery' => 'applications/diffusion/query/committags/DiffusionSvnCommitTagsQuery.php', 487 485 'DiffusionSvnContainsQuery' => 'applications/diffusion/query/contains/DiffusionSvnContainsQuery.php', ··· 1921 1919 'ConduitAPI_diffusion_Method' => 'ConduitAPIMethod', 1922 1920 'ConduitAPI_diffusion_abstractquery_Method' => 'ConduitAPI_diffusion_Method', 1923 1921 'ConduitAPI_diffusion_branchquery_Method' => 'ConduitAPI_diffusion_abstractquery_Method', 1922 + 'ConduitAPI_diffusion_browsequery_Method' => 'ConduitAPI_diffusion_abstractquery_Method', 1924 1923 'ConduitAPI_diffusion_existsquery_Method' => 'ConduitAPI_diffusion_abstractquery_Method', 1925 1924 'ConduitAPI_diffusion_filecontentquery_Method' => 'ConduitAPI_diffusion_abstractquery_Method', 1926 1925 'ConduitAPI_diffusion_findsymbols_Method' => 'ConduitAPI_diffusion_Method', ··· 2190 2189 'DiffusionExternalController' => 'DiffusionController', 2191 2190 'DiffusionFileContentQuery' => 'DiffusionQuery', 2192 2191 'DiffusionGitBranchTestCase' => 'PhabricatorTestCase', 2193 - 'DiffusionGitBrowseQuery' => 'DiffusionBrowseQuery', 2194 2192 'DiffusionGitCommitParentsQuery' => 'DiffusionCommitParentsQuery', 2195 2193 'DiffusionGitCommitTagsQuery' => 'DiffusionCommitTagsQuery', 2196 2194 'DiffusionGitContainsQuery' => 'DiffusionContainsQuery', ··· 2213 2211 'DiffusionLastModifiedQuery' => 'DiffusionQuery', 2214 2212 'DiffusionLintController' => 'DiffusionController', 2215 2213 'DiffusionLintDetailsController' => 'DiffusionController', 2216 - 'DiffusionMercurialBrowseQuery' => 'DiffusionBrowseQuery', 2217 2214 'DiffusionMercurialCommitParentsQuery' => 'DiffusionCommitParentsQuery', 2218 2215 'DiffusionMercurialCommitTagsQuery' => 'DiffusionCommitTagsQuery', 2219 2216 'DiffusionMercurialContainsQuery' => 'DiffusionContainsQuery', ··· 2235 2232 'DiffusionRemarkupRule' => 'PhabricatorRemarkupRuleObject', 2236 2233 'DiffusionRepositoryController' => 'DiffusionController', 2237 2234 'DiffusionSetupException' => 'AphrontUsageException', 2238 - 'DiffusionSvnBrowseQuery' => 'DiffusionBrowseQuery', 2239 2235 'DiffusionSvnCommitParentsQuery' => 'DiffusionCommitParentsQuery', 2240 2236 'DiffusionSvnCommitTagsQuery' => 'DiffusionCommitTagsQuery', 2241 2237 'DiffusionSvnContainsQuery' => 'DiffusionContainsQuery',
+547
src/applications/diffusion/conduit/ConduitAPI_diffusion_browsequery_Method.php
··· 1 + <?php 2 + 3 + /** 4 + * @group conduit 5 + */ 6 + final class ConduitAPI_diffusion_browsequery_Method 7 + extends ConduitAPI_diffusion_abstractquery_Method { 8 + 9 + public function getMethodDescription() { 10 + return 11 + 'File(s) information for a repository at an (optional) path and '. 12 + '(optional) commit.'; 13 + } 14 + 15 + public function defineReturnType() { 16 + return 'array'; 17 + } 18 + 19 + protected function defineCustomParamTypes() { 20 + return array( 21 + 'path' => 'optional string', 22 + 'commit' => 'optional string', 23 + 'needValidityOnly' => 'optional bool', 24 + 'renderReadme' => 'optional bool', 25 + ); 26 + } 27 + 28 + protected function getResult(ConduitAPIRequest $request) { 29 + $result = parent::getResult($request); 30 + if ($request->getValue('renderReadme', false)) { 31 + $readme = $this->renderReadme($request, $result); 32 + } 33 + return $result->toDictionary(); 34 + } 35 + 36 + final private function renderReadme( 37 + ConduitAPIRequest $request, 38 + DiffusionBrowseResultSet $result) { 39 + $drequest = $this->getDiffusionRequest(); 40 + 41 + $readme = null; 42 + foreach ($result->getPaths() as $result_path) { 43 + $file_type = $result_path->getFileType(); 44 + if (($file_type != ArcanistDiffChangeType::FILE_NORMAL) && 45 + ($file_type != ArcanistDiffChangeType::FILE_TEXT)) { 46 + // Skip directories, etc. 47 + continue; 48 + } 49 + 50 + $path = $result_path->getPath(); 51 + 52 + if (preg_match('/^readme(|\.txt|\.remarkup|\.rainbow)$/i', $path)) { 53 + $readme = $result_path; 54 + break; 55 + } 56 + } 57 + 58 + if (!$readme) { 59 + return null; 60 + } 61 + 62 + $readme_request = DiffusionRequest::newFromDictionary( 63 + array( 64 + 'repository' => $drequest->getRepository(), 65 + 'commit' => $drequest->getStableCommitName(), 66 + 'path' => $readme->getFullPath(), 67 + )); 68 + 69 + $file_content = DiffusionFileContent::newFromConduit( 70 + DiffusionQuery::callConduitWithDiffusionRequest( 71 + $request->getUser(), 72 + $readme_request, 73 + 'diffusion.filecontentquery', 74 + array( 75 + 'commit' => $drequest->getStableCommitName(), 76 + 'path' => $readme->getFullPath(), 77 + 'needsBlame' => false, 78 + ))); 79 + $readme_content = $file_content->getCorpus(); 80 + 81 + if (preg_match('/\\.txt$/', $readme->getPath())) { 82 + $readme_content = phutil_escape_html_newlines($readme_content); 83 + 84 + $class = null; 85 + } else if (preg_match('/\\.rainbow$/', $readme->getPath())) { 86 + $highlighter = new PhutilRainbowSyntaxHighlighter(); 87 + $readme_content = $highlighter 88 + ->getHighlightFuture($readme_content) 89 + ->resolve(); 90 + $readme_content = phutil_escape_html_newlines($readme_content); 91 + 92 + require_celerity_resource('syntax-highlighting-css'); 93 + $class = 'remarkup-code'; 94 + } else { 95 + // Markup extensionless files as remarkup so we get links and such. 96 + $engine = PhabricatorMarkupEngine::newDiffusionMarkupEngine(); 97 + $engine->setConfig('viewer', $request->getUser()); 98 + $readme_content = $engine->markupText($readme_content); 99 + 100 + $class = 'phabricator-remarkup'; 101 + } 102 + 103 + $readme_content = phutil_tag( 104 + 'div', 105 + array( 106 + 'class' => $class, 107 + ), 108 + $readme_content); 109 + 110 + $result->setReadmeContent($readme_content); 111 + return $result; 112 + } 113 + 114 + protected function getGitResult(ConduitAPIRequest $request) { 115 + $drequest = $this->getDiffusionRequest(); 116 + $repository = $drequest->getRepository(); 117 + $path = $request->getValue('path'); 118 + $commit = $request->getValue('commit'); 119 + $result = $this->getEmptyResultSet(); 120 + 121 + if ($path == '') { 122 + // Fast path to improve the performance of the repository view; we know 123 + // the root is always a tree at any commit and always exists. 124 + $stdout = 'tree'; 125 + } else { 126 + try { 127 + list($stdout) = $repository->execxLocalCommand( 128 + 'cat-file -t %s:%s', 129 + $commit, 130 + $path); 131 + } catch (CommandException $e) { 132 + $stderr = $e->getStdErr(); 133 + if (preg_match('/^fatal: Not a valid object name/', $stderr)) { 134 + // Grab two logs, since the first one is when the object was deleted. 135 + list($stdout) = $repository->execxLocalCommand( 136 + 'log -n2 --format="%%H" %s -- %s', 137 + $commit, 138 + $path); 139 + $stdout = trim($stdout); 140 + if ($stdout) { 141 + $commits = explode("\n", $stdout); 142 + $result 143 + ->setReasonForEmptyResultSet( 144 + DiffusionBrowseResultSet::REASON_IS_DELETED) 145 + ->setDeletedAtCommit(idx($commits, 0)) 146 + ->setExistedAtCommit(idx($commits, 1)); 147 + return $result; 148 + } 149 + 150 + $result->setReasonForEmptyResultSet( 151 + DiffusionBrowseResultSet::REASON_IS_NONEXISTENT); 152 + return $result; 153 + } else { 154 + throw $e; 155 + } 156 + } 157 + } 158 + 159 + if (trim($stdout) == 'blob') { 160 + $result->setReasonForEmptyResultSet( 161 + DiffusionBrowseResultSet::REASON_IS_FILE); 162 + return $result; 163 + } 164 + 165 + $result->setIsValidResults(true); 166 + if ($this->shouldOnlyTestValidity($request)) { 167 + return $result; 168 + } 169 + 170 + list($stdout) = $repository->execxLocalCommand( 171 + 'ls-tree -z -l %s:%s', 172 + $commit, 173 + $path); 174 + 175 + $submodules = array(); 176 + 177 + if (strlen($path)) { 178 + $prefix = rtrim($path, '/').'/'; 179 + } else { 180 + $prefix = ''; 181 + } 182 + 183 + $results = array(); 184 + foreach (explode("\0", rtrim($stdout)) as $line) { 185 + 186 + // NOTE: Limit to 5 components so we parse filenames with spaces in them 187 + // correctly. 188 + list($mode, $type, $hash, $size, $name) = preg_split('/\s+/', $line, 5); 189 + 190 + $path_result = new DiffusionRepositoryPath(); 191 + 192 + if ($type == 'tree') { 193 + $file_type = DifferentialChangeType::FILE_DIRECTORY; 194 + } else if ($type == 'commit') { 195 + $file_type = DifferentialChangeType::FILE_SUBMODULE; 196 + $submodules[] = $path_result; 197 + } else { 198 + $mode = intval($mode, 8); 199 + if (($mode & 0120000) == 0120000) { 200 + $file_type = DifferentialChangeType::FILE_SYMLINK; 201 + } else { 202 + $file_type = DifferentialChangeType::FILE_NORMAL; 203 + } 204 + } 205 + 206 + $path_result->setFullPath($prefix.$name); 207 + $path_result->setPath($name); 208 + $path_result->setHash($hash); 209 + $path_result->setFileType($file_type); 210 + $path_result->setFileSize($size); 211 + 212 + $results[] = $path_result; 213 + } 214 + 215 + // If we identified submodules, lookup the module info at this commit to 216 + // find their source URIs. 217 + 218 + if ($submodules) { 219 + 220 + // NOTE: We need to read the file out of git and write it to a temporary 221 + // location because "git config -f" doesn't accept a "commit:path"-style 222 + // argument. 223 + 224 + // NOTE: This file may not exist, e.g. because the commit author removed 225 + // it when they added the submodule. See T1448. If it's not present, just 226 + // show the submodule without enriching it. If ".gitmodules" was removed 227 + // it seems to partially break submodules, but the repository as a whole 228 + // continues to work fine and we've seen at least two cases of this in 229 + // the wild. 230 + 231 + list($err, $contents) = $repository->execLocalCommand( 232 + 'cat-file blob %s:.gitmodules', 233 + $commit); 234 + 235 + if (!$err) { 236 + $tmp = new TempFile(); 237 + Filesystem::writeFile($tmp, $contents); 238 + list($module_info) = $repository->execxLocalCommand( 239 + 'config -l -f %s', 240 + $tmp); 241 + 242 + $dict = array(); 243 + $lines = explode("\n", trim($module_info)); 244 + foreach ($lines as $line) { 245 + list($key, $value) = explode('=', $line, 2); 246 + $parts = explode('.', $key); 247 + $dict[$key] = $value; 248 + } 249 + 250 + foreach ($submodules as $path) { 251 + $full_path = $path->getFullPath(); 252 + $key = 'submodule.'.$full_path.'.url'; 253 + if (isset($dict[$key])) { 254 + $path->setExternalURI($dict[$key]); 255 + } 256 + } 257 + } 258 + } 259 + 260 + return $result->setPaths($results); 261 + } 262 + 263 + protected function getMercurialResult(ConduitAPIRequest $request) { 264 + $drequest = $this->getDiffusionRequest(); 265 + $repository = $drequest->getRepository(); 266 + $path = $request->getValue('path'); 267 + $commit = $request->getValue('commit'); 268 + $result = $this->getEmptyResultSet(); 269 + 270 + // TODO: This is a really really awful mess but Mercurial doesn't offer 271 + // an equivalent of "git ls-files -- directory". If it's any comfort, this 272 + // is what "hgweb" does too, see: 273 + // 274 + // http://selenic.com/repo/hg/file/91dc8878f888/mercurial/hgweb/webcommands.py#l320 275 + // 276 + // derp derp derp derp 277 + // 278 + // Anyway, figure out what's in this path by applying massive amounts 279 + // of brute force. 280 + 281 + list($entire_manifest) = $repository->execxLocalCommand( 282 + 'manifest --rev %s', 283 + $commit); 284 + $entire_manifest = explode("\n", $entire_manifest); 285 + 286 + $results = array(); 287 + 288 + $match_against = trim($path, '/'); 289 + $match_len = strlen($match_against); 290 + 291 + // For the root, don't trim. For other paths, trim the "/" after we match. 292 + // We need this because Mercurial's canonical paths have no leading "/", 293 + // but ours do. 294 + $trim_len = $match_len ? $match_len + 1 : 0; 295 + 296 + foreach ($entire_manifest as $path) { 297 + if (strncmp($path, $match_against, $match_len)) { 298 + continue; 299 + } 300 + if (!strlen($path)) { 301 + continue; 302 + } 303 + $remainder = substr($path, $trim_len); 304 + if (!strlen($remainder)) { 305 + // There is a file with this exact name in the manifest, so clearly 306 + // it's a file. 307 + $result->setReasonForEmptyResultSet( 308 + DiffusionBrowseResultSet::REASON_IS_FILE); 309 + return $result; 310 + } 311 + $parts = explode('/', $remainder); 312 + if (count($parts) == 1) { 313 + $type = DifferentialChangeType::FILE_NORMAL; 314 + } else { 315 + $type = DifferentialChangeType::FILE_DIRECTORY; 316 + } 317 + $results[reset($parts)] = $type; 318 + } 319 + 320 + foreach ($results as $key => $type) { 321 + $path_result = new DiffusionRepositoryPath(); 322 + $path_result->setPath($key); 323 + $path_result->setFileType($type); 324 + $path_result->setFullPath(ltrim($match_against.'/', '/').$key); 325 + 326 + $results[$key] = $path_result; 327 + } 328 + 329 + $valid_results = true; 330 + if (empty($results)) { 331 + // TODO: Detect "deleted" by issuing "hg log"? 332 + $result->setReasonForEmptyResultSet( 333 + DiffusionBrowseResultSet::REASON_IS_NONEXISTENT); 334 + $valid_results = false; 335 + } 336 + 337 + return $result 338 + ->setPaths($results) 339 + ->setIsValidResults($valid_results); 340 + } 341 + 342 + protected function getSVNResult(ConduitAPIRequest $request) { 343 + $drequest = $this->getDiffusionRequest(); 344 + $repository = $drequest->getRepository(); 345 + $path = $request->getValue('path'); 346 + $commit = $request->getValue('commit'); 347 + $result = $this->getEmptyResultSet(); 348 + 349 + $subpath = $repository->getDetail('svn-subpath'); 350 + if ($subpath && strncmp($subpath, $path, strlen($subpath))) { 351 + // If we have a subpath and the path isn't a child of it, it (almost 352 + // certainly) won't exist since we don't track commits which affect 353 + // it. (Even if it exists, return a consistent result.) 354 + $result->setReasonForEmptyResultSet( 355 + DiffusionBrowseResultSet::REASON_IS_UNTRACKED_PARENT); 356 + return $result; 357 + } 358 + 359 + $conn_r = $repository->establishConnection('r'); 360 + 361 + $parent_path = DiffusionPathIDQuery::getParentPath($path); 362 + $path_query = new DiffusionPathIDQuery( 363 + array( 364 + $path, 365 + $parent_path, 366 + )); 367 + $path_map = $path_query->loadPathIDs(); 368 + 369 + $path_id = $path_map[$path]; 370 + $parent_path_id = $path_map[$parent_path]; 371 + 372 + if (empty($path_id)) { 373 + $result->setReasonForEmptyResultSet( 374 + DiffusionBrowseResultSet::REASON_IS_NONEXISTENT); 375 + return $result; 376 + } 377 + 378 + if ($commit) { 379 + $slice_clause = 'AND svnCommit <= '.(int)$commit; 380 + } else { 381 + $slice_clause = ''; 382 + } 383 + 384 + $index = queryfx_all( 385 + $conn_r, 386 + 'SELECT pathID, max(svnCommit) maxCommit FROM %T WHERE 387 + repositoryID = %d AND parentID = %d 388 + %Q GROUP BY pathID', 389 + PhabricatorRepository::TABLE_FILESYSTEM, 390 + $repository->getID(), 391 + $path_id, 392 + $slice_clause); 393 + 394 + if (!$index) { 395 + if ($path == '/') { 396 + $result->setReasonForEmptyResultSet( 397 + DiffusionBrowseResultSet::REASON_IS_EMPTY); 398 + } else { 399 + 400 + // NOTE: The parent path ID is included so this query can take 401 + // advantage of the table's primary key; it is uniquely determined by 402 + // the pathID but if we don't do the lookup ourselves MySQL doesn't have 403 + // the information it needs to avoid a table scan. 404 + 405 + $reasons = queryfx_all( 406 + $conn_r, 407 + 'SELECT * FROM %T WHERE repositoryID = %d 408 + AND parentID = %d 409 + AND pathID = %d 410 + %Q ORDER BY svnCommit DESC LIMIT 2', 411 + PhabricatorRepository::TABLE_FILESYSTEM, 412 + $repository->getID(), 413 + $parent_path_id, 414 + $path_id, 415 + $slice_clause); 416 + 417 + $reason = reset($reasons); 418 + 419 + if (!$reason) { 420 + $result->setReasonForEmptyResultSet( 421 + DiffusionBrowseResultSet::REASON_IS_NONEXISTENT); 422 + } else { 423 + $file_type = $reason['fileType']; 424 + if (empty($reason['existed'])) { 425 + $result->setReasonForEmptyResultSet( 426 + DiffusionBrowseResultSet::REASON_IS_DELETED); 427 + $result->setDeletedAtCommit($reason['svnCommit']); 428 + if (!empty($reasons[1])) { 429 + $result->setExistedAtCommit($reasons[1]['svnCommit']); 430 + } 431 + } else if ($file_type == DifferentialChangeType::FILE_DIRECTORY) { 432 + $result->setReasonForEmptyResultSet( 433 + DiffusionBrowseResultSet::REASON_IS_EMPTY); 434 + } else { 435 + $result->setReasonForEmptyResultSet( 436 + DiffusionBrowseResultSet::REASON_IS_FILE); 437 + } 438 + } 439 + } 440 + return $result; 441 + } 442 + 443 + $result->setIsValidResults(true); 444 + if ($this->shouldOnlyTestValidity($request)) { 445 + return $result; 446 + } 447 + 448 + $sql = array(); 449 + foreach ($index as $row) { 450 + $sql[] = 451 + '(pathID = '.(int)$row['pathID'].' AND '. 452 + 'svnCommit = '.(int)$row['maxCommit'].')'; 453 + } 454 + 455 + $browse = queryfx_all( 456 + $conn_r, 457 + 'SELECT *, p.path pathName 458 + FROM %T f JOIN %T p ON f.pathID = p.id 459 + WHERE repositoryID = %d 460 + AND parentID = %d 461 + AND existed = 1 462 + AND (%Q) 463 + ORDER BY pathName', 464 + PhabricatorRepository::TABLE_FILESYSTEM, 465 + PhabricatorRepository::TABLE_PATH, 466 + $repository->getID(), 467 + $path_id, 468 + implode(' OR ', $sql)); 469 + 470 + $loadable_commits = array(); 471 + foreach ($browse as $key => $file) { 472 + // We need to strip out directories because we don't store last-modified 473 + // in the filesystem table. 474 + if ($file['fileType'] != DifferentialChangeType::FILE_DIRECTORY) { 475 + $loadable_commits[] = $file['svnCommit']; 476 + $browse[$key]['hasCommit'] = true; 477 + } 478 + } 479 + 480 + $commits = array(); 481 + $commit_data = array(); 482 + if ($loadable_commits) { 483 + // NOTE: Even though these are integers, use '%Ls' because MySQL doesn't 484 + // use the second part of the key otherwise! 485 + $commits = id(new PhabricatorRepositoryCommit())->loadAllWhere( 486 + 'repositoryID = %d AND commitIdentifier IN (%Ls)', 487 + $repository->getID(), 488 + $loadable_commits); 489 + $commits = mpull($commits, null, 'getCommitIdentifier'); 490 + if ($commits) { 491 + $commit_data = id(new PhabricatorRepositoryCommitData())->loadAllWhere( 492 + 'commitID in (%Ld)', 493 + mpull($commits, 'getID')); 494 + $commit_data = mpull($commit_data, null, 'getCommitID'); 495 + } else { 496 + $commit_data = array(); 497 + } 498 + } 499 + 500 + $path_normal = DiffusionPathIDQuery::normalizePath($path); 501 + 502 + $results = array(); 503 + foreach ($browse as $file) { 504 + 505 + $full_path = $file['pathName']; 506 + $file_path = ltrim(substr($full_path, strlen($path_normal)), '/'); 507 + $full_path = ltrim($full_path, '/'); 508 + 509 + $result_path = new DiffusionRepositoryPath(); 510 + $result_path->setPath($file_path); 511 + $result_path->setFullPath($full_path); 512 + // $result_path->setHash($hash); 513 + $result_path->setFileType($file['fileType']); 514 + // $result_path->setFileSize($size); 515 + 516 + if (!empty($file['hasCommit'])) { 517 + $commit = idx($commits, $file['svnCommit']); 518 + if ($commit) { 519 + $data = idx($commit_data, $commit->getID()); 520 + $result_path->setLastModifiedCommit($commit); 521 + $result_path->setLastCommitData($data); 522 + } 523 + } 524 + 525 + $results[] = $result_path; 526 + } 527 + 528 + if (empty($results)) { 529 + $result->setReasonForEmptyResultSet( 530 + DiffusionBrowseResultSet::REASON_IS_EMPTY); 531 + } 532 + 533 + return $result->setPaths($results); 534 + } 535 + 536 + private function getEmptyResultSet() { 537 + return id(new DiffusionBrowseResultSet()) 538 + ->setPaths(array()) 539 + ->setReasonForEmptyResultSet(null) 540 + ->setIsValidResults(false); 541 + } 542 + 543 + private function shouldOnlyTestValidity(ConduitAPIRequest $request) { 544 + return $request->getValue('needValidityOnly', false); 545 + } 546 + 547 + }
+15 -11
src/applications/diffusion/controller/DiffusionBrowseController.php
··· 9 9 if ($this->getRequest()->getStr('before')) { 10 10 $is_file = true; 11 11 } else if ($this->getRequest()->getStr('grep') == '') { 12 - $browse_query = DiffusionBrowseQuery::newFromDiffusionRequest($drequest); 13 - $browse_query->setViewer($this->getRequest()->getUser()); 14 - $results = $browse_query->loadPaths(); 15 - $reason = $browse_query->getReasonForEmptyResultSet(); 16 - $is_file = ($reason == DiffusionBrowseQuery::REASON_IS_FILE); 12 + $results = DiffusionBrowseResultSet::newFromConduit( 13 + $this->callConduitWithDiffusionRequest( 14 + 'diffusion.browsequery', 15 + array( 16 + 'path' => $drequest->getPath(), 17 + 'commit' => $drequest->getCommit(), 18 + 'renderReadme' => true, 19 + ))); 20 + $reason = $results->getReasonForEmptyResultSet(); 21 + $is_file = ($reason == DiffusionBrowseResultSet::REASON_IS_FILE); 17 22 } 18 23 19 24 if ($is_file) { ··· 42 47 $content[] = $this->renderSearchResults(); 43 48 44 49 } else { 45 - if (!$results) { 50 + if (!$results->isValidResults()) { 46 51 $empty_result = new DiffusionEmptyResultView(); 47 52 $empty_result->setDiffusionRequest($drequest); 48 - $empty_result->setBrowseQuery($browse_query); 53 + $empty_result->setDiffusionBrowseResultSet($results); 49 54 $empty_result->setView($this->getRequest()->getStr('view')); 50 55 $content[] = $empty_result; 51 56 52 57 } else { 53 58 54 59 $phids = array(); 55 - foreach ($results as $result) { 60 + foreach ($results->getPaths() as $result) { 56 61 $data = $result->getLastCommitData(); 57 62 if ($data) { 58 63 if ($data->getCommitDetail('authorPHID')) { ··· 67 72 $browse_table = new DiffusionBrowseTableView(); 68 73 $browse_table->setDiffusionRequest($drequest); 69 74 $browse_table->setHandles($handles); 70 - $browse_table->setPaths($results); 75 + $browse_table->setPaths($results->getPaths()); 71 76 $browse_table->setUser($this->getRequest()->getUser()); 72 77 73 78 $browse_panel = new AphrontPanelView(); ··· 79 84 80 85 $content[] = $this->buildOpenRevisions(); 81 86 82 - $readme_content = $browse_query->renderReadme($results); 87 + $readme_content = $results->getReadmeContent(); 83 88 if ($readme_content) { 84 89 $readme_panel = new AphrontPanelView(); 85 90 $readme_panel->setHeader('README'); ··· 99 104 'view' => 'browse', 100 105 )); 101 106 $nav->setCrumbs($crumbs); 102 - 103 107 return $this->buildApplicationPage( 104 108 $nav, 105 109 array(
+9 -3
src/applications/diffusion/controller/DiffusionPathCompleteController.php
··· 30 30 'repository' => $repository, 31 31 'path' => $query_dir, 32 32 )); 33 + $this->setDiffusionRequest($drequest); 33 34 34 - $browse_query = DiffusionBrowseQuery::newFromDiffusionRequest($drequest); 35 - $browse_query->setViewer($request->getUser()); 36 - $paths = $browse_query->loadPaths(); 35 + $browse_results = DiffusionBrowseResultSet::newFromConduit( 36 + $this->callConduitWithDiffusionRequest( 37 + 'diffusion.browsequery', 38 + array( 39 + 'path' => $drequest->getPath(), 40 + 'commit' => $drequest->getCommit(), 41 + ))); 42 + $paths = $browse_results->getPaths(); 37 43 38 44 $output = array(); 39 45 foreach ($paths as $path) {
+13 -7
src/applications/diffusion/controller/DiffusionPathValidateController.php
··· 25 25 'repository' => $repository, 26 26 'path' => $path, 27 27 )); 28 + $this->setDiffusionRequest($drequest); 28 29 29 - $browse_query = DiffusionBrowseQuery::newFromDiffusionRequest($drequest); 30 - $browse_query->setViewer($request->getUser()); 31 - $browse_query->needValidityOnly(true); 32 - $valid = $browse_query->loadPaths(); 30 + $browse_results = DiffusionBrowseResultSet::newFromConduit( 31 + $this->callConduitWithDiffusionRequest( 32 + 'diffusion.browsequery', 33 + array( 34 + 'path' => $drequest->getPath(), 35 + 'commit' => $drequest->getCommit(), 36 + 'needValidityOnly' => true, 37 + ))); 38 + $valid = $browse_results->isValidResults(); 33 39 34 40 if (!$valid) { 35 - switch ($browse_query->getReasonForEmptyResultSet()) { 36 - case DiffusionBrowseQuery::REASON_IS_FILE: 41 + switch ($browse_results->getReasonForEmptyResultSet()) { 42 + case DiffusionBrowseResultSet::REASON_IS_FILE: 37 43 $valid = true; 38 44 break; 39 - case DiffusionBrowseQuery::REASON_IS_EMPTY: 45 + case DiffusionBrowseResultSet::REASON_IS_EMPTY: 40 46 $valid = true; 41 47 break; 42 48 }
+12 -6
src/applications/diffusion/controller/DiffusionRepositoryController.php
··· 18 18 $history_query->needParents(true); 19 19 $history = $history_query->loadHistory(); 20 20 21 - $browse_query = DiffusionBrowseQuery::newFromDiffusionRequest($drequest); 22 - $browse_query->setViewer($this->getRequest()->getUser()); 23 - $browse_results = $browse_query->loadPaths(); 21 + $browse_results = DiffusionBrowseResultSet::newFromConduit( 22 + $this->callConduitWithDiffusionRequest( 23 + 'diffusion.browsequery', 24 + array( 25 + 'path' => $drequest->getPath(), 26 + 'commit' => $drequest->getCommit(), 27 + 'renderReadme' => true, 28 + ))); 29 + $browse_paths = $browse_results->getPaths(); 24 30 25 31 $phids = array(); 26 32 ··· 36 42 } 37 43 } 38 44 39 - foreach ($browse_results as $item) { 45 + foreach ($browse_paths as $item) { 40 46 $data = $item->getLastCommitData(); 41 47 if ($data) { 42 48 if ($data->getCommitDetail('authorPHID')) { ··· 82 88 $browse_table = new DiffusionBrowseTableView(); 83 89 $browse_table->setDiffusionRequest($drequest); 84 90 $browse_table->setHandles($handles); 85 - $browse_table->setPaths($browse_results); 91 + $browse_table->setPaths($browse_paths); 86 92 $browse_table->setUser($this->getRequest()->getUser()); 87 93 88 94 $browse_panel = new AphrontPanelView(); ··· 99 105 100 106 $content[] = $this->buildBranchListTable($drequest); 101 107 102 - $readme = $browse_query->renderReadme($browse_results); 108 + $readme = $browse_results->getReadmeContent(); 103 109 if ($readme) { 104 110 $panel = new AphrontPanelView(); 105 111 $panel->setHeader('README');
+97
src/applications/diffusion/data/DiffusionBrowseResultSet.php
··· 1 + <?php 2 + 3 + final class DiffusionBrowseResultSet { 4 + 5 + const REASON_IS_FILE = 'is-file'; 6 + const REASON_IS_DELETED = 'is-deleted'; 7 + const REASON_IS_NONEXISTENT = 'nonexistent'; 8 + const REASON_BAD_COMMIT = 'bad-commit'; 9 + const REASON_IS_EMPTY = 'empty'; 10 + const REASON_IS_UNTRACKED_PARENT = 'untracked-parent'; 11 + 12 + private $paths; 13 + private $isValidResults; 14 + private $reasonForEmptyResultSet; 15 + private $existedAtCommit; 16 + private $deletedAtCommit; 17 + private $readmeContent; 18 + 19 + public function setPaths(array $paths) { 20 + assert_instances_of($paths, 'DiffusionRepositoryPath'); 21 + $this->paths = $paths; 22 + return $this; 23 + } 24 + public function getPaths() { 25 + return $this->paths; 26 + } 27 + 28 + public function setIsValidResults($is_valid) { 29 + $this->isValidResults = $is_valid; 30 + return $this; 31 + } 32 + public function isValidResults() { 33 + return $this->isValidResults; 34 + } 35 + 36 + public function setReasonForEmptyResultSet($reason) { 37 + $this->reasonForEmptyResultSet = $reason; 38 + return $this; 39 + } 40 + public function getReasonForEmptyResultSet() { 41 + return $this->reasonForEmptyResultSet; 42 + } 43 + 44 + public function setExistedAtCommit($existed_at_commit) { 45 + $this->existedAtCommit = $existed_at_commit; 46 + return $this; 47 + } 48 + public function getExistedAtCommit() { 49 + return $this->existedAtCommit; 50 + } 51 + 52 + public function setDeletedAtCommit($deleted_at_commit) { 53 + $this->deletedAtCommit = $deleted_at_commit; 54 + return $this; 55 + } 56 + public function getDeletedAtCommit() { 57 + return $this->deletedAtCommit; 58 + } 59 + 60 + public function setReadmeContent($readme_content) { 61 + $this->readmeContent = $readme_content; 62 + return $this; 63 + } 64 + public function getReadmeContent() { 65 + return $this->readmeContent; 66 + } 67 + 68 + public function toDictionary() { 69 + $paths = $this->getPaths(); 70 + if ($paths) { 71 + $paths = mpull($paths, 'toDictionary'); 72 + } 73 + 74 + return array( 75 + 'paths' => $paths, 76 + 'isValidResults' => $this->isValidResults(), 77 + 'reasonForEmptyResultSet' => $this->getReasonForEmptyResultSet(), 78 + 'existedAtCommit' => $this->getExistedAtCommit(), 79 + 'deletedAtCommit' => $this->getDeletedAtCommit(), 80 + 'readmeContent' => $this->getReadmeContent()); 81 + } 82 + 83 + public static function newFromConduit(array $data) { 84 + $paths = array(); 85 + $path_dicts = $data['paths']; 86 + foreach ($path_dicts as $dict) { 87 + $paths[] = DiffusionRepositoryPath::newFromDictionary($dict); 88 + } 89 + return id(new DiffusionBrowseResultSet()) 90 + ->setPaths($paths) 91 + ->setIsValidResults($data['isValidResults']) 92 + ->setReasonForEmptyResultSet($data['reasonForEmptyResultSet']) 93 + ->setExistedAtCommit($data['existedAtCommit']) 94 + ->setDeletedAtCommit($data['deletedAtCommit']) 95 + ->setReadmeContent($data['readmeContent']); 96 + } 97 + }
+55 -14
src/applications/diffusion/data/DiffusionRepositoryPath.php
··· 21 21 return $this->fullPath; 22 22 } 23 23 24 - final public function setPath($path) { 24 + public function setPath($path) { 25 25 $this->path = $path; 26 26 return $this; 27 27 } 28 28 29 - final public function getPath() { 29 + public function getPath() { 30 30 return $this->path; 31 31 } 32 32 33 - final public function setHash($hash) { 33 + public function setHash($hash) { 34 34 $this->hash = $hash; 35 35 return $this; 36 36 } 37 37 38 - final public function getHash() { 38 + public function getHash() { 39 39 return $this->hash; 40 40 } 41 41 42 - final public function setLastModifiedCommit( 42 + public function setLastModifiedCommit( 43 43 PhabricatorRepositoryCommit $commit) { 44 44 $this->lastModifiedCommit = $commit; 45 45 return $this; 46 46 } 47 47 48 - final public function getLastModifiedCommit() { 48 + public function getLastModifiedCommit() { 49 49 return $this->lastModifiedCommit; 50 50 } 51 51 52 - final public function setLastCommitData( 52 + public function setLastCommitData( 53 53 PhabricatorRepositoryCommitData $last_commit_data) { 54 54 $this->lastCommitData = $last_commit_data; 55 55 return $this; 56 56 } 57 57 58 - final public function getLastCommitData() { 58 + public function getLastCommitData() { 59 59 return $this->lastCommitData; 60 60 } 61 61 62 - final public function setFileType($file_type) { 62 + public function setFileType($file_type) { 63 63 $this->fileType = $file_type; 64 64 return $this; 65 65 } 66 66 67 - final public function getFileType() { 67 + public function getFileType() { 68 68 return $this->fileType; 69 69 } 70 70 71 - final public function setFileSize($file_size) { 71 + public function setFileSize($file_size) { 72 72 $this->fileSize = $file_size; 73 73 return $this; 74 74 } 75 75 76 - final public function getFileSize() { 76 + public function getFileSize() { 77 77 return $this->fileSize; 78 78 } 79 79 80 - final public function setExternalURI($external_uri) { 80 + public function setExternalURI($external_uri) { 81 81 $this->externalURI = $external_uri; 82 82 return $this; 83 83 } 84 84 85 - final public function getExternalURI() { 85 + public function getExternalURI() { 86 86 return $this->externalURI; 87 87 } 88 88 89 + public function toDictionary() { 90 + $last_modified_commit = $this->getLastModifiedCommit(); 91 + if ($last_modified_commit) { 92 + $last_modified_commit = $last_modified_commit->toDictionary(); 93 + } 94 + $last_commit_data = $this->getLastCommitData(); 95 + if ($last_commit_data) { 96 + $last_commit_data = $last_commit_data->toDictionary(); 97 + } 98 + return array( 99 + 'fullPath' => $this->getFullPath(), 100 + 'path' => $this->getPath(), 101 + 'hash' => $this->getHash(), 102 + 'fileType' => $this->getFileType(), 103 + 'fileSize' => $this->getFileSize(), 104 + 'externalURI' => $this->getExternalURI(), 105 + 'lastModifiedCommit' => $last_modified_commit, 106 + 'lastCommitData' => $last_commit_data, 107 + ); 108 + } 109 + 110 + public static function newFromDictionary(array $dict) { 111 + $path = id(new DiffusionRepositoryPath()) 112 + ->setFullPath($dict['fullPath']) 113 + ->setPath($dict['path']) 114 + ->setHash($dict['hash']) 115 + ->setFileType($dict['fileType']) 116 + ->setFileSize($dict['fileSize']) 117 + ->setExternalURI($dict['externalURI']); 118 + if ($dict['lastModifiedCommit']) { 119 + $last_modified_commit = PhabricatorRepositoryCommit::newFromDictionary( 120 + $dict['lastModifiedCommit']); 121 + $path->setLastModifiedCommit($last_modified_commit); 122 + } 123 + if ($dict['lastCommitData']) { 124 + $last_commit_data = PhabricatorRepositoryCommitData::newFromDictionary( 125 + $dict['lastCommitData']); 126 + $path->setLastCommitData($last_commit_data); 127 + } 128 + return $path; 129 + } 89 130 }
-164
src/applications/diffusion/query/browse/DiffusionBrowseQuery.php
··· 1 - <?php 2 - 3 - abstract class DiffusionBrowseQuery { 4 - 5 - private $request; 6 - 7 - protected $reason; 8 - protected $existedAtCommit; 9 - protected $deletedAtCommit; 10 - protected $validityOnly; 11 - private $viewer; 12 - 13 - public function setViewer(PhabricatorUser $viewer) { 14 - $this->viewer = $viewer; 15 - return $this; 16 - } 17 - 18 - public function getViewer() { 19 - return $this->viewer; 20 - } 21 - 22 - const REASON_IS_FILE = 'is-file'; 23 - const REASON_IS_DELETED = 'is-deleted'; 24 - const REASON_IS_NONEXISTENT = 'nonexistent'; 25 - const REASON_BAD_COMMIT = 'bad-commit'; 26 - const REASON_IS_EMPTY = 'empty'; 27 - const REASON_IS_UNTRACKED_PARENT = 'untracked-parent'; 28 - 29 - final private function __construct() { 30 - // <private> 31 - } 32 - 33 - final public static function newFromDiffusionRequest( 34 - DiffusionRequest $request) { 35 - 36 - $repository = $request->getRepository(); 37 - 38 - switch ($repository->getVersionControlSystem()) { 39 - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 40 - // TODO: Verify local-path? 41 - $query = new DiffusionGitBrowseQuery(); 42 - break; 43 - case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 44 - $query = new DiffusionMercurialBrowseQuery(); 45 - break; 46 - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 47 - $query = new DiffusionSvnBrowseQuery(); 48 - break; 49 - default: 50 - throw new Exception("Unsupported VCS!"); 51 - } 52 - 53 - $query->request = $request; 54 - 55 - return $query; 56 - } 57 - 58 - final protected function getRequest() { 59 - return $this->request; 60 - } 61 - 62 - final public function getReasonForEmptyResultSet() { 63 - return $this->reason; 64 - } 65 - 66 - final public function getExistedAtCommit() { 67 - return $this->existedAtCommit; 68 - } 69 - 70 - final public function getDeletedAtCommit() { 71 - return $this->deletedAtCommit; 72 - } 73 - 74 - final public function loadPaths() { 75 - $this->reason = null; 76 - return $this->executeQuery(); 77 - } 78 - 79 - final public function shouldOnlyTestValidity() { 80 - return $this->validityOnly; 81 - } 82 - 83 - final public function needValidityOnly($need_validity_only) { 84 - $this->validityOnly = $need_validity_only; 85 - return $this; 86 - } 87 - 88 - final public function renderReadme(array $results) { 89 - $drequest = $this->getRequest(); 90 - 91 - $readme = null; 92 - foreach ($results as $result) { 93 - $file_type = $result->getFileType(); 94 - if (($file_type != ArcanistDiffChangeType::FILE_NORMAL) && 95 - ($file_type != ArcanistDiffChangeType::FILE_TEXT)) { 96 - // Skip directories, etc. 97 - continue; 98 - } 99 - 100 - $path = $result->getPath(); 101 - 102 - if (preg_match('/^readme(|\.txt|\.remarkup|\.rainbow)$/i', $path)) { 103 - $readme = $result; 104 - break; 105 - } 106 - } 107 - 108 - if (!$readme) { 109 - return null; 110 - } 111 - 112 - $readme_request = DiffusionRequest::newFromDictionary( 113 - array( 114 - 'repository' => $drequest->getRepository(), 115 - 'commit' => $drequest->getStableCommitName(), 116 - 'path' => $readme->getFullPath(), 117 - )); 118 - 119 - $file_content = DiffusionFileContent::newFromConduit( 120 - DiffusionQuery::callConduitWithDiffusionRequest( 121 - $this->getViewer(), 122 - $readme_request, 123 - 'diffusion.filecontentquery', 124 - array( 125 - 'commit' => $drequest->getStableCommitName(), 126 - 'path' => $readme->getFullPath(), 127 - 'needsBlame' => false, 128 - ))); 129 - $readme_content = $file_content->getCorpus(); 130 - 131 - if (preg_match('/\\.txt$/', $readme->getPath())) { 132 - $readme_content = phutil_escape_html_newlines($readme_content); 133 - 134 - $class = null; 135 - } else if (preg_match('/\\.rainbow$/', $readme->getPath())) { 136 - $highlighter = new PhutilRainbowSyntaxHighlighter(); 137 - $readme_content = $highlighter 138 - ->getHighlightFuture($readme_content) 139 - ->resolve(); 140 - $readme_content = phutil_escape_html_newlines($readme_content); 141 - 142 - require_celerity_resource('syntax-highlighting-css'); 143 - $class = 'remarkup-code'; 144 - } else { 145 - // Markup extensionless files as remarkup so we get links and such. 146 - $engine = PhabricatorMarkupEngine::newDiffusionMarkupEngine(); 147 - $engine->setConfig('viewer', $this->getViewer()); 148 - $readme_content = $engine->markupText($readme_content); 149 - 150 - $class = 'phabricator-remarkup'; 151 - } 152 - 153 - $readme_content = phutil_tag( 154 - 'div', 155 - array( 156 - 'class' => $class, 157 - ), 158 - $readme_content); 159 - 160 - return $readme_content; 161 - } 162 - 163 - abstract protected function executeQuery(); 164 - }
-149
src/applications/diffusion/query/browse/DiffusionGitBrowseQuery.php
··· 1 - <?php 2 - 3 - final class DiffusionGitBrowseQuery extends DiffusionBrowseQuery { 4 - 5 - protected function executeQuery() { 6 - $drequest = $this->getRequest(); 7 - $repository = $drequest->getRepository(); 8 - 9 - $path = $drequest->getPath(); 10 - $commit = $drequest->getCommit(); 11 - 12 - if ($path == '') { 13 - // Fast path to improve the performance of the repository view; we know 14 - // the root is always a tree at any commit and always exists. 15 - $stdout = 'tree'; 16 - } else { 17 - try { 18 - list($stdout) = $repository->execxLocalCommand( 19 - 'cat-file -t %s:%s', 20 - $commit, 21 - $path); 22 - } catch (CommandException $e) { 23 - $stderr = $e->getStdErr(); 24 - if (preg_match('/^fatal: Not a valid object name/', $stderr)) { 25 - // Grab two logs, since the first one is when the object was deleted. 26 - list($stdout) = $repository->execxLocalCommand( 27 - 'log -n2 --format="%%H" %s -- %s', 28 - $commit, 29 - $path); 30 - $stdout = trim($stdout); 31 - if ($stdout) { 32 - $commits = explode("\n", $stdout); 33 - $this->reason = self::REASON_IS_DELETED; 34 - $this->deletedAtCommit = idx($commits, 0); 35 - $this->existedAtCommit = idx($commits, 1); 36 - return array(); 37 - } 38 - 39 - $this->reason = self::REASON_IS_NONEXISTENT; 40 - return array(); 41 - } else { 42 - throw $e; 43 - } 44 - } 45 - } 46 - 47 - if (trim($stdout) == 'blob') { 48 - $this->reason = self::REASON_IS_FILE; 49 - return array(); 50 - } 51 - 52 - if ($this->shouldOnlyTestValidity()) { 53 - return true; 54 - } 55 - 56 - list($stdout) = $repository->execxLocalCommand( 57 - 'ls-tree -z -l %s:%s', 58 - $commit, 59 - $path); 60 - 61 - $submodules = array(); 62 - 63 - if (strlen($path)) { 64 - $prefix = rtrim($path, '/').'/'; 65 - } else { 66 - $prefix = ''; 67 - } 68 - 69 - $results = array(); 70 - foreach (explode("\0", rtrim($stdout)) as $line) { 71 - 72 - // NOTE: Limit to 5 components so we parse filenames with spaces in them 73 - // correctly. 74 - list($mode, $type, $hash, $size, $name) = preg_split('/\s+/', $line, 5); 75 - 76 - $result = new DiffusionRepositoryPath(); 77 - 78 - if ($type == 'tree') { 79 - $file_type = DifferentialChangeType::FILE_DIRECTORY; 80 - } else if ($type == 'commit') { 81 - $file_type = DifferentialChangeType::FILE_SUBMODULE; 82 - $submodules[] = $result; 83 - } else { 84 - $mode = intval($mode, 8); 85 - if (($mode & 0120000) == 0120000) { 86 - $file_type = DifferentialChangeType::FILE_SYMLINK; 87 - } else { 88 - $file_type = DifferentialChangeType::FILE_NORMAL; 89 - } 90 - } 91 - 92 - $result->setFullPath($prefix.$name); 93 - $result->setPath($name); 94 - $result->setHash($hash); 95 - $result->setFileType($file_type); 96 - $result->setFileSize($size); 97 - 98 - $results[] = $result; 99 - } 100 - 101 - // If we identified submodules, lookup the module info at this commit to 102 - // find their source URIs. 103 - 104 - if ($submodules) { 105 - 106 - // NOTE: We need to read the file out of git and write it to a temporary 107 - // location because "git config -f" doesn't accept a "commit:path"-style 108 - // argument. 109 - 110 - // NOTE: This file may not exist, e.g. because the commit author removed 111 - // it when they added the submodule. See T1448. If it's not present, just 112 - // show the submodule without enriching it. If ".gitmodules" was removed 113 - // it seems to partially break submodules, but the repository as a whole 114 - // continues to work fine and we've seen at least two cases of this in 115 - // the wild. 116 - 117 - list($err, $contents) = $repository->execLocalCommand( 118 - 'cat-file blob %s:.gitmodules', 119 - $commit); 120 - 121 - if (!$err) { 122 - $tmp = new TempFile(); 123 - Filesystem::writeFile($tmp, $contents); 124 - list($module_info) = $repository->execxLocalCommand( 125 - 'config -l -f %s', 126 - $tmp); 127 - 128 - $dict = array(); 129 - $lines = explode("\n", trim($module_info)); 130 - foreach ($lines as $line) { 131 - list($key, $value) = explode('=', $line, 2); 132 - $parts = explode('.', $key); 133 - $dict[$key] = $value; 134 - } 135 - 136 - foreach ($submodules as $path) { 137 - $full_path = $path->getFullPath(); 138 - $key = 'submodule.'.$full_path.'.url'; 139 - if (isset($dict[$key])) { 140 - $path->setExternalURI($dict[$key]); 141 - } 142 - } 143 - } 144 - } 145 - 146 - return $results; 147 - } 148 - 149 - }
-80
src/applications/diffusion/query/browse/DiffusionMercurialBrowseQuery.php
··· 1 - <?php 2 - 3 - final class DiffusionMercurialBrowseQuery extends DiffusionBrowseQuery { 4 - 5 - protected function executeQuery() { 6 - $drequest = $this->getRequest(); 7 - $repository = $drequest->getRepository(); 8 - 9 - $path = $drequest->getPath(); 10 - $commit = $drequest->getStableCommitName(); 11 - 12 - // TODO: This is a really really awful mess but Mercurial doesn't offer 13 - // an equivalent of "git ls-files -- directory". If it's any comfort, this 14 - // is what "hgweb" does too, see: 15 - // 16 - // http://selenic.com/repo/hg/file/91dc8878f888/mercurial/hgweb/webcommands.py#l320 17 - // 18 - // derp derp derp derp 19 - // 20 - // Anyway, figure out what's in this path by applying massive amounts 21 - // of brute force. 22 - 23 - list($entire_manifest) = $repository->execxLocalCommand( 24 - 'manifest --rev %s', 25 - $commit); 26 - $entire_manifest = explode("\n", $entire_manifest); 27 - 28 - $results = array(); 29 - 30 - $match_against = trim($path, '/'); 31 - $match_len = strlen($match_against); 32 - 33 - // For the root, don't trim. For other paths, trim the "/" after we match. 34 - // We need this because Mercurial's canonical paths have no leading "/", 35 - // but ours do. 36 - $trim_len = $match_len ? $match_len + 1 : 0; 37 - 38 - foreach ($entire_manifest as $path) { 39 - if (strncmp($path, $match_against, $match_len)) { 40 - continue; 41 - } 42 - if (!strlen($path)) { 43 - continue; 44 - } 45 - $remainder = substr($path, $trim_len); 46 - if (!strlen($remainder)) { 47 - // There is a file with this exact name in the manifest, so clearly 48 - // it's a file. 49 - $this->reason = self::REASON_IS_FILE; 50 - return array(); 51 - } 52 - $parts = explode('/', $remainder); 53 - if (count($parts) == 1) { 54 - $type = DifferentialChangeType::FILE_NORMAL; 55 - } else { 56 - $type = DifferentialChangeType::FILE_DIRECTORY; 57 - } 58 - $results[reset($parts)] = $type; 59 - } 60 - 61 - foreach ($results as $key => $type) { 62 - $result = new DiffusionRepositoryPath(); 63 - $result->setPath($key); 64 - $result->setFileType($type); 65 - $result->setFullPath(ltrim($match_against.'/', '/').$key); 66 - 67 - $results[$key] = $result; 68 - } 69 - 70 - if (empty($results)) { 71 - // TODO: Detect "deleted" by issuing "hg log"? 72 - 73 - $this->reason = self::REASON_IS_NONEXISTENT; 74 - } 75 - 76 - 77 - return $results; 78 - } 79 - 80 - }
-190
src/applications/diffusion/query/browse/DiffusionSvnBrowseQuery.php
··· 1 - <?php 2 - 3 - final class DiffusionSvnBrowseQuery extends DiffusionBrowseQuery { 4 - 5 - protected function executeQuery() { 6 - $drequest = $this->getRequest(); 7 - $repository = $drequest->getRepository(); 8 - 9 - $path = $drequest->getPath(); 10 - $commit = $drequest->getCommit(); 11 - 12 - $subpath = $repository->getDetail('svn-subpath'); 13 - if ($subpath && strncmp($subpath, $path, strlen($subpath))) { 14 - // If we have a subpath and the path isn't a child of it, it (almost 15 - // certainly) won't exist since we don't track commits which affect 16 - // it. (Even if it exists, return a consistent result.) 17 - $this->reason = self::REASON_IS_UNTRACKED_PARENT; 18 - return array(); 19 - } 20 - 21 - $conn_r = $repository->establishConnection('r'); 22 - 23 - $parent_path = DiffusionPathIDQuery::getParentPath($path); 24 - $path_query = new DiffusionPathIDQuery( 25 - array( 26 - $path, 27 - $parent_path, 28 - )); 29 - $path_map = $path_query->loadPathIDs(); 30 - 31 - $path_id = $path_map[$path]; 32 - $parent_path_id = $path_map[$parent_path]; 33 - 34 - if (empty($path_id)) { 35 - $this->reason = self::REASON_IS_NONEXISTENT; 36 - return array(); 37 - } 38 - 39 - if ($commit) { 40 - $slice_clause = 'AND svnCommit <= '.(int)$commit; 41 - } else { 42 - $slice_clause = ''; 43 - } 44 - 45 - $index = queryfx_all( 46 - $conn_r, 47 - 'SELECT pathID, max(svnCommit) maxCommit FROM %T WHERE 48 - repositoryID = %d AND parentID = %d 49 - %Q GROUP BY pathID', 50 - PhabricatorRepository::TABLE_FILESYSTEM, 51 - $repository->getID(), 52 - $path_id, 53 - $slice_clause); 54 - 55 - if (!$index) { 56 - if ($path == '/') { 57 - $this->reason = self::REASON_IS_EMPTY; 58 - } else { 59 - 60 - // NOTE: The parent path ID is included so this query can take 61 - // advantage of the table's primary key; it is uniquely determined by 62 - // the pathID but if we don't do the lookup ourselves MySQL doesn't have 63 - // the information it needs to avoid a table scan. 64 - 65 - $reasons = queryfx_all( 66 - $conn_r, 67 - 'SELECT * FROM %T WHERE repositoryID = %d 68 - AND parentID = %d 69 - AND pathID = %d 70 - %Q ORDER BY svnCommit DESC LIMIT 2', 71 - PhabricatorRepository::TABLE_FILESYSTEM, 72 - $repository->getID(), 73 - $parent_path_id, 74 - $path_id, 75 - $slice_clause); 76 - 77 - $reason = reset($reasons); 78 - 79 - if (!$reason) { 80 - $this->reason = self::REASON_IS_NONEXISTENT; 81 - } else { 82 - $file_type = $reason['fileType']; 83 - if (empty($reason['existed'])) { 84 - $this->reason = self::REASON_IS_DELETED; 85 - $this->deletedAtCommit = $reason['svnCommit']; 86 - if (!empty($reasons[1])) { 87 - $this->existedAtCommit = $reasons[1]['svnCommit']; 88 - } 89 - } else if ($file_type == DifferentialChangeType::FILE_DIRECTORY) { 90 - $this->reason = self::REASON_IS_EMPTY; 91 - } else { 92 - $this->reason = self::REASON_IS_FILE; 93 - } 94 - } 95 - } 96 - return array(); 97 - } 98 - 99 - if ($this->shouldOnlyTestValidity()) { 100 - return true; 101 - } 102 - 103 - $sql = array(); 104 - foreach ($index as $row) { 105 - $sql[] = 106 - '(pathID = '.(int)$row['pathID'].' AND '. 107 - 'svnCommit = '.(int)$row['maxCommit'].')'; 108 - } 109 - 110 - $browse = queryfx_all( 111 - $conn_r, 112 - 'SELECT *, p.path pathName 113 - FROM %T f JOIN %T p ON f.pathID = p.id 114 - WHERE repositoryID = %d 115 - AND parentID = %d 116 - AND existed = 1 117 - AND (%Q) 118 - ORDER BY pathName', 119 - PhabricatorRepository::TABLE_FILESYSTEM, 120 - PhabricatorRepository::TABLE_PATH, 121 - $repository->getID(), 122 - $path_id, 123 - implode(' OR ', $sql)); 124 - 125 - $loadable_commits = array(); 126 - foreach ($browse as $key => $file) { 127 - // We need to strip out directories because we don't store last-modified 128 - // in the filesystem table. 129 - if ($file['fileType'] != DifferentialChangeType::FILE_DIRECTORY) { 130 - $loadable_commits[] = $file['svnCommit']; 131 - $browse[$key]['hasCommit'] = true; 132 - } 133 - } 134 - 135 - $commits = array(); 136 - $commit_data = array(); 137 - if ($loadable_commits) { 138 - // NOTE: Even though these are integers, use '%Ls' because MySQL doesn't 139 - // use the second part of the key otherwise! 140 - $commits = id(new PhabricatorRepositoryCommit())->loadAllWhere( 141 - 'repositoryID = %d AND commitIdentifier IN (%Ls)', 142 - $repository->getID(), 143 - $loadable_commits); 144 - $commits = mpull($commits, null, 'getCommitIdentifier'); 145 - if ($commits) { 146 - $commit_data = id(new PhabricatorRepositoryCommitData())->loadAllWhere( 147 - 'commitID in (%Ld)', 148 - mpull($commits, 'getID')); 149 - $commit_data = mpull($commit_data, null, 'getCommitID'); 150 - } else { 151 - $commit_data = array(); 152 - } 153 - } 154 - 155 - $path_normal = DiffusionPathIDQuery::normalizePath($path); 156 - 157 - $results = array(); 158 - foreach ($browse as $file) { 159 - 160 - $full_path = $file['pathName']; 161 - $file_path = ltrim(substr($full_path, strlen($path_normal)), '/'); 162 - $full_path = ltrim($full_path, '/'); 163 - 164 - $result = new DiffusionRepositoryPath(); 165 - $result->setPath($file_path); 166 - $result->setFullPath($full_path); 167 - // $result->setHash($hash); 168 - $result->setFileType($file['fileType']); 169 - // $result->setFileSize($size); 170 - 171 - if (!empty($file['hasCommit'])) { 172 - $commit = idx($commits, $file['svnCommit']); 173 - if ($commit) { 174 - $data = idx($commit_data, $commit->getID()); 175 - $result->setLastModifiedCommit($commit); 176 - $result->setLastCommitData($data); 177 - } 178 - } 179 - 180 - $results[] = $result; 181 - } 182 - 183 - if (empty($results)) { 184 - $this->reason = self::REASON_IS_EMPTY; 185 - } 186 - 187 - return $results; 188 - } 189 - 190 - }
+12 -11
src/applications/diffusion/view/DiffusionEmptyResultView.php
··· 2 2 3 3 final class DiffusionEmptyResultView extends DiffusionView { 4 4 5 - private $browseQuery; 5 + private $browseResultSet; 6 6 private $view; 7 7 8 - public function setBrowseQuery($browse_query) { 9 - $this->browseQuery = $browse_query; 8 + public function setDiffusionBrowseResultSet(DiffusionBrowseResultSet $set) { 9 + $this->browseResultSet = $set; 10 10 return $this; 11 11 } 12 12 ··· 26 26 $commit = 'HEAD'; 27 27 } 28 28 29 - switch ($this->browseQuery->getReasonForEmptyResultSet()) { 30 - case DiffusionBrowseQuery::REASON_IS_NONEXISTENT: 29 + $reason = $this->browseResultSet->getReasonForEmptyResultSet(); 30 + switch ($reason) { 31 + case DiffusionBrowseResultSet::REASON_IS_NONEXISTENT: 31 32 $title = 'Path Does Not Exist'; 32 33 // TODO: Under git, this error message should be more specific. It 33 34 // may exist on some other branch. 34 35 $body = "This path does not exist anywhere."; 35 36 $severity = AphrontErrorView::SEVERITY_ERROR; 36 37 break; 37 - case DiffusionBrowseQuery::REASON_IS_EMPTY: 38 + case DiffusionBrowseResultSet::REASON_IS_EMPTY: 38 39 $title = 'Empty Directory'; 39 40 $body = "This path was an empty directory at {$commit}.\n"; 40 41 $severity = AphrontErrorView::SEVERITY_NOTICE; 41 42 break; 42 - case DiffusionBrowseQuery::REASON_IS_DELETED: 43 - $deleted = $this->browseQuery->getDeletedAtCommit(); 44 - $existed = $this->browseQuery->getExistedAtCommit(); 43 + case DiffusionBrowseResultSet::REASON_IS_DELETED: 44 + $deleted = $this->browseResultSet->getDeletedAtCommit(); 45 + $existed = $this->browseResultSet->getExistedAtCommit(); 45 46 46 47 $browse = $this->linkBrowse( 47 48 $drequest->getPath(), ··· 61 62 "r{$callsign}{$existed}"); 62 63 $severity = AphrontErrorView::SEVERITY_WARNING; 63 64 break; 64 - case DiffusionBrowseQuery::REASON_IS_UNTRACKED_PARENT: 65 + case DiffusionBrowseResultSet::REASON_IS_UNTRACKED_PARENT: 65 66 $subdir = $drequest->getRepository()->getDetail('svn-subpath'); 66 67 $title = 'Directory Not Tracked'; 67 68 $body = ··· 72 73 $severity = AphrontErrorView::SEVERITY_WARNING; 73 74 break; 74 75 default: 75 - throw new Exception("Unknown failure reason!"); 76 + throw new Exception("Unknown failure reason: $reason"); 76 77 } 77 78 78 79 $error_view = new AphrontErrorView();
+12 -7
src/applications/owners/storage/PhabricatorOwnersPackage.php
··· 312 312 'repository' => $repository, 313 313 'path' => $path, 314 314 )); 315 - $query = DiffusionBrowseQuery::newFromDiffusionRequest($drequest); 316 - $query->setViewer($this->getActor()); 317 - $query->needValidityOnly(true); 318 - $valid = $query->loadPaths(); 315 + $results = DiffusionBrowseResultSet::newFromConduit( 316 + DiffusionQuery::callConduitWithDiffusionRequest( 317 + $this->getActor(), 318 + $drequest, 319 + 'diffusion.browsequery', 320 + array( 321 + 'path' => $path, 322 + 'needValidityOnly' => true))); 323 + $valid = $results->isValidResults(); 319 324 $is_directory = true; 320 325 if (!$valid) { 321 - switch ($query->getReasonForEmptyResultSet()) { 322 - case DiffusionBrowseQuery::REASON_IS_FILE: 326 + switch ($results->getReasonForEmptyResultSet()) { 327 + case DiffusionBrowseResultSet::REASON_IS_FILE: 323 328 $valid = true; 324 329 $is_directory = false; 325 330 break; 326 - case DiffusionBrowseQuery::REASON_IS_EMPTY: 331 + case DiffusionBrowseResultSet::REASON_IS_EMPTY: 327 332 $valid = true; 328 333 break; 329 334 }
+23
src/applications/repository/storage/PhabricatorRepositoryCommit.php
··· 180 180 ); 181 181 } 182 182 183 + /* -( Stuff for serialization )---------------------------------------------- */ 184 + 185 + /** 186 + * NOTE: this is not a complete serialization; only the 'protected' fields are 187 + * involved. This is due to ease of (ab)using the Lisk abstraction to get this 188 + * done, as well as complexity of the other fields. 189 + */ 190 + public function toDictionary() { 191 + return array( 192 + 'repositoryID' => $this->getRepositoryID(), 193 + 'phid' => $this->getPHID(), 194 + 'commitIdentifier' => $this->getCommitIdentifier(), 195 + 'epoch' => $this->getEpoch(), 196 + 'mailKey' => $this->getMailKey(), 197 + 'authorPHID' => $this->getAuthorPHID(), 198 + 'auditStatus' => $this->getAuditStatus(), 199 + 'summary' => $this->getSummary()); 200 + } 201 + 202 + public static function newFromDictionary(array $dict) { 203 + return id(new PhabricatorRepositoryCommit()) 204 + ->loadFromArray($dict); 205 + } 183 206 }
+14
src/applications/repository/storage/PhabricatorRepositoryCommitData.php
··· 41 41 return $this; 42 42 } 43 43 44 + public function toDictionary() { 45 + return array( 46 + 'commitID' => $this->commitID, 47 + 'authorName' => $this->authorName, 48 + 'commitMessage' => $this->commitMessage, 49 + 'commitDetails' => json_encode($this->commitDetails), 50 + ); 51 + } 52 + 53 + public static function newFromDictionary(array $dict) { 54 + return id(new PhabricatorRepositoryCommitData()) 55 + ->loadFromArray($dict); 56 + } 57 + 44 58 }