diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index c3d27ac196..2034e55743 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -1,457 +1,459 @@ <?php final class DiffusionRepositoryController extends DiffusionController { public function shouldAllowPublic() { return true; } public function processRequest() { $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $content = array(); $crumbs = $this->buildCrumbs(); $content[] = $crumbs; $content[] = $this->buildPropertiesTable($drequest->getRepository()); $phids = array(); try { $history_results = $this->callConduitWithDiffusionRequest( 'diffusion.historyquery', array( 'commit' => $drequest->getCommit(), 'path' => $drequest->getPath(), 'offset' => 0, 'limit' => 15)); $history = DiffusionPathChange::newFromConduit( $history_results['pathChanges']); foreach ($history as $item) { $data = $item->getCommitData(); if ($data) { if ($data->getCommitDetail('authorPHID')) { $phids[$data->getCommitDetail('authorPHID')] = true; } if ($data->getCommitDetail('committerPHID')) { $phids[$data->getCommitDetail('committerPHID')] = true; } } } $history_exception = null; } catch (Exception $ex) { $history_results = null; $history = null; $history_exception = $ex; } try { $browse_results = DiffusionBrowseResultSet::newFromConduit( $this->callConduitWithDiffusionRequest( 'diffusion.browsequery', array( 'path' => $drequest->getPath(), 'commit' => $drequest->getCommit(), ))); $browse_paths = $browse_results->getPaths(); foreach ($browse_paths as $item) { $data = $item->getLastCommitData(); if ($data) { if ($data->getCommitDetail('authorPHID')) { $phids[$data->getCommitDetail('authorPHID')] = true; } if ($data->getCommitDetail('committerPHID')) { $phids[$data->getCommitDetail('committerPHID')] = true; } } } $browse_exception = null; } catch (Exception $ex) { $browse_results = null; $browse_paths = null; $browse_exception = $ex; } $phids = array_keys($phids); $handles = $this->loadViewerHandles($phids); if ($browse_results) { $readme = $this->callConduitWithDiffusionRequest( 'diffusion.readmequery', array( 'paths' => $browse_results->getPathDicts() )); } else { $readme = null; } $content[] = $this->buildHistoryTable( $history_results, $history, $history_exception, $handles); $content[] = $this->buildBrowseTable( $browse_results, $browse_paths, $browse_exception, $handles); try { $content[] = $this->buildTagListTable($drequest); } catch (Exception $ex) { if (!$repository->isImporting()) { $content[] = $this->renderStatusMessage( pht('Unable to Load Tags'), $ex->getMessage()); } } try { $content[] = $this->buildBranchListTable($drequest); } catch (Exception $ex) { if (!$repository->isImporting()) { $content[] = $this->renderStatusMessage( pht('Unable to Load Branches'), $ex->getMessage()); } } if ($readme) { $box = new PHUIBoxView(); $box->setShadow(true); $box->appendChild($readme); $box->addPadding(PHUI::PADDING_LARGE); $panel = new AphrontPanelView(); $panel->setHeader(pht('README')); $panel->setNoBackground(); $panel->appendChild($box); $content[] = $panel; } return $this->buildApplicationPage( $content, array( 'title' => $drequest->getRepository()->getName(), 'device' => true, )); } private function buildPropertiesTable(PhabricatorRepository $repository) { $user = $this->getRequest()->getUser(); $header = id(new PHUIHeaderView()) ->setHeader($repository->getName()) ->setUser($user) ->setPolicyObject($repository); if (!$repository->isTracked()) { $header->setStatus('policy-noone', '', pht('Inactive')); } else if ($repository->isImporting()) { $header->setStatus('time', 'red', pht('Importing...')); } else { $header->setStatus('oh-ok', '', pht('Active')); } $actions = $this->buildActionList($repository); $view = id(new PHUIPropertyListView()) ->setUser($user); $view->addProperty(pht('Callsign'), $repository->getCallsign()); switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $view->addProperty( pht('Clone URI'), $repository->getPublicRemoteURI()); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $view->addProperty( pht('Repository Root'), $repository->getPublicRemoteURI()); break; } $description = $repository->getDetail('description'); if (strlen($description)) { $description = PhabricatorMarkupEngine::renderOneObject( $repository, 'description', $user); $view->addSectionHeader(pht('Description')); $view->addTextContent($description); } $view->setActionList($actions); return id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($view); } private function buildBranchListTable(DiffusionRequest $drequest) { $viewer = $this->getRequest()->getUser(); if ($drequest->getBranch() === null) { return null; } $limit = 15; $branches = DiffusionBranchInformation::newFromConduit( $this->callConduitWithDiffusionRequest( 'diffusion.branchquery', array( 'limit' => $limit + 1, ))); if (!$branches) { return null; } $more_branches = (count($branches) > $limit); $branches = array_slice($branches, 0, $limit); $commits = id(new DiffusionCommitQuery()) ->setViewer($viewer) ->withIdentifiers(mpull($branches, 'getHeadCommitIdentifier')) ->withRepositoryIDs(array($drequest->getRepository()->getID())) ->execute(); - $table = new DiffusionBranchTableView(); - $table->setDiffusionRequest($drequest); - $table->setBranches($branches); - $table->setCommits($commits); - $table->setUser($this->getRequest()->getUser()); + $table = id(new DiffusionBranchTableView()) + ->setUser($viewer) + ->setDiffusionRequest($drequest) + ->setBranches($branches) + ->setCommits($commits); - $panel = new AphrontPanelView(); - $panel->setHeader(pht('Branches')); - $panel->setNoBackground(); + $panel = id(new AphrontPanelView()) + ->setHeader(pht('Branches')) + ->setNoBackground(); if ($more_branches) { $panel->setCaption(pht('Showing %d branches.', $limit)); } $panel->addButton( phutil_tag( 'a', array( 'href' => $drequest->generateURI( array( 'action' => 'branches', )), 'class' => 'grey button', ), pht("Show All Branches \xC2\xBB"))); $panel->appendChild($table); return $panel; } private function buildTagListTable(DiffusionRequest $drequest) { + $viewer = $this->getRequest()->getUser(); + $tag_limit = 15; $tags = array(); try { $tags = DiffusionRepositoryTag::newFromConduit( $this->callConduitWithDiffusionRequest( 'diffusion.tagsquery', array( // On the home page, we want to find tags on any branch. 'commit' => null, 'limit' => $tag_limit + 1, ))); } catch (ConduitException $e) { if ($e->getMessage() != 'ERR-UNSUPPORTED-VCS') { throw $e; } } if (!$tags) { return null; } $more_tags = (count($tags) > $tag_limit); $tags = array_slice($tags, 0, $tag_limit); - $commits = id(new PhabricatorAuditCommitQuery()) - ->withIdentifiers( - $drequest->getRepository()->getID(), - mpull($tags, 'getCommitIdentifier')) + $commits = id(new DiffusionCommitQuery()) + ->setViewer($viewer) + ->withIdentifiers(mpull($tags, 'getCommitIdentifier')) + ->withRepositoryIDs(array($drequest->getRepository()->getID())) ->needCommitData(true) ->execute(); - $view = new DiffusionTagListView(); - $view->setDiffusionRequest($drequest); - $view->setTags($tags); - $view->setUser($this->getRequest()->getUser()); - $view->setCommits($commits); + $view = id(new DiffusionTagListView()) + ->setUser($viewer) + ->setDiffusionRequest($drequest) + ->setTags($tags) + ->setCommits($commits); $phids = $view->getRequiredHandlePHIDs(); $handles = $this->loadViewerHandles($phids); $view->setHandles($handles); - $panel = new AphrontPanelView(); - $panel->setHeader(pht('Tags')); - $panel->setNoBackground(true); + $panel = id(new AphrontPanelView()) + ->setHeader(pht('Tags')) + ->setNoBackground(true); if ($more_tags) { $panel->setCaption(pht('Showing the %d most recent tags.', $tag_limit)); } $panel->addButton( phutil_tag( 'a', array( 'href' => $drequest->generateURI( array( 'action' => 'tags', )), 'class' => 'grey button', ), pht("Show All Tags \xC2\xBB"))); $panel->appendChild($view); return $panel; } private function buildActionList(PhabricatorRepository $repository) { $viewer = $this->getRequest()->getUser(); $view_uri = $this->getApplicationURI($repository->getCallsign().'/'); $edit_uri = $this->getApplicationURI($repository->getCallsign().'/edit/'); $view = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObject($repository) ->setObjectURI($view_uri); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $repository, PhabricatorPolicyCapability::CAN_EDIT); $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Repository')) ->setIcon('edit') ->setHref($edit_uri) ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); return $view; } private function buildHistoryTable( $history_results, $history, $history_exception, array $handles) { $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()); } } $history_table = id(new DiffusionHistoryTableView()) ->setUser($viewer) ->setDiffusionRequest($drequest) ->setHandles($handles) ->setHistory($history); // TODO: Super sketchy. $history_table->loadRevisions(); if ($history_results) { $history_table->setParents($history_results['parents']); } $history_table->setIsHead(true); $callsign = $drequest->getRepository()->getCallsign(); $all = phutil_tag( 'a', array( 'href' => $drequest->generateURI( array( 'action' => 'history', )), ), pht('View Full Commit History')); $panel = new AphrontPanelView(); $panel->setHeader(pht("Recent Commits · %s", $all)); $panel->appendChild($history_table); $panel->setNoBackground(); return $panel; } private function buildBrowseTable( $browse_results, $browse_paths, $browse_exception, array $handles) { $request = $this->getRequest(); $viewer = $request->getUser(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); if ($browse_exception) { if ($repository->isImporting()) { // The history table renders a useful message. return null; } else { return $this->renderStatusMessage( pht('Unable to Retrieve Paths'), $browse_exception->getMessage()); } } $browse_table = id(new DiffusionBrowseTableView()) ->setUser($viewer) ->setDiffusionRequest($drequest) ->setHandles($handles); if ($browse_paths) { $browse_table->setPaths($browse_paths); } else { $browse_table->setPaths(array()); } $browse_uri = $drequest->generateURI(array('action' => 'browse')); $browse_panel = new AphrontPanelView(); $browse_panel->setHeader( phutil_tag( 'a', array('href' => $browse_uri), pht('Browse Repository'))); $browse_panel->appendChild($browse_table); $browse_panel->setNoBackground(); return $browse_panel; } } diff --git a/src/applications/diffusion/view/DiffusionHistoryTableView.php b/src/applications/diffusion/view/DiffusionHistoryTableView.php index 6cf855da43..cc35e160a6 100644 --- a/src/applications/diffusion/view/DiffusionHistoryTableView.php +++ b/src/applications/diffusion/view/DiffusionHistoryTableView.php @@ -1,334 +1,334 @@ <?php final class DiffusionHistoryTableView extends DiffusionView { private $history; private $revisions = array(); private $handles = array(); private $isHead; private $parents; public function setHistory(array $history) { assert_instances_of($history, 'DiffusionPathChange'); $this->history = $history; return $this; } public function loadRevisions() { $commit_phids = array(); foreach ($this->history as $item) { if ($item->getCommit()) { $commit_phids[] = $item->getCommit()->getPHID(); } } // TODO: Get rid of this. $this->revisions = id(new DifferentialRevision()) ->loadIDsByCommitPHIDs($commit_phids); return $this; } public function setHandles(array $handles) { assert_instances_of($handles, 'PhabricatorObjectHandle'); $this->handles = $handles; return $this; } public function getRequiredHandlePHIDs() { $phids = array(); foreach ($this->history as $item) { $data = $item->getCommitData(); if ($data) { if ($data->getCommitDetail('authorPHID')) { $phids[$data->getCommitDetail('authorPHID')] = true; } if ($data->getCommitDetail('committerPHID')) { $phids[$data->getCommitDetail('committerPHID')] = true; } } } return array_keys($phids); } public function setParents(array $parents) { $this->parents = $parents; return $this; } public function setIsHead($is_head) { $this->isHead = $is_head; return $this; } public function render() { $drequest = $this->getDiffusionRequest(); $handles = $this->handles; $graph = null; if ($this->parents) { $graph = $this->renderGraph(); } $rows = array(); $ii = 0; foreach ($this->history as $history) { $epoch = $history->getEpoch(); if ($epoch) { $date = phabricator_date($epoch, $this->user); $time = phabricator_time($epoch, $this->user); } else { $date = null; $time = null; } $data = $history->getCommitData(); $author_phid = $committer = $committer_phid = null; if ($data) { $author_phid = $data->getCommitDetail('authorPHID'); $committer_phid = $data->getCommitDetail('committerPHID'); $committer = $data->getCommitDetail('committer'); } if ($author_phid && isset($handles[$author_phid])) { $author = $handles[$author_phid]->renderLink(); } else { $author = self::renderName($history->getAuthorName()); } $different_committer = false; if ($committer_phid) { $different_committer = ($committer_phid != $author_phid); } else if ($committer != '') { $different_committer = ($committer != $history->getAuthorName()); } if ($different_committer) { if ($committer_phid && isset($handles[$committer_phid])) { $committer = $handles[$committer_phid]->renderLink(); } else { $committer = self::renderName($committer); } $author = hsprintf('%s/%s', $author, $committer); } $commit = $history->getCommit(); - if ($commit && !$commit->getIsUnparsed() && $data) { + if ($commit && $commit->isImported() && $data) { $change = $this->linkChange( $history->getChangeType(), $history->getFileType(), $path = null, $history->getCommitIdentifier()); } else { $change = phutil_tag('em', array(), "Importing\xE2\x80\xA6"); } $rows[] = array( $this->linkBrowse( $drequest->getPath(), array( 'commit' => $history->getCommitIdentifier(), )), $graph ? $graph[$ii++] : null, self::linkCommit( $drequest->getRepository(), $history->getCommitIdentifier()), ($commit ? self::linkRevision(idx($this->revisions, $commit->getPHID())) : null), $change, $date, $time, $author, AphrontTableView::renderSingleDisplayLine($history->getSummary()), // TODO: etc etc ); } $view = new AphrontTableView($rows); $view->setHeaders( array( pht('Browse'), '', pht('Commit'), pht('Revision'), pht('Change'), pht('Date'), pht('Time'), pht('Author/Committer'), pht('Details'), )); $view->setColumnClasses( array( '', 'threads', 'n', 'n', '', '', 'right', '', 'wide', )); $view->setColumnVisibility( array( true, $graph ? true : false, )); return $view->render(); } /** * Draw a merge/branch graph from the parent revision data. We're basically * building up a bunch of strings like this: * * ^ * |^ * o| * |o * o * * ...which form an ASCII representation of the graph we eventaully want to * draw. * * NOTE: The actual implementation is black magic. */ private function renderGraph() { // This keeps our accumulated information about each line of the // merge/branch graph. $graph = array(); // This holds the next commit we're looking for in each column of the // graph. $threads = array(); // This is the largest number of columns any row has, i.e. the width of // the graph. $count = 0; foreach ($this->history as $key => $history) { $joins = array(); $splits = array(); $parent_list = $this->parents[$history->getCommitIdentifier()]; // Look for some thread which has this commit as the next commit. If // we find one, this commit goes on that thread. Otherwise, this commit // goes on a new thread. $line = ''; $found = false; $pos = count($threads); for ($n = 0; $n < $count; $n++) { if (empty($threads[$n])) { $line .= ' '; continue; } if ($threads[$n] == $history->getCommitIdentifier()) { if ($found) { $line .= ' '; $joins[] = $n; unset($threads[$n]); } else { $line .= 'o'; $found = true; $pos = $n; } } else { // We render a "|" for any threads which have a commit that we haven't // seen yet, this is later drawn as a vertical line. $line .= '|'; } } // If we didn't find the thread this commit goes on, start a new thread. // We use "o" to mark the commit for the rendering engine, or "^" to // indicate that there's nothing after it so the line from the commit // upward should not be drawn. if (!$found) { if ($this->isHead) { $line .= '^'; } else { $line .= 'o'; foreach ($graph as $k => $meta) { // Go back across all the lines we've already drawn and add a // "|" to the end, since this is connected to some future commit // we don't know about. for ($jj = strlen($meta['line']); $jj <= $count; $jj++) { $graph[$k]['line'] .= '|'; } } } } // Update the next commit on this thread to the commit's first parent. // This might have the effect of making a new thread. $threads[$pos] = head($parent_list); // If we made a new thread, increase the thread count. $count = max($pos + 1, $count); // Now, deal with splits (merges). I picked this terms opposite to the // underlying repository term to confuse you. foreach (array_slice($parent_list, 1) as $parent) { $found = false; // Try to find the other parent(s) in our existing threads. If we find // them, split to that thread. foreach ($threads as $idx => $thread_commit) { if ($thread_commit == $parent) { $found = true; $splits[] = $idx; } } // If we didn't find the parent, we don't know about it yet. Find the // first free thread and add it as the "next" commit in that thread. // This might create a new thread. if (!$found) { for ($n = 0; $n < $count; $n++) { if (empty($threads[$n])) { break; } } $threads[$n] = $parent; $splits[] = $n; $count = max($n + 1, $count); } } $graph[] = array( 'line' => $line, 'split' => $splits, 'join' => $joins, ); } // Render into tags for the behavior. foreach ($graph as $k => $meta) { $graph[$k] = javelin_tag( 'div', array( 'sigil' => 'commit-graph', 'meta' => $meta, ), ''); } Javelin::initBehavior( 'diffusion-commit-graph', array( 'count' => $count, )); return $graph; } } diff --git a/src/applications/diffusion/view/DiffusionTagListView.php b/src/applications/diffusion/view/DiffusionTagListView.php index 25e8dd41fb..668453f88e 100644 --- a/src/applications/diffusion/view/DiffusionTagListView.php +++ b/src/applications/diffusion/view/DiffusionTagListView.php @@ -1,110 +1,110 @@ <?php final class DiffusionTagListView extends DiffusionView { private $tags; private $commits = array(); private $handles = array(); public function setTags($tags) { $this->tags = $tags; return $this; } public function setCommits(array $commits) { $this->commits = mpull($commits, null, 'getCommitIdentifier'); return $this; } public function setHandles(array $handles) { $this->handles = $handles; return $this; } public function getRequiredHandlePHIDs() { return array_filter(mpull($this->commits, 'getAuthorPHID')); } public function render() { $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $rows = array(); foreach ($this->tags as $tag) { $commit = idx($this->commits, $tag->getCommitIdentifier()); $tag_link = phutil_tag( 'a', array( 'href' => $drequest->generateURI( array( 'action' => 'browse', 'commit' => $tag->getName(), )), ), $tag->getName()); $commit_link = phutil_tag( 'a', array( 'href' => $drequest->generateURI( array( 'action' => 'commit', 'commit' => $tag->getCommitIdentifier(), )), ), $repository->formatCommitName( $tag->getCommitIdentifier())); $author = null; if ($commit && $commit->getAuthorPHID()) { $author = $this->handles[$commit->getAuthorPHID()]->renderLink(); } else if ($commit && $commit->getCommitData()) { $author = self::renderName($commit->getCommitData()->getAuthorName()); } else { $author = self::renderName($tag->getAuthor()); } $description = null; if ($tag->getType() == 'git/tag') { // In Git, a tag may be a "real" tag, or just a reference to a commit. // If it's a real tag, use the message on the tag, since this may be // unique data which isn't otherwise available. $description = $tag->getDescription(); } else { - if ($commit && $commit->getCommitData()) { - $description = $commit->getCommitData()->getSummary(); + if ($commit) { + $description = $commit->getSummary(); } else { $description = $tag->getDescription(); } } $rows[] = array( $tag_link, $commit_link, $description, $author, phabricator_datetime($tag->getEpoch(), $this->user), ); } $table = new AphrontTableView($rows); $table->setHeaders( array( pht('Tag'), pht('Commit'), pht('Description'), pht('Author'), pht('Created'), )); $table->setColumnClasses( array( 'pri', '', 'wide', )); return $table->render(); } }