diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 631e4a71a3..82ca8cbae3 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -321,6 +321,7 @@ phutil_register_library_map(array( 'DiffusionGitLastModifiedQuery' => 'applications/diffusion/query/lastmodified/git', 'DiffusionGitMergedCommitsQuery' => 'applications/diffusion/query/mergedcommits/git', 'DiffusionGitRequest' => 'applications/diffusion/request/git', + 'DiffusionGitTagListQuery' => 'applications/diffusion/query/taglist/git', 'DiffusionHistoryController' => 'applications/diffusion/controller/history', 'DiffusionHistoryQuery' => 'applications/diffusion/query/history/base', 'DiffusionHistoryTableView' => 'applications/diffusion/view/historytable', @@ -338,6 +339,7 @@ phutil_register_library_map(array( 'DiffusionMercurialLastModifiedQuery' => 'applications/diffusion/query/lastmodified/mercurial', 'DiffusionMercurialMergedCommitsQuery' => 'applications/diffusion/query/mergedcommits/mercurial', 'DiffusionMercurialRequest' => 'applications/diffusion/request/mercurial', + 'DiffusionMercurialTagListQuery' => 'applications/diffusion/query/taglist/mercurial', 'DiffusionMergedCommitsQuery' => 'applications/diffusion/query/mergedcommits/base', 'DiffusionPathChange' => 'applications/diffusion/data/pathchange', 'DiffusionPathChangeQuery' => 'applications/diffusion/query/pathchange/base', @@ -349,6 +351,7 @@ phutil_register_library_map(array( 'DiffusionQuery' => 'applications/diffusion/query/base', 'DiffusionRepositoryController' => 'applications/diffusion/controller/repository', 'DiffusionRepositoryPath' => 'applications/diffusion/data/repositorypath', + 'DiffusionRepositoryTag' => 'applications/diffusion/tag', 'DiffusionRequest' => 'applications/diffusion/request/base', 'DiffusionSvnBrowseQuery' => 'applications/diffusion/query/browse/svn', 'DiffusionSvnCommitParentsQuery' => 'applications/diffusion/query/parents/svn', @@ -359,8 +362,11 @@ phutil_register_library_map(array( 'DiffusionSvnLastModifiedQuery' => 'applications/diffusion/query/lastmodified/svn', 'DiffusionSvnMergedCommitsQuery' => 'applications/diffusion/query/mergedcommits/svn', 'DiffusionSvnRequest' => 'applications/diffusion/request/svn', + 'DiffusionSvnTagListQuery' => 'applications/diffusion/query/taglist/svn', 'DiffusionSymbolController' => 'applications/diffusion/controller/symbol', 'DiffusionSymbolQuery' => 'applications/diffusion/query/symbol', + 'DiffusionTagListQuery' => 'applications/diffusion/query/taglist/base', + 'DiffusionTagListView' => 'applications/diffusion/view/taglist', 'DiffusionURITestCase' => 'applications/diffusion/request/base/__tests__', 'DiffusionView' => 'applications/diffusion/view/base', 'DrydockAllocator' => 'applications/drydock/allocator/resource', @@ -1273,6 +1279,7 @@ phutil_register_library_map(array( 'DiffusionGitLastModifiedQuery' => 'DiffusionLastModifiedQuery', 'DiffusionGitMergedCommitsQuery' => 'DiffusionMergedCommitsQuery', 'DiffusionGitRequest' => 'DiffusionRequest', + 'DiffusionGitTagListQuery' => 'DiffusionTagListQuery', 'DiffusionHistoryController' => 'DiffusionController', 'DiffusionHistoryQuery' => 'DiffusionQuery', 'DiffusionHistoryTableView' => 'DiffusionView', @@ -1290,6 +1297,7 @@ phutil_register_library_map(array( 'DiffusionMercurialLastModifiedQuery' => 'DiffusionLastModifiedQuery', 'DiffusionMercurialMergedCommitsQuery' => 'DiffusionMergedCommitsQuery', 'DiffusionMercurialRequest' => 'DiffusionRequest', + 'DiffusionMercurialTagListQuery' => 'DiffusionTagListQuery', 'DiffusionMergedCommitsQuery' => 'DiffusionQuery', 'DiffusionPathCompleteController' => 'DiffusionController', 'DiffusionPathQueryTestCase' => 'PhabricatorTestCase', @@ -1304,7 +1312,10 @@ phutil_register_library_map(array( 'DiffusionSvnLastModifiedQuery' => 'DiffusionLastModifiedQuery', 'DiffusionSvnMergedCommitsQuery' => 'DiffusionMergedCommitsQuery', 'DiffusionSvnRequest' => 'DiffusionRequest', + 'DiffusionSvnTagListQuery' => 'DiffusionTagListQuery', 'DiffusionSymbolController' => 'DiffusionController', + 'DiffusionTagListQuery' => 'DiffusionQuery', + 'DiffusionTagListView' => 'DiffusionView', 'DiffusionURITestCase' => 'ArcanistPhutilTestCase', 'DiffusionView' => 'AphrontView', 'DrydockAllocatorWorker' => 'PhabricatorWorker', diff --git a/src/applications/diffusion/controller/repository/DiffusionRepositoryController.php b/src/applications/diffusion/controller/repository/DiffusionRepositoryController.php index 467275455c..cdc6657e63 100644 --- a/src/applications/diffusion/controller/repository/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/repository/DiffusionRepositoryController.php @@ -93,6 +93,8 @@ final class DiffusionRepositoryController extends DiffusionController { $content[] = $browse_panel; + $content[] = $this->buildTagListTable($drequest); + if ($drequest->getBranch() !== null) { $branch_query = DiffusionBranchQuery::newFromDiffusionRequest($drequest); $branches = $branch_query->loadBranches(); @@ -152,4 +154,37 @@ final class DiffusionRepositoryController extends DiffusionController { return $panel; } + private function buildTagListTable(DiffusionRequest $drequest) { + $query = DiffusionTagListQuery::newFromDiffusionRequest($drequest); + $query->setLimit(25); + $tags = $query->loadTags(); + + if (!$tags) { + return null; + } + + $commits = id(new PhabricatorAuditCommitQuery()) + ->withIdentifiers( + $drequest->getRepository()->getID(), + mpull($tags, 'getCommitIdentifier')) + ->needCommitData(true) + ->execute(); + + $view = new DiffusionTagListView(); + $view->setDiffusionRequest($drequest); + $view->setTags($tags); + $view->setUser($this->getRequest()->getUser()); + $view->setCommits($commits); + + $phids = $view->getRequiredHandlePHIDs(); + $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); + $view->setHandles($handles); + + $panel = new AphrontPanelView(); + $panel->setHeader('Tags'); + $panel->appendChild($view); + + return $panel; + } + } diff --git a/src/applications/diffusion/controller/repository/__init__.php b/src/applications/diffusion/controller/repository/__init__.php index 499d2f0136..7524f502f2 100644 --- a/src/applications/diffusion/controller/repository/__init__.php +++ b/src/applications/diffusion/controller/repository/__init__.php @@ -6,13 +6,16 @@ +phutil_require_module('phabricator', 'applications/audit/query/commit'); phutil_require_module('phabricator', 'applications/diffusion/controller/base'); phutil_require_module('phabricator', 'applications/diffusion/query/branch/base'); phutil_require_module('phabricator', 'applications/diffusion/query/browse/base'); phutil_require_module('phabricator', 'applications/diffusion/query/history/base'); +phutil_require_module('phabricator', 'applications/diffusion/query/taglist/base'); phutil_require_module('phabricator', 'applications/diffusion/view/branchtable'); phutil_require_module('phabricator', 'applications/diffusion/view/browsetable'); phutil_require_module('phabricator', 'applications/diffusion/view/historytable'); +phutil_require_module('phabricator', 'applications/diffusion/view/taglist'); phutil_require_module('phabricator', 'applications/phid/handle/data'); phutil_require_module('phabricator', 'applications/repository/constants/repositorytype'); phutil_require_module('phabricator', 'view/control/table'); diff --git a/src/applications/diffusion/query/taglist/base/DiffusionTagListQuery.php b/src/applications/diffusion/query/taglist/base/DiffusionTagListQuery.php new file mode 100644 index 0000000000..3c410dbfb6 --- /dev/null +++ b/src/applications/diffusion/query/taglist/base/DiffusionTagListQuery.php @@ -0,0 +1,41 @@ +limit = $limit; + return $this; + } + + protected function getLimit() { + return $this->limit; + } + + final public static function newFromDiffusionRequest( + DiffusionRequest $request) { + return self::newQueryObject(__CLASS__, $request); + } + + final public function loadTags() { + return $this->executeQuery(); + } + +} diff --git a/src/applications/diffusion/query/taglist/base/__init__.php b/src/applications/diffusion/query/taglist/base/__init__.php new file mode 100644 index 0000000000..bb2b644cc3 --- /dev/null +++ b/src/applications/diffusion/query/taglist/base/__init__.php @@ -0,0 +1,12 @@ +getRequest(); + $repository = $drequest->getRepository(); + + $limit = $this->getLimit(); + + list($stdout) = $repository->execxLocalCommand( + 'for-each-ref %C --sort=-creatordate --format=%s refs/tags', + $limit ? '--count='.(int)$limit : null, + '%(objectname) %(objecttype) %(refname) %(*objectname) %(*objecttype) '. + '%(subject)%01%(creator)' + ); + + $stdout = trim($stdout); + if (!strlen($stdout)) { + return array(); + } + + $tags = array(); + foreach (explode("\n", $stdout) as $line) { + list($info, $creator) = explode("\1", $line); + list( + $objectname, + $objecttype, + $refname, + $refobjectname, + $refobjecttype, + $description) = explode(' ', $info, 6); + + $matches = null; + if (!preg_match('/^(.*) ([0-9]+) ([0-9+-]+)$/', $creator, $matches)) { + throw new Exception( + "Unparseable output from 'git for-each-ref': {$line}"); + } + + $author = $matches[1]; + $epoch = $matches[2]; + + $tag = new DiffusionRepositoryTag(); + $tag->setAuthor($author); + $tag->setEpoch($epoch); + $tag->setCommitIdentifier(nonempty($refobjectname, $objectname)); + $tag->setName(preg_replace('@^refs/tags/@', '', $refname)); + $tag->setDescription($description); + $tag->setType('git/'.$objecttype); + + $tags[] = $tag; + } + + return $tags; + } + +} diff --git a/src/applications/diffusion/query/taglist/git/__init__.php b/src/applications/diffusion/query/taglist/git/__init__.php new file mode 100644 index 0000000000..58fc52415c --- /dev/null +++ b/src/applications/diffusion/query/taglist/git/__init__.php @@ -0,0 +1,15 @@ +type = $type; + return $this; + } + + public function getType() { + return $this->type; + } + + public function setDescription($description) { + $this->description = $description; + return $this; + } + + public function getDescription() { + return $this->description; + } + + public function setName($name) { + $this->name = $name; + return $this; + } + + public function getName() { + return $this->name; + } + + public function setCommitIdentifier($commit_identifier) { + $this->commitIdentifier = $commit_identifier; + return $this; + } + + public function getCommitIdentifier() { + return $this->commitIdentifier; + } + + public function setEpoch($epoch) { + $this->epoch = $epoch; + return $this; + } + + public function getEpoch() { + return $this->epoch; + } + + public function setAuthor($author) { + $this->author = $author; + return $this; + } + + public function getAuthor() { + return $this->author; + } + +} diff --git a/src/applications/diffusion/tag/__init__.php b/src/applications/diffusion/tag/__init__.php new file mode 100644 index 0000000000..44e04f82d6 --- /dev/null +++ b/src/applications/diffusion/tag/__init__.php @@ -0,0 +1,10 @@ + $drequest->generateURI( array( - 'action' => 'branch', + 'action' => 'browse', 'branch' => $branch->getName(), )), ), @@ -64,6 +64,7 @@ final class DiffusionBranchTableView extends DiffusionView { )); $view->setColumnClasses( array( + 'pri', 'wide', )); $view->setRowClasses($rowc); diff --git a/src/applications/diffusion/view/taglist/DiffusionTagListView.php b/src/applications/diffusion/view/taglist/DiffusionTagListView.php new file mode 100644 index 0000000000..b26c147050 --- /dev/null +++ b/src/applications/diffusion/view/taglist/DiffusionTagListView.php @@ -0,0 +1,134 @@ +user = $user; + return $this; + } + + 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_render_tag( + 'a', + array( + 'href' => $drequest->generateURI( + array( + 'action' => 'browse', + 'commit' => $tag->getCommitIdentifier(), + )), + ), + phutil_escape_html($tag->getName())); + + $commit_link = phutil_render_tag( + 'a', + array( + 'href' => $drequest->generateURI( + array( + 'action' => 'commit', + 'commit' => $tag->getCommitIdentifier(), + )), + ), + phutil_escape_html( + $repository->formatCommitName( + $tag->getCommitIdentifier()))); + + $author = null; + if ($commit && $commit->getAuthorPHID()) { + $author = $this->handles[$commit->getAuthorPHID()]->renderLink(); + } else if ($commit && $commit->getCommitData()) { + $author = phutil_escape_html($commit->getCommitData()->getAuthorName()); + } else { + $author = phutil_escape_html($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(); + } else { + $description = $tag->getDescription(); + } + } + $description = phutil_escape_html($description); + + $rows[] = array( + $tag_link, + $commit_link, + $description, + $author, + phabricator_datetime($tag->getEpoch(), $this->user), + ); + } + + $table = new AphrontTableView($rows); + $table->setHeaders( + array( + 'Tag', + 'Commit', + 'Description', + 'Author', + 'Created', + )); + $table->setColumnClasses( + array( + 'pri', + '', + 'wide', + )); + return $table->render(); + } + +} diff --git a/src/applications/diffusion/view/taglist/__init__.php b/src/applications/diffusion/view/taglist/__init__.php new file mode 100644 index 0000000000..d74164a299 --- /dev/null +++ b/src/applications/diffusion/view/taglist/__init__.php @@ -0,0 +1,17 @@ +getVersionControlSystem(); - - $type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; - $type_hg = PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL; - - $is_git = ($vcs == $type_git); - $is_hg = ($vcs == $type_hg); - if ($is_git || $is_hg) { - $short_identifier = substr($commit_identifier, 0, 12); - } else { - $short_identifier = $commit_identifier; - } - - $handle->setName('r'.$callsign.$short_identifier); + $name = $repository->formatCommitName($commit_identifier); + $handle->setName($name); } else { - $handle->setName('Commit '.'r'.$callsign.$commit_identifier); } diff --git a/src/applications/phid/handle/data/__init__.php b/src/applications/phid/handle/data/__init__.php index 59fd9a6be7..3cfb40dbf3 100644 --- a/src/applications/phid/handle/data/__init__.php +++ b/src/applications/phid/handle/data/__init__.php @@ -16,7 +16,6 @@ phutil_require_module('phabricator', 'applications/phid/handle'); phutil_require_module('phabricator', 'applications/phid/handle/const/status'); phutil_require_module('phabricator', 'applications/phid/utils'); phutil_require_module('phabricator', 'applications/phriction/storage/document'); -phutil_require_module('phabricator', 'applications/repository/constants/repositorytype'); phutil_require_module('phabricator', 'applications/repository/storage/repository'); phutil_require_module('phabricator', 'infrastructure/env'); phutil_require_module('phabricator', 'storage/queryfx'); diff --git a/src/applications/repository/storage/repository/PhabricatorRepository.php b/src/applications/repository/storage/repository/PhabricatorRepository.php index 5ecbdce3a1..d88c1f6a8b 100644 --- a/src/applications/repository/storage/repository/PhabricatorRepository.php +++ b/src/applications/repository/storage/repository/PhabricatorRepository.php @@ -358,4 +358,21 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO { return true; } + public function formatCommitName($commit_identifier) { + $vcs = $this->getVersionControlSystem(); + + $type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; + $type_hg = PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL; + + $is_git = ($vcs == $type_git); + $is_hg = ($vcs == $type_hg); + if ($is_git || $is_hg) { + $short_identifier = substr($commit_identifier, 0, 12); + } else { + $short_identifier = $commit_identifier; + } + + return 'r'.$this->getCallsign().$short_identifier; + } + } diff --git a/src/view/utils/viewutils.php b/src/view/utils/viewutils.php index 5eafbc14ed..7496420b27 100644 --- a/src/view/utils/viewutils.php +++ b/src/view/utils/viewutils.php @@ -57,7 +57,7 @@ function phabricator_time($epoch, $user) { 'g:i A'); } -function phabricator_datetime($epoch, $user) { + function phabricator_datetime($epoch, $user) { return phabricator_format_local_time( $epoch, $user, @@ -103,7 +103,16 @@ function phabricator_format_local_time($epoch, $user, $format) { // constructor, it ignores it if the date string includes timezone // information. Further, it treats epoch timestamps ("@946684800") as having // a UTC timezone. Set the timezone explicitly after constructing the object. - $date = new DateTime('@'.$epoch); + try { + $date = new DateTime('@'.$epoch); + } catch (Exception $ex) { + // NOTE: DateTime throws an empty exception if the format is invalid, + // just replace it with a useful one. + throw new Exception( + "Construction of a DateTime() with epoch '{$epoch}' ". + "raised an exception."); + } + $date->setTimeZone($zone); return $date->format($format);