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 @@ -608,6 +608,7 @@ 'DiffusionCommitRevisionReviewersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionReviewersHeraldField.php', 'DiffusionCommitRevisionSubscribersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionSubscribersHeraldField.php', 'DiffusionCommitTagsController' => 'applications/diffusion/controller/DiffusionCommitTagsController.php', + 'DiffusionCompareController' => 'applications/diffusion/controller/DiffusionCompareController.php', 'DiffusionConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionConduitAPIMethod.php', 'DiffusionController' => 'applications/diffusion/controller/DiffusionController.php', 'DiffusionCreateCommentConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionCreateCommentConduitAPIMethod.php', @@ -4688,6 +4689,7 @@ 'DiffusionCommitRevisionReviewersHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitRevisionSubscribersHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitTagsController' => 'DiffusionController', + 'DiffusionCompareController' => 'DiffusionController', 'DiffusionConduitAPIMethod' => 'ConduitAPIMethod', 'DiffusionController' => 'PhabricatorController', 'DiffusionCreateCommentConduitAPIMethod' => 'DiffusionConduitAPIMethod', 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 @@ -77,6 +77,7 @@ 'browse/(?P.*)' => 'DiffusionBrowseController', 'lastmodified/(?P.*)' => 'DiffusionLastModifiedController', 'diff/' => 'DiffusionDiffController', + 'compare/' => 'DiffusionCompareController', 'tags/(?P.*)' => 'DiffusionTagListController', 'branches/(?P.*)' => 'DiffusionBranchTableController', 'refs/(?P.*)' => 'DiffusionRefTableController', diff --git a/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php --- a/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php @@ -4,6 +4,7 @@ extends DiffusionQueryConduitAPIMethod { private $parents = array(); + private $mergeBase; public function getAPIMethodName() { return 'diffusion.historyquery'; @@ -22,6 +23,7 @@ protected function defineCustomParamTypes() { return array( 'commit' => 'required string', + 'against' => 'optional string', 'path' => 'required string', 'offset' => 'required int', 'limit' => 'required int', @@ -36,6 +38,7 @@ return array( 'pathChanges' => mpull($path_changes, 'toDictionary'), 'parents' => $this->parents, + 'mergeBase' => $this->mergeBase, ); } @@ -43,10 +46,17 @@ $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $commit_hash = $request->getValue('commit'); + $against_hash = $request->getValue('against'); $path = $request->getValue('path'); $offset = $request->getValue('offset'); $limit = $request->getValue('limit'); + if (strlen($against_hash)) { + $commit_range = "${against_hash}..${commit_hash}"; + } else { + $commit_range = $commit_hash; + } + list($stdout) = $repository->execxLocalCommand( 'log '. '--skip=%d '. @@ -56,15 +66,12 @@ $offset, $limit, '%H:%P', - $commit_hash, + $commit_range, // Git omits merge commits if the path is provided, even if it is empty. (strlen($path) ? csprintf('%s', $path) : '')); $lines = explode("\n", trim($stdout)); $lines = array_filter($lines); - if (!$lines) { - return array(); - } $hash_list = array(); $parent_map = array(); @@ -76,6 +83,19 @@ $this->parents = $parent_map; + if (strlen($against_hash)) { + list($stdout) = $repository->execxLocalCommand( + 'merge-base %s %s', + $commit_hash, + $against_hash); + + $this->mergeBase = $stdout; + } + + if (!$hash_list) { + return array(); + } + return DiffusionQuery::loadHistoryForCommitIdentifiers( $hash_list, $drequest); diff --git a/src/applications/diffusion/controller/DiffusionCompareController.php b/src/applications/diffusion/controller/DiffusionCompareController.php new file mode 100644 --- /dev/null +++ b/src/applications/diffusion/controller/DiffusionCompareController.php @@ -0,0 +1,263 @@ +loadDiffusionContext(); + if ($response) { + return $response; + } + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $content = array(); + + $crumbs = $this->buildCrumbs(array('view' => 'compare')); + + $empty_title = null; + $error_message = null; + + if ($repository->getVersionControlSystem() != + PhabricatorRepositoryType::REPOSITORY_TYPE_GIT) { + $empty_title = pht('Not supported'); + $error_message = pht( + 'This feature is not yet supported for %s repositories.', + $repository->getVersionControlSystem()); + $content[] = id(new PHUIInfoView()) + ->setTitle($empty_title) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setErrors(array($error_message)); + } + + $from_ref = $request->getStr('from'); + $onto_ref = $request->getStr('onto'); + + $refs = id(new DiffusionCachedResolveRefsQuery()) + ->setRepository($repository) + ->withRefs(array($from_ref, $onto_ref)) + ->execute(); + + + if (count($refs) == 2) { + $content[] = $this->buildCompareContent($drequest); + } else { + $content[] = $this->buildCompareForm($request, $refs); + } + + + return $this->newPage() + ->setTitle( + array( + $repository->getName(), + $repository->getDisplayName(), + )) + ->setCrumbs($crumbs) + ->appendChild($content); + } + + private function buildCompareForm(AphrontRequest $request, array $resolved) { + $viewer = $this->getViewer(); + + $from_ref = $request->getStr('from'); + $onto_ref = $request->getStr('onto'); + + if (idx($resolved, $from_ref)) { + $e_from = null; + } else { + $e_from = 'Not Found'; + } + + if (idx($resolved, $onto_ref)) { + $e_onto = null; + } else { + $e_onto = 'Not Found'; + } + + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->setMethod('GET') + ->appendControl( + id(new AphrontFormTextControl()) + ->setLabel(pht('From')) + ->setName('from') + ->setError($e_from) + ->setValue($from_ref)) + ->appendControl( + id(new AphrontFormTextControl()) + ->setLabel(pht('Onto')) + ->setName('onto') + ->setError($e_onto) + ->setValue($onto_ref)) + ->appendControl( + id(new AphrontFormSubmitControl()) + ->setValue('Compare')); + + return $form; + } + + private function buildCompareContent(DiffusionRequest $drequest) { + $request = $this->getRequest(); + $repository = $drequest->getRepository(); + + $from_ref = $request->getStr('from'); + $onto_ref = $request->getStr('onto'); + + $content = array(); + + $pager = id(new PHUIPagerView()) + ->readFromRequest($request); + + try { + $history_results = $this->callConduitWithDiffusionRequest( + 'diffusion.historyquery', + array( + 'commit' => $from_ref, + 'against' => $onto_ref, + 'path' => $drequest->getPath(), + 'offset' => $pager->getOffset(), + 'limit' => $pager->getPageSize() + 1, + )); + $history = DiffusionPathChange::newFromConduit( + $history_results['pathChanges']); + + $history = $pager->sliceResults($history); + + $history_exception = null; + } catch (Exception $ex) { + $history_results = null; + $history = null; + $history_exception = $ex; + } + + if ($history_results) { + $content[] = $this->buildCompareProperties($drequest, $history_results); + } + $content[] = $this->buildCompareForm( + $request, + array($from_ref => true, $onto_ref => true)); + + $content[] = $this->buildHistoryTable( + $history_results, + $history, + $pager, + $history_exception); + + $content[] = $this->renderTablePagerBox($pager); + + return $content; + } + + private function buildCompareProperties($drequest, array $history_results) { + $viewer = $this->getViewer(); + + $request = $this->getRequest(); + $repository = $drequest->getRepository(); + + $from_ref = $request->getStr('from'); + $onto_ref = $request->getStr('onto'); + + $reverse_uri = $repository->getPathURI( + "compare/?from=${onto_ref}&onto=${from_ref}"); + $actions = id(new PhabricatorActionListView()); + $actions->setUser($viewer); + + + $actions->addAction(id(new PhabricatorActionView()) + ->setName(pht('Reverse Comparison')) + ->setHref($reverse_uri) + ->setIcon('fa-list')); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer) + ->setActionList($actions); + + $readme = + 'These are the commits that will be added to **Onto** if you merge '. + '**From** onto it.'; + $readme = new PHUIRemarkupView($viewer, $readme); + $view->addTextContent($readme); + + $view->addProperty(pht('From'), $from_ref); + $view->addProperty(pht('Onto'), $onto_ref); + $view->addProperty( + pht('merge-base'), + new PHUIRemarkupView($viewer, idx($history_results, 'mergeBase'))); + + $header = id(new PHUIHeaderView()) + ->setUser($viewer) + ->setPolicyObject($drequest->getRepository()); + + $box = id(new PHUIObjectBoxView()) + ->setUser($viewer) + ->setHeader($header) + ->addPropertyList($view); + return $box; + } + + private function buildHistoryTable( + $history_results, + $history, + $pager, + $history_exception) { + + $request = $this->getRequest(); + $viewer = $request->getUser(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + if ($history_exception) { + if ($repository->isImporting()) { + return $this->renderStatusMessage( + pht('Still Importing...'), + pht( + 'This repository is still importing. History is not yet '. + 'available.')); + } else { + return $this->renderStatusMessage( + pht('Unable to Retrieve History'), + $history_exception->getMessage()); + } + } + + if (!$history) { + return $this->renderStatusMessage( + pht('Up to date'), + new PHUIRemarkupView( + $viewer, + pht( + '**Onto** is strictly ahead of **From** '. + '- no commits are missing.'))); + } + + $history_table = id(new DiffusionHistoryTableView()) + ->setUser($viewer) + ->setDiffusionRequest($drequest) + ->setHistory($history); + + // TODO: Super sketchy? + $history_table->loadRevisions(); + + if ($history_results) { + $history_table->setParents($history_results['parents']); + $history_table->setIsHead(!$pager->getOffset()); + $history_table->setIsTail(!$pager->getHasMorePages()); + } + + // TODO also expose diff. + + $panel = new PHUIObjectBoxView(); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Missing Commits')); + $panel->setHeader($header); + $panel->setTable($history_table); + + return $panel; + } +} diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -206,6 +206,9 @@ case 'change': $view_name = pht('Change'); break; + case 'compare': + $view_name = pht('Compare'); + break; } $crumb = id(new PHUICrumbView()) diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -506,6 +506,12 @@ ->setHref($push_uri)); } + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Compare Branches')) + ->setIcon('fa-usb') + ->setHref($repository->getPathURI('compare/'))); + return $view; }