diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index de73525ec5..affafaff5f 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -146,6 +146,7 @@ phutil_register_library_map(array( 'ConduitAPI_diffusion_Method' => 'applications/diffusion/conduit/ConduitAPI_diffusion_Method.php', 'ConduitAPI_diffusion_abstractquery_Method' => 'applications/diffusion/conduit/ConduitAPI_diffusion_abstractquery_Method.php', 'ConduitAPI_diffusion_branchquery_Method' => 'applications/diffusion/conduit/ConduitAPI_diffusion_branchquery_Method.php', + 'ConduitAPI_diffusion_browsequery_Method' => 'applications/diffusion/conduit/ConduitAPI_diffusion_browsequery_Method.php', 'ConduitAPI_diffusion_existsquery_Method' => 'applications/diffusion/conduit/ConduitAPI_diffusion_existsquery_Method.php', 'ConduitAPI_diffusion_filecontentquery_Method' => 'applications/diffusion/conduit/ConduitAPI_diffusion_filecontentquery_Method.php', 'ConduitAPI_diffusion_findsymbols_Method' => 'applications/diffusion/conduit/ConduitAPI_diffusion_findsymbols_Method.php', @@ -404,7 +405,7 @@ phutil_register_library_map(array( 'DiffusionBranchTableView' => 'applications/diffusion/view/DiffusionBranchTableView.php', 'DiffusionBrowseController' => 'applications/diffusion/controller/DiffusionBrowseController.php', 'DiffusionBrowseFileController' => 'applications/diffusion/controller/DiffusionBrowseFileController.php', - 'DiffusionBrowseQuery' => 'applications/diffusion/query/browse/DiffusionBrowseQuery.php', + 'DiffusionBrowseResultSet' => 'applications/diffusion/data/DiffusionBrowseResultSet.php', 'DiffusionBrowseTableView' => 'applications/diffusion/view/DiffusionBrowseTableView.php', 'DiffusionChangeController' => 'applications/diffusion/controller/DiffusionChangeController.php', 'DiffusionCommentListView' => 'applications/diffusion/view/DiffusionCommentListView.php', @@ -427,7 +428,6 @@ phutil_register_library_map(array( 'DiffusionFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionFileContentQuery.php', 'DiffusionGitBranch' => 'applications/diffusion/data/DiffusionGitBranch.php', 'DiffusionGitBranchTestCase' => 'applications/diffusion/data/__tests__/DiffusionGitBranchTestCase.php', - 'DiffusionGitBrowseQuery' => 'applications/diffusion/query/browse/DiffusionGitBrowseQuery.php', 'DiffusionGitCommitParentsQuery' => 'applications/diffusion/query/parents/DiffusionGitCommitParentsQuery.php', 'DiffusionGitCommitTagsQuery' => 'applications/diffusion/query/committags/DiffusionGitCommitTagsQuery.php', 'DiffusionGitContainsQuery' => 'applications/diffusion/query/contains/DiffusionGitContainsQuery.php', @@ -451,7 +451,6 @@ phutil_register_library_map(array( 'DiffusionLintController' => 'applications/diffusion/controller/DiffusionLintController.php', 'DiffusionLintDetailsController' => 'applications/diffusion/controller/DiffusionLintDetailsController.php', 'DiffusionLintSaveRunner' => 'applications/diffusion/DiffusionLintSaveRunner.php', - 'DiffusionMercurialBrowseQuery' => 'applications/diffusion/query/browse/DiffusionMercurialBrowseQuery.php', 'DiffusionMercurialCommitParentsQuery' => 'applications/diffusion/query/parents/DiffusionMercurialCommitParentsQuery.php', 'DiffusionMercurialCommitTagsQuery' => 'applications/diffusion/query/committags/DiffusionMercurialCommitTagsQuery.php', 'DiffusionMercurialContainsQuery' => 'applications/diffusion/query/contains/DiffusionMercurialContainsQuery.php', @@ -481,7 +480,6 @@ phutil_register_library_map(array( 'DiffusionRepositoryTag' => 'applications/diffusion/data/DiffusionRepositoryTag.php', 'DiffusionRequest' => 'applications/diffusion/request/DiffusionRequest.php', 'DiffusionSetupException' => 'applications/diffusion/exception/DiffusionSetupException.php', - 'DiffusionSvnBrowseQuery' => 'applications/diffusion/query/browse/DiffusionSvnBrowseQuery.php', 'DiffusionSvnCommitParentsQuery' => 'applications/diffusion/query/parents/DiffusionSvnCommitParentsQuery.php', 'DiffusionSvnCommitTagsQuery' => 'applications/diffusion/query/committags/DiffusionSvnCommitTagsQuery.php', 'DiffusionSvnContainsQuery' => 'applications/diffusion/query/contains/DiffusionSvnContainsQuery.php', @@ -1921,6 +1919,7 @@ phutil_register_library_map(array( 'ConduitAPI_diffusion_Method' => 'ConduitAPIMethod', 'ConduitAPI_diffusion_abstractquery_Method' => 'ConduitAPI_diffusion_Method', 'ConduitAPI_diffusion_branchquery_Method' => 'ConduitAPI_diffusion_abstractquery_Method', + 'ConduitAPI_diffusion_browsequery_Method' => 'ConduitAPI_diffusion_abstractquery_Method', 'ConduitAPI_diffusion_existsquery_Method' => 'ConduitAPI_diffusion_abstractquery_Method', 'ConduitAPI_diffusion_filecontentquery_Method' => 'ConduitAPI_diffusion_abstractquery_Method', 'ConduitAPI_diffusion_findsymbols_Method' => 'ConduitAPI_diffusion_Method', @@ -2190,7 +2189,6 @@ phutil_register_library_map(array( 'DiffusionExternalController' => 'DiffusionController', 'DiffusionFileContentQuery' => 'DiffusionQuery', 'DiffusionGitBranchTestCase' => 'PhabricatorTestCase', - 'DiffusionGitBrowseQuery' => 'DiffusionBrowseQuery', 'DiffusionGitCommitParentsQuery' => 'DiffusionCommitParentsQuery', 'DiffusionGitCommitTagsQuery' => 'DiffusionCommitTagsQuery', 'DiffusionGitContainsQuery' => 'DiffusionContainsQuery', @@ -2213,7 +2211,6 @@ phutil_register_library_map(array( 'DiffusionLastModifiedQuery' => 'DiffusionQuery', 'DiffusionLintController' => 'DiffusionController', 'DiffusionLintDetailsController' => 'DiffusionController', - 'DiffusionMercurialBrowseQuery' => 'DiffusionBrowseQuery', 'DiffusionMercurialCommitParentsQuery' => 'DiffusionCommitParentsQuery', 'DiffusionMercurialCommitTagsQuery' => 'DiffusionCommitTagsQuery', 'DiffusionMercurialContainsQuery' => 'DiffusionContainsQuery', @@ -2235,7 +2232,6 @@ phutil_register_library_map(array( 'DiffusionRemarkupRule' => 'PhabricatorRemarkupRuleObject', 'DiffusionRepositoryController' => 'DiffusionController', 'DiffusionSetupException' => 'AphrontUsageException', - 'DiffusionSvnBrowseQuery' => 'DiffusionBrowseQuery', 'DiffusionSvnCommitParentsQuery' => 'DiffusionCommitParentsQuery', 'DiffusionSvnCommitTagsQuery' => 'DiffusionCommitTagsQuery', 'DiffusionSvnContainsQuery' => 'DiffusionContainsQuery', diff --git a/src/applications/diffusion/conduit/ConduitAPI_diffusion_browsequery_Method.php b/src/applications/diffusion/conduit/ConduitAPI_diffusion_browsequery_Method.php new file mode 100644 index 0000000000..671287a89a --- /dev/null +++ b/src/applications/diffusion/conduit/ConduitAPI_diffusion_browsequery_Method.php @@ -0,0 +1,547 @@ + 'optional string', + 'commit' => 'optional string', + 'needValidityOnly' => 'optional bool', + 'renderReadme' => 'optional bool', + ); + } + + protected function getResult(ConduitAPIRequest $request) { + $result = parent::getResult($request); + if ($request->getValue('renderReadme', false)) { + $readme = $this->renderReadme($request, $result); + } + return $result->toDictionary(); + } + + final private function renderReadme( + ConduitAPIRequest $request, + DiffusionBrowseResultSet $result) { + $drequest = $this->getDiffusionRequest(); + + $readme = null; + foreach ($result->getPaths() as $result_path) { + $file_type = $result_path->getFileType(); + if (($file_type != ArcanistDiffChangeType::FILE_NORMAL) && + ($file_type != ArcanistDiffChangeType::FILE_TEXT)) { + // Skip directories, etc. + continue; + } + + $path = $result_path->getPath(); + + if (preg_match('/^readme(|\.txt|\.remarkup|\.rainbow)$/i', $path)) { + $readme = $result_path; + break; + } + } + + if (!$readme) { + return null; + } + + $readme_request = DiffusionRequest::newFromDictionary( + array( + 'repository' => $drequest->getRepository(), + 'commit' => $drequest->getStableCommitName(), + 'path' => $readme->getFullPath(), + )); + + $file_content = DiffusionFileContent::newFromConduit( + DiffusionQuery::callConduitWithDiffusionRequest( + $request->getUser(), + $readme_request, + 'diffusion.filecontentquery', + array( + 'commit' => $drequest->getStableCommitName(), + 'path' => $readme->getFullPath(), + 'needsBlame' => false, + ))); + $readme_content = $file_content->getCorpus(); + + if (preg_match('/\\.txt$/', $readme->getPath())) { + $readme_content = phutil_escape_html_newlines($readme_content); + + $class = null; + } else if (preg_match('/\\.rainbow$/', $readme->getPath())) { + $highlighter = new PhutilRainbowSyntaxHighlighter(); + $readme_content = $highlighter + ->getHighlightFuture($readme_content) + ->resolve(); + $readme_content = phutil_escape_html_newlines($readme_content); + + require_celerity_resource('syntax-highlighting-css'); + $class = 'remarkup-code'; + } else { + // Markup extensionless files as remarkup so we get links and such. + $engine = PhabricatorMarkupEngine::newDiffusionMarkupEngine(); + $engine->setConfig('viewer', $request->getUser()); + $readme_content = $engine->markupText($readme_content); + + $class = 'phabricator-remarkup'; + } + + $readme_content = phutil_tag( + 'div', + array( + 'class' => $class, + ), + $readme_content); + + $result->setReadmeContent($readme_content); + return $result; + } + + protected function getGitResult(ConduitAPIRequest $request) { + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $path = $request->getValue('path'); + $commit = $request->getValue('commit'); + $result = $this->getEmptyResultSet(); + + if ($path == '') { + // Fast path to improve the performance of the repository view; we know + // the root is always a tree at any commit and always exists. + $stdout = 'tree'; + } else { + try { + list($stdout) = $repository->execxLocalCommand( + 'cat-file -t %s:%s', + $commit, + $path); + } catch (CommandException $e) { + $stderr = $e->getStdErr(); + if (preg_match('/^fatal: Not a valid object name/', $stderr)) { + // Grab two logs, since the first one is when the object was deleted. + list($stdout) = $repository->execxLocalCommand( + 'log -n2 --format="%%H" %s -- %s', + $commit, + $path); + $stdout = trim($stdout); + if ($stdout) { + $commits = explode("\n", $stdout); + $result + ->setReasonForEmptyResultSet( + DiffusionBrowseResultSet::REASON_IS_DELETED) + ->setDeletedAtCommit(idx($commits, 0)) + ->setExistedAtCommit(idx($commits, 1)); + return $result; + } + + $result->setReasonForEmptyResultSet( + DiffusionBrowseResultSet::REASON_IS_NONEXISTENT); + return $result; + } else { + throw $e; + } + } + } + + if (trim($stdout) == 'blob') { + $result->setReasonForEmptyResultSet( + DiffusionBrowseResultSet::REASON_IS_FILE); + return $result; + } + + $result->setIsValidResults(true); + if ($this->shouldOnlyTestValidity($request)) { + return $result; + } + + list($stdout) = $repository->execxLocalCommand( + 'ls-tree -z -l %s:%s', + $commit, + $path); + + $submodules = array(); + + if (strlen($path)) { + $prefix = rtrim($path, '/').'/'; + } else { + $prefix = ''; + } + + $results = array(); + foreach (explode("\0", rtrim($stdout)) as $line) { + + // NOTE: Limit to 5 components so we parse filenames with spaces in them + // correctly. + list($mode, $type, $hash, $size, $name) = preg_split('/\s+/', $line, 5); + + $path_result = new DiffusionRepositoryPath(); + + if ($type == 'tree') { + $file_type = DifferentialChangeType::FILE_DIRECTORY; + } else if ($type == 'commit') { + $file_type = DifferentialChangeType::FILE_SUBMODULE; + $submodules[] = $path_result; + } else { + $mode = intval($mode, 8); + if (($mode & 0120000) == 0120000) { + $file_type = DifferentialChangeType::FILE_SYMLINK; + } else { + $file_type = DifferentialChangeType::FILE_NORMAL; + } + } + + $path_result->setFullPath($prefix.$name); + $path_result->setPath($name); + $path_result->setHash($hash); + $path_result->setFileType($file_type); + $path_result->setFileSize($size); + + $results[] = $path_result; + } + + // If we identified submodules, lookup the module info at this commit to + // find their source URIs. + + if ($submodules) { + + // NOTE: We need to read the file out of git and write it to a temporary + // location because "git config -f" doesn't accept a "commit:path"-style + // argument. + + // NOTE: This file may not exist, e.g. because the commit author removed + // it when they added the submodule. See T1448. If it's not present, just + // show the submodule without enriching it. If ".gitmodules" was removed + // it seems to partially break submodules, but the repository as a whole + // continues to work fine and we've seen at least two cases of this in + // the wild. + + list($err, $contents) = $repository->execLocalCommand( + 'cat-file blob %s:.gitmodules', + $commit); + + if (!$err) { + $tmp = new TempFile(); + Filesystem::writeFile($tmp, $contents); + list($module_info) = $repository->execxLocalCommand( + 'config -l -f %s', + $tmp); + + $dict = array(); + $lines = explode("\n", trim($module_info)); + foreach ($lines as $line) { + list($key, $value) = explode('=', $line, 2); + $parts = explode('.', $key); + $dict[$key] = $value; + } + + foreach ($submodules as $path) { + $full_path = $path->getFullPath(); + $key = 'submodule.'.$full_path.'.url'; + if (isset($dict[$key])) { + $path->setExternalURI($dict[$key]); + } + } + } + } + + return $result->setPaths($results); + } + + protected function getMercurialResult(ConduitAPIRequest $request) { + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $path = $request->getValue('path'); + $commit = $request->getValue('commit'); + $result = $this->getEmptyResultSet(); + + // TODO: This is a really really awful mess but Mercurial doesn't offer + // an equivalent of "git ls-files -- directory". If it's any comfort, this + // is what "hgweb" does too, see: + // + // http://selenic.com/repo/hg/file/91dc8878f888/mercurial/hgweb/webcommands.py#l320 + // + // derp derp derp derp + // + // Anyway, figure out what's in this path by applying massive amounts + // of brute force. + + list($entire_manifest) = $repository->execxLocalCommand( + 'manifest --rev %s', + $commit); + $entire_manifest = explode("\n", $entire_manifest); + + $results = array(); + + $match_against = trim($path, '/'); + $match_len = strlen($match_against); + + // For the root, don't trim. For other paths, trim the "/" after we match. + // We need this because Mercurial's canonical paths have no leading "/", + // but ours do. + $trim_len = $match_len ? $match_len + 1 : 0; + + foreach ($entire_manifest as $path) { + if (strncmp($path, $match_against, $match_len)) { + continue; + } + if (!strlen($path)) { + continue; + } + $remainder = substr($path, $trim_len); + if (!strlen($remainder)) { + // There is a file with this exact name in the manifest, so clearly + // it's a file. + $result->setReasonForEmptyResultSet( + DiffusionBrowseResultSet::REASON_IS_FILE); + return $result; + } + $parts = explode('/', $remainder); + if (count($parts) == 1) { + $type = DifferentialChangeType::FILE_NORMAL; + } else { + $type = DifferentialChangeType::FILE_DIRECTORY; + } + $results[reset($parts)] = $type; + } + + foreach ($results as $key => $type) { + $path_result = new DiffusionRepositoryPath(); + $path_result->setPath($key); + $path_result->setFileType($type); + $path_result->setFullPath(ltrim($match_against.'/', '/').$key); + + $results[$key] = $path_result; + } + + $valid_results = true; + if (empty($results)) { + // TODO: Detect "deleted" by issuing "hg log"? + $result->setReasonForEmptyResultSet( + DiffusionBrowseResultSet::REASON_IS_NONEXISTENT); + $valid_results = false; + } + + return $result + ->setPaths($results) + ->setIsValidResults($valid_results); + } + + protected function getSVNResult(ConduitAPIRequest $request) { + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $path = $request->getValue('path'); + $commit = $request->getValue('commit'); + $result = $this->getEmptyResultSet(); + + $subpath = $repository->getDetail('svn-subpath'); + if ($subpath && strncmp($subpath, $path, strlen($subpath))) { + // If we have a subpath and the path isn't a child of it, it (almost + // certainly) won't exist since we don't track commits which affect + // it. (Even if it exists, return a consistent result.) + $result->setReasonForEmptyResultSet( + DiffusionBrowseResultSet::REASON_IS_UNTRACKED_PARENT); + return $result; + } + + $conn_r = $repository->establishConnection('r'); + + $parent_path = DiffusionPathIDQuery::getParentPath($path); + $path_query = new DiffusionPathIDQuery( + array( + $path, + $parent_path, + )); + $path_map = $path_query->loadPathIDs(); + + $path_id = $path_map[$path]; + $parent_path_id = $path_map[$parent_path]; + + if (empty($path_id)) { + $result->setReasonForEmptyResultSet( + DiffusionBrowseResultSet::REASON_IS_NONEXISTENT); + return $result; + } + + if ($commit) { + $slice_clause = 'AND svnCommit <= '.(int)$commit; + } else { + $slice_clause = ''; + } + + $index = queryfx_all( + $conn_r, + 'SELECT pathID, max(svnCommit) maxCommit FROM %T WHERE + repositoryID = %d AND parentID = %d + %Q GROUP BY pathID', + PhabricatorRepository::TABLE_FILESYSTEM, + $repository->getID(), + $path_id, + $slice_clause); + + if (!$index) { + if ($path == '/') { + $result->setReasonForEmptyResultSet( + DiffusionBrowseResultSet::REASON_IS_EMPTY); + } else { + + // NOTE: The parent path ID is included so this query can take + // advantage of the table's primary key; it is uniquely determined by + // the pathID but if we don't do the lookup ourselves MySQL doesn't have + // the information it needs to avoid a table scan. + + $reasons = queryfx_all( + $conn_r, + 'SELECT * FROM %T WHERE repositoryID = %d + AND parentID = %d + AND pathID = %d + %Q ORDER BY svnCommit DESC LIMIT 2', + PhabricatorRepository::TABLE_FILESYSTEM, + $repository->getID(), + $parent_path_id, + $path_id, + $slice_clause); + + $reason = reset($reasons); + + if (!$reason) { + $result->setReasonForEmptyResultSet( + DiffusionBrowseResultSet::REASON_IS_NONEXISTENT); + } else { + $file_type = $reason['fileType']; + if (empty($reason['existed'])) { + $result->setReasonForEmptyResultSet( + DiffusionBrowseResultSet::REASON_IS_DELETED); + $result->setDeletedAtCommit($reason['svnCommit']); + if (!empty($reasons[1])) { + $result->setExistedAtCommit($reasons[1]['svnCommit']); + } + } else if ($file_type == DifferentialChangeType::FILE_DIRECTORY) { + $result->setReasonForEmptyResultSet( + DiffusionBrowseResultSet::REASON_IS_EMPTY); + } else { + $result->setReasonForEmptyResultSet( + DiffusionBrowseResultSet::REASON_IS_FILE); + } + } + } + return $result; + } + + $result->setIsValidResults(true); + if ($this->shouldOnlyTestValidity($request)) { + return $result; + } + + $sql = array(); + foreach ($index as $row) { + $sql[] = + '(pathID = '.(int)$row['pathID'].' AND '. + 'svnCommit = '.(int)$row['maxCommit'].')'; + } + + $browse = queryfx_all( + $conn_r, + 'SELECT *, p.path pathName + FROM %T f JOIN %T p ON f.pathID = p.id + WHERE repositoryID = %d + AND parentID = %d + AND existed = 1 + AND (%Q) + ORDER BY pathName', + PhabricatorRepository::TABLE_FILESYSTEM, + PhabricatorRepository::TABLE_PATH, + $repository->getID(), + $path_id, + implode(' OR ', $sql)); + + $loadable_commits = array(); + foreach ($browse as $key => $file) { + // We need to strip out directories because we don't store last-modified + // in the filesystem table. + if ($file['fileType'] != DifferentialChangeType::FILE_DIRECTORY) { + $loadable_commits[] = $file['svnCommit']; + $browse[$key]['hasCommit'] = true; + } + } + + $commits = array(); + $commit_data = array(); + if ($loadable_commits) { + // NOTE: Even though these are integers, use '%Ls' because MySQL doesn't + // use the second part of the key otherwise! + $commits = id(new PhabricatorRepositoryCommit())->loadAllWhere( + 'repositoryID = %d AND commitIdentifier IN (%Ls)', + $repository->getID(), + $loadable_commits); + $commits = mpull($commits, null, 'getCommitIdentifier'); + if ($commits) { + $commit_data = id(new PhabricatorRepositoryCommitData())->loadAllWhere( + 'commitID in (%Ld)', + mpull($commits, 'getID')); + $commit_data = mpull($commit_data, null, 'getCommitID'); + } else { + $commit_data = array(); + } + } + + $path_normal = DiffusionPathIDQuery::normalizePath($path); + + $results = array(); + foreach ($browse as $file) { + + $full_path = $file['pathName']; + $file_path = ltrim(substr($full_path, strlen($path_normal)), '/'); + $full_path = ltrim($full_path, '/'); + + $result_path = new DiffusionRepositoryPath(); + $result_path->setPath($file_path); + $result_path->setFullPath($full_path); +// $result_path->setHash($hash); + $result_path->setFileType($file['fileType']); +// $result_path->setFileSize($size); + + if (!empty($file['hasCommit'])) { + $commit = idx($commits, $file['svnCommit']); + if ($commit) { + $data = idx($commit_data, $commit->getID()); + $result_path->setLastModifiedCommit($commit); + $result_path->setLastCommitData($data); + } + } + + $results[] = $result_path; + } + + if (empty($results)) { + $result->setReasonForEmptyResultSet( + DiffusionBrowseResultSet::REASON_IS_EMPTY); + } + + return $result->setPaths($results); + } + + private function getEmptyResultSet() { + return id(new DiffusionBrowseResultSet()) + ->setPaths(array()) + ->setReasonForEmptyResultSet(null) + ->setIsValidResults(false); + } + + private function shouldOnlyTestValidity(ConduitAPIRequest $request) { + return $request->getValue('needValidityOnly', false); + } + +} diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 450646e046..7b249a0975 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -9,11 +9,16 @@ final class DiffusionBrowseController extends DiffusionController { if ($this->getRequest()->getStr('before')) { $is_file = true; } else if ($this->getRequest()->getStr('grep') == '') { - $browse_query = DiffusionBrowseQuery::newFromDiffusionRequest($drequest); - $browse_query->setViewer($this->getRequest()->getUser()); - $results = $browse_query->loadPaths(); - $reason = $browse_query->getReasonForEmptyResultSet(); - $is_file = ($reason == DiffusionBrowseQuery::REASON_IS_FILE); + $results = DiffusionBrowseResultSet::newFromConduit( + $this->callConduitWithDiffusionRequest( + 'diffusion.browsequery', + array( + 'path' => $drequest->getPath(), + 'commit' => $drequest->getCommit(), + 'renderReadme' => true, + ))); + $reason = $results->getReasonForEmptyResultSet(); + $is_file = ($reason == DiffusionBrowseResultSet::REASON_IS_FILE); } if ($is_file) { @@ -42,17 +47,17 @@ final class DiffusionBrowseController extends DiffusionController { $content[] = $this->renderSearchResults(); } else { - if (!$results) { + if (!$results->isValidResults()) { $empty_result = new DiffusionEmptyResultView(); $empty_result->setDiffusionRequest($drequest); - $empty_result->setBrowseQuery($browse_query); + $empty_result->setDiffusionBrowseResultSet($results); $empty_result->setView($this->getRequest()->getStr('view')); $content[] = $empty_result; } else { $phids = array(); - foreach ($results as $result) { + foreach ($results->getPaths() as $result) { $data = $result->getLastCommitData(); if ($data) { if ($data->getCommitDetail('authorPHID')) { @@ -67,7 +72,7 @@ final class DiffusionBrowseController extends DiffusionController { $browse_table = new DiffusionBrowseTableView(); $browse_table->setDiffusionRequest($drequest); $browse_table->setHandles($handles); - $browse_table->setPaths($results); + $browse_table->setPaths($results->getPaths()); $browse_table->setUser($this->getRequest()->getUser()); $browse_panel = new AphrontPanelView(); @@ -79,7 +84,7 @@ final class DiffusionBrowseController extends DiffusionController { $content[] = $this->buildOpenRevisions(); - $readme_content = $browse_query->renderReadme($results); + $readme_content = $results->getReadmeContent(); if ($readme_content) { $readme_panel = new AphrontPanelView(); $readme_panel->setHeader('README'); @@ -99,7 +104,6 @@ final class DiffusionBrowseController extends DiffusionController { 'view' => 'browse', )); $nav->setCrumbs($crumbs); - return $this->buildApplicationPage( $nav, array( diff --git a/src/applications/diffusion/controller/DiffusionPathCompleteController.php b/src/applications/diffusion/controller/DiffusionPathCompleteController.php index bea6d524f7..fbba21b953 100644 --- a/src/applications/diffusion/controller/DiffusionPathCompleteController.php +++ b/src/applications/diffusion/controller/DiffusionPathCompleteController.php @@ -30,10 +30,16 @@ final class DiffusionPathCompleteController extends DiffusionController { 'repository' => $repository, 'path' => $query_dir, )); + $this->setDiffusionRequest($drequest); - $browse_query = DiffusionBrowseQuery::newFromDiffusionRequest($drequest); - $browse_query->setViewer($request->getUser()); - $paths = $browse_query->loadPaths(); + $browse_results = DiffusionBrowseResultSet::newFromConduit( + $this->callConduitWithDiffusionRequest( + 'diffusion.browsequery', + array( + 'path' => $drequest->getPath(), + 'commit' => $drequest->getCommit(), + ))); + $paths = $browse_results->getPaths(); $output = array(); foreach ($paths as $path) { diff --git a/src/applications/diffusion/controller/DiffusionPathValidateController.php b/src/applications/diffusion/controller/DiffusionPathValidateController.php index 0de54834db..5533599948 100644 --- a/src/applications/diffusion/controller/DiffusionPathValidateController.php +++ b/src/applications/diffusion/controller/DiffusionPathValidateController.php @@ -25,18 +25,24 @@ final class DiffusionPathValidateController extends DiffusionController { 'repository' => $repository, 'path' => $path, )); + $this->setDiffusionRequest($drequest); - $browse_query = DiffusionBrowseQuery::newFromDiffusionRequest($drequest); - $browse_query->setViewer($request->getUser()); - $browse_query->needValidityOnly(true); - $valid = $browse_query->loadPaths(); + $browse_results = DiffusionBrowseResultSet::newFromConduit( + $this->callConduitWithDiffusionRequest( + 'diffusion.browsequery', + array( + 'path' => $drequest->getPath(), + 'commit' => $drequest->getCommit(), + 'needValidityOnly' => true, + ))); + $valid = $browse_results->isValidResults(); if (!$valid) { - switch ($browse_query->getReasonForEmptyResultSet()) { - case DiffusionBrowseQuery::REASON_IS_FILE: + switch ($browse_results->getReasonForEmptyResultSet()) { + case DiffusionBrowseResultSet::REASON_IS_FILE: $valid = true; break; - case DiffusionBrowseQuery::REASON_IS_EMPTY: + case DiffusionBrowseResultSet::REASON_IS_EMPTY: $valid = true; break; } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index 56bcb0a3e3..0d4cefd06d 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -18,9 +18,15 @@ final class DiffusionRepositoryController extends DiffusionController { $history_query->needParents(true); $history = $history_query->loadHistory(); - $browse_query = DiffusionBrowseQuery::newFromDiffusionRequest($drequest); - $browse_query->setViewer($this->getRequest()->getUser()); - $browse_results = $browse_query->loadPaths(); + $browse_results = DiffusionBrowseResultSet::newFromConduit( + $this->callConduitWithDiffusionRequest( + 'diffusion.browsequery', + array( + 'path' => $drequest->getPath(), + 'commit' => $drequest->getCommit(), + 'renderReadme' => true, + ))); + $browse_paths = $browse_results->getPaths(); $phids = array(); @@ -36,7 +42,7 @@ final class DiffusionRepositoryController extends DiffusionController { } } - foreach ($browse_results as $item) { + foreach ($browse_paths as $item) { $data = $item->getLastCommitData(); if ($data) { if ($data->getCommitDetail('authorPHID')) { @@ -82,7 +88,7 @@ final class DiffusionRepositoryController extends DiffusionController { $browse_table = new DiffusionBrowseTableView(); $browse_table->setDiffusionRequest($drequest); $browse_table->setHandles($handles); - $browse_table->setPaths($browse_results); + $browse_table->setPaths($browse_paths); $browse_table->setUser($this->getRequest()->getUser()); $browse_panel = new AphrontPanelView(); @@ -99,7 +105,7 @@ final class DiffusionRepositoryController extends DiffusionController { $content[] = $this->buildBranchListTable($drequest); - $readme = $browse_query->renderReadme($browse_results); + $readme = $browse_results->getReadmeContent(); if ($readme) { $panel = new AphrontPanelView(); $panel->setHeader('README'); diff --git a/src/applications/diffusion/data/DiffusionBrowseResultSet.php b/src/applications/diffusion/data/DiffusionBrowseResultSet.php new file mode 100644 index 0000000000..dce08b1344 --- /dev/null +++ b/src/applications/diffusion/data/DiffusionBrowseResultSet.php @@ -0,0 +1,97 @@ +paths = $paths; + return $this; + } + public function getPaths() { + return $this->paths; + } + + public function setIsValidResults($is_valid) { + $this->isValidResults = $is_valid; + return $this; + } + public function isValidResults() { + return $this->isValidResults; + } + + public function setReasonForEmptyResultSet($reason) { + $this->reasonForEmptyResultSet = $reason; + return $this; + } + public function getReasonForEmptyResultSet() { + return $this->reasonForEmptyResultSet; + } + + public function setExistedAtCommit($existed_at_commit) { + $this->existedAtCommit = $existed_at_commit; + return $this; + } + public function getExistedAtCommit() { + return $this->existedAtCommit; + } + + public function setDeletedAtCommit($deleted_at_commit) { + $this->deletedAtCommit = $deleted_at_commit; + return $this; + } + public function getDeletedAtCommit() { + return $this->deletedAtCommit; + } + + public function setReadmeContent($readme_content) { + $this->readmeContent = $readme_content; + return $this; + } + public function getReadmeContent() { + return $this->readmeContent; + } + + public function toDictionary() { + $paths = $this->getPaths(); + if ($paths) { + $paths = mpull($paths, 'toDictionary'); + } + + return array( + 'paths' => $paths, + 'isValidResults' => $this->isValidResults(), + 'reasonForEmptyResultSet' => $this->getReasonForEmptyResultSet(), + 'existedAtCommit' => $this->getExistedAtCommit(), + 'deletedAtCommit' => $this->getDeletedAtCommit(), + 'readmeContent' => $this->getReadmeContent()); + } + + public static function newFromConduit(array $data) { + $paths = array(); + $path_dicts = $data['paths']; + foreach ($path_dicts as $dict) { + $paths[] = DiffusionRepositoryPath::newFromDictionary($dict); + } + return id(new DiffusionBrowseResultSet()) + ->setPaths($paths) + ->setIsValidResults($data['isValidResults']) + ->setReasonForEmptyResultSet($data['reasonForEmptyResultSet']) + ->setExistedAtCommit($data['existedAtCommit']) + ->setDeletedAtCommit($data['deletedAtCommit']) + ->setReadmeContent($data['readmeContent']); + } +} diff --git a/src/applications/diffusion/data/DiffusionRepositoryPath.php b/src/applications/diffusion/data/DiffusionRepositoryPath.php index b050b75bd5..dca03d9abd 100644 --- a/src/applications/diffusion/data/DiffusionRepositoryPath.php +++ b/src/applications/diffusion/data/DiffusionRepositoryPath.php @@ -21,69 +21,110 @@ final class DiffusionRepositoryPath { return $this->fullPath; } - final public function setPath($path) { + public function setPath($path) { $this->path = $path; return $this; } - final public function getPath() { + public function getPath() { return $this->path; } - final public function setHash($hash) { + public function setHash($hash) { $this->hash = $hash; return $this; } - final public function getHash() { + public function getHash() { return $this->hash; } - final public function setLastModifiedCommit( + public function setLastModifiedCommit( PhabricatorRepositoryCommit $commit) { $this->lastModifiedCommit = $commit; return $this; } - final public function getLastModifiedCommit() { + public function getLastModifiedCommit() { return $this->lastModifiedCommit; } - final public function setLastCommitData( + public function setLastCommitData( PhabricatorRepositoryCommitData $last_commit_data) { $this->lastCommitData = $last_commit_data; return $this; } - final public function getLastCommitData() { + public function getLastCommitData() { return $this->lastCommitData; } - final public function setFileType($file_type) { + public function setFileType($file_type) { $this->fileType = $file_type; return $this; } - final public function getFileType() { + public function getFileType() { return $this->fileType; } - final public function setFileSize($file_size) { + public function setFileSize($file_size) { $this->fileSize = $file_size; return $this; } - final public function getFileSize() { + public function getFileSize() { return $this->fileSize; } - final public function setExternalURI($external_uri) { + public function setExternalURI($external_uri) { $this->externalURI = $external_uri; return $this; } - final public function getExternalURI() { + public function getExternalURI() { return $this->externalURI; } + public function toDictionary() { + $last_modified_commit = $this->getLastModifiedCommit(); + if ($last_modified_commit) { + $last_modified_commit = $last_modified_commit->toDictionary(); + } + $last_commit_data = $this->getLastCommitData(); + if ($last_commit_data) { + $last_commit_data = $last_commit_data->toDictionary(); + } + return array( + 'fullPath' => $this->getFullPath(), + 'path' => $this->getPath(), + 'hash' => $this->getHash(), + 'fileType' => $this->getFileType(), + 'fileSize' => $this->getFileSize(), + 'externalURI' => $this->getExternalURI(), + 'lastModifiedCommit' => $last_modified_commit, + 'lastCommitData' => $last_commit_data, + ); + } + + public static function newFromDictionary(array $dict) { + $path = id(new DiffusionRepositoryPath()) + ->setFullPath($dict['fullPath']) + ->setPath($dict['path']) + ->setHash($dict['hash']) + ->setFileType($dict['fileType']) + ->setFileSize($dict['fileSize']) + ->setExternalURI($dict['externalURI']); + if ($dict['lastModifiedCommit']) { + $last_modified_commit = PhabricatorRepositoryCommit::newFromDictionary( + $dict['lastModifiedCommit']); + $path->setLastModifiedCommit($last_modified_commit); + } + if ($dict['lastCommitData']) { + $last_commit_data = PhabricatorRepositoryCommitData::newFromDictionary( + $dict['lastCommitData']); + $path->setLastCommitData($last_commit_data); + } + return $path; + } } diff --git a/src/applications/diffusion/query/browse/DiffusionBrowseQuery.php b/src/applications/diffusion/query/browse/DiffusionBrowseQuery.php deleted file mode 100644 index 30b8d1e601..0000000000 --- a/src/applications/diffusion/query/browse/DiffusionBrowseQuery.php +++ /dev/null @@ -1,164 +0,0 @@ -viewer = $viewer; - return $this; - } - - public function getViewer() { - return $this->viewer; - } - - const REASON_IS_FILE = 'is-file'; - const REASON_IS_DELETED = 'is-deleted'; - const REASON_IS_NONEXISTENT = 'nonexistent'; - const REASON_BAD_COMMIT = 'bad-commit'; - const REASON_IS_EMPTY = 'empty'; - const REASON_IS_UNTRACKED_PARENT = 'untracked-parent'; - - final private function __construct() { - // - } - - final public static function newFromDiffusionRequest( - DiffusionRequest $request) { - - $repository = $request->getRepository(); - - switch ($repository->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - // TODO: Verify local-path? - $query = new DiffusionGitBrowseQuery(); - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - $query = new DiffusionMercurialBrowseQuery(); - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - $query = new DiffusionSvnBrowseQuery(); - break; - default: - throw new Exception("Unsupported VCS!"); - } - - $query->request = $request; - - return $query; - } - - final protected function getRequest() { - return $this->request; - } - - final public function getReasonForEmptyResultSet() { - return $this->reason; - } - - final public function getExistedAtCommit() { - return $this->existedAtCommit; - } - - final public function getDeletedAtCommit() { - return $this->deletedAtCommit; - } - - final public function loadPaths() { - $this->reason = null; - return $this->executeQuery(); - } - - final public function shouldOnlyTestValidity() { - return $this->validityOnly; - } - - final public function needValidityOnly($need_validity_only) { - $this->validityOnly = $need_validity_only; - return $this; - } - - final public function renderReadme(array $results) { - $drequest = $this->getRequest(); - - $readme = null; - foreach ($results as $result) { - $file_type = $result->getFileType(); - if (($file_type != ArcanistDiffChangeType::FILE_NORMAL) && - ($file_type != ArcanistDiffChangeType::FILE_TEXT)) { - // Skip directories, etc. - continue; - } - - $path = $result->getPath(); - - if (preg_match('/^readme(|\.txt|\.remarkup|\.rainbow)$/i', $path)) { - $readme = $result; - break; - } - } - - if (!$readme) { - return null; - } - - $readme_request = DiffusionRequest::newFromDictionary( - array( - 'repository' => $drequest->getRepository(), - 'commit' => $drequest->getStableCommitName(), - 'path' => $readme->getFullPath(), - )); - - $file_content = DiffusionFileContent::newFromConduit( - DiffusionQuery::callConduitWithDiffusionRequest( - $this->getViewer(), - $readme_request, - 'diffusion.filecontentquery', - array( - 'commit' => $drequest->getStableCommitName(), - 'path' => $readme->getFullPath(), - 'needsBlame' => false, - ))); - $readme_content = $file_content->getCorpus(); - - if (preg_match('/\\.txt$/', $readme->getPath())) { - $readme_content = phutil_escape_html_newlines($readme_content); - - $class = null; - } else if (preg_match('/\\.rainbow$/', $readme->getPath())) { - $highlighter = new PhutilRainbowSyntaxHighlighter(); - $readme_content = $highlighter - ->getHighlightFuture($readme_content) - ->resolve(); - $readme_content = phutil_escape_html_newlines($readme_content); - - require_celerity_resource('syntax-highlighting-css'); - $class = 'remarkup-code'; - } else { - // Markup extensionless files as remarkup so we get links and such. - $engine = PhabricatorMarkupEngine::newDiffusionMarkupEngine(); - $engine->setConfig('viewer', $this->getViewer()); - $readme_content = $engine->markupText($readme_content); - - $class = 'phabricator-remarkup'; - } - - $readme_content = phutil_tag( - 'div', - array( - 'class' => $class, - ), - $readme_content); - - return $readme_content; - } - - abstract protected function executeQuery(); -} diff --git a/src/applications/diffusion/query/browse/DiffusionGitBrowseQuery.php b/src/applications/diffusion/query/browse/DiffusionGitBrowseQuery.php deleted file mode 100644 index 3f6c81c76f..0000000000 --- a/src/applications/diffusion/query/browse/DiffusionGitBrowseQuery.php +++ /dev/null @@ -1,149 +0,0 @@ -getRequest(); - $repository = $drequest->getRepository(); - - $path = $drequest->getPath(); - $commit = $drequest->getCommit(); - - if ($path == '') { - // Fast path to improve the performance of the repository view; we know - // the root is always a tree at any commit and always exists. - $stdout = 'tree'; - } else { - try { - list($stdout) = $repository->execxLocalCommand( - 'cat-file -t %s:%s', - $commit, - $path); - } catch (CommandException $e) { - $stderr = $e->getStdErr(); - if (preg_match('/^fatal: Not a valid object name/', $stderr)) { - // Grab two logs, since the first one is when the object was deleted. - list($stdout) = $repository->execxLocalCommand( - 'log -n2 --format="%%H" %s -- %s', - $commit, - $path); - $stdout = trim($stdout); - if ($stdout) { - $commits = explode("\n", $stdout); - $this->reason = self::REASON_IS_DELETED; - $this->deletedAtCommit = idx($commits, 0); - $this->existedAtCommit = idx($commits, 1); - return array(); - } - - $this->reason = self::REASON_IS_NONEXISTENT; - return array(); - } else { - throw $e; - } - } - } - - if (trim($stdout) == 'blob') { - $this->reason = self::REASON_IS_FILE; - return array(); - } - - if ($this->shouldOnlyTestValidity()) { - return true; - } - - list($stdout) = $repository->execxLocalCommand( - 'ls-tree -z -l %s:%s', - $commit, - $path); - - $submodules = array(); - - if (strlen($path)) { - $prefix = rtrim($path, '/').'/'; - } else { - $prefix = ''; - } - - $results = array(); - foreach (explode("\0", rtrim($stdout)) as $line) { - - // NOTE: Limit to 5 components so we parse filenames with spaces in them - // correctly. - list($mode, $type, $hash, $size, $name) = preg_split('/\s+/', $line, 5); - - $result = new DiffusionRepositoryPath(); - - if ($type == 'tree') { - $file_type = DifferentialChangeType::FILE_DIRECTORY; - } else if ($type == 'commit') { - $file_type = DifferentialChangeType::FILE_SUBMODULE; - $submodules[] = $result; - } else { - $mode = intval($mode, 8); - if (($mode & 0120000) == 0120000) { - $file_type = DifferentialChangeType::FILE_SYMLINK; - } else { - $file_type = DifferentialChangeType::FILE_NORMAL; - } - } - - $result->setFullPath($prefix.$name); - $result->setPath($name); - $result->setHash($hash); - $result->setFileType($file_type); - $result->setFileSize($size); - - $results[] = $result; - } - - // If we identified submodules, lookup the module info at this commit to - // find their source URIs. - - if ($submodules) { - - // NOTE: We need to read the file out of git and write it to a temporary - // location because "git config -f" doesn't accept a "commit:path"-style - // argument. - - // NOTE: This file may not exist, e.g. because the commit author removed - // it when they added the submodule. See T1448. If it's not present, just - // show the submodule without enriching it. If ".gitmodules" was removed - // it seems to partially break submodules, but the repository as a whole - // continues to work fine and we've seen at least two cases of this in - // the wild. - - list($err, $contents) = $repository->execLocalCommand( - 'cat-file blob %s:.gitmodules', - $commit); - - if (!$err) { - $tmp = new TempFile(); - Filesystem::writeFile($tmp, $contents); - list($module_info) = $repository->execxLocalCommand( - 'config -l -f %s', - $tmp); - - $dict = array(); - $lines = explode("\n", trim($module_info)); - foreach ($lines as $line) { - list($key, $value) = explode('=', $line, 2); - $parts = explode('.', $key); - $dict[$key] = $value; - } - - foreach ($submodules as $path) { - $full_path = $path->getFullPath(); - $key = 'submodule.'.$full_path.'.url'; - if (isset($dict[$key])) { - $path->setExternalURI($dict[$key]); - } - } - } - } - - return $results; - } - -} diff --git a/src/applications/diffusion/query/browse/DiffusionMercurialBrowseQuery.php b/src/applications/diffusion/query/browse/DiffusionMercurialBrowseQuery.php deleted file mode 100644 index 990169a3e7..0000000000 --- a/src/applications/diffusion/query/browse/DiffusionMercurialBrowseQuery.php +++ /dev/null @@ -1,80 +0,0 @@ -getRequest(); - $repository = $drequest->getRepository(); - - $path = $drequest->getPath(); - $commit = $drequest->getStableCommitName(); - - // TODO: This is a really really awful mess but Mercurial doesn't offer - // an equivalent of "git ls-files -- directory". If it's any comfort, this - // is what "hgweb" does too, see: - // - // http://selenic.com/repo/hg/file/91dc8878f888/mercurial/hgweb/webcommands.py#l320 - // - // derp derp derp derp - // - // Anyway, figure out what's in this path by applying massive amounts - // of brute force. - - list($entire_manifest) = $repository->execxLocalCommand( - 'manifest --rev %s', - $commit); - $entire_manifest = explode("\n", $entire_manifest); - - $results = array(); - - $match_against = trim($path, '/'); - $match_len = strlen($match_against); - - // For the root, don't trim. For other paths, trim the "/" after we match. - // We need this because Mercurial's canonical paths have no leading "/", - // but ours do. - $trim_len = $match_len ? $match_len + 1 : 0; - - foreach ($entire_manifest as $path) { - if (strncmp($path, $match_against, $match_len)) { - continue; - } - if (!strlen($path)) { - continue; - } - $remainder = substr($path, $trim_len); - if (!strlen($remainder)) { - // There is a file with this exact name in the manifest, so clearly - // it's a file. - $this->reason = self::REASON_IS_FILE; - return array(); - } - $parts = explode('/', $remainder); - if (count($parts) == 1) { - $type = DifferentialChangeType::FILE_NORMAL; - } else { - $type = DifferentialChangeType::FILE_DIRECTORY; - } - $results[reset($parts)] = $type; - } - - foreach ($results as $key => $type) { - $result = new DiffusionRepositoryPath(); - $result->setPath($key); - $result->setFileType($type); - $result->setFullPath(ltrim($match_against.'/', '/').$key); - - $results[$key] = $result; - } - - if (empty($results)) { - // TODO: Detect "deleted" by issuing "hg log"? - - $this->reason = self::REASON_IS_NONEXISTENT; - } - - - return $results; - } - -} diff --git a/src/applications/diffusion/query/browse/DiffusionSvnBrowseQuery.php b/src/applications/diffusion/query/browse/DiffusionSvnBrowseQuery.php deleted file mode 100644 index 074aaf92d0..0000000000 --- a/src/applications/diffusion/query/browse/DiffusionSvnBrowseQuery.php +++ /dev/null @@ -1,190 +0,0 @@ -getRequest(); - $repository = $drequest->getRepository(); - - $path = $drequest->getPath(); - $commit = $drequest->getCommit(); - - $subpath = $repository->getDetail('svn-subpath'); - if ($subpath && strncmp($subpath, $path, strlen($subpath))) { - // If we have a subpath and the path isn't a child of it, it (almost - // certainly) won't exist since we don't track commits which affect - // it. (Even if it exists, return a consistent result.) - $this->reason = self::REASON_IS_UNTRACKED_PARENT; - return array(); - } - - $conn_r = $repository->establishConnection('r'); - - $parent_path = DiffusionPathIDQuery::getParentPath($path); - $path_query = new DiffusionPathIDQuery( - array( - $path, - $parent_path, - )); - $path_map = $path_query->loadPathIDs(); - - $path_id = $path_map[$path]; - $parent_path_id = $path_map[$parent_path]; - - if (empty($path_id)) { - $this->reason = self::REASON_IS_NONEXISTENT; - return array(); - } - - if ($commit) { - $slice_clause = 'AND svnCommit <= '.(int)$commit; - } else { - $slice_clause = ''; - } - - $index = queryfx_all( - $conn_r, - 'SELECT pathID, max(svnCommit) maxCommit FROM %T WHERE - repositoryID = %d AND parentID = %d - %Q GROUP BY pathID', - PhabricatorRepository::TABLE_FILESYSTEM, - $repository->getID(), - $path_id, - $slice_clause); - - if (!$index) { - if ($path == '/') { - $this->reason = self::REASON_IS_EMPTY; - } else { - - // NOTE: The parent path ID is included so this query can take - // advantage of the table's primary key; it is uniquely determined by - // the pathID but if we don't do the lookup ourselves MySQL doesn't have - // the information it needs to avoid a table scan. - - $reasons = queryfx_all( - $conn_r, - 'SELECT * FROM %T WHERE repositoryID = %d - AND parentID = %d - AND pathID = %d - %Q ORDER BY svnCommit DESC LIMIT 2', - PhabricatorRepository::TABLE_FILESYSTEM, - $repository->getID(), - $parent_path_id, - $path_id, - $slice_clause); - - $reason = reset($reasons); - - if (!$reason) { - $this->reason = self::REASON_IS_NONEXISTENT; - } else { - $file_type = $reason['fileType']; - if (empty($reason['existed'])) { - $this->reason = self::REASON_IS_DELETED; - $this->deletedAtCommit = $reason['svnCommit']; - if (!empty($reasons[1])) { - $this->existedAtCommit = $reasons[1]['svnCommit']; - } - } else if ($file_type == DifferentialChangeType::FILE_DIRECTORY) { - $this->reason = self::REASON_IS_EMPTY; - } else { - $this->reason = self::REASON_IS_FILE; - } - } - } - return array(); - } - - if ($this->shouldOnlyTestValidity()) { - return true; - } - - $sql = array(); - foreach ($index as $row) { - $sql[] = - '(pathID = '.(int)$row['pathID'].' AND '. - 'svnCommit = '.(int)$row['maxCommit'].')'; - } - - $browse = queryfx_all( - $conn_r, - 'SELECT *, p.path pathName - FROM %T f JOIN %T p ON f.pathID = p.id - WHERE repositoryID = %d - AND parentID = %d - AND existed = 1 - AND (%Q) - ORDER BY pathName', - PhabricatorRepository::TABLE_FILESYSTEM, - PhabricatorRepository::TABLE_PATH, - $repository->getID(), - $path_id, - implode(' OR ', $sql)); - - $loadable_commits = array(); - foreach ($browse as $key => $file) { - // We need to strip out directories because we don't store last-modified - // in the filesystem table. - if ($file['fileType'] != DifferentialChangeType::FILE_DIRECTORY) { - $loadable_commits[] = $file['svnCommit']; - $browse[$key]['hasCommit'] = true; - } - } - - $commits = array(); - $commit_data = array(); - if ($loadable_commits) { - // NOTE: Even though these are integers, use '%Ls' because MySQL doesn't - // use the second part of the key otherwise! - $commits = id(new PhabricatorRepositoryCommit())->loadAllWhere( - 'repositoryID = %d AND commitIdentifier IN (%Ls)', - $repository->getID(), - $loadable_commits); - $commits = mpull($commits, null, 'getCommitIdentifier'); - if ($commits) { - $commit_data = id(new PhabricatorRepositoryCommitData())->loadAllWhere( - 'commitID in (%Ld)', - mpull($commits, 'getID')); - $commit_data = mpull($commit_data, null, 'getCommitID'); - } else { - $commit_data = array(); - } - } - - $path_normal = DiffusionPathIDQuery::normalizePath($path); - - $results = array(); - foreach ($browse as $file) { - - $full_path = $file['pathName']; - $file_path = ltrim(substr($full_path, strlen($path_normal)), '/'); - $full_path = ltrim($full_path, '/'); - - $result = new DiffusionRepositoryPath(); - $result->setPath($file_path); - $result->setFullPath($full_path); -// $result->setHash($hash); - $result->setFileType($file['fileType']); -// $result->setFileSize($size); - - if (!empty($file['hasCommit'])) { - $commit = idx($commits, $file['svnCommit']); - if ($commit) { - $data = idx($commit_data, $commit->getID()); - $result->setLastModifiedCommit($commit); - $result->setLastCommitData($data); - } - } - - $results[] = $result; - } - - if (empty($results)) { - $this->reason = self::REASON_IS_EMPTY; - } - - return $results; - } - -} diff --git a/src/applications/diffusion/view/DiffusionEmptyResultView.php b/src/applications/diffusion/view/DiffusionEmptyResultView.php index fc4138ce4c..a2d9bd44cf 100644 --- a/src/applications/diffusion/view/DiffusionEmptyResultView.php +++ b/src/applications/diffusion/view/DiffusionEmptyResultView.php @@ -2,11 +2,11 @@ final class DiffusionEmptyResultView extends DiffusionView { - private $browseQuery; + private $browseResultSet; private $view; - public function setBrowseQuery($browse_query) { - $this->browseQuery = $browse_query; + public function setDiffusionBrowseResultSet(DiffusionBrowseResultSet $set) { + $this->browseResultSet = $set; return $this; } @@ -26,22 +26,23 @@ final class DiffusionEmptyResultView extends DiffusionView { $commit = 'HEAD'; } - switch ($this->browseQuery->getReasonForEmptyResultSet()) { - case DiffusionBrowseQuery::REASON_IS_NONEXISTENT: + $reason = $this->browseResultSet->getReasonForEmptyResultSet(); + switch ($reason) { + case DiffusionBrowseResultSet::REASON_IS_NONEXISTENT: $title = 'Path Does Not Exist'; // TODO: Under git, this error message should be more specific. It // may exist on some other branch. $body = "This path does not exist anywhere."; $severity = AphrontErrorView::SEVERITY_ERROR; break; - case DiffusionBrowseQuery::REASON_IS_EMPTY: + case DiffusionBrowseResultSet::REASON_IS_EMPTY: $title = 'Empty Directory'; $body = "This path was an empty directory at {$commit}.\n"; $severity = AphrontErrorView::SEVERITY_NOTICE; break; - case DiffusionBrowseQuery::REASON_IS_DELETED: - $deleted = $this->browseQuery->getDeletedAtCommit(); - $existed = $this->browseQuery->getExistedAtCommit(); + case DiffusionBrowseResultSet::REASON_IS_DELETED: + $deleted = $this->browseResultSet->getDeletedAtCommit(); + $existed = $this->browseResultSet->getExistedAtCommit(); $browse = $this->linkBrowse( $drequest->getPath(), @@ -61,7 +62,7 @@ final class DiffusionEmptyResultView extends DiffusionView { "r{$callsign}{$existed}"); $severity = AphrontErrorView::SEVERITY_WARNING; break; - case DiffusionBrowseQuery::REASON_IS_UNTRACKED_PARENT: + case DiffusionBrowseResultSet::REASON_IS_UNTRACKED_PARENT: $subdir = $drequest->getRepository()->getDetail('svn-subpath'); $title = 'Directory Not Tracked'; $body = @@ -72,7 +73,7 @@ final class DiffusionEmptyResultView extends DiffusionView { $severity = AphrontErrorView::SEVERITY_WARNING; break; default: - throw new Exception("Unknown failure reason!"); + throw new Exception("Unknown failure reason: $reason"); } $error_view = new AphrontErrorView(); diff --git a/src/applications/owners/storage/PhabricatorOwnersPackage.php b/src/applications/owners/storage/PhabricatorOwnersPackage.php index b2e575f4ec..f625aa6355 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackage.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackage.php @@ -312,18 +312,23 @@ final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO 'repository' => $repository, 'path' => $path, )); - $query = DiffusionBrowseQuery::newFromDiffusionRequest($drequest); - $query->setViewer($this->getActor()); - $query->needValidityOnly(true); - $valid = $query->loadPaths(); + $results = DiffusionBrowseResultSet::newFromConduit( + DiffusionQuery::callConduitWithDiffusionRequest( + $this->getActor(), + $drequest, + 'diffusion.browsequery', + array( + 'path' => $path, + 'needValidityOnly' => true))); + $valid = $results->isValidResults(); $is_directory = true; if (!$valid) { - switch ($query->getReasonForEmptyResultSet()) { - case DiffusionBrowseQuery::REASON_IS_FILE: + switch ($results->getReasonForEmptyResultSet()) { + case DiffusionBrowseResultSet::REASON_IS_FILE: $valid = true; $is_directory = false; break; - case DiffusionBrowseQuery::REASON_IS_EMPTY: + case DiffusionBrowseResultSet::REASON_IS_EMPTY: $valid = true; break; } diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index ff9d2367ea..e33806a97e 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -180,4 +180,27 @@ final class PhabricatorRepositoryCommit ); } +/* -( Stuff for serialization )---------------------------------------------- */ + + /** + * NOTE: this is not a complete serialization; only the 'protected' fields are + * involved. This is due to ease of (ab)using the Lisk abstraction to get this + * done, as well as complexity of the other fields. + */ + public function toDictionary() { + return array( + 'repositoryID' => $this->getRepositoryID(), + 'phid' => $this->getPHID(), + 'commitIdentifier' => $this->getCommitIdentifier(), + 'epoch' => $this->getEpoch(), + 'mailKey' => $this->getMailKey(), + 'authorPHID' => $this->getAuthorPHID(), + 'auditStatus' => $this->getAuditStatus(), + 'summary' => $this->getSummary()); + } + + public static function newFromDictionary(array $dict) { + return id(new PhabricatorRepositoryCommit()) + ->loadFromArray($dict); + } } diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommitData.php b/src/applications/repository/storage/PhabricatorRepositoryCommitData.php index c8821f0be7..4db17bc10a 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommitData.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommitData.php @@ -41,4 +41,18 @@ final class PhabricatorRepositoryCommitData extends PhabricatorRepositoryDAO { return $this; } + public function toDictionary() { + return array( + 'commitID' => $this->commitID, + 'authorName' => $this->authorName, + 'commitMessage' => $this->commitMessage, + 'commitDetails' => json_encode($this->commitDetails), + ); + } + + public static function newFromDictionary(array $dict) { + return id(new PhabricatorRepositoryCommitData()) + ->loadFromArray($dict); + } + }