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; }
protected function executeQuery() { $drequest = $this->getRequest(); $repository = $drequest->getRepository(); $path = $drequest->getPath(); $commit = $drequest->getCommit(); $local_path = $repository->getDetail('local-path'); 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) = execx("(cd %s && git cat-file -t %s:%s)", $local_path, $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) = execx('(cd %s && git log -n2 --format="%%H" %s -- %s)', $local_path, $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) = execx("(cd %s && git ls-tree -l %s:%s)", $local_path, $commit, $path); $results = array(); foreach (explode("\n", rtrim($stdout)) as $line) { list($mode, $type, $hash, $size, $name) = preg_split('/\\s+/', $line); if ($type == 'tree') { $file_type = DifferentialChangeType::FILE_DIRECTORY; } else { $file_type = DifferentialChangeType::FILE_NORMAL; } $result = new DiffusionRepositoryPath(); $result->setPath($name); $result->setHash($hash); $result->setFileType($file_type); $result->setFileSize($size); $results[] = $result; } return $results; }
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']); }
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[] = '(' . (int) $row['pathID'] . ', ' . (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 (pathID, svnCommit) in (%Q) ORDER BY pathName', PhabricatorRepository::TABLE_FILESYSTEM, PhabricatorRepository::TABLE_PATH, $repository->getID(), $path_id, implode(', ', $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; }
protected function getResult(ConduitAPIRequest $request) { $drequest = $this->getDiffusionRequest(); $path_dicts = $request->getValue('paths', array()); $paths = array(); foreach ($path_dicts as $dict) { $paths[] = DiffusionRepositoryPath::newFromDictionary($dict); } $best = -1; $readme = ''; $best_render_type = 'plain'; foreach ($paths as $result_path) { $file_type = $result_path->getFileType(); if ($file_type != ArcanistDiffChangeType::FILE_NORMAL && $file_type != ArcanistDiffChangeType::FILE_TEXT) { // Skip directories, etc. continue; } $path = strtolower($result_path->getPath()); if ($path === 'readme') { $path .= '.remarkup'; } if (strncmp($path, 'readme.', 7) !== 0) { continue; } $priority = 0; switch (substr($path, 7)) { case 'remarkup': $priority = 100; $render_type = 'remarkup'; break; case 'rainbow': $priority = 90; $render_type = 'rainbow'; break; case 'md': $priority = 50; $render_type = 'remarkup'; break; case 'txt': $priority = 10; $render_type = 'plain'; break; default: $priority = 0; $render_type = 'plain'; break; } if ($priority > $best) { $best = $priority; $readme = $result_path; $best_render_type = $render_type; } } if (!$readme) { return ''; } $readme_request = DiffusionRequest::newFromDictionary(array('user' => $request->getUser(), 'repository' => $drequest->getRepository(), 'commit' => $drequest->getStableCommit(), 'path' => $readme->getFullPath())); $file_content = DiffusionFileContent::newFromConduit(DiffusionQuery::callConduitWithDiffusionRequest($request->getUser(), $readme_request, 'diffusion.filecontentquery', array('commit' => $drequest->getStableCommit(), 'path' => $readme->getFullPath(), 'needsBlame' => false))); $readme_content = $file_content->getCorpus(); switch ($best_render_type) { case 'plain': $readme_content = phutil_escape_html_newlines($readme_content); $class = null; break; case 'rainbow': $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'; break; case 'remarkup': // TODO: This is sketchy, but make sure we hit the markup cache. $markup_object = id(new PhabricatorMarkupOneOff())->setEngineRuleset('diffusion-readme')->setContent($readme_content); $markup_field = 'default'; $readme_content = id(new PhabricatorMarkupEngine())->setViewer($request->getUser())->addObject($markup_object, $markup_field)->process()->getOutput($markup_object, $markup_field); $engine = $markup_object->newMarkupEngine($markup_field); $toc = PhutilRemarkupHeaderBlockRule::renderTableOfContents($engine); if ($toc) { $toc = phutil_tag_div('phabricator-remarkup-toc', array(phutil_tag_div('phabricator-remarkup-toc-header', pht('Table of Contents')), $toc)); $readme_content = array($toc, $readme_content); } $class = 'phabricator-remarkup'; break; } $readme_content = phutil_tag('div', array('class' => $class), $readme_content); return $readme_content; }
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(); $results = array(); foreach (explode("", 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($path . $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; }
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(); $results = array(); foreach (explode("", 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($path . $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) { list($module_info) = $repository->execxLocalCommand('show %s:.gitmodules', $commit); $module_info = parse_ini_string($module_info, $process_sections = true, $scanner_mode = INI_SCANNER_RAW); foreach ($submodules as $path) { foreach ($module_info as $section) { if (empty($section['path']) || empty($section['url'])) { continue; } if ($section['path'] == $path->getFullPath()) { $path->setExternalURI($section['url']); } } } } return $results; }