diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -533,12 +533,8 @@ 'DiffusionBranchTableController' => 'applications/diffusion/controller/DiffusionBranchTableController.php', 'DiffusionBranchTableView' => 'applications/diffusion/view/DiffusionBranchTableView.php', 'DiffusionBrowseController' => 'applications/diffusion/controller/DiffusionBrowseController.php', - 'DiffusionBrowseDirectoryController' => 'applications/diffusion/controller/DiffusionBrowseDirectoryController.php', - 'DiffusionBrowseFileController' => 'applications/diffusion/controller/DiffusionBrowseFileController.php', - 'DiffusionBrowseMainController' => 'applications/diffusion/controller/DiffusionBrowseMainController.php', 'DiffusionBrowseQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php', 'DiffusionBrowseResultSet' => 'applications/diffusion/data/DiffusionBrowseResultSet.php', - 'DiffusionBrowseSearchController' => 'applications/diffusion/controller/DiffusionBrowseSearchController.php', 'DiffusionBrowseTableView' => 'applications/diffusion/view/DiffusionBrowseTableView.php', 'DiffusionCachedResolveRefsQuery' => 'applications/diffusion/query/DiffusionCachedResolveRefsQuery.php', 'DiffusionChangeController' => 'applications/diffusion/controller/DiffusionChangeController.php', @@ -4501,12 +4497,8 @@ 'DiffusionBranchTableController' => 'DiffusionController', 'DiffusionBranchTableView' => 'DiffusionView', 'DiffusionBrowseController' => 'DiffusionController', - 'DiffusionBrowseDirectoryController' => 'DiffusionBrowseController', - 'DiffusionBrowseFileController' => 'DiffusionBrowseController', - 'DiffusionBrowseMainController' => 'DiffusionBrowseController', 'DiffusionBrowseQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionBrowseResultSet' => 'Phobject', - 'DiffusionBrowseSearchController' => 'DiffusionBrowseController', 'DiffusionBrowseTableView' => 'DiffusionView', 'DiffusionCachedResolveRefsQuery' => 'DiffusionLowLevelQuery', 'DiffusionChangeController' => 'DiffusionController', diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -70,7 +70,7 @@ 'repository/(?P.*)' => 'DiffusionRepositoryController', 'change/(?P.*)' => 'DiffusionChangeController', 'history/(?P.*)' => 'DiffusionHistoryController', - 'browse/(?P.*)' => 'DiffusionBrowseMainController', + 'browse/(?P.*)' => 'DiffusionBrowseController', 'lastmodified/(?P.*)' => 'DiffusionLastModifiedController', 'diff/' => 'DiffusionDiffController', 'tags/(?P.*)' => 'DiffusionTagListController', diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -1,11 +1,1485 @@ loadDiffusionContext(); + if ($response) { + return $response; + } + + $drequest = $this->getDiffusionRequest(); + + // Figure out if we're browsing a directory, a file, or a search result + // list. + + $grep = $request->getStr('grep'); + $find = $request->getStr('find'); + if (strlen($grep) || strlen($find)) { + return $this->browseSearch(); + } + + $results = DiffusionBrowseResultSet::newFromConduit( + $this->callConduitWithDiffusionRequest( + 'diffusion.browsequery', + array( + 'path' => $drequest->getPath(), + 'commit' => $drequest->getStableCommit(), + ))); + $reason = $results->getReasonForEmptyResultSet(); + $is_file = ($reason == DiffusionBrowseResultSet::REASON_IS_FILE); + + if ($is_file) { + return $this->browseFile($results); + } else { + return $this->browseDirectory($results); + } + } + + private function browseSearch() { + $drequest = $this->getDiffusionRequest(); + + $actions = $this->buildActionView($drequest); + $properties = $this->buildPropertyView($drequest, $actions); + + $object_box = id(new PHUIObjectBoxView()) + ->setHeader($this->buildHeaderView($drequest)) + ->addPropertyList($properties); + + $content = array(); + + $content[] = $object_box; + $content[] = $this->renderSearchForm($collapsed = false); + $content[] = $this->renderSearchResults(); + + $crumbs = $this->buildCrumbs( + array( + 'branch' => true, + 'path' => true, + 'view' => 'browse', + )); + + return $this->newPage() + ->setTitle( + array( + nonempty(basename($drequest->getPath()), '/'), + $drequest->getRepository()->getDisplayName(), + )) + ->setCrumbs($crumbs) + ->appendChild($content); + } + + private function browseFile() { + $viewer = $this->getViewer(); + $request = $this->getRequest(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $before = $request->getStr('before'); + if ($before) { + return $this->buildBeforeResponse($before); + } + + $path = $drequest->getPath(); + + $preferences = $viewer->loadPreferences(); + + $show_blame = $request->getBool( + 'blame', + $preferences->getPreference( + PhabricatorUserPreferences::PREFERENCE_DIFFUSION_BLAME, + false)); + $show_color = $request->getBool( + 'color', + $preferences->getPreference( + PhabricatorUserPreferences::PREFERENCE_DIFFUSION_COLOR, + true)); + + $view = $request->getStr('view'); + if ($request->isFormPost() && $view != 'raw' && $viewer->isLoggedIn()) { + $preferences->setPreference( + PhabricatorUserPreferences::PREFERENCE_DIFFUSION_BLAME, + $show_blame); + $preferences->setPreference( + PhabricatorUserPreferences::PREFERENCE_DIFFUSION_COLOR, + $show_color); + $preferences->save(); + + $uri = $request->getRequestURI() + ->alter('blame', null) + ->alter('color', null); + + return id(new AphrontRedirectResponse())->setURI($uri); + } + + // We need the blame information if blame is on and we're building plain + // text, or blame is on and this is an Ajax request. If blame is on and + // this is a colorized request, we don't show blame at first (we ajax it + // in afterward) so we don't need to query for it. + $needs_blame = ($show_blame && !$show_color) || + ($show_blame && $request->isAjax()); + + $params = array( + 'commit' => $drequest->getCommit(), + 'path' => $drequest->getPath(), + 'needsBlame' => $needs_blame, + ); + + $byte_limit = null; + if ($view !== 'raw') { + $byte_limit = PhabricatorFileStorageEngine::getChunkThreshold(); + $time_limit = 10; + + $params += array( + 'timeout' => $time_limit, + 'byteLimit' => $byte_limit, + ); + } + + $file_content = DiffusionFileContent::newFromConduit( + $this->callConduitWithDiffusionRequest( + 'diffusion.filecontentquery', + $params)); + $data = $file_content->getCorpus(); + + if ($view === 'raw') { + return $this->buildRawResponse($path, $data); + } + + $this->loadLintMessages(); + $this->coverage = $drequest->loadCoverage(); + + if ($byte_limit && (strlen($data) == $byte_limit)) { + $corpus = $this->buildErrorCorpus( + pht( + 'This file is larger than %s byte(s), and too large to display '. + 'in the web UI.', + $byte_limit)); + } else if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) { + $file = $this->loadFileForData($path, $data); + $file_uri = $file->getBestURI(); + + if ($file->isViewableImage()) { + $corpus = $this->buildImageCorpus($file_uri); + } else { + $corpus = $this->buildBinaryCorpus($file_uri, $data); + } + } else { + // Build the content of the file. + $corpus = $this->buildCorpus( + $show_blame, + $show_color, + $file_content, + $needs_blame, + $drequest, + $path, + $data); + } + + if ($request->isAjax()) { + return id(new AphrontAjaxResponse())->setContent($corpus); + } + + require_celerity_resource('diffusion-source-css'); + + // Render the page. + $view = $this->buildActionView($drequest); + $action_list = $this->enrichActionView( + $view, + $drequest, + $show_blame, + $show_color); + + $properties = $this->buildPropertyView($drequest, $action_list); + $object_box = id(new PHUIObjectBoxView()) + ->setHeader($this->buildHeaderView($drequest)) + ->addPropertyList($properties); + + $content = array(); + $content[] = $object_box; + + $follow = $request->getStr('follow'); + if ($follow) { + $notice = new PHUIInfoView(); + $notice->setSeverity(PHUIInfoView::SEVERITY_WARNING); + $notice->setTitle(pht('Unable to Continue')); + switch ($follow) { + case 'first': + $notice->appendChild( + pht( + 'Unable to continue tracing the history of this file because '. + 'this commit is the first commit in the repository.')); + break; + case 'created': + $notice->appendChild( + pht( + 'Unable to continue tracing the history of this file because '. + 'this commit created the file.')); + break; + } + $content[] = $notice; + } + + $renamed = $request->getStr('renamed'); + if ($renamed) { + $notice = new PHUIInfoView(); + $notice->setSeverity(PHUIInfoView::SEVERITY_NOTICE); + $notice->setTitle(pht('File Renamed')); + $notice->appendChild( + pht( + 'File history passes through a rename from "%s" to "%s".', + $drequest->getPath(), + $renamed)); + $content[] = $notice; + } + + $content[] = $corpus; + $content[] = $this->buildOpenRevisions(); + + $crumbs = $this->buildCrumbs( + array( + 'branch' => true, + 'path' => true, + 'view' => 'browse', + )); + + $basename = basename($this->getDiffusionRequest()->getPath()); + + return $this->newPage() + ->setTitle( + array( + $basename, + $repository->getDisplayName(), + )) + ->setCrumbs($crumbs) + ->appendChild($content); + } + + public function browseDirectory(DiffusionBrowseResultSet $results) { + $request = $this->getRequest(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $reason = $results->getReasonForEmptyResultSet(); + + $content = array(); + $actions = $this->buildActionView($drequest); + $properties = $this->buildPropertyView($drequest, $actions); + + $object_box = id(new PHUIObjectBoxView()) + ->setHeader($this->buildHeaderView($drequest)) + ->addPropertyList($properties); + + $content[] = $object_box; + $content[] = $this->renderSearchForm($collapsed = true); + + if (!$results->isValidResults()) { + $empty_result = new DiffusionEmptyResultView(); + $empty_result->setDiffusionRequest($drequest); + $empty_result->setDiffusionBrowseResultSet($results); + $empty_result->setView($request->getStr('view')); + $content[] = $empty_result; + } else { + $phids = array(); + foreach ($results->getPaths() as $result) { + $data = $result->getLastCommitData(); + if ($data) { + if ($data->getCommitDetail('authorPHID')) { + $phids[$data->getCommitDetail('authorPHID')] = true; + } + } + } + + $phids = array_keys($phids); + $handles = $this->loadViewerHandles($phids); + + $browse_table = new DiffusionBrowseTableView(); + $browse_table->setDiffusionRequest($drequest); + $browse_table->setHandles($handles); + $browse_table->setPaths($results->getPaths()); + $browse_table->setUser($request->getUser()); + + $browse_panel = new PHUIObjectBoxView(); + $browse_panel->setHeaderText($drequest->getPath(), '/'); + $browse_panel->setTable($browse_table); + + $content[] = $browse_panel; + } + + $content[] = $this->buildOpenRevisions(); + + + $readme_path = $results->getReadmePath(); + if ($readme_path) { + $readme_content = $this->callConduitWithDiffusionRequest( + 'diffusion.filecontentquery', + array( + 'path' => $readme_path, + 'commit' => $drequest->getStableCommit(), + )); + if ($readme_content) { + $content[] = id(new DiffusionReadmeView()) + ->setUser($this->getViewer()) + ->setPath($readme_path) + ->setContent($readme_content['corpus']); + } + } + + $crumbs = $this->buildCrumbs( + array( + 'branch' => true, + 'path' => true, + 'view' => 'browse', + )); + + return $this->newPage() + ->setTitle( + array( + nonempty(basename($drequest->getPath()), '/'), + $repository->getDisplayName(), + )) + ->setCrumbs($crumbs) + ->appendChild($content); + } + + private function renderSearchResults() { + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $results = array(); + + $limit = 100; + $page = $this->getRequest()->getInt('page', 0); + $pager = new PHUIPagerView(); + $pager->setPageSize($limit); + $pager->setOffset($page); + $pager->setURI($this->getRequest()->getRequestURI(), 'page'); + + $search_mode = null; + + switch ($repository->getVersionControlSystem()) { + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: + $results = array(); + break; + default: + if (strlen($this->getRequest()->getStr('grep'))) { + $search_mode = 'grep'; + $query_string = $this->getRequest()->getStr('grep'); + $results = $this->callConduitWithDiffusionRequest( + 'diffusion.searchquery', + array( + 'grep' => $query_string, + 'commit' => $drequest->getStableCommit(), + 'path' => $drequest->getPath(), + 'limit' => $limit + 1, + 'offset' => $page, + )); + } else { // Filename search. + $search_mode = 'find'; + $query_string = $this->getRequest()->getStr('find'); + $results = $this->callConduitWithDiffusionRequest( + 'diffusion.querypaths', + array( + 'pattern' => $query_string, + 'commit' => $drequest->getStableCommit(), + 'path' => $drequest->getPath(), + 'limit' => $limit + 1, + 'offset' => $page, + )); + } + break; + } + + $results = $pager->sliceResults($results); + + if ($search_mode == 'grep') { + $table = $this->renderGrepResults($results, $query_string); + $header = pht( + 'File content matching "%s" under "%s"', + $query_string, + nonempty($drequest->getPath(), '/')); + } else { + $table = $this->renderFindResults($results); + $header = pht( + 'Paths matching "%s" under "%s"', + $query_string, + nonempty($drequest->getPath(), '/')); + } + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText($header) + ->setTable($table); + + $pager_box = $this->renderTablePagerBox($pager); + + return array($box, $pager_box); + } + + private function renderGrepResults(array $results, $pattern) { + $drequest = $this->getDiffusionRequest(); + + require_celerity_resource('phabricator-search-results-css'); + + $rows = array(); + foreach ($results as $result) { + list($path, $line, $string) = $result; + + $href = $drequest->generateURI(array( + 'action' => 'browse', + 'path' => $path, + 'line' => $line, + )); + + $matches = null; + $count = @preg_match_all( + '('.$pattern.')u', + $string, + $matches, + PREG_OFFSET_CAPTURE); + + if (!$count) { + $output = ltrim($string); + } else { + $output = array(); + $cursor = 0; + $length = strlen($string); + foreach ($matches[0] as $match) { + $offset = $match[1]; + if ($cursor != $offset) { + $output[] = array( + 'text' => substr($string, $cursor, $offset), + 'highlight' => false, + ); + } + $output[] = array( + 'text' => $match[0], + 'highlight' => true, + ); + $cursor = $offset + strlen($match[0]); + } + if ($cursor != $length) { + $output[] = array( + 'text' => substr($string, $cursor), + 'highlight' => false, + ); + } + + if ($output) { + $output[0]['text'] = ltrim($output[0]['text']); + } + + foreach ($output as $key => $segment) { + if ($segment['highlight']) { + $output[$key] = phutil_tag('strong', array(), $segment['text']); + } else { + $output[$key] = $segment['text']; + } + } + } + + $string = phutil_tag( + 'pre', + array('class' => 'PhabricatorMonospaced phui-source-fragment'), + $output); + + $path = Filesystem::readablePath($path, $drequest->getPath()); + + $rows[] = array( + phutil_tag('a', array('href' => $href), $path), + $line, + $string, + ); + } + + $table = id(new AphrontTableView($rows)) + ->setClassName('remarkup-code') + ->setHeaders(array(pht('Path'), pht('Line'), pht('String'))) + ->setColumnClasses(array('', 'n', 'wide')) + ->setNoDataString( + pht( + 'The pattern you searched for was not found in the content of any '. + 'files.')); + + return $table; + } + + private function renderFindResults(array $results) { + $drequest = $this->getDiffusionRequest(); + + $rows = array(); + foreach ($results as $result) { + $href = $drequest->generateURI(array( + 'action' => 'browse', + 'path' => $result, + )); + + $readable = Filesystem::readablePath($result, $drequest->getPath()); + + $rows[] = array( + phutil_tag('a', array('href' => $href), $readable), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders(array(pht('Path'))) + ->setColumnClasses(array('wide')) + ->setNoDataString( + pht( + 'The pattern you searched for did not match the names of any '. + 'files.')); + + return $table; + } + + private function loadLintMessages() { + $drequest = $this->getDiffusionRequest(); + $branch = $drequest->loadBranch(); + + if (!$branch || !$branch->getLintCommit()) { + return; + } + + $this->lintCommit = $branch->getLintCommit(); + + $conn = id(new PhabricatorRepository())->establishConnection('r'); + + $where = ''; + if ($drequest->getLint()) { + $where = qsprintf( + $conn, + 'AND code = %s', + $drequest->getLint()); + } + + $this->lintMessages = queryfx_all( + $conn, + 'SELECT * FROM %T WHERE branchID = %d %Q AND path = %s', + PhabricatorRepository::TABLE_LINTMESSAGE, + $branch->getID(), + $where, + '/'.$drequest->getPath()); + } + + private function buildCorpus( + $show_blame, + $show_color, + DiffusionFileContent $file_content, + $needs_blame, + DiffusionRequest $drequest, + $path, + $data) { + + if (!$show_color) { + $style = + 'border: none; width: 100%; height: 80em; font-family: monospace'; + if (!$show_blame) { + $corpus = phutil_tag( + 'textarea', + array( + 'style' => $style, + ), + $file_content->getCorpus()); + } else { + $text_list = $file_content->getTextList(); + $rev_list = $file_content->getRevList(); + $blame_dict = $file_content->getBlameDict(); + + $rows = array(); + foreach ($text_list as $k => $line) { + $rev = $rev_list[$k]; + $author = $blame_dict[$rev]['author']; + $rows[] = + sprintf('%-10s %-20s %s', substr($rev, 0, 7), $author, $line); + } + + $corpus = phutil_tag( + 'textarea', + array( + 'style' => $style, + ), + implode("\n", $rows)); + } + } else { + require_celerity_resource('syntax-highlighting-css'); + $text_list = $file_content->getTextList(); + $rev_list = $file_content->getRevList(); + $blame_dict = $file_content->getBlameDict(); + + $text_list = implode("\n", $text_list); + $text_list = PhabricatorSyntaxHighlighter::highlightWithFilename( + $path, + $text_list); + $text_list = explode("\n", $text_list); + + $rows = $this->buildDisplayRows($text_list, $rev_list, $blame_dict, + $needs_blame, $drequest, $show_blame, $show_color); + + $corpus_table = javelin_tag( + 'table', + array( + 'class' => 'diffusion-source remarkup-code PhabricatorMonospaced', + 'sigil' => 'phabricator-source', + ), + $rows); + + if ($this->getRequest()->isAjax()) { + return $corpus_table; + } + + $id = celerity_generate_unique_node_id(); + + $repo = $drequest->getRepository(); + $symbol_repos = nonempty($repo->getSymbolSources(), array()); + $symbol_repos[] = $repo->getPHID(); + + $lang = last(explode('.', $drequest->getPath())); + $repo_languages = $repo->getSymbolLanguages(); + $repo_languages = nonempty($repo_languages, array()); + $repo_languages = array_fill_keys($repo_languages, true); + + $needs_symbols = true; + if ($repo_languages && $symbol_repos) { + $have_symbols = id(new DiffusionSymbolQuery()) + ->existsSymbolsInRepository($repo->getPHID()); + if (!$have_symbols) { + $needs_symbols = false; + } + } + + if ($needs_symbols && $repo_languages) { + $needs_symbols = isset($repo_languages[$lang]); + } + + if ($needs_symbols) { + Javelin::initBehavior( + 'repository-crossreference', + array( + 'container' => $id, + 'lang' => $lang, + 'repositories' => $symbol_repos, + )); + } + + $corpus = phutil_tag( + 'div', + array( + 'id' => $id, + ), + $corpus_table); + + Javelin::initBehavior('load-blame', array('id' => $id)); + } + + $edit = $this->renderEditButton(); + $file = $this->renderFileButton(); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('File Contents')) + ->addActionLink($edit) + ->addActionLink($file); + + $corpus = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($corpus) + ->setCollapsed(true); + + return $corpus; + } + + private function enrichActionView( + PhabricatorActionListView $view, + DiffusionRequest $drequest, + $show_blame, + $show_color) { + + $viewer = $this->getRequest()->getUser(); + $base_uri = $this->getRequest()->getRequestURI(); + + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Show Last Change')) + ->setHref( + $drequest->generateURI( + array( + 'action' => 'change', + ))) + ->setIcon('fa-backward')); + + if ($show_blame) { + $blame_text = pht('Disable Blame'); + $blame_icon = 'fa-exclamation-circle lightgreytext'; + $blame_value = 0; + } else { + $blame_text = pht('Enable Blame'); + $blame_icon = 'fa-exclamation-circle'; + $blame_value = 1; + } + + $view->addAction( + id(new PhabricatorActionView()) + ->setName($blame_text) + ->setHref($base_uri->alter('blame', $blame_value)) + ->setIcon($blame_icon) + ->setUser($viewer) + ->setRenderAsForm($viewer->isLoggedIn())); + + if ($show_color) { + $highlight_text = pht('Disable Highlighting'); + $highlight_icon = 'fa-star-o grey'; + $highlight_value = 0; + } else { + $highlight_text = pht('Enable Highlighting'); + $highlight_icon = 'fa-star'; + $highlight_value = 1; + } + + $view->addAction( + id(new PhabricatorActionView()) + ->setName($highlight_text) + ->setHref($base_uri->alter('color', $highlight_value)) + ->setIcon($highlight_icon) + ->setUser($viewer) + ->setRenderAsForm($viewer->isLoggedIn())); + + $href = null; + if ($this->getRequest()->getStr('lint') !== null) { + $lint_text = pht('Hide %d Lint Message(s)', count($this->lintMessages)); + $href = $base_uri->alter('lint', null); + + } else if ($this->lintCommit === null) { + $lint_text = pht('Lint not Available'); + } else { + $lint_text = pht( + 'Show %d Lint Message(s)', + count($this->lintMessages)); + $href = $this->getDiffusionRequest()->generateURI(array( + 'action' => 'browse', + 'commit' => $this->lintCommit, + ))->alter('lint', ''); + } + + $view->addAction( + id(new PhabricatorActionView()) + ->setName($lint_text) + ->setHref($href) + ->setIcon('fa-exclamation-triangle') + ->setDisabled(!$href)); + + return $view; + } + + private function renderEditButton() { + $request = $this->getRequest(); + $user = $request->getUser(); + + $drequest = $this->getDiffusionRequest(); + + $repository = $drequest->getRepository(); + $path = $drequest->getPath(); + $line = nonempty((int)$drequest->getLine(), 1); + + $editor_link = $user->loadEditorLink($path, $line, $repository); + $template = $user->loadEditorLink($path, '%l', $repository); + + $icon_edit = id(new PHUIIconView()) + ->setIconFont('fa-pencil'); + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Open in Editor')) + ->setHref($editor_link) + ->setIcon($icon_edit) + ->setID('editor_link') + ->setMetadata(array('link_template' => $template)) + ->setDisabled(!$editor_link); + + return $button; + } + + private function renderFileButton($file_uri = null) { + + $base_uri = $this->getRequest()->getRequestURI(); + + if ($file_uri) { + $text = pht('Download Raw File'); + $href = $file_uri; + $icon = 'fa-download'; + } else { + $text = pht('View Raw File'); + $href = $base_uri->alter('view', 'raw'); + $icon = 'fa-file-text'; + } + + $iconview = id(new PHUIIconView()) + ->setIconFont($icon); + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setText($text) + ->setHref($href) + ->setIcon($iconview); + + return $button; + } + + + private function buildDisplayRows( + array $text_list, + array $rev_list, + array $blame_dict, + $needs_blame, + DiffusionRequest $drequest, + $show_blame, + $show_color) { + + $handles = array(); + if ($blame_dict) { + $epoch_list = ipull(ifilter($blame_dict, 'epoch'), 'epoch'); + $epoch_min = min($epoch_list); + $epoch_max = max($epoch_list); + $epoch_range = ($epoch_max - $epoch_min) + 1; + + $author_phids = ipull(ifilter($blame_dict, 'authorPHID'), 'authorPHID'); + $handles = $this->loadViewerHandles($author_phids); + } + + $line_arr = array(); + $line_str = $drequest->getLine(); + $ranges = explode(',', $line_str); + foreach ($ranges as $range) { + if (strpos($range, '-') !== false) { + list($min, $max) = explode('-', $range, 2); + $line_arr[] = array( + 'min' => min($min, $max), + 'max' => max($min, $max), + ); + } else if (strlen($range)) { + $line_arr[] = array( + 'min' => $range, + 'max' => $range, + ); + } + } + + $display = array(); + + $line_number = 1; + $last_rev = null; + $color = null; + foreach ($text_list as $k => $line) { + $display_line = array( + 'epoch' => null, + 'commit' => null, + 'author' => null, + 'target' => null, + 'highlighted' => null, + 'line' => $line_number, + 'data' => $line, + ); + + if ($show_blame) { + // If the line's rev is same as the line above, show empty content + // with same color; otherwise generate blame info. The newer a change + // is, the more saturated the color. + + $rev = idx($rev_list, $k, $last_rev); + + if ($last_rev == $rev) { + $display_line['color'] = $color; + } else { + $blame = $blame_dict[$rev]; + + if (!isset($blame['epoch'])) { + $color = '#ffd'; // Render as warning. + } else { + $color_ratio = ($blame['epoch'] - $epoch_min) / $epoch_range; + $color_value = 0xE6 * (1.0 - $color_ratio); + $color = sprintf( + '#%02x%02x%02x', + $color_value, + 0xF6, + $color_value); + } + + $display_line['epoch'] = idx($blame, 'epoch'); + $display_line['color'] = $color; + $display_line['commit'] = $rev; + + $author_phid = idx($blame, 'authorPHID'); + if ($author_phid && $handles[$author_phid]) { + $author_link = $handles[$author_phid]->renderLink(); + } else { + $author_link = $blame['author']; + } + $display_line['author'] = $author_link; + + $last_rev = $rev; + } + } + + if ($line_arr) { + if ($line_number == $line_arr[0]['min']) { + $display_line['target'] = true; + } + foreach ($line_arr as $range) { + if ($line_number >= $range['min'] && + $line_number <= $range['max']) { + $display_line['highlighted'] = true; + } + } + } + + $display[] = $display_line; + ++$line_number; + } + + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $commits = array_filter(ipull($display, 'commit')); + if ($commits) { + $commits = id(new DiffusionCommitQuery()) + ->setViewer($viewer) + ->withRepository($drequest->getRepository()) + ->withIdentifiers($commits) + ->execute(); + $commits = mpull($commits, null, 'getCommitIdentifier'); + } + + $revision_ids = id(new DifferentialRevision()) + ->loadIDsByCommitPHIDs(mpull($commits, 'getPHID')); + $revisions = array(); + if ($revision_ids) { + $revisions = id(new DifferentialRevisionQuery()) + ->setViewer($viewer) + ->withIDs($revision_ids) + ->execute(); + } + + $phids = array(); + foreach ($commits as $commit) { + if ($commit->getAuthorPHID()) { + $phids[] = $commit->getAuthorPHID(); + } + } + foreach ($revisions as $revision) { + if ($revision->getAuthorPHID()) { + $phids[] = $revision->getAuthorPHID(); + } + } + $handles = $this->loadViewerHandles($phids); + + Javelin::initBehavior('phabricator-oncopy', array()); + + $engine = null; + $inlines = array(); + if ($this->getRequest()->getStr('lint') !== null && $this->lintMessages) { + $engine = new PhabricatorMarkupEngine(); + $engine->setViewer($viewer); + + foreach ($this->lintMessages as $message) { + $inline = id(new PhabricatorAuditInlineComment()) + ->setSyntheticAuthor( + ArcanistLintSeverity::getStringForSeverity($message['severity']). + ' '.$message['code'].' ('.$message['name'].')') + ->setLineNumber($message['line']) + ->setContent($message['description']); + $inlines[$message['line']][] = $inline; + + $engine->addObject( + $inline, + PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); + } + + $engine->process(); + require_celerity_resource('differential-changeset-view-css'); + } + + $rows = $this->renderInlines( + idx($inlines, 0, array()), + $show_blame, + (bool)$this->coverage, + $engine); + + foreach ($display as $line) { + + $line_href = $drequest->generateURI( + array( + 'action' => 'browse', + 'line' => $line['line'], + 'stable' => true, + )); + + $blame = array(); + $style = null; + if (array_key_exists('color', $line)) { + if ($line['color']) { + $style = 'background: '.$line['color'].';'; + } + + $before_link = null; + $commit_link = null; + $revision_link = null; + if (idx($line, 'commit')) { + $commit = $line['commit']; + + if (idx($commits, $commit)) { + $tooltip = $this->renderCommitTooltip( + $commits[$commit], + $handles, + $line['author']); + } else { + $tooltip = null; + } + + Javelin::initBehavior('phabricator-tooltips', array()); + require_celerity_resource('aphront-tooltip-css'); + + $commit_link = javelin_tag( + 'a', + array( + 'href' => $drequest->generateURI( + array( + 'action' => 'commit', + 'commit' => $line['commit'], + )), + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $tooltip, + 'align' => 'E', + 'size' => 600, + ), + ), + id(new PhutilUTF8StringTruncator()) + ->setMaximumGlyphs(9) + ->setTerminator('') + ->truncateString($line['commit'])); + + $revision_id = null; + if (idx($commits, $commit)) { + $revision_id = idx($revision_ids, $commits[$commit]->getPHID()); + } + + if ($revision_id) { + $revision = idx($revisions, $revision_id); + if ($revision) { + $tooltip = $this->renderRevisionTooltip($revision, $handles); + $revision_link = javelin_tag( + 'a', + array( + 'href' => '/D'.$revision->getID(), + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $tooltip, + 'align' => 'E', + 'size' => 600, + ), + ), + 'D'.$revision->getID()); + } + } + + $uri = $line_href->alter('before', $commit); + $before_link = javelin_tag( + 'a', + array( + 'href' => $uri->setQueryParam('view', 'blame'), + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => pht('Skip Past This Commit'), + 'align' => 'E', + 'size' => 300, + ), + ), + "\xC2\xAB"); + } + + $blame[] = phutil_tag( + 'th', + array( + 'class' => 'diffusion-blame-link', + ), + $before_link); + + $object_links = array(); + $object_links[] = $commit_link; + if ($revision_link) { + $object_links[] = phutil_tag('span', array(), '/'); + $object_links[] = $revision_link; + } + + $blame[] = phutil_tag( + 'th', + array( + 'class' => 'diffusion-rev-link', + ), + $object_links); + } + + $line_link = phutil_tag( + 'a', + array( + 'href' => $line_href, + 'style' => $style, + ), + $line['line']); + + $blame[] = javelin_tag( + 'th', + array( + 'class' => 'diffusion-line-link', + 'sigil' => 'phabricator-source-line', + 'style' => $style, + ), + $line_link); + + Javelin::initBehavior('phabricator-line-linker'); + + if ($line['target']) { + Javelin::initBehavior( + 'diffusion-jump-to', + array( + 'target' => 'scroll_target', + )); + $anchor_text = phutil_tag( + 'a', + array( + 'id' => 'scroll_target', + ), + ''); + } else { + $anchor_text = null; + } + + $blame[] = phutil_tag( + 'td', + array( + ), + array( + $anchor_text, + + // NOTE: See phabricator-oncopy behavior. + "\xE2\x80\x8B", + + // TODO: [HTML] Not ideal. + phutil_safe_html(str_replace("\t", ' ', $line['data'])), + )); + + if ($this->coverage) { + require_celerity_resource('differential-changeset-view-css'); + $cov_index = $line['line'] - 1; + + if (isset($this->coverage[$cov_index])) { + $cov_class = $this->coverage[$cov_index]; + } else { + $cov_class = 'N'; + } + + $blame[] = phutil_tag( + 'td', + array( + 'class' => 'cov cov-'.$cov_class, + ), + ''); + } + + $rows[] = phutil_tag( + 'tr', + array( + 'class' => ($line['highlighted'] ? + 'phabricator-source-highlight' : + null), + ), + $blame); + + $cur_inlines = $this->renderInlines( + idx($inlines, $line['line'], array()), + $show_blame, + $this->coverage, + $engine); + foreach ($cur_inlines as $cur_inline) { + $rows[] = $cur_inline; + } + } + + return $rows; + } + + private function renderInlines( + array $inlines, + $needs_blame, + $has_coverage, + $engine) { + + $rows = array(); + foreach ($inlines as $inline) { + + // TODO: This should use modern scaffolding code. + + $inline_view = id(new PHUIDiffInlineCommentDetailView()) + ->setUser($this->getViewer()) + ->setMarkupEngine($engine) + ->setInlineComment($inline) + ->render(); + + $row = array_fill(0, ($needs_blame ? 3 : 1), phutil_tag('th')); + + $row[] = phutil_tag('td', array(), $inline_view); + + if ($has_coverage) { + $row[] = phutil_tag( + 'td', + array( + 'class' => 'cov cov-I', + )); + } + + $rows[] = phutil_tag('tr', array('class' => 'inline'), $row); + } + + return $rows; + } + + private function loadFileForData($path, $data) { + $file = PhabricatorFile::buildFromFileDataOrHash( + $data, + array( + 'name' => basename($path), + 'ttl' => time() + 60 * 60 * 24, + 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, + )); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $file->attachToObject( + $this->getDiffusionRequest()->getRepository()->getPHID()); + unset($unguarded); + + return $file; + } + + private function buildRawResponse($path, $data) { + $file = $this->loadFileForData($path, $data); + return $file->getRedirectResponse(); + } + + private function buildImageCorpus($file_uri) { + $properties = new PHUIPropertyListView(); + + $properties->addImageContent( + phutil_tag( + 'img', + array( + 'src' => $file_uri, + ))); + + $file = $this->renderFileButton($file_uri); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Image')) + ->addActionLink($file); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->addPropertyList($properties); + } + + private function buildBinaryCorpus($file_uri, $data) { + + $size = new PhutilNumber(strlen($data)); + $text = pht('This is a binary file. It is %s byte(s) in length.', $size); + $text = id(new PHUIBoxView()) + ->addPadding(PHUI::PADDING_LARGE) + ->appendChild($text); + + $file = $this->renderFileButton($file_uri); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Details')) + ->addActionLink($file); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($text); + + return $box; + } + + private function buildErrorCorpus($message) { + $text = id(new PHUIBoxView()) + ->addPadding(PHUI::PADDING_LARGE) + ->appendChild($message); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Details')); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($text); + + return $box; + } + + private function buildBeforeResponse($before) { + $request = $this->getRequest(); + $drequest = $this->getDiffusionRequest(); + + // NOTE: We need to get the grandparent so we can capture filename changes + // in the parent. + + $parent = $this->loadParentCommitOf($before); + $old_filename = null; + $was_created = false; + if ($parent) { + $grandparent = $this->loadParentCommitOf($parent); + + if ($grandparent) { + $rename_query = new DiffusionRenameHistoryQuery(); + $rename_query->setRequest($drequest); + $rename_query->setOldCommit($grandparent); + $rename_query->setViewer($request->getUser()); + $old_filename = $rename_query->loadOldFilename(); + $was_created = $rename_query->getWasCreated(); + } + } + + $follow = null; + if ($was_created) { + // If the file was created in history, that means older commits won't + // have it. Since we know it existed at 'before', it must have been + // created then; jump there. + $target_commit = $before; + $follow = 'created'; + } else if ($parent) { + // If we found a parent, jump to it. This is the normal case. + $target_commit = $parent; + } else { + // If there's no parent, this was probably created in the initial commit? + // And the "was_created" check will fail because we can't identify the + // grandparent. Keep the user at 'before'. + $target_commit = $before; + $follow = 'first'; + } + + $path = $drequest->getPath(); + $renamed = null; + if ($old_filename !== null && + $old_filename !== '/'.$path) { + $renamed = $path; + $path = $old_filename; + } + + $line = null; + // If there's a follow error, drop the line so the user sees the message. + if (!$follow) { + $line = $this->getBeforeLineNumber($target_commit); + } + + $before_uri = $drequest->generateURI( + array( + 'action' => 'browse', + 'commit' => $target_commit, + 'line' => $line, + 'path' => $path, + )); + + $before_uri->setQueryParams($request->getRequestURI()->getQueryParams()); + $before_uri = $before_uri->alter('before', null); + $before_uri = $before_uri->alter('renamed', $renamed); + $before_uri = $before_uri->alter('follow', $follow); + + return id(new AphrontRedirectResponse())->setURI($before_uri); + } + + private function getBeforeLineNumber($target_commit) { + $drequest = $this->getDiffusionRequest(); + + $line = $drequest->getLine(); + if (!$line) { + return null; + } + + $raw_diff = $this->callConduitWithDiffusionRequest( + 'diffusion.rawdiffquery', + array( + 'commit' => $drequest->getCommit(), + 'path' => $drequest->getPath(), + 'againstCommit' => $target_commit, + )); + $old_line = 0; + $new_line = 0; + + foreach (explode("\n", $raw_diff) as $text) { + if ($text[0] == '-' || $text[0] == ' ') { + $old_line++; + } + if ($text[0] == '+' || $text[0] == ' ') { + $new_line++; + } + if ($new_line == $line) { + return $old_line; + } + } + + // We didn't find the target line. + return $line; + } + + private function loadParentCommitOf($commit) { + $drequest = $this->getDiffusionRequest(); + $user = $this->getRequest()->getUser(); + + $before_req = DiffusionRequest::newFromDictionary( + array( + 'user' => $user, + 'repository' => $drequest->getRepository(), + 'commit' => $commit, + )); + + $parents = DiffusionQuery::callConduitWithDiffusionRequest( + $user, + $before_req, + 'diffusion.commitparentsquery', + array( + 'commit' => $commit, + )); + + return head($parents); + } + + private function renderRevisionTooltip( + DifferentialRevision $revision, + array $handles) { + $viewer = $this->getRequest()->getUser(); + + $date = phabricator_date($revision->getDateModified(), $viewer); + $id = $revision->getID(); + $title = $revision->getTitle(); + $header = "D{$id} {$title}"; + + $author = $handles[$revision->getAuthorPHID()]->getName(); + + return "{$header}\n{$date} \xC2\xB7 {$author}"; + } + + private function renderCommitTooltip( + PhabricatorRepositoryCommit $commit, + array $handles, + $author) { + + $viewer = $this->getRequest()->getUser(); + + $date = phabricator_date($commit->getEpoch(), $viewer); + $summary = trim($commit->getSummary()); + + if ($commit->getAuthorPHID()) { + $author = $handles[$commit->getAuthorPHID()]->getName(); + } + + return "{$summary}\n{$date} \xC2\xB7 {$author}"; + } + protected function renderSearchForm($collapsed) { $drequest = $this->getDiffusionRequest(); @@ -204,8 +1678,8 @@ return $view; } - protected function buildOpenRevisions() { - $user = $this->getRequest()->getUser(); + private function buildOpenRevisions() { + $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); @@ -220,7 +1694,7 @@ $recent = (PhabricatorTime::getNow() - phutil_units('30 days in seconds')); $revisions = id(new DifferentialRevisionQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPath($repository->getID(), $path_id) ->withStatus(DifferentialRevisionQuery::STATUS_OPEN) ->withUpdatedEpochBetween($recent, null) @@ -243,7 +1717,7 @@ $view = id(new DifferentialRevisionListView()) ->setHeader($header) ->setRevisions($revisions) - ->setUser($user); + ->setUser($viewer); $phids = $view->getRequiredHandlePHIDs(); $handles = $this->loadViewerHandles($phids); diff --git a/src/applications/diffusion/controller/DiffusionBrowseDirectoryController.php b/src/applications/diffusion/controller/DiffusionBrowseDirectoryController.php deleted file mode 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseDirectoryController.php +++ /dev/null @@ -1,106 +0,0 @@ -browseQueryResults = $results; - return $this; - } - - public function getBrowseQueryResults() { - return $this->browseQueryResults; - } - - protected function processDiffusionRequest(AphrontRequest $request) { - $drequest = $this->diffusionRequest; - - $results = $this->getBrowseQueryResults(); - $reason = $results->getReasonForEmptyResultSet(); - - $content = array(); - $actions = $this->buildActionView($drequest); - $properties = $this->buildPropertyView($drequest, $actions); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($this->buildHeaderView($drequest)) - ->addPropertyList($properties); - - $content[] = $object_box; - $content[] = $this->renderSearchForm($collapsed = true); - - if (!$results->isValidResults()) { - $empty_result = new DiffusionEmptyResultView(); - $empty_result->setDiffusionRequest($drequest); - $empty_result->setDiffusionBrowseResultSet($results); - $empty_result->setView($request->getStr('view')); - $content[] = $empty_result; - } else { - $phids = array(); - foreach ($results->getPaths() as $result) { - $data = $result->getLastCommitData(); - if ($data) { - if ($data->getCommitDetail('authorPHID')) { - $phids[$data->getCommitDetail('authorPHID')] = true; - } - } - } - - $phids = array_keys($phids); - $handles = $this->loadViewerHandles($phids); - - $browse_table = new DiffusionBrowseTableView(); - $browse_table->setDiffusionRequest($drequest); - $browse_table->setHandles($handles); - $browse_table->setPaths($results->getPaths()); - $browse_table->setUser($request->getUser()); - - $browse_panel = new PHUIObjectBoxView(); - $browse_panel->setHeaderText($drequest->getPath(), '/'); - $browse_panel->setTable($browse_table); - - $content[] = $browse_panel; - } - - $content[] = $this->buildOpenRevisions(); - - - $readme_path = $results->getReadmePath(); - if ($readme_path) { - $readme_content = $this->callConduitWithDiffusionRequest( - 'diffusion.filecontentquery', - array( - 'path' => $readme_path, - 'commit' => $drequest->getStableCommit(), - )); - if ($readme_content) { - $content[] = id(new DiffusionReadmeView()) - ->setUser($this->getViewer()) - ->setPath($readme_path) - ->setContent($readme_content['corpus']); - } - } - - $crumbs = $this->buildCrumbs( - array( - 'branch' => true, - 'path' => true, - 'view' => 'browse', - )); - - return $this->buildApplicationPage( - array( - $crumbs, - $content, - ), - array( - 'title' => array( - nonempty(basename($drequest->getPath()), '/'), - $drequest->getRepository()->getDisplayName(), - ), - )); - } - -} diff --git a/src/applications/diffusion/controller/DiffusionBrowseFileController.php b/src/applications/diffusion/controller/DiffusionBrowseFileController.php deleted file mode 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseFileController.php +++ /dev/null @@ -1,1134 +0,0 @@ -getDiffusionRequest(); - $viewer = $request->getUser(); - - $before = $request->getStr('before'); - if ($before) { - return $this->buildBeforeResponse($before); - } - - $path = $drequest->getPath(); - - $preferences = $viewer->loadPreferences(); - - $show_blame = $request->getBool( - 'blame', - $preferences->getPreference( - PhabricatorUserPreferences::PREFERENCE_DIFFUSION_BLAME, - false)); - $show_color = $request->getBool( - 'color', - $preferences->getPreference( - PhabricatorUserPreferences::PREFERENCE_DIFFUSION_COLOR, - true)); - - $view = $request->getStr('view'); - if ($request->isFormPost() && $view != 'raw' && $viewer->isLoggedIn()) { - $preferences->setPreference( - PhabricatorUserPreferences::PREFERENCE_DIFFUSION_BLAME, - $show_blame); - $preferences->setPreference( - PhabricatorUserPreferences::PREFERENCE_DIFFUSION_COLOR, - $show_color); - $preferences->save(); - - $uri = $request->getRequestURI() - ->alter('blame', null) - ->alter('color', null); - - return id(new AphrontRedirectResponse())->setURI($uri); - } - - // We need the blame information if blame is on and we're building plain - // text, or blame is on and this is an Ajax request. If blame is on and - // this is a colorized request, we don't show blame at first (we ajax it - // in afterward) so we don't need to query for it. - $needs_blame = ($show_blame && !$show_color) || - ($show_blame && $request->isAjax()); - - $params = array( - 'commit' => $drequest->getCommit(), - 'path' => $drequest->getPath(), - 'needsBlame' => $needs_blame, - ); - - $byte_limit = null; - if ($view !== 'raw') { - $byte_limit = PhabricatorFileStorageEngine::getChunkThreshold(); - $time_limit = 10; - - $params += array( - 'timeout' => $time_limit, - 'byteLimit' => $byte_limit, - ); - } - - $file_content = DiffusionFileContent::newFromConduit( - $this->callConduitWithDiffusionRequest( - 'diffusion.filecontentquery', - $params)); - $data = $file_content->getCorpus(); - - if ($view === 'raw') { - return $this->buildRawResponse($path, $data); - } - - $this->loadLintMessages(); - $this->coverage = $drequest->loadCoverage(); - - if ($byte_limit && (strlen($data) == $byte_limit)) { - $corpus = $this->buildErrorCorpus( - pht( - 'This file is larger than %s byte(s), and too large to display '. - 'in the web UI.', - $byte_limit)); - } else if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) { - $file = $this->loadFileForData($path, $data); - $file_uri = $file->getBestURI(); - - if ($file->isViewableImage()) { - $corpus = $this->buildImageCorpus($file_uri); - } else { - $corpus = $this->buildBinaryCorpus($file_uri, $data); - } - } else { - // Build the content of the file. - $corpus = $this->buildCorpus( - $show_blame, - $show_color, - $file_content, - $needs_blame, - $drequest, - $path, - $data); - } - - if ($request->isAjax()) { - return id(new AphrontAjaxResponse())->setContent($corpus); - } - - require_celerity_resource('diffusion-source-css'); - - // Render the page. - $view = $this->buildActionView($drequest); - $action_list = $this->enrichActionView( - $view, - $drequest, - $show_blame, - $show_color); - - $properties = $this->buildPropertyView($drequest, $action_list); - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($this->buildHeaderView($drequest)) - ->addPropertyList($properties); - - $content = array(); - $content[] = $object_box; - - $follow = $request->getStr('follow'); - if ($follow) { - $notice = new PHUIInfoView(); - $notice->setSeverity(PHUIInfoView::SEVERITY_WARNING); - $notice->setTitle(pht('Unable to Continue')); - switch ($follow) { - case 'first': - $notice->appendChild( - pht( - 'Unable to continue tracing the history of this file because '. - 'this commit is the first commit in the repository.')); - break; - case 'created': - $notice->appendChild( - pht( - 'Unable to continue tracing the history of this file because '. - 'this commit created the file.')); - break; - } - $content[] = $notice; - } - - $renamed = $request->getStr('renamed'); - if ($renamed) { - $notice = new PHUIInfoView(); - $notice->setSeverity(PHUIInfoView::SEVERITY_NOTICE); - $notice->setTitle(pht('File Renamed')); - $notice->appendChild( - pht( - "File history passes through a rename from '%s' to '%s'.", - $drequest->getPath(), $renamed)); - $content[] = $notice; - } - - $content[] = $corpus; - $content[] = $this->buildOpenRevisions(); - - $crumbs = $this->buildCrumbs( - array( - 'branch' => true, - 'path' => true, - 'view' => 'browse', - )); - - $basename = basename($this->getDiffusionRequest()->getPath()); - - return $this->buildApplicationPage( - array( - $crumbs, - $content, - ), - array( - 'title' => $basename, - )); - } - - private function loadLintMessages() { - $drequest = $this->getDiffusionRequest(); - $branch = $drequest->loadBranch(); - - if (!$branch || !$branch->getLintCommit()) { - return; - } - - $this->lintCommit = $branch->getLintCommit(); - - $conn = id(new PhabricatorRepository())->establishConnection('r'); - - $where = ''; - if ($drequest->getLint()) { - $where = qsprintf( - $conn, - 'AND code = %s', - $drequest->getLint()); - } - - $this->lintMessages = queryfx_all( - $conn, - 'SELECT * FROM %T WHERE branchID = %d %Q AND path = %s', - PhabricatorRepository::TABLE_LINTMESSAGE, - $branch->getID(), - $where, - '/'.$drequest->getPath()); - } - - private function buildCorpus( - $show_blame, - $show_color, - DiffusionFileContent $file_content, - $needs_blame, - DiffusionRequest $drequest, - $path, - $data) { - - if (!$show_color) { - $style = - 'border: none; width: 100%; height: 80em; font-family: monospace'; - if (!$show_blame) { - $corpus = phutil_tag( - 'textarea', - array( - 'style' => $style, - ), - $file_content->getCorpus()); - } else { - $text_list = $file_content->getTextList(); - $rev_list = $file_content->getRevList(); - $blame_dict = $file_content->getBlameDict(); - - $rows = array(); - foreach ($text_list as $k => $line) { - $rev = $rev_list[$k]; - $author = $blame_dict[$rev]['author']; - $rows[] = - sprintf('%-10s %-20s %s', substr($rev, 0, 7), $author, $line); - } - - $corpus = phutil_tag( - 'textarea', - array( - 'style' => $style, - ), - implode("\n", $rows)); - } - } else { - require_celerity_resource('syntax-highlighting-css'); - $text_list = $file_content->getTextList(); - $rev_list = $file_content->getRevList(); - $blame_dict = $file_content->getBlameDict(); - - $text_list = implode("\n", $text_list); - $text_list = PhabricatorSyntaxHighlighter::highlightWithFilename( - $path, - $text_list); - $text_list = explode("\n", $text_list); - - $rows = $this->buildDisplayRows($text_list, $rev_list, $blame_dict, - $needs_blame, $drequest, $show_blame, $show_color); - - $corpus_table = javelin_tag( - 'table', - array( - 'class' => 'diffusion-source remarkup-code PhabricatorMonospaced', - 'sigil' => 'phabricator-source', - ), - $rows); - - if ($this->getRequest()->isAjax()) { - return $corpus_table; - } - - $id = celerity_generate_unique_node_id(); - - $repo = $drequest->getRepository(); - $symbol_repos = nonempty($repo->getSymbolSources(), array()); - $symbol_repos[] = $repo->getPHID(); - - $lang = last(explode('.', $drequest->getPath())); - $repo_languages = $repo->getSymbolLanguages(); - $repo_languages = nonempty($repo_languages, array()); - $repo_languages = array_fill_keys($repo_languages, true); - - $needs_symbols = true; - if ($repo_languages && $symbol_repos) { - $have_symbols = id(new DiffusionSymbolQuery()) - ->existsSymbolsInRepository($repo->getPHID()); - if (!$have_symbols) { - $needs_symbols = false; - } - } - - if ($needs_symbols && $repo_languages) { - $needs_symbols = isset($repo_languages[$lang]); - } - - if ($needs_symbols) { - Javelin::initBehavior( - 'repository-crossreference', - array( - 'container' => $id, - 'lang' => $lang, - 'repositories' => $symbol_repos, - )); - } - - $corpus = phutil_tag( - 'div', - array( - 'id' => $id, - ), - $corpus_table); - - Javelin::initBehavior('load-blame', array('id' => $id)); - } - - $edit = $this->renderEditButton(); - $file = $this->renderFileButton(); - $header = id(new PHUIHeaderView()) - ->setHeader(pht('File Contents')) - ->addActionLink($edit) - ->addActionLink($file); - - $corpus = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->appendChild($corpus) - ->setCollapsed(true); - - return $corpus; - } - - private function enrichActionView( - PhabricatorActionListView $view, - DiffusionRequest $drequest, - $show_blame, - $show_color) { - - $viewer = $this->getRequest()->getUser(); - $base_uri = $this->getRequest()->getRequestURI(); - - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Show Last Change')) - ->setHref( - $drequest->generateURI( - array( - 'action' => 'change', - ))) - ->setIcon('fa-backward')); - - if ($show_blame) { - $blame_text = pht('Disable Blame'); - $blame_icon = 'fa-exclamation-circle lightgreytext'; - $blame_value = 0; - } else { - $blame_text = pht('Enable Blame'); - $blame_icon = 'fa-exclamation-circle'; - $blame_value = 1; - } - - $view->addAction( - id(new PhabricatorActionView()) - ->setName($blame_text) - ->setHref($base_uri->alter('blame', $blame_value)) - ->setIcon($blame_icon) - ->setUser($viewer) - ->setRenderAsForm($viewer->isLoggedIn())); - - if ($show_color) { - $highlight_text = pht('Disable Highlighting'); - $highlight_icon = 'fa-star-o grey'; - $highlight_value = 0; - } else { - $highlight_text = pht('Enable Highlighting'); - $highlight_icon = 'fa-star'; - $highlight_value = 1; - } - - $view->addAction( - id(new PhabricatorActionView()) - ->setName($highlight_text) - ->setHref($base_uri->alter('color', $highlight_value)) - ->setIcon($highlight_icon) - ->setUser($viewer) - ->setRenderAsForm($viewer->isLoggedIn())); - - $href = null; - if ($this->getRequest()->getStr('lint') !== null) { - $lint_text = pht('Hide %d Lint Message(s)', count($this->lintMessages)); - $href = $base_uri->alter('lint', null); - - } else if ($this->lintCommit === null) { - $lint_text = pht('Lint not Available'); - } else { - $lint_text = pht( - 'Show %d Lint Message(s)', - count($this->lintMessages)); - $href = $this->getDiffusionRequest()->generateURI(array( - 'action' => 'browse', - 'commit' => $this->lintCommit, - ))->alter('lint', ''); - } - - $view->addAction( - id(new PhabricatorActionView()) - ->setName($lint_text) - ->setHref($href) - ->setIcon('fa-exclamation-triangle') - ->setDisabled(!$href)); - - return $view; - } - - private function renderEditButton() { - $request = $this->getRequest(); - $user = $request->getUser(); - - $drequest = $this->getDiffusionRequest(); - - $repository = $drequest->getRepository(); - $path = $drequest->getPath(); - $line = nonempty((int)$drequest->getLine(), 1); - - $editor_link = $user->loadEditorLink($path, $line, $repository); - $template = $user->loadEditorLink($path, '%l', $repository); - - $icon_edit = id(new PHUIIconView()) - ->setIconFont('fa-pencil'); - $button = id(new PHUIButtonView()) - ->setTag('a') - ->setText(pht('Open in Editor')) - ->setHref($editor_link) - ->setIcon($icon_edit) - ->setID('editor_link') - ->setMetadata(array('link_template' => $template)) - ->setDisabled(!$editor_link); - - return $button; - } - - private function renderFileButton($file_uri = null) { - - $base_uri = $this->getRequest()->getRequestURI(); - - if ($file_uri) { - $text = pht('Download Raw File'); - $href = $file_uri; - $icon = 'fa-download'; - } else { - $text = pht('View Raw File'); - $href = $base_uri->alter('view', 'raw'); - $icon = 'fa-file-text'; - } - - $iconview = id(new PHUIIconView()) - ->setIconFont($icon); - $button = id(new PHUIButtonView()) - ->setTag('a') - ->setText($text) - ->setHref($href) - ->setIcon($iconview); - - return $button; - } - - - private function buildDisplayRows( - array $text_list, - array $rev_list, - array $blame_dict, - $needs_blame, - DiffusionRequest $drequest, - $show_blame, - $show_color) { - - $handles = array(); - if ($blame_dict) { - $epoch_list = ipull(ifilter($blame_dict, 'epoch'), 'epoch'); - $epoch_min = min($epoch_list); - $epoch_max = max($epoch_list); - $epoch_range = ($epoch_max - $epoch_min) + 1; - - $author_phids = ipull(ifilter($blame_dict, 'authorPHID'), 'authorPHID'); - $handles = $this->loadViewerHandles($author_phids); - } - - $line_arr = array(); - $line_str = $drequest->getLine(); - $ranges = explode(',', $line_str); - foreach ($ranges as $range) { - if (strpos($range, '-') !== false) { - list($min, $max) = explode('-', $range, 2); - $line_arr[] = array( - 'min' => min($min, $max), - 'max' => max($min, $max), - ); - } else if (strlen($range)) { - $line_arr[] = array( - 'min' => $range, - 'max' => $range, - ); - } - } - - $display = array(); - - $line_number = 1; - $last_rev = null; - $color = null; - foreach ($text_list as $k => $line) { - $display_line = array( - 'epoch' => null, - 'commit' => null, - 'author' => null, - 'target' => null, - 'highlighted' => null, - 'line' => $line_number, - 'data' => $line, - ); - - if ($show_blame) { - // If the line's rev is same as the line above, show empty content - // with same color; otherwise generate blame info. The newer a change - // is, the more saturated the color. - - $rev = idx($rev_list, $k, $last_rev); - - if ($last_rev == $rev) { - $display_line['color'] = $color; - } else { - $blame = $blame_dict[$rev]; - - if (!isset($blame['epoch'])) { - $color = '#ffd'; // Render as warning. - } else { - $color_ratio = ($blame['epoch'] - $epoch_min) / $epoch_range; - $color_value = 0xE6 * (1.0 - $color_ratio); - $color = sprintf( - '#%02x%02x%02x', - $color_value, - 0xF6, - $color_value); - } - - $display_line['epoch'] = idx($blame, 'epoch'); - $display_line['color'] = $color; - $display_line['commit'] = $rev; - - $author_phid = idx($blame, 'authorPHID'); - if ($author_phid && $handles[$author_phid]) { - $author_link = $handles[$author_phid]->renderLink(); - } else { - $author_link = $blame['author']; - } - $display_line['author'] = $author_link; - - $last_rev = $rev; - } - } - - if ($line_arr) { - if ($line_number == $line_arr[0]['min']) { - $display_line['target'] = true; - } - foreach ($line_arr as $range) { - if ($line_number >= $range['min'] && - $line_number <= $range['max']) { - $display_line['highlighted'] = true; - } - } - } - - $display[] = $display_line; - ++$line_number; - } - - $request = $this->getRequest(); - $viewer = $request->getUser(); - - $commits = array_filter(ipull($display, 'commit')); - if ($commits) { - $commits = id(new DiffusionCommitQuery()) - ->setViewer($viewer) - ->withRepository($drequest->getRepository()) - ->withIdentifiers($commits) - ->execute(); - $commits = mpull($commits, null, 'getCommitIdentifier'); - } - - $revision_ids = id(new DifferentialRevision()) - ->loadIDsByCommitPHIDs(mpull($commits, 'getPHID')); - $revisions = array(); - if ($revision_ids) { - $revisions = id(new DifferentialRevisionQuery()) - ->setViewer($viewer) - ->withIDs($revision_ids) - ->execute(); - } - - $phids = array(); - foreach ($commits as $commit) { - if ($commit->getAuthorPHID()) { - $phids[] = $commit->getAuthorPHID(); - } - } - foreach ($revisions as $revision) { - if ($revision->getAuthorPHID()) { - $phids[] = $revision->getAuthorPHID(); - } - } - $handles = $this->loadViewerHandles($phids); - - Javelin::initBehavior('phabricator-oncopy', array()); - - $engine = null; - $inlines = array(); - if ($this->getRequest()->getStr('lint') !== null && $this->lintMessages) { - $engine = new PhabricatorMarkupEngine(); - $engine->setViewer($viewer); - - foreach ($this->lintMessages as $message) { - $inline = id(new PhabricatorAuditInlineComment()) - ->setSyntheticAuthor( - ArcanistLintSeverity::getStringForSeverity($message['severity']). - ' '.$message['code'].' ('.$message['name'].')') - ->setLineNumber($message['line']) - ->setContent($message['description']); - $inlines[$message['line']][] = $inline; - - $engine->addObject( - $inline, - PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); - } - - $engine->process(); - require_celerity_resource('differential-changeset-view-css'); - } - - $rows = $this->renderInlines( - idx($inlines, 0, array()), - $show_blame, - (bool)$this->coverage, - $engine); - - foreach ($display as $line) { - - $line_href = $drequest->generateURI( - array( - 'action' => 'browse', - 'line' => $line['line'], - 'stable' => true, - )); - - $blame = array(); - $style = null; - if (array_key_exists('color', $line)) { - if ($line['color']) { - $style = 'background: '.$line['color'].';'; - } - - $before_link = null; - $commit_link = null; - $revision_link = null; - if (idx($line, 'commit')) { - $commit = $line['commit']; - - if (idx($commits, $commit)) { - $tooltip = $this->renderCommitTooltip( - $commits[$commit], - $handles, - $line['author']); - } else { - $tooltip = null; - } - - Javelin::initBehavior('phabricator-tooltips', array()); - require_celerity_resource('aphront-tooltip-css'); - - $commit_link = javelin_tag( - 'a', - array( - 'href' => $drequest->generateURI( - array( - 'action' => 'commit', - 'commit' => $line['commit'], - )), - 'sigil' => 'has-tooltip', - 'meta' => array( - 'tip' => $tooltip, - 'align' => 'E', - 'size' => 600, - ), - ), - id(new PhutilUTF8StringTruncator()) - ->setMaximumGlyphs(9) - ->setTerminator('') - ->truncateString($line['commit'])); - - $revision_id = null; - if (idx($commits, $commit)) { - $revision_id = idx($revision_ids, $commits[$commit]->getPHID()); - } - - if ($revision_id) { - $revision = idx($revisions, $revision_id); - if ($revision) { - $tooltip = $this->renderRevisionTooltip($revision, $handles); - $revision_link = javelin_tag( - 'a', - array( - 'href' => '/D'.$revision->getID(), - 'sigil' => 'has-tooltip', - 'meta' => array( - 'tip' => $tooltip, - 'align' => 'E', - 'size' => 600, - ), - ), - 'D'.$revision->getID()); - } - } - - $uri = $line_href->alter('before', $commit); - $before_link = javelin_tag( - 'a', - array( - 'href' => $uri->setQueryParam('view', 'blame'), - 'sigil' => 'has-tooltip', - 'meta' => array( - 'tip' => pht('Skip Past This Commit'), - 'align' => 'E', - 'size' => 300, - ), - ), - "\xC2\xAB"); - } - - $blame[] = phutil_tag( - 'th', - array( - 'class' => 'diffusion-blame-link', - ), - $before_link); - - $object_links = array(); - $object_links[] = $commit_link; - if ($revision_link) { - $object_links[] = phutil_tag('span', array(), '/'); - $object_links[] = $revision_link; - } - - $blame[] = phutil_tag( - 'th', - array( - 'class' => 'diffusion-rev-link', - ), - $object_links); - } - - $line_link = phutil_tag( - 'a', - array( - 'href' => $line_href, - 'style' => $style, - ), - $line['line']); - - $blame[] = javelin_tag( - 'th', - array( - 'class' => 'diffusion-line-link', - 'sigil' => 'phabricator-source-line', - 'style' => $style, - ), - $line_link); - - Javelin::initBehavior('phabricator-line-linker'); - - if ($line['target']) { - Javelin::initBehavior( - 'diffusion-jump-to', - array( - 'target' => 'scroll_target', - )); - $anchor_text = phutil_tag( - 'a', - array( - 'id' => 'scroll_target', - ), - ''); - } else { - $anchor_text = null; - } - - $blame[] = phutil_tag( - 'td', - array( - ), - array( - $anchor_text, - - // NOTE: See phabricator-oncopy behavior. - "\xE2\x80\x8B", - - // TODO: [HTML] Not ideal. - phutil_safe_html(str_replace("\t", ' ', $line['data'])), - )); - - if ($this->coverage) { - require_celerity_resource('differential-changeset-view-css'); - $cov_index = $line['line'] - 1; - - if (isset($this->coverage[$cov_index])) { - $cov_class = $this->coverage[$cov_index]; - } else { - $cov_class = 'N'; - } - - $blame[] = phutil_tag( - 'td', - array( - 'class' => 'cov cov-'.$cov_class, - ), - ''); - } - - $rows[] = phutil_tag( - 'tr', - array( - 'class' => ($line['highlighted'] ? - 'phabricator-source-highlight' : - null), - ), - $blame); - - $cur_inlines = $this->renderInlines( - idx($inlines, $line['line'], array()), - $show_blame, - $this->coverage, - $engine); - foreach ($cur_inlines as $cur_inline) { - $rows[] = $cur_inline; - } - } - - return $rows; - } - - private function renderInlines( - array $inlines, - $needs_blame, - $has_coverage, - $engine) { - - $rows = array(); - foreach ($inlines as $inline) { - - // TODO: This should use modern scaffolding code. - - $inline_view = id(new PHUIDiffInlineCommentDetailView()) - ->setUser($this->getViewer()) - ->setMarkupEngine($engine) - ->setInlineComment($inline) - ->render(); - - $row = array_fill(0, ($needs_blame ? 3 : 1), phutil_tag('th')); - - $row[] = phutil_tag('td', array(), $inline_view); - - if ($has_coverage) { - $row[] = phutil_tag( - 'td', - array( - 'class' => 'cov cov-I', - )); - } - - $rows[] = phutil_tag('tr', array('class' => 'inline'), $row); - } - - return $rows; - } - - private function loadFileForData($path, $data) { - $file = PhabricatorFile::buildFromFileDataOrHash( - $data, - array( - 'name' => basename($path), - 'ttl' => time() + 60 * 60 * 24, - 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, - )); - - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - $file->attachToObject( - $this->getDiffusionRequest()->getRepository()->getPHID()); - unset($unguarded); - - return $file; - } - - private function buildRawResponse($path, $data) { - $file = $this->loadFileForData($path, $data); - return $file->getRedirectResponse(); - } - - private function buildImageCorpus($file_uri) { - $properties = new PHUIPropertyListView(); - - $properties->addImageContent( - phutil_tag( - 'img', - array( - 'src' => $file_uri, - ))); - - $file = $this->renderFileButton($file_uri); - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Image')) - ->addActionLink($file); - - return id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); - } - - private function buildBinaryCorpus($file_uri, $data) { - - $size = new PhutilNumber(strlen($data)); - $text = pht('This is a binary file. It is %s byte(s) in length.', $size); - $text = id(new PHUIBoxView()) - ->addPadding(PHUI::PADDING_LARGE) - ->appendChild($text); - - $file = $this->renderFileButton($file_uri); - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Details')) - ->addActionLink($file); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->appendChild($text); - - return $box; - } - - private function buildErrorCorpus($message) { - $text = id(new PHUIBoxView()) - ->addPadding(PHUI::PADDING_LARGE) - ->appendChild($message); - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Details')); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->appendChild($text); - - return $box; - } - - private function buildBeforeResponse($before) { - $request = $this->getRequest(); - $drequest = $this->getDiffusionRequest(); - - // NOTE: We need to get the grandparent so we can capture filename changes - // in the parent. - - $parent = $this->loadParentCommitOf($before); - $old_filename = null; - $was_created = false; - if ($parent) { - $grandparent = $this->loadParentCommitOf($parent); - - if ($grandparent) { - $rename_query = new DiffusionRenameHistoryQuery(); - $rename_query->setRequest($drequest); - $rename_query->setOldCommit($grandparent); - $rename_query->setViewer($request->getUser()); - $old_filename = $rename_query->loadOldFilename(); - $was_created = $rename_query->getWasCreated(); - } - } - - $follow = null; - if ($was_created) { - // If the file was created in history, that means older commits won't - // have it. Since we know it existed at 'before', it must have been - // created then; jump there. - $target_commit = $before; - $follow = 'created'; - } else if ($parent) { - // If we found a parent, jump to it. This is the normal case. - $target_commit = $parent; - } else { - // If there's no parent, this was probably created in the initial commit? - // And the "was_created" check will fail because we can't identify the - // grandparent. Keep the user at 'before'. - $target_commit = $before; - $follow = 'first'; - } - - $path = $drequest->getPath(); - $renamed = null; - if ($old_filename !== null && - $old_filename !== '/'.$path) { - $renamed = $path; - $path = $old_filename; - } - - $line = null; - // If there's a follow error, drop the line so the user sees the message. - if (!$follow) { - $line = $this->getBeforeLineNumber($target_commit); - } - - $before_uri = $drequest->generateURI( - array( - 'action' => 'browse', - 'commit' => $target_commit, - 'line' => $line, - 'path' => $path, - )); - - $before_uri->setQueryParams($request->getRequestURI()->getQueryParams()); - $before_uri = $before_uri->alter('before', null); - $before_uri = $before_uri->alter('renamed', $renamed); - $before_uri = $before_uri->alter('follow', $follow); - - return id(new AphrontRedirectResponse())->setURI($before_uri); - } - - private function getBeforeLineNumber($target_commit) { - $drequest = $this->getDiffusionRequest(); - - $line = $drequest->getLine(); - if (!$line) { - return null; - } - - $raw_diff = $this->callConduitWithDiffusionRequest( - 'diffusion.rawdiffquery', - array( - 'commit' => $drequest->getCommit(), - 'path' => $drequest->getPath(), - 'againstCommit' => $target_commit, - )); - $old_line = 0; - $new_line = 0; - - foreach (explode("\n", $raw_diff) as $text) { - if ($text[0] == '-' || $text[0] == ' ') { - $old_line++; - } - if ($text[0] == '+' || $text[0] == ' ') { - $new_line++; - } - if ($new_line == $line) { - return $old_line; - } - } - - // We didn't find the target line. - return $line; - } - - private function loadParentCommitOf($commit) { - $drequest = $this->getDiffusionRequest(); - $user = $this->getRequest()->getUser(); - - $before_req = DiffusionRequest::newFromDictionary( - array( - 'user' => $user, - 'repository' => $drequest->getRepository(), - 'commit' => $commit, - )); - - $parents = DiffusionQuery::callConduitWithDiffusionRequest( - $user, - $before_req, - 'diffusion.commitparentsquery', - array( - 'commit' => $commit, - )); - - return head($parents); - } - - private function renderRevisionTooltip( - DifferentialRevision $revision, - array $handles) { - $viewer = $this->getRequest()->getUser(); - - $date = phabricator_date($revision->getDateModified(), $viewer); - $id = $revision->getID(); - $title = $revision->getTitle(); - $header = "D{$id} {$title}"; - - $author = $handles[$revision->getAuthorPHID()]->getName(); - - return "{$header}\n{$date} \xC2\xB7 {$author}"; - } - - private function renderCommitTooltip( - PhabricatorRepositoryCommit $commit, - array $handles, - $author) { - - $viewer = $this->getRequest()->getUser(); - - $date = phabricator_date($commit->getEpoch(), $viewer); - $summary = trim($commit->getSummary()); - - if ($commit->getAuthorPHID()) { - $author = $handles[$commit->getAuthorPHID()]->getName(); - } - - return "{$summary}\n{$date} \xC2\xB7 {$author}"; - } - -} diff --git a/src/applications/diffusion/controller/DiffusionBrowseMainController.php b/src/applications/diffusion/controller/DiffusionBrowseMainController.php deleted file mode 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseMainController.php +++ /dev/null @@ -1,39 +0,0 @@ -diffusionRequest; - - // Figure out if we're browsing a directory, a file, or a search result - // list. Then delegate to the appropriate controller. - - $grep = $request->getStr('grep'); - $find = $request->getStr('find'); - if (strlen($grep) || strlen($find)) { - $controller = new DiffusionBrowseSearchController(); - } else { - $results = DiffusionBrowseResultSet::newFromConduit( - $this->callConduitWithDiffusionRequest( - 'diffusion.browsequery', - array( - 'path' => $drequest->getPath(), - 'commit' => $drequest->getStableCommit(), - ))); - $reason = $results->getReasonForEmptyResultSet(); - $is_file = ($reason == DiffusionBrowseResultSet::REASON_IS_FILE); - - if ($is_file) { - $controller = new DiffusionBrowseFileController($request); - } else { - $controller = new DiffusionBrowseDirectoryController($request); - $controller->setBrowseQueryResults($results); - } - } - - $controller->setDiffusionRequest($drequest); - $controller->setCurrentApplication($this->getCurrentApplication()); - return $this->delegateToController($controller); - } - -} diff --git a/src/applications/diffusion/controller/DiffusionBrowseSearchController.php b/src/applications/diffusion/controller/DiffusionBrowseSearchController.php deleted file mode 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseSearchController.php +++ /dev/null @@ -1,231 +0,0 @@ -diffusionRequest; - - $actions = $this->buildActionView($drequest); - $properties = $this->buildPropertyView($drequest, $actions); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($this->buildHeaderView($drequest)) - ->addPropertyList($properties); - - $content = array(); - - $content[] = $object_box; - $content[] = $this->renderSearchForm($collapsed = false); - $content[] = $this->renderSearchResults(); - - $crumbs = $this->buildCrumbs( - array( - 'branch' => true, - 'path' => true, - 'view' => 'browse', - )); - - return $this->buildApplicationPage( - array( - $crumbs, - $content, - ), - array( - 'title' => array( - nonempty(basename($drequest->getPath()), '/'), - $drequest->getRepository()->getDisplayName(), - ), - )); - } - - private function renderSearchResults() { - $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); - $results = array(); - - $limit = 100; - $page = $this->getRequest()->getInt('page', 0); - $pager = new PHUIPagerView(); - $pager->setPageSize($limit); - $pager->setOffset($page); - $pager->setURI($this->getRequest()->getRequestURI(), 'page'); - - $search_mode = null; - - switch ($repository->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - $results = array(); - break; - default: - if (strlen($this->getRequest()->getStr('grep'))) { - $search_mode = 'grep'; - $query_string = $this->getRequest()->getStr('grep'); - $results = $this->callConduitWithDiffusionRequest( - 'diffusion.searchquery', - array( - 'grep' => $query_string, - 'commit' => $drequest->getStableCommit(), - 'path' => $drequest->getPath(), - 'limit' => $limit + 1, - 'offset' => $page, - )); - } else { // Filename search. - $search_mode = 'find'; - $query_string = $this->getRequest()->getStr('find'); - $results = $this->callConduitWithDiffusionRequest( - 'diffusion.querypaths', - array( - 'pattern' => $query_string, - 'commit' => $drequest->getStableCommit(), - 'path' => $drequest->getPath(), - 'limit' => $limit + 1, - 'offset' => $page, - )); - } - break; - } - - $results = $pager->sliceResults($results); - - if ($search_mode == 'grep') { - $table = $this->renderGrepResults($results, $query_string); - $header = pht( - 'File content matching "%s" under "%s"', - $query_string, - nonempty($drequest->getPath(), '/')); - } else { - $table = $this->renderFindResults($results); - $header = pht( - 'Paths matching "%s" under "%s"', - $query_string, - nonempty($drequest->getPath(), '/')); - } - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText($header) - ->setTable($table); - - $pager_box = id(new PHUIBoxView()) - ->addMargin(PHUI::MARGIN_LARGE) - ->appendChild($pager); - - return array($box, $pager_box); - } - - private function renderGrepResults(array $results, $pattern) { - $drequest = $this->getDiffusionRequest(); - - require_celerity_resource('phabricator-search-results-css'); - - $rows = array(); - foreach ($results as $result) { - list($path, $line, $string) = $result; - - $href = $drequest->generateURI(array( - 'action' => 'browse', - 'path' => $path, - 'line' => $line, - )); - - $matches = null; - $count = @preg_match_all( - '('.$pattern.')u', - $string, - $matches, - PREG_OFFSET_CAPTURE); - - if (!$count) { - $output = ltrim($string); - } else { - $output = array(); - $cursor = 0; - $length = strlen($string); - foreach ($matches[0] as $match) { - $offset = $match[1]; - if ($cursor != $offset) { - $output[] = array( - 'text' => substr($string, $cursor, $offset), - 'highlight' => false, - ); - } - $output[] = array( - 'text' => $match[0], - 'highlight' => true, - ); - $cursor = $offset + strlen($match[0]); - } - if ($cursor != $length) { - $output[] = array( - 'text' => substr($string, $cursor), - 'highlight' => false, - ); - } - - if ($output) { - $output[0]['text'] = ltrim($output[0]['text']); - } - - foreach ($output as $key => $segment) { - if ($segment['highlight']) { - $output[$key] = phutil_tag('strong', array(), $segment['text']); - } else { - $output[$key] = $segment['text']; - } - } - } - - $string = phutil_tag( - 'pre', - array('class' => 'PhabricatorMonospaced phui-source-fragment'), - $output); - - $path = Filesystem::readablePath($path, $drequest->getPath()); - - $rows[] = array( - phutil_tag('a', array('href' => $href), $path), - $line, - $string, - ); - } - - $table = id(new AphrontTableView($rows)) - ->setClassName('remarkup-code') - ->setHeaders(array(pht('Path'), pht('Line'), pht('String'))) - ->setColumnClasses(array('', 'n', 'wide')) - ->setNoDataString( - pht( - 'The pattern you searched for was not found in the content of any '. - 'files.')); - - return $table; - } - - private function renderFindResults(array $results) { - $drequest = $this->getDiffusionRequest(); - - $rows = array(); - foreach ($results as $result) { - $href = $drequest->generateURI(array( - 'action' => 'browse', - 'path' => $result, - )); - - $readable = Filesystem::readablePath($result, $drequest->getPath()); - - $rows[] = array( - phutil_tag('a', array('href' => $href), $readable), - ); - } - - $table = id(new AphrontTableView($rows)) - ->setHeaders(array(pht('Path'))) - ->setColumnClasses(array('wide')) - ->setNoDataString( - pht( - 'The pattern you searched for did not match the names of any '. - 'files.')); - - return $table; - } - -}