diff --git a/src/applications/diffusion/conduit/ConduitAPI_diffusion_abstractquery_Method.php b/src/applications/diffusion/conduit/ConduitAPI_diffusion_abstractquery_Method.php index 1da13b9b4c..ea09339d33 100644 --- a/src/applications/diffusion/conduit/ConduitAPI_diffusion_abstractquery_Method.php +++ b/src/applications/diffusion/conduit/ConduitAPI_diffusion_abstractquery_Method.php @@ -1,170 +1,173 @@ diffusionRequest = $request; return $this; } protected function getDiffusionRequest() { return $this->diffusionRequest; } /** * A wee bit of magic here. If @{method:shouldCreateDiffusionRequest} * returns false, this function grabs a repository object based on the * callsign directly. Otherwise, the repository was loaded when we created a * @{class:DiffusionRequest}, so this function just pulls it out of the * @{class:DiffusionRequest}. * * @return @{class:PhabricatorRepository} $repository */ protected function getRepository(ConduitAPIRequest $request) { if (!$this->repository) { if ($this->shouldCreateDiffusionRequest()) { $this->repository = $this->getDiffusionRequest()->getRepository(); } else { $callsign = $request->getValue('callsign'); $repository = id(new PhabricatorRepository())->loadOneWhere( 'callsign = %s', $callsign); if (!$repository) { throw new ConduitException('ERR-UNKNOWN-REPOSITORY'); } $this->repository = $repository; } } return $this->repository; } /** * You should probably not mess with this unless your conduit method is * involved with the creation / validation / etc. of * @{class:DiffusionRequest}s. If you are dealing with * @{class:DiffusionRequest}, setting this to false should help avoid * infinite loops. */ protected function setShouldCreateDiffusionRequest($should) { $this->shouldCreateDiffusionRequest = $should; return $this; } private function shouldCreateDiffusionRequest() { return $this->shouldCreateDiffusionRequest; } final public function defineErrorTypes() { return $this->defineCustomErrorTypes() + array( 'ERR-UNKNOWN-REPOSITORY' => pht('There is no repository with that callsign.'), 'ERR-UNKNOWN-VCS-TYPE' => pht('Unknown repository VCS type.'), 'ERR-UNSUPPORTED-VCS' => pht('VCS is not supported for this method.')); } /** * Subclasses should override this to specify custom error types. */ protected function defineCustomErrorTypes() { return array(); } final public function defineParamTypes() { return $this->defineCustomParamTypes() + array( - 'callsign' => 'required string'); + 'callsign' => 'required string', + 'branch' => 'optional string', + ); } /** * Subclasses should override this to specify custom param types. */ protected function defineCustomParamTypes() { return array(); } /** * Subclasses should override these methods with the proper result for the * pertinent version control system, e.g. getGitResult for Git. * * If the result is not supported for that VCS, do not implement it. e.g. * Subversion (SVN) does not support branches. */ protected function getGitResult(ConduitAPIRequest $request) { throw new ConduitException('ERR-UNSUPPORTED-VCS'); } protected function getSVNResult(ConduitAPIRequest $request) { throw new ConduitException('ERR-UNSUPPORTED-VCS'); } protected function getMercurialResult(ConduitAPIRequest $request) { throw new ConduitException('ERR-UNSUPPORTED-VCS'); } /** * This method is final because most queries will need to construct a * @{class:DiffusionRequest} and use it. Consolidating this codepath and * enforcing @{method:getDiffusionRequest} works when we need it is good. * * @{method:getResult} should be overridden by subclasses as necessary, e.g. * there is a common operation across all version control systems that * should occur after @{method:getResult}, like formatting a timestamp. * * In the rare cases where one does not want to create a * @{class:DiffusionRequest} - suppose to avoid infinite loops in the * creation of a @{class:DiffusionRequest} - make sure to call * * $this->setShouldCreateDiffusionRequest(false); * * in the constructor of the pertinent Conduit method. */ final protected function execute(ConduitAPIRequest $request) { if ($this->shouldCreateDiffusionRequest()) { $drequest = DiffusionRequest::newFromDictionary( array( 'user' => $request->getUser(), 'callsign' => $request->getValue('callsign'), + 'branch' => $request->getValue('branch'), 'path' => $request->getValue('path'), 'commit' => $request->getValue('commit'), )); $this->setDiffusionRequest($drequest); } return $this->getResult($request); } protected function getResult(ConduitAPIRequest $request) { $repository = $this->getRepository($request); $result = null; switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $result = $this->getGitResult($request); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $result = $this->getMercurialResult($request); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $result = $this->getSVNResult($request); break; default: throw new ConduitException('ERR-UNKNOWN-VCS-TYPE'); break; } return $result; } } diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index a77a0fff17..ffe66a6055 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -1,275 +1,275 @@ diffusionRequest; $is_file = false; if ($this->getRequest()->getStr('before')) { $is_file = true; } else if ($this->getRequest()->getStr('grep') == '') { $results = DiffusionBrowseResultSet::newFromConduit( $this->callConduitWithDiffusionRequest( 'diffusion.browsequery', array( 'path' => $drequest->getPath(), 'commit' => $drequest->getCommit(), ))); $reason = $results->getReasonForEmptyResultSet(); $is_file = ($reason == DiffusionBrowseResultSet::REASON_IS_FILE); } if ($is_file) { $controller = new DiffusionBrowseFileController($this->getRequest()); $controller->setDiffusionRequest($drequest); $controller->setCurrentApplication($this->getCurrentApplication()); return $this->delegateToController($controller); } $content = array(); if ($drequest->getTagContent()) { $title = pht('Tag: %s', $drequest->getSymbolicCommit()); $tag_view = new AphrontPanelView(); $tag_view->setHeader($title); $tag_view->appendChild( $this->markupText($drequest->getTagContent())); $content[] = $tag_view; } $content[] = $this->renderSearchForm(); if ($this->getRequest()->getStr('grep') != '') { $content[] = $this->renderSearchResults(); } else { if (!$results->isValidResults()) { $empty_result = new DiffusionEmptyResultView(); $empty_result->setDiffusionRequest($drequest); $empty_result->setDiffusionBrowseResultSet($results); $empty_result->setView($this->getRequest()->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($this->getRequest()->getUser()); $browse_panel = new AphrontPanelView(); $browse_panel->appendChild($browse_table); $browse_panel->setNoBackground(); $content[] = $browse_panel; } $content[] = $this->buildOpenRevisions(); $readme = $this->callConduitWithDiffusionRequest( 'diffusion.readmequery', array( - 'paths' => $results->getPathDicts() + 'paths' => $results->getPathDicts(), )); if ($readme) { $box = new PHUIBoxView(); $box->setShadow(true); $box->appendChild($readme); $box->addPadding(PHUI::PADDING_LARGE); $box->addMargin(PHUI::MARGIN_LARGE); $header = id(new PhabricatorHeaderView()) ->setHeader(pht('README')); $content[] = array( $header, $box, ); } } $nav = $this->buildSideNav('browse', false); $nav->appendChild($content); $crumbs = $this->buildCrumbs( array( 'branch' => true, 'path' => true, 'view' => 'browse', )); $nav->setCrumbs($crumbs); return $this->buildApplicationPage( $nav, array( 'device' => true, 'dust' => true, 'title' => array( nonempty(basename($drequest->getPath()), '/'), $drequest->getRepository()->getCallsign().' Repository', ), )); } private function renderSearchForm() { $drequest = $this->getDiffusionRequest(); $form = id(new AphrontFormView()) ->setUser($this->getRequest()->getUser()) ->setMethod('GET') ->setNoShading(true); switch ($drequest->getRepository()->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $form->appendChild(pht('Search is not available in Subversion.')); break; default: $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Search Here')) ->setName('grep') ->setValue($this->getRequest()->getStr('grep')) ->setCaption(pht('Regular expression'))) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Grep'))); break; } $filter = new AphrontListFilterView(); $filter->appendChild($form); return $filter; } private function renderSearchResults() { $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $results = array(); $no_data = pht('No results found.'); $limit = 100; $page = $this->getRequest()->getInt('page', 0); $pager = new AphrontPagerView(); $pager->setPageSize($limit); $pager->setOffset($page); $pager->setURI($this->getRequest()->getRequestURI(), 'page'); try { $results = $this->callConduitWithDiffusionRequest( 'diffusion.searchquery', array( 'grep' => $this->getRequest()->getStr('grep'), 'stableCommitName' => $drequest->getStableCommitName(), 'path' => $drequest->getPath(), 'limit' => $limit + 1, 'offset' => $page)); } catch (ConduitException $ex) { $err = $ex->getErrorDescription(); if ($err != '') { return id(new AphrontErrorView()) ->setTitle(pht('Search Error')) ->appendChild($err); } } $results = $pager->sliceResults($results); require_celerity_resource('syntax-highlighting-css'); // NOTE: This can be wrong because we may find the string inside the // comment. But it's correct in most cases and highlighting the whole file // would be too expensive. $futures = array(); $engine = PhabricatorSyntaxHighlighter::newEngine(); foreach ($results as $result) { list($path, $line, $string) = $result; $futures["{$path}:{$line}"] = $engine->getHighlightFuture( $engine->getLanguageFromFilename($path), ltrim($string)); } try { Futures($futures)->limit(8)->resolveAll(); } catch (PhutilSyntaxHighlighterException $ex) { } $rows = array(); foreach ($results as $result) { list($path, $line, $string) = $result; $href = $drequest->generateURI(array( 'action' => 'browse', 'path' => $path, 'line' => $line, )); try { $string = $futures["{$path}:{$line}"]->resolve(); } catch (PhutilSyntaxHighlighterException $ex) { } $string = phutil_tag( 'pre', array('class' => 'PhabricatorMonospaced'), $string); $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($no_data); return id(new AphrontPanelView()) ->setHeader(pht('Search Results')) ->appendChild($table) ->appendChild($pager); } private function markupText($text) { $engine = PhabricatorMarkupEngine::newDiffusionMarkupEngine(); $engine->setConfig('viewer', $this->getRequest()->getUser()); $text = $engine->markupText($text); $text = phutil_tag( 'div', array( 'class' => 'phabricator-remarkup', ), $text); return $text; } } diff --git a/src/applications/diffusion/query/DiffusionQuery.php b/src/applications/diffusion/query/DiffusionQuery.php index f377afb839..992cc6b6d6 100644 --- a/src/applications/diffusion/query/DiffusionQuery.php +++ b/src/applications/diffusion/query/DiffusionQuery.php @@ -1,199 +1,204 @@ } protected static function newQueryObject( $base_class, DiffusionRequest $request) { $repository = $request->getRepository(); $obj = self::initQueryObject($base_class, $repository); $obj->request = $request; return $obj; } final protected static function initQueryObject( $base_class, PhabricatorRepository $repository) { $map = array( PhabricatorRepositoryType::REPOSITORY_TYPE_GIT => 'Git', PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL => 'Mercurial', PhabricatorRepositoryType::REPOSITORY_TYPE_SVN => 'Svn', ); $name = idx($map, $repository->getVersionControlSystem()); if (!$name) { throw new Exception("Unsupported VCS!"); } $class = str_replace('Diffusion', 'Diffusion'.$name, $base_class); $obj = new $class(); return $obj; } final protected function getRequest() { return $this->request; } final public static function callConduitWithDiffusionRequest( PhabricatorUser $user, DiffusionRequest $drequest, $method, array $params = array()) { $repository = $drequest->getRepository(); $core_params = array( - 'callsign' => $repository->getCallsign() + 'callsign' => $repository->getCallsign(), ); + + if ($drequest->getBranch() !== null) { + $core_params['branch'] = $drequest->getBranch(); + } + $params = $params + $core_params; return id(new ConduitCall( $method, $params )) ->setUser($user) ->execute(); } public function execute() { return $this->executeQuery(); } abstract protected function executeQuery(); /* -( Query Utilities )---------------------------------------------------- */ final public static function loadCommitsByIdentifiers( array $identifiers, DiffusionRequest $drequest) { if (!$identifiers) { return array(); } $commits = array(); $commit_data = array(); $repository = $drequest->getRepository(); $commits = id(new PhabricatorRepositoryCommit())->loadAllWhere( 'repositoryID = %d AND commitIdentifier IN (%Ls)', $repository->getID(), $identifiers); $commits = mpull($commits, null, 'getCommitIdentifier'); // Build empty commit objects for every commit, so we can show unparsed // commits in history views as "unparsed" instead of not showing them. This // makes the process of importing and parsing commits much clearer to the // user. $commit_list = array(); foreach ($identifiers as $identifier) { $commit_obj = idx($commits, $identifier); if (!$commit_obj) { $commit_obj = new PhabricatorRepositoryCommit(); $commit_obj->setRepositoryID($repository->getID()); $commit_obj->setCommitIdentifier($identifier); $commit_obj->setIsUnparsed(true); $commit_obj->makeEphemeral(); } $commit_list[$identifier] = $commit_obj; } $commits = $commit_list; $commit_ids = array_filter(mpull($commits, 'getID')); if ($commit_ids) { $commit_data = id(new PhabricatorRepositoryCommitData())->loadAllWhere( 'commitID in (%Ld)', $commit_ids); $commit_data = mpull($commit_data, null, 'getCommitID'); } foreach ($commits as $commit) { if (!$commit->getID()) { continue; } if (idx($commit_data, $commit->getID())) { $commit->attachCommitData($commit_data[$commit->getID()]); } } return $commits; } final public static function loadHistoryForCommitIdentifiers( array $identifiers, DiffusionRequest $drequest) { if (!$identifiers) { return array(); } $repository = $drequest->getRepository(); $commits = self::loadCommitsByIdentifiers($identifiers, $drequest); if (!$commits) { return array(); } $path = $drequest->getPath(); $conn_r = $repository->establishConnection('r'); $path_normal = DiffusionPathIDQuery::normalizePath($path); $paths = queryfx_all( $conn_r, 'SELECT id, path FROM %T WHERE pathHash IN (%Ls)', PhabricatorRepository::TABLE_PATH, array(md5($path_normal))); $paths = ipull($paths, 'id', 'path'); $path_id = idx($paths, $path_normal); $commit_ids = array_filter(mpull($commits, 'getID')); $path_changes = array(); if ($path_id && $commit_ids) { $path_changes = queryfx_all( $conn_r, 'SELECT * FROM %T WHERE commitID IN (%Ld) AND pathID = %d', PhabricatorRepository::TABLE_PATHCHANGE, $commit_ids, $path_id); $path_changes = ipull($path_changes, null, 'commitID'); } $history = array(); foreach ($identifiers as $identifier) { $item = new DiffusionPathChange(); $item->setCommitIdentifier($identifier); $commit = idx($commits, $identifier); if ($commit) { $item->setCommit($commit); try { $item->setCommitData($commit->getCommitData()); } catch (Exception $ex) { // Ignore, commit just doesn't have data. } $change = idx($path_changes, $commit->getID()); if ($change) { $item->setChangeType($change['changeType']); $item->setFileType($change['fileType']); } } $history[] = $item; } return $history; } }