1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-23 05:50:55 +01:00

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
This commit is contained in:
Bob Trahan 2013-05-10 11:02:58 -07:00
parent 5c2715a13b
commit a42851501f
16 changed files with 812 additions and 649 deletions

View file

@ -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',

View file

@ -0,0 +1,547 @@
<?php
/**
* @group conduit
*/
final class ConduitAPI_diffusion_browsequery_Method
extends ConduitAPI_diffusion_abstractquery_Method {
public function getMethodDescription() {
return
'File(s) information for a repository at an (optional) path and '.
'(optional) commit.';
}
public function defineReturnType() {
return 'array';
}
protected function defineCustomParamTypes() {
return array(
'path' => '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);
}
}

View file

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

View file

@ -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) {

View file

@ -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;
}

View file

@ -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');

View file

@ -0,0 +1,97 @@
<?php
final class DiffusionBrowseResultSet {
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';
private $paths;
private $isValidResults;
private $reasonForEmptyResultSet;
private $existedAtCommit;
private $deletedAtCommit;
private $readmeContent;
public function setPaths(array $paths) {
assert_instances_of($paths, 'DiffusionRepositoryPath');
$this->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']);
}
}

View file

@ -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;
}
}

View file

@ -1,164 +0,0 @@
<?php
abstract class DiffusionBrowseQuery {
private $request;
protected $reason;
protected $existedAtCommit;
protected $deletedAtCommit;
protected $validityOnly;
private $viewer;
public function setViewer(PhabricatorUser $viewer) {
$this->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() {
// <private>
}
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();
}

View file

@ -1,149 +0,0 @@
<?php
final class DiffusionGitBrowseQuery extends DiffusionBrowseQuery {
protected function executeQuery() {
$drequest = $this->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;
}
}

View file

@ -1,80 +0,0 @@
<?php
final class DiffusionMercurialBrowseQuery extends DiffusionBrowseQuery {
protected function executeQuery() {
$drequest = $this->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;
}
}

View file

@ -1,190 +0,0 @@
<?php
final class DiffusionSvnBrowseQuery extends DiffusionBrowseQuery {
protected function executeQuery() {
$drequest = $this->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;
}
}

View file

@ -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();

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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);
}
}