diff --git a/bin/differential b/bin/differential new file mode 120000 index 0000000000..5a50360da3 --- /dev/null +++ b/bin/differential @@ -0,0 +1 @@ +../scripts/setup/manage_differential.php \ No newline at end of file diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d09165b13a..c32a98f1f2 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,13 +7,13 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'a419cf4b', - 'core.pkg.js' => '400453e4', + 'core.pkg.css' => '3ea6dc33', + 'core.pkg.js' => '57dff7df', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', 'differential.pkg.js' => '64e69521', 'diffusion.pkg.css' => 'f45955ed', - 'diffusion.pkg.js' => 'ca1c8b5a', + 'diffusion.pkg.js' => '3a9a8bfa', 'maniphest.pkg.css' => '4845691a', 'maniphest.pkg.js' => '949a7498', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', @@ -37,7 +37,6 @@ return array( 'rsrc/css/application/base/phabricator-application-launch-view.css' => '95351601', 'rsrc/css/application/base/phui-theme.css' => '6b451f24', 'rsrc/css/application/base/standard-page-view.css' => '3c99cdf4', - 'rsrc/css/application/calendar/calendar-icon.css' => 'c69aa59f', 'rsrc/css/application/chatlog/chatlog.css' => 'd295b020', 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', 'rsrc/css/application/config/config-options.css' => '0ede4c9b', @@ -82,7 +81,7 @@ return array( 'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b', 'rsrc/css/application/paste/paste.css' => 'a5157c48', 'rsrc/css/application/people/people-profile.css' => '25970776', - 'rsrc/css/application/phame/phame.css' => '09a39e8d', + 'rsrc/css/application/phame/phame.css' => 'dac8fdf2', 'rsrc/css/application/pholio/pholio-edit.css' => '3ad9d1ee', 'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49', 'rsrc/css/application/pholio/pholio.css' => '95174bdd', @@ -94,7 +93,6 @@ return array( 'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43', 'rsrc/css/application/policy/policy.css' => '957ea14c', 'rsrc/css/application/ponder/ponder-view.css' => '7b0df4da', - 'rsrc/css/application/projects/project-icon.css' => '4e3eaa5a', 'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733', 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', 'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd', @@ -114,7 +112,7 @@ return array( 'rsrc/css/font/phui-font-icon-base.css' => 'ecbbb4c2', 'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82', 'rsrc/css/layout/phabricator-hovercard-view.css' => '1239cd52', - 'rsrc/css/layout/phabricator-side-menu-view.css' => 'bec2458e', + 'rsrc/css/layout/phabricator-side-menu-view.css' => '91b7a42c', 'rsrc/css/layout/phabricator-source-code-view.css' => 'cbeef983', 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'd1cf6f93', 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1c7f338', @@ -127,7 +125,7 @@ return array( 'rsrc/css/phui/phui-box.css' => 'a5bb366d', 'rsrc/css/phui/phui-button.css' => '16020a60', 'rsrc/css/phui/phui-crumbs-view.css' => '414406b5', - 'rsrc/css/phui/phui-document-pro.css' => 'e0fad431', + 'rsrc/css/phui/phui-document-pro.css' => '8799acf7', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document.css' => 'a4a1c3b9', 'rsrc/css/phui/phui-feed-story.css' => 'b7b26d23', @@ -135,6 +133,7 @@ return array( 'rsrc/css/phui/phui-form-view.css' => '4a1a0f5e', 'rsrc/css/phui/phui-form.css' => '0b98e572', 'rsrc/css/phui/phui-header-view.css' => '55bb32dd', + 'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad', 'rsrc/css/phui/phui-icon.css' => 'b0a6b1b6', 'rsrc/css/phui/phui-image-mask.css' => '5a8b09c8', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', @@ -391,7 +390,7 @@ return array( 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'b42eddc7', 'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'd835b03a', 'rsrc/js/application/diffusion/behavior-commit-branches.js' => 'bdaf4d04', - 'rsrc/js/application/diffusion/behavior-commit-graph.js' => '9007c197', + 'rsrc/js/application/diffusion/behavior-commit-graph.js' => '5a0b1a64', 'rsrc/js/application/diffusion/behavior-jump-to.js' => '73d09eef', 'rsrc/js/application/diffusion/behavior-load-blame.js' => '42126667', 'rsrc/js/application/diffusion/behavior-locate-file.js' => '6d3e1947', @@ -465,7 +464,7 @@ return array( 'rsrc/js/core/behavior-active-nav.js' => 'e379b58e', 'rsrc/js/core/behavior-audio-source.js' => '59b251eb', 'rsrc/js/core/behavior-autofocus.js' => '7319e029', - 'rsrc/js/core/behavior-choose-control.js' => 'dfaafb14', + 'rsrc/js/core/behavior-choose-control.js' => '327a00d1', 'rsrc/js/core/behavior-crop.js' => 'fa0f4fc2', 'rsrc/js/core/behavior-dark-console.js' => 'f411b6ae', 'rsrc/js/core/behavior-device.js' => 'a205cf28', @@ -487,9 +486,9 @@ return array( 'rsrc/js/core/behavior-object-selector.js' => '49b73b36', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', 'rsrc/js/core/behavior-phabricator-nav.js' => '56a1ca03', - 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'ecddcbe2', + 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'b60b6d9b', 'rsrc/js/core/behavior-refresh-csrf.js' => 'ab2f381b', - 'rsrc/js/core/behavior-remarkup-preview.js' => 'f7379f45', + 'rsrc/js/core/behavior-remarkup-preview.js' => '4b700e9e', 'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e', 'rsrc/js/core/behavior-reveal-content.js' => '60821bc7', 'rsrc/js/core/behavior-scrollbar.js' => '834a1173', @@ -524,7 +523,6 @@ return array( 'aphront-typeahead-control-css' => '0e403212', 'auth-css' => '0877ed6e', 'bulk-job-css' => 'df9c1d4a', - 'calendar-icon-css' => 'c69aa59f', 'changeset-view-manager' => '58562350', 'conduit-api-css' => '7bc725c4', 'config-options-css' => '0ede4c9b', @@ -571,7 +569,7 @@ return array( 'javelin-behavior-audio-source' => '59b251eb', 'javelin-behavior-audit-preview' => 'd835b03a', 'javelin-behavior-bulk-job-reload' => 'edf8a145', - 'javelin-behavior-choose-control' => 'dfaafb14', + 'javelin-behavior-choose-control' => '327a00d1', 'javelin-behavior-comment-actions' => 'b65559c0', 'javelin-behavior-config-reorder-fields' => 'b6993408', 'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a', @@ -598,7 +596,7 @@ return array( 'javelin-behavior-differential-toggle-files' => 'ca3f91eb', 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-commit-branches' => 'bdaf4d04', - 'javelin-behavior-diffusion-commit-graph' => '9007c197', + 'javelin-behavior-diffusion-commit-graph' => '5a0b1a64', 'javelin-behavior-diffusion-jump-to' => '73d09eef', 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', @@ -640,7 +638,7 @@ return array( 'javelin-behavior-phabricator-notification-example' => '8ce821c5', 'javelin-behavior-phabricator-object-selector' => '49b73b36', 'javelin-behavior-phabricator-oncopy' => '2926fff2', - 'javelin-behavior-phabricator-remarkup-assist' => 'ecddcbe2', + 'javelin-behavior-phabricator-remarkup-assist' => 'b60b6d9b', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', 'javelin-behavior-phabricator-search-typeahead' => '048330fa', 'javelin-behavior-phabricator-show-older-transactions' => 'dbbf48b6', @@ -662,7 +660,7 @@ return array( 'javelin-behavior-releeph-preview-branch' => 'b2b4fbaf', 'javelin-behavior-releeph-request-state-change' => 'a0b57eb8', 'javelin-behavior-releeph-request-typeahead' => 'de2e896f', - 'javelin-behavior-remarkup-preview' => 'f7379f45', + 'javelin-behavior-remarkup-preview' => '4b700e9e', 'javelin-behavior-reorder-applications' => '76b9fc3e', 'javelin-behavior-reorder-columns' => 'e1d25dfb', 'javelin-behavior-repository-crossreference' => 'e5339c43', @@ -762,7 +760,7 @@ return array( 'phabricator-remarkup-css' => '7afb543c', 'phabricator-search-results-css' => '7dea472c', 'phabricator-shaped-request' => '7cbe244b', - 'phabricator-side-menu-view-css' => 'bec2458e', + 'phabricator-side-menu-view-css' => '91b7a42c', 'phabricator-slowvote-css' => 'da0afb1b', 'phabricator-source-code-view-css' => 'cbeef983', 'phabricator-standard-page-view' => '3c99cdf4', @@ -781,7 +779,7 @@ return array( 'phabricator-uiexample-reactor-sendclass' => '1def2711', 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', 'phabricator-zindex-css' => '57ddcaa2', - 'phame-css' => '09a39e8d', + 'phame-css' => 'dac8fdf2', 'pholio-css' => '95174bdd', 'pholio-edit-css' => '3ad9d1ee', 'pholio-inline-comments-css' => '8e545e49', @@ -802,13 +800,14 @@ return array( 'phui-crumbs-view-css' => '414406b5', 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => 'a4a1c3b9', - 'phui-document-view-pro-css' => 'e0fad431', + 'phui-document-view-pro-css' => '8799acf7', 'phui-feed-story-css' => 'b7b26d23', 'phui-font-icon-base-css' => 'ecbbb4c2', 'phui-fontkit-css' => '9cda225e', 'phui-form-css' => '0b98e572', 'phui-form-view-css' => '4a1a0f5e', 'phui-header-view-css' => '55bb32dd', + 'phui-icon-set-selector-css' => '1ab67aad', 'phui-icon-view-css' => 'b0a6b1b6', 'phui-image-mask-css' => '5a8b09c8', 'phui-info-panel-css' => '27ea50a1', @@ -839,7 +838,6 @@ return array( 'policy-edit-css' => '815c66f7', 'policy-transaction-detail-css' => '82100a43', 'ponder-view-css' => '7b0df4da', - 'project-icon-css' => '4e3eaa5a', 'raphael-core' => '51ee6b43', 'raphael-g' => '40dde778', 'raphael-g-line' => '40da039e', @@ -1044,6 +1042,12 @@ return array( '2f670a96' => array( 'phui-theme-css', ), + '327a00d1' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-workflow', + ), '331b1611' => array( 'javelin-install', ), @@ -1125,6 +1129,12 @@ return array( 'javelin-request', 'javelin-util', ), + '4b700e9e' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'phabricator-shaped-request', + ), '4e3e79a6' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1212,6 +1222,11 @@ return array( 'javelin-vector', 'javelin-dom', ), + '5a0b1a64' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + ), '5b2e3e2b' => array( 'javelin-stratcom', 'javelin-request', @@ -1511,11 +1526,6 @@ return array( 'javelin-install', 'javelin-dom', ), - '9007c197' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - ), '901935ef' => array( 'javelin-behavior', 'javelin-dom', @@ -1721,6 +1731,15 @@ return array( 'javelin-dom', 'javelin-util', ), + 'b60b6d9b' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'phabricator-phtize', + 'phabricator-textareautils', + 'javelin-workflow', + 'javelin-vector', + ), 'b65559c0' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1894,12 +1913,6 @@ return array( 'df5e11d2' => array( 'javelin-install', ), - 'dfaafb14' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-workflow', - ), 'e10f8e18' => array( 'javelin-behavior', 'javelin-dom', @@ -1960,15 +1973,6 @@ return array( 'phabricator-phtize', 'javelin-dom', ), - 'ecddcbe2' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'phabricator-phtize', - 'phabricator-textareautils', - 'javelin-workflow', - 'javelin-vector', - ), 'edd1ba66' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2005,12 +2009,6 @@ return array( 'javelin-util', 'javelin-reactor', ), - 'f7379f45' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'phabricator-shaped-request', - ), 'f7fc67ec' => array( 'javelin-install', 'javelin-typeahead', diff --git a/resources/sql/autopatches/20151226.reop.1.sql b/resources/sql/autopatches/20151226.reop.1.sql new file mode 100644 index 0000000000..4daca60aeb --- /dev/null +++ b/resources/sql/autopatches/20151226.reop.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_drydock.drydock_repositoryoperation + ADD isDismissed BOOL NOT NULL; diff --git a/resources/sql/autopatches/20151227.proj.01.materialize.sql b/resources/sql/autopatches/20151227.proj.01.materialize.sql new file mode 100644 index 0000000000..ceac969e8f --- /dev/null +++ b/resources/sql/autopatches/20151227.proj.01.materialize.sql @@ -0,0 +1,6 @@ +/* PhabricatorProjectProjectHasMemberEdgeType::EDGECONST = 13 */ +/* PhabricatorProjectMaterializedMemberEdgeType::EDGECONST = 60 */ + +INSERT IGNORE INTO {$NAMESPACE}_project.edge (src, type, dst, dateCreated) + SELECT src, 60, dst, dateCreated FROM {$NAMESPACE}_project.edge + WHERE type = 13; diff --git a/resources/sql/autopatches/20151231.proj.01.icon.php b/resources/sql/autopatches/20151231.proj.01.icon.php new file mode 100644 index 0000000000..501614df3d --- /dev/null +++ b/resources/sql/autopatches/20151231.proj.01.icon.php @@ -0,0 +1,34 @@ + 'project', + 'fa-tags' => 'tag', + 'fa-lock' => 'policy', + 'fa-users' => 'group', + + 'fa-folder' => 'folder', + 'fa-calendar' => 'timeline', + 'fa-flag-checkered' => 'goal', + 'fa-truck' => 'release', + + 'fa-bug' => 'bugs', + 'fa-trash-o' => 'cleanup', + 'fa-umbrella' => 'umbrella', + 'fa-envelope' => 'communication', + + 'fa-building' => 'organization', + 'fa-cloud' => 'infrastructure', + 'fa-credit-card' => 'account', + 'fa-flask' => 'experimental', +); + +$table = new PhabricatorProject(); +$conn_w = $table->establishConnection('w'); +foreach ($icon_map as $old_icon => $new_key) { + queryfx( + $conn_w, + 'UPDATE %T SET icon = %s WHERE icon = %s', + $table->getTableName(), + $new_key, + $old_icon); +} diff --git a/scripts/setup/manage_differential.php b/scripts/setup/manage_differential.php new file mode 100755 index 0000000000..30e11d27a9 --- /dev/null +++ b/scripts/setup/manage_differential.php @@ -0,0 +1,21 @@ +#!/usr/bin/env php +setTagline(pht('manage hunks')); +$args->setSynopsis(<<parseStandardArguments(); + +$workflows = id(new PhutilClassMapQuery()) + ->setAncestorClass('PhabricatorDifferentialManagementWorkflow') + ->execute(); +$workflows[] = new PhutilHelpArgumentWorkflow(); +$args->parseWorkflows($workflows); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d3d33adb15..2a0de867aa 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -386,6 +386,7 @@ phutil_register_library_map(array( 'DifferentialDiffContentRemovedHeraldField' => 'applications/differential/herald/DifferentialDiffContentRemovedHeraldField.php', 'DifferentialDiffCreateController' => 'applications/differential/controller/DifferentialDiffCreateController.php', 'DifferentialDiffEditor' => 'applications/differential/editor/DifferentialDiffEditor.php', + 'DifferentialDiffExtractionEngine' => 'applications/differential/engine/DifferentialDiffExtractionEngine.php', 'DifferentialDiffHeraldField' => 'applications/differential/herald/DifferentialDiffHeraldField.php', 'DifferentialDiffHeraldFieldGroup' => 'applications/differential/herald/DifferentialDiffHeraldFieldGroup.php', 'DifferentialDiffInlineCommentQuery' => 'applications/differential/query/DifferentialDiffInlineCommentQuery.php', @@ -528,17 +529,15 @@ phutil_register_library_map(array( 'DiffusionAuditorsAddAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php', 'DiffusionAuditorsAddSelfHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddSelfHeraldAction.php', 'DiffusionAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsHeraldAction.php', + 'DiffusionBlameConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBlameConduitAPIMethod.php', + 'DiffusionBlameQuery' => 'applications/diffusion/query/blame/DiffusionBlameQuery.php', 'DiffusionBlockHeraldAction' => 'applications/diffusion/herald/DiffusionBlockHeraldAction.php', 'DiffusionBranchQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBranchQueryConduitAPIMethod.php', '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', @@ -599,17 +598,15 @@ phutil_register_library_map(array( 'DiffusionExternalController' => 'applications/diffusion/controller/DiffusionExternalController.php', 'DiffusionExternalSymbolQuery' => 'applications/diffusion/symbol/DiffusionExternalSymbolQuery.php', 'DiffusionExternalSymbolsSource' => 'applications/diffusion/symbol/DiffusionExternalSymbolsSource.php', - 'DiffusionFileContent' => 'applications/diffusion/data/DiffusionFileContent.php', 'DiffusionFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionFileContentQuery.php', 'DiffusionFileContentQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionFileContentQueryConduitAPIMethod.php', 'DiffusionFindSymbolsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionFindSymbolsConduitAPIMethod.php', - 'DiffusionGetCommitsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionGetCommitsConduitAPIMethod.php', 'DiffusionGetLintMessagesConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionGetLintMessagesConduitAPIMethod.php', 'DiffusionGetRecentCommitsByPathConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionGetRecentCommitsByPathConduitAPIMethod.php', + 'DiffusionGitBlameQuery' => 'applications/diffusion/query/blame/DiffusionGitBlameQuery.php', 'DiffusionGitBranch' => 'applications/diffusion/data/DiffusionGitBranch.php', 'DiffusionGitBranchTestCase' => 'applications/diffusion/data/__tests__/DiffusionGitBranchTestCase.php', 'DiffusionGitFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php', - 'DiffusionGitFileContentQueryTestCase' => 'applications/diffusion/query/__tests__/DiffusionGitFileContentQueryTestCase.php', 'DiffusionGitRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php', 'DiffusionGitReceivePackSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php', 'DiffusionGitRequest' => 'applications/diffusion/request/DiffusionGitRequest.php', @@ -626,7 +623,6 @@ phutil_register_library_map(array( 'DiffusionLastModifiedQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionLastModifiedQueryConduitAPIMethod.php', 'DiffusionLintController' => 'applications/diffusion/controller/DiffusionLintController.php', 'DiffusionLintCountQuery' => 'applications/diffusion/query/DiffusionLintCountQuery.php', - 'DiffusionLintDetailsController' => 'applications/diffusion/controller/DiffusionLintDetailsController.php', 'DiffusionLintSaveRunner' => 'applications/diffusion/DiffusionLintSaveRunner.php', 'DiffusionLookSoonConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionLookSoonConduitAPIMethod.php', 'DiffusionLowLevelCommitFieldsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelCommitFieldsQuery.php', @@ -638,6 +634,7 @@ phutil_register_library_map(array( 'DiffusionLowLevelParentsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelParentsQuery.php', 'DiffusionLowLevelQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelQuery.php', 'DiffusionLowLevelResolveRefsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php', + 'DiffusionMercurialBlameQuery' => 'applications/diffusion/query/blame/DiffusionMercurialBlameQuery.php', 'DiffusionMercurialFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionMercurialFileContentQuery.php', 'DiffusionMercurialRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionMercurialRawDiffQuery.php', 'DiffusionMercurialRequest' => 'applications/diffusion/request/DiffusionMercurialRequest.php', @@ -749,6 +746,7 @@ phutil_register_library_map(array( 'DiffusionSubversionServeSSHWorkflow' => 'applications/diffusion/ssh/DiffusionSubversionServeSSHWorkflow.php', 'DiffusionSubversionWireProtocol' => 'applications/diffusion/protocol/DiffusionSubversionWireProtocol.php', 'DiffusionSubversionWireProtocolTestCase' => 'applications/diffusion/protocol/__tests__/DiffusionSubversionWireProtocolTestCase.php', + 'DiffusionSvnBlameQuery' => 'applications/diffusion/query/blame/DiffusionSvnBlameQuery.php', 'DiffusionSvnFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionSvnFileContentQuery.php', 'DiffusionSvnRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionSvnRawDiffQuery.php', 'DiffusionSvnRequest' => 'applications/diffusion/request/DiffusionSvnRequest.php', @@ -911,6 +909,7 @@ phutil_register_library_map(array( 'DrydockObjectAuthorizationView' => 'applications/drydock/view/DrydockObjectAuthorizationView.php', 'DrydockQuery' => 'applications/drydock/query/DrydockQuery.php', 'DrydockRepositoryOperation' => 'applications/drydock/storage/DrydockRepositoryOperation.php', + 'DrydockRepositoryOperationDismissController' => 'applications/drydock/controller/DrydockRepositoryOperationDismissController.php', 'DrydockRepositoryOperationListController' => 'applications/drydock/controller/DrydockRepositoryOperationListController.php', 'DrydockRepositoryOperationPHIDType' => 'applications/drydock/phid/DrydockRepositoryOperationPHIDType.php', 'DrydockRepositoryOperationQuery' => 'applications/drydock/query/DrydockRepositoryOperationQuery.php', @@ -1160,6 +1159,8 @@ phutil_register_library_map(array( 'HeraldNewObjectField' => 'applications/herald/field/HeraldNewObjectField.php', 'HeraldNotifyActionGroup' => 'applications/herald/action/HeraldNotifyActionGroup.php', 'HeraldObjectTranscript' => 'applications/herald/storage/transcript/HeraldObjectTranscript.php', + 'HeraldPhameBlogAdapter' => 'applications/phame/herald/HeraldPhameBlogAdapter.php', + 'HeraldPhamePostAdapter' => 'applications/phame/herald/HeraldPhamePostAdapter.php', 'HeraldPholioMockAdapter' => 'applications/pholio/herald/HeraldPholioMockAdapter.php', 'HeraldPonderQuestionAdapter' => 'applications/ponder/herald/HeraldPonderQuestionAdapter.php', 'HeraldPreCommitAdapter' => 'applications/diffusion/herald/HeraldPreCommitAdapter.php', @@ -1500,6 +1501,7 @@ phutil_register_library_map(array( 'PHUIListItemView' => 'view/phui/PHUIListItemView.php', 'PHUIListView' => 'view/phui/PHUIListView.php', 'PHUIListViewTestCase' => 'view/layout/__tests__/PHUIListViewTestCase.php', + 'PHUIMainMenuView' => 'view/phui/PHUIMainMenuView.php', 'PHUIObjectBoxView' => 'view/phui/PHUIObjectBoxView.php', 'PHUIObjectItemListExample' => 'applications/uiexample/examples/PHUIObjectItemListExample.php', 'PHUIObjectItemListView' => 'view/phui/PHUIObjectItemListView.php', @@ -1722,6 +1724,7 @@ phutil_register_library_map(array( 'PhabricatorAuthListController' => 'applications/auth/controller/config/PhabricatorAuthListController.php', 'PhabricatorAuthLoginController' => 'applications/auth/controller/PhabricatorAuthLoginController.php', 'PhabricatorAuthLoginHandler' => 'applications/auth/handler/PhabricatorAuthLoginHandler.php', + 'PhabricatorAuthMainMenuBarExtension' => 'applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php', 'PhabricatorAuthManagementCachePKCS8Workflow' => 'applications/auth/management/PhabricatorAuthManagementCachePKCS8Workflow.php', 'PhabricatorAuthManagementLDAPWorkflow' => 'applications/auth/management/PhabricatorAuthManagementLDAPWorkflow.php', 'PhabricatorAuthManagementListFactorsWorkflow' => 'applications/auth/management/PhabricatorAuthManagementListFactorsWorkflow.php', @@ -1782,6 +1785,7 @@ phutil_register_library_map(array( 'PhabricatorBadgesDAO' => 'applications/badges/storage/PhabricatorBadgesDAO.php', 'PhabricatorBadgesDefaultEditCapability' => 'applications/badges/capability/PhabricatorBadgesDefaultEditCapability.php', 'PhabricatorBadgesEditController' => 'applications/badges/controller/PhabricatorBadgesEditController.php', + 'PhabricatorBadgesEditEngine' => 'applications/badges/editor/PhabricatorBadgesEditEngine.php', 'PhabricatorBadgesEditRecipientsController' => 'applications/badges/controller/PhabricatorBadgesEditRecipientsController.php', 'PhabricatorBadgesEditor' => 'applications/badges/editor/PhabricatorBadgesEditor.php', 'PhabricatorBadgesIconSet' => 'applications/badges/icon/PhabricatorBadgesIconSet.php', @@ -2138,7 +2142,10 @@ phutil_register_library_map(array( 'PhabricatorDiffPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorDiffPreferencesSettingsPanel.php', 'PhabricatorDifferenceEngine' => 'infrastructure/diff/PhabricatorDifferenceEngine.php', 'PhabricatorDifferentialApplication' => 'applications/differential/application/PhabricatorDifferentialApplication.php', + 'PhabricatorDifferentialAttachCommitWorkflow' => 'applications/differential/management/PhabricatorDifferentialAttachCommitWorkflow.php', 'PhabricatorDifferentialConfigOptions' => 'applications/differential/config/PhabricatorDifferentialConfigOptions.php', + 'PhabricatorDifferentialExtractWorkflow' => 'applications/differential/management/PhabricatorDifferentialExtractWorkflow.php', + 'PhabricatorDifferentialManagementWorkflow' => 'applications/differential/management/PhabricatorDifferentialManagementWorkflow.php', 'PhabricatorDifferentialRevisionTestDataGenerator' => 'applications/differential/lipsum/PhabricatorDifferentialRevisionTestDataGenerator.php', 'PhabricatorDiffusionApplication' => 'applications/diffusion/application/PhabricatorDiffusionApplication.php', 'PhabricatorDiffusionConfigOptions' => 'applications/diffusion/config/PhabricatorDiffusionConfigOptions.php', @@ -2362,6 +2369,7 @@ phutil_register_library_map(array( 'PhabricatorHelpDocumentationController' => 'applications/help/controller/PhabricatorHelpDocumentationController.php', 'PhabricatorHelpEditorProtocolController' => 'applications/help/controller/PhabricatorHelpEditorProtocolController.php', 'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/PhabricatorHelpKeyboardShortcutController.php', + 'PhabricatorHelpMainMenuBarExtension' => 'applications/help/extension/PhabricatorHelpMainMenuBarExtension.php', 'PhabricatorHeraldApplication' => 'applications/herald/application/PhabricatorHeraldApplication.php', 'PhabricatorHighSecurityRequestExceptionHandler' => 'aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php', 'PhabricatorHomeApplication' => 'applications/home/application/PhabricatorHomeApplication.php', @@ -2380,6 +2388,7 @@ phutil_register_library_map(array( 'PhabricatorIRCProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorIRCProtocolAdapter.php', 'PhabricatorIconRemarkupRule' => 'applications/macro/markup/PhabricatorIconRemarkupRule.php', 'PhabricatorIconSet' => 'applications/files/iconset/PhabricatorIconSet.php', + 'PhabricatorIconSetEditField' => 'applications/transactions/editfield/PhabricatorIconSetEditField.php', 'PhabricatorIconSetIcon' => 'applications/files/iconset/PhabricatorIconSetIcon.php', 'PhabricatorImageMacroRemarkupRule' => 'applications/macro/markup/PhabricatorImageMacroRemarkupRule.php', 'PhabricatorImageTransformer' => 'applications/files/PhabricatorImageTransformer.php', @@ -2478,6 +2487,7 @@ phutil_register_library_map(array( 'PhabricatorMailSetupCheck' => 'applications/config/check/PhabricatorMailSetupCheck.php', 'PhabricatorMailTarget' => 'applications/metamta/replyhandler/PhabricatorMailTarget.php', 'PhabricatorMailgunConfigOptions' => 'applications/config/option/PhabricatorMailgunConfigOptions.php', + 'PhabricatorMainMenuBarExtension' => 'view/page/menu/PhabricatorMainMenuBarExtension.php', 'PhabricatorMainMenuSearchView' => 'view/page/menu/PhabricatorMainMenuSearchView.php', 'PhabricatorMainMenuView' => 'view/page/menu/PhabricatorMainMenuView.php', 'PhabricatorManagementWorkflow' => 'infrastructure/management/PhabricatorManagementWorkflow.php', @@ -2722,6 +2732,7 @@ phutil_register_library_map(array( 'PhabricatorPeopleLogQuery' => 'applications/people/query/PhabricatorPeopleLogQuery.php', 'PhabricatorPeopleLogSearchEngine' => 'applications/people/query/PhabricatorPeopleLogSearchEngine.php', 'PhabricatorPeopleLogsController' => 'applications/people/controller/PhabricatorPeopleLogsController.php', + 'PhabricatorPeopleMainMenuBarExtension' => 'applications/people/extension/PhabricatorPeopleMainMenuBarExtension.php', 'PhabricatorPeopleNewController' => 'applications/people/controller/PhabricatorPeopleNewController.php', 'PhabricatorPeopleNoOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleNoOwnerDatasource.php', 'PhabricatorPeopleOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleOwnerDatasource.php', @@ -2844,7 +2855,8 @@ phutil_register_library_map(array( 'PhabricatorProjectDAO' => 'applications/project/storage/PhabricatorProjectDAO.php', 'PhabricatorProjectDatasource' => 'applications/project/typeahead/PhabricatorProjectDatasource.php', 'PhabricatorProjectDescriptionField' => 'applications/project/customfield/PhabricatorProjectDescriptionField.php', - 'PhabricatorProjectEditDetailsController' => 'applications/project/controller/PhabricatorProjectEditDetailsController.php', + 'PhabricatorProjectEditController' => 'applications/project/controller/PhabricatorProjectEditController.php', + 'PhabricatorProjectEditEngine' => 'applications/project/engine/PhabricatorProjectEditEngine.php', 'PhabricatorProjectEditPictureController' => 'applications/project/controller/PhabricatorProjectEditPictureController.php', 'PhabricatorProjectFeedController' => 'applications/project/controller/PhabricatorProjectFeedController.php', 'PhabricatorProjectFulltextEngine' => 'applications/project/search/PhabricatorProjectFulltextEngine.php', @@ -2852,7 +2864,9 @@ phutil_register_library_map(array( 'PhabricatorProjectIconSet' => 'applications/project/icon/PhabricatorProjectIconSet.php', 'PhabricatorProjectInterface' => 'applications/project/interface/PhabricatorProjectInterface.php', 'PhabricatorProjectListController' => 'applications/project/controller/PhabricatorProjectListController.php', - 'PhabricatorProjectLogicalAndDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalAndDatasource.php', + 'PhabricatorProjectListView' => 'applications/project/view/PhabricatorProjectListView.php', + 'PhabricatorProjectLockController' => 'applications/project/controller/PhabricatorProjectLockController.php', + 'PhabricatorProjectLogicalAncestorDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalAncestorDatasource.php', 'PhabricatorProjectLogicalDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalDatasource.php', 'PhabricatorProjectLogicalOrNotDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalOrNotDatasource.php', 'PhabricatorProjectLogicalUserDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalUserDatasource.php', @@ -2863,6 +2877,7 @@ phutil_register_library_map(array( 'PhabricatorProjectMembersEditController' => 'applications/project/controller/PhabricatorProjectMembersEditController.php', 'PhabricatorProjectMembersPolicyRule' => 'applications/project/policyrule/PhabricatorProjectMembersPolicyRule.php', 'PhabricatorProjectMembersRemoveController' => 'applications/project/controller/PhabricatorProjectMembersRemoveController.php', + 'PhabricatorProjectMilestonesController' => 'applications/project/controller/PhabricatorProjectMilestonesController.php', 'PhabricatorProjectMoveController' => 'applications/project/controller/PhabricatorProjectMoveController.php', 'PhabricatorProjectNameContextFreeGrammar' => 'applications/project/lipsum/PhabricatorProjectNameContextFreeGrammar.php', 'PhabricatorProjectNoProjectsDatasource' => 'applications/project/typeahead/PhabricatorProjectNoProjectsDatasource.php', @@ -2882,10 +2897,12 @@ phutil_register_library_map(array( 'PhabricatorProjectSlug' => 'applications/project/storage/PhabricatorProjectSlug.php', 'PhabricatorProjectStandardCustomField' => 'applications/project/customfield/PhabricatorProjectStandardCustomField.php', 'PhabricatorProjectStatus' => 'applications/project/constants/PhabricatorProjectStatus.php', + 'PhabricatorProjectSubprojectsController' => 'applications/project/controller/PhabricatorProjectSubprojectsController.php', 'PhabricatorProjectTestDataGenerator' => 'applications/project/lipsum/PhabricatorProjectTestDataGenerator.php', 'PhabricatorProjectTransaction' => 'applications/project/storage/PhabricatorProjectTransaction.php', 'PhabricatorProjectTransactionEditor' => 'applications/project/editor/PhabricatorProjectTransactionEditor.php', 'PhabricatorProjectTransactionQuery' => 'applications/project/query/PhabricatorProjectTransactionQuery.php', + 'PhabricatorProjectTypeConfigOptionType' => 'applications/project/config/PhabricatorProjectTypeConfigOptionType.php', 'PhabricatorProjectUIEventListener' => 'applications/project/events/PhabricatorProjectUIEventListener.php', 'PhabricatorProjectUpdateController' => 'applications/project/controller/PhabricatorProjectUpdateController.php', 'PhabricatorProjectUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectUserFunctionDatasource.php', @@ -3084,6 +3101,7 @@ phutil_register_library_map(array( 'PhabricatorSettingsAdjustController' => 'applications/settings/controller/PhabricatorSettingsAdjustController.php', 'PhabricatorSettingsApplication' => 'applications/settings/application/PhabricatorSettingsApplication.php', 'PhabricatorSettingsMainController' => 'applications/settings/controller/PhabricatorSettingsMainController.php', + 'PhabricatorSettingsMainMenuBarExtension' => 'applications/settings/extension/PhabricatorSettingsMainMenuBarExtension.php', 'PhabricatorSettingsPanel' => 'applications/settings/panel/PhabricatorSettingsPanel.php', 'PhabricatorSetupCheck' => 'applications/config/check/PhabricatorSetupCheck.php', 'PhabricatorSetupCheckTestCase' => 'applications/config/check/__tests__/PhabricatorSetupCheckTestCase.php', @@ -3183,6 +3201,7 @@ phutil_register_library_map(array( 'PhabricatorStorageSchemaSpec' => 'infrastructure/storage/schema/PhabricatorStorageSchemaSpec.php', 'PhabricatorStorageSetupCheck' => 'applications/config/check/PhabricatorStorageSetupCheck.php', 'PhabricatorStreamingProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorStreamingProtocolAdapter.php', + 'PhabricatorStringListEditField' => 'applications/transactions/editfield/PhabricatorStringListEditField.php', 'PhabricatorSubscribableInterface' => 'applications/subscriptions/interface/PhabricatorSubscribableInterface.php', 'PhabricatorSubscribedToObjectEdgeType' => 'applications/transactions/edges/PhabricatorSubscribedToObjectEdgeType.php', 'PhabricatorSubscribersEditField' => 'applications/transactions/editfield/PhabricatorSubscribersEditField.php', @@ -3395,6 +3414,7 @@ phutil_register_library_map(array( 'PhameBlogArchiveController' => 'applications/phame/controller/blog/PhameBlogArchiveController.php', 'PhameBlogController' => 'applications/phame/controller/blog/PhameBlogController.php', 'PhameBlogCreateCapability' => 'applications/phame/capability/PhameBlogCreateCapability.php', + 'PhameBlogEditConduitAPIMethod' => 'applications/phame/conduit/PhameBlogEditConduitAPIMethod.php', 'PhameBlogEditController' => 'applications/phame/controller/blog/PhameBlogEditController.php', 'PhameBlogEditEngine' => 'applications/phame/editor/PhameBlogEditEngine.php', 'PhameBlogEditor' => 'applications/phame/editor/PhameBlogEditor.php', @@ -3405,24 +3425,26 @@ phutil_register_library_map(array( 'PhameBlogProfilePictureController' => 'applications/phame/controller/blog/PhameBlogProfilePictureController.php', 'PhameBlogQuery' => 'applications/phame/query/PhameBlogQuery.php', 'PhameBlogReplyHandler' => 'applications/phame/mail/PhameBlogReplyHandler.php', + 'PhameBlogSearchConduitAPIMethod' => 'applications/phame/conduit/PhameBlogSearchConduitAPIMethod.php', 'PhameBlogSearchEngine' => 'applications/phame/query/PhameBlogSearchEngine.php', 'PhameBlogSite' => 'applications/phame/site/PhameBlogSite.php', 'PhameBlogTransaction' => 'applications/phame/storage/PhameBlogTransaction.php', 'PhameBlogTransactionQuery' => 'applications/phame/query/PhameBlogTransactionQuery.php', 'PhameBlogViewController' => 'applications/phame/controller/blog/PhameBlogViewController.php', - 'PhameConduitAPIMethod' => 'applications/phame/conduit/PhameConduitAPIMethod.php', 'PhameConstants' => 'applications/phame/constants/PhameConstants.php', 'PhameController' => 'applications/phame/controller/PhameController.php', - 'PhameCreatePostConduitAPIMethod' => 'applications/phame/conduit/PhameCreatePostConduitAPIMethod.php', 'PhameDAO' => 'applications/phame/storage/PhameDAO.php', 'PhameDescriptionView' => 'applications/phame/view/PhameDescriptionView.php', 'PhameDraftListView' => 'applications/phame/view/PhameDraftListView.php', 'PhameHomeController' => 'applications/phame/controller/PhameHomeController.php', 'PhameLiveController' => 'applications/phame/controller/PhameLiveController.php', + 'PhameNextPostView' => 'applications/phame/view/PhameNextPostView.php', 'PhamePost' => 'applications/phame/storage/PhamePost.php', 'PhamePostCommentController' => 'applications/phame/controller/post/PhamePostCommentController.php', 'PhamePostController' => 'applications/phame/controller/post/PhamePostController.php', + 'PhamePostEditConduitAPIMethod' => 'applications/phame/conduit/PhamePostEditConduitAPIMethod.php', 'PhamePostEditController' => 'applications/phame/controller/post/PhamePostEditController.php', + 'PhamePostEditEngine' => 'applications/phame/editor/PhamePostEditEngine.php', 'PhamePostEditor' => 'applications/phame/editor/PhamePostEditor.php', 'PhamePostHistoryController' => 'applications/phame/controller/post/PhamePostHistoryController.php', 'PhamePostListController' => 'applications/phame/controller/post/PhamePostListController.php', @@ -3432,13 +3454,12 @@ phutil_register_library_map(array( 'PhamePostPublishController' => 'applications/phame/controller/post/PhamePostPublishController.php', 'PhamePostQuery' => 'applications/phame/query/PhamePostQuery.php', 'PhamePostReplyHandler' => 'applications/phame/mail/PhamePostReplyHandler.php', + 'PhamePostSearchConduitAPIMethod' => 'applications/phame/conduit/PhamePostSearchConduitAPIMethod.php', 'PhamePostSearchEngine' => 'applications/phame/query/PhamePostSearchEngine.php', 'PhamePostTransaction' => 'applications/phame/storage/PhamePostTransaction.php', 'PhamePostTransactionComment' => 'applications/phame/storage/PhamePostTransactionComment.php', 'PhamePostTransactionQuery' => 'applications/phame/query/PhamePostTransactionQuery.php', 'PhamePostViewController' => 'applications/phame/controller/post/PhamePostViewController.php', - 'PhameQueryConduitAPIMethod' => 'applications/phame/conduit/PhameQueryConduitAPIMethod.php', - 'PhameQueryPostsConduitAPIMethod' => 'applications/phame/conduit/PhameQueryPostsConduitAPIMethod.php', 'PhameSchemaSpec' => 'applications/phame/storage/PhameSchemaSpec.php', 'PhameSite' => 'applications/phame/site/PhameSite.php', 'PhluxController' => 'applications/phlux/controller/PhluxController.php', @@ -3742,10 +3763,12 @@ phutil_register_library_map(array( 'ProjectDefaultEditCapability' => 'applications/project/capability/ProjectDefaultEditCapability.php', 'ProjectDefaultJoinCapability' => 'applications/project/capability/ProjectDefaultJoinCapability.php', 'ProjectDefaultViewCapability' => 'applications/project/capability/ProjectDefaultViewCapability.php', + 'ProjectEditConduitAPIMethod' => 'applications/project/conduit/ProjectEditConduitAPIMethod.php', 'ProjectQueryConduitAPIMethod' => 'applications/project/conduit/ProjectQueryConduitAPIMethod.php', 'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php', 'ProjectRemarkupRuleTestCase' => 'applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php', 'ProjectReplyHandler' => 'applications/project/mail/ProjectReplyHandler.php', + 'ProjectSearchConduitAPIMethod' => 'applications/project/conduit/ProjectSearchConduitAPIMethod.php', 'QueryFormattingTestCase' => 'infrastructure/storage/__tests__/QueryFormattingTestCase.php', 'ReleephAuthorFieldSpecification' => 'applications/releeph/field/specification/ReleephAuthorFieldSpecification.php', 'ReleephBranch' => 'applications/releeph/storage/ReleephBranch.php', @@ -4150,7 +4173,7 @@ phutil_register_library_map(array( 'ConduitMethodDoesNotExistException' => 'ConduitMethodNotFoundException', 'ConduitMethodNotFoundException' => 'ConduitException', 'ConduitPHIDListParameterType' => 'ConduitListParameterType', - 'ConduitPHIDParameterType' => 'ConduitListParameterType', + 'ConduitPHIDParameterType' => 'ConduitParameterType', 'ConduitParameterType' => 'Phobject', 'ConduitPingConduitAPIMethod' => 'ConduitAPIMethod', 'ConduitProjectListParameterType' => 'ConduitListParameterType', @@ -4158,7 +4181,7 @@ phutil_register_library_map(array( 'ConduitResultSearchEngineExtension' => 'PhabricatorSearchEngineExtension', 'ConduitSSHWorkflow' => 'PhabricatorSSHWorkflow', 'ConduitStringListParameterType' => 'ConduitListParameterType', - 'ConduitStringParameterType' => 'ConduitListParameterType', + 'ConduitStringParameterType' => 'ConduitParameterType', 'ConduitTokenGarbageCollector' => 'PhabricatorGarbageCollector', 'ConduitUserListParameterType' => 'ConduitListParameterType', 'ConduitWildParameterType' => 'ConduitListParameterType', @@ -4315,6 +4338,7 @@ phutil_register_library_map(array( 'DifferentialDiffContentRemovedHeraldField' => 'DifferentialDiffHeraldField', 'DifferentialDiffCreateController' => 'DifferentialController', 'DifferentialDiffEditor' => 'PhabricatorApplicationTransactionEditor', + 'DifferentialDiffExtractionEngine' => 'Phobject', 'DifferentialDiffHeraldField' => 'HeraldField', 'DifferentialDiffHeraldFieldGroup' => 'HeraldFieldGroup', 'DifferentialDiffInlineCommentQuery' => 'PhabricatorDiffInlineCommentQuery', @@ -4478,17 +4502,15 @@ phutil_register_library_map(array( 'DiffusionAuditorsAddAuditorsHeraldAction' => 'DiffusionAuditorsHeraldAction', 'DiffusionAuditorsAddSelfHeraldAction' => 'DiffusionAuditorsHeraldAction', 'DiffusionAuditorsHeraldAction' => 'HeraldAction', + 'DiffusionBlameConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', + 'DiffusionBlameQuery' => 'DiffusionQuery', 'DiffusionBlockHeraldAction' => 'HeraldAction', 'DiffusionBranchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionBranchTableController' => 'DiffusionController', 'DiffusionBranchTableView' => 'DiffusionView', 'DiffusionBrowseController' => 'DiffusionController', - 'DiffusionBrowseDirectoryController' => 'DiffusionBrowseController', - 'DiffusionBrowseFileController' => 'DiffusionBrowseController', - 'DiffusionBrowseMainController' => 'DiffusionBrowseController', 'DiffusionBrowseQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionBrowseResultSet' => 'Phobject', - 'DiffusionBrowseSearchController' => 'DiffusionBrowseController', 'DiffusionBrowseTableView' => 'DiffusionView', 'DiffusionCachedResolveRefsQuery' => 'DiffusionLowLevelQuery', 'DiffusionChangeController' => 'DiffusionController', @@ -4549,17 +4571,15 @@ phutil_register_library_map(array( 'DiffusionExternalController' => 'DiffusionController', 'DiffusionExternalSymbolQuery' => 'Phobject', 'DiffusionExternalSymbolsSource' => 'Phobject', - 'DiffusionFileContent' => 'Phobject', 'DiffusionFileContentQuery' => 'DiffusionQuery', 'DiffusionFileContentQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionFindSymbolsConduitAPIMethod' => 'DiffusionConduitAPIMethod', - 'DiffusionGetCommitsConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionGetLintMessagesConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionGetRecentCommitsByPathConduitAPIMethod' => 'DiffusionConduitAPIMethod', + 'DiffusionGitBlameQuery' => 'DiffusionBlameQuery', 'DiffusionGitBranch' => 'Phobject', 'DiffusionGitBranchTestCase' => 'PhabricatorTestCase', 'DiffusionGitFileContentQuery' => 'DiffusionFileContentQuery', - 'DiffusionGitFileContentQueryTestCase' => 'PhabricatorTestCase', 'DiffusionGitRawDiffQuery' => 'DiffusionRawDiffQuery', 'DiffusionGitReceivePackSSHWorkflow' => 'DiffusionGitSSHWorkflow', 'DiffusionGitRequest' => 'DiffusionRequest', @@ -4576,7 +4596,6 @@ phutil_register_library_map(array( 'DiffusionLastModifiedQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionLintController' => 'DiffusionController', 'DiffusionLintCountQuery' => 'PhabricatorQuery', - 'DiffusionLintDetailsController' => 'DiffusionController', 'DiffusionLintSaveRunner' => 'Phobject', 'DiffusionLookSoonConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionLowLevelCommitFieldsQuery' => 'DiffusionLowLevelQuery', @@ -4588,6 +4607,7 @@ phutil_register_library_map(array( 'DiffusionLowLevelParentsQuery' => 'DiffusionLowLevelQuery', 'DiffusionLowLevelQuery' => 'Phobject', 'DiffusionLowLevelResolveRefsQuery' => 'DiffusionLowLevelQuery', + 'DiffusionMercurialBlameQuery' => 'DiffusionBlameQuery', 'DiffusionMercurialFileContentQuery' => 'DiffusionFileContentQuery', 'DiffusionMercurialRawDiffQuery' => 'DiffusionRawDiffQuery', 'DiffusionMercurialRequest' => 'DiffusionRequest', @@ -4699,6 +4719,7 @@ phutil_register_library_map(array( 'DiffusionSubversionServeSSHWorkflow' => 'DiffusionSubversionSSHWorkflow', 'DiffusionSubversionWireProtocol' => 'Phobject', 'DiffusionSubversionWireProtocolTestCase' => 'PhabricatorTestCase', + 'DiffusionSvnBlameQuery' => 'DiffusionBlameQuery', 'DiffusionSvnFileContentQuery' => 'DiffusionFileContentQuery', 'DiffusionSvnRawDiffQuery' => 'DiffusionRawDiffQuery', 'DiffusionSvnRequest' => 'DiffusionRequest', @@ -4900,6 +4921,7 @@ phutil_register_library_map(array( 'DrydockDAO', 'PhabricatorPolicyInterface', ), + 'DrydockRepositoryOperationDismissController' => 'DrydockController', 'DrydockRepositoryOperationListController' => 'DrydockController', 'DrydockRepositoryOperationPHIDType' => 'PhabricatorPHIDType', 'DrydockRepositoryOperationQuery' => 'DrydockQuery', @@ -5205,6 +5227,8 @@ phutil_register_library_map(array( 'HeraldNewObjectField' => 'HeraldField', 'HeraldNotifyActionGroup' => 'HeraldActionGroup', 'HeraldObjectTranscript' => 'Phobject', + 'HeraldPhameBlogAdapter' => 'HeraldAdapter', + 'HeraldPhamePostAdapter' => 'HeraldAdapter', 'HeraldPholioMockAdapter' => 'HeraldAdapter', 'HeraldPonderQuestionAdapter' => 'HeraldAdapter', 'HeraldPreCommitAdapter' => 'HeraldAdapter', @@ -5603,6 +5627,7 @@ phutil_register_library_map(array( 'PHUIListItemView' => 'AphrontTagView', 'PHUIListView' => 'AphrontTagView', 'PHUIListViewTestCase' => 'PhabricatorTestCase', + 'PHUIMainMenuView' => 'AphrontView', 'PHUIObjectBoxView' => 'AphrontView', 'PHUIObjectItemListExample' => 'PhabricatorUIExample', 'PHUIObjectItemListView' => 'AphrontTagView', @@ -5851,6 +5876,7 @@ phutil_register_library_map(array( 'PhabricatorAuthListController' => 'PhabricatorAuthProviderConfigController', 'PhabricatorAuthLoginController' => 'PhabricatorAuthController', 'PhabricatorAuthLoginHandler' => 'Phobject', + 'PhabricatorAuthMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension', 'PhabricatorAuthManagementCachePKCS8Workflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementLDAPWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementListFactorsWorkflow' => 'PhabricatorAuthManagementWorkflow', @@ -5933,6 +5959,7 @@ phutil_register_library_map(array( 'PhabricatorBadgesDAO' => 'PhabricatorLiskDAO', 'PhabricatorBadgesDefaultEditCapability' => 'PhabricatorPolicyCapability', 'PhabricatorBadgesEditController' => 'PhabricatorBadgesController', + 'PhabricatorBadgesEditEngine' => 'PhabricatorEditEngine', 'PhabricatorBadgesEditRecipientsController' => 'PhabricatorBadgesController', 'PhabricatorBadgesEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorBadgesIconSet' => 'PhabricatorIconSet', @@ -6352,7 +6379,10 @@ phutil_register_library_map(array( 'PhabricatorDiffPreferencesSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorDifferenceEngine' => 'Phobject', 'PhabricatorDifferentialApplication' => 'PhabricatorApplication', + 'PhabricatorDifferentialAttachCommitWorkflow' => 'PhabricatorDifferentialManagementWorkflow', 'PhabricatorDifferentialConfigOptions' => 'PhabricatorApplicationConfigOptions', + 'PhabricatorDifferentialExtractWorkflow' => 'PhabricatorDifferentialManagementWorkflow', + 'PhabricatorDifferentialManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorDifferentialRevisionTestDataGenerator' => 'PhabricatorTestDataGenerator', 'PhabricatorDiffusionApplication' => 'PhabricatorApplication', 'PhabricatorDiffusionConfigOptions' => 'PhabricatorApplicationConfigOptions', @@ -6618,6 +6648,7 @@ phutil_register_library_map(array( 'PhabricatorHelpDocumentationController' => 'PhabricatorHelpController', 'PhabricatorHelpEditorProtocolController' => 'PhabricatorHelpController', 'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController', + 'PhabricatorHelpMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension', 'PhabricatorHeraldApplication' => 'PhabricatorApplication', 'PhabricatorHighSecurityRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 'PhabricatorHomeApplication' => 'PhabricatorApplication', @@ -6636,6 +6667,7 @@ phutil_register_library_map(array( 'PhabricatorIRCProtocolAdapter' => 'PhabricatorProtocolAdapter', 'PhabricatorIconRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorIconSet' => 'Phobject', + 'PhabricatorIconSetEditField' => 'PhabricatorEditField', 'PhabricatorIconSetIcon' => 'Phobject', 'PhabricatorImageMacroRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorImageTransformer' => 'Phobject', @@ -6734,6 +6766,7 @@ phutil_register_library_map(array( 'PhabricatorMailSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorMailTarget' => 'Phobject', 'PhabricatorMailgunConfigOptions' => 'PhabricatorApplicationConfigOptions', + 'PhabricatorMainMenuBarExtension' => 'Phobject', 'PhabricatorMainMenuSearchView' => 'AphrontView', 'PhabricatorMainMenuView' => 'AphrontView', 'PhabricatorManagementWorkflow' => 'PhutilArgumentWorkflow', @@ -7023,6 +7056,7 @@ phutil_register_library_map(array( 'PhabricatorPeopleLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorPeopleLogSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorPeopleLogsController' => 'PhabricatorPeopleController', + 'PhabricatorPeopleMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension', 'PhabricatorPeopleNewController' => 'PhabricatorPeopleController', 'PhabricatorPeopleNoOwnerDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorPeopleOwnerDatasource' => 'PhabricatorTypeaheadCompositeDatasource', @@ -7144,6 +7178,7 @@ phutil_register_library_map(array( 'PhabricatorCustomFieldInterface', 'PhabricatorDestructibleInterface', 'PhabricatorFulltextInterface', + 'PhabricatorConduitResultInterface', ), 'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction', 'PhabricatorProjectApplication' => 'PhabricatorApplication', @@ -7185,14 +7220,17 @@ phutil_register_library_map(array( 'PhabricatorProjectDAO' => 'PhabricatorLiskDAO', 'PhabricatorProjectDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorProjectDescriptionField' => 'PhabricatorProjectStandardCustomField', - 'PhabricatorProjectEditDetailsController' => 'PhabricatorProjectController', + 'PhabricatorProjectEditController' => 'PhabricatorProjectController', + 'PhabricatorProjectEditEngine' => 'PhabricatorEditEngine', 'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController', 'PhabricatorProjectFeedController' => 'PhabricatorProjectController', 'PhabricatorProjectFulltextEngine' => 'PhabricatorFulltextEngine', 'PhabricatorProjectHeraldAction' => 'HeraldAction', 'PhabricatorProjectIconSet' => 'PhabricatorIconSet', 'PhabricatorProjectListController' => 'PhabricatorProjectController', - 'PhabricatorProjectLogicalAndDatasource' => 'PhabricatorTypeaheadCompositeDatasource', + 'PhabricatorProjectListView' => 'AphrontView', + 'PhabricatorProjectLockController' => 'PhabricatorProjectController', + 'PhabricatorProjectLogicalAncestorDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectLogicalDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectLogicalOrNotDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectLogicalUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource', @@ -7203,6 +7241,7 @@ phutil_register_library_map(array( 'PhabricatorProjectMembersEditController' => 'PhabricatorProjectController', 'PhabricatorProjectMembersPolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorProjectMembersRemoveController' => 'PhabricatorProjectController', + 'PhabricatorProjectMilestonesController' => 'PhabricatorProjectController', 'PhabricatorProjectMoveController' => 'PhabricatorProjectController', 'PhabricatorProjectNameContextFreeGrammar' => 'PhutilContextFreeGrammar', 'PhabricatorProjectNoProjectsDatasource' => 'PhabricatorTypeaheadDatasource', @@ -7225,10 +7264,12 @@ phutil_register_library_map(array( 'PhabricatorStandardCustomFieldInterface', ), 'PhabricatorProjectStatus' => 'Phobject', + 'PhabricatorProjectSubprojectsController' => 'PhabricatorProjectController', 'PhabricatorProjectTestDataGenerator' => 'PhabricatorTestDataGenerator', 'PhabricatorProjectTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorProjectTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorProjectTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorProjectTypeConfigOptionType' => 'PhabricatorConfigJSONOptionType', 'PhabricatorProjectUIEventListener' => 'PhabricatorEventListener', 'PhabricatorProjectUpdateController' => 'PhabricatorProjectController', 'PhabricatorProjectUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', @@ -7468,6 +7509,7 @@ phutil_register_library_map(array( 'PhabricatorSettingsAdjustController' => 'PhabricatorController', 'PhabricatorSettingsApplication' => 'PhabricatorApplication', 'PhabricatorSettingsMainController' => 'PhabricatorController', + 'PhabricatorSettingsMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension', 'PhabricatorSettingsPanel' => 'Phobject', 'PhabricatorSetupCheck' => 'Phobject', 'PhabricatorSetupCheckTestCase' => 'PhabricatorTestCase', @@ -7584,6 +7626,7 @@ phutil_register_library_map(array( 'PhabricatorStorageSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorStorageSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorStreamingProtocolAdapter' => 'PhabricatorProtocolAdapter', + 'PhabricatorStringListEditField' => 'PhabricatorEditField', 'PhabricatorSubscribedToObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorSubscribersEditField' => 'PhabricatorTokenizerEditField', 'PhabricatorSubscribersQuery' => 'PhabricatorQuery', @@ -7831,10 +7874,12 @@ phutil_register_library_map(array( 'PhabricatorProjectInterface', 'PhabricatorDestructibleInterface', 'PhabricatorApplicationTransactionInterface', + 'PhabricatorConduitResultInterface', ), 'PhameBlogArchiveController' => 'PhameBlogController', 'PhameBlogController' => 'PhameController', 'PhameBlogCreateCapability' => 'PhabricatorPolicyCapability', + 'PhameBlogEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhameBlogEditController' => 'PhameBlogController', 'PhameBlogEditEngine' => 'PhabricatorEditEngine', 'PhameBlogEditor' => 'PhabricatorApplicationTransactionEditor', @@ -7845,20 +7890,20 @@ phutil_register_library_map(array( 'PhameBlogProfilePictureController' => 'PhameBlogController', 'PhameBlogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhameBlogReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', + 'PhameBlogSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'PhameBlogSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhameBlogSite' => 'PhameSite', 'PhameBlogTransaction' => 'PhabricatorApplicationTransaction', 'PhameBlogTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhameBlogViewController' => 'PhameLiveController', - 'PhameConduitAPIMethod' => 'ConduitAPIMethod', 'PhameConstants' => 'Phobject', 'PhameController' => 'PhabricatorController', - 'PhameCreatePostConduitAPIMethod' => 'PhameConduitAPIMethod', 'PhameDAO' => 'PhabricatorLiskDAO', 'PhameDescriptionView' => 'AphrontTagView', 'PhameDraftListView' => 'AphrontTagView', 'PhameHomeController' => 'PhamePostController', 'PhameLiveController' => 'PhameController', + 'PhameNextPostView' => 'AphrontTagView', 'PhamePost' => array( 'PhameDAO', 'PhabricatorPolicyInterface', @@ -7869,10 +7914,13 @@ phutil_register_library_map(array( 'PhabricatorSubscribableInterface', 'PhabricatorDestructibleInterface', 'PhabricatorTokenReceiverInterface', + 'PhabricatorConduitResultInterface', ), 'PhamePostCommentController' => 'PhamePostController', 'PhamePostController' => 'PhameController', + 'PhamePostEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhamePostEditController' => 'PhamePostController', + 'PhamePostEditEngine' => 'PhabricatorEditEngine', 'PhamePostEditor' => 'PhabricatorApplicationTransactionEditor', 'PhamePostHistoryController' => 'PhamePostController', 'PhamePostListController' => 'PhamePostController', @@ -7882,13 +7930,12 @@ phutil_register_library_map(array( 'PhamePostPublishController' => 'PhamePostController', 'PhamePostQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhamePostReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', + 'PhamePostSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'PhamePostSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhamePostTransaction' => 'PhabricatorApplicationTransaction', 'PhamePostTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhamePostTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhamePostViewController' => 'PhameLiveController', - 'PhameQueryConduitAPIMethod' => 'PhameConduitAPIMethod', - 'PhameQueryPostsConduitAPIMethod' => 'PhameConduitAPIMethod', 'PhameSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhameSite' => 'PhabricatorSite', 'PhluxController' => 'PhabricatorController', @@ -8290,10 +8337,12 @@ phutil_register_library_map(array( 'ProjectDefaultEditCapability' => 'PhabricatorPolicyCapability', 'ProjectDefaultJoinCapability' => 'PhabricatorPolicyCapability', 'ProjectDefaultViewCapability' => 'PhabricatorPolicyCapability', + 'ProjectEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'ProjectQueryConduitAPIMethod' => 'ProjectConduitAPIMethod', 'ProjectRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'ProjectRemarkupRuleTestCase' => 'PhabricatorTestCase', 'ProjectReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', + 'ProjectSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'QueryFormattingTestCase' => 'PhabricatorTestCase', 'ReleephAuthorFieldSpecification' => 'ReleephFieldSpecification', 'ReleephBranch' => array( diff --git a/src/aphront/AphrontRequest.php b/src/aphront/AphrontRequest.php index f15b289d38..e88a5a5e36 100644 --- a/src/aphront/AphrontRequest.php +++ b/src/aphront/AphrontRequest.php @@ -123,6 +123,11 @@ final class AphrontRequest extends Phobject { */ public function getInt($name, $default = null) { if (isset($this->requestData[$name])) { + // Converting from array to int is "undefined". Don't rely on whatever + // PHP decides to do. + if (is_array($this->requestData[$name])) { + return $default; + } return (int)$this->requestData[$name]; } else { return $default; diff --git a/src/applications/audit/editor/PhabricatorAuditCommentEditor.php b/src/applications/audit/editor/PhabricatorAuditCommentEditor.php index 03476f70a7..8a17999ee0 100644 --- a/src/applications/audit/editor/PhabricatorAuditCommentEditor.php +++ b/src/applications/audit/editor/PhabricatorAuditCommentEditor.php @@ -46,7 +46,7 @@ final class PhabricatorAuditCommentEditor extends PhabricatorEditor { 'diffusion-audit-'.$commit->getPHID(), pht( 'Commit %s', - 'r'.$repository->getCallsign().$commit->getCommitIdentifier()), + $commit->getMonogram()), ); } diff --git a/src/applications/audit/management/PhabricatorAuditManagementDeleteWorkflow.php b/src/applications/audit/management/PhabricatorAuditManagementDeleteWorkflow.php index fa9dbff635..95fbdb430f 100644 --- a/src/applications/audit/management/PhabricatorAuditManagementDeleteWorkflow.php +++ b/src/applications/audit/management/PhabricatorAuditManagementDeleteWorkflow.php @@ -222,22 +222,23 @@ final class PhabricatorAuditManagementDeleteWorkflow return $list; } - private function loadRepos($callsigns) { - $callsigns = $this->parseList($callsigns); - if (!$callsigns) { + private function loadRepos($identifiers) { + $identifiers = $this->parseList($identifiers); + if (!$identifiers) { return null; } - $repos = id(new PhabricatorRepositoryQuery()) + $query = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getViewer()) - ->withCallsigns($callsigns) - ->execute(); - $repos = mpull($repos, null, 'getCallsign'); + ->withIdentifiers($identifiers); - foreach ($callsigns as $sign) { - if (empty($repos[$sign])) { + $repos = $query->execute(); + + $map = $query->getIdentifierMap(); + foreach ($identifiers as $identifier) { + if (empty($map[$identifier])) { throw new PhutilArgumentUsageException( - pht('No such repository with callsign "%s"!', $sign)); + pht('No repository "%s" exists!', $identifier)); } } diff --git a/src/applications/auth/application/PhabricatorAuthApplication.php b/src/applications/auth/application/PhabricatorAuthApplication.php index dfa1be6f75..a9831b20e3 100644 --- a/src/applications/auth/application/PhabricatorAuthApplication.php +++ b/src/applications/auth/application/PhabricatorAuthApplication.php @@ -38,48 +38,6 @@ final class PhabricatorAuthApplication extends PhabricatorApplication { return array(); } - public function buildMainMenuItems( - PhabricatorUser $user, - PhabricatorController $controller = null) { - - $items = array(); - - if ($user->isLoggedIn()) { - $item = id(new PHUIListItemView()) - ->addClass('core-menu-item') - ->setName(pht('Log Out')) - ->setIcon('fa-sign-out') - ->setWorkflow(true) - ->setHref('/logout/') - ->setSelected(($controller instanceof PhabricatorLogoutController)) - ->setAural(pht('Log Out')) - ->setOrder(900); - $items[] = $item; - } else { - if ($controller instanceof PhabricatorAuthController) { - // Don't show the "Login" item on auth controllers, since they're - // generally all related to logging in anyway. - } else { - $uri = new PhutilURI('/auth/start/'); - if ($controller) { - $path = $controller->getRequest()->getPath(); - $uri->setQueryParam('next', $path); - } - $item = id(new PHUIListItemView()) - ->addClass('core-menu-item') - ->setName(pht('Log In')) - // TODO: Login icon? - ->setIcon('fa-sign-in') - ->setHref($uri) - ->setAural(pht('Log In')) - ->setOrder(900); - $items[] = $item; - } - } - - return $items; - } - public function getApplicationGroup() { return self::GROUP_ADMIN; } diff --git a/src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php b/src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php new file mode 100644 index 0000000000..47264517e0 --- /dev/null +++ b/src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php @@ -0,0 +1,73 @@ +getViewer(); + + if ($viewer->isLoggedIn()) { + return array( + $this->buildLogoutMenu(), + ); + } + + $controller = $this->getController(); + if ($controller instanceof PhabricatorAuthController) { + // Don't show the "Login" item on auth controllers, since they're + // generally all related to logging in anyway. + return array(); + } + + return array( + $this->buildLoginMenu(), + ); + } + + private function buildLogoutMenu() { + $controller = $this->getController(); + + $is_selected = ($controller instanceof PhabricatorLogoutController); + + $bar_item = id(new PHUIListItemView()) + ->addClass('core-menu-item') + ->setName(pht('Log Out')) + ->setIcon('fa-sign-out') + ->setWorkflow(true) + ->setHref('/logout/') + ->setSelected($is_selected) + ->setAural(pht('Log Out')); + + return id(new PHUIMainMenuView()) + ->setOrder(900) + ->setMenuBarItem($bar_item); + } + + private function buildLoginMenu() { + $controller = $this->getController(); + + $uri = new PhutilURI('/auth/start/'); + if ($controller) { + $path = $controller->getRequest()->getPath(); + $uri->setQueryParam('next', $path); + } + + $bar_item = id(new PHUIListItemView()) + ->addClass('core-menu-item') + ->setName(pht('Log In')) + ->setIcon('fa-sign-in') + ->setHref($uri) + ->setAural(pht('Log In')); + + return id(new PHUIMainMenuView()) + ->setOrder(900) + ->setMenuBarItem($bar_item); + } + +} diff --git a/src/applications/badges/controller/PhabricatorBadgesEditController.php b/src/applications/badges/controller/PhabricatorBadgesEditController.php index b1a4fb1030..3ac1aebe58 100644 --- a/src/applications/badges/controller/PhabricatorBadgesEditController.php +++ b/src/applications/badges/controller/PhabricatorBadgesEditController.php @@ -1,186 +1,12 @@ getViewer(); - $id = $request->getURIData('id'); - - if ($id) { - $badge = id(new PhabricatorBadgesQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$badge) { - return new Aphront404Response(); - } - $is_new = false; - } else { - $this->requireApplicationCapability( - PhabricatorBadgesCreateCapability::CAPABILITY); - - $badge = PhabricatorBadgesBadge::initializeNewBadge($viewer); - $is_new = true; - } - - if ($is_new) { - $title = pht('Create Badge'); - $button_text = pht('Create Badge'); - $cancel_uri = $this->getApplicationURI(); - } else { - $title = pht( - 'Edit %s', - $badge->getName()); - $button_text = pht('Save Changes'); - $cancel_uri = $this->getApplicationURI('view/'.$id.'/'); - } - - $e_name = true; - $v_name = $badge->getName(); - $v_icon = $badge->getIcon(); - $v_flav = $badge->getFlavor(); - $v_desc = $badge->getDescription(); - $v_qual = $badge->getQuality(); - $v_edit = $badge->getEditPolicy(); - - $validation_exception = null; - if ($request->isFormPost()) { - $v_name = $request->getStr('name'); - $v_flav = $request->getStr('flavor'); - $v_desc = $request->getStr('description'); - $v_icon = $request->getStr('icon'); - $v_qual = $request->getStr('quality'); - - $v_view = $request->getStr('viewPolicy'); - $v_edit = $request->getStr('editPolicy'); - - $type_name = PhabricatorBadgesTransaction::TYPE_NAME; - $type_flav = PhabricatorBadgesTransaction::TYPE_FLAVOR; - $type_desc = PhabricatorBadgesTransaction::TYPE_DESCRIPTION; - $type_icon = PhabricatorBadgesTransaction::TYPE_ICON; - $type_qual = PhabricatorBadgesTransaction::TYPE_QUALITY; - - $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; - - $xactions = array(); - - $xactions[] = id(new PhabricatorBadgesTransaction()) - ->setTransactionType($type_name) - ->setNewValue($v_name); - - $xactions[] = id(new PhabricatorBadgesTransaction()) - ->setTransactionType($type_flav) - ->setNewValue($v_flav); - - $xactions[] = id(new PhabricatorBadgesTransaction()) - ->setTransactionType($type_desc) - ->setNewValue($v_desc); - - $xactions[] = id(new PhabricatorBadgesTransaction()) - ->setTransactionType($type_icon) - ->setNewValue($v_icon); - - $xactions[] = id(new PhabricatorBadgesTransaction()) - ->setTransactionType($type_qual) - ->setNewValue($v_qual); - - $xactions[] = id(new PhabricatorBadgesTransaction()) - ->setTransactionType($type_edit) - ->setNewValue($v_edit); - - $editor = id(new PhabricatorBadgesEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true); - - try { - $editor->applyTransactions($badge, $xactions); - $return_uri = $this->getApplicationURI('view/'.$badge->getID().'/'); - return id(new AphrontRedirectResponse())->setURI($return_uri); - - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - - $e_name = $ex->getShortMessage($type_name); - } - } - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($badge) - ->execute(); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('name') - ->setLabel(pht('Name')) - ->setValue($v_name) - ->setError($e_name)) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('flavor') - ->setLabel(pht('Flavor Text')) - ->setValue($v_flav)) - ->appendChild( - id(new PHUIFormIconSetControl()) - ->setLabel(pht('Icon')) - ->setName('icon') - ->setIconSet(new PhabricatorBadgesIconSet()) - ->setValue($v_icon)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setName('quality') - ->setLabel(pht('Quality')) - ->setValue($v_qual) - ->setOptions($badge->getQualityNameMap())) - ->appendChild( - id(new PhabricatorRemarkupControl()) - ->setUser($viewer) - ->setName('description') - ->setLabel(pht('Description')) - ->setValue($v_desc)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('editPolicy') - ->setPolicyObject($badge) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setValue($v_edit) - ->setPolicies($policies)) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue($button_text) - ->addCancelButton($cancel_uri)); - - $crumbs = $this->buildApplicationCrumbs(); - if ($is_new) { - $crumbs->addTextCrumb(pht('Create Badge')); - } else { - $crumbs->addTextCrumb( - $badge->getName(), - '/badges/view/'.$badge->getID().'/'); - $crumbs->addTextCrumb(pht('Edit')); - } - - $box = id(new PHUIObjectBoxView()) - ->setValidationException($validation_exception) - ->setHeaderText($title) - ->appendChild($form); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild( - array( - $box, - )); + return id(new PhabricatorBadgesEditEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/badges/editor/PhabricatorBadgesEditEngine.php b/src/applications/badges/editor/PhabricatorBadgesEditEngine.php new file mode 100644 index 0000000000..49a8e85022 --- /dev/null +++ b/src/applications/badges/editor/PhabricatorBadgesEditEngine.php @@ -0,0 +1,98 @@ +getViewer()); + } + + protected function newObjectQuery() { + return new PhabricatorBadgesQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Badge'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return $object->getName(); + } + + protected function getObjectCreateShortText() { + return pht('Create Badge'); + } + + protected function getCommentViewHeaderText($object) { + return pht('Add Comment'); + } + + protected function getCommentViewButtonText($object) { + return pht('Submit'); + } + + protected function getObjectViewURI($object) { + return $object->getViewURI(); + } + + protected function buildCustomEditFields($object) { + + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Badge name.')) + ->setTransactionType(PhabricatorBadgesTransaction::TYPE_NAME) + ->setValue($object->getName()), + id(new PhabricatorTextEditField()) + ->setKey('flavor') + ->setLabel(pht('Flavor text')) + ->setDescription(pht('Short description of the badge.')) + ->setValue($object->getFlavor()) + ->setTransactionType(PhabricatorBadgesTransaction::TYPE_FLAVOR), + id(new PhabricatorIconSetEditField()) + ->setKey('icon') + ->setLabel(pht('Icon')) + ->setIconSet(new PhabricatorBadgesIconSet()) + ->setTransactionType(PhabricatorBadgesTransaction::TYPE_ICON) + ->setConduitDescription(pht('Change the badge icon.')) + ->setConduitTypeDescription(pht('New badge icon.')) + ->setValue($object->getIcon()), + id(new PhabricatorSelectEditField()) + ->setKey('quality') + ->setLabel(pht('Quality')) + ->setValue($object->getQuality()) + ->setTransactionType(PhabricatorBadgesTransaction::TYPE_QUALITY) + ->setOptions($object->getQualityNameMap()), + id(new PhabricatorRemarkupEditField()) + ->setKey('description') + ->setLabel(pht('Description')) + ->setDescription(pht('Badge long description.')) + ->setTransactionType(PhabricatorBadgesTransaction::TYPE_DESCRIPTION) + ->setValue($object->getDescription()), + ); + } + +} diff --git a/src/applications/badges/storage/PhabricatorBadgesBadge.php b/src/applications/badges/storage/PhabricatorBadgesBadge.php index f6ff495a6e..29cccb9f44 100644 --- a/src/applications/badges/storage/PhabricatorBadgesBadge.php +++ b/src/applications/badges/storage/PhabricatorBadgesBadge.php @@ -111,6 +111,10 @@ final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO return $this->assertAttached($this->recipientPHIDs); } + public function getViewURI() { + return '/badges/view/'.$this->getID().'/'; + } + public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20)); diff --git a/src/applications/conduit/parametertype/ConduitPHIDParameterType.php b/src/applications/conduit/parametertype/ConduitPHIDParameterType.php index c10aa2a791..f182758071 100644 --- a/src/applications/conduit/parametertype/ConduitPHIDParameterType.php +++ b/src/applications/conduit/parametertype/ConduitPHIDParameterType.php @@ -1,7 +1,7 @@ encodeFormatted($value); + return PhabricatorConfigJSON::prettyPrintJSON($value); } else { return $value; } diff --git a/src/applications/console/plugin/DarkConsoleErrorLogPlugin.php b/src/applications/console/plugin/DarkConsoleErrorLogPlugin.php index 591904a85f..0d9604dcba 100644 --- a/src/applications/console/plugin/DarkConsoleErrorLogPlugin.php +++ b/src/applications/console/plugin/DarkConsoleErrorLogPlugin.php @@ -64,7 +64,7 @@ final class DarkConsoleErrorLogPlugin extends DarkConsolePlugin { $line .= ' called at ['.$entry['file'].':'.$entry['line'].']'; try { $user = $this->getRequest()->getUser(); - $href = $user->loadEditorLink($entry['file'], $entry['line'], ''); + $href = $user->loadEditorLink($entry['file'], $entry['line'], null); } catch (Exception $ex) { // The database can be inaccessible. } diff --git a/src/applications/differential/controller/DifferentialRevisionLandController.php b/src/applications/differential/controller/DifferentialRevisionLandController.php index 4f8787956f..f2d2497a42 100644 --- a/src/applications/differential/controller/DifferentialRevisionLandController.php +++ b/src/applications/differential/controller/DifferentialRevisionLandController.php @@ -146,7 +146,7 @@ final class DifferentialRevisionLandController extends DifferentialController { $looksoon = new ConduitCall( 'diffusion.looksoon', array( - 'callsigns' => array($repository->getCallsign()), + 'repositories' => array($repository->getPHID()), )); $looksoon->setUser($request->getUser()); $looksoon->execute(); @@ -155,7 +155,7 @@ final class DifferentialRevisionLandController extends DifferentialController { } private function lockRepository($repository) { - $lock_name = __CLASS__.':'.($repository->getCallsign()); + $lock_name = __CLASS__.':'.($repository->getPHID()); $lock = PhabricatorGlobalLock::newLock($lock_name); $lock->lock(); return $lock; diff --git a/src/applications/differential/controller/DifferentialRevisionOperationController.php b/src/applications/differential/controller/DifferentialRevisionOperationController.php index 99067f9f2e..feeb6770b8 100644 --- a/src/applications/differential/controller/DifferentialRevisionOperationController.php +++ b/src/applications/differential/controller/DifferentialRevisionOperationController.php @@ -94,8 +94,7 @@ final class DifferentialRevisionOperationController ->setUser($viewer) ->appendRemarkupInstructions( pht( - 'In theory, this will do approximately what `arc land` would do. '. - 'In practice, you will have a riveting adventure instead.')) + '(NOTE) This feature is new and experimental.')) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Onto Branch')) @@ -103,11 +102,7 @@ final class DifferentialRevisionOperationController ->setLimit(1) ->setError($e_ref) ->setValue($v_ref) - ->setDatasource($ref_datasource)) - ->appendRemarkupInstructions( - pht( - '(WARNING) THIS FEATURE IS EXPERIMENTAL AND DANGEROUS! USE IT AT '. - 'YOUR OWN RISK!')); + ->setDatasource($ref_datasource)); return $this->newDialog() ->setWidth(AphrontDialogView::WIDTH_FORM) @@ -115,7 +110,7 @@ final class DifferentialRevisionOperationController ->setErrors($errors) ->appendForm($form) ->addCancelButton($detail_uri) - ->addSubmitButton(pht('Mutate Repository Unpredictably')); + ->addSubmitButton(pht('Land Revision')); } private function newRefQuery(PhabricatorRepository $repository) { diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 621464cc9b..71f4932fed 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -1047,6 +1047,7 @@ final class DifferentialRevisionViewController extends DifferentialController { $operations = id(new DrydockRepositoryOperationQuery()) ->setViewer($viewer) ->withObjectPHIDs(array($revision->getPHID())) + ->withIsDismissed(false) ->withOperationTypes( array( DrydockLandRepositoryOperation::OPCONST, diff --git a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php new file mode 100644 index 0000000000..0fc6391987 --- /dev/null +++ b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php @@ -0,0 +1,276 @@ +viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + + public function setAuthorPHID($author_phid) { + $this->authorPHID = $author_phid; + return $this; + } + + public function getAuthorPHID() { + return $this->authorPHID; + } + + public function newDiffFromCommit(PhabricatorRepositoryCommit $commit) { + $viewer = $this->getViewer(); + + $repository = $commit->getRepository(); + $identifier = $commit->getCommitIdentifier(); + $monogram = $commit->getMonogram(); + + $drequest = DiffusionRequest::newFromDictionary( + array( + 'user' => $viewer, + 'repository' => $repository, + )); + + $raw_diff = DiffusionQuery::callConduitWithDiffusionRequest( + $viewer, + $drequest, + 'diffusion.rawdiffquery', + array( + 'commit' => $identifier, + )); + + // TODO: Support adds, deletes and moves under SVN. + if (strlen($raw_diff)) { + $changes = id(new ArcanistDiffParser())->parseDiff($raw_diff); + } else { + // This is an empty diff, maybe made with `git commit --allow-empty`. + // NOTE: These diffs have the same tree hash as their ancestors, so + // they may attach to revisions in an unexpected way. Just let this + // happen for now, although it might make sense to special case it + // eventually. + $changes = array(); + } + + $diff = DifferentialDiff::newFromRawChanges($viewer, $changes) + ->setRepositoryPHID($repository->getPHID()) + ->setCreationMethod('commit') + ->setSourceControlSystem($repository->getVersionControlSystem()) + ->setLintStatus(DifferentialLintStatus::LINT_AUTO_SKIP) + ->setUnitStatus(DifferentialUnitStatus::UNIT_AUTO_SKIP) + ->setDateCreated($commit->getEpoch()) + ->setDescription($monogram); + + $author_phid = $this->getAuthorPHID(); + if ($author_phid !== null) { + $diff->setAuthorPHID($author_phid); + } + + $parents = DiffusionQuery::callConduitWithDiffusionRequest( + $viewer, + $drequest, + 'diffusion.commitparentsquery', + array( + 'commit' => $identifier, + )); + + if ($parents) { + $diff->setSourceControlBaseRevision(head($parents)); + } + + // TODO: Attach binary files. + + return $diff->save(); + } + + public function isDiffChangedBeforeCommit( + PhabricatorRepositoryCommit $commit, + DifferentialDiff $old, + DifferentialDiff $new) { + + $viewer = $this->getViewer(); + $repository = $commit->getRepository(); + $identifier = $commit->getCommitIdentifier(); + + $vs_changesets = array(); + foreach ($old->getChangesets() as $changeset) { + $path = $changeset->getAbsoluteRepositoryPath($repository, $old); + $path = ltrim($path, '/'); + $vs_changesets[$path] = $changeset; + } + + $changesets = array(); + foreach ($new->getChangesets() as $changeset) { + $path = $changeset->getAbsoluteRepositoryPath($repository, $new); + $path = ltrim($path, '/'); + $changesets[$path] = $changeset; + } + + if (array_fill_keys(array_keys($changesets), true) != + array_fill_keys(array_keys($vs_changesets), true)) { + return true; + } + + $file_phids = array(); + foreach ($vs_changesets as $changeset) { + $metadata = $changeset->getMetadata(); + $file_phid = idx($metadata, 'new:binary-phid'); + if ($file_phid) { + $file_phids[$file_phid] = $file_phid; + } + } + + $files = array(); + if ($file_phids) { + $files = id(new PhabricatorFileQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs($file_phids) + ->execute(); + $files = mpull($files, null, 'getPHID'); + } + + foreach ($changesets as $path => $changeset) { + $vs_changeset = $vs_changesets[$path]; + + $file_phid = idx($vs_changeset->getMetadata(), 'new:binary-phid'); + if ($file_phid) { + if (!isset($files[$file_phid])) { + return true; + } + + $drequest = DiffusionRequest::newFromDictionary( + array( + 'user' => $viewer, + 'repository' => $repository, + )); + + $response = DiffusionQuery::callConduitWithDiffusionRequest( + $viewer, + $drequest, + 'diffusion.filecontentquery', + array( + 'commit' => $identifier, + 'path' => $path, + )); + + $new_file_phid = $response['filePHID']; + if (!$new_file_phid) { + return true; + } + + $new_file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($new_file_phid)) + ->executeOne(); + if (!$new_file) { + return true; + } + + if ($files[$file_phid]->loadFileData() != $new_file->loadFileData()) { + return true; + } + } else { + $context = implode("\n", $changeset->makeChangesWithContext()); + $vs_context = implode("\n", $vs_changeset->makeChangesWithContext()); + + // We couldn't just compare $context and $vs_context because following + // diffs will be considered different: + // + // -(empty line) + // -echo 'test'; + // (empty line) + // + // (empty line) + // -echo "test"; + // -(empty line) + + $hunk = id(new DifferentialModernHunk())->setChanges($context); + $vs_hunk = id(new DifferentialModernHunk())->setChanges($vs_context); + if ($hunk->makeOldFile() != $vs_hunk->makeOldFile() || + $hunk->makeNewFile() != $vs_hunk->makeNewFile()) { + return true; + } + } + } + + return false; + } + + public function updateRevisionWithCommit( + DifferentialRevision $revision, + PhabricatorRepositoryCommit $commit, + array $more_xactions, + PhabricatorContentSource $content_source) { + + $viewer = $this->getViewer(); + $result_data = array(); + + $new_diff = $this->newDiffFromCommit($commit); + + $old_diff = $revision->getActiveDiff(); + $changed_uri = null; + if ($old_diff) { + $old_diff = id(new DifferentialDiffQuery()) + ->setViewer($viewer) + ->withIDs(array($old_diff->getID())) + ->needChangesets(true) + ->executeOne(); + if ($old_diff) { + $has_changed = $this->isDiffChangedBeforeCommit( + $commit, + $old_diff, + $new_diff); + if ($has_changed) { + $result_data['vsDiff'] = $old_diff->getID(); + + $revision_monogram = $revision->getMonogram(); + $old_id = $old_diff->getID(); + $new_id = $new_diff->getID(); + + $changed_uri = "/{$revision_monogram}?vs={$old_id}&id={$new_id}#toc"; + $changed_uri = PhabricatorEnv::getProductionURI($changed_uri); + } + } + } + + $xactions = array(); + + $xactions[] = id(new DifferentialTransaction()) + ->setTransactionType(DifferentialTransaction::TYPE_UPDATE) + ->setIgnoreOnNoEffect(true) + ->setNewValue($new_diff->getPHID()) + ->setMetadataValue('isCommitUpdate', true); + + foreach ($more_xactions as $more_xaction) { + $xactions[] = $more_xaction; + } + + $editor = id(new DifferentialTransactionEditor()) + ->setActor($viewer) + ->setContinueOnMissingFields(true) + ->setContentSource($content_source) + ->setChangedPriorToCommitURI($changed_uri) + ->setIsCloseByCommit(true); + + $author_phid = $this->getAuthorPHID(); + if ($author_phid !== null) { + $editor->setActingAsPHID($author_phid); + } + + try { + $editor->applyTransactions($revision, $xactions); + } catch (PhabricatorApplicationTransactionNoEffectException $ex) { + // NOTE: We've marked transactions other than the CLOSE transaction + // as ignored when they don't have an effect, so this means that we + // lost a race to close the revision. That's perfectly fine, we can + // just continue normally. + } + + return $result_data; + } + +} diff --git a/src/applications/differential/management/PhabricatorDifferentialAttachCommitWorkflow.php b/src/applications/differential/management/PhabricatorDifferentialAttachCommitWorkflow.php new file mode 100644 index 0000000000..5150dba315 --- /dev/null +++ b/src/applications/differential/management/PhabricatorDifferentialAttachCommitWorkflow.php @@ -0,0 +1,90 @@ +setName('attach-commit') + ->setExamples('**attach-commit** __commit__ __revision__') + ->setSynopsis(pht('Forcefully attach a commit to a revision.')) + ->setArguments( + array( + array( + 'name' => 'argv', + 'wildcard' => true, + 'help' => pht('Commit, and a revision to attach it to.'), + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $viewer = $this->getViewer(); + + $argv = $args->getArg('argv'); + if (count($argv) !== 2) { + throw new PhutilArgumentUsageException( + pht('Specify a commit and a revision to attach it to.')); + } + + $commit_name = head($argv); + $revision_name = last($argv); + + $commit = id(new DiffusionCommitQuery()) + ->setViewer($viewer) + ->withIdentifiers(array($commit_name)) + ->executeOne(); + if (!$commit) { + throw new PhutilArgumentUsageException( + pht('Commit "%s" does not exist.', $commit_name)); + } + + $revision = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withNames(array($revision_name)) + ->executeOne(); + + if (!$revision) { + throw new PhutilArgumentUsageException( + pht('Revision "%s" does not exist.', $revision_name)); + } + + if (!($revision instanceof DifferentialRevision)) { + throw new PhutilArgumentUsageException( + pht('Object "%s" must be a Differential revision.', $revision_name)); + } + + // Reload the revision to get the active diff. + $revision = id(new DifferentialRevisionQuery()) + ->setViewer($viewer) + ->withIDs(array($revision->getID())) + ->needActiveDiffs(true) + ->executeOne(); + + $differential_phid = id(new PhabricatorDifferentialApplication()) + ->getPHID(); + + $extraction_engine = id(new DifferentialDiffExtractionEngine()) + ->setViewer($viewer) + ->setAuthorPHID($differential_phid); + + $content_source = PhabricatorContentSource::newForSource( + PhabricatorContentSource::SOURCE_CONSOLE, + array()); + + $extraction_engine->updateRevisionWithCommit( + $revision, + $commit, + array(), + $content_source); + + echo tsprintf( + "%s\n", + pht( + 'Attached "%s" to "%s".', + $commit->getMonogram(), + $revision->getMonogram())); + } + + +} diff --git a/src/applications/differential/management/PhabricatorDifferentialExtractWorkflow.php b/src/applications/differential/management/PhabricatorDifferentialExtractWorkflow.php new file mode 100644 index 0000000000..3afe232c42 --- /dev/null +++ b/src/applications/differential/management/PhabricatorDifferentialExtractWorkflow.php @@ -0,0 +1,63 @@ +setName('extract') + ->setExamples('**extract** __commit__') + ->setSynopsis(pht('Extract a diff from a commit.')) + ->setArguments( + array( + array( + 'name' => 'extract', + 'wildcard' => true, + 'help' => pht('Commit to extract.'), + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $viewer = $this->getViewer(); + + $extract = $args->getArg('extract'); + + if (!$extract) { + throw new PhutilArgumentUsageException( + pht('Specify a commit to extract the diff from.')); + } + + if (count($extract) > 1) { + throw new PhutilArgumentUsageException( + pht('Specify exactly one commit to extract.')); + } + + $extract = head($extract); + + $commit = id(new DiffusionCommitQuery()) + ->setViewer($viewer) + ->withIdentifiers(array($extract)) + ->executeOne(); + + if (!$commit) { + throw new PhutilArgumentUsageException( + pht( + 'Commit "%s" is not valid.', + $extract)); + } + + $diff = id(new DifferentialDiffExtractionEngine()) + ->setViewer($viewer) + ->newDiffFromCommit($commit); + + $uri = PhabricatorEnv::getProductionURI($diff->getURI()); + + echo tsprintf( + "%s\n\n %s\n", + pht('Extracted diff from "%s":', $extract), + $uri); + } + + +} diff --git a/src/applications/differential/management/PhabricatorDifferentialManagementWorkflow.php b/src/applications/differential/management/PhabricatorDifferentialManagementWorkflow.php new file mode 100644 index 0000000000..1a369e57ce --- /dev/null +++ b/src/applications/differential/management/PhabricatorDifferentialManagementWorkflow.php @@ -0,0 +1,4 @@ +getID(); + return "/differential/diff/{$id}/"; + } + + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index f849cdc347..83815cda82 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -314,8 +314,7 @@ final class DifferentialChangesetListView extends AphrontView { $changeset->getAbsoluteRepositoryPath($repository, $this->diff), '/'); $line = idx($changeset->getMetadata(), 'line:first', 1); - $callsign = $repository->getCallsign(); - $editor_link = $user->loadEditorLink($path, $line, $callsign); + $editor_link = $user->loadEditorLink($path, $line, $repository); if ($editor_link) { $meta['editor'] = $editor_link; } else { diff --git a/src/applications/diffusion/DiffusionLintSaveRunner.php b/src/applications/diffusion/DiffusionLintSaveRunner.php index b726089fe2..e32392be40 100644 --- a/src/applications/diffusion/DiffusionLintSaveRunner.php +++ b/src/applications/diffusion/DiffusionLintSaveRunner.php @@ -257,8 +257,10 @@ final class DiffusionLintSaveRunner extends Phobject { 'path' => $path, 'commit' => $this->lintCommit, )); - $query = DiffusionFileContentQuery::newFromDiffusionRequest($drequest) - ->setNeedsBlame(true); + + // TODO: Restore blame information / generally fix this workflow. + + $query = DiffusionFileContentQuery::newFromDiffusionRequest($drequest); $queries[$path] = $query; $futures[$path] = $query->getFileContentFuture(); } diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index cbbfff8763..0f809e9983 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -47,8 +47,13 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { public function getRoutes() { return array( - '/r(?P[A-Z]+)(?P[a-z0-9]+)' + '/(?:'. + 'r(?P[A-Z]+)'. + '|'. + 'R(?P[1-9]\d*):'. + ')(?P[a-f0-9]+)' => 'DiffusionCommitController', + '/diffusion/' => array( '(?:query/(?P[^/]+)/)?' => 'DiffusionRepositoryListController', @@ -59,13 +64,13 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { '(?:query/(?P[^/]+)/)?' => 'DiffusionPushLogListController', 'view/(?P\d+)/' => 'DiffusionPushEventViewController', ), - '(?P[A-Z]+)/' => array( + '(?P[A-Z]+)/' => array( '' => 'DiffusionRepositoryController', 'repository/(?P.*)' => 'DiffusionRepositoryController', 'change/(?P.*)' => 'DiffusionChangeController', 'history/(?P.*)' => 'DiffusionHistoryController', - 'browse/(?P.*)' => 'DiffusionBrowseMainController', + 'browse/(?P.*)' => 'DiffusionBrowseController', 'lastmodified/(?P.*)' => 'DiffusionLastModifiedController', 'diff/' => 'DiffusionDiffController', 'tags/(?P.*)' => 'DiffusionTagListController', @@ -110,7 +115,8 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { // catch-all for serving repositories over HTTP. We must accept // requests without the trailing "/" because SVN commands don't // necessarily include it. - '(?P[A-Z]+)(/|$).*' => 'DiffusionRepositoryDefaultController', + '(?P[A-Z]+)(?:/.*)?' => + 'DiffusionRepositoryDefaultController', 'inline/' => array( 'edit/(?P[^/]+)/' => 'DiffusionInlineCommentController', diff --git a/src/applications/diffusion/conduit/DiffusionBlameConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionBlameConduitAPIMethod.php new file mode 100644 index 0000000000..7cbb379039 --- /dev/null +++ b/src/applications/diffusion/conduit/DiffusionBlameConduitAPIMethod.php @@ -0,0 +1,44 @@ +'; + } + + protected function defineCustomParamTypes() { + return array( + 'paths' => 'required list', + 'commit' => 'required string', + 'timeout' => 'optional int', + ); + } + + protected function getResult(ConduitAPIRequest $request) { + $drequest = $this->getDiffusionRequest(); + + $paths = $request->getValue('paths'); + + $blame_query = DiffusionBlameQuery::newFromDiffusionRequest($drequest) + ->setPaths($paths); + + $timeout = $request->getValue('timeout'); + if ($timeout) { + $blame_query->setTimeout($timeout); + } + + $blame = $blame_query->execute(); + + return $blame; + } + +} diff --git a/src/applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php index e2d3a941bf..f32583708c 100644 --- a/src/applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php @@ -22,6 +22,8 @@ final class DiffusionBrowseQueryConduitAPIMethod 'path' => 'optional string', 'commit' => 'optional string', 'needValidityOnly' => 'optional bool', + 'limit' => 'optional int', + 'offset' => 'optional int', ); } @@ -35,6 +37,8 @@ final class DiffusionBrowseQueryConduitAPIMethod $repository = $drequest->getRepository(); $path = $request->getValue('path'); $commit = $request->getValue('commit'); + $offset = (int)$request->getValue('offset'); + $limit = (int)$request->getValue('limit'); $result = $this->getEmptyResultSet(); if ($path == '') { @@ -99,6 +103,7 @@ final class DiffusionBrowseQueryConduitAPIMethod $prefix = ''; } + $count = 0; $results = array(); foreach (explode("\0", rtrim($stdout)) as $line) { // NOTE: Limit to 5 components so we parse filenames with spaces in them @@ -140,7 +145,15 @@ final class DiffusionBrowseQueryConduitAPIMethod $path_result->setFileType($file_type); $path_result->setFileSize($size); - $results[] = $path_result; + if ($count >= $offset) { + $results[] = $path_result; + } + + $count++; + + if ($limit && $count >= ($offset + $limit)) { + break; + } } // If we identified submodules, lookup the module info at this commit to @@ -196,6 +209,8 @@ final class DiffusionBrowseQueryConduitAPIMethod $repository = $drequest->getRepository(); $path = $request->getValue('path'); $commit = $request->getValue('commit'); + $offset = (int)$request->getValue('offset'); + $limit = (int)$request->getValue('limit'); $result = $this->getEmptyResultSet(); @@ -215,6 +230,7 @@ final class DiffusionBrowseQueryConduitAPIMethod // but ours do. $trim_len = $match_len ? $match_len + 1 : 0; + $count = 0; foreach ($entire_manifest as $path) { if (strncmp($path, $match_against, $match_len)) { continue; @@ -236,7 +252,16 @@ final class DiffusionBrowseQueryConduitAPIMethod } else { $type = DifferentialChangeType::FILE_DIRECTORY; } - $results[reset($parts)] = $type; + + if ($count >= $offset) { + $results[reset($parts)] = $type; + } + + $count++; + + if ($limit && ($count >= ($offset + $limit))) { + break; + } } foreach ($results as $key => $type) { @@ -266,6 +291,8 @@ final class DiffusionBrowseQueryConduitAPIMethod $repository = $drequest->getRepository(); $path = $request->getValue('path'); $commit = $request->getValue('commit'); + $offset = (int)$request->getValue('offset'); + $limit = (int)$request->getValue('limit'); $result = $this->getEmptyResultSet(); $subpath = $repository->getDetail('svn-subpath'); @@ -422,6 +449,7 @@ final class DiffusionBrowseQueryConduitAPIMethod $path_normal = DiffusionPathIDQuery::normalizePath($path); $results = array(); + $count = 0; foreach ($browse as $file) { $full_path = $file['pathName']; @@ -431,9 +459,7 @@ final class DiffusionBrowseQueryConduitAPIMethod $result_path = new DiffusionRepositoryPath(); $result_path->setPath($file_path); $result_path->setFullPath($full_path); -// $result_path->setHash($hash); $result_path->setFileType($file['fileType']); -// $result_path->setFileSize($size); if (!empty($file['hasCommit'])) { $commit = idx($commits, $file['svnCommit']); @@ -444,7 +470,15 @@ final class DiffusionBrowseQueryConduitAPIMethod } } - $results[] = $result_path; + if ($count >= $offset) { + $results[] = $result_path; + } + + $count++; + + if ($limit && ($count >= ($offset + $limit))) { + break; + } } if (empty($results)) { diff --git a/src/applications/diffusion/conduit/DiffusionFileContentQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionFileContentQueryConduitAPIMethod.php index 61987b4d77..8088aa66b8 100644 --- a/src/applications/diffusion/conduit/DiffusionFileContentQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionFileContentQueryConduitAPIMethod.php @@ -19,7 +19,6 @@ final class DiffusionFileContentQueryConduitAPIMethod return array( 'path' => 'required string', 'commit' => 'required string', - 'needsBlame' => 'optional bool', 'timeout' => 'optional int', 'byteLimit' => 'optional int', ); @@ -27,12 +26,8 @@ final class DiffusionFileContentQueryConduitAPIMethod protected function getResult(ConduitAPIRequest $request) { $drequest = $this->getDiffusionRequest(); - $needs_blame = $request->getValue('needsBlame'); - $file_query = DiffusionFileContentQuery::newFromDiffusionRequest( - $drequest); - $file_query - ->setViewer($request->getUser()) - ->setNeedsBlame($needs_blame); + + $file_query = DiffusionFileContentQuery::newFromDiffusionRequest($drequest); $timeout = $request->getValue('timeout'); if ($timeout) { @@ -44,20 +39,44 @@ final class DiffusionFileContentQueryConduitAPIMethod $file_query->setByteLimit($byte_limit); } - $file_content = $file_query->loadFileContent(); + $content = $file_query->execute(); - if ($needs_blame) { - list($text_list, $rev_list, $blame_dict) = $file_query->getBlameData(); - } else { - $text_list = $rev_list = $blame_dict = array(); + $too_slow = (bool)$file_query->getExceededTimeLimit(); + $too_huge = (bool)$file_query->getExceededByteLimit(); + + $file_phid = null; + if (!$too_slow && !$too_huge) { + $file = $this->newFile($drequest, $content); + $file_phid = $file->getPHID(); } - $file_content - ->setBlameDict($blame_dict) - ->setRevList($rev_list) - ->setTextList($text_list); + return array( + 'tooSlow' => $too_slow, + 'tooHuge' => $too_huge, + 'filePHID' => $file_phid, + ); + } - return $file_content->toDictionary(); + private function newFile(DiffusionRequest $drequest, $content) { + $path = $drequest->getPath(); + $name = basename($path); + + $repository = $drequest->getRepository(); + $repository_phid = $repository->getPHID(); + + $file = PhabricatorFile::buildFromFileDataOrHash( + $content, + array( + 'name' => $name, + 'ttl' => time() + phutil_units('48 hours in seconds'), + 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, + )); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $file->attachToObject($repository_phid); + unset($unguarded); + + return $file; } } diff --git a/src/applications/diffusion/conduit/DiffusionGetCommitsConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionGetCommitsConduitAPIMethod.php deleted file mode 100644 index 15dd1f53f6..0000000000 --- a/src/applications/diffusion/conduit/DiffusionGetCommitsConduitAPIMethod.php +++ /dev/null @@ -1,293 +0,0 @@ - 'required list', - ); - } - - protected function defineReturnType() { - return 'nonempty list>'; - } - - protected function execute(ConduitAPIRequest $request) { - $results = array(); - - $commits = $request->getValue('commits'); - $commits = array_fill_keys($commits, array()); - foreach ($commits as $name => $info) { - $matches = null; - if (!preg_match('/^r([A-Z]+)([0-9a-f]+)\z/', $name, $matches)) { - $results[$name] = array( - 'error' => 'ERR-UNPARSEABLE', - ); - unset($commits[$name]); - continue; - } - $commits[$name] = array( - 'callsign' => $matches[1], - 'commitIdentifier' => $matches[2], - ); - } - - if (!$commits) { - return $results; - } - - $callsigns = ipull($commits, 'callsign'); - $callsigns = array_unique($callsigns); - $repos = id(new PhabricatorRepositoryQuery()) - ->setViewer($request->getUser()) - ->withCallsigns($callsigns) - ->execute(); - $repos = mpull($repos, null, 'getCallsign'); - - foreach ($commits as $name => $info) { - $repo = idx($repos, $info['callsign']); - if (!$repo) { - $results[$name] = $info + array( - 'error' => 'ERR-UNKNOWN-REPOSITORY', - ); - unset($commits[$name]); - continue; - } - $commits[$name] += array( - 'repositoryPHID' => $repo->getPHID(), - 'repositoryID' => $repo->getID(), - ); - } - - if (!$commits) { - return $results; - } - - // Execute a complicated query to figure out the primary commit information - // for each referenced commit. - $cdata = $this->queryCommitInformation($commits, $repos); - - // We've built the queries so that each row also has the identifier we used - // to select it, which might be a git prefix rather than a full identifier. - $ref_map = ipull($cdata, 'commitIdentifier', 'commitRef'); - - $cobjs = id(new PhabricatorRepositoryCommit())->loadAllFromArray($cdata); - $cobjs = mgroup($cobjs, 'getRepositoryID', 'getCommitIdentifier'); - foreach ($commits as $name => $commit) { - - // Expand short git names into full identifiers. For SVN this map is just - // the identity. - $full_identifier = idx($ref_map, $commit['commitIdentifier']); - - $repo_id = $commit['repositoryID']; - unset($commits[$name]['repositoryID']); - - if (empty($full_identifier) || - empty($cobjs[$commit['repositoryID']][$full_identifier])) { - $results[$name] = $commit + array( - 'error' => 'ERR-UNKNOWN-COMMIT', - ); - unset($commits[$name]); - continue; - } - - $cobj_arr = $cobjs[$commit['repositoryID']][$full_identifier]; - $cobj = head($cobj_arr); - - $commits[$name] += array( - 'epoch' => $cobj->getEpoch(), - 'commitPHID' => $cobj->getPHID(), - 'commitID' => $cobj->getID(), - ); - - // Upgrade git short references into full commit identifiers. - $identifier = $cobj->getCommitIdentifier(); - $commits[$name]['commitIdentifier'] = $identifier; - - $callsign = $commits[$name]['callsign']; - $uri = "/r{$callsign}{$identifier}"; - $commits[$name]['uri'] = PhabricatorEnv::getProductionURI($uri); - } - - if (!$commits) { - return $results; - } - - $commits = $this->addRepositoryCommitDataInformation($commits); - $commits = $this->addDifferentialInformation($commits, $request); - $commits = $this->addManiphestInformation($commits); - - foreach ($commits as $name => $commit) { - $results[$name] = $commit; - } - - return $results; - } - - /** - * Retrieve primary commit information for all referenced commits. - */ - private function queryCommitInformation(array $commits, array $repos) { - assert_instances_of($repos, 'PhabricatorRepository'); - $conn_r = id(new PhabricatorRepositoryCommit())->establishConnection('r'); - $repos = mpull($repos, null, 'getID'); - - $groups = array(); - foreach ($commits as $name => $commit) { - $groups[$commit['repositoryID']][] = $commit['commitIdentifier']; - } - - // NOTE: MySQL goes crazy and does a massive table scan if we build a more - // sensible version of this query. Make sure the query plan is OK if you - // attempt to reduce the craziness here. METANOTE: The addition of prefix - // selection for Git further complicates matters. - $query = array(); - $commit_table = id(new PhabricatorRepositoryCommit())->getTableName(); - - foreach ($groups as $repository_id => $identifiers) { - $vcs = $repos[$repository_id]->getVersionControlSystem(); - $is_git = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT); - if ($is_git) { - foreach ($identifiers as $identifier) { - if (strlen($identifier) < 7) { - // Don't bother with silly stuff like 'rX2', which will select - // 1/16th of all commits. Note that with length 7 we'll still get - // collisions in repositories at the tens-of-thousands-of-commits - // scale. - continue; - } - $query[] = qsprintf( - $conn_r, - 'SELECT %T.*, %s commitRef - FROM %T WHERE repositoryID = %d - AND commitIdentifier LIKE %>', - $commit_table, - $identifier, - $commit_table, - $repository_id, - $identifier); - } - } else { - $query[] = qsprintf( - $conn_r, - 'SELECT %T.*, commitIdentifier commitRef - FROM %T WHERE repositoryID = %d - AND commitIdentifier IN (%Ls)', - $commit_table, - $commit_table, - $repository_id, - $identifiers); - } - } - - return queryfx_all( - $conn_r, - '%Q', - implode(' UNION ALL ', $query)); - } - - /** - * Enhance the commit list with RepositoryCommitData information. - */ - private function addRepositoryCommitDataInformation(array $commits) { - $commit_ids = ipull($commits, 'commitID'); - - $data = id(new PhabricatorRepositoryCommitData())->loadAllWhere( - 'commitID in (%Ld)', - $commit_ids); - $data = mpull($data, null, 'getCommitID'); - - foreach ($commits as $name => $commit) { - if (isset($data[$commit['commitID']])) { - $dobj = $data[$commit['commitID']]; - $commits[$name] += array( - 'commitMessage' => $dobj->getCommitMessage(), - 'commitDetails' => $dobj->getCommitDetails(), - ); - } - - // Remove this information so we don't expose it via the API since - // external services shouldn't be storing internal Commit IDs. - unset($commits[$name]['commitID']); - } - - return $commits; - } - - /** - * Enhance the commit list with Differential information. - */ - private function addDifferentialInformation( - array $commits, - ConduitAPIRequest $request) { - - $commit_phids = ipull($commits, 'commitPHID'); - - $revisions = id(new DifferentialRevisionQuery()) - ->setViewer($request->getUser()) - ->withCommitPHIDs($commit_phids) - ->needCommitPHIDs(true) - ->execute(); - $rev_phid_commit_phids_map = mpull($revisions, 'getCommitPHIDs', 'getPHID'); - $revisions = mpull($revisions, null, 'getPHID'); - foreach ($rev_phid_commit_phids_map as $rev_phid => $commit_phids) { - foreach ($commits as $name => $commit) { - $commit_phid = $commit['commitPHID']; - if (in_array($commit_phid, $commit_phids)) { - $revision = $revisions[$rev_phid]; - $commits[$name] += array( - 'differentialRevisionID' => 'D'.$revision->getID(), - 'differentialRevisionPHID' => $revision->getPHID(), - ); - } - } - } - - return $commits; - } - - /** - * Enhances the commits list with Maniphest information. - */ - private function addManiphestInformation(array $commits) { - $task_type = DiffusionCommitHasTaskEdgeType::EDGECONST; - - $commit_phids = ipull($commits, 'commitPHID'); - - $edge_query = id(new PhabricatorEdgeQuery()) - ->withSourcePHIDs($commit_phids) - ->withEdgeTypes(array($task_type)); - - $edges = $edge_query->execute(); - - foreach ($commits as $name => $commit) { - $task_phids = $edge_query->getDestinationPHIDs( - array($commit['commitPHID']), - array($task_type)); - - $commits[$name] += array( - 'taskPHIDs' => $task_phids, - ); - } - - return $commits; - } - -} diff --git a/src/applications/diffusion/conduit/DiffusionLookSoonConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionLookSoonConduitAPIMethod.php index 49dd5dc117..88d216e4a1 100644 --- a/src/applications/diffusion/conduit/DiffusionLookSoonConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionLookSoonConduitAPIMethod.php @@ -24,7 +24,8 @@ final class DiffusionLookSoonConduitAPIMethod protected function defineParamTypes() { return array( - 'callsigns' => 'required list', + 'callsigns' => 'optional list (deprecated)', + 'repositories' => 'optional list', 'urgency' => 'optional string', ); } @@ -33,14 +34,19 @@ final class DiffusionLookSoonConduitAPIMethod // NOTE: The "urgency" parameter does nothing, it is just a hilarious joke // which exemplifies the boundless clever wit of this project. - $callsigns = $request->getValue('callsigns'); - if (!$callsigns) { + $identifiers = $request->getValue('repositories'); + + if (!$identifiers) { + $identifiers = $request->getValue('callsigns'); + } + + if (!$identifiers) { return null; } $repositories = id(new PhabricatorRepositoryQuery()) ->setViewer($request->getUser()) - ->withCallsigns($callsigns) + ->withIdentifiers($identifiers) ->execute(); foreach ($repositories as $repository) { diff --git a/src/applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php index c2e1a3de50..3bb69276c8 100644 --- a/src/applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php @@ -70,16 +70,14 @@ final class DiffusionQueryCommitsConduitAPIMethod foreach ($commits as $commit) { $commit_data = $commit->getCommitData(); - $callsign = $commit->getRepository()->getCallsign(); - $identifier = $commit->getCommitIdentifier(); - $uri = '/r'.$callsign.$identifier; + $uri = $commit->getURI(); $uri = PhabricatorEnv::getProductionURI($uri); $dict = array( 'id' => $commit->getID(), 'phid' => $commit->getPHID(), 'repositoryPHID' => $commit->getRepository()->getPHID(), - 'identifier' => $identifier, + 'identifier' => $commit->getCommitIdentifier(), 'epoch' => $commit->getEpoch(), 'uri' => $uri, 'isImporting' => !$commit->isImported(), diff --git a/src/applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php index e9f5681f9f..21e8da485f 100644 --- a/src/applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php @@ -38,7 +38,7 @@ abstract class DiffusionQueryConduitAPIMethod return $this->defineCustomErrorTypes() + array( 'ERR-UNKNOWN-REPOSITORY' => - pht('There is no repository with that callsign.'), + pht('There is no matching repository.'), 'ERR-UNKNOWN-VCS-TYPE' => pht('Unknown repository VCS type.'), 'ERR-UNSUPPORTED-VCS' => @@ -56,7 +56,8 @@ abstract class DiffusionQueryConduitAPIMethod final protected function defineParamTypes() { return $this->defineCustomParamTypes() + array( - 'callsign' => 'required string', + 'callsign' => 'optional string (deprecated)', + 'repository' => 'optional string', 'branch' => 'optional string', ); } @@ -95,15 +96,27 @@ abstract class DiffusionQueryConduitAPIMethod * should occur after @{method:getResult}, like formatting a timestamp. */ final protected function execute(ConduitAPIRequest $request) { + $identifier = $request->getValue('repository'); + if ($identifier === null) { + $identifier = $request->getValue('callsign'); + } + $drequest = DiffusionRequest::newFromDictionary( array( 'user' => $request->getUser(), - 'callsign' => $request->getValue('callsign'), + 'repository' => $identifier, 'branch' => $request->getValue('branch'), 'path' => $request->getValue('path'), 'commit' => $request->getValue('commit'), )); + if (!$drequest) { + throw new Exception( + pht( + 'Repository "%s" is not a valid repository.', + $identifier)); + } + // Figure out whether we're going to handle this request on this device, // or proxy it to another node in the cluster. diff --git a/src/applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php index 6061825444..5ca2783838 100644 --- a/src/applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php @@ -44,7 +44,6 @@ final class DiffusionQueryPathsConduitAPIMethod $commit, $path); - $lines = id(new LinesOfALargeExecFuture($future))->setDelimiter("\0"); return $this->filterResults($lines, $request); } @@ -86,16 +85,15 @@ final class DiffusionQueryPathsConduitAPIMethod $results = array(); $count = 0; foreach ($lines as $line) { - if (!$pattern || preg_match($pattern, $line)) { - if ($count >= $offset) { - $results[] = $line; - } + if (strlen($pattern) && !preg_match($pattern, $line)) { + continue; + } - $count++; + $results[] = $line; + $count++; - if ($limit && ($count >= ($offset + $limit))) { - break; - } + if ($limit && ($count >= ($offset + $limit))) { + break; } } diff --git a/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php index 16b9762e1d..e9b8f8d15e 100644 --- a/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php @@ -25,18 +25,19 @@ final class DiffusionSearchQueryConduitAPIMethod ); } - protected function defineCustomErrorTypes() { - return array( - 'ERR-GREP-COMMAND' => pht('Grep command failed.'), - ); - } - protected function getResult(ConduitAPIRequest $request) { try { $results = parent::getResult($request); } catch (CommandException $ex) { - throw id(new ConduitException('ERR-GREP-COMMAND')) - ->setErrorDescription($ex->getStderr()); + $err = $ex->getError(); + + if ($err === 1) { + // `git grep` and `hg grep` exit with 1 if there are no matches; + // assume we just didn't get any hits. + return array(); + } + + throw $ex; } $offset = $request->getValue('offset'); @@ -63,6 +64,7 @@ final class DiffusionSearchQueryConduitAPIMethod $binary_pattern = '/Binary file [^:]*:(.+) matches/'; $lines = new LinesOfALargeExecFuture($future); + foreach ($lines as $line) { $result = null; if (preg_match('/[^:]*:(.+)\0(.+)\0(.*)/', $line, $result)) { diff --git a/src/applications/diffusion/controller/DiffusionBranchTableController.php b/src/applications/diffusion/controller/DiffusionBranchTableController.php index e6c3b9592e..a040c8084e 100644 --- a/src/applications/diffusion/controller/DiffusionBranchTableController.php +++ b/src/applications/diffusion/controller/DiffusionBranchTableController.php @@ -6,15 +6,18 @@ final class DiffusionBranchTableController extends DiffusionController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { - $drequest = $this->getDiffusionRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $pager = new PHUIPagerView(); - $pager->setURI($request->getRequestURI(), 'offset'); - $pager->setOffset($request->getInt('offset')); + $pager = id(new PHUIPagerView()) + ->readFromRequest($request); // TODO: Add support for branches that contain commit $branches = $this->callConduitWithDiffusionRequest( @@ -57,18 +60,20 @@ final class DiffusionBranchTableController extends DiffusionController { 'branches' => true, )); - return $this->buildApplicationPage( - array( - $crumbs, - $content, - $pager, - ), - array( - 'title' => array( + $pager_box = $this->renderTablePagerBox($pager); + + return $this->newPage() + ->setTitle( + array( pht('Branches'), - 'r'.$repository->getCallsign(), - ), - )); + $repository->getDisplayName(), + )) + ->setCrumbs($crumbs) + ->appendChild( + array( + $content, + $pager_box, + )); } } diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index c924a9b0b3..eef95bb1ee 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -1,11 +1,1470 @@ 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(); + } + + $pager = id(new PHUIPagerView()) + ->readFromRequest($request); + + $results = DiffusionBrowseResultSet::newFromConduit( + $this->callConduitWithDiffusionRequest( + 'diffusion.browsequery', + array( + 'path' => $drequest->getPath(), + 'commit' => $drequest->getStableCommit(), + 'offset' => $pager->getOffset(), + 'limit' => $pager->getPageSize() + 1, + ))); + + $reason = $results->getReasonForEmptyResultSet(); + $is_file = ($reason == DiffusionBrowseResultSet::REASON_IS_FILE); + + if ($is_file) { + return $this->browseFile($results); + } else { + $paths = $results->getPaths(); + $paths = $pager->sliceResults($paths); + $results->setPaths($paths); + + return $this->browseDirectory($results, $pager); + } + } + + 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(), + ); + + $byte_limit = null; + if ($view !== 'raw') { + $byte_limit = PhabricatorFileStorageEngine::getChunkThreshold(); + $time_limit = 10; + + $params += array( + 'timeout' => $time_limit, + 'byteLimit' => $byte_limit, + ); + } + + $response = $this->callConduitWithDiffusionRequest( + 'diffusion.filecontentquery', + $params); + + $hit_byte_limit = $response['tooHuge']; + $hit_time_limit = $response['tooSlow']; + + $file_phid = $response['filePHID']; + if ($hit_byte_limit) { + $corpus = $this->buildErrorCorpus( + pht( + 'This file is larger than %s byte(s), and too large to display '. + 'in the web UI.', + phutil_format_bytes($byte_limit))); + } else if ($hit_time_limit) { + $corpus = $this->buildErrorCorpus( + pht( + 'This file took too long to load from the repository (more than '. + '%s second(s)).', + new PhutilNumber($time_limit))); + } else { + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phid)) + ->executeOne(); + if (!$file) { + throw new Exception(pht('Failed to load content file!')); + } + + if ($view === 'raw') { + return $file->getRedirectResponse(); + } + + $data = $file->loadFileData(); + if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) { + $file_uri = $file->getBestURI(); + + if ($file->isViewableImage()) { + $corpus = $this->buildImageCorpus($file_uri); + } else { + $corpus = $this->buildBinaryCorpus($file_uri, $data); + } + } else { + $this->loadLintMessages(); + $this->coverage = $drequest->loadCoverage(); + + // Build the content of the file. + $corpus = $this->buildCorpus( + $show_blame, + $show_color, + $data, + $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, + PHUIPagerView $pager) { + + $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(); + $content[] = $this->renderDirectoryReadme($results); + + $crumbs = $this->buildCrumbs( + array( + 'branch' => true, + 'path' => true, + 'view' => 'browse', + )); + + $pager_box = $this->renderTablePagerBox($pager); + + return $this->newPage() + ->setTitle( + array( + nonempty(basename($drequest->getPath()), '/'), + $repository->getDisplayName(), + )) + ->setCrumbs($crumbs) + ->appendChild( + array( + $content, + $pager_box, + )); + } + + private function renderSearchResults() { + $request = $this->getRequest(); + + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $results = array(); + + $pager = id(new PHUIPagerView()) + ->readFromRequest($request); + + $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 = $request->getStr('grep'); + $results = $this->callConduitWithDiffusionRequest( + 'diffusion.searchquery', + array( + 'grep' => $query_string, + 'commit' => $drequest->getStableCommit(), + 'path' => $drequest->getPath(), + 'limit' => $pager->getPageSize() + 1, + 'offset' => $pager->getOffset(), + )); + } else { // Filename search. + $search_mode = 'find'; + $query_string = $request->getStr('find'); + $results = $this->callConduitWithDiffusionRequest( + 'diffusion.querypaths', + array( + 'pattern' => $query_string, + 'commit' => $drequest->getStableCommit(), + 'path' => $drequest->getPath(), + 'limit' => $pager->getPageSize() + 1, + 'offset' => $pager->getOffset(), + )); + } + 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, + $file_corpus, + $needs_blame, + DiffusionRequest $drequest, + $path, + $data) { + + $viewer = $this->getViewer(); + $blame_timeout = 15; + $blame_failed = false; + + $highlight_limit = DifferentialChangesetParser::HIGHLIGHT_BYTE_LIMIT; + $blame_limit = DifferentialChangesetParser::HIGHLIGHT_BYTE_LIMIT; + $can_highlight = (strlen($file_corpus) <= $highlight_limit); + $can_blame = (strlen($file_corpus) <= $blame_limit); + + if ($needs_blame && $can_blame) { + $blame = $this->loadBlame($path, $drequest->getCommit(), $blame_timeout); + list($blame_list, $blame_commits) = $blame; + if ($blame_list === null) { + $blame_failed = true; + $blame_list = array(); + } + } else { + $blame_list = array(); + $blame_commits = array(); + } + + if (!$show_color) { + $corpus = $this->renderPlaintextCorpus( + $file_corpus, + $blame_list, + $blame_commits, + $show_blame); + } else { + if ($can_highlight) { + require_celerity_resource('syntax-highlighting-css'); + + $highlighted = PhabricatorSyntaxHighlighter::highlightWithFilename( + $path, + $file_corpus); + $lines = phutil_split_lines($highlighted); + } else { + $lines = phutil_split_lines($file_corpus); + } + + $rows = $this->buildDisplayRows( + $lines, + $blame_list, + $blame_commits, + $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); + + $messages = array(); + + if (!$can_highlight) { + $messages[] = pht( + 'This file is larger than %s, so syntax highlighting is disabled '. + 'by default.', + phutil_format_bytes($highlight_limit)); + } + + if ($show_blame && !$can_blame) { + $messages[] = pht( + 'This file is larger than %s, so blame is disabled.', + phutil_format_bytes($blame_limit)); + } + + if ($blame_failed) { + $messages[] = pht( + 'Failed to load blame information for this file in %s second(s).', + new PhutilNumber($blame_timeout)); + } + + if ($messages) { + $corpus->setInfoView( + id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setErrors($messages)); + } + + 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 $lines, + array $blame_list, + array $blame_commits, + $show_color, + $show_blame) { + + $request = $this->getRequest(); + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $revision_map = array(); + $revisions = array(); + if ($blame_commits) { + $commit_map = mpull($blame_commits, 'getCommitIdentifier', 'getPHID'); + + $revision_ids = id(new DifferentialRevision()) + ->loadIDsByCommitPHIDs(array_keys($commit_map)); + if ($revision_ids) { + $revisions = id(new DifferentialRevisionQuery()) + ->setViewer($viewer) + ->withIDs($revision_ids) + ->execute(); + $revisions = mpull($revisions, null, 'getID'); + } + + foreach ($revision_ids as $commit_phid => $revision_id) { + $revision_map[$commit_map[$commit_phid]] = $revision_id; + } + } + + $phids = array(); + foreach ($blame_commits as $commit) { + $author_phid = $commit->getAuthorPHID(); + if ($author_phid === null) { + continue; + } + $phids[$author_phid] = $author_phid; + } + + foreach ($revisions as $revision) { + $author_phid = $revision->getAuthorPHID(); + if ($author_phid === null) { + continue; + } + $phids[$author_phid] = $author_phid; + } + + $handles = $viewer->loadHandles($phids); + + $colors = array(); + if ($blame_commits) { + $epochs = array(); + + foreach ($blame_commits as $identifier => $commit) { + $epochs[$identifier] = $commit->getEpoch(); + } + + $epoch_list = array_filter($epochs); + $epoch_list = array_unique($epoch_list); + $epoch_list = array_values($epoch_list); + + $epoch_min = min($epoch_list); + $epoch_max = max($epoch_list); + $epoch_range = ($epoch_max - $epoch_min) + 1; + + foreach ($blame_commits as $identifier => $commit) { + $epoch = $epochs[$identifier]; + if (!$epoch) { + $color = '#ffffdd'; // Warning color, missing data. + } else { + $color_ratio = ($epoch - $epoch_min) / $epoch_range; + $color_value = 0xE6 * (1.0 - $color_ratio); + $color = sprintf( + '#%02x%02x%02x', + $color_value, + 0xF6, + $color_value); + } + + $colors[$identifier] = $color; + } + } + + $display = array(); + $last_identifier = null; + $last_color = null; + foreach ($lines as $line_index => $line) { + $color = '#f6f6f6'; + $duplicate = false; + if (isset($blame_list[$line_index])) { + $identifier = $blame_list[$line_index]; + if (isset($colors[$identifier])) { + $color = $colors[$identifier]; + } + + if ($identifier === $last_identifier) { + $duplicate = true; + } else { + $last_identifier = $identifier; + } + } + + $display[$line_index] = array( + 'data' => $line, + 'target' => false, + 'highlighted' => false, + 'color' => $color, + 'duplicate' => $duplicate, + ); + } + + $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, + ); + } + } + + // Mark the first highlighted line as the target line. + if ($line_arr) { + $target_line = $line_arr[0]['min']; + if (isset($display[$target_line - 1])) { + $display[$target_line - 1]['target'] = true; + } + } + + // Mark all other highlighted lines as highlighted. + foreach ($line_arr as $range) { + for ($ii = $range['min']; $ii <= $range['max']; $ii++) { + if (isset($display[$ii - 1])) { + $display[$ii - 1]['highlighted'] = true; + } + } + } + + $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); + + // NOTE: We're doing this manually because rendering is otherwise + // dominated by URI generation for very large files. + $line_base = (string)$drequest->generateURI( + array( + 'action' => 'browse', + 'stable' => true, + )); + + require_celerity_resource('aphront-tooltip-css'); + Javelin::initBehavior('phabricator-oncopy'); + Javelin::initBehavior('phabricator-tooltips'); + Javelin::initBehavior('phabricator-line-linker'); + + // Render these once, since they tend to get repeated many times in large + // blame outputs. + $commit_links = $this->renderCommitLinks($blame_commits, $handles); + $revision_links = $this->renderRevisionLinks($revisions, $handles); + + $skip_text = pht('Skip Past This Commit'); + foreach ($display as $line_index => $line) { + $row = array(); + + $line_number = $line_index + 1; + $line_href = $line_base.'$'.$line_number; + + if (isset($blame_list[$line_index])) { + $identifier = $blame_list[$line_index]; + } else { + $identifier = null; + } + + $revision_link = null; + $commit_link = null; + $before_link = null; + $style = null; + if ($identifier && !$line['duplicate']) { + $style = 'background: '.$line['color'].';'; + + if (isset($commit_links[$identifier])) { + $commit_link = $commit_links[$identifier]; + } + + if (isset($revision_map[$identifier])) { + $revision_id = $revision_map[$identifier]; + if (isset($revision_links[$revision_id])) { + $revision_link = $revision_links[$revision_id]; + } + } + + $skip_href = $line_href.'?before='.$identifier.'&view=blame'; + $before_link = javelin_tag( + 'a', + array( + 'href' => $skip_href, + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $skip_text, + 'align' => 'E', + 'size' => 300, + ), + ), + "\xC2\xAB"); + } + + $row[] = 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; + } + + $row[] = phutil_tag( + 'th', + array( + 'class' => 'diffusion-rev-link', + ), + $object_links); + + $line_link = phutil_tag( + 'a', + array( + 'href' => $line_href, + 'style' => $style, + ), + $line_number); + + $row[] = javelin_tag( + 'th', + array( + 'class' => 'diffusion-line-link', + 'sigil' => 'phabricator-source-line', + 'style' => $style, + ), + $line_link); + + 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; + } + + $row[] = 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'; + } + + $row[] = phutil_tag( + 'td', + array( + 'class' => 'cov cov-'.$cov_class, + ), + ''); + } + + $rows[] = phutil_tag( + 'tr', + array( + 'class' => ($line['highlighted'] ? + 'phabricator-source-highlight' : + null), + ), + $row); + + $cur_inlines = $this->renderInlines( + idx($inlines, $line_number, array()), + $show_blame, + $this->coverage, + $engine); + foreach ($cur_inlines as $cur_inline) { + $rows[] = $cur_inline; + } + } + + return $rows; + } + + private function renderInlines( + array $inlines, + $show_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, ($show_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 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, + $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, + $author) { + + $viewer = $this->getRequest()->getUser(); + + $date = phabricator_date($commit->getEpoch(), $viewer); + $summary = trim($commit->getSummary()); + + return "{$summary}\n{$date} \xC2\xB7 {$author}"; + } + protected function renderSearchForm($collapsed) { $drequest = $this->getDiffusionRequest(); @@ -125,7 +1584,6 @@ abstract class DiffusionBrowseController extends DiffusionController { ->setActionList($actions); $stable_commit = $drequest->getStableCommit(); - $callsign = $drequest->getRepository()->getCallsign(); $view->addProperty( pht('Commit'), @@ -205,8 +1663,8 @@ abstract class DiffusionBrowseController extends DiffusionController { return $view; } - protected function buildOpenRevisions() { - $user = $this->getRequest()->getUser(); + private function buildOpenRevisions() { + $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); @@ -221,7 +1679,7 @@ abstract class DiffusionBrowseController extends DiffusionController { $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) @@ -244,7 +1702,7 @@ abstract class DiffusionBrowseController extends DiffusionController { $view = id(new DifferentialRevisionListView()) ->setHeader($header) ->setRevisions($revisions) - ->setUser($user); + ->setUser($viewer); $phids = $view->getRequiredHandlePHIDs(); $handles = $this->loadViewerHandles($phids); @@ -253,4 +1711,162 @@ abstract class DiffusionBrowseController extends DiffusionController { return $view; } + private function loadBlame($path, $commit, $timeout) { + $blame = $this->callConduitWithDiffusionRequest( + 'diffusion.blame', + array( + 'commit' => $commit, + 'paths' => array($path), + 'timeout' => $timeout, + )); + + $identifiers = idx($blame, $path, null); + + if ($identifiers) { + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $commits = id(new DiffusionCommitQuery()) + ->setViewer($viewer) + ->withRepository($repository) + ->withIdentifiers($identifiers) + // TODO: We only fetch this to improve author display behavior, but + // shouldn't really need to? + ->needCommitData(true) + ->execute(); + $commits = mpull($commits, null, 'getCommitIdentifier'); + } else { + $commits = array(); + } + + return array($identifiers, $commits); + } + + private function renderCommitLinks(array $commits, $handles) { + $links = array(); + foreach ($commits as $identifier => $commit) { + $tooltip = $this->renderCommitTooltip( + $commit, + $commit->renderAuthorShortName($handles)); + + $commit_link = javelin_tag( + 'a', + array( + 'href' => $commit->getURI(), + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $tooltip, + 'align' => 'E', + 'size' => 600, + ), + ), + $commit->getShortName()); + + $links[$identifier] = $commit_link; + } + + return $links; + } + + private function renderRevisionLinks(array $revisions, $handles) { + $links = array(); + + foreach ($revisions as $revision) { + $revision_id = $revision->getID(); + + $tooltip = $this->renderRevisionTooltip($revision, $handles); + + $revision_link = javelin_tag( + 'a', + array( + 'href' => '/'.$revision->getMonogram(), + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $tooltip, + 'align' => 'E', + 'size' => 600, + ), + ), + $revision->getMonogram()); + + $links[$revision_id] = $revision_link; + } + + return $links; + } + + private function renderPlaintextCorpus( + $file_corpus, + array $blame_list, + array $blame_commits, + $show_blame) { + + $viewer = $this->getViewer(); + + if (!$show_blame) { + $corpus = $file_corpus; + } else { + $author_phids = array(); + foreach ($blame_commits as $commit) { + $author_phid = $commit->getAuthorPHID(); + if ($author_phid === null) { + continue; + } + $author_phids[$author_phid] = $author_phid; + } + + if ($author_phids) { + $handles = $viewer->loadHandles($author_phids); + } else { + $handles = array(); + } + + $authors = array(); + $names = array(); + foreach ($blame_commits as $identifier => $commit) { + $author = $commit->renderAuthorShortName($handles); + $name = $commit->getShortName(); + + $authors[$identifier] = $author; + $names[$identifier] = $name; + } + + $lines = phutil_split_lines($file_corpus); + + $rows = array(); + foreach ($lines as $line_number => $line) { + $commit_name = null; + $author = null; + + if (isset($blame_list[$line_number])) { + $identifier = $blame_list[$line_number]; + + if (isset($names[$identifier])) { + $commit_name = $names[$identifier]; + } + + if (isset($authors[$identifier])) { + $author = $authors[$identifier]; + } + } + + $rows[] = sprintf( + '%-10s %-20s %s', + $commit_name, + $author, + $line); + } + $corpus = implode('', $rows); + } + + return phutil_tag( + 'textarea', + array( + 'style' => 'border: none; width: 100%; height: 80em; '. + 'font-family: monospace', + ), + $corpus); + } + } diff --git a/src/applications/diffusion/controller/DiffusionBrowseDirectoryController.php b/src/applications/diffusion/controller/DiffusionBrowseDirectoryController.php deleted file mode 100644 index f455b1b00d..0000000000 --- a/src/applications/diffusion/controller/DiffusionBrowseDirectoryController.php +++ /dev/null @@ -1,108 +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()), '/'), - pht( - '%s Repository', - $drequest->getRepository()->getCallsign()), - ), - )); - } - -} diff --git a/src/applications/diffusion/controller/DiffusionBrowseFileController.php b/src/applications/diffusion/controller/DiffusionBrowseFileController.php deleted file mode 100644 index eb935208e5..0000000000 --- a/src/applications/diffusion/controller/DiffusionBrowseFileController.php +++ /dev/null @@ -1,1135 +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); - - $callsign = $repository->getCallsign(); - $editor_link = $user->loadEditorLink($path, $line, $callsign); - $template = $user->loadEditorLink($path, '%l', $callsign); - - $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 index 8c0318a870..0000000000 --- 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 index a2b1f8aa83..0000000000 --- a/src/applications/diffusion/controller/DiffusionBrowseSearchController.php +++ /dev/null @@ -1,233 +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()), '/'), - pht( - '%s Repository', - $drequest->getRepository()->getCallsign()), - ), - )); - } - - 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; - } - -} diff --git a/src/applications/diffusion/controller/DiffusionChangeController.php b/src/applications/diffusion/controller/DiffusionChangeController.php index d91c95a9a2..86b371c38b 100644 --- a/src/applications/diffusion/controller/DiffusionChangeController.php +++ b/src/applications/diffusion/controller/DiffusionChangeController.php @@ -6,9 +6,14 @@ final class DiffusionChangeController extends DiffusionController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { - $drequest = $this->diffusionRequest; - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); $content = array(); @@ -33,7 +38,6 @@ final class DiffusionChangeController extends DiffusionController { } $repository = $drequest->getRepository(); - $callsign = $repository->getCallsign(); $changesets = array( 0 => $changeset, ); @@ -59,7 +63,8 @@ final class DiffusionChangeController extends DiffusionController { $left_uri = $drequest->generateURI($raw_params); $changeset_view->setRawFileURIs($left_uri, $right_uri); - $changeset_view->setRenderURI('/diffusion/'.$callsign.'/diff/'); + $changeset_view->setRenderURI($repository->getPathURI('diff/')); + $changeset_view->setWhitespace( DifferentialChangesetParser::WHITESPACE_SHOW_ALL); $changeset_view->setUser($viewer); @@ -89,15 +94,18 @@ final class DiffusionChangeController extends DiffusionController { ->setHeader($header) ->addPropertyList($properties); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - $content, - ), - array( - 'title' => pht('Change'), - )); + return $this->newPage() + ->setTitle( + array( + basename($drequest->getPath()), + $repository->getDisplayName(), + )) + ->setCrumbs($crumbs) + ->appendChild( + array( + $object_box, + $content, + )); } private function buildActionView(DiffusionRequest $drequest) { @@ -142,7 +150,6 @@ final class DiffusionChangeController extends DiffusionController { ->setActionList($actions); $stable_commit = $drequest->getStableCommit(); - $callsign = $drequest->getRepository()->getCallsign(); $view->addProperty( pht('Commit'), diff --git a/src/applications/diffusion/controller/DiffusionCommitBranchesController.php b/src/applications/diffusion/controller/DiffusionCommitBranchesController.php index 4f12e17656..1e5cc0ac31 100644 --- a/src/applications/diffusion/controller/DiffusionCommitBranchesController.php +++ b/src/applications/diffusion/controller/DiffusionCommitBranchesController.php @@ -6,7 +6,12 @@ final class DiffusionCommitBranchesController extends DiffusionController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 6f1dbf26ac..2b0e589687 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -17,26 +17,21 @@ final class DiffusionCommitController extends DiffusionController { return true; } - protected function shouldLoadDiffusionRequest() { - return false; - } + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + + $drequest = $this->getDiffusionRequest(); - protected function processDiffusionRequest(AphrontRequest $request) { $user = $request->getUser(); - // This controller doesn't use blob/path stuff, just pass the dictionary - // in directly instead of using the AphrontRequest parsing mechanism. - $data = $request->getURIMap(); - $data['user'] = $user; - $drequest = DiffusionRequest::newFromDictionary($data); - $this->diffusionRequest = $drequest; - if ($request->getStr('diff')) { return $this->buildRawDiffResponse($drequest); } $repository = $drequest->getRepository(); - $callsign = $repository->getCallsign(); $content = array(); $commit = id(new DiffusionCommitQuery()) @@ -181,7 +176,7 @@ final class DiffusionCommitController extends DiffusionController { id(new PhabricatorRepository())->establishConnection('r'), 'SELECT * FROM %T WHERE fullCommitName = %s', PhabricatorRepository::TABLE_BADCOMMIT, - 'r'.$callsign.$commit->getCommitIdentifier()); + $commit->getMonogram()); } $show_changesets = false; @@ -314,27 +309,28 @@ final class DiffusionCommitController extends DiffusionController { } } - $change_list_title = DiffusionView::nameCommit( - $repository, - $commit->getCommitIdentifier()); + $change_list_title = $commit->getDisplayName(); + $change_list = new DifferentialChangesetListView(); $change_list->setTitle($change_list_title); $change_list->setChangesets($changesets); $change_list->setVisibleChangesets($visible_changesets); $change_list->setRenderingReferences($references); - $change_list->setRenderURI('/diffusion/'.$callsign.'/diff/'); + $change_list->setRenderURI( + $repository->getPathURI('diff/')); $change_list->setRepository($repository); $change_list->setUser($user); // TODO: Try to setBranch() to something reasonable here? $change_list->setStandaloneURI( - '/diffusion/'.$callsign.'/diff/'); + $repository->getPathURI('diff/')); + $change_list->setRawFileURIs( // TODO: Implement this, somewhat tricky if there's an octopus merge // or whatever? null, - '/diffusion/'.$callsign.'/diff/?view=r'); + $repository->getPathURI('diff/?view=r')); $change_list->setInlineCommentControllerURI( '/diffusion/inline/edit/'.phutil_escape_uri($commit->getPHID()).'/'); @@ -344,11 +340,6 @@ final class DiffusionCommitController extends DiffusionController { $content[] = $this->renderAddCommentPanel($commit, $audit_requests); - $commit_id = 'r'.$callsign.$commit->getCommitIdentifier(); - $short_name = DiffusionView::nameCommit( - $repository, - $commit->getCommitIdentifier()); - $prefs = $user->loadPreferences(); $pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE; $pref_collapse = PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED; @@ -357,8 +348,8 @@ final class DiffusionCommitController extends DiffusionController { if ($show_changesets && $show_filetree) { $nav = id(new DifferentialChangesetFileTreeSideNavBuilder()) - ->setTitle($short_name) - ->setBaseURI(new PhutilURI('/'.$commit_id)) + ->setTitle($commit->getDisplayName()) + ->setBaseURI(new PhutilURI($commit->getURI())) ->build($changesets) ->setCrumbs($crumbs) ->setCollapsed((bool)$collapsed) @@ -371,7 +362,7 @@ final class DiffusionCommitController extends DiffusionController { return $this->buildApplicationPage( $content, array( - 'title' => $commit_id, + 'title' => $commit->getDisplayName(), 'pageObjects' => array($commit->getPHID()), )); } @@ -573,8 +564,8 @@ final class DiffusionCommitController extends DiffusionController { ), pht('Unknown')); - $callsign = $repository->getCallsign(); - $root = '/diffusion/'.$callsign.'/commit/'.$commit->getCommitIdentifier(); + $identifier = $commit->getCommitIdentifier(); + $root = $repository->getPathURI("commit/{$identifier}"); Javelin::initBehavior( 'diffusion-commit-branches', array( @@ -910,8 +901,8 @@ final class DiffusionCommitController extends DiffusionController { $commit, PhabricatorPolicyCapability::CAN_EDIT); - $uri = '/diffusion/'.$repository->getCallsign().'/commit/'. - $commit->getCommitIdentifier().'/edit/'; + $identifier = $commit->getCommitIdentifier(); + $uri = $repository->getPathURI("commit/{$identifier}/edit/"); $action = id(new PhabricatorActionView()) ->setName(pht('Edit Commit')) diff --git a/src/applications/diffusion/controller/DiffusionCommitEditController.php b/src/applications/diffusion/controller/DiffusionCommitEditController.php index c9526883e0..895eda4c6d 100644 --- a/src/applications/diffusion/controller/DiffusionCommitEditController.php +++ b/src/applications/diffusion/controller/DiffusionCommitEditController.php @@ -2,19 +2,24 @@ final class DiffusionCommitEditController extends DiffusionController { - protected function processDiffusionRequest(AphrontRequest $request) { - $user = $request->getUser(); - $drequest = $this->getDiffusionRequest(); - $callsign = $drequest->getRepository()->getCallsign(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $commit = $drequest->loadCommit(); - $data = $commit->loadCommitData(); - $page_title = pht('Edit Diffusion Commit'); + $commit = $drequest->loadCommit(); if (!$commit) { return new Aphront404Response(); } + $data = $commit->loadCommitData(); + $page_title = pht('Edit Diffusion Commit'); + $commit_phid = $commit->getPHID(); $edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; $current_proj_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( @@ -28,18 +33,21 @@ final class DiffusionCommitEditController extends DiffusionController { ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $edge_type) ->setNewValue(array('=' => array_fuse($proj_phids))); + $editor = id(new PhabricatorAuditEditor()) - ->setActor($user) + ->setActor($viewer) ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request); - $xactions = $editor->applyTransactions($commit, $xactions); + + $editor->applyTransactions($commit, $xactions); + return id(new AphrontRedirectResponse()) - ->setURI('/r'.$callsign.$commit->getCommitIdentifier()); + ->setURI($commit->getURI()); } $tokenizer_id = celerity_generate_unique_node_id(); $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->setAction($request->getRequestURI()->getPath()) ->appendControl( id(new AphrontFormTokenizerControl()) @@ -47,15 +55,6 @@ final class DiffusionCommitEditController extends DiffusionController { ->setName('projects') ->setValue($current_proj_phids) ->setID($tokenizer_id) - ->setCaption( - javelin_tag( - 'a', - array( - 'href' => '/project/create/', - 'mustcapture' => true, - 'sigil' => 'project-create', - ), - pht('Create New Project'))) ->setDatasource(new PhabricatorProjectDatasource())); $reason = $data->getCommitDetail('autocloseReason', false); @@ -97,33 +96,25 @@ final class DiffusionCommitEditController extends DiffusionController { ->setValue(array($desc, " \xC2\xB7 ", $doc_link))); } + $form->appendControl( + id(new AphrontFormSubmitControl()) + ->setValue(pht('Save')) + ->addCancelButton($commit->getURI())); - Javelin::initBehavior('project-create', array( - 'tokenizerID' => $tokenizer_id, - )); - - $submit = id(new AphrontFormSubmitControl()) - ->setValue(pht('Save')) - ->addCancelButton('/r'.$callsign.$commit->getCommitIdentifier()); - $form->appendChild($submit); - - $crumbs = $this->buildCrumbs(array( - 'commit' => true, - )); + $crumbs = $this->buildCrumbs( + array( + 'commit' => true, + )); $crumbs->addTextCrumb(pht('Edit')); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($page_title) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => $page_title, - )); + return $this->newPage() + ->setTitle($page_title) + ->setCrumbs($crumbs) + ->appendChild($form_box); } } diff --git a/src/applications/diffusion/controller/DiffusionCommitTagsController.php b/src/applications/diffusion/controller/DiffusionCommitTagsController.php index 3c698f9a70..0f825a9b64 100644 --- a/src/applications/diffusion/controller/DiffusionCommitTagsController.php +++ b/src/applications/diffusion/controller/DiffusionCommitTagsController.php @@ -6,7 +6,12 @@ final class DiffusionCommitTagsController extends DiffusionController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index 262223c6f9..a3c661a26d 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -2,56 +2,92 @@ abstract class DiffusionController extends PhabricatorController { - protected $diffusionRequest; - - public function setDiffusionRequest(DiffusionRequest $request) { - $this->diffusionRequest = $request; - return $this; - } + private $diffusionRequest; protected function getDiffusionRequest() { if (!$this->diffusionRequest) { - throw new Exception(pht('No Diffusion request object!')); + throw new PhutilInvalidStateException('loadDiffusionContext'); } return $this->diffusionRequest; } + protected function hasDiffusionRequest() { + return (bool)$this->diffusionRequest; + } + public function willBeginExecution() { $request = $this->getRequest(); // Check if this is a VCS request, e.g. from "git clone", "hg clone", or // "svn checkout". If it is, we jump off into repository serving code to // process the request. - if (DiffusionServeController::isVCSRequest($request)) { - $serve_controller = id(new DiffusionServeController()) - ->setCurrentApplication($this->getCurrentApplication()); + + $serve_controller = new DiffusionServeController(); + if ($serve_controller->isVCSRequest($request)) { return $this->delegateToController($serve_controller); } return parent::willBeginExecution(); } - protected function shouldLoadDiffusionRequest() { - return true; + protected function loadDiffusionContextForEdit() { + return $this->loadContext( + array( + 'edit' => true, + )); } - final public function handleRequest(AphrontRequest $request) { - if ($request->getURIData('callsign') && - $this->shouldLoadDiffusionRequest()) { - try { - $drequest = DiffusionRequest::newFromAphrontRequestDictionary( - $request->getURIMap(), - $request); - } catch (Exception $ex) { - return id(new Aphront404Response()) - ->setRequest($request); - } - $this->setDiffusionRequest($drequest); + protected function loadDiffusionContext() { + return $this->loadContext(array()); + } + + private function loadContext(array $options) { + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $identifier = $this->getRepositoryIdentifierFromRequest($request); + + $params = $options + array( + 'repository' => $identifier, + 'user' => $viewer, + 'blob' => $this->getDiffusionBlobFromRequest($request), + 'commit' => $request->getURIData('commit'), + 'path' => $request->getURIData('path'), + 'line' => $request->getURIData('line'), + 'branch' => $request->getURIData('branch'), + 'lint' => $request->getStr('lint'), + ); + + $drequest = DiffusionRequest::newFromDictionary($params); + + if (!$drequest) { + return new Aphront404Response(); } - return $this->processDiffusionRequest($request); + + $this->diffusionRequest = $drequest; + + return null; } - abstract protected function processDiffusionRequest(AphrontRequest $request); + protected function getDiffusionBlobFromRequest(AphrontRequest $request) { + return $request->getURIData('dblob'); + } + + protected function getRepositoryIdentifierFromRequest( + AphrontRequest $request) { + + $identifier = $request->getURIData('repositoryCallsign'); + if (strlen($identifier)) { + return $identifier; + } + + $id = $request->getURIData('repositoryID'); + if (strlen($id)) { + return (int)$id; + } + + return null; + } public function buildCrumbs(array $spec = array()) { $crumbs = $this->buildApplicationCrumbs(); @@ -74,7 +110,7 @@ abstract class DiffusionController extends PhabricatorController { $crumb_list = array(); // On the home page, we don't have a DiffusionRequest. - if ($this->diffusionRequest) { + if ($this->hasDiffusionRequest()) { $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); } else { @@ -86,7 +122,6 @@ abstract class DiffusionController extends PhabricatorController { return $crumb_list; } - $callsign = $repository->getCallsign(); $repository_name = $repository->getName(); if (!$spec['commit'] && !$spec['tags'] && !$spec['branches']) { @@ -112,17 +147,14 @@ abstract class DiffusionController extends PhabricatorController { $crumb_list[] = $crumb; $stable_commit = $drequest->getStableCommit(); + $commit_name = $repository->formatCommitName($stable_commit); + $commit_uri = $repository->getCommitURI($stable_commit); if ($spec['tags']) { $crumb = new PHUICrumbView(); if ($spec['commit']) { - $crumb->setName( - pht('Tags for %s', 'r'.$callsign.$stable_commit)); - $crumb->setHref($drequest->generateURI( - array( - 'action' => 'commit', - 'commit' => $drequest->getStableCommit(), - ))); + $crumb->setName(pht('Tags for %s', $commit_name)); + $crumb->setHref($commit_uri); } else { $crumb->setName(pht('Tags')); } @@ -139,8 +171,8 @@ abstract class DiffusionController extends PhabricatorController { if ($spec['commit']) { $crumb = id(new PHUICrumbView()) - ->setName("r{$callsign}{$stable_commit}") - ->setHref("r{$callsign}{$stable_commit}"); + ->setName($commit_name) + ->setHref($commit_uri); $crumb_list[] = $crumb; return $crumb_list; } @@ -187,7 +219,7 @@ abstract class DiffusionController extends PhabricatorController { protected function getRepositoryControllerURI( PhabricatorRepository $repository, $path) { - return $this->getApplicationURI($repository->getCallsign().'/'.$path); + return $repository->getPathURI($path); } protected function renderPathLinks(DiffusionRequest $drequest, $action) { @@ -212,7 +244,7 @@ abstract class DiffusionController extends PhabricatorController { 'path' => '', )), ), - 'r'.$drequest->getRepository()->getCallsign()); + $drequest->getRepository()->getDisplayName()); $links[] = $divider; $accum = ''; $last_key = last_key($path_parts); @@ -235,7 +267,7 @@ abstract class DiffusionController extends PhabricatorController { } } } else { - $links[] = 'r'.$drequest->getRepository()->getCallsign(); + $links[] = $drequest->getRepository()->getDisplayName(); $links[] = $divider; } @@ -249,4 +281,55 @@ abstract class DiffusionController extends PhabricatorController { ->appendChild($body); } + protected function renderTablePagerBox(PHUIPagerView $pager) { + return id(new PHUIBoxView()) + ->addMargin(PHUI::MARGIN_LARGE) + ->appendChild($pager); + } + + protected function renderDirectoryReadme(DiffusionBrowseResultSet $browse) { + $readme_path = $browse->getReadmePath(); + if ($readme_path === null) { + return null; + } + + $drequest = $this->getDiffusionRequest(); + $viewer = $this->getViewer(); + + try { + $result = $this->callConduitWithDiffusionRequest( + 'diffusion.filecontentquery', + array( + 'path' => $readme_path, + 'commit' => $drequest->getStableCommit(), + )); + } catch (Exception $ex) { + return null; + } + + $file_phid = $result['filePHID']; + if (!$file_phid) { + return null; + } + + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phid)) + ->executeOne(); + if (!$file) { + return null; + } + + $corpus = $file->loadFileData(); + + if (!strlen($corpus)) { + return null; + } + + return id(new DiffusionReadmeView()) + ->setUser($this->getViewer()) + ->setPath($readme_path) + ->setContent($corpus); + } + } diff --git a/src/applications/diffusion/controller/DiffusionDiffController.php b/src/applications/diffusion/controller/DiffusionDiffController.php index bb9354c0f9..86409c6faa 100644 --- a/src/applications/diffusion/controller/DiffusionDiffController.php +++ b/src/applications/diffusion/controller/DiffusionDiffController.php @@ -6,27 +6,18 @@ final class DiffusionDiffController extends DiffusionController { return true; } - protected function shouldLoadDiffusionRequest() { - return false; + protected function getDiffusionBlobFromRequest(AphrontRequest $request) { + return $request->getStr('ref'); } - protected function processDiffusionRequest(AphrontRequest $request) { - $data = $request->getURIMap(); - $data = $data + array( - 'dblob' => $this->getRequest()->getStr('ref'), - ); - try { - $drequest = DiffusionRequest::newFromAphrontRequestDictionary( - $data, - $request); - } catch (Exception $ex) { - return id(new Aphront404Response()) - ->setRequest($request); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; } - $this->setDiffusionRequest($drequest); - $drequest = $this->getDiffusionRequest(); $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); if (!$request->isAjax()) { diff --git a/src/applications/diffusion/controller/DiffusionExternalController.php b/src/applications/diffusion/controller/DiffusionExternalController.php index aa9375cf98..09687f11d0 100644 --- a/src/applications/diffusion/controller/DiffusionExternalController.php +++ b/src/applications/diffusion/controller/DiffusionExternalController.php @@ -6,12 +6,7 @@ final class DiffusionExternalController extends DiffusionController { return true; } - protected function shouldLoadDiffusionRequest() { - return false; - } - - protected function processDiffusionRequest(AphrontRequest $request) { - + public function handleRequest(AphrontRequest $request) { $uri = $request->getStr('uri'); $id = $request->getStr('id'); @@ -45,13 +40,13 @@ final class DiffusionExternalController extends DiffusionController { if ($best_match) { $repository = $repositories[$best_match]; - $redirect = DiffusionRequest::generateDiffusionURI( + $redirect = $repository->generateURI( array( - 'action' => 'browse', - 'callsign' => $repository->getCallsign(), - 'branch' => $repository->getDefaultBranch(), - 'commit' => $id, + 'action' => 'browse', + 'branch' => $repository->getDefaultBranch(), + 'commit' => $id, )); + return id(new AphrontRedirectResponse())->setURI($redirect); } } @@ -64,10 +59,11 @@ final class DiffusionExternalController extends DiffusionController { if (empty($commits)) { $desc = null; - if ($uri) { - $desc = $uri.', at '; + if (strlen($uri)) { + $desc = pht('"%s", at "%s"', $uri, $id); + } else { + $desc = pht('"%s"', $id); } - $desc .= $id; $content = id(new PHUIInfoView()) ->setTitle(pht('Unknown External')) @@ -83,10 +79,9 @@ final class DiffusionExternalController extends DiffusionController { } else if (count($commits) == 1) { $commit = head($commits); $repo = $repositories[$commit->getRepositoryID()]; - $redirect = DiffusionRequest::generateDiffusionURI( + $redirect = $repo->generateURI( array( 'action' => 'browse', - 'callsign' => $repo->getCallsign(), 'branch' => $repo->getDefaultBranch(), 'commit' => $commit->getCommitIdentifier(), )); @@ -96,10 +91,9 @@ final class DiffusionExternalController extends DiffusionController { $rows = array(); foreach ($commits as $commit) { $repo = $repositories[$commit->getRepositoryID()]; - $href = DiffusionRequest::generateDiffusionURI( + $href = $repo->generateURI( array( 'action' => 'browse', - 'callsign' => $repo->getCallsign(), 'branch' => $repo->getDefaultBranch(), 'commit' => $commit->getCommitIdentifier(), )); @@ -109,7 +103,7 @@ final class DiffusionExternalController extends DiffusionController { array( 'href' => $href, ), - 'r'.$repo->getCallsign().$commit->getCommitIdentifier()), + $commit->getMonogram()), $commit->loadCommitData()->getSummary(), ); } @@ -137,11 +131,13 @@ final class DiffusionExternalController extends DiffusionController { $content->setTable($table); } - return $this->buildApplicationPage( - $content, - array( - 'title' => pht('Unresolvable External'), - )); + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('External')); + + return $this->newPage() + ->setTitle(pht('Unresolvable External')) + ->setCrumbs($crumbs) + ->appendChild($content); } } diff --git a/src/applications/diffusion/controller/DiffusionHistoryController.php b/src/applications/diffusion/controller/DiffusionHistoryController.php index 65286e9d34..59248b7acf 100644 --- a/src/applications/diffusion/controller/DiffusionHistoryController.php +++ b/src/applications/diffusion/controller/DiffusionHistoryController.php @@ -6,19 +6,24 @@ final class DiffusionHistoryController extends DiffusionController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { - $drequest = $this->diffusionRequest; - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $page_size = $request->getInt('pagesize', 100); - $offset = $request->getInt('offset', 0); + $pager = id(new PHUIPagerView()) + ->readFromRequest($request); $params = array( 'commit' => $drequest->getCommit(), 'path' => $drequest->getPath(), - 'offset' => $offset, - 'limit' => $page_size + 1, + 'offset' => $pager->getOffset(), + 'limit' => $pager->getPageSize() + 1, ); if (!$request->getBool('copies')) { @@ -32,13 +37,8 @@ final class DiffusionHistoryController extends DiffusionController { $history = DiffusionPathChange::newFromConduit( $history_results['pathChanges']); - $pager = new PHUIPagerView(); - $pager->setPageSize($page_size); - $pager->setOffset($offset); $history = $pager->sliceResults($history); - $pager->setURI($request->getRequestURI(), 'offset'); - $show_graph = !strlen($drequest->getPath()); $content = array(); @@ -51,7 +51,8 @@ final class DiffusionHistoryController extends DiffusionController { if ($show_graph) { $history_table->setParents($history_results['parents']); - $history_table->setIsHead($offset == 0); + $history_table->setIsHead(!$pager->getOffset()); + $history_table->setIsTail(!$pager->getHasMorePages()); } $history_panel = new PHUIObjectBoxView(); @@ -79,23 +80,21 @@ final class DiffusionHistoryController extends DiffusionController { 'view' => 'history', )); - $pager = id(new PHUIBoxView()) - ->addClass('ml') - ->appendChild($pager); + $pager_box = $this->renderTablePagerBox($pager); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - $content, - $pager, - ), - array( - 'title' => array( + return $this->newPage() + ->setTitle( + array( pht('History'), - pht('%s Repository', $drequest->getRepository()->getCallsign()), - ), - )); + $repository->getDisplayName(), + )) + ->setCrumbs($crumbs) + ->appendChild( + array( + $object_box, + $content, + $pager_box, + )); } private function buildActionView(DiffusionRequest $drequest) { @@ -151,7 +150,6 @@ final class DiffusionHistoryController extends DiffusionController { ->setActionList($actions); $stable_commit = $drequest->getStableCommit(); - $callsign = $drequest->getRepository()->getCallsign(); $view->addProperty( pht('Commit'), diff --git a/src/applications/diffusion/controller/DiffusionLastModifiedController.php b/src/applications/diffusion/controller/DiffusionLastModifiedController.php index b23abf1903..0fea238249 100644 --- a/src/applications/diffusion/controller/DiffusionLastModifiedController.php +++ b/src/applications/diffusion/controller/DiffusionLastModifiedController.php @@ -6,9 +6,14 @@ final class DiffusionLastModifiedController extends DiffusionController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + + $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); - $viewer = $request->getUser(); $paths = $request->getStr('paths'); try { diff --git a/src/applications/diffusion/controller/DiffusionLintController.php b/src/applications/diffusion/controller/DiffusionLintController.php index b5bf561f8f..dcbb217d99 100644 --- a/src/applications/diffusion/controller/DiffusionLintController.php +++ b/src/applications/diffusion/controller/DiffusionLintController.php @@ -6,81 +6,116 @@ final class DiffusionLintController extends DiffusionController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { - $user = $request->getUser(); - $drequest = $this->diffusionRequest; + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); - if ($request->getStr('lint') !== null) { - $controller = new DiffusionLintDetailsController(); - $controller->setDiffusionRequest($drequest); - $controller->setCurrentApplication($this->getCurrentApplication()); - return $this->delegateToController($controller); + if ($this->getRepositoryIdentifierFromRequest($request)) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + + $drequest = $this->getDiffusionRequest(); + } else { + $drequest = null; + } + + $code = $request->getStr('lint'); + if (strlen($code)) { + return $this->buildDetailsResponse(); } $owners = array(); if (!$drequest) { if (!$request->getArr('owner')) { - $owners = array($user->getPHID()); + $owners = array($viewer->getPHID()); } else { $owners = array(head($request->getArr('owner'))); } } - $codes = $this->loadLintCodes($owners); - - if ($codes && !$drequest) { - // TODO: Build some real Query classes for this stuff. + $codes = $this->loadLintCodes($drequest, $owners); + if ($codes) { $branches = id(new PhabricatorRepositoryBranch())->loadAllWhere( 'id IN (%Ld)', array_unique(ipull($codes, 'branchID'))); + $branches = mpull($branches, null, 'getID'); + } else { + $branches = array(); + } + if ($branches) { $repositories = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withIDs(mpull($branches, 'getRepositoryID')) ->execute(); - - $drequests = array(); - foreach ($branches as $id => $branch) { - if (empty($repositories[$branch->getRepositoryID()])) { - continue; - } - - $drequests[$id] = DiffusionRequest::newFromDictionary(array( - 'user' => $user, - 'repository' => $repositories[$branch->getRepositoryID()], - 'branch' => $branch->getName(), - )); - } + $repositories = mpull($repositories, null, 'getID'); + } else { + $repositories = array(); } + $rows = array(); $total = 0; foreach ($codes as $code) { - if (!$this->diffusionRequest) { - $drequest = idx($drequests, $code['branchID']); + $branch = idx($branches, $code['branchID']); + if (!$branch) { + continue; } - if (!$drequest) { + $repository = idx($repositories, $branch->getRepositoryID()); + if (!$repository) { continue; } $total += $code['n']; - $href_lint = $drequest->generateURI(array( - 'action' => 'lint', - 'lint' => $code['code'], - )); - $href_browse = $drequest->generateURI(array( - 'action' => 'browse', - 'lint' => $code['code'], - )); - $href_repo = $drequest->generateURI(array('action' => 'lint')); + if ($drequest) { + $href_lint = $drequest->generateURI( + array( + 'action' => 'lint', + 'lint' => $code['code'], + )); + + $href_browse = $drequest->generateURI( + array( + 'action' => 'browse', + 'lint' => $code['code'], + )); + + $href_repo = $drequest->generateURI( + array( + 'action' => 'lint', + )); + } else { + $href_lint = $repository->generateURI( + array( + 'action' => 'lint', + 'lint' => $code['code'], + )); + + $href_browse = $repository->generateURI( + array( + 'action' => 'browse', + 'lint' => $code['code'], + )); + + $href_repo = $repository->generateURI( + array( + 'action' => 'lint', + )); + } $rows[] = array( phutil_tag('a', array('href' => $href_lint), $code['n']), phutil_tag('a', array('href' => $href_browse), $code['files']), - phutil_tag('a', array('href' => $href_repo), $drequest->getCallsign()), + phutil_tag( + 'a', + array( + 'href' => $href_repo, + ), + $repository->getDisplayName()), ArcanistLintSeverity::getStringForSeverity($code['maxSeverity']), $code['code'], $code['maxName'], @@ -98,14 +133,14 @@ final class DiffusionLintController extends DiffusionController { pht('Name'), pht('Example'), )) - ->setColumnVisibility(array(true, true, !$this->diffusionRequest)) + ->setColumnVisibility(array(true, true, !$drequest)) ->setColumnClasses(array('n', 'n', '', '', 'pri', '', '')); $content = array(); - if (!$this->diffusionRequest) { + if (!$drequest) { $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->setMethod('GET') ->appendControl( id(new AphrontFormTokenizerControl()) @@ -132,18 +167,18 @@ final class DiffusionLintController extends DiffusionController { 'view' => 'lint', )); - if ($this->diffusionRequest) { - $title[] = $drequest->getCallsign(); + if ($drequest) { + $title[] = $drequest->getRepository()->getDisplayName(); } else { $crumbs->addTextCrumb(pht('All Lint')); } - if ($this->diffusionRequest) { + if ($drequest) { $branch = $drequest->loadBranch(); $header = id(new PHUIHeaderView()) ->setHeader($this->renderPathLinks($drequest, 'lint')) - ->setUser($user) + ->setUser($viewer) ->setPolicyObject($drequest->getRepository()); $actions = $this->buildActionView($drequest); $properties = $this->buildPropertyView( @@ -159,20 +194,17 @@ final class DiffusionLintController extends DiffusionController { $object_box = null; } - - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - $content, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $object_box, + $content, + )); } - private function loadLintCodes(array $owner_phids) { - $drequest = $this->diffusionRequest; + private function loadLintCodes($drequest, array $owner_phids) { $conn = id(new PhabricatorRepository())->establishConnection('r'); $where = array('1 = 1'); @@ -314,7 +346,6 @@ final class DiffusionLintController extends DiffusionController { ->setUser($viewer) ->setActionList($actions); - $callsign = $drequest->getRepository()->getCallsign(); $lint_commit = $branch->getLintCommit(); $view->addProperty( @@ -338,4 +369,146 @@ final class DiffusionLintController extends DiffusionController { } + private function buildDetailsResponse() { + $request = $this->getRequest(); + + $limit = 500; + + $pager = id(new PHUIPagerView()) + ->readFromRequest($request) + ->setPageSize($limit); + + $offset = $pager->getOffset(); + + $drequest = $this->getDiffusionRequest(); + $branch = $drequest->loadBranch(); + $messages = $this->loadLintMessages($branch, $limit, $offset); + $is_dir = (substr('/'.$drequest->getPath(), -1) == '/'); + + $pager->setHasMorePages(count($messages) >= $limit); + + $authors = $this->loadViewerHandles(ipull($messages, 'authorPHID')); + + $rows = array(); + foreach ($messages as $message) { + $path = phutil_tag( + 'a', + array( + 'href' => $drequest->generateURI(array( + 'action' => 'lint', + 'path' => $message['path'], + )), + ), + substr($message['path'], strlen($drequest->getPath()) + 1)); + + $line = phutil_tag( + 'a', + array( + 'href' => $drequest->generateURI(array( + 'action' => 'browse', + 'path' => $message['path'], + 'line' => $message['line'], + 'commit' => $branch->getLintCommit(), + )), + ), + $message['line']); + + $author = $message['authorPHID']; + if ($author && $authors[$author]) { + $author = $authors[$author]->renderLink(); + } + + $rows[] = array( + $path, + $line, + $author, + ArcanistLintSeverity::getStringForSeverity($message['severity']), + $message['name'], + $message['description'], + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders(array( + pht('Path'), + pht('Line'), + pht('Author'), + pht('Severity'), + pht('Name'), + pht('Description'), + )) + ->setColumnClasses(array('', 'n')) + ->setColumnVisibility(array($is_dir)); + + $content = array(); + + $content[] = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Lint Details')) + ->setTable($table); + + $crumbs = $this->buildCrumbs( + array( + 'branch' => true, + 'path' => true, + 'view' => 'lint', + )); + + $pager_box = $this->renderTablePagerBox($pager); + + return $this->newPage() + ->setTitle( + array( + pht('Lint'), + $drequest->getRepository()->getDisplayName(), + )) + ->setCrumbs($crumbs) + ->appendChild( + array( + $content, + $pager_box, + )); + } + + private function loadLintMessages( + PhabricatorRepositoryBranch $branch, + $limit, + $offset) { + + $drequest = $this->getDiffusionRequest(); + if (!$branch) { + return array(); + } + + $conn = $branch->establishConnection('r'); + + $where = array( + qsprintf($conn, 'branchID = %d', $branch->getID()), + ); + + if ($drequest->getPath() != '') { + $path = '/'.$drequest->getPath(); + $is_dir = (substr($path, -1) == '/'); + $where[] = ($is_dir + ? qsprintf($conn, 'path LIKE %>', $path) + : qsprintf($conn, 'path = %s', $path)); + } + + if ($drequest->getLint() != '') { + $where[] = qsprintf( + $conn, + 'code = %s', + $drequest->getLint()); + } + + return queryfx_all( + $conn, + 'SELECT * + FROM %T + WHERE %Q + ORDER BY path, code, line LIMIT %d OFFSET %d', + PhabricatorRepository::TABLE_LINTMESSAGE, + implode(' AND ', $where), + $limit, + $offset); + } } diff --git a/src/applications/diffusion/controller/DiffusionLintDetailsController.php b/src/applications/diffusion/controller/DiffusionLintDetailsController.php deleted file mode 100644 index 71b823d508..0000000000 --- a/src/applications/diffusion/controller/DiffusionLintDetailsController.php +++ /dev/null @@ -1,144 +0,0 @@ -getInt('offset', 0); - - $drequest = $this->getDiffusionRequest(); - $branch = $drequest->loadBranch(); - $messages = $this->loadLintMessages($branch, $limit, $offset); - $is_dir = (substr('/'.$drequest->getPath(), -1) == '/'); - - $authors = $this->loadViewerHandles(ipull($messages, 'authorPHID')); - - $rows = array(); - foreach ($messages as $message) { - $path = phutil_tag( - 'a', - array( - 'href' => $drequest->generateURI(array( - 'action' => 'lint', - 'path' => $message['path'], - )), - ), - substr($message['path'], strlen($drequest->getPath()) + 1)); - - $line = phutil_tag( - 'a', - array( - 'href' => $drequest->generateURI(array( - 'action' => 'browse', - 'path' => $message['path'], - 'line' => $message['line'], - 'commit' => $branch->getLintCommit(), - )), - ), - $message['line']); - - $author = $message['authorPHID']; - if ($author && $authors[$author]) { - $author = $authors[$author]->renderLink(); - } - - $rows[] = array( - $path, - $line, - $author, - ArcanistLintSeverity::getStringForSeverity($message['severity']), - $message['name'], - $message['description'], - ); - } - - $table = id(new AphrontTableView($rows)) - ->setHeaders(array( - pht('Path'), - pht('Line'), - pht('Author'), - pht('Severity'), - pht('Name'), - pht('Description'), - )) - ->setColumnClasses(array('', 'n')) - ->setColumnVisibility(array($is_dir)); - - $content = array(); - - $pager = id(new PHUIPagerView()) - ->setPageSize($limit) - ->setOffset($offset) - ->setHasMorePages(count($messages) >= $limit) - ->setURI($request->getRequestURI(), 'offset'); - - $content[] = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Lint Details')) - ->setTable($table); - - $crumbs = $this->buildCrumbs( - array( - 'branch' => true, - 'path' => true, - 'view' => 'lint', - )); - - return $this->buildApplicationPage( - array( - $crumbs, - $content, - $pager, - ), - array( - 'title' => - array( - pht('Lint'), - $drequest->getRepository()->getCallsign(), - ), - )); - } - - private function loadLintMessages( - PhabricatorRepositoryBranch $branch, - $limit, - $offset) { - - $drequest = $this->getDiffusionRequest(); - if (!$branch) { - return array(); - } - - $conn = $branch->establishConnection('r'); - - $where = array( - qsprintf($conn, 'branchID = %d', $branch->getID()), - ); - - if ($drequest->getPath() != '') { - $path = '/'.$drequest->getPath(); - $is_dir = (substr($path, -1) == '/'); - $where[] = ($is_dir - ? qsprintf($conn, 'path LIKE %>', $path) - : qsprintf($conn, 'path = %s', $path)); - } - - if ($drequest->getLint() != '') { - $where[] = qsprintf( - $conn, - 'code = %s', - $drequest->getLint()); - } - - return queryfx_all( - $conn, - 'SELECT * - FROM %T - WHERE %Q - ORDER BY path, code, line LIMIT %d OFFSET %d', - PhabricatorRepository::TABLE_LINTMESSAGE, - implode(' AND ', $where), - $limit, - $offset); - } - -} diff --git a/src/applications/diffusion/controller/DiffusionMirrorDeleteController.php b/src/applications/diffusion/controller/DiffusionMirrorDeleteController.php index 3a4300690e..a049146cb5 100644 --- a/src/applications/diffusion/controller/DiffusionMirrorDeleteController.php +++ b/src/applications/diffusion/controller/DiffusionMirrorDeleteController.php @@ -3,9 +3,14 @@ final class DiffusionMirrorDeleteController extends DiffusionController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - $drequest = $this->diffusionRequest; + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $mirror = id(new PhabricatorRepositoryMirrorQuery()) @@ -28,16 +33,12 @@ final class DiffusionMirrorDeleteController return id(new AphrontReloadResponse())->setURI($edit_uri); } - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) + return $this->newDialog() ->setTitle(pht('Really delete mirror?')) ->appendChild( pht('Phabricator will stop pushing updates to this mirror.')) ->addSubmitButton(pht('Delete Mirror')) ->addCancelButton($edit_uri); - - return id(new AphrontDialogResponse()) - ->setDialog($dialog); } diff --git a/src/applications/diffusion/controller/DiffusionMirrorEditController.php b/src/applications/diffusion/controller/DiffusionMirrorEditController.php index 4d5e9f139f..d438a9e6d2 100644 --- a/src/applications/diffusion/controller/DiffusionMirrorEditController.php +++ b/src/applications/diffusion/controller/DiffusionMirrorEditController.php @@ -3,9 +3,14 @@ final class DiffusionMirrorEditController extends DiffusionController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - $drequest = $this->diffusionRequest; + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); PhabricatorPolicyFilter::requireCapability( @@ -112,17 +117,13 @@ final class DiffusionMirrorEditController ->setError($e_credentials) ->setOptions($credentials)); - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) + return $this->newDialog() ->setTitle($title) ->setWidth(AphrontDialogView::WIDTH_FORM) ->appendChild($form_errors) ->appendChild($form) ->addSubmitButton($submit) ->addCancelButton($edit_uri); - - return id(new AphrontDialogResponse()) - ->setDialog($dialog); } diff --git a/src/applications/diffusion/controller/DiffusionPathCompleteController.php b/src/applications/diffusion/controller/DiffusionPathCompleteController.php index 80cebaed03..999a04dbf8 100644 --- a/src/applications/diffusion/controller/DiffusionPathCompleteController.php +++ b/src/applications/diffusion/controller/DiffusionPathCompleteController.php @@ -2,21 +2,20 @@ final class DiffusionPathCompleteController extends DiffusionController { - protected function shouldLoadDiffusionRequest() { - return false; + protected function getRepositoryIdentifierFromRequest( + AphrontRequest $request) { + return $request->getStr('repositoryPHID'); } - protected function processDiffusionRequest(AphrontRequest $request) { - - $repository_phid = $request->getStr('repositoryPHID'); - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($request->getUser()) - ->withPHIDs(array($repository_phid)) - ->executeOne(); - if (!$repository) { - return new Aphront400Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $query_path = $request->getStr('q'); if (preg_match('@/$@', $query_path)) { $query_dir = $query_path; @@ -25,19 +24,11 @@ final class DiffusionPathCompleteController extends DiffusionController { } $query_dir = ltrim($query_dir, '/'); - $drequest = DiffusionRequest::newFromDictionary( - array( - 'user' => $request->getUser(), - 'repository' => $repository, - 'path' => $query_dir, - )); - $this->setDiffusionRequest($drequest); - $browse_results = DiffusionBrowseResultSet::newFromConduit( $this->callConduitWithDiffusionRequest( 'diffusion.browsequery', array( - 'path' => $drequest->getPath(), + 'path' => $query_dir, 'commit' => $drequest->getCommit(), ))); $paths = $browse_results->getPaths(); diff --git a/src/applications/diffusion/controller/DiffusionPathTreeController.php b/src/applications/diffusion/controller/DiffusionPathTreeController.php index 01a9e42552..231c7b6208 100644 --- a/src/applications/diffusion/controller/DiffusionPathTreeController.php +++ b/src/applications/diffusion/controller/DiffusionPathTreeController.php @@ -2,10 +2,16 @@ final class DiffusionPathTreeController extends DiffusionController { - protected function processDiffusionRequest(AphrontRequest $request) { - $drequest = $this->getDiffusionRequest(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } - if (!$drequest->getRepository()->canUsePathTree()) { + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + if (!$repository->canUsePathTree()) { return new Aphront404Response(); } diff --git a/src/applications/diffusion/controller/DiffusionPathValidateController.php b/src/applications/diffusion/controller/DiffusionPathValidateController.php index d54ddd7af6..e6fbc41132 100644 --- a/src/applications/diffusion/controller/DiffusionPathValidateController.php +++ b/src/applications/diffusion/controller/DiffusionPathValidateController.php @@ -2,37 +2,29 @@ final class DiffusionPathValidateController extends DiffusionController { - protected function shouldLoadDiffusionRequest() { - return false; + protected function getRepositoryIdentifierFromRequest( + AphrontRequest $request) { + return $request->getStr('repositoryPHID'); } - protected function processDiffusionRequest(AphrontRequest $request) { - - $repository_phid = $request->getStr('repositoryPHID'); - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($request->getUser()) - ->withPHIDs(array($repository_phid)) - ->executeOne(); - if (!$repository) { - return new Aphront400Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $path = $request->getStr('path'); $path = ltrim($path, '/'); - $drequest = DiffusionRequest::newFromDictionary( - array( - 'user' => $request->getUser(), - 'repository' => $repository, - 'path' => $path, - )); - $this->setDiffusionRequest($drequest); - $browse_results = DiffusionBrowseResultSet::newFromConduit( $this->callConduitWithDiffusionRequest( 'diffusion.browsequery', array( - 'path' => $drequest->getPath(), + 'path' => $path, 'commit' => $drequest->getCommit(), 'needValidityOnly' => true, ))); diff --git a/src/applications/diffusion/controller/DiffusionPushEventViewController.php b/src/applications/diffusion/controller/DiffusionPushEventViewController.php index 0ad40ae840..027cf16bbe 100644 --- a/src/applications/diffusion/controller/DiffusionPushEventViewController.php +++ b/src/applications/diffusion/controller/DiffusionPushEventViewController.php @@ -7,8 +7,8 @@ final class DiffusionPushEventViewController return true; } - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); $event = id(new PhabricatorRepositoryPushEventQuery()) ->setViewer($viewer) @@ -25,7 +25,8 @@ final class DiffusionPushEventViewController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb( $repository->getName(), - $this->getApplicationURI($repository->getCallsign().'/')); + $repository->getURI()); + $crumbs->addTextCrumb( pht('Push Logs'), $this->getApplicationURI( @@ -56,16 +57,15 @@ final class DiffusionPushEventViewController ->setHeaderText(pht('All Pushed Updates')) ->setTable($updates_table); - return $this->buildApplicationPage( - array( - $crumbs, - $detail_box, - $commits_box, - $update_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $detail_box, + $commits_box, + $update_box, + )); } private function buildPropertyList(PhabricatorRepositoryPushEvent $event) { diff --git a/src/applications/diffusion/controller/DiffusionPushLogListController.php b/src/applications/diffusion/controller/DiffusionPushLogListController.php index 4f2460be89..5b58881470 100644 --- a/src/applications/diffusion/controller/DiffusionPushLogListController.php +++ b/src/applications/diffusion/controller/DiffusionPushLogListController.php @@ -6,29 +6,10 @@ final class DiffusionPushLogListController extends DiffusionPushLogController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($request->getURIData('queryKey')) - ->setSearchEngine(new PhabricatorRepositoryPushLogSearchEngine()) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); - } - - public function buildSideNavView($for_app = false) { - $viewer = $this->getRequest()->getUser(); - - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - id(new PhabricatorRepositoryPushLogSearchEngine()) - ->setViewer($viewer) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; + public function handleRequest(AphrontRequest $request) { + return id(new PhabricatorRepositoryPushLogSearchEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/diffusion/controller/DiffusionRefTableController.php b/src/applications/diffusion/controller/DiffusionRefTableController.php index dac0c3a136..8a4e274819 100644 --- a/src/applications/diffusion/controller/DiffusionRefTableController.php +++ b/src/applications/diffusion/controller/DiffusionRefTableController.php @@ -6,9 +6,13 @@ final class DiffusionRefTableController extends DiffusionController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $this->getViewer(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); @@ -132,18 +136,15 @@ final class DiffusionRefTableController extends DiffusionController { $crumbs = $this->buildCrumbs(array()); $crumbs->addTextCrumb(pht('Refs')); - return $this->buildApplicationPage( - array( - $crumbs, - $content, - ), - array( - 'title' => array( - pht('Refs'), - $repository->getMonogram(), + return $this->newPage() + ->setTitle( + array( $ref_name, - ), - )); + pht('Ref'), + $repository->getDisplayName(), + )) + ->setCrumbs($crumbs) + ->appendChild($content); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index cd7859cc0a..9fc948d19a 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -6,16 +6,19 @@ final class DiffusionRepositoryController extends DiffusionController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $content = array(); $crumbs = $this->buildCrumbs(); - $content[] = $crumbs; $content[] = $this->buildPropertiesTable($drequest->getRepository()); @@ -73,15 +76,19 @@ final class DiffusionRepositoryController extends DiffusionController { ->setErrors(array($empty_message)); } - return $this->buildApplicationPage( - $content, - array( - 'title' => $drequest->getRepository()->getName(), - )); + return $this->newPage() + ->setTitle( + array( + $repository->getName(), + $repository->getDisplayName(), + )) + ->setCrumbs($crumbs) + ->appendChild($content); } private function buildNormalContent(DiffusionRequest $drequest) { + $request = $this->getRequest(); $repository = $drequest->getRepository(); $phids = array(); @@ -117,6 +124,9 @@ final class DiffusionRepositoryController extends DiffusionController { $history_exception = $ex; } + $browse_pager = id(new PHUIPagerView()) + ->readFromRequest($request); + try { $browse_results = DiffusionBrowseResultSet::newFromConduit( $this->callConduitWithDiffusionRequest( @@ -124,8 +134,10 @@ final class DiffusionRepositoryController extends DiffusionController { array( 'path' => $drequest->getPath(), 'commit' => $drequest->getCommit(), + 'limit' => $browse_pager->getPageSize() + 1, ))); $browse_paths = $browse_results->getPaths(); + $browse_paths = $browse_pager->sliceResults($browse_paths); foreach ($browse_paths as $item) { $data = $item->getLastCommitData(); @@ -149,30 +161,18 @@ final class DiffusionRepositoryController extends DiffusionController { $phids = array_keys($phids); $handles = $this->loadViewerHandles($phids); - $readme = null; if ($browse_results) { - $readme_path = $browse_results->getReadmePath(); - if ($readme_path) { - $readme_content = $this->callConduitWithDiffusionRequest( - 'diffusion.filecontentquery', - array( - 'path' => $readme_path, - 'commit' => $drequest->getStableCommit(), - )); - if ($readme_content) { - $readme = id(new DiffusionReadmeView()) - ->setUser($this->getViewer()) - ->setPath($readme_path) - ->setContent($readme_content['corpus']); - } - } + $readme = $this->renderDirectoryReadme($browse_results); + } else { + $readme = null; } $content[] = $this->buildBrowseTable( $browse_results, $browse_paths, $browse_exception, - $handles); + $handles, + $browse_pager); $content[] = $this->buildHistoryTable( $history_results, @@ -217,7 +217,12 @@ final class DiffusionRepositoryController extends DiffusionController { if (!$repository->isTracked()) { $header->setStatus('fa-ban', 'dark', pht('Inactive')); } else if ($repository->isImporting()) { - $header->setStatus('fa-clock-o', 'indigo', pht('Importing...')); + $ratio = $repository->loadImportProgress(); + $percentage = sprintf('%.2f%%', 100 * $ratio); + $header->setStatus( + 'fa-clock-o', + 'indigo', + pht('Importing (%s)...', $percentage)); } else { $header->setStatus('fa-check', 'bluegrey', pht('Active')); } @@ -480,7 +485,7 @@ final class DiffusionRepositoryController extends DiffusionController { private function buildActionList(PhabricatorRepository $repository) { $viewer = $this->getRequest()->getUser(); - $edit_uri = $this->getApplicationURI($repository->getCallsign().'/edit/'); + $edit_uri = $repository->getPathURI('edit/'); $view = id(new PhabricatorActionListView()) ->setUser($viewer) @@ -500,9 +505,8 @@ final class DiffusionRepositoryController extends DiffusionController { ->setDisabled(!$can_edit)); if ($repository->isHosted()) { - $callsign = $repository->getCallsign(); $push_uri = $this->getApplicationURI( - 'pushlog/?repositories=r'.$callsign); + 'pushlog/?repositories='.$repository->getMonogram()); $view->addAction( id(new PhabricatorActionView()) @@ -551,7 +555,6 @@ final class DiffusionRepositoryController extends DiffusionController { } $history_table->setIsHead(true); - $callsign = $drequest->getRepository()->getCallsign(); $icon = id(new PHUIIconView()) ->setIconFont('fa-list-alt'); @@ -579,7 +582,8 @@ final class DiffusionRepositoryController extends DiffusionController { $browse_results, $browse_paths, $browse_exception, - array $handles) { + array $handles, + PHUIPagerView $pager) { require_celerity_resource('diffusion-icons-css'); @@ -660,7 +664,19 @@ final class DiffusionRepositoryController extends DiffusionController { $browse_panel->setTable($browse_table); - return array($locate_panel, $browse_panel); + $pager->setURI($browse_uri, 'offset'); + + if ($pager->willShowPagingControls()) { + $pager_box = $this->renderTablePagerBox($pager); + } else { + $pager_box = null; + } + + return array( + $locate_panel, + $browse_panel, + $pager_box, + ); } private function renderCloneCommand( diff --git a/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php b/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php index 79c617f220..c6e2796fc1 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php @@ -6,7 +6,7 @@ final class DiffusionRepositoryCreateController private $edit; private $repository; - protected function processDiffusionRequest(AphrontRequest $request) { + public function handleRequest(AphrontRequest $request) { $viewer = $request->getUser(); $this->edit = $request->getURIData('edit'); @@ -19,6 +19,11 @@ final class DiffusionRepositoryCreateController switch ($this->edit) { case 'remote': case 'policy': + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; + } + $repository = $this->getDiffusionRequest()->getRepository(); // Make sure we have CAN_EDIT. @@ -275,14 +280,10 @@ final class DiffusionRepositoryCreateController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title); - return $this->buildApplicationPage( - array( - $crumbs, - $form, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($form); } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryDefaultController.php b/src/applications/diffusion/controller/DiffusionRepositoryDefaultController.php index 9b72c66eea..efebd12c6f 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryDefaultController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryDefaultController.php @@ -2,7 +2,12 @@ final class DiffusionRepositoryDefaultController extends DiffusionController { - protected function processDiffusionRequest(AphrontRequest $request) { + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + // NOTE: This controller is just here to make sure we call // willBeginExecution() on any /diffusion/X/ URI, so we can intercept // `git`, `hg` and `svn` HTTP protocol requests. @@ -11,7 +16,10 @@ final class DiffusionRepositoryDefaultController extends DiffusionController { // clone URI with "/anything.git" at the end into their web browser. // Send them to the canonical repository URI. + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + return id(new AphrontRedirectResponse()) - ->setURI($this->getDiffusionRequest()->getRepository()->getURI()); + ->setURI($repository->getURI()); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php index 2b0bc674be..8459498374 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php @@ -3,25 +3,16 @@ final class DiffusionRepositoryEditActionsController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); // NOTE: We're inverting these here, because the storage is silly. @@ -109,14 +100,10 @@ final class DiffusionRepositoryEditActionsController ->setHeaderText($title) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($form_box); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php index 2342530195..c8428833ee 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php @@ -3,25 +3,16 @@ final class DiffusionRepositoryEditActivateController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); if ($request->isFormPost()) { @@ -38,28 +29,21 @@ final class DiffusionRepositoryEditActivateController return id(new AphrontReloadResponse())->setURI($edit_uri); } - $dialog = id(new AphrontDialogView()) - ->setUser($viewer); - if ($repository->isTracked()) { - $dialog + return $this->newDialog() ->setTitle(pht('Deactivate Repository?')) ->appendChild( pht('Deactivate this repository?')) ->addSubmitButton(pht('Deactivate Repository')) ->addCancelButton($edit_uri); } else { - $dialog + return $this->newDialog() ->setTitle(pht('Activate Repository?')) ->appendChild( pht('Activate this repository?')) ->addSubmitButton(pht('Activate Repository')) ->addCancelButton($edit_uri); } - - return id(new AphrontDialogResponse()) - ->setDialog($dialog); } - } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php index 8fe5b70f97..e47bde3902 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php @@ -3,24 +3,16 @@ final class DiffusionRepositoryEditAutomationController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $this->getViewer(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + if (!$repository->supportsAutomation()) { return new Aphront404Response(); } @@ -81,14 +73,10 @@ final class DiffusionRepositoryEditAutomationController ->setHeaderText($title) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($object_box); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php index 80771f65c4..5b4429b61a 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php @@ -3,31 +3,24 @@ final class DiffusionRepositoryEditBasicController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $user = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->needProjectPHIDs(true) - ->withIDs(array($repository->getID())) - ->executeOne(); - - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $request->getUser(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); $v_name = $repository->getName(); $v_desc = $repository->getDetail('description'); $v_clone_name = $repository->getDetail('clone-name'); + $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( + $repository->getPHID(), + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); $e_name = true; $errors = array(); @@ -81,7 +74,7 @@ final class DiffusionRepositoryEditBasicController id(new PhabricatorRepositoryEditor()) ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request) - ->setActor($user) + ->setActor($viewer) ->applyTransactions($repository, $xactions); return id(new AphrontRedirectResponse())->setURI($edit_uri); @@ -94,7 +87,7 @@ final class DiffusionRepositoryEditBasicController $title = pht('Edit %s', $repository->getName()); $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->appendChild( id(new AphrontFormTextControl()) ->setName('name') @@ -118,7 +111,7 @@ final class DiffusionRepositoryEditBasicController $form ->appendChild( id(new PhabricatorRemarkupControl()) - ->setUser($user) + ->setUser($viewer) ->setName('description') ->setLabel(pht('Description')) ->setValue($v_desc)) @@ -127,7 +120,7 @@ final class DiffusionRepositoryEditBasicController ->setDatasource(new PhabricatorProjectDatasource()) ->setName('projectPHIDs') ->setLabel(pht('Projects')) - ->setValue($repository->getProjectPHIDs())) + ->setValue($v_projects)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Save')) @@ -140,14 +133,10 @@ final class DiffusionRepositoryEditBasicController ->setForm($form) ->setFormErrors($errors); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($object_box); } private function getReadmeInstructions() { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php index a175fba941..c256336ec2 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php @@ -3,25 +3,16 @@ final class DiffusionRepositoryEditBranchesController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $request = $this->getRequest(); - $viewer = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $is_git = false; $is_hg = false; @@ -226,14 +217,10 @@ final class DiffusionRepositoryEditBranchesController ->setHeaderText($title) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($form_box); } private function processBranches($string) { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php index 5c2a7669e0..1e3cb553da 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php @@ -6,12 +6,13 @@ abstract class DiffusionRepositoryEditController protected function buildApplicationCrumbs($is_main = false) { $crumbs = parent::buildApplicationCrumbs(); - if ($this->diffusionRequest) { - $repository = $this->getDiffusionRequest()->getRepository(); - $repo_uri = $this->getRepositoryControllerURI($repository, ''); + if ($this->hasDiffusionRequest()) { + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $repo_uri = $repository->getURI(); $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); - $crumbs->addTextCrumb('r'.$repository->getCallsign(), $repo_uri); + $crumbs->addTextCrumb($repository->getDisplayname(), $repo_uri); if ($is_main) { $crumbs->addTextCrumb(pht('Edit Repository')); diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php index 076c2580e5..26d8b57f33 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php @@ -3,25 +3,16 @@ final class DiffusionRepositoryEditDangerousController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + if (!$repository->canAllowDangerousChanges()) { return new Aphront400Response(); } @@ -42,13 +33,10 @@ final class DiffusionRepositoryEditDangerousController return id(new AphrontReloadResponse())->setURI($edit_uri); } - $dialog = id(new AphrontDialogView()) - ->setUser($viewer); - $force = phutil_tag('tt', array(), '--force'); if ($repository->shouldAllowDangerousChanges()) { - $dialog + return $this->newDialog() ->setTitle(pht('Prevent Dangerous changes?')) ->appendChild( pht( @@ -58,7 +46,7 @@ final class DiffusionRepositoryEditDangerousController ->addSubmitButton(pht('Prevent Dangerous Changes')) ->addCancelButton($edit_uri); } else { - $dialog + return $this->newDialog() ->setTitle(pht('Allow Dangerous Changes?')) ->appendChild( pht( @@ -69,9 +57,6 @@ final class DiffusionRepositoryEditDangerousController ->addSubmitButton(pht('Allow Dangerous Changes')) ->addCancelButton($edit_uri); } - - return id(new AphrontDialogResponse()) - ->setDialog($dialog); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php index f39708fe1b..074e79445b 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php @@ -3,24 +3,16 @@ final class DiffusionRepositoryEditDeleteController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); $dialog = new AphrontDialogView(); @@ -45,14 +37,10 @@ final class DiffusionRepositoryEditDeleteController phutil_tag('p', array(), $text_2), )); - $dialog = id(new AphrontDialogView()) - ->setUser($request->getUser()) + return $this->newDialog() ->setTitle(pht('Really want to delete the repository?')) ->appendChild($body) ->addCancelButton($edit_uri, pht('Okay')); - - return id(new AphrontDialogResponse())->setDialog($dialog); } - } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php index f71516e6ca..0059c9d6c0 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php @@ -3,25 +3,16 @@ final class DiffusionRepositoryEditEncodingController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $user = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $user = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); $v_encoding = $repository->getDetail('encoding'); @@ -79,14 +70,10 @@ final class DiffusionRepositoryEditEncodingController ->setForm($form) ->setFormErrors($errors); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($object_box); } private function getEncodingInstructions() { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php index 1fcf366967..be40b794b3 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php @@ -5,25 +5,17 @@ final class DiffusionRepositoryEditHostingController private $serve; - protected function processDiffusionRequest(AphrontRequest $request) { - $user = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - $this->serve = $request->getURIData('serve'); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $this->serve = $request->getURIData('serve'); + if (!$this->serve) { return $this->handleHosting($repository); } else { @@ -107,14 +99,10 @@ final class DiffusionRepositoryEditHostingController ->setHeaderText($title) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($object_box); } public function handleProtocols(PhabricatorRepository $repository) { @@ -272,14 +260,10 @@ final class DiffusionRepositoryEditHostingController ->setHeaderText($title) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($object_box); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php index 780e83d089..bbf7152bf3 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php @@ -3,15 +3,15 @@ final class DiffusionRepositoryEditMainController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; + } - PhabricatorPolicyFilter::requireCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); $is_svn = false; $is_git = false; @@ -1137,45 +1137,8 @@ final class DiffusionRepositoryEditMainController } if ($repository->isImporting()) { - $progress = queryfx_all( - $repository->establishConnection('r'), - 'SELECT importStatus, count(*) N FROM %T WHERE repositoryID = %d - GROUP BY importStatus', - id(new PhabricatorRepositoryCommit())->getTableName(), - $repository->getID()); - - $done = 0; - $total = 0; - foreach ($progress as $row) { - $total += $row['N'] * 4; - $status = $row['importStatus']; - if ($status & PhabricatorRepositoryCommit::IMPORTED_MESSAGE) { - $done += $row['N']; - } - if ($status & PhabricatorRepositoryCommit::IMPORTED_CHANGE) { - $done += $row['N']; - } - if ($status & PhabricatorRepositoryCommit::IMPORTED_OWNERS) { - $done += $row['N']; - } - if ($status & PhabricatorRepositoryCommit::IMPORTED_HERALD) { - $done += $row['N']; - } - } - - if ($total) { - $percentage = 100 * ($done / $total); - } else { - $percentage = 0; - } - - // Cap this at "99.99%", because it's confusing to users when the actual - // fraction is "99.996%" and it rounds up to "100.00%". - if ($percentage > 99.99) { - $percentage = 99.99; - } - - $percentage = sprintf('%.2f%%', $percentage); + $ratio = $repository->loadImportProgress(); + $percentage = sprintf('%.2f%%', 100 * $ratio); $view->addItem( id(new PHUIStatusItemView()) diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php index 9fcfd767c0..deb8fad669 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php @@ -3,23 +3,16 @@ final class DiffusionRepositoryEditStagingController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $user = $request->getUser(); - $drequest = $this->diffusionRequest; + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; + } + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - if (!$repository) { - return new Aphront404Response(); - } if (!$repository->supportsStaging()) { return new Aphront404Response(); @@ -43,7 +36,7 @@ final class DiffusionRepositoryEditStagingController id(new PhabricatorRepositoryEditor()) ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request) - ->setActor($user) + ->setActor($viewer) ->applyTransactions($repository, $xactions); return id(new AphrontRedirectResponse())->setURI($edit_uri); @@ -55,7 +48,7 @@ final class DiffusionRepositoryEditStagingController $title = pht('Edit %s', $repository->getName()); $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->appendRemarkupInstructions( pht( "To make it easier to run integration tests and builds on code ". @@ -79,14 +72,10 @@ final class DiffusionRepositoryEditStagingController ->setHeaderText($title) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($object_box); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php index 345464a005..711844188a 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php @@ -3,25 +3,16 @@ final class DiffusionRepositoryEditStorageController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $user = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); $v_local = $repository->getHumanReadableDetail('local-path'); @@ -44,7 +35,7 @@ final class DiffusionRepositoryEditStorageController } $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Storage Service')) @@ -60,8 +51,8 @@ final class DiffusionRepositoryEditStorageController "web interface. To edit it, run this command:\n\n %s", sprintf( 'phabricator/ $ ./bin/repository edit %s --as %s --local-path ...', - $repository->getCallsign(), - $user->getUsername()))) + $repository->getMonogram(), + $viewer->getUsername()))) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($edit_uri, pht('Done'))); @@ -71,14 +62,10 @@ final class DiffusionRepositoryEditStorageController ->setForm($form) ->setFormErrors($errors); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($object_box); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php index b125f26cae..93b9193f14 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php @@ -3,25 +3,16 @@ final class DiffusionRepositoryEditSubversionController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: @@ -108,14 +99,10 @@ final class DiffusionRepositoryEditSubversionController ->setHeaderText($title) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($form_box); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php index e50d297586..303959d098 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php @@ -3,30 +3,22 @@ final class DiffusionRepositoryEditUpdateController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); if ($request->isFormPost()) { $params = array( - 'callsigns' => array( - $repository->getCallsign(), + 'repositories' => array( + $repository->getPHID(), ), ); diff --git a/src/applications/diffusion/controller/DiffusionRepositoryListController.php b/src/applications/diffusion/controller/DiffusionRepositoryListController.php index ec93edbc41..5897d2f162 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryListController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryListController.php @@ -6,28 +6,10 @@ final class DiffusionRepositoryListController extends DiffusionController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($request->getURIData('queryKey')) - ->setSearchEngine(new PhabricatorRepositorySearchEngine()) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); - } - - public function buildSideNavView($for_app = false) { - $viewer = $this->getRequest()->getUser(); - - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - id(new PhabricatorRepositorySearchEngine()) - ->setViewer($viewer) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; + public function handleRequest(AphrontRequest $request) { + return id(new PhabricatorRepositorySearchEngine()) + ->setController($this) + ->buildResponse(); } protected function buildApplicationCrumbs() { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryNewController.php b/src/applications/diffusion/controller/DiffusionRepositoryNewController.php index f7fac7b183..560d896aff 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryNewController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryNewController.php @@ -2,8 +2,8 @@ final class DiffusionRepositoryNewController extends DiffusionController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); $this->requireApplicationCapability( DiffusionCreateRepositoriesCapability::CAPABILITY); @@ -70,14 +70,10 @@ final class DiffusionRepositoryNewController extends DiffusionController { ->setHeaderText(pht('Create or Import Repository')) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => pht('New Repository'), - )); + return $this->newPage() + ->setTitle(pht('New Repository')) + ->setCrumbs($crumbs) + ->appendChild($form_box); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositorySymbolsController.php b/src/applications/diffusion/controller/DiffusionRepositorySymbolsController.php index 133ee38813..6076d2df65 100644 --- a/src/applications/diffusion/controller/DiffusionRepositorySymbolsController.php +++ b/src/applications/diffusion/controller/DiffusionRepositorySymbolsController.php @@ -3,25 +3,16 @@ final class DiffusionRepositorySymbolsController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $user = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); $v_sources = $repository->getSymbolSources(); @@ -55,7 +46,7 @@ final class DiffusionRepositorySymbolsController id(new PhabricatorRepositoryEditor()) ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request) - ->setActor($user) + ->setActor($viewer) ->applyTransactions($repository, $xactions); return id(new AphrontRedirectResponse())->setURI($edit_uri); @@ -71,7 +62,7 @@ final class DiffusionRepositorySymbolsController $title = pht('Edit %s', $repository->getName()); $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->appendRemarkupInstructions($this->getInstructions()) ->appendChild( id(new AphrontFormTextControl()) @@ -99,14 +90,10 @@ final class DiffusionRepositorySymbolsController ->setForm($form) ->setFormErrors($errors); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($object_box); } private function getInstructions() { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php b/src/applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php index 6a9039889e..c52b0a14e9 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php @@ -3,24 +3,16 @@ final class DiffusionRepositoryTestAutomationController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $this->getViewer(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); if (!$repository->canPerformAutomation()) { diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php index 271ab36e91..ea5d4c209d 100644 --- a/src/applications/diffusion/controller/DiffusionServeController.php +++ b/src/applications/diffusion/controller/DiffusionServeController.php @@ -2,12 +2,9 @@ final class DiffusionServeController extends DiffusionController { - protected function shouldLoadDiffusionRequest() { - return false; - } - - public static function isVCSRequest(AphrontRequest $request) { - if (!self::getCallsign($request)) { + public function isVCSRequest(AphrontRequest $request) { + $identifier = $this->getRepositoryIdentifierFromRequest($request); + if ($identifier === null) { return null; } @@ -47,20 +44,8 @@ final class DiffusionServeController extends DiffusionController { return $vcs; } - private static function getCallsign(AphrontRequest $request) { - $uri = $request->getRequestURI(); - - $regex = '@^/diffusion/(?P[A-Z]+)(/|$)@'; - $matches = null; - if (!preg_match($regex, (string)$uri, $matches)) { - return null; - } - - return $matches['callsign']; - } - - protected function processDiffusionRequest(AphrontRequest $request) { - $callsign = self::getCallsign($request); + public function handleRequest(AphrontRequest $request) { + $identifier = $this->getRepositoryIdentifierFromRequest($request); // If authentication credentials have been provided, try to find a user // that actually matches those credentials. @@ -99,7 +84,7 @@ final class DiffusionServeController extends DiffusionController { try { $repository = id(new PhabricatorRepositoryQuery()) ->setViewer($viewer) - ->withCallsigns(array($callsign)) + ->withIdentifiers(array($identifier)) ->executeOne(); if (!$repository) { return new PhabricatorVCSResponse( diff --git a/src/applications/diffusion/controller/DiffusionSymbolController.php b/src/applications/diffusion/controller/DiffusionSymbolController.php index 131e8774bd..defe09d58c 100644 --- a/src/applications/diffusion/controller/DiffusionSymbolController.php +++ b/src/applications/diffusion/controller/DiffusionSymbolController.php @@ -2,15 +2,13 @@ final class DiffusionSymbolController extends DiffusionController { - private $name; - - protected function processDiffusionRequest(AphrontRequest $request) { - $user = $request->getUser(); - $this->name = $request->getURIData('name'); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $name = $request->getURIData('name'); $query = id(new DiffusionSymbolQuery()) - ->setViewer($user) - ->setName($this->name); + ->setViewer($viewer) + ->setName($name); if ($request->getStr('context')) { $query->setContext($request->getStr('context')); @@ -48,9 +46,8 @@ final class DiffusionSymbolController extends DiffusionController { $symbols = $query->execute(); - $external_query = id(new DiffusionExternalSymbolQuery()) - ->withNames(array($this->name)); + ->withNames(array($name)); if ($request->getStr('context')) { $external_query->withContexts(array($request->getStr('context'))); @@ -137,15 +134,17 @@ final class DiffusionSymbolController extends DiffusionController { $table->setNoDataString( pht('No matching symbol could be found in any indexed repository.')); - $panel = new PHUIObjectBoxView(); - $panel->setHeaderText(pht('Similar Symbols')); - $panel->setTable($table); + $panel = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Similar Symbols')) + ->setTable($table); - return $this->buildApplicationPage( - $panel, - array( - 'title' => pht('Find Symbol'), - )); + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Find Symbol')); + + return $this->newPage() + ->setTitle(pht('Find Symbol')) + ->setCrumbs($crumbs) + ->appendChild($panel); } } diff --git a/src/applications/diffusion/controller/DiffusionTagListController.php b/src/applications/diffusion/controller/DiffusionTagListController.php index 897de30650..4749675bbb 100644 --- a/src/applications/diffusion/controller/DiffusionTagListController.php +++ b/src/applications/diffusion/controller/DiffusionTagListController.php @@ -6,22 +6,25 @@ final class DiffusionTagListController extends DiffusionController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { - $drequest = $this->getDiffusionRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $pager = new PHUIPagerView(); - $pager->setURI($request->getRequestURI(), 'offset'); - $pager->setOffset($request->getInt('offset')); + $pager = id(new PHUIPagerView()) + ->readFromRequest($request); $params = array( 'limit' => $pager->getPageSize() + 1, 'offset' => $pager->getOffset(), ); - if ($drequest->getSymbolicCommit()) { + if (strlen($drequest->getSymbolicCommit())) { $is_commit = true; $params['commit'] = $drequest->getSymbolicCommit(); } else { @@ -79,18 +82,20 @@ final class DiffusionTagListController extends DiffusionController { 'commit' => $drequest->getSymbolicCommit(), )); - return $this->buildApplicationPage( - array( - $crumbs, - $content, - $pager, - ), - array( - 'title' => array( + $pager_box = $this->renderTablePagerBox($pager); + + return $this->newPage() + ->setTitle( + array( pht('Tags'), - pht('%s Repository', $repository->getCallsign()), - ), - )); + $repository->getDisplayName(), + )) + ->setCrumbs($crumbs) + ->appendChild( + array( + $content, + $pager_box, + )); } } diff --git a/src/applications/diffusion/data/DiffusionFileContent.php b/src/applications/diffusion/data/DiffusionFileContent.php deleted file mode 100644 index 1680d1f964..0000000000 --- a/src/applications/diffusion/data/DiffusionFileContent.php +++ /dev/null @@ -1,63 +0,0 @@ -textList = $text_list; - return $this; - } - public function getTextList() { - if (!$this->textList) { - return phutil_split_lines($this->getCorpus(), $retain_ends = false); - } - return $this->textList; - } - - public function setRevList(array $rev_list) { - $this->revList = $rev_list; - return $this; - } - public function getRevList() { - return $this->revList; - } - - public function setBlameDict(array $blame_dict) { - $this->blameDict = $blame_dict; - return $this; - } - public function getBlameDict() { - return $this->blameDict; - } - - public function setCorpus($corpus) { - $this->corpus = $corpus; - return $this; - } - - public function getCorpus() { - return $this->corpus; - } - - public function toDictionary() { - return array( - 'corpus' => $this->getCorpus(), - 'blameDict' => $this->getBlameDict(), - 'revList' => $this->getRevList(), - 'textList' => $this->getTextList(), - ); - } - - public static function newFromConduit(array $dict) { - return id(new DiffusionFileContent()) - ->setCorpus($dict['corpus']) - ->setBlameDict($dict['blameDict']) - ->setRevList($dict['revList']) - ->setTextList($dict['textList']); - } - -} diff --git a/src/applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php b/src/applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php index a8193e569f..64caee879f 100644 --- a/src/applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php +++ b/src/applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php @@ -28,7 +28,7 @@ final class DiffusionHovercardEngineExtension $author_phid = $commit->getAuthorPHID(); if ($author_phid) { - $author = $viewer->loadHandle($author)->renderLink(); + $author = $viewer->renderHandle($author_phid); } else { $commit_data = $commit->loadCommitData(); $author = phutil_tag('em', array(), $commit_data->getAuthorName()); diff --git a/src/applications/diffusion/herald/DiffusionCommitBranchesHeraldField.php b/src/applications/diffusion/herald/DiffusionCommitBranchesHeraldField.php index dec0a05f0d..03c4e3be62 100644 --- a/src/applications/diffusion/herald/DiffusionCommitBranchesHeraldField.php +++ b/src/applications/diffusion/herald/DiffusionCommitBranchesHeraldField.php @@ -14,7 +14,7 @@ final class DiffusionCommitBranchesHeraldField $repository = $object->getRepository(); $params = array( - 'callsign' => $repository->getCallsign(), + 'repository' => $repository->getPHID(), 'contains' => $commit->getCommitIdentifier(), ); diff --git a/src/applications/diffusion/herald/HeraldCommitAdapter.php b/src/applications/diffusion/herald/HeraldCommitAdapter.php index 9baac6f2ad..67234edeab 100644 --- a/src/applications/diffusion/herald/HeraldCommitAdapter.php +++ b/src/applications/diffusion/herald/HeraldCommitAdapter.php @@ -130,10 +130,7 @@ final class HeraldCommitAdapter } public function getHeraldName() { - return - 'r'. - $this->repository->getCallsign(). - $this->commit->getCommitIdentifier(); + return $this->commit->getMonogram(); } public function loadAffectedPaths() { diff --git a/src/applications/diffusion/query/DiffusionCommitQuery.php b/src/applications/diffusion/query/DiffusionCommitQuery.php index e8f499955a..ec76468332 100644 --- a/src/applications/diffusion/query/DiffusionCommitQuery.php +++ b/src/applications/diffusion/query/DiffusionCommitQuery.php @@ -51,6 +51,11 @@ final class DiffusionCommitQuery * they queried for. */ public function withIdentifiers(array $identifiers) { + // Some workflows (like blame lookups) can pass in large numbers of + // duplicate identifiers. We only care about unique identifiers, so + // get rid of duplicates immediately. + $identifiers = array_fuse($identifiers); + $this->identifiers = $identifiers; return $this; } @@ -185,7 +190,7 @@ final class DiffusionCommitQuery // Build the identifierMap if ($this->identifiers !== null) { - $ids = array_fuse($this->identifiers); + $ids = $this->identifiers; $prefixes = array( 'r'.$commit->getRepository()->getCallsign(), 'r'.$commit->getRepository()->getCallsign().':', @@ -395,7 +400,6 @@ final class DiffusionCommitQuery $repos->execute(); $repos = $repos->getIdentifierMap(); - foreach ($refs as $key => $ref) { $repo = idx($repos, $ref['callsign']); @@ -404,7 +408,7 @@ final class DiffusionCommitQuery } if ($repo->isSVN()) { - if (!ctype_digit($ref['identifier'])) { + if (!ctype_digit((string)$ref['identifier'])) { continue; } $sql[] = qsprintf( @@ -419,11 +423,25 @@ final class DiffusionCommitQuery if (strlen($ref['identifier']) < $min_qualified) { continue; } - $sql[] = qsprintf( - $conn, - '(commit.repositoryID = %d AND commit.commitIdentifier LIKE %>)', - $repo->getID(), - $ref['identifier']); + + $identifier = $ref['identifier']; + if (strlen($identifier) == 40) { + // MySQL seems to do slightly better with this version if the + // clause, so issue it if we have a full commit hash. + $sql[] = qsprintf( + $conn, + '(commit.repositoryID = %d + AND commit.commitIdentifier = %s)', + $repo->getID(), + $identifier); + } else { + $sql[] = qsprintf( + $conn, + '(commit.repositoryID = %d + AND commit.commitIdentifier LIKE %>)', + $repo->getID(), + $identifier); + } } } } diff --git a/src/applications/diffusion/query/DiffusionQuery.php b/src/applications/diffusion/query/DiffusionQuery.php index a180a61f5a..409bf68980 100644 --- a/src/applications/diffusion/query/DiffusionQuery.php +++ b/src/applications/diffusion/query/DiffusionQuery.php @@ -53,7 +53,7 @@ abstract class DiffusionQuery extends PhabricatorQuery { $repository = $drequest->getRepository(); $core_params = array( - 'callsign' => $repository->getCallsign(), + 'repository' => $repository->getPHID(), ); if ($drequest->getBranch() !== null) { diff --git a/src/applications/diffusion/query/__tests__/DiffusionGitFileContentQueryTestCase.php b/src/applications/diffusion/query/__tests__/DiffusionGitFileContentQueryTestCase.php deleted file mode 100644 index 1e69ecd5fa..0000000000 --- a/src/applications/diffusion/query/__tests__/DiffusionGitFileContentQueryTestCase.php +++ /dev/null @@ -1,32 +0,0 @@ -call()'); - $this->assertEqual($result[0], '8220d5d54f6d5d5552a636576cbe9c35f15b65b2'); - $this->assertEqual($result[1], 'Andrew Gallagher'); - $this->assertEqual($result[2], ' $somevar = $this->call()'); - - // User name like 'Jimmy (He) Zhang' - $result = DiffusionGitFileContentQuery::match( - '8220d5d54f6d5d5552a636576cbe9c35f15b65b2 '. - '( Jimmy (He) Zhang 2013-10-11 5) '. - 'code(); "(string literal 9999-99-99 2)"; more_code();'); - $this->assertEqual($result[1], 'Jimmy (He) Zhang'); - $this->assertEqual($result[2], - ' code(); "(string literal 9999-99-99 2)"; more_code();'); - - // User name like 'Scott Shapiro (Ads Product Marketing)' - $result = DiffusionGitFileContentQuery::match( - '8220d5d54f6d5d5552a636576cbe9c35f15b65b2 '. - '( Scott Shapiro (Ads Product Marketing) 2013-10-11 5) '. - 'code(); "(string literal 9999-99-99 2)"; more_code();'); - $this->assertEqual($result[1], 'Scott Shapiro (Ads Product Marketing)'); - $this->assertEqual($result[2], - ' code(); "(string literal 9999-99-99 2)"; more_code();'); - } -} diff --git a/src/applications/diffusion/query/blame/DiffusionBlameQuery.php b/src/applications/diffusion/query/blame/DiffusionBlameQuery.php new file mode 100644 index 0000000000..1c2221eae3 --- /dev/null +++ b/src/applications/diffusion/query/blame/DiffusionBlameQuery.php @@ -0,0 +1,182 @@ +timeout = $timeout; + return $this; + } + + public function getTimeout() { + return $this->timeout; + } + + public function setPaths(array $paths) { + $this->paths = $paths; + return $this; + } + + public function getPaths() { + return $this->paths; + } + + abstract protected function newBlameFuture(DiffusionRequest $request, $path); + + abstract protected function resolveBlameFuture(ExecFuture $future); + + final public static function newFromDiffusionRequest( + DiffusionRequest $request) { + return parent::newQueryObject(__CLASS__, $request); + } + + final protected function executeQuery() { + $paths = $this->getPaths(); + + $blame = array(); + + // Load cache keys: these are the commits at which each path was last + // touched. + $keys = $this->loadCacheKeys($paths); + + // Try to read blame data from cache. + $cache = $this->readCacheData($keys); + foreach ($paths as $key => $path) { + if (!isset($cache[$path])) { + continue; + } + + $blame[$path] = $cache[$path]; + unset($paths[$key]); + } + + // If we have no paths left, we filled everything from cache and can + // bail out early. + if (!$paths) { + return $blame; + } + + $request = $this->getRequest(); + $timeout = $this->getTimeout(); + + // We're still missing at least some data, so we need to run VCS commands + // to pull it. + $futures = array(); + foreach ($paths as $path) { + $future = $this->newBlameFuture($request, $path); + + if ($timeout) { + $future->setTimeout($timeout); + } + + $futures[$path] = $future; + } + + $futures = id(new FutureIterator($futures)) + ->limit(4); + + foreach ($futures as $path => $future) { + $path_blame = $this->resolveBlameFuture($future); + if ($path_blame !== null) { + $blame[$path] = $path_blame; + } + } + + // Fill the cache with anything we generated. + $this->writeCacheData( + array_select_keys($keys, $paths), + $blame); + + return $blame; + } + + private function loadCacheKeys(array $paths) { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $repository = $request->getRepository(); + $repository_id = $repository->getID(); + + $last_modified = parent::callConduitWithDiffusionRequest( + $viewer, + $request, + 'diffusion.lastmodifiedquery', + array( + 'paths' => array_fill_keys($paths, $request->getCommit()), + )); + + $map = array(); + foreach ($paths as $path) { + $identifier = idx($last_modified, $path); + if ($identifier === null) { + continue; + } + + $path_hash = PhabricatorHash::digestForIndex($path); + + $map[$path] = "blame({$repository_id}, {$identifier}, {$path_hash}, raw)"; + } + + return $map; + } + + private function readCacheData(array $keys) { + $cache = PhabricatorCaches::getImmutableCache(); + $data = $cache->getKeys($keys); + + $results = array(); + foreach ($keys as $path => $key) { + if (!isset($data[$key])) { + continue; + } + $results[$path] = $data[$key]; + } + + // Decode the cache storage format. + foreach ($results as $path => $cache) { + list($head, $body) = explode("\n", $cache, 2); + switch ($head) { + case 'raw': + $body = explode("\n", $body); + break; + default: + $body = null; + break; + } + + if ($body === null) { + unset($results[$path]); + } else { + $results[$path] = $body; + } + } + + return $results; + } + + private function writeCacheData(array $keys, array $blame) { + $writes = array(); + foreach ($keys as $path => $key) { + $value = idx($blame, $path); + if ($value === null) { + continue; + } + + // For now, just store the entire value with a "raw" header. In the + // future, we could compress this or use IDs instead. + $value = "raw\n".implode("\n", $value); + + $writes[$key] = $value; + } + + if (!$writes) { + return; + } + + $cache = PhabricatorCaches::getImmutableCache(); + $data = $cache->setKeys($writes, phutil_units('14 days in seconds')); + } + +} diff --git a/src/applications/diffusion/query/blame/DiffusionGitBlameQuery.php b/src/applications/diffusion/query/blame/DiffusionGitBlameQuery.php new file mode 100644 index 0000000000..f98cc645a7 --- /dev/null +++ b/src/applications/diffusion/query/blame/DiffusionGitBlameQuery.php @@ -0,0 +1,34 @@ +getRepository(); + + $commit = $request->getCommit(); + + return $repository->getLocalCommandFuture( + '--no-pager blame -s -l %s -- %s', + $commit, + $path); + } + + protected function resolveBlameFuture(ExecFuture $future) { + list($err, $stdout) = $future->resolve(); + + if ($err) { + return null; + } + + $result = array(); + + $lines = phutil_split_lines($stdout); + foreach ($lines as $line) { + list($commit) = explode(' ', $line, 2); + $result[] = $commit; + } + + return $result; + } + +} diff --git a/src/applications/diffusion/query/blame/DiffusionMercurialBlameQuery.php b/src/applications/diffusion/query/blame/DiffusionMercurialBlameQuery.php new file mode 100644 index 0000000000..60eeb31c4f --- /dev/null +++ b/src/applications/diffusion/query/blame/DiffusionMercurialBlameQuery.php @@ -0,0 +1,36 @@ +getRepository(); + $commit = $request->getCommit(); + + // NOTE: We're using "--debug" to make "--changeset" give us the full + // commit hashes. + + return $repository->getLocalCommandFuture( + 'annotate --debug --changeset --rev %s -- %s', + $commit, + $path); + } + + protected function resolveBlameFuture(ExecFuture $future) { + list($err, $stdout) = $future->resolve(); + + if ($err) { + return null; + } + + $result = array(); + + $lines = phutil_split_lines($stdout); + foreach ($lines as $line) { + list($commit) = explode(':', $line, 2); + $result[] = $commit; + } + + return $result; + } + +} diff --git a/src/applications/diffusion/query/blame/DiffusionSvnBlameQuery.php b/src/applications/diffusion/query/blame/DiffusionSvnBlameQuery.php new file mode 100644 index 0000000000..afa99e2bb3 --- /dev/null +++ b/src/applications/diffusion/query/blame/DiffusionSvnBlameQuery.php @@ -0,0 +1,36 @@ +getRepository(); + $commit = $request->getCommit(); + + return $repository->getRemoteCommandFuture( + 'blame --force %s', + $repository->getSubversionPathURI($path, $commit)); + } + + protected function resolveBlameFuture(ExecFuture $future) { + list($err, $stdout) = $future->resolve(); + + if ($err) { + return null; + } + + $result = array(); + $matches = null; + + $lines = phutil_split_lines($stdout); + foreach ($lines as $line) { + if (preg_match('/^\s*(\d+)/', $line, $matches)) { + $result[] = (int)$matches[1]; + } else { + $result[] = null; + } + } + + return $result; + } + +} diff --git a/src/applications/diffusion/query/filecontent/DiffusionFileContentQuery.php b/src/applications/diffusion/query/filecontent/DiffusionFileContentQuery.php index 9664d98f07..3ea0e12ba1 100644 --- a/src/applications/diffusion/query/filecontent/DiffusionFileContentQuery.php +++ b/src/applications/diffusion/query/filecontent/DiffusionFileContentQuery.php @@ -1,19 +1,13 @@ timeout = $timeout; return $this; @@ -37,163 +31,46 @@ abstract class DiffusionFileContentQuery extends DiffusionQuery { return parent::newQueryObject(__CLASS__, $request); } - abstract public function getFileContentFuture(); - abstract protected function executeQueryFromFuture(Future $future); + final public function getExceededByteLimit() { + return $this->didHitByteLimit; + } - final public function loadFileContentFromFuture(Future $future) { + final public function getExceededTimeLimit() { + return $this->didHitTimeLimit; + } - if ($this->timeout) { - $future->setTimeout($this->timeout); + abstract protected function getFileContentFuture(); + abstract protected function resolveFileContentFuture(Future $future); + + final protected function executeQuery() { + $future = $this->getFileContentFuture(); + + if ($this->getTimeout()) { + $future->setTimeout($this->getTimeout()); } - if ($this->getByteLimit()) { - $future->setStdoutSizeLimit($this->getByteLimit()); + $byte_limit = $this->getByteLimit(); + if ($byte_limit) { + $future->setStdoutSizeLimit($byte_limit + 1); } try { - $file_content = $this->executeQueryFromFuture($future); + $file_content = $this->resolveFileContentFuture($future); } catch (CommandException $ex) { if (!$future->getWasKilledByTimeout()) { throw $ex; } - $message = pht( - '', - $this->timeout); - - $file_content = new DiffusionFileContent(); - $file_content->setCorpus($message); + $this->didHitTimeLimit = true; + $file_content = null; } - $this->fileContent = $file_content; - - $repository = $this->getRequest()->getRepository(); - $try_encoding = $repository->getDetail('encoding'); - if ($try_encoding) { - $this->fileContent->setCorpus( - phutil_utf8_convert( - $this->fileContent->getCorpus(), 'UTF-8', $try_encoding)); + if ($byte_limit && (strlen($file_content) > $byte_limit)) { + $this->didHitByteLimit = true; + $file_content = null; } - return $this->fileContent; + return $file_content; } - final protected function executeQuery() { - return $this->loadFileContentFromFuture($this->getFileContentFuture()); - } - - final public function loadFileContent() { - return $this->executeQuery(); - } - - final public function getRawData() { - return $this->fileContent->getCorpus(); - } - - /** - * Pretty hairy function. If getNeedsBlame is false, this returns - * - * ($text_list, array(), array()) - * - * Where $text_list is the raw file content with trailing new lines stripped. - * - * If getNeedsBlame is true, this returns - * - * ($text_list, $line_rev_dict, $blame_dict) - * - * Where $text_list is just the lines of code -- the raw file content will - * contain lots of blame data, $line_rev_dict is a dictionary of line number - * => revision id, and $blame_dict is another complicated data structure. - * In detail, $blame_dict contains [revision id][author] keys, as well - * as [commit id][authorPhid] and [commit id][epoch] keys. - * - * @return ($text_list, $line_rev_dict, $blame_dict) - */ - final public function getBlameData() { - $raw_data = preg_replace('/\n$/', '', $this->getRawData()); - - $text_list = array(); - $line_rev_dict = array(); - $blame_dict = array(); - - if (!$this->getNeedsBlame()) { - $text_list = explode("\n", $raw_data); - } else if ($raw_data != '') { - $lines = array(); - foreach (explode("\n", $raw_data) as $k => $line) { - $lines[$k] = $this->tokenizeLine($line); - - list($rev_id, $author, $text) = $lines[$k]; - $text_list[$k] = $text; - $line_rev_dict[$k] = $rev_id; - } - - $line_rev_dict = $this->processRevList($line_rev_dict); - - foreach ($lines as $k => $line) { - list($rev_id, $author, $text) = $line; - $rev_id = $line_rev_dict[$k]; - - if (!isset($blame_dict[$rev_id])) { - $blame_dict[$rev_id]['author'] = $author; - } - } - - $repository = $this->getRequest()->getRepository(); - - $commits = id(new DiffusionCommitQuery()) - ->setViewer($this->getViewer()) - ->withDefaultRepository($repository) - ->withIdentifiers(array_unique($line_rev_dict)) - ->execute(); - - foreach ($commits as $commit) { - $blame_dict[$commit->getCommitIdentifier()]['epoch'] = - $commit->getEpoch(); - } - - if ($commits) { - $commits_data = id(new PhabricatorRepositoryCommitData())->loadAllWhere( - 'commitID IN (%Ls)', - mpull($commits, 'getID')); - - foreach ($commits_data as $data) { - $author_phid = $data->getCommitDetail('authorPHID'); - if (!$author_phid) { - continue; - } - $commit = $commits[$data->getCommitID()]; - $commit_identifier = $commit->getCommitIdentifier(); - $blame_dict[$commit_identifier]['authorPHID'] = $author_phid; - } - } - - } - - return array($text_list, $line_rev_dict, $blame_dict); - } - - abstract protected function tokenizeLine($line); - - public function setNeedsBlame($needs_blame) { - $this->needsBlame = $needs_blame; - return $this; - } - - public function getNeedsBlame() { - return $this->needsBlame; - } - - public function setViewer(PhabricatorUser $user) { - $this->viewer = $user; - return $this; - } - - public function getViewer() { - return $this->viewer; - } - - protected function processRevList(array $rev_list) { - return $rev_list; - } } diff --git a/src/applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php b/src/applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php index 67c6369940..a383aee922 100644 --- a/src/applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php +++ b/src/applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php @@ -2,57 +2,22 @@ final class DiffusionGitFileContentQuery extends DiffusionFileContentQuery { - public function getFileContentFuture() { + protected function getFileContentFuture() { $drequest = $this->getRequest(); $repository = $drequest->getRepository(); $path = $drequest->getPath(); $commit = $drequest->getCommit(); - if ($this->getNeedsBlame()) { - return $repository->getLocalCommandFuture( - '--no-pager blame -c -l --date=short %s -- %s', - $commit, - $path); - } else { - return $repository->getLocalCommandFuture( - 'cat-file blob %s:%s', - $commit, - $path); - } + return $repository->getLocalCommandFuture( + 'cat-file blob %s:%s', + $commit, + $path); } - protected function executeQueryFromFuture(Future $future) { + protected function resolveFileContentFuture(Future $future) { list($corpus) = $future->resolvex(); - - $file_content = new DiffusionFileContent(); - $file_content->setCorpus($corpus); - - return $file_content; - } - - protected function tokenizeLine($line) { - return self::match($line); - } - - public static function match($line) { - $m = array(); - // sample lines: - // - // d1b4fcdd2a7c8c0f8cbdd01ca839d992135424dc - // ( hzhao 2009-05-01 202)function print(); - // - // 8220d5d54f6d5d5552a636576cbe9c35f15b65b2 - // (Andrew Gallagher 2010-12-03 324) - // // Add the lines for trailing context - preg_match( - '/^\s*?(\S+?)\s*\(\s*(.*?)\s+\d{4}-\d{2}-\d{2}\s+\d+\)(.*)?$/', - $line, - $m); - $rev_id = $m[1]; - $author = $m[2]; - $text = idx($m, 3); - return array($rev_id, $author, $text); + return $corpus; } } diff --git a/src/applications/diffusion/query/filecontent/DiffusionMercurialFileContentQuery.php b/src/applications/diffusion/query/filecontent/DiffusionMercurialFileContentQuery.php index c97b53e75a..539f5752c2 100644 --- a/src/applications/diffusion/query/filecontent/DiffusionMercurialFileContentQuery.php +++ b/src/applications/diffusion/query/filecontent/DiffusionMercurialFileContentQuery.php @@ -3,76 +3,22 @@ final class DiffusionMercurialFileContentQuery extends DiffusionFileContentQuery { - public function getFileContentFuture() { + protected function getFileContentFuture() { $drequest = $this->getRequest(); $repository = $drequest->getRepository(); $path = $drequest->getPath(); $commit = $drequest->getCommit(); - if ($this->getNeedsBlame()) { - // NOTE: We're using "--number" instead of "--changeset" because there is - // no way to get "--changeset" to show us the full commit hashes. - return $repository->getLocalCommandFuture( - 'annotate --user --number --rev %s -- %s', - $commit, - $path); - } else { - return $repository->getLocalCommandFuture( - 'cat --rev %s -- %s', - $commit, - $path); - } + return $repository->getLocalCommandFuture( + 'cat --rev %s -- %s', + $commit, + $path); } - protected function executeQueryFromFuture(Future $future) { + protected function resolveFileContentFuture(Future $future) { list($corpus) = $future->resolvex(); - - $file_content = new DiffusionFileContent(); - $file_content->setCorpus($corpus); - - return $file_content; - } - - protected function tokenizeLine($line) { - $matches = null; - - preg_match( - '/^(.*?)\s+([0-9]+): (.*)$/', - $line, - $matches); - - return array($matches[2], $matches[1], $matches[3]); - } - - /** - * Convert local revision IDs into full commit identifier hashes. - */ - protected function processRevList(array $rev_list) { - $drequest = $this->getRequest(); - $repository = $drequest->getRepository(); - - $revs = array_unique($rev_list); - foreach ($revs as $key => $rev) { - $revs[$key] = '--rev '.(int)$rev; - } - - list($stdout) = $repository->execxLocalCommand( - 'log --template=%s %C', - '{rev} {node}\\n', - implode(' ', $revs)); - - $map = array(); - foreach (explode("\n", trim($stdout)) as $line) { - list($rev, $node) = explode(' ', $line); - $map[$rev] = $node; - } - - foreach ($rev_list as $k => $rev) { - $rev_list[$k] = $map[$rev]; - } - - return $rev_list; + return $corpus; } } diff --git a/src/applications/diffusion/query/filecontent/DiffusionSvnFileContentQuery.php b/src/applications/diffusion/query/filecontent/DiffusionSvnFileContentQuery.php index be7487969b..0f6f30b355 100644 --- a/src/applications/diffusion/query/filecontent/DiffusionSvnFileContentQuery.php +++ b/src/applications/diffusion/query/filecontent/DiffusionSvnFileContentQuery.php @@ -2,7 +2,7 @@ final class DiffusionSvnFileContentQuery extends DiffusionFileContentQuery { - public function getFileContentFuture() { + protected function getFileContentFuture() { $drequest = $this->getRequest(); $repository = $drequest->getRepository(); @@ -10,47 +10,13 @@ final class DiffusionSvnFileContentQuery extends DiffusionFileContentQuery { $commit = $drequest->getCommit(); return $repository->getRemoteCommandFuture( - '%C %s', - $this->getNeedsBlame() ? 'blame --force' : 'cat', + 'cat %s', $repository->getSubversionPathURI($path, $commit)); } - protected function executeQueryFromFuture(Future $future) { - try { - list($corpus) = $future->resolvex(); - } catch (CommandException $ex) { - $stderr = $ex->getStdErr(); - if (preg_match('/path not found$/', trim($stderr))) { - // TODO: Improve user experience for this. One way to end up here - // is to have the parser behind and look at a file which was recently - // nuked; Diffusion will think it still exists and try to grab content - // at HEAD. - throw new Exception( - pht( - 'Failed to retrieve file content from Subversion. The file may '. - 'have been recently deleted, or the Diffusion cache may be out of '. - 'date.')); - } else { - throw $ex; - } - } - - $file_content = new DiffusionFileContent(); - $file_content->setCorpus($corpus); - - return $file_content; - } - - protected function tokenizeLine($line) { - // sample line: - // 347498 yliu function print(); - $m = array(); - preg_match('/^\s*(\d+)\s+(\S+)(?: (.*))?$/', $line, $m); - $rev_id = $m[1]; - $author = $m[2]; - $text = idx($m, 3); - - return array($rev_id, $author, $text); + protected function resolveFileContentFuture(Future $future) { + list($corpus) = $future->resolvex(); + return $corpus; } } diff --git a/src/applications/diffusion/request/DiffusionRequest.php b/src/applications/diffusion/request/DiffusionRequest.php index 6cf9aba8b3..8d326b81bc 100644 --- a/src/applications/diffusion/request/DiffusionRequest.php +++ b/src/applications/diffusion/request/DiffusionRequest.php @@ -9,7 +9,6 @@ */ abstract class DiffusionRequest extends Phobject { - protected $callsign; protected $path; protected $line; protected $branch; @@ -47,9 +46,8 @@ abstract class DiffusionRequest extends Phobject { * * Parameters are: * - * - `callsign` Repository callsign. Provide this or `repository`. - * - `user` Viewing user. Required if `callsign` is provided. - * - `repository` Repository object. Provide this or `callsign`. + * - `repository` Repository object or identifier. + * - `user` Viewing user. Required if `repository` is an identifier. * - `branch` Optional, branch name. * - `path` Optional, file path. * - `commit` Optional, commit identifier. @@ -60,30 +58,58 @@ abstract class DiffusionRequest extends Phobject { * @task new */ final public static function newFromDictionary(array $data) { - if (isset($data['repository']) && isset($data['callsign'])) { + $repository_key = 'repository'; + $identifier_key = 'callsign'; + $viewer_key = 'user'; + + $repository = idx($data, $repository_key); + $identifier = idx($data, $identifier_key); + + $have_repository = ($repository !== null); + $have_identifier = ($identifier !== null); + + if ($have_repository && $have_identifier) { throw new Exception( pht( - "Specify '%s' or '%s', but not both.", - 'repository', - 'callsign')); - } else if (!isset($data['repository']) && !isset($data['callsign'])) { - throw new Exception( - pht( - "One of '%s' and '%s' is required.", - 'repository', - 'callsign')); - } else if (isset($data['callsign']) && empty($data['user'])) { - throw new Exception( - pht( - "Parameter '%s' is required if '%s' is provided.", - 'user', - 'callsign')); + 'Specify "%s" or "%s", but not both.', + $repository_key, + $identifier_key)); } - if (isset($data['repository'])) { - $object = self::newFromRepository($data['repository']); + if (!$have_repository && !$have_identifier) { + throw new Exception( + pht( + 'One of "%s" and "%s" is required.', + $repository_key, + $identifier_key)); + } + + if ($have_repository) { + if (!($repository instanceof PhabricatorRepository)) { + if (empty($data[$viewer_key])) { + throw new Exception( + pht( + 'Parameter "%s" is required if "%s" is provided.', + $viewer_key, + $identifier_key)); + } + + $identifier = $repository; + $repository = null; + } + } + + if ($identifier !== null) { + $object = self::newFromIdentifier( + $identifier, + $data[$viewer_key], + idx($data, 'edit')); } else { - $object = self::newFromCallsign($data['callsign'], $data['user']); + $object = self::newFromRepository($repository); + } + + if (!$object) { + return null; } $object->initializeFromDictionary($data); @@ -91,43 +117,6 @@ abstract class DiffusionRequest extends Phobject { return $object; } - - /** - * Create a new request from an Aphront request dictionary. This is an - * internal method that you generally should not call directly; instead, - * call @{method:newFromDictionary}. - * - * @param map Map of Aphront request data. - * @return DiffusionRequest New request object. - * @task new - */ - final public static function newFromAphrontRequestDictionary( - array $data, - AphrontRequest $request) { - - $callsign = phutil_unescape_uri_path_component(idx($data, 'callsign')); - $object = self::newFromCallsign($callsign, $request->getUser()); - - $use_branches = $object->supportsBranches(); - - if (isset($data['dblob'])) { - $parsed = self::parseRequestBlob(idx($data, 'dblob'), $use_branches); - } else { - $parsed = array( - 'commit' => idx($data, 'commit'), - 'path' => idx($data, 'path'), - 'line' => idx($data, 'line'), - 'branch' => idx($data, 'branch'), - ); - } - - $object->setUser($request->getUser()); - $object->initializeFromDictionary($parsed); - $object->lint = $request->getStr('lint'); - return $object; - } - - /** * Internal. * @@ -141,22 +130,32 @@ abstract class DiffusionRequest extends Phobject { /** * Internal. Use @{method:newFromDictionary}, not this method. * - * @param string Repository callsign. + * @param string Repository identifier. * @param PhabricatorUser Viewing user. * @return DiffusionRequest New request object. * @task new */ - final private static function newFromCallsign( - $callsign, - PhabricatorUser $viewer) { + final private static function newFromIdentifier( + $identifier, + PhabricatorUser $viewer, + $need_edit = false) { - $repository = id(new PhabricatorRepositoryQuery()) + $query = id(new PhabricatorRepositoryQuery()) ->setViewer($viewer) - ->withCallsigns(array($callsign)) - ->executeOne(); + ->withIdentifiers(array($identifier)); + + if ($need_edit) { + $query->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )); + } + + $repository = $query->executeOne(); if (!$repository) { - throw new Exception(pht("No such repository '%s'.", $callsign)); + return null; } return self::newFromRepository($repository); @@ -189,7 +188,6 @@ abstract class DiffusionRequest extends Phobject { $object = new $class(); $object->repository = $repository; - $object->callsign = $repository->getCallsign(); return $object; } @@ -203,9 +201,16 @@ abstract class DiffusionRequest extends Phobject { * @task new */ final private function initializeFromDictionary(array $data) { - $this->path = idx($data, 'path'); - $this->line = idx($data, 'line'); + $blob = idx($data, 'blob'); + if (strlen($blob)) { + $blob = self::parseRequestBlob($blob, $this->supportsBranches()); + $data = $blob + $data; + } + + $this->path = idx($data, 'path'); + $this->line = idx($data, 'line'); $this->initFromConduit = idx($data, 'initFromConduit', true); + $this->lint = idx($data, 'lint'); $this->symbolicCommit = idx($data, 'commit'); if ($this->supportsBranches()) { @@ -239,7 +244,7 @@ abstract class DiffusionRequest extends Phobject { } public function getCallsign() { - return $this->callsign; + return $this->getRepository()->getCallsign(); } public function setPath($path) { @@ -430,15 +435,6 @@ abstract class DiffusionRequest extends Phobject { /* -( Managing Diffusion URIs )-------------------------------------------- */ - /** - * Generate a Diffusion URI using this request to provide defaults. See - * @{method:generateDiffusionURI} for details. This method is the same, but - * preserves the request parameters if they are not overridden. - * - * @param map See @{method:generateDiffusionURI}. - * @return PhutilURI Generated URI. - * @task uri - */ public function generateURI(array $params) { if (empty($params['stable'])) { $default_commit = $this->getSymbolicCommit(); @@ -447,173 +443,21 @@ abstract class DiffusionRequest extends Phobject { } $defaults = array( - 'callsign' => $this->getCallsign(), 'path' => $this->getPath(), 'branch' => $this->getBranch(), 'commit' => $default_commit, 'lint' => idx($params, 'lint', $this->getLint()), ); + foreach ($defaults as $key => $val) { if (!isset($params[$key])) { // Overwrite NULL. $params[$key] = $val; } } - return self::generateDiffusionURI($params); + + return $this->getRepository()->generateURI($params); } - - /** - * Generate a Diffusion URI from a parameter map. Applies the correct encoding - * and formatting to the URI. Parameters are: - * - * - `action` One of `history`, `browse`, `change`, `lastmodified`, - * `branch`, `tags`, `branches`, or `revision-ref`. The action specified - * by the URI. - * - `callsign` Repository callsign. - * - `branch` Optional if action is not `branch`, branch name. - * - `path` Optional, path to file. - * - `commit` Optional, commit identifier. - * - `line` Optional, line range. - * - `lint` Optional, lint code. - * - `params` Optional, query parameters. - * - * The function generates the specified URI and returns it. - * - * @param map See documentation. - * @return PhutilURI Generated URI. - * @task uri - */ - public static function generateDiffusionURI(array $params) { - $action = idx($params, 'action'); - - $callsign = idx($params, 'callsign'); - $path = idx($params, 'path'); - $branch = idx($params, 'branch'); - $commit = idx($params, 'commit'); - $line = idx($params, 'line'); - - if (strlen($callsign)) { - $callsign = phutil_escape_uri_path_component($callsign).'/'; - } - - if (strlen($branch)) { - $branch = phutil_escape_uri_path_component($branch).'/'; - } - - if (strlen($path)) { - $path = ltrim($path, '/'); - $path = str_replace(array(';', '$'), array(';;', '$$'), $path); - $path = phutil_escape_uri($path); - } - - $path = "{$branch}{$path}"; - - if (strlen($commit)) { - $commit = str_replace('$', '$$', $commit); - $commit = ';'.phutil_escape_uri($commit); - } - - if (strlen($line)) { - $line = '$'.phutil_escape_uri($line); - } - - $req_callsign = false; - $req_branch = false; - $req_commit = false; - - switch ($action) { - case 'history': - case 'browse': - case 'change': - case 'lastmodified': - case 'tags': - case 'branches': - case 'lint': - case 'refs': - $req_callsign = true; - break; - case 'branch': - $req_callsign = true; - $req_branch = true; - break; - case 'commit': - $req_callsign = true; - $req_commit = true; - break; - } - - if ($req_callsign && !strlen($callsign)) { - throw new Exception( - pht( - "Diffusion URI action '%s' requires callsign!", - $action)); - } - - if ($req_commit && !strlen($commit)) { - throw new Exception( - pht( - "Diffusion URI action '%s' requires commit!", - $action)); - } - - switch ($action) { - case 'change': - case 'history': - case 'browse': - case 'lastmodified': - case 'tags': - case 'branches': - case 'lint': - case 'pathtree': - case 'refs': - $uri = "/diffusion/{$callsign}{$action}/{$path}{$commit}{$line}"; - break; - case 'branch': - if (strlen($path)) { - $uri = "/diffusion/{$callsign}repository/{$path}"; - } else { - $uri = "/diffusion/{$callsign}"; - } - break; - case 'external': - $commit = ltrim($commit, ';'); - $uri = "/diffusion/external/{$commit}/"; - break; - case 'rendering-ref': - // This isn't a real URI per se, it's passed as a query parameter to - // the ajax changeset stuff but then we parse it back out as though - // it came from a URI. - $uri = rawurldecode("{$path}{$commit}"); - break; - case 'commit': - $commit = ltrim($commit, ';'); - $callsign = rtrim($callsign, '/'); - $uri = "/r{$callsign}{$commit}"; - break; - default: - throw new Exception(pht("Unknown Diffusion URI action '%s'!", $action)); - } - - if ($action == 'rendering-ref') { - return $uri; - } - - $uri = new PhutilURI($uri); - - if (isset($params['lint'])) { - $params['params'] = idx($params, 'params', array()) + array( - 'lint' => $params['lint'], - ); - } - - if (idx($params, 'params')) { - $uri->setQueryParams($params['params']); - } - - return $uri; - } - - /** * Internal. Public only for unit tests. * @@ -702,28 +546,26 @@ abstract class DiffusionRequest extends Phobject { protected function raisePermissionException() { $host = php_uname('n'); - $callsign = $this->getRepository()->getCallsign(); throw new DiffusionSetupException( pht( - "The clone of this repository ('%s') on the local machine ('%s') ". - "could not be read. Ensure that the repository is in a ". - "location where the web server has read permissions.", - $callsign, + 'The clone of this repository ("%s") on the local machine ("%s") '. + 'could not be read. Ensure that the repository is in a '. + 'location where the web server has read permissions.', + $this->getRepository()->getDisplayName(), $host)); } protected function raiseCloneException() { $host = php_uname('n'); - $callsign = $this->getRepository()->getCallsign(); throw new DiffusionSetupException( pht( - "The working copy for this repository ('%s') hasn't been cloned yet ". - "on this machine ('%s'). Make sure you've started the Phabricator ". - "daemons. If this problem persists for longer than a clone should ". - "take, check the daemon logs (in the Daemon Console) to see if there ". - "were errors cloning the repository. Consult the 'Diffusion User ". - "Guide' in the documentation for help setting up repositories.", - $callsign, + 'The working copy for this repository ("%s") has not been cloned yet '. + 'on this machine ("%s"). Make sure you havestarted the Phabricator '. + 'daemons. If this problem persists for longer than a clone should '. + 'take, check the daemon logs (in the Daemon Console) to see if there '. + 'were errors cloning the repository. Consult the "Diffusion User '. + 'Guide" in the documentation for help setting up repositories.', + $this->getRepository()->getDisplayName(), $host)); } diff --git a/src/applications/diffusion/request/__tests__/DiffusionURITestCase.php b/src/applications/diffusion/request/__tests__/DiffusionURITestCase.php index 43a2bd3404..b7899de9cd 100644 --- a/src/applications/diffusion/request/__tests__/DiffusionURITestCase.php +++ b/src/applications/diffusion/request/__tests__/DiffusionURITestCase.php @@ -86,10 +86,15 @@ final class DiffusionURITestCase extends PhutilTestCase { } public function testURIGeneration() { + $actor = PhabricatorUser::getOmnipotentUser(); + + $repository = PhabricatorRepository::initializeNewRepository($actor) + ->setCallsign('A') + ->makeEphemeral(); + $map = array( '/diffusion/A/browse/branch/path.ext;abc$1' => array( 'action' => 'browse', - 'callsign' => 'A', 'branch' => 'branch', 'path' => 'path.ext', 'commit' => 'abc', @@ -97,24 +102,20 @@ final class DiffusionURITestCase extends PhutilTestCase { ), '/diffusion/A/browse/a%252Fb/path.ext' => array( 'action' => 'browse', - 'callsign' => 'A', 'branch' => 'a/b', 'path' => 'path.ext', ), '/diffusion/A/browse/%2B/%20%21' => array( 'action' => 'browse', - 'callsign' => 'A', 'path' => '+/ !', ), '/diffusion/A/browse/money/%24%24100$2' => array( 'action' => 'browse', - 'callsign' => 'A', 'path' => 'money/$100', 'line' => '2', ), '/diffusion/A/browse/path/to/file.ext?view=things' => array( 'action' => 'browse', - 'callsign' => 'A', 'path' => 'path/to/file.ext', 'params' => array( 'view' => 'things', @@ -122,7 +123,6 @@ final class DiffusionURITestCase extends PhutilTestCase { ), '/diffusion/A/repository/master/' => array( 'action' => 'branch', - 'callsign' => 'A', 'branch' => 'master', ), 'path/to/file.ext;abc' => array( @@ -132,7 +132,6 @@ final class DiffusionURITestCase extends PhutilTestCase { ), '/diffusion/A/browse/branch/path.ext$3-5%2C7-12%2C14' => array( 'action' => 'browse', - 'callsign' => 'A', 'branch' => 'branch', 'path' => 'path.ext', 'line' => '3-5,7-12,14', @@ -140,10 +139,8 @@ final class DiffusionURITestCase extends PhutilTestCase { ); foreach ($map as $expect => $input) { - $actual = DiffusionRequest::generateDiffusionURI($input); - $this->assertEqual( - $expect, - (string)$actual); + $actual = $repository->generateURI($input); + $this->assertEqual($expect, (string)$actual); } } diff --git a/src/applications/diffusion/typeahead/DiffusionRepositoryDatasource.php b/src/applications/diffusion/typeahead/DiffusionRepositoryDatasource.php index 4c2b8e1fb0..06d4ff0be9 100644 --- a/src/applications/diffusion/typeahead/DiffusionRepositoryDatasource.php +++ b/src/applications/diffusion/typeahead/DiffusionRepositoryDatasource.php @@ -28,7 +28,7 @@ final class DiffusionRepositoryDatasource foreach ($repos as $repo) { $results[] = id(new PhabricatorTypeaheadResult()) ->setName($repo->getMonogram().' '.$repo->getName()) - ->setURI('/diffusion/'.$repo->getCallsign().'/') + ->setURI($repo->getURI()) ->setPHID($repo->getPHID()) ->setPriorityString($repo->getMonogram()); } diff --git a/src/applications/diffusion/view/DiffusionHistoryTableView.php b/src/applications/diffusion/view/DiffusionHistoryTableView.php index 314bfb435c..e62c4382c9 100644 --- a/src/applications/diffusion/view/DiffusionHistoryTableView.php +++ b/src/applications/diffusion/view/DiffusionHistoryTableView.php @@ -6,6 +6,7 @@ final class DiffusionHistoryTableView extends DiffusionView { private $revisions = array(); private $handles = array(); private $isHead; + private $isTail; private $parents; public function setHistory(array $history) { @@ -60,6 +61,11 @@ final class DiffusionHistoryTableView extends DiffusionView { return $this; } + public function setIsTail($is_tail) { + $this->isTail = $is_tail; + return $this; + } + public function render() { $drequest = $this->getDiffusionRequest(); @@ -344,6 +350,16 @@ final class DiffusionHistoryTableView extends DiffusionView { ); } + // If this is the last page in history, replace the "o" with an "x" so we + // do not draw a connecting line downward, and replace "^" with an "X" for + // repositories with exactly one commit. + if ($this->isTail && $graph) { + $last = array_pop($graph); + $last['line'] = str_replace('o', 'x', $last['line']); + $last['line'] = str_replace('^', 'X', $last['line']); + $graph[] = $last; + } + // Render into tags for the behavior. foreach ($graph as $k => $meta) { diff --git a/src/applications/diffusion/view/DiffusionPushLogListView.php b/src/applications/diffusion/view/DiffusionPushLogListView.php index acb1b60131..860320e624 100644 --- a/src/applications/diffusion/view/DiffusionPushLogListView.php +++ b/src/applications/diffusion/view/DiffusionPushLogListView.php @@ -39,6 +39,7 @@ final class DiffusionPushLogListView extends AphrontView { $rows = array(); foreach ($logs as $log) { + $repository = $log->getRepository(); // Reveal this if it's valid and the user can edit the repository. $remote_addr = '-'; @@ -51,16 +52,16 @@ final class DiffusionPushLogListView extends AphrontView { $event_id = $log->getPushEvent()->getID(); - $callsign = $log->getRepository()->getCallsign(); $old_ref_link = null; if ($log->getRefOld() != DiffusionCommitHookEngine::EMPTY_HASH) { $old_ref_link = phutil_tag( 'a', array( - 'href' => '/r'.$callsign.$log->getRefOld(), + 'href' => $repository->getCommitURI($log->getRefOld()), ), $log->getRefOldShort()); } + $rows[] = array( phutil_tag( 'a', @@ -71,9 +72,9 @@ final class DiffusionPushLogListView extends AphrontView { phutil_tag( 'a', array( - 'href' => '/diffusion/'.$callsign.'/', + 'href' => $repository->getURI(), ), - $callsign), + $repository->getDisplayName()), $handles[$log->getPusherPHID()]->renderLink(), $remote_addr, $log->getPushEvent()->getRemoteProtocol(), @@ -83,7 +84,7 @@ final class DiffusionPushLogListView extends AphrontView { phutil_tag( 'a', array( - 'href' => '/r'.$callsign.$log->getRefNew(), + 'href' => $repository->getCommitURI($log->getRefNew()), ), $log->getRefNewShort()), diff --git a/src/applications/diffusion/view/DiffusionView.php b/src/applications/diffusion/view/DiffusionView.php index 83fdc1f7e5..b540dc3a4e 100644 --- a/src/applications/diffusion/view/DiffusionView.php +++ b/src/applications/diffusion/view/DiffusionView.php @@ -123,31 +123,12 @@ abstract class DiffusionView extends AphrontView { )); } - final public static function nameCommit( - PhabricatorRepository $repository, - $commit) { - - switch ($repository->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - $commit_name = substr($commit, 0, 12); - break; - default: - $commit_name = $commit; - break; - } - - $callsign = $repository->getCallsign(); - return "r{$callsign}{$commit_name}"; - } - final public static function linkCommit( PhabricatorRepository $repository, $commit, $summary = '') { - $commit_name = self::nameCommit($repository, $commit); - $callsign = $repository->getCallsign(); + $commit_name = $repository->formatCommitName($commit); if (strlen($summary)) { $commit_name .= ': '.$summary; @@ -156,7 +137,7 @@ abstract class DiffusionView extends AphrontView { return phutil_tag( 'a', array( - 'href' => "/r{$callsign}{$commit}", + 'href' => $repository->getCommitURI($commit), ), $commit_name); } diff --git a/src/applications/drydock/application/PhabricatorDrydockApplication.php b/src/applications/drydock/application/PhabricatorDrydockApplication.php index 0b109e68da..6267c26f1e 100644 --- a/src/applications/drydock/application/PhabricatorDrydockApplication.php +++ b/src/applications/drydock/application/PhabricatorDrydockApplication.php @@ -96,6 +96,7 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication { '(?P[1-9]\d*)/' => array( '' => 'DrydockRepositoryOperationViewController', 'status/' => 'DrydockRepositoryOperationStatusController', + 'dismiss/' => 'DrydockRepositoryOperationDismissController', ), ), ), diff --git a/src/applications/drydock/controller/DrydockRepositoryOperationDismissController.php b/src/applications/drydock/controller/DrydockRepositoryOperationDismissController.php new file mode 100644 index 0000000000..28e14bcb90 --- /dev/null +++ b/src/applications/drydock/controller/DrydockRepositoryOperationDismissController.php @@ -0,0 +1,56 @@ +getViewer(); + $id = $request->getURIData('id'); + + $operation = id(new DrydockRepositoryOperationQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$operation) { + return new Aphront404Response(); + } + + $object_phid = $operation->getObjectPHID(); + $handles = $viewer->loadHandles(array($object_phid)); + $done_uri = $handles[$object_phid]->getURI(); + + if ($operation->getIsDismissed()) { + return $this->newDialog() + ->setTitle(pht('Already Dismissed')) + ->appendParagraph( + pht( + 'This operation has already been dismissed, and can not be '. + 'dismissed any further.')) + ->addCancelButton($done_uri); + } + + + if ($request->isFormPost()) { + $operation + ->setIsDismissed(1) + ->save(); + + return id(new AphrontRedirectResponse())->setURI($done_uri); + } + + return $this->newDialog() + ->setTitle(pht('Dismiss Operation')) + ->appendParagraph( + pht( + 'Dismiss this operation? It will no longer be shown, but logs '. + 'can be found in Drydock.')) + ->addSubmitButton(pht('Dismiss')) + ->addCancelButton($done_uri); + } + +} diff --git a/src/applications/drydock/operation/DrydockLandRepositoryOperation.php b/src/applications/drydock/operation/DrydockLandRepositoryOperation.php index 48a3e8075f..af2f9f271f 100644 --- a/src/applications/drydock/operation/DrydockLandRepositoryOperation.php +++ b/src/applications/drydock/operation/DrydockLandRepositoryOperation.php @@ -235,12 +235,29 @@ final class DrydockLandRepositoryOperation $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; if ($revision->getStatus() != $status_accepted) { - return array( - 'title' => pht('Revision Not Accepted'), - 'body' => pht( - 'This revision is still under review. Only revisions which have '. - 'been accepted may land.'), - ); + switch ($revision->getStatus()) { + case ArcanistDifferentialRevisionStatus::CLOSED: + return array( + 'title' => pht('Revision Closed'), + 'body' => pht( + 'This revision has already been closed. Only open, accepted '. + 'revisions may land.'), + ); + case ArcanistDifferentialRevisionStatus::ABANDONED: + return array( + 'title' => pht('Revision Abandoned'), + 'body' => pht( + 'This revision has been abandoned. Only accepted revisions '. + 'may land.'), + ); + default: + return array( + 'title' => pht('Revision Not Accepted'), + 'body' => pht( + 'This revision is still under review. Only revisions which '. + 'have been accepted may land.'), + ); + } } // Check for other operations. Eventually this should probably be more diff --git a/src/applications/drydock/query/DrydockRepositoryOperationQuery.php b/src/applications/drydock/query/DrydockRepositoryOperationQuery.php index 38e1b4787b..570e68e825 100644 --- a/src/applications/drydock/query/DrydockRepositoryOperationQuery.php +++ b/src/applications/drydock/query/DrydockRepositoryOperationQuery.php @@ -8,6 +8,7 @@ final class DrydockRepositoryOperationQuery extends DrydockQuery { private $repositoryPHIDs; private $operationStates; private $operationTypes; + private $isDismissed; public function withIDs(array $ids) { $this->ids = $ids; @@ -39,6 +40,11 @@ final class DrydockRepositoryOperationQuery extends DrydockQuery { return $this; } + public function withIsDismissed($dismissed) { + $this->isDismissed = $dismissed; + return $this; + } + public function newResultObject() { return new DrydockRepositoryOperation(); } @@ -152,6 +158,13 @@ final class DrydockRepositoryOperationQuery extends DrydockQuery { $this->operationTypes); } + if ($this->isDismissed !== null) { + $where[] = qsprintf( + $conn, + 'isDismissed = %d', + (int)$this->isDismissed); + } + return $where; } diff --git a/src/applications/drydock/storage/DrydockRepositoryOperation.php b/src/applications/drydock/storage/DrydockRepositoryOperation.php index 83e660ffa9..87565edc86 100644 --- a/src/applications/drydock/storage/DrydockRepositoryOperation.php +++ b/src/applications/drydock/storage/DrydockRepositoryOperation.php @@ -20,6 +20,7 @@ final class DrydockRepositoryOperation extends DrydockDAO protected $operationType; protected $operationState; protected $properties = array(); + protected $isDismissed; private $repository = self::ATTACHABLE; private $object = self::ATTACHABLE; @@ -30,7 +31,8 @@ final class DrydockRepositoryOperation extends DrydockDAO return id(new DrydockRepositoryOperation()) ->setOperationState(self::STATE_WAIT) - ->setOperationType($op->getOperationConstant()); + ->setOperationType($op->getOperationConstant()) + ->setIsDismissed(0); } protected function getConfiguration() { @@ -43,6 +45,7 @@ final class DrydockRepositoryOperation extends DrydockDAO 'repositoryTarget' => 'bytes', 'operationType' => 'text32', 'operationState' => 'text32', + 'isDismissed' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( 'key_object' => array( @@ -196,11 +199,17 @@ final class DrydockRepositoryOperation extends DrydockDAO } public function getPolicy($capability) { - return $this->getRepository()->getPolicy($capability); + $need_capability = $this->getRequiredRepositoryCapability($capability); + + return $this->getRepository() + ->getPolicy($need_capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { - return $this->getRepository()->hasAutomaticCapability($capability, $viewer); + $need_capability = $this->getRequiredRepositoryCapability($capability); + + return $this->getRepository() + ->hasAutomaticCapability($need_capability, $viewer); } public function describeAutomaticCapability($capability) { @@ -209,4 +218,17 @@ final class DrydockRepositoryOperation extends DrydockDAO 'affects.'); } + private function getRequiredRepositoryCapability($capability) { + // To edit a RepositoryOperation, require that the user be able to push + // to the repository. + + $map = array( + PhabricatorPolicyCapability::CAN_EDIT => + DiffusionPushCapability::CAPABILITY, + ); + + return idx($map, $capability, $capability); + } + + } diff --git a/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php b/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php index bf1040afe6..07c5a7b706 100644 --- a/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php +++ b/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php @@ -67,6 +67,7 @@ final class DrydockRepositoryOperationStatusView $item = id(new PHUIObjectItemView()) ->setHref("/drydock/operation/{$id}/") + ->setObjectName(pht('Operation %d', $id)) ->setHeader($operation->getOperationDescription($viewer)) ->setStatusIcon($icon, $name); @@ -98,6 +99,16 @@ final class DrydockRepositoryOperationStatusView } else { $item->addAttribute(pht('Operation encountered an error.')); } + + $is_dismissed = $operation->getIsDismissed(); + + $item->addAction( + id(new PHUIListItemView()) + ->setName('Dismiss') + ->setIcon('fa-times') + ->setDisabled($is_dismissed) + ->setWorkflow(true) + ->setHref("/drydock/operation/{$id}/dismiss/")); } return id(new PHUIObjectItemListView()) diff --git a/src/applications/files/controller/PhabricatorFileIconSetSelectController.php b/src/applications/files/controller/PhabricatorFileIconSetSelectController.php index af46e2d66e..ed7d18a5ee 100644 --- a/src/applications/files/controller/PhabricatorFileIconSetSelectController.php +++ b/src/applications/files/controller/PhabricatorFileIconSetSelectController.php @@ -26,7 +26,7 @@ final class PhabricatorFileIconSetSelectController } } - require_celerity_resource('project-icon-css'); + require_celerity_resource('phui-icon-set-selector-css'); Javelin::initBehavior('phabricator-tooltips'); $ii = 0; @@ -37,6 +37,20 @@ final class PhabricatorFileIconSetSelectController $view = id(new PHUIIconView()) ->setIconFont($icon->getIcon()); + $classes = array(); + $classes[] = 'icon-button'; + + $is_selected = ($icon->getKey() == $v_icon); + + if ($is_selected) { + $classes[] = 'selected'; + } + + $is_disabled = $icon->getIsDisabled(); + if ($is_disabled && !$is_selected) { + continue; + } + $aural = javelin_tag( 'span', array( @@ -44,13 +58,6 @@ final class PhabricatorFileIconSetSelectController ), pht('Choose "%s" Icon', $label)); - $classes = array(); - $classes[] = 'icon-button'; - - if ($icon->getKey() == $v_icon) { - $classes[] = 'selected'; - } - $buttons[] = javelin_tag( 'button', array( diff --git a/src/applications/files/iconset/PhabricatorIconSetIcon.php b/src/applications/files/iconset/PhabricatorIconSetIcon.php index 1e1773aa16..03511cc75b 100644 --- a/src/applications/files/iconset/PhabricatorIconSetIcon.php +++ b/src/applications/files/iconset/PhabricatorIconSetIcon.php @@ -6,6 +6,7 @@ final class PhabricatorIconSetIcon private $key; private $icon; private $label; + private $isDisabled; public function setKey($key) { $this->key = $key; @@ -28,6 +29,15 @@ final class PhabricatorIconSetIcon return $this->icon; } + public function setIsDisabled($is_disabled) { + $this->isDisabled = $is_disabled; + return $this; + } + + public function getIsDisabled() { + return $this->isDisabled; + } + public function setLabel($label) { $this->label = $label; return $this; diff --git a/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php b/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php index 094968437b..195a68a695 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php +++ b/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php @@ -112,7 +112,7 @@ final class HarbormasterBuildableSearchEngine } else if ($object instanceof DifferentialDiff) { $diff_names[] = $object->getID(); } else if ($object instanceof PhabricatorRepository) { - $repository_names[] = 'r'.$object->getCallsign(); + $repository_names[] = $object->getMonogram(); } else if ($object instanceof PhabricatorRepositoryCommit) { $repository = $object->getRepository(); $commit_names[] = $repository->formatCommitName( diff --git a/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php index f4e965b6c2..7f63eda072 100644 --- a/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php @@ -63,8 +63,8 @@ final class HarbormasterWaitForPreviousBuildStepImplementation $call = new ConduitCall( 'diffusion.commitparentsquery', array( - 'commit' => $commit->getCommitIdentifier(), - 'callsign' => $commit->getRepository()->getCallsign(), + 'commit' => $commit->getCommitIdentifier(), + 'repository' => $commit->getRepository()->getPHID(), )); $call->setUser(PhabricatorUser::getOmnipotentUser()); $parents = $call->execute(); diff --git a/src/applications/help/application/PhabricatorHelpApplication.php b/src/applications/help/application/PhabricatorHelpApplication.php index b1f66b02cd..deeae7fa94 100644 --- a/src/applications/help/application/PhabricatorHelpApplication.php +++ b/src/applications/help/application/PhabricatorHelpApplication.php @@ -25,84 +25,4 @@ final class PhabricatorHelpApplication extends PhabricatorApplication { ); } - public function buildMainMenuItems( - PhabricatorUser $user, - PhabricatorController $controller = null) { - - $application = null; - if ($controller) { - $application = $controller->getCurrentApplication(); - } - - $items = array(); - - $help_id = celerity_generate_unique_node_id(); - - Javelin::initBehavior( - 'aphlict-dropdown', - array( - 'bubbleID' => $help_id, - 'dropdownID' => 'phabricator-help-menu', - 'applicationClass' => __CLASS__, - 'local' => true, - 'desktop' => true, - 'right' => true, - )); - - $item = id(new PHUIListItemView()) - ->setIcon('fa-life-ring') - ->addClass('core-menu-item') - ->setID($help_id) - ->setOrder(200); - - $hide = true; - if ($application) { - $help_name = pht('%s Help', $application->getName()); - $item - ->setName($help_name) - ->setHref('/help/documentation/'.get_class($application).'/') - ->setAural($help_name); - $help_items = $application->getHelpMenuItems($user); - if ($help_items) { - $hide = false; - } - } - if ($hide) { - $item->setStyle('display: none'); - } - $items[] = $item; - - return $items; - } - - public function buildMainMenuExtraNodes( - PhabricatorUser $viewer, - PhabricatorController $controller = null) { - - $application = null; - if ($controller) { - $application = $controller->getCurrentApplication(); - } - - $view = null; - if ($application) { - $help_items = $application->getHelpMenuItems($viewer); - if ($help_items) { - $view = new PHUIListView(); - foreach ($help_items as $item) { - $view->addMenuItem($item); - } - } - } - - return phutil_tag( - 'div', - array( - 'id' => 'phabricator-help-menu', - 'class' => 'phabricator-main-menu-dropdown phui-list-sidenav', - 'style' => 'display: none', - ), - $view); - } - } diff --git a/src/applications/help/extension/PhabricatorHelpMainMenuBarExtension.php b/src/applications/help/extension/PhabricatorHelpMainMenuBarExtension.php new file mode 100644 index 0000000000..f12714b5ac --- /dev/null +++ b/src/applications/help/extension/PhabricatorHelpMainMenuBarExtension.php @@ -0,0 +1,70 @@ +getApplication(); + if (!$application) { + return array(); + } + + $viewer = $this->getViewer(); + $help_links = $application->getHelpMenuItems($viewer); + if (!$help_links) { + return array(); + } + + $help_id = celerity_generate_unique_node_id(); + + Javelin::initBehavior( + 'aphlict-dropdown', + array( + 'bubbleID' => $help_id, + 'dropdownID' => 'phabricator-help-menu', + 'local' => true, + 'desktop' => true, + 'right' => true, + )); + + $help_name = pht('%s Help', $application->getName()); + + $help_item = id(new PHUIListItemView()) + ->setIcon('fa-life-ring') + ->addClass('core-menu-item') + ->setID($help_id) + ->setName($help_name) + ->setHref('/help/documentation/'.get_class($application).'/') + ->setAural($help_name); + + $view = new PHUIListView(); + foreach ($help_links as $help_link) { + $view->addMenuItem($help_link); + } + + $dropdown_menu = phutil_tag( + 'div', + array( + 'id' => 'phabricator-help-menu', + 'class' => 'phabricator-main-menu-dropdown phui-list-sidenav', + 'style' => 'display: none', + ), + $view); + + $help_menu = id(new PHUIMainMenuView()) + ->setOrder(200) + ->setMenuBarItem($help_item) + ->appendChild($dropdown_menu); + + return array( + $help_menu, + ); + } + +} diff --git a/src/applications/maniphest/application/PhabricatorManiphestApplication.php b/src/applications/maniphest/application/PhabricatorManiphestApplication.php index c0d0a4d7ef..3fcbbf49f0 100644 --- a/src/applications/maniphest/application/PhabricatorManiphestApplication.php +++ b/src/applications/maniphest/application/PhabricatorManiphestApplication.php @@ -52,13 +52,6 @@ final class PhabricatorManiphestApplication extends PhabricatorApplication { 'task/' => array( $this->getEditRoutePattern('edit/') => 'ManiphestTaskEditController', - 'descriptionpreview/' - => 'PhabricatorMarkupPreviewController', - ), - 'transaction/' => array( - 'save/' => 'ManiphestTransactionSaveController', - 'preview/(?P[1-9]\d*)/' - => 'ManiphestTransactionPreviewController', ), 'export/(?P[^/]+)/' => 'ManiphestExportController', 'subpriority/' => 'ManiphestSubpriorityController', diff --git a/src/applications/maniphest/command/ManiphestClaimEmailCommand.php b/src/applications/maniphest/command/ManiphestClaimEmailCommand.php index 38919617c6..4a6a348dbb 100644 --- a/src/applications/maniphest/command/ManiphestClaimEmailCommand.php +++ b/src/applications/maniphest/command/ManiphestClaimEmailCommand.php @@ -8,7 +8,10 @@ final class ManiphestClaimEmailCommand } public function getCommandSummary() { - return pht('Assign yourself as the owner of a task.'); + return pht( + 'Assign yourself as the owner of a task. To assign another user, '. + 'see `%s`.', + '!assign'); } public function buildTransactions( diff --git a/src/applications/maniphest/command/ManiphestCloseEmailCommand.php b/src/applications/maniphest/command/ManiphestCloseEmailCommand.php index a30f3a35f0..8104fd8b8d 100644 --- a/src/applications/maniphest/command/ManiphestCloseEmailCommand.php +++ b/src/applications/maniphest/command/ManiphestCloseEmailCommand.php @@ -8,7 +8,11 @@ final class ManiphestCloseEmailCommand } public function getCommandSummary() { - return pht('Close a task.'); + return pht( + 'Close a task. This changes the task status to the default closed '. + 'status. For a more powerful (but less concise) way to change task '. + 'statuses, see `%s`.', + '!status'); } public function buildTransactions( diff --git a/src/applications/maniphest/command/ManiphestStatusEmailCommand.php b/src/applications/maniphest/command/ManiphestStatusEmailCommand.php index aa74e33bec..dace0cb255 100644 --- a/src/applications/maniphest/command/ManiphestStatusEmailCommand.php +++ b/src/applications/maniphest/command/ManiphestStatusEmailCommand.php @@ -36,9 +36,11 @@ final class ManiphestStatusEmailCommand "To change the status of a task, specify the desired status, like ". "`%s`. This table shows the configured names for statuses.\n\n%s\n\n". "If you specify an invalid status, the command is ignored. This ". - "command has no effect if you do not specify a status.", + "command has no effect if you do not specify a status.\n\n". + "To quickly close a task, see `%s`.", '!status invalid', - $table); + $table, + '!close'); } public function buildTransactions( diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php index aac21ea7ac..b7e3cee965 100644 --- a/src/applications/maniphest/editor/ManiphestEditEngine.php +++ b/src/applications/maniphest/editor/ManiphestEditEngine.php @@ -156,7 +156,10 @@ final class ManiphestEditEngine ->setConduitDescription(pht('Update the task description.')) ->setConduitTypeDescription(pht('New task description.')) ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION) - ->setValue($object->getDescription()), + ->setValue($object->getDescription()) + ->setPreviewPanel( + id(new PHUIRemarkupPreviewPanel()) + ->setHeader(pht('Description Preview'))), ); } diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index 6193e5b367..7565da6287 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -268,9 +268,8 @@ final class PhabricatorOwnersDetailController if (!$repo) { continue; } - $href = DiffusionRequest::generateDiffusionURI( + $href = $repo->generateURI( array( - 'callsign' => $repo->getCallsign(), 'branch' => $repo->getDefaultBranch(), 'path' => $path->getPath(), 'action' => 'browse', diff --git a/src/applications/owners/controller/PhabricatorOwnersPathsController.php b/src/applications/owners/controller/PhabricatorOwnersPathsController.php index ad278d5296..b02f5437be 100644 --- a/src/applications/owners/controller/PhabricatorOwnersPathsController.php +++ b/src/applications/owners/controller/PhabricatorOwnersPathsController.php @@ -83,7 +83,7 @@ final class PhabricatorOwnersPathsController } } - $repos = mpull($repos, 'getCallsign', 'getPHID'); + $repos = mpull($repos, 'getMonogram', 'getPHID'); asort($repos); $template = new AphrontTypeaheadTemplateView(); diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php index 539bdffa62..f2cfc5151a 100644 --- a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php +++ b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php @@ -204,6 +204,10 @@ final class PhabricatorOwnersPackageTransactionEditor } break; case PhabricatorOwnersPackageTransaction::TYPE_PATHS: + if (!$xactions) { + continue; + } + $old = mpull($object->getPaths(), 'getRef'); foreach ($xactions as $xaction) { $new = $xaction->getNewValue(); diff --git a/src/applications/owners/storage/PhabricatorOwnersOwner.php b/src/applications/owners/storage/PhabricatorOwnersOwner.php index c3d1aaa605..07d5b59a20 100644 --- a/src/applications/owners/storage/PhabricatorOwnersOwner.php +++ b/src/applications/owners/storage/PhabricatorOwnersOwner.php @@ -40,6 +40,7 @@ final class PhabricatorOwnersOwner extends PhabricatorOwnersDAO { if (!$package_ids) { return array(); } + $owners = id(new PhabricatorOwnersOwner())->loadAllWhere( 'packageID IN (%Ls)', $package_ids); @@ -50,20 +51,19 @@ final class PhabricatorOwnersOwner extends PhabricatorOwnersDAO { PhabricatorPeopleUserPHIDType::TYPECONST, array()); - $users_in_project_phids = array(); - $project_phids = idx( - $all_phids, - PhabricatorProjectProjectPHIDType::TYPECONST); - if ($project_phids) { - $query = id(new PhabricatorEdgeQuery()) - ->withSourcePHIDs($project_phids) - ->withEdgeTypes(array( - PhabricatorProjectProjectHasMemberEdgeType::EDGECONST, - )); - $query->execute(); - $users_in_project_phids = $query->getDestinationPHIDs(); + if ($user_phids) { + $projects = id(new PhabricatorProjectQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withMemberPHIDs($user_phids) + ->withIsMilestone(false) + ->execute(); + $project_phids = mpull($projects, 'getPHID'); + } else { + $project_phids = array(); } - return array_unique(array_merge($users_in_project_phids, $user_phids)); + $all_phids = array_fuse($user_phids) + array_fuse($project_phids); + + return array_values($all_phids); } } diff --git a/src/applications/people/application/PhabricatorPeopleApplication.php b/src/applications/people/application/PhabricatorPeopleApplication.php index d1cb552e18..45599907c8 100644 --- a/src/applications/people/application/PhabricatorPeopleApplication.php +++ b/src/applications/people/application/PhabricatorPeopleApplication.php @@ -123,48 +123,6 @@ final class PhabricatorPeopleApplication extends PhabricatorApplication { return $status; } - public function buildMainMenuItems( - PhabricatorUser $user, - PhabricatorController $controller = null) { - - $items = array(); - - if ($user->isLoggedIn() && $user->isUserActivated()) { - $profile = id(new PhabricatorPeopleQuery()) - ->setViewer($user) - ->needProfileImage(true) - ->withPHIDs(array($user->getPHID())) - ->executeOne(); - $image = $profile->getProfileImageURI(); - - $item = id(new PHUIListItemView()) - ->setName($user->getUsername()) - ->setHref('/p/'.$user->getUsername().'/') - ->addClass('core-menu-item') - ->setAural(pht('Profile')) - ->setOrder(100); - - $classes = array( - 'phabricator-core-menu-icon', - 'phabricator-core-menu-profile-image', - ); - - $item->appendChild( - phutil_tag( - 'span', - array( - 'class' => implode(' ', $classes), - 'style' => 'background-image: url('.$image.')', - ), - '')); - - $items[] = $item; - } - - return $items; - } - - public function getQuickCreateItems(PhabricatorUser $viewer) { $items = array(); diff --git a/src/applications/people/extension/PhabricatorPeopleMainMenuBarExtension.php b/src/applications/people/extension/PhabricatorPeopleMainMenuBarExtension.php new file mode 100644 index 0000000000..a92c5ba235 --- /dev/null +++ b/src/applications/people/extension/PhabricatorPeopleMainMenuBarExtension.php @@ -0,0 +1,49 @@ +getViewer(); + + // TODO: This should get cached. + + $profile = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->needProfileImage(true) + ->withPHIDs(array($viewer->getPHID())) + ->executeOne(); + $image = $profile->getProfileImageURI(); + + $bar_item = id(new PHUIListItemView()) + ->setName($viewer->getUsername()) + ->setHref('/p/'.$viewer->getUsername().'/') + ->addClass('core-menu-item') + ->setAural(pht('Profile')); + + $classes = array( + 'phabricator-core-menu-icon', + 'phabricator-core-menu-profile-image', + ); + + $bar_item->appendChild( + phutil_tag( + 'span', + array( + 'class' => implode(' ', $classes), + 'style' => 'background-image: url('.$image.')', + ), + '')); + + $profile_menu = id(new PHUIMainMenuView()) + ->setOrder(100) + ->setMenuBarItem($bar_item); + + return array( + $profile_menu, + ); + } + +} diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index c805eb2b5c..18526ac04f 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -504,7 +504,11 @@ final class PhabricatorUser return $preferences; } - public function loadEditorLink($path, $line, $callsign) { + public function loadEditorLink( + $path, + $line, + PhabricatorRepository $repository = null) { + $editor = $this->loadPreferences()->getPreference( PhabricatorUserPreferences::PREFERENCE_EDITOR); @@ -524,6 +528,12 @@ final class PhabricatorUser return null; } + if ($repository) { + $callsign = $repository->getCallsign(); + } else { + $callsign = null; + } + $uri = strtr($editor, array( '%%' => '%', '%f' => phutil_escape_uri($path), diff --git a/src/applications/phame/conduit/PhameBlogEditConduitAPIMethod.php b/src/applications/phame/conduit/PhameBlogEditConduitAPIMethod.php new file mode 100644 index 0000000000..98845de2a2 --- /dev/null +++ b/src/applications/phame/conduit/PhameBlogEditConduitAPIMethod.php @@ -0,0 +1,18 @@ + 'required phid', - 'title' => 'required string', - 'body' => 'required string', - 'bloggerPHID' => 'optional phid', - 'isDraft' => 'optional bool', - ); - } - - protected function defineReturnType() { - return 'list'; - } - - protected function defineErrorTypes() { - return array( - 'ERR-INVALID-PARAMETER' => - pht('Missing or malformed parameter.'), - 'ERR-INVALID-BLOG' => - pht('Invalid blog PHID or user can not post to blog.'), - ); - } - - protected function execute(ConduitAPIRequest $request) { - $user = $request->getUser(); - $blog_phid = $request->getValue('blogPHID'); - $title = $request->getValue('title'); - $body = $request->getValue('body'); - $exception_description = array(); - if (!$blog_phid) { - $exception_description[] = pht('No blog phid.'); - } - if (!strlen($title)) { - $exception_description[] = pht('No post title.'); - } - if (!strlen($body)) { - $exception_description[] = pht('No post body.'); - } - if ($exception_description) { - throw id(new ConduitException('ERR-INVALID-PARAMETER')) - ->setErrorDescription(implode("\n", $exception_description)); - } - - $blogger_phid = $request->getValue('bloggerPHID'); - if ($blogger_phid) { - $blogger = id(new PhabricatorPeopleQuery()) - ->setViewer($user) - ->withPHIDs(array($blogger_phid)) - ->executeOne(); - } else { - $blogger = $user; - } - - $blog = id(new PhameBlogQuery()) - ->setViewer($blogger) - ->withPHIDs(array($blog_phid)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - - if (!$blog) { - throw new ConduitException('ERR-INVALID-BLOG'); - } - - $post = PhamePost::initializePost($blogger, $blog); - $is_draft = $request->getValue('isDraft', false); - if (!$is_draft) { - $post->setDatePublished(time()); - $post->setVisibility(PhameConstants::VISIBILITY_PUBLISHED); - } - $post->setTitle($title); - $post->setBody($body); - $post->save(); - - return $post->toDictionary(); - } - -} diff --git a/src/applications/phame/conduit/PhamePostEditConduitAPIMethod.php b/src/applications/phame/conduit/PhamePostEditConduitAPIMethod.php new file mode 100644 index 0000000000..8579a4283b --- /dev/null +++ b/src/applications/phame/conduit/PhamePostEditConduitAPIMethod.php @@ -0,0 +1,18 @@ + 'optional list', - 'phids' => 'optional list', - 'after' => 'optional int', - 'before' => 'optional int', - 'limit' => 'optional int', - ); - } - - protected function defineReturnType() { - return 'list'; - } - - protected function execute(ConduitAPIRequest $request) { - $query = new PhameBlogQuery(); - - $query->setViewer($request->getUser()); - - $ids = $request->getValue('ids', array()); - if ($ids) { - $query->withIDs($ids); - } - - $phids = $request->getValue('phids', array()); - if ($phids) { - $query->withPHIDs($phids); - } - - $after = $request->getValue('after', null); - if ($after !== null) { - $query->setAfterID($after); - } - - $before = $request->getValue('before', null); - if ($before !== null) { - $query->setBeforeID($before); - } - - $limit = $request->getValue('limit', null); - if ($limit !== null) { - $query->setLimit($limit); - } - - $blogs = $query->execute(); - - $results = array(); - foreach ($blogs as $blog) { - $results[] = array( - 'id' => $blog->getID(), - 'phid' => $blog->getPHID(), - 'name' => $blog->getName(), - 'description' => $blog->getDescription(), - 'domain' => $blog->getDomain(), - 'creatorPHID' => $blog->getCreatorPHID(), - ); - } - - return $results; - } - -} diff --git a/src/applications/phame/conduit/PhameQueryPostsConduitAPIMethod.php b/src/applications/phame/conduit/PhameQueryPostsConduitAPIMethod.php deleted file mode 100644 index fba2536140..0000000000 --- a/src/applications/phame/conduit/PhameQueryPostsConduitAPIMethod.php +++ /dev/null @@ -1,97 +0,0 @@ - 'optional list', - 'phids' => 'optional list', - 'blogPHIDs' => 'optional list', - 'bloggerPHIDs' => 'optional list', - 'published' => 'optional bool', - 'publishedAfter' => 'optional date', - 'before' => 'optional int', - 'after' => 'optional int', - 'limit' => 'optional int', - ); - } - - protected function defineReturnType() { - return 'list'; - } - - protected function execute(ConduitAPIRequest $request) { - $query = new PhamePostQuery(); - - $query->setViewer($request->getUser()); - - $ids = $request->getValue('ids', array()); - if ($ids) { - $query->withIDs($ids); - } - - $phids = $request->getValue('phids', array()); - if ($phids) { - $query->withPHIDs($phids); - } - - $blog_phids = $request->getValue('blogPHIDs', array()); - if ($blog_phids) { - $query->withBlogPHIDs($blog_phids); - } - - $blogger_phids = $request->getValue('bloggerPHIDs', array()); - if ($blogger_phids) { - $query->withBloggerPHIDs($blogger_phids); - } - - $published = $request->getValue('published', null); - if ($published === true) { - $query->withVisibility(PhameConstants::VISIBILITY_PUBLISHED); - } else if ($published === false) { - $query->withVisibility(PhameConstants::VISIBILITY_DRAFT); - } - - $published_after = $request->getValue('publishedAfter', null); - if ($published_after !== null) { - $query->withPublishedAfter($published_after); - } - - $after = $request->getValue('after', null); - if ($after !== null) { - $query->setAfterID($after); - } - - $before = $request->getValue('before', null); - if ($before !== null) { - $query->setBeforeID($before); - } - - $limit = $request->getValue('limit', null); - if ($limit !== null) { - $query->setLimit($limit); - } - - $posts = $query->execute(); - - $results = array(); - foreach ($posts as $post) { - $results[] = $post->toDictionary(); - } - - return $results; - } - -} diff --git a/src/applications/phame/controller/PhameHomeController.php b/src/applications/phame/controller/PhameHomeController.php index c547071e67..4028f98e50 100644 --- a/src/applications/phame/controller/PhameHomeController.php +++ b/src/applications/phame/controller/PhameHomeController.php @@ -28,10 +28,18 @@ final class PhameHomeController extends PhamePostController { ->withVisibility(PhameConstants::VISIBILITY_PUBLISHED) ->executeWithCursorPager($pager); - $post_list = id(new PhamePostListView()) - ->setPosts($posts) - ->setViewer($viewer) - ->showBlog(true); + if ($posts) { + $post_list = id(new PhamePostListView()) + ->setPosts($posts) + ->setViewer($viewer) + ->showBlog(true); + } else { + $post_list = id(new PHUIBigInfoView()) + ->setIcon('fa-star') + ->setTitle('No Visible Posts') + ->setDescription( + pht('There aren\'t any visible blog posts.')); + } } else { $create_button = id(new PHUIButtonView()) ->setTag('a') @@ -43,7 +51,7 @@ final class PhameHomeController extends PhamePostController { ->setIcon('fa-star') ->setTitle('Welcome to Phame') ->setDescription( - pht('There aren\'t any visible Blog Posts.')) + pht('There aren\'t any visible blog posts.')) ->addAction($create_button); } @@ -77,7 +85,7 @@ final class PhameHomeController extends PhamePostController { ->setViewer($viewer); $draft_list = null; - if ($viewer->isLoggedIn()) { + if ($viewer->isLoggedIn() && $blogs) { $drafts = id(new PhamePostQuery()) ->setViewer($viewer) ->withBloggerPHIDs(array($viewer->getPHID())) diff --git a/src/applications/phame/controller/post/PhamePostEditController.php b/src/applications/phame/controller/post/PhamePostEditController.php index ff8855be69..91df519adb 100644 --- a/src/applications/phame/controller/post/PhamePostEditController.php +++ b/src/applications/phame/controller/post/PhamePostEditController.php @@ -2,193 +2,81 @@ final class PhamePostEditController extends PhamePostController { + private $blog; + + public function setBlog(PhameBlog $blog) { + $this->blog = $blog; + return $this; + } + + public function getBlog() { + return $this->blog; + } + public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb( - pht('Blogs'), - $this->getApplicationURI('blog/')); if ($id) { $post = id(new PhamePostQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( + PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$post) { return new Aphront404Response(); } - - $cancel_uri = $this->getApplicationURI('/post/view/'.$id.'/'); - $submit_button = pht('Save Changes'); - $page_title = pht('Edit Post'); - - $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( - $post->getPHID(), - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); - $v_projects = array_reverse($v_projects); - $v_cc = PhabricatorSubscribersQuery::loadSubscribersForPHID( - $post->getPHID()); - $blog = $post->getBlog(); - - + $blog_id = $post->getBlog()->getID(); } else { - $blog = id(new PhameBlogQuery()) - ->setViewer($viewer) - ->withIDs(array($request->getInt('blog'))) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$blog) { - return new Aphront404Response(); - } - $v_projects = array(); - $v_cc = array(); - - $post = PhamePost::initializePost($viewer, $blog); - $cancel_uri = $this->getApplicationURI('/blog/view/'.$blog->getID().'/'); - - $submit_button = pht('Create Post'); - $page_title = pht('Create Post'); - } - - $title = $post->getTitle(); - $body = $post->getBody(); - $visibility = $post->getVisibility(); - - $e_title = true; - $validation_exception = null; - if ($request->isFormPost()) { - $title = $request->getStr('title'); - $body = $request->getStr('body'); - $v_projects = $request->getArr('projects'); - $v_cc = $request->getArr('cc'); - $visibility = $request->getInt('visibility'); - - $xactions = array( - id(new PhamePostTransaction()) - ->setTransactionType(PhamePostTransaction::TYPE_TITLE) - ->setNewValue($title), - id(new PhamePostTransaction()) - ->setTransactionType(PhamePostTransaction::TYPE_BODY) - ->setNewValue($body), - id(new PhamePostTransaction()) - ->setTransactionType(PhamePostTransaction::TYPE_VISIBILITY) - ->setNewValue($visibility), - id(new PhamePostTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) - ->setNewValue(array('=' => $v_cc)), - - ); - - $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; - $xactions[] = id(new PhamePostTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $proj_edge_type) - ->setNewValue(array('=' => array_fuse($v_projects))); - - $editor = id(new PhamePostEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true); - - try { - $editor->applyTransactions($post, $xactions); - - $uri = $post->getViewURI(); - return id(new AphrontRedirectResponse())->setURI($uri); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - $e_title = $validation_exception->getShortMessage( - PhamePostTransaction::TYPE_TITLE); + $blog_id = head($request->getArr('blog')); + if (!$blog_id) { + $blog_id = $request->getStr('blog'); } } - $handle = id(new PhabricatorHandleQuery()) + $query = id(new PhameBlogQuery()) ->setViewer($viewer) - ->withPHIDs(array($post->getBlogPHID())) - ->executeOne(); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->addHiddenInput('blog', $request->getInt('blog')) - ->appendChild( - id(new AphrontFormMarkupControl()) - ->setLabel(pht('Blog')) - ->setValue($handle->renderLink())) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Title')) - ->setName('title') - ->setValue($title) - ->setID('post-title') - ->setError($e_title)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Visibility')) - ->setName('visibility') - ->setValue($visibility) - ->setOptions(PhameConstants::getPhamePostStatusMap())) - ->appendChild( - id(new PhabricatorRemarkupControl()) - ->setLabel(pht('Body')) - ->setName('body') - ->setValue($body) - ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) - ->setID('post-body') - ->setUser($viewer) - ->setDisableMacros(true)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Subscribers')) - ->setName('cc') - ->setValue($v_cc) - ->setUser($viewer) - ->setDatasource(new PhabricatorMetaMTAMailableDatasource())) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Projects')) - ->setName('projects') - ->setValue($v_projects) - ->setDatasource(new PhabricatorProjectDatasource())) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton($cancel_uri) - ->setValue($submit_button)); - - $preview = id(new PHUIRemarkupPreviewPanel()) - ->setHeader($post->getTitle()) - ->setPreviewURI($this->getApplicationURI('post/preview/')) - ->setControlID('post-body') - ->setPreviewType(PHUIRemarkupPreviewPanel::DOCUMENT); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($page_title) - ->setValidationException($validation_exception) - ->setForm($form); - - $crumbs->addTextCrumb( - $blog->getName(), - $blog->getViewURI()); - $crumbs->addTextCrumb( - $page_title, - $cancel_uri); - - return $this->newPage() - ->setTitle($page_title) - ->setCrumbs($crumbs) - ->appendChild( + ->requireCapabilities( array( - $form_box, - $preview, - )); + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )); + + if (ctype_digit($blog_id)) { + $query->withIDs(array($blog_id)); + } else { + $query->withPHIDs(array($blog_id)); + } + + $blog = $query->executeOne(); + if (!$blog) { + return new Aphront404Response(); + } + + $this->setBlog($blog); + + return id(new PhamePostEditEngine()) + ->setController($this) + ->setBlog($blog) + ->buildResponse(); } + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $blog = $this->getBlog(); + if ($blog) { + $crumbs->addTextCrumb( + $blog->getName(), + $blog->getViewURI()); + } + + return $crumbs; + } + + } diff --git a/src/applications/phame/controller/post/PhamePostMoveController.php b/src/applications/phame/controller/post/PhamePostMoveController.php index 3ce2586e58..3e09a9ba2d 100644 --- a/src/applications/phame/controller/post/PhamePostMoveController.php +++ b/src/applications/phame/controller/post/PhamePostMoveController.php @@ -20,59 +20,57 @@ final class PhamePostMoveController extends PhamePostController { return new Aphront404Response(); } - $view_uri = '/post/view/'.$post->getID().'/'; - $view_uri = $this->getApplicationURI($view_uri); + $view_uri = $post->getViewURI(); + $v_blog = $post->getBlog()->getPHID(); if ($request->isFormPost()) { - $blog = id(new PhameBlogQuery()) - ->setViewer($viewer) - ->withIDs(array($request->getInt('blog'))) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); + $v_blog = $request->getStr('blogPHID'); - if ($blog) { - $post->setBlogPHID($blog->getPHID()); - $post->save(); + $xactions = array(); + $xactions[] = id(new PhamePostTransaction()) + ->setTransactionType(PhamePostTransaction::TYPE_BLOG) + ->setNewValue($v_blog); - return id(new AphrontRedirectResponse()) - ->setURI($view_uri.'?moved=1'); - } + $editor = id(new PhamePostEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true); + + $editor->applyTransactions($post, $xactions); + + $view_uri = $post->getViewURI(); + + return id(new AphrontRedirectResponse()) + ->setURI($view_uri.'?moved=1'); } $blogs = id(new PhameBlogQuery()) ->setViewer($viewer) ->requireCapabilities( array( + PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->execute(); - $options = mpull($blogs, 'getName', 'getID'); + $options = mpull($blogs, 'getName', 'getPHID'); asort($options); - $selected_value = null; - if ($post && $post->getBlog()) { - $selected_value = $post->getBlog()->getID(); - } - $form = id(new PHUIFormLayoutView()) ->setUser($viewer) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Blog')) - ->setName('blog') + ->setName('blogPHID') ->setOptions($options) - ->setValue($selected_value)); + ->setValue($v_blog)); return $this->newDialog() ->setTitle(pht('Move Post')) ->appendChild($form) ->addSubmitButton(pht('Move Post')) ->addCancelButton($view_uri); - } } diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php index 240f8f3a69..6a0ceace4f 100644 --- a/src/applications/phame/controller/post/PhamePostViewController.php +++ b/src/applications/phame/controller/post/PhamePostViewController.php @@ -120,15 +120,24 @@ final class PhamePostViewController $add_comment = phutil_tag_div('mlb mlt', $add_comment); } + list($prev, $next) = $this->loadAdjacentPosts($post); + $properties = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($post); - $properties->invokeWillRenderEvent(); + $next_view = new PhameNextPostView(); + if ($next) { + $next_view->setNext($next->getTitle(), $next->getViewURI()); + } + if ($prev) { + $next_view->setPrevious($prev->getTitle(), $prev->getViewURI()); + } + $document->setFoot($next_view); $crumbs = $this->buildApplicationCrumbs(); - $page = $this->newPage() + $page = $this->newPage() ->setTitle($post->getTitle()) ->setPageObjectPHIDs(array($post->getPHID())) ->setCrumbs($crumbs) @@ -236,4 +245,24 @@ final class PhamePostViewController return phutil_tag_div('phui-document-view-pro-box', $box); } + private function loadAdjacentPosts(PhamePost $post) { + $viewer = $this->getViewer(); + + $query = id(new PhamePostQuery()) + ->setViewer($viewer) + ->withVisibility(PhameConstants::VISIBILITY_PUBLISHED) + ->withBlogPHIDs(array($post->getBlog()->getPHID())) + ->setLimit(1); + + $prev = id(clone $query) + ->setAfterID($post->getID()) + ->execute(); + + $next = id(clone $query) + ->setBeforeID($post->getID()) + ->execute(); + + return array(head($prev), head($next)); + } + } diff --git a/src/applications/phame/editor/PhameBlogEditor.php b/src/applications/phame/editor/PhameBlogEditor.php index 3698293059..a59ede32be 100644 --- a/src/applications/phame/editor/PhameBlogEditor.php +++ b/src/applications/phame/editor/PhameBlogEditor.php @@ -47,9 +47,14 @@ final class PhameBlogEditor switch ($xaction->getTransactionType()) { case PhameBlogTransaction::TYPE_NAME: case PhameBlogTransaction::TYPE_DESCRIPTION: - case PhameBlogTransaction::TYPE_DOMAIN: case PhameBlogTransaction::TYPE_STATUS: return $xaction->getNewValue(); + case PhameBlogTransaction::TYPE_DOMAIN: + $domain = $xaction->getNewValue(); + if (!strlen($xaction->getNewValue())) { + return null; + } + return $domain; } } @@ -225,4 +230,18 @@ final class PhameBlogEditor return false; } + protected function shouldApplyHeraldRules( + PhabricatorLiskDAO $object, + array $xactions) { + return true; + } + + protected function buildHeraldAdapter( + PhabricatorLiskDAO $object, + array $xactions) { + + return id(new HeraldPhameBlogAdapter()) + ->setBlog($object); + } + } diff --git a/src/applications/phame/editor/PhamePostEditEngine.php b/src/applications/phame/editor/PhamePostEditEngine.php new file mode 100644 index 0000000000..652922ea56 --- /dev/null +++ b/src/applications/phame/editor/PhamePostEditEngine.php @@ -0,0 +1,118 @@ +blog = $blog; + return $this; + } + + public function getEngineApplicationClass() { + return 'PhabricatorPhameApplication'; + } + + protected function newEditableObject() { + $viewer = $this->getViewer(); + + if ($this->blog) { + $blog = $this->blog; + } else { + $blog = PhameBlog::initializeNewBlog($viewer); + } + + return PhamePost::initializePost($viewer, $blog); + } + + protected function newObjectQuery() { + return new PhamePostQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Post'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit %s', $object->getTitle()); + } + + protected function getObjectEditShortText($object) { + return $object->getTitle(); + } + + protected function getObjectCreateShortText() { + return pht('Create Post'); + } + + protected function getObjectViewURI($object) { + return $object->getViewURI(); + } + + protected function buildCustomEditFields($object) { + $blog_phid = $object->getBlog()->getPHID(); + + return array( + id(new PhabricatorHandlesEditField()) + ->setKey('blog') + ->setLabel(pht('Blog')) + ->setDescription(pht('Blog to publish this post to.')) + ->setConduitDescription( + pht('Choose a blog to create a post on (or move a post to).')) + ->setConduitTypeDescription(pht('PHID of the blog.')) + ->setAliases(array('blogPHID')) + ->setTransactionType(PhamePostTransaction::TYPE_BLOG) + ->setHandleParameterType(new AphrontPHIDListHTTPParameterType()) + ->setSingleValue($blog_phid) + ->setIsReorderable(false) + ->setIsDefaultable(false) + ->setIsLockable(false) + ->setIsLocked(true), + id(new PhabricatorTextEditField()) + ->setKey('title') + ->setLabel(pht('Title')) + ->setDescription(pht('Post title.')) + ->setConduitDescription(pht('Retitle the post.')) + ->setConduitTypeDescription(pht('New post title.')) + ->setTransactionType(PhamePostTransaction::TYPE_TITLE) + ->setValue($object->getTitle()), + id(new PhabricatorSelectEditField()) + ->setKey('visibility') + ->setLabel(pht('Visibility')) + ->setDescription(pht('Post visibility.')) + ->setConduitDescription(pht('Change post visibility.')) + ->setConduitTypeDescription(pht('New post visibility constant.')) + ->setTransactionType(PhamePostTransaction::TYPE_VISIBILITY) + ->setValue($object->getVisibility()) + ->setOptions(PhameConstants::getPhamePostStatusMap()), + id(new PhabricatorRemarkupEditField()) + ->setKey('body') + ->setLabel(pht('Body')) + ->setDescription(pht('Post body.')) + ->setConduitDescription(pht('Change post body.')) + ->setConduitTypeDescription(pht('New post body.')) + ->setTransactionType(PhamePostTransaction::TYPE_BODY) + ->setValue($object->getBody()) + ->setPreviewPanel( + id(new PHUIRemarkupPreviewPanel()) + ->setHeader(pht('Blog Post')) + ->setPreviewType(PHUIRemarkupPreviewPanel::DOCUMENT)), + ); + } + +} diff --git a/src/applications/phame/editor/PhamePostEditor.php b/src/applications/phame/editor/PhamePostEditor.php index 4ca1710340..fa43a131ff 100644 --- a/src/applications/phame/editor/PhamePostEditor.php +++ b/src/applications/phame/editor/PhamePostEditor.php @@ -14,6 +14,7 @@ final class PhamePostEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); + $types[] = PhamePostTransaction::TYPE_BLOG; $types[] = PhamePostTransaction::TYPE_TITLE; $types[] = PhamePostTransaction::TYPE_BODY; $types[] = PhamePostTransaction::TYPE_VISIBILITY; @@ -27,6 +28,8 @@ final class PhamePostEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { + case PhamePostTransaction::TYPE_BLOG: + return $object->getBlogPHID(); case PhamePostTransaction::TYPE_TITLE: return $object->getTitle(); case PhamePostTransaction::TYPE_BODY: @@ -44,6 +47,7 @@ final class PhamePostEditor case PhamePostTransaction::TYPE_TITLE: case PhamePostTransaction::TYPE_BODY: case PhamePostTransaction::TYPE_VISIBILITY: + case PhamePostTransaction::TYPE_BLOG: return $xaction->getNewValue(); } } @@ -57,6 +61,8 @@ final class PhamePostEditor return $object->setTitle($xaction->getNewValue()); case PhamePostTransaction::TYPE_BODY: return $object->setBody($xaction->getNewValue()); + case PhamePostTransaction::TYPE_BLOG: + return $object->setBlogPHID($xaction->getNewValue()); case PhamePostTransaction::TYPE_VISIBILITY: if ($xaction->getNewValue() == PhameConstants::VISIBILITY_DRAFT) { $object->setDatePublished(0); @@ -77,6 +83,7 @@ final class PhamePostEditor case PhamePostTransaction::TYPE_TITLE: case PhamePostTransaction::TYPE_BODY: case PhamePostTransaction::TYPE_VISIBILITY: + case PhamePostTransaction::TYPE_BLOG: return; } @@ -106,6 +113,53 @@ final class PhamePostEditor $error->setIsMissingFieldError(true); $errors[] = $error; } + break; + case PhamePostTransaction::TYPE_BLOG: + if ($this->getIsNewObject()) { + if (!$xactions) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht( + 'When creating a post, you must specify which blog it '. + 'should belong to.'), + null); + + $error->setIsMissingFieldError(true); + + $errors[] = $error; + break; + } + } + + foreach ($xactions as $xaction) { + $new_phid = $xaction->getNewValue(); + + $blog = id(new PhameBlogQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($new_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->execute(); + + if ($blog) { + continue; + } + + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'The specified blog PHID ("%s") is not valid. You can only '. + 'create a post on (or move a post into) a blog which you '. + 'have permission to see and edit.', + $new_phid), + $xaction); + } + break; } return $errors; @@ -210,4 +264,18 @@ final class PhamePostEditor return false; } + protected function shouldApplyHeraldRules( + PhabricatorLiskDAO $object, + array $xactions) { + return true; + } + + protected function buildHeraldAdapter( + PhabricatorLiskDAO $object, + array $xactions) { + + return id(new HeraldPhamePostAdapter()) + ->setPost($object); + } + } diff --git a/src/applications/phame/herald/HeraldPhameBlogAdapter.php b/src/applications/phame/herald/HeraldPhameBlogAdapter.php new file mode 100644 index 0000000000..d8956ba4a6 --- /dev/null +++ b/src/applications/phame/herald/HeraldPhameBlogAdapter.php @@ -0,0 +1,62 @@ +blog = $this->newObject(); + } + + public function supportsApplicationEmail() { + return true; + } + + public function getRepetitionOptions() { + return array( + HeraldRepetitionPolicyConfig::EVERY, + HeraldRepetitionPolicyConfig::FIRST, + ); + } + + public function supportsRuleType($rule_type) { + switch ($rule_type) { + case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: + case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: + return true; + case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: + default: + return false; + } + } + + public function setBlog(PhameBlog $blog) { + $this->blog = $blog; + return $this; + } + + public function getObject() { + return $this->blog; + } + + public function getAdapterContentName() { + return pht('Phame Blogs'); + } + + public function getHeraldName() { + return 'BLOG'.$this->getObject()->getID(); + } + +} diff --git a/src/applications/phame/herald/HeraldPhamePostAdapter.php b/src/applications/phame/herald/HeraldPhamePostAdapter.php new file mode 100644 index 0000000000..4776bbbdbc --- /dev/null +++ b/src/applications/phame/herald/HeraldPhamePostAdapter.php @@ -0,0 +1,62 @@ +post = $this->newObject(); + } + + public function supportsApplicationEmail() { + return true; + } + + public function getRepetitionOptions() { + return array( + HeraldRepetitionPolicyConfig::EVERY, + HeraldRepetitionPolicyConfig::FIRST, + ); + } + + public function supportsRuleType($rule_type) { + switch ($rule_type) { + case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: + case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: + return true; + case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: + default: + return false; + } + } + + public function setPost(PhamePost $post) { + $this->post = $post; + return $this; + } + + public function getObject() { + return $this->post; + } + + public function getAdapterContentName() { + return pht('Phame Posts'); + } + + public function getHeraldName() { + return 'POST'.$this->getObject()->getID(); + } + +} diff --git a/src/applications/phame/query/PhamePostQuery.php b/src/applications/phame/query/PhamePostQuery.php index a6c2f22ee5..d2a1feb2c1 100644 --- a/src/applications/phame/query/PhamePostQuery.php +++ b/src/applications/phame/query/PhamePostQuery.php @@ -39,38 +39,36 @@ final class PhamePostQuery extends PhabricatorCursorPagedPolicyAwareQuery { return $this; } + public function newResultObject() { + return new PhamePost(); + } + protected function loadPage() { - $table = new PhamePost(); - $conn_r = $table->establishConnection('r'); + return $this->loadStandardPage($this->newResultObject()); + } - $where_clause = $this->buildWhereClause($conn_r); - $order_clause = $this->buildOrderClause($conn_r); - $limit_clause = $this->buildLimitClause($conn_r); + protected function willFilterPage(array $posts) { + // We require blogs to do visibility checks, so load them unconditionally. + $blog_phids = mpull($posts, 'getBlogPHID'); - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T p %Q %Q %Q', - $table->getTableName(), - $where_clause, - $order_clause, - $limit_clause); + $blogs = id(new PhameBlogQuery()) + ->setViewer($this->getViewer()) + ->needProfileImage(true) + ->withPHIDs($blog_phids) + ->execute(); - $posts = $table->loadAllFromArray($data); + $blogs = mpull($blogs, null, 'getPHID'); + foreach ($posts as $key => $post) { + $blog_phid = $post->getBlogPHID(); - if ($posts) { - // We require these to do visibility checks, so load them unconditionally. - $blog_phids = mpull($posts, 'getBlogPHID'); - $blogs = id(new PhameBlogQuery()) - ->setViewer($this->getViewer()) - ->needProfileImage(true) - ->withPHIDs($blog_phids) - ->execute(); - $blogs = mpull($blogs, null, 'getPHID'); - foreach ($posts as $post) { - if (isset($blogs[$post->getBlogPHID()])) { - $post->setBlog($blogs[$post->getBlogPHID()]); - } + $blog = idx($blogs, $blog_phid); + if (!$blog) { + $this->didRejectResult($post); + unset($posts[$key]); + continue; } + + $post->attachBlog($blog); } return $posts; @@ -82,42 +80,42 @@ final class PhamePostQuery extends PhabricatorCursorPagedPolicyAwareQuery { if ($this->ids) { $where[] = qsprintf( $conn, - 'p.id IN (%Ld)', + 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn, - 'p.phid IN (%Ls)', + 'phid IN (%Ls)', $this->phids); } if ($this->bloggerPHIDs) { $where[] = qsprintf( $conn, - 'p.bloggerPHID IN (%Ls)', + 'bloggerPHID IN (%Ls)', $this->bloggerPHIDs); } if ($this->visibility !== null) { $where[] = qsprintf( $conn, - 'p.visibility = %d', + 'visibility = %d', $this->visibility); } if ($this->publishedAfter !== null) { $where[] = qsprintf( $conn, - 'p.datePublished > %d', + 'datePublished > %d', $this->publishedAfter); } - if ($this->blogPHIDs) { + if ($this->blogPHIDs !== null) { $where[] = qsprintf( $conn, - 'p.blogPHID in (%Ls)', + 'blogPHID in (%Ls)', $this->blogPHIDs); } diff --git a/src/applications/phame/query/PhamePostSearchEngine.php b/src/applications/phame/query/PhamePostSearchEngine.php index 5002c84e0d..a7a331fbdf 100644 --- a/src/applications/phame/query/PhamePostSearchEngine.php +++ b/src/applications/phame/query/PhamePostSearchEngine.php @@ -30,10 +30,11 @@ final class PhamePostSearchEngine id(new PhabricatorSearchSelectField()) ->setKey('visibility') ->setLabel(pht('Visibility')) - ->setOptions(array( - '' => pht('All'), - PhameConstants::VISIBILITY_PUBLISHED => pht('Published'), - PhameConstants::VISIBILITY_DRAFT => pht('Draft'), + ->setOptions( + array( + '' => pht('All'), + PhameConstants::VISIBILITY_PUBLISHED => pht('Published'), + PhameConstants::VISIBILITY_DRAFT => pht('Draft'), )), ); } @@ -68,6 +69,8 @@ final class PhamePostSearchEngine return parent::buildSavedQueryFromBuiltin($query_key); } + + protected function renderResultList( array $posts, PhabricatorSavedQuery $query, @@ -82,12 +85,10 @@ final class PhamePostSearchEngine foreach ($posts as $post) { $id = $post->getID(); $blog = $post->getBlog(); - if ($blog) { - $blog_name = $viewer->renderHandle($post->getBlogPHID())->render(); - $blog_name = pht('Blog: %s', $blog_name); - } else { - $blog_name = pht('[No Blog]'); - } + + $blog_name = $viewer->renderHandle($post->getBlogPHID())->render(); + $blog_name = pht('Blog: %s', $blog_name); + $item = id(new PHUIObjectItemView()) ->setUser($viewer) ->setObject($post) diff --git a/src/applications/phame/storage/PhameBlog.php b/src/applications/phame/storage/PhameBlog.php index fb1d47ad5f..5b5b128ae6 100644 --- a/src/applications/phame/storage/PhameBlog.php +++ b/src/applications/phame/storage/PhameBlog.php @@ -8,7 +8,8 @@ final class PhameBlog extends PhameDAO PhabricatorFlaggableInterface, PhabricatorProjectInterface, PhabricatorDestructibleInterface, - PhabricatorApplicationTransactionInterface { + PhabricatorApplicationTransactionInterface, + PhabricatorConduitResultInterface { const MARKUP_FIELD_DESCRIPTION = 'markup:description'; @@ -344,4 +345,37 @@ final class PhameBlog extends PhameDAO } +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('name') + ->setType('string') + ->setDescription(pht('The name of the blog.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('description') + ->setType('string') + ->setDescription(pht('Blog description.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('status') + ->setType('string') + ->setDescription(pht('Archived or active status.')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'name' => $this->getName(), + 'description' => $this->getDescription(), + 'status' => $this->getStatus(), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + + } diff --git a/src/applications/phame/storage/PhamePost.php b/src/applications/phame/storage/PhamePost.php index 94e9c63f4a..57519bd2f1 100644 --- a/src/applications/phame/storage/PhamePost.php +++ b/src/applications/phame/storage/PhamePost.php @@ -9,7 +9,8 @@ final class PhamePost extends PhameDAO PhabricatorApplicationTransactionInterface, PhabricatorSubscribableInterface, PhabricatorDestructibleInterface, - PhabricatorTokenReceiverInterface { + PhabricatorTokenReceiverInterface, + PhabricatorConduitResultInterface { const MARKUP_FIELD_BODY = 'markup:body'; const MARKUP_FIELD_SUMMARY = 'markup:summary'; @@ -24,7 +25,7 @@ final class PhamePost extends PhameDAO protected $blogPHID; protected $mailKey; - private $blog; + private $blog = self::ATTACHABLE; public static function initializePost( PhabricatorUser $blogger, @@ -33,19 +34,20 @@ final class PhamePost extends PhameDAO $post = id(new PhamePost()) ->setBloggerPHID($blogger->getPHID()) ->setBlogPHID($blog->getPHID()) - ->setBlog($blog) + ->attachBlog($blog) ->setDatePublished(PhabricatorTime::getNow()) ->setVisibility(PhameConstants::VISIBILITY_PUBLISHED); + return $post; } - public function setBlog(PhameBlog $blog) { + public function attachBlog(PhameBlog $blog) { $this->blog = $blog; return $this; } public function getBlog() { - return $this->blog; + return $this->assertAttached($this->blog); } public function getLiveURI() { @@ -146,21 +148,6 @@ final class PhamePost extends PhameDAO return PhabricatorSlug::normalizeProjectSlug($this->getTitle(), true); } - public function toDictionary() { - return array( - 'id' => $this->getID(), - 'phid' => $this->getPHID(), - 'blogPHID' => $this->getBlogPHID(), - 'bloggerPHID' => $this->getBloggerPHID(), - 'viewURI' => $this->getViewURI(), - 'title' => $this->getTitle(), - 'body' => $this->getBody(), - 'summary' => PhabricatorMarkupEngine::summarize($this->getBody()), - 'datePublished' => $this->getDatePublished(), - 'published' => !$this->isDraft(), - ); - } - /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ @@ -303,4 +290,59 @@ final class PhamePost extends PhameDAO return true; } + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('title') + ->setType('string') + ->setDescription(pht('Title of the post.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('slug') + ->setType('string') + ->setDescription(pht('Slug for the post.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('blogPHID') + ->setType('phid') + ->setDescription(pht('PHID of the blog that the post belongs to.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('authorPHID') + ->setType('phid') + ->setDescription(pht('PHID of the author of the post.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('body') + ->setType('string') + ->setDescription(pht('Body of the post.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('datePublished') + ->setType('epoch?') + ->setDescription(pht('Publish date, if the post has been published.')), + + ); + } + + public function getFieldValuesForConduit() { + if ($this->isDraft()) { + $date_published = null; + } else { + $date_published = (int)$this->getDatePublished(); + } + + return array( + 'title' => $this->getTitle(), + 'slug' => $this->getSlug(), + 'blogPHID' => $this->getBlogPHID(), + 'authorPHID' => $this->getBloggerPHID(), + 'body' => $this->getBody(), + 'datePublished' => $date_published, + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + } diff --git a/src/applications/phame/storage/PhamePostTransaction.php b/src/applications/phame/storage/PhamePostTransaction.php index ed341e17db..be5dbfd8b5 100644 --- a/src/applications/phame/storage/PhamePostTransaction.php +++ b/src/applications/phame/storage/PhamePostTransaction.php @@ -4,9 +4,9 @@ final class PhamePostTransaction extends PhabricatorApplicationTransaction { const TYPE_TITLE = 'phame.post.title'; - const TYPE_PHAME_TITLE = 'phame.post.phame.title'; const TYPE_BODY = 'phame.post.body'; const TYPE_VISIBILITY = 'phame.post.visibility'; + const TYPE_BLOG = 'phame.post.blog'; const MAILTAG_CONTENT = 'phame-post-content'; const MAILTAG_SUBSCRIBERS = 'phame-post-subscribers'; @@ -38,30 +38,45 @@ final class PhamePostTransaction } public function shouldHide() { - $old = $this->getOldValue(); - switch ($this->getTransactionType()) { - case self::TYPE_PHAME_TITLE: - case self::TYPE_BODY: - return ($old === null); - } return parent::shouldHide(); } - public function getIcon() { - $old = $this->getOldValue(); + public function getRequiredHandlePHIDs() { + $phids = parent::getRequiredHandlePHIDs(); + switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - return 'fa-plus'; - } else { - return 'fa-pencil'; + case self::TYPE_BLOG: + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old) { + $phids[] = $old; + } + + if ($new) { + $phids[] = $new; } break; - case self::TYPE_PHAME_TITLE: - case self::TYPE_BODY: + } + + return $phids; + } + + + public function getIcon() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + switch ($this->getTransactionType()) { + case PhabricatorTransactions::TYPE_CREATE: + return 'fa-plus'; + break; case self::TYPE_VISIBILITY: - return 'fa-pencil'; - break; + if ($new == PhameConstants::VISIBILITY_PUBLISHED) { + return 'fa-globe'; + } else { + return 'fa-eye-slash'; + } + break; } return parent::getIcon(); } @@ -77,7 +92,6 @@ final class PhamePostTransaction $tags[] = self::MAILTAG_SUBSCRIBERS; break; case self::TYPE_TITLE: - case self::TYPE_PHAME_TITLE: case self::TYPE_BODY: $tags[] = self::MAILTAG_CONTENT; break; @@ -98,6 +112,16 @@ final class PhamePostTransaction $type = $this->getTransactionType(); switch ($type) { + case PhabricatorTransactions::TYPE_CREATE: + return pht( + '%s authored this post.', + $this->renderHandleLink($author_phid)); + case self::TYPE_BLOG: + return pht( + '%s moved this post from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($old), + $this->renderHandleLink($new)); case self::TYPE_TITLE: if ($old === null) { return pht( @@ -126,12 +150,6 @@ final class PhamePostTransaction $this->renderHandleLink($author_phid)); } break; - case self::TYPE_PHAME_TITLE: - return pht( - '%s updated the post\'s Phame title to "%s".', - $this->renderHandleLink($author_phid), - rtrim($new, '/')); - break; } return parent::getTitle(); @@ -146,6 +164,18 @@ final class PhamePostTransaction $type = $this->getTransactionType(); switch ($type) { + case PhabricatorTransactions::TYPE_CREATE: + return pht( + '%s authored %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + case self::TYPE_BLOG: + return pht( + '%s moved post "%s" from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid), + $this->renderHandleLink($old), + $this->renderHandleLink($new)); case self::TYPE_TITLE: if ($old === null) { return pht( @@ -178,37 +208,30 @@ final class PhamePostTransaction $this->renderHandleLink($object_phid)); } break; - case self::TYPE_PHAME_TITLE: - return pht( - '%s updated the Phame title for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; } return parent::getTitleForFeed(); } public function getRemarkupBodyForFeed(PhabricatorFeedStory $story) { + $old = $this->getOldValue(); + switch ($this->getTransactionType()) { case self::TYPE_BODY: - return $this->getNewValue(); + if ($old === null) { + return $this->getNewValue(); + } + break; } return null; } public function getColor() { - $old = $this->getOldValue(); - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - return PhabricatorTransactions::COLOR_GREEN; - } - break; + case PhabricatorTransactions::TYPE_CREATE: + return PhabricatorTransactions::COLOR_GREEN; } - return parent::getColor(); } diff --git a/src/applications/phame/view/PhameNextPostView.php b/src/applications/phame/view/PhameNextPostView.php new file mode 100644 index 0000000000..87f4f7aa1a --- /dev/null +++ b/src/applications/phame/view/PhameNextPostView.php @@ -0,0 +1,113 @@ +nextTitle = $title; + $this->nextHref = $href; + return $this; + } + + public function setPrevious($title, $href) { + $this->previousTitle = $title; + $this->previousHref = $href; + return $this; + } + + protected function getTagAttributes() { + $classes = array(); + $classes[] = 'phame-next-post-view'; + $classes[] = 'grouped'; + return array('class' => implode(' ', $classes)); + } + + protected function getTagContent() { + require_celerity_resource('phame-css'); + + $p_icon = id(new PHUIIconView()) + ->setIconFont('fa-angle-left'); + + $previous_icon = phutil_tag( + 'div', + array( + 'class' => 'phame-previous-arrow', + ), + $p_icon); + + $previous_text = phutil_tag( + 'div', + array( + 'class' => 'phame-previous-header', + ), + pht('Previous Post')); + + $previous_title = phutil_tag( + 'div', + array( + 'class' => 'phame-previous-title', + ), + $this->previousTitle); + + $previous = null; + if ($this->previousHref) { + $previous = phutil_tag( + 'a', + array( + 'class' => 'phame-previous', + 'href' => $this->previousHref, + ), + array( + $previous_icon, + $previous_text, + $previous_title, + )); + } + + $n_icon = id(new PHUIIconView()) + ->setIconFont('fa-angle-right'); + + $next_icon = phutil_tag( + 'div', + array( + 'class' => 'phame-next-arrow', + ), + $n_icon); + + $next_text = phutil_tag( + 'div', + array( + 'class' => 'phame-next-header', + ), + pht('Next Post')); + + $next_title = phutil_tag( + 'div', + array( + 'class' => 'phame-next-title', + ), + $this->nextTitle); + + $next = null; + if ($this->nextHref) { + $next = phutil_tag( + 'a', + array( + 'class' => 'phame-next', + 'href' => $this->nextHref, + ), + array( + $next_icon, + $next_text, + $next_title, + )); + } + + return array($previous, $next); + } + +} diff --git a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php index 5b8e797bc2..36c78386f3 100644 --- a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php +++ b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php @@ -493,6 +493,90 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $this->assertFalse((bool)$this->refreshProject($child, $user2)); } + public function testSlugMaps() { + // When querying by slugs, slugs should be normalized and the mapping + // should be reported correctly. + $user = $this->createUser(); + $user->save(); + + $name = 'queryslugproject'; + $name2 = 'QUERYslugPROJECT'; + $slug = 'queryslugextra'; + $slug2 = 'QuErYSlUgExTrA'; + + $project = PhabricatorProject::initializeNewProject($user); + + $xactions = array(); + + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setNewValue($name); + + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setNewValue(array($slug)); + + $this->applyTransactions($project, $user, $xactions); + + $project_query = id(new PhabricatorProjectQuery()) + ->setViewer($user) + ->withSlugs(array($name)); + $project_query->execute(); + $map = $project_query->getSlugMap(); + + $this->assertEqual( + array( + $name => $project->getPHID(), + ), + ipull($map, 'projectPHID')); + + $project_query = id(new PhabricatorProjectQuery()) + ->setViewer($user) + ->withSlugs(array($slug)); + $project_query->execute(); + $map = $project_query->getSlugMap(); + + $this->assertEqual( + array( + $slug => $project->getPHID(), + ), + ipull($map, 'projectPHID')); + + $project_query = id(new PhabricatorProjectQuery()) + ->setViewer($user) + ->withSlugs(array($name, $slug, $name2, $slug2)); + $project_query->execute(); + $map = $project_query->getSlugMap(); + + $expect = array( + $name => $project->getPHID(), + $slug => $project->getPHID(), + $name2 => $project->getPHID(), + $slug2 => $project->getPHID(), + ); + + $actual = ipull($map, 'projectPHID'); + + ksort($expect); + ksort($actual); + + $this->assertEqual($expect, $actual); + + $expect = array( + $name => $name, + $slug => $slug, + $name2 => $name, + $slug2 => $slug, + ); + + $actual = ipull($map, 'slug'); + + ksort($expect); + ksort($actual); + + $this->assertEqual($expect, $actual); + } + private function attemptProjectEdit( PhabricatorProject $proj, PhabricatorUser $user, @@ -627,6 +711,157 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { pht('Leave allowed without any permission.')); } + + public function testComplexConstraints() { + $user = $this->createUser(); + $user->save(); + + $engineering = $this->createProject($user); + $engineering_scan = $this->createProject($user, $engineering); + $engineering_warp = $this->createProject($user, $engineering); + + $exploration = $this->createProject($user); + $exploration_diplomacy = $this->createProject($user, $exploration); + + $task_engineering = $this->newTask( + $user, + array($engineering), + pht('Engineering Only')); + + $task_exploration = $this->newTask( + $user, + array($exploration), + pht('Exploration Only')); + + $task_warp_explore = $this->newTask( + $user, + array($engineering_warp, $exploration), + pht('Warp to New Planet')); + + $task_diplomacy_scan = $this->newTask( + $user, + array($engineering_scan, $exploration_diplomacy), + pht('Scan Diplomat')); + + $task_diplomacy = $this->newTask( + $user, + array($exploration_diplomacy), + pht('Diplomatic Meeting')); + + $task_warp_scan = $this->newTask( + $user, + array($engineering_scan, $engineering_warp), + pht('Scan Warp Drives')); + + $this->assertQueryByProjects( + $user, + array( + $task_engineering, + $task_warp_explore, + $task_diplomacy_scan, + $task_warp_scan, + ), + array($engineering), + pht('All Engineering')); + + $this->assertQueryByProjects( + $user, + array( + $task_diplomacy_scan, + $task_warp_scan, + ), + array($engineering_scan), + pht('All Scan')); + + $this->assertQueryByProjects( + $user, + array( + $task_warp_explore, + $task_diplomacy_scan, + ), + array($engineering, $exploration), + pht('Engineering + Exploration')); + + // This is testing that a query for "Parent" and "Parent > Child" works + // properly. + $this->assertQueryByProjects( + $user, + array( + $task_diplomacy_scan, + $task_warp_scan, + ), + array($engineering, $engineering_scan), + pht('Engineering + Scan')); + } + + private function newTask( + PhabricatorUser $viewer, + array $projects, + $name = null) { + + $task = ManiphestTask::initializeNewTask($viewer); + + if (!strlen($name)) { + $name = pht('Test Task'); + } + + $xactions = array(); + + $xactions[] = id(new ManiphestTransaction()) + ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setNewValue($name); + + if ($projects) { + $xactions[] = id(new ManiphestTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue( + 'edge:type', + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST) + ->setNewValue( + array( + '=' => array_fuse(mpull($projects, 'getPHID')), + )); + } + + $editor = id(new ManiphestTransactionEditor()) + ->setActor($viewer) + ->setContentSource(PhabricatorContentSource::newConsoleSource()) + ->setContinueOnNoEffect(true) + ->applyTransactions($task, $xactions); + + return $task; + } + + private function assertQueryByProjects( + PhabricatorUser $viewer, + array $expect, + array $projects, + $label = null) { + + $datasource = id(new PhabricatorProjectLogicalDatasource()) + ->setViewer($viewer); + + $project_phids = mpull($projects, 'getPHID'); + $constraints = $datasource->evaluateTokens($project_phids); + + $query = id(new ManiphestTaskQuery()) + ->setViewer($viewer); + + $query->withEdgeLogicConstraints( + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, + $constraints); + + $tasks = $query->execute(); + + $expect_phids = mpull($expect, 'getTitle', 'getPHID'); + ksort($expect_phids); + + $actual_phids = mpull($tasks, 'getTitle', 'getPHID'); + ksort($actual_phids); + + $this->assertEqual($expect_phids, $actual_phids, $label); + } + private function refreshProject( PhabricatorProject $project, PhabricatorUser $viewer, @@ -664,15 +899,15 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { ->setNewValue($name); if ($parent) { - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_PARENT) - ->setNewValue($parent->getPHID()); - } - - if ($is_milestone) { - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_MILESTONE) - ->setNewValue(true); + if ($is_milestone) { + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_MILESTONE) + ->setNewValue($parent->getPHID()); + } else { + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_PARENT) + ->setNewValue($parent->getPHID()); + } } $this->applyTransactions($project, $user, $xactions); diff --git a/src/applications/project/application/PhabricatorProjectApplication.php b/src/applications/project/application/PhabricatorProjectApplication.php index 4a4778cde5..954c46c170 100644 --- a/src/applications/project/application/PhabricatorProjectApplication.php +++ b/src/applications/project/application/PhabricatorProjectApplication.php @@ -43,10 +43,10 @@ final class PhabricatorProjectApplication extends PhabricatorApplication { '/project/' => array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorProjectListController', 'filter/(?P[^/]+)/' => 'PhabricatorProjectListController', - 'details/(?P[1-9]\d*)/' - => 'PhabricatorProjectEditDetailsController', 'archive/(?P[1-9]\d*)/' => 'PhabricatorProjectArchiveController', + 'lock/(?P[1-9]\d*)/' + => 'PhabricatorProjectLockController', 'members/(?P[1-9]\d*)/' => 'PhabricatorProjectMembersEditController', 'members/(?P[1-9]\d*)/remove/' @@ -59,7 +59,12 @@ final class PhabricatorProjectApplication extends PhabricatorApplication { => 'PhabricatorProjectViewController', 'picture/(?P[1-9]\d*)/' => 'PhabricatorProjectEditPictureController', - 'create/' => 'PhabricatorProjectEditDetailsController', + $this->getEditRoutePattern('edit/') + => 'PhabricatorProjectEditController', + 'subprojects/(?P[1-9]\d*)/' + => 'PhabricatorProjectSubprojectsController', + 'milestones/(?P[1-9]\d*)/' + => 'PhabricatorProjectMilestonesController', 'board/(?P[1-9]\d*)/'. '(?Pfilter/)?'. '(?:query/(?P[^/]+)/)?' @@ -91,21 +96,9 @@ final class PhabricatorProjectApplication extends PhabricatorApplication { } public function getQuickCreateItems(PhabricatorUser $viewer) { - $can_create = PhabricatorPolicyFilter::hasCapability( - $viewer, - $this, - ProjectCreateProjectsCapability::CAPABILITY); - - $items = array(); - if ($can_create) { - $item = id(new PHUIListItemView()) - ->setName(pht('Project')) - ->setIcon('fa-briefcase') - ->setHref($this->getBaseURI().'create/'); - $items[] = $item; - } - - return $items; + return id(new PhabricatorProjectEditEngine()) + ->setViewer($viewer) + ->loadQuickCreateItems(); } protected function getCustomCapabilities() { diff --git a/src/applications/project/conduit/ProjectConduitAPIMethod.php b/src/applications/project/conduit/ProjectConduitAPIMethod.php index 6acf320daf..f6e40f38a3 100644 --- a/src/applications/project/conduit/ProjectConduitAPIMethod.php +++ b/src/applications/project/conduit/ProjectConduitAPIMethod.php @@ -26,7 +26,7 @@ abstract class ProjectConduitAPIMethod extends ConduitAPIMethod { $project_slugs = $project->getSlugs(); $project_slugs = array_values(mpull($project_slugs, 'getSlug')); - $project_icon = substr($project->getIcon(), 3); + $project_icon = $project->getDisplayIconKey(); $result[$project->getPHID()] = array( 'id' => $project->getID(), diff --git a/src/applications/project/conduit/ProjectEditConduitAPIMethod.php b/src/applications/project/conduit/ProjectEditConduitAPIMethod.php new file mode 100644 index 0000000000..acdcf4c1bc --- /dev/null +++ b/src/applications/project/conduit/ProjectEditConduitAPIMethod.php @@ -0,0 +1,19 @@ +getValue('icons'); if ($request->getValue('icons')) { $icons = array(); - // the internal 'fa-' prefix is a detail hidden from api clients - // but needs to pre prepended to the values in the icons array: - foreach ($request->getValue('icons') as $value) { - $icons[] = 'fa-'.$value; - } $query->withIcons($icons); } diff --git a/src/applications/project/conduit/ProjectSearchConduitAPIMethod.php b/src/applications/project/conduit/ProjectSearchConduitAPIMethod.php new file mode 100644 index 0000000000..c6b27d5288 --- /dev/null +++ b/src/applications/project/conduit/ProjectSearchConduitAPIMethod.php @@ -0,0 +1,24 @@ + $query->getSlugMap(), + ); + } + +} diff --git a/src/applications/project/config/PhabricatorProjectConfigOptions.php b/src/applications/project/config/PhabricatorProjectConfigOptions.php index 8ec8bc8607..1cb7654732 100644 --- a/src/applications/project/config/PhabricatorProjectConfigOptions.php +++ b/src/applications/project/config/PhabricatorProjectConfigOptions.php @@ -20,6 +20,34 @@ final class PhabricatorProjectConfigOptions } public function getOptions() { + $default_icons = PhabricatorProjectIconSet::getDefaultConfiguration(); + $icons_type = 'custom:PhabricatorProjectTypeConfigOptionType'; + + $icons_description = $this->deformat(pht(<< Icons and Images}. + +Configure a list of icon specifications. Each icon specification should be +a dictionary, which may contain these keys: + + - `key` //Required string.// Internal key identifying the icon. + - `name` //Required string.// Human-readable icon name. + - `icon` //Required string.// Specifies which actual icon image to use. + - `default` //Optional bool.// Selects a default icon. Exactly one icon must + be selected as the default. + - `disabled` //Optional bool.// If true, this icon will no longer be + available for selection when creating or editing projects. + - `special` //Optional string.// Marks an icon as a special icon: + - `milestone` This is the icon for milestones. Exactly one icon must be + selected as the milestone icon. + +You can look at the default configuration below for an example of a valid +configuration. +EOTEXT + )); + + $default_fields = array( 'std:project:internal:description' => true, ); @@ -45,6 +73,9 @@ final class PhabricatorProjectConfigOptions $this->newOption('projects.fields', $custom_field_type, $default_fields) ->setCustomData(id(new PhabricatorProject())->getCustomFieldBaseClass()) ->setDescription(pht('Select and reorder project fields.')), + $this->newOption('projects.icons', $icons_type, $default_icons) + ->setSummary(pht('Adjust project icons.')) + ->setDescription($icons_description), ); } diff --git a/src/applications/project/config/PhabricatorProjectTypeConfigOptionType.php b/src/applications/project/config/PhabricatorProjectTypeConfigOptionType.php new file mode 100644 index 0000000000..8c65dfe35a --- /dev/null +++ b/src/applications/project/config/PhabricatorProjectTypeConfigOptionType.php @@ -0,0 +1,10 @@ +project; } + protected function loadProject() { + $viewer = $this->getViewer(); + $request = $this->getRequest(); + + $id = $request->getURIData('id'); + $slug = $request->getURIData('slug'); + + if ($slug) { + $normal_slug = PhabricatorSlug::normalizeProjectSlug($slug); + $is_abnormal = ($slug !== $normal_slug); + $normal_uri = "/tag/{$normal_slug}/"; + } else { + $is_abnormal = false; + } + + $query = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->needMembers(true) + ->needWatchers(true) + ->needImages(true) + ->needSlugs(true); + + if ($slug) { + $query->withSlugs(array($slug)); + } else { + $query->withIDs(array($id)); + } + + $policy_exception = null; + try { + $project = $query->executeOne(); + } catch (PhabricatorPolicyException $ex) { + $policy_exception = $ex; + $project = null; + } + + if (!$project) { + // This project legitimately does not exist, so just 404 the user. + if (!$policy_exception) { + return new Aphront404Response(); + } + + // Here, the project exists but the user can't see it. If they are + // using a non-canonical slug to view the project, redirect to the + // canonical slug. If they're already using the canonical slug, rethrow + // the exception to give them the policy error. + if ($is_abnormal) { + return id(new AphrontRedirectResponse())->setURI($normal_uri); + } else { + throw $policy_exception; + } + } + + // The user can view the project, but is using a noncanonical slug. + // Redirect to the canonical slug. + $primary_slug = $project->getPrimarySlug(); + if ($slug && ($slug !== $primary_slug)) { + $primary_uri = "/tag/{$primary_slug}/"; + return id(new AphrontRedirectResponse())->setURI($primary_uri); + } + + $this->setProject($project); + + return null; + } + public function buildApplicationMenu() { return $this->buildSideNavView(true)->getMenu(); } @@ -33,7 +99,6 @@ abstract class PhabricatorProjectController extends PhabricatorController { $nav->addFilter("board/{$id}/", pht('Workboard')); $nav->addFilter("members/{$id}/", pht('Members')); $nav->addFilter("feed/{$id}/", pht('Feed')); - $nav->addFilter("details/{$id}/", pht('Edit Details')); } $nav->addFilter('create', pht('Create Project')); } @@ -83,9 +148,51 @@ abstract class PhabricatorProjectController extends PhabricatorController { $nav->addIcon("feed/{$id}/", pht('Feed'), 'fa-newspaper-o'); $nav->addIcon("members/{$id}/", pht('Members'), 'fa-group'); - $nav->addIcon("details/{$id}/", pht('Edit Details'), 'fa-pencil'); + + if (PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) { + if ($project->supportsSubprojects()) { + $subprojects_icon = 'fa-sitemap'; + } else { + $subprojects_icon = 'fa-sitemap grey'; + } + + $key = PhabricatorProjectIconSet::getMilestoneIconKey(); + $milestones_icon = PhabricatorProjectIconSet::getIconIcon($key); + if (!$project->supportsMilestones()) { + $milestones_icon = "{$milestones_icon} grey"; + } + + $nav->addIcon( + "subprojects/{$id}/", + pht('Subprojects'), + $subprojects_icon); + + $nav->addIcon( + "milestones/{$id}/", + pht('Milestones'), + $milestones_icon); + } + return $nav; } + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $project = $this->getProject(); + if ($project) { + $ancestors = $project->getAncestorProjects(); + $ancestors = array_reverse($ancestors); + $ancestors[] = $project; + foreach ($ancestors as $ancestor) { + $crumbs->addTextCrumb( + $ancestor->getName(), + $ancestor->getURI()); + } + } + + return $crumbs; + } + } diff --git a/src/applications/project/controller/PhabricatorProjectEditController.php b/src/applications/project/controller/PhabricatorProjectEditController.php new file mode 100644 index 0000000000..d87910462c --- /dev/null +++ b/src/applications/project/controller/PhabricatorProjectEditController.php @@ -0,0 +1,113 @@ +engine = $engine; + return $this; + } + + public function getEngine() { + return $this->engine; + } + + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + + $engine = id(new PhabricatorProjectEditEngine()) + ->setController($this); + + $this->setEngine($engine); + + $id = $request->getURIData('id'); + if (!$id) { + $parent_id = head($request->getArr('parent')); + if (!$parent_id) { + $parent_id = $request->getStr('parent'); + } + + if ($parent_id) { + $is_milestone = false; + } else { + $parent_id = head($request->getArr('milestone')); + if (!$parent_id) { + $parent_id = $request->getStr('milestone'); + } + $is_milestone = true; + } + + if ($parent_id) { + $query = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )); + + if (ctype_digit($parent_id)) { + $query->withIDs(array($parent_id)); + } else { + $query->withPHIDs(array($parent_id)); + } + + $parent = $query->executeOne(); + + if ($is_milestone) { + if (!$parent->supportsMilestones()) { + $cancel_uri = "/project/milestones/{$parent_id}/"; + return $this->newDialog() + ->setTitle(pht('No Milestones')) + ->appendParagraph( + pht('You can not add milestones to this project.')) + ->addCancelButton($cancel_uri); + } + $engine->setMilestoneProject($parent); + } else { + if (!$parent->supportsSubprojects()) { + $cancel_uri = "/project/subprojects/{$parent_id}/"; + return $this->newDialog() + ->setTitle(pht('No Subprojects')) + ->appendParagraph( + pht('You can not add subprojects to this project.')) + ->addCancelButton($cancel_uri); + } + $engine->setParentProject($parent); + } + + $this->setProject($parent); + } + } + + return $engine->buildResponse(); + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $engine = $this->getEngine(); + if ($engine) { + $parent = $engine->getParentProject(); + if ($parent) { + $id = $parent->getID(); + $crumbs->addTextCrumb( + pht('Subprojects'), + $this->getApplicationURI("subprojects/{$id}/")); + } + + $milestone = $engine->getMilestoneProject(); + if ($milestone) { + $id = $milestone->getID(); + $crumbs->addTextCrumb( + pht('Milestones'), + $this->getApplicationURI("milestones/{$id}/")); + } + } + + return $crumbs; + } + +} diff --git a/src/applications/project/controller/PhabricatorProjectEditDetailsController.php b/src/applications/project/controller/PhabricatorProjectEditDetailsController.php deleted file mode 100644 index d3a3fb6374..0000000000 --- a/src/applications/project/controller/PhabricatorProjectEditDetailsController.php +++ /dev/null @@ -1,304 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - if ($id) { - $id = $request->getURIData('id'); - $is_new = false; - - $project = id(new PhabricatorProjectQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needSlugs(true) - ->needImages(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$project) { - return new Aphront404Response(); - } - - } else { - $is_new = true; - - $this->requireApplicationCapability( - ProjectCreateProjectsCapability::CAPABILITY); - - $project = PhabricatorProject::initializeNewProject($viewer); - } - - $field_list = PhabricatorCustomField::getObjectFields( - $project, - PhabricatorCustomField::ROLE_EDIT); - $field_list - ->setViewer($viewer) - ->readFieldsFromStorage($project); - - $e_name = true; - $e_slugs = false; - $e_edit = null; - - $v_name = $project->getName(); - $project_slugs = $project->getSlugs(); - $project_slugs = mpull($project_slugs, 'getSlug', 'getSlug'); - $v_primary_slug = $project->getPrimarySlug(); - unset($project_slugs[$v_primary_slug]); - $v_slugs = $project_slugs; - $v_color = $project->getColor(); - $v_icon = $project->getIcon(); - $v_locked = $project->getIsMembershipLocked(); - - $validation_exception = null; - - if ($request->isFormPost()) { - $e_name = null; - $e_slugs = null; - - $v_name = $request->getStr('name'); - $v_slugs = $request->getStrList('slugs'); - $v_view = $request->getStr('can_view'); - $v_edit = $request->getStr('can_edit'); - $v_join = $request->getStr('can_join'); - $v_color = $request->getStr('color'); - $v_icon = $request->getStr('icon'); - $v_locked = $request->getInt('is_membership_locked', 0); - - $type_name = PhabricatorProjectTransaction::TYPE_NAME; - $type_slugs = PhabricatorProjectTransaction::TYPE_SLUGS; - $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; - $type_icon = PhabricatorProjectTransaction::TYPE_ICON; - $type_color = PhabricatorProjectTransaction::TYPE_COLOR; - $type_locked = PhabricatorProjectTransaction::TYPE_LOCKED; - - $xactions = array(); - - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType($type_name) - ->setNewValue($v_name); - - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType($type_slugs) - ->setNewValue($v_slugs); - - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) - ->setNewValue($v_view); - - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType($type_edit) - ->setNewValue($v_edit); - - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_JOIN_POLICY) - ->setNewValue($v_join); - - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType($type_icon) - ->setNewValue($v_icon); - - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType($type_color) - ->setNewValue($v_color); - - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType($type_locked) - ->setNewValue($v_locked); - - $xactions = array_merge( - $xactions, - $field_list->buildFieldTransactionsFromRequest( - new PhabricatorProjectTransaction(), - $request)); - - $editor = id(new PhabricatorProjectTransactionEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true); - - if ($is_new) { - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue( - 'edge:type', - PhabricatorProjectProjectHasMemberEdgeType::EDGECONST) - ->setNewValue( - array( - '+' => array($viewer->getPHID() => $viewer->getPHID()), - )); - } - - try { - - $editor->applyTransactions($project, $xactions); - - if ($request->isAjax()) { - return id(new AphrontAjaxResponse()) - ->setContent(array( - 'phid' => $project->getPHID(), - 'name' => $project->getName(), - )); - } - - $redirect_uri = - $this->getApplicationURI('profile/'.$project->getID().'/'); - - return id(new AphrontRedirectResponse())->setURI($redirect_uri); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - - $e_name = $ex->getShortMessage($type_name); - $e_slugs = $ex->getShortMessage($type_slugs); - $e_edit = $ex->getShortMessage($type_edit); - - $project->setViewPolicy($v_view); - $project->setEditPolicy($v_edit); - $project->setJoinPolicy($v_join); - } - } - - if ($is_new) { - $header_name = pht('Create a New Project'); - $title = pht('Create Project'); - } else { - $header_name = pht('Edit Project'); - $title = pht('Edit Project'); - } - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($project) - ->execute(); - $v_slugs = implode(', ', $v_slugs); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Name')) - ->setName('name') - ->setValue($v_name) - ->setError($e_name)); - $field_list->appendFieldsToForm($form); - - $shades = PhabricatorProjectIconSet::getColorMap(); - - list($can_lock, $lock_message) = $this->explainApplicationCapability( - ProjectCanLockProjectsCapability::CAPABILITY, - pht('You can update the Lock Project setting.'), - pht('You can not update the Lock Project setting.')); - - $form - ->appendChild( - id(new PHUIFormIconSetControl()) - ->setLabel(pht('Icon')) - ->setName('icon') - ->setIconSet(new PhabricatorProjectIconSet()) - ->setValue($v_icon)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Color')) - ->setName('color') - ->setValue($v_color) - ->setOptions($shades)); - - if (!$is_new) { - $form->appendChild( - id(new AphrontFormStaticControl()) - ->setLabel(pht('Primary Hashtag')) - ->setCaption(pht('The primary hashtag is derived from the name.')) - ->setValue($v_primary_slug)); - } - - $form - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Additional Hashtags')) - ->setCaption(pht( - 'Specify a comma-separated list of additional hashtags.')) - ->setName('slugs') - ->setValue($v_slugs) - ->setError($e_slugs)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setName('can_view') - ->setPolicyObject($project) - ->setPolicies($policies) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setName('can_edit') - ->setPolicyObject($project) - ->setPolicies($policies) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setError($e_edit)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setName('can_join') - ->setCaption( - pht('Users who can edit a project can always join a project.')) - ->setPolicyObject($project) - ->setPolicies($policies) - ->setCapability(PhabricatorPolicyCapability::CAN_JOIN)) - ->appendChild( - id(new AphrontFormCheckboxControl()) - ->setLabel(pht('Lock Project')) - ->setDisabled(!$can_lock) - ->addCheckbox( - 'is_membership_locked', - 1, - pht('Prevent members from leaving this project.'), - $v_locked) - ->setCaption($lock_message)); - - if ($request->isAjax()) { - $errors = array(); - if ($validation_exception) { - $errors = mpull($ex->getErrors(), 'getMessage'); - } - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) - ->setWidth(AphrontDialogView::WIDTH_FULL) - ->setTitle($header_name) - ->setErrors($errors) - ->appendForm($form) - ->addSubmitButton($title) - ->addCancelButton('/project/'); - return id(new AphrontDialogResponse())->setDialog($dialog); - } - - $form->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton($this->getApplicationURI()) - ->setValue(pht('Save'))); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->setValidationException($validation_exception) - ->setForm($form); - - if (!$is_new) { - $nav = $this->buildIconNavView($project); - $nav->selectFilter("details/{$id}/"); - $nav->appendChild($form_box); - } else { - $nav = array($form_box); - } - - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, - )); - } -} diff --git a/src/applications/project/controller/PhabricatorProjectFeedController.php b/src/applications/project/controller/PhabricatorProjectFeedController.php index efd82d31d0..44c1c12926 100644 --- a/src/applications/project/controller/PhabricatorProjectFeedController.php +++ b/src/applications/project/controller/PhabricatorProjectFeedController.php @@ -8,38 +8,25 @@ final class PhabricatorProjectFeedController } public function handleRequest(AphrontRequest $request) { - $user = $request->getUser(); + $viewer = $request->getUser(); - $query = id(new PhabricatorProjectQuery()) - ->setViewer($user) - ->needMembers(true) - ->needWatchers(true) - ->needImages(true) - ->needSlugs(true); - $id = $request->getURIData('id'); - $slug = $request->getURIData('slug'); - if ($slug) { - $query->withSlugs(array($slug)); - } else { - $query->withIDs(array($id)); - } - $project = $query->executeOne(); - if (!$project) { - return new Aphront404Response(); - } - if ($slug && $slug != $project->getPrimarySlug()) { - return id(new AphrontRedirectResponse()) - ->setURI('/tag/'.$project->getPrimarySlug().'/'); + $response = $this->loadProject(); + if ($response) { + return $response; } - $query = new PhabricatorFeedQuery(); - $query->setFilterPHIDs( - array( - $project->getPHID(), - )); - $query->setLimit(50); - $query->setViewer($request->getUser()); - $stories = $query->execute(); + $project = $this->getProject(); + $id = $project->getID(); + + $stories = id(new PhabricatorFeedQuery()) + ->setViewer($viewer) + ->setFilterPHIDs( + array( + $project->getPHID(), + )) + ->setLimit(50) + ->execute(); + $feed = $this->renderStories($stories); $box = id(new PHUIObjectBoxView()) @@ -48,28 +35,15 @@ final class PhabricatorProjectFeedController $nav = $this->buildIconNavView($project); $nav->selectFilter("feed/{$id}/"); - $nav->appendChild($box); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $project->getName(), - )); - } + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Feed')); - private function renderFeedPage(PhabricatorProject $project) { - - $query = new PhabricatorFeedQuery(); - $query->setFilterPHIDs(array($project->getPHID())); - $query->setViewer($this->getRequest()->getUser()); - $query->setLimit(100); - $stories = $query->execute(); - - if (!$stories) { - return pht('There are no stories about this project.'); - } - - return $this->renderStories($stories); + return $this->newPage() + ->setNavigation($nav) + ->setCrumbs($crumbs) + ->setTitle(array($project->getName(), pht('Feed'))) + ->appendChild($box); } private function renderStories(array $stories) { @@ -85,5 +59,4 @@ final class PhabricatorProjectFeedController $view->render()); } - } diff --git a/src/applications/project/controller/PhabricatorProjectListController.php b/src/applications/project/controller/PhabricatorProjectListController.php index ea56036c7b..db4ef5b9f5 100644 --- a/src/applications/project/controller/PhabricatorProjectListController.php +++ b/src/applications/project/controller/PhabricatorProjectListController.php @@ -26,16 +26,9 @@ final class PhabricatorProjectListController protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $can_create = $this->hasApplicationCapability( - ProjectCreateProjectsCapability::CAPABILITY); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('Create Project')) - ->setHref($this->getApplicationURI('create/')) - ->setIcon('fa-plus-square') - ->setWorkflow(!$can_create) - ->setDisabled(!$can_create)); + id(new PhabricatorProjectEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); return $crumbs; } diff --git a/src/applications/project/controller/PhabricatorProjectLockController.php b/src/applications/project/controller/PhabricatorProjectLockController.php new file mode 100644 index 0000000000..744be32f99 --- /dev/null +++ b/src/applications/project/controller/PhabricatorProjectLockController.php @@ -0,0 +1,76 @@ +getViewer(); + + $this->requireApplicationCapability( + ProjectCanLockProjectsCapability::CAPABILITY); + + $id = $request->getURIData('id'); + $project = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$project) { + return new Aphront404Response(); + } + + $done_uri = $project->getURI(); + $is_locked = $project->getIsMembershipLocked(); + + if ($request->isFormPost()) { + $xactions = array(); + + if ($is_locked) { + $new_value = 0; + } else { + $new_value = 1; + } + + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_LOCKED) + ->setNewValue($new_value); + + $editor = id(new PhabricatorProjectTransactionEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->applyTransactions($project, $xactions); + + return id(new AphrontRedirectResponse())->setURI($done_uri); + } + + if ($project->getIsMembershipLocked()) { + $title = pht('Unlock Project'); + $body = pht( + 'If you unlock this project, members will be free to leave.'); + $button = pht('Unlock Project'); + } else { + $title = pht('Lock Project'); + $body = pht( + 'If you lock this project, members will be prevented from '. + 'leaving it.'); + $button = pht('Lock Project'); + } + + return $this->newDialog() + ->setTitle($title) + ->appendParagraph($body) + ->addSubmitbutton($button) + ->addCancelButton($done_uri); + } + +} diff --git a/src/applications/project/controller/PhabricatorProjectMembersEditController.php b/src/applications/project/controller/PhabricatorProjectMembersEditController.php index c7a9144188..099443fa66 100644 --- a/src/applications/project/controller/PhabricatorProjectMembersEditController.php +++ b/src/applications/project/controller/PhabricatorProjectMembersEditController.php @@ -12,10 +12,6 @@ final class PhabricatorProjectMembersEditController ->withIDs(array($id)) ->needMembers(true) ->needImages(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - )) ->executeOne(); if (!$project) { return new Aphront404Response(); @@ -72,9 +68,11 @@ final class PhabricatorProjectMembersEditController $project, PhabricatorPolicyCapability::CAN_EDIT); + $supports_edit = $project->supportsEditMembers(); + $form_box = null; $title = pht('Add Members'); - if ($can_edit) { + if ($can_edit && $supports_edit) { $header_name = pht('Edit Members'); $view_uri = $this->getApplicationURI('profile/'.$project->getID().'/'); @@ -99,14 +97,16 @@ final class PhabricatorProjectMembersEditController $nav = $this->buildIconNavView($project); $nav->selectFilter("members/{$id}/"); - $nav->appendChild($form_box); - $nav->appendChild($member_list); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, - )); + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Members')); + + return $this->newPage() + ->setNavigation($nav) + ->setCrumbs($crumbs) + ->setTitle(array($project->getName(), $title)) + ->appendChild($form_box) + ->appendChild($member_list); } private function renderMemberList( diff --git a/src/applications/project/controller/PhabricatorProjectMilestonesController.php b/src/applications/project/controller/PhabricatorProjectMilestonesController.php new file mode 100644 index 0000000000..532e448a18 --- /dev/null +++ b/src/applications/project/controller/PhabricatorProjectMilestonesController.php @@ -0,0 +1,92 @@ +getViewer(); + + $response = $this->loadProject(); + if ($response) { + return $response; + } + + $project = $this->getProject(); + $id = $project->getID(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $project, + PhabricatorPolicyCapability::CAN_EDIT); + + $has_support = $project->supportsMilestones(); + if ($has_support) { + $milestones = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withParentProjectPHIDs(array($project->getPHID())) + ->needImages(true) + ->withIsMilestone(true) + ->setOrder('newest') + ->execute(); + } else { + $milestones = array(); + } + + $can_create = $can_edit && $has_support; + + if ($project->getHasMilestones()) { + $button_text = pht('Create Next Milestone'); + } else { + $button_text = pht('Add Milestones'); + } + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Milestones')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setHref("/project/edit/?milestone={$id}") + ->setIconFont('fa-plus') + ->setDisabled(!$can_create) + ->setWorkflow(!$can_create) + ->setText($button_text)); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header); + + if (!$has_support) { + $no_support = pht( + 'This project is a milestone. Milestones can not have their own '. + 'milestones.'); + + $info_view = id(new PHUIInfoView()) + ->setErrors(array($no_support)) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING); + + $box->setInfoView($info_view); + } + + $box->setObjectList( + id(new PhabricatorProjectListView()) + ->setUser($viewer) + ->setProjects($milestones) + ->renderList()); + + $nav = $this->buildIconNavView($project); + $nav->selectFilter("milestones/{$id}/"); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Milestones')); + + return $this->newPage() + ->setNavigation($nav) + ->setCrumbs($crumbs) + ->setTitle(array($project->getName(), pht('Milestones'))) + ->appendChild($box); + } + +} diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index 89880bdd1a..9ee3526c4f 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -8,35 +8,20 @@ final class PhabricatorProjectProfileController } public function handleRequest(AphrontRequest $request) { - $user = $request->getUser(); + $viewer = $request->getUser(); - $query = id(new PhabricatorProjectQuery()) - ->setViewer($user) - ->needMembers(true) - ->needWatchers(true) - ->needImages(true) - ->needSlugs(true); - $id = $request->getURIData('id'); - $slug = $request->getURIData('slug'); - if ($slug) { - $query->withSlugs(array($slug)); - } else { - $query->withIDs(array($id)); - } - $project = $query->executeOne(); - if (!$project) { - return new Aphront404Response(); - } - if ($slug && $slug != $project->getPrimarySlug()) { - return id(new AphrontRedirectResponse()) - ->setURI('/tag/'.$project->getPrimarySlug().'/'); + $response = $this->loadProject(); + if ($response) { + return $response; } + $project = $this->getProject(); + $id = $project->getID(); $picture = $project->getProfileImageURI(); $header = id(new PHUIHeaderView()) ->setHeader($project->getName()) - ->setUser($user) + ->setUser($viewer) ->setPolicyObject($project) ->setImage($picture); @@ -60,15 +45,16 @@ final class PhabricatorProjectProfileController $nav = $this->buildIconNavView($project); $nav->selectFilter("profile/{$id}/"); - $nav->appendChild($object_box); - $nav->appendChild($timeline); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $project->getName(), - 'pageObjects' => array($project->getPHID()), - )); + $crumbs = $this->buildApplicationCrumbs(); + + return $this->newPage() + ->setNavigation($nav) + ->setCrumbs($crumbs) + ->setTitle($project->getName()) + ->setPageObjectPHIDs(array($project->getPHID())) + ->appendChild($object_box) + ->appendChild($timeline); } private function buildActionListView(PhabricatorProject $project) { @@ -90,7 +76,7 @@ final class PhabricatorProjectProfileController id(new PhabricatorActionView()) ->setName(pht('Edit Details')) ->setIcon('fa-pencil') - ->setHref($this->getApplicationURI("details/{$id}/")) + ->setHref($this->getApplicationURI("edit/{$id}/")) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); @@ -120,6 +106,25 @@ final class PhabricatorProjectProfileController ->setWorkflow(true)); } + $can_lock = $can_edit && $this->hasApplicationCapability( + ProjectCanLockProjectsCapability::CAPABILITY); + + if ($project->getIsMembershipLocked()) { + $lock_name = pht('Unlock Project'); + $lock_icon = 'fa-unlock'; + } else { + $lock_name = pht('Lock Project'); + $lock_icon = 'fa-lock'; + } + + $view->addAction( + id(new PhabricatorActionView()) + ->setName($lock_name) + ->setIcon($lock_icon) + ->setHref($this->getApplicationURI("lock/{$id}/")) + ->setDisabled(!$can_lock) + ->setWorkflow(true)); + $action = null; if (!$project->isUserMember($viewer->getPHID())) { $can_join = PhabricatorPolicyFilter::hasCapability( @@ -181,7 +186,9 @@ final class PhabricatorProjectProfileController ->setName('#'.$slug->getSlug()); } - $view->addProperty(pht('Hashtags'), phutil_implode_html(' ', $hashtags)); + if ($hashtags) { + $view->addProperty(pht('Hashtags'), phutil_implode_html(' ', $hashtags)); + } $view->addProperty( pht('Members'), diff --git a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php new file mode 100644 index 0000000000..a75a9ebc5f --- /dev/null +++ b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php @@ -0,0 +1,91 @@ +getViewer(); + + $response = $this->loadProject(); + if ($response) { + return $response; + } + + $project = $this->getProject(); + $id = $project->getID(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $project, + PhabricatorPolicyCapability::CAN_EDIT); + + $has_support = $project->supportsSubprojects(); + + if ($has_support) { + $subprojects = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withParentProjectPHIDs(array($project->getPHID())) + ->needImages(true) + ->withIsMilestone(false) + ->execute(); + } else { + $subprojects = array(); + } + + $can_create = $can_edit && $has_support; + + if ($project->getHasSubprojects()) { + $button_text = pht('Create Subproject'); + } else { + $button_text = pht('Add Subprojects'); + } + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Subprojects')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setHref("/project/edit/?parent={$id}") + ->setIconFont('fa-plus') + ->setDisabled(!$can_create) + ->setWorkflow(!$can_create) + ->setText($button_text)); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header); + + if (!$has_support) { + $no_support = pht( + 'This project is a milestone. Milestones can not have subprojects.'); + + $info_view = id(new PHUIInfoView()) + ->setErrors(array($no_support)) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING); + + $box->setInfoView($info_view); + } + + $box->setObjectList( + id(new PhabricatorProjectListView()) + ->setUser($viewer) + ->setProjects($subprojects) + ->renderList()); + + $nav = $this->buildIconNavView($project); + $nav->selectFilter("subprojects/{$id}/"); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Subprojects')); + + return $this->newPage() + ->setNavigation($nav) + ->setCrumbs($crumbs) + ->setTitle(array($project->getName(), pht('Subprojects'))) + ->appendChild($box); + } + +} diff --git a/src/applications/project/controller/PhabricatorProjectViewController.php b/src/applications/project/controller/PhabricatorProjectViewController.php index bfc9827ab5..087971878b 100644 --- a/src/applications/project/controller/PhabricatorProjectViewController.php +++ b/src/applications/project/controller/PhabricatorProjectViewController.php @@ -11,31 +11,11 @@ final class PhabricatorProjectViewController $request = $this->getRequest(); $viewer = $request->getViewer(); - $query = id(new PhabricatorProjectQuery()) - ->setViewer($viewer) - ->needMembers(true) - ->needWatchers(true) - ->needImages(true) - ->needSlugs(true); - $id = $request->getURIData('id'); - $slug = $request->getURIData('slug'); - if ($slug) { - $query->withSlugs(array($slug)); - } else { - $query->withIDs(array($id)); - } - $project = $query->executeOne(); - if (!$project) { - - // If this request corresponds to a project but just doesn't have the - // slug quite right, redirect to the proper URI. - $uri = $this->getNormalizedURI($slug); - if ($uri !== null) { - return id(new AphrontRedirectResponse())->setURI($uri); - } - - return new Aphront404Response(); + $response = $this->loadProject(); + if ($response) { + return $response; } + $project = $this->getProject(); $columns = id(new PhabricatorProjectColumnQuery()) ->setViewer($viewer) @@ -60,31 +40,4 @@ final class PhabricatorProjectViewController return $this->delegateToController($controller_object); } - private function getNormalizedURI($slug) { - if (!strlen($slug)) { - return null; - } - - $normal = PhabricatorSlug::normalizeProjectSlug($slug); - if ($normal === $slug) { - return null; - } - - $viewer = $this->getViewer(); - - // Do execute() instead of executeOne() here so we canonicalize before - // raising a policy exception. This is a little more polished than letting - // the user hit the error on any variant of the slug. - - $projects = id(new PhabricatorProjectQuery()) - ->setViewer($viewer) - ->withSlugs(array($normal)) - ->execute(); - if (!$projects) { - return null; - } - - return "/tag/{$normal}/"; - } - } diff --git a/src/applications/project/customfield/PhabricatorProjectDescriptionField.php b/src/applications/project/customfield/PhabricatorProjectDescriptionField.php index 0c18398293..5e4e561523 100644 --- a/src/applications/project/customfield/PhabricatorProjectDescriptionField.php +++ b/src/applications/project/customfield/PhabricatorProjectDescriptionField.php @@ -13,7 +13,8 @@ final class PhabricatorProjectDescriptionField 'description' => pht('Short project description.'), 'fulltext' => PhabricatorSearchDocumentFieldType::FIELD_BODY, ), - )); + ), + $internal = true); } } diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index 7626729b8c..54440a6ee9 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -3,6 +3,17 @@ final class PhabricatorProjectTransactionEditor extends PhabricatorApplicationTransactionEditor { + private $isMilestone; + + private function setIsMilestone($is_milestone) { + $this->isMilestone = $is_milestone; + return $this; + } + + private function getIsMilestone() { + return $this->isMilestone; + } + public function getEditorApplicationClass() { return 'PhabricatorProjectApplication'; } @@ -74,23 +85,10 @@ final class PhabricatorProjectTransactionEditor case PhabricatorProjectTransaction::TYPE_COLOR: case PhabricatorProjectTransaction::TYPE_LOCKED: case PhabricatorProjectTransaction::TYPE_PARENT: + case PhabricatorProjectTransaction::TYPE_MILESTONE: return $xaction->getNewValue(); case PhabricatorProjectTransaction::TYPE_SLUGS: return $this->normalizeSlugs($xaction->getNewValue()); - case PhabricatorProjectTransaction::TYPE_MILESTONE: - $current = queryfx_one( - $object->establishConnection('w'), - 'SELECT MAX(milestoneNumber) n - FROM %T - WHERE parentProjectPHID = %s', - $object->getTableName(), - $object->getParentProject()->getPHID()); - if (!$current) { - $number = 1; - } else { - $number = (int)$current['n'] + 1; - } - return $number; } return parent::getCustomTransactionNewValue($object, $xaction); @@ -104,7 +102,9 @@ final class PhabricatorProjectTransactionEditor case PhabricatorProjectTransaction::TYPE_NAME: $name = $xaction->getNewValue(); $object->setName($name); - $object->setPrimarySlug(PhabricatorSlug::normalizeProjectSlug($name)); + if (!$this->getIsMilestone()) { + $object->setPrimarySlug(PhabricatorSlug::normalizeProjectSlug($name)); + } return; case PhabricatorProjectTransaction::TYPE_SLUGS: return; @@ -127,7 +127,9 @@ final class PhabricatorProjectTransactionEditor $object->setParentProjectPHID($xaction->getNewValue()); return; case PhabricatorProjectTransaction::TYPE_MILESTONE: - $object->setMilestoneNumber($xaction->getNewValue()); + $number = $object->getParentProject()->loadNextMilestoneNumber(); + $object->setMilestoneNumber($number); + $object->setParentProjectPHID($xaction->getNewValue()); return; } @@ -239,6 +241,75 @@ final class PhabricatorProjectTransactionEditor return parent::applyBuiltinExternalTransaction($object, $xaction); } + protected function validateAllTransactions( + PhabricatorLiskDAO $object, + array $xactions) { + + $errors = array(); + + // Prevent creating projects which are both subprojects and milestones, + // since this does not make sense, won't work, and will break everything. + $parent_xaction = null; + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case PhabricatorProjectTransaction::TYPE_PARENT: + case PhabricatorProjectTransaction::TYPE_MILESTONE: + if ($xaction->getNewValue() === null) { + continue; + } + + if (!$parent_xaction) { + $parent_xaction = $xaction; + continue; + } + + $errors[] = new PhabricatorApplicationTransactionValidationError( + $xaction->getTransactionType(), + pht('Invalid'), + pht( + 'When creating a project, specify a maximum of one parent '. + 'project or milestone project. A project can not be both a '. + 'subproject and a milestone.'), + $xaction); + break; + break; + } + } + + $is_milestone = $this->getIsMilestone(); + + $is_parent = $object->getHasSubprojects(); + + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case PhabricatorProjectTransaction::TYPE_MEMBERS: + if ($is_parent) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + $xaction->getTransactionType(), + pht('Invalid'), + pht( + 'You can not change members of a project with subprojects '. + 'directly. Members of any subproject are automatically '. + 'members of the parent project.'), + $xaction); + } + + if ($is_milestone) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + $xaction->getTransactionType(), + pht('Invalid'), + pht( + 'You can not change members of a milestone. Members of the '. + 'parent project are automatically members of the milestone.'), + $xaction); + } + break; + } + } + + return $errors; + } + protected function validateTransaction( PhabricatorLiskDAO $object, $type, @@ -267,6 +338,10 @@ final class PhabricatorProjectTransactionEditor break; } + if ($this->getIsMilestone()) { + break; + } + $name = last($xactions)->getNewValue(); if (!PhabricatorSlug::isValidProjectSlug($name)) { @@ -279,20 +354,6 @@ final class PhabricatorProjectTransactionEditor break; } - $name_used_already = id(new PhabricatorProjectQuery()) - ->setViewer($this->getActor()) - ->withNames(array($name)) - ->executeOne(); - if ($name_used_already && - ($name_used_already->getPHID() != $object->getPHID())) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Duplicate'), - pht('Project name is already used.'), - nonempty(last($xactions), null)); - $errors[] = $error; - } - $slug = PhabricatorSlug::normalizeProjectSlug($name); $slug_used_already = id(new PhabricatorProjectSlug()) @@ -302,7 +363,10 @@ final class PhabricatorProjectTransactionEditor $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Duplicate'), - pht('Project name can not be used due to hashtag collision.'), + pht( + 'Project name generates the same hashtag ("%s") as another '. + 'existing project. Choose a unique name.', + '#'.$slug), nonempty(last($xactions), null)); $errors[] = $error; } @@ -367,25 +431,29 @@ final class PhabricatorProjectTransactionEditor break; case PhabricatorProjectTransaction::TYPE_PARENT: + case PhabricatorProjectTransaction::TYPE_MILESTONE: if (!$xactions) { break; } $xaction = last($xactions); + $parent_phid = $xaction->getNewValue(); + if (!$parent_phid) { + continue; + } + if (!$this->getIsNewObject()) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht( - 'You can only set a parent project when creating a project '. - 'for the first time.'), + 'You can only set a parent or milestone project when creating a '. + 'project for the first time.'), $xaction); break; } - $parent_phid = $xaction->getNewValue(); - $projects = id(new PhabricatorProjectQuery()) ->setViewer($this->requireActor()) ->withPHIDs(array($parent_phid)) @@ -400,8 +468,8 @@ final class PhabricatorProjectTransactionEditor $type, pht('Invalid'), pht( - 'Parent project PHID ("%s") must be the PHID of a valid, '. - 'visible project which you have permission to edit.', + 'Parent or milestone project PHID ("%s") must be the PHID of a '. + 'valid, visible project which you have permission to edit.', $parent_phid), $xaction); break; @@ -414,8 +482,8 @@ final class PhabricatorProjectTransactionEditor $type, pht('Invalid'), pht( - 'Parent project PHID ("%s") must not be a milestone. '. - 'Milestones may not have subprojects.', + 'Parent or milestone project PHID ("%s") must not be a '. + 'milestone. Milestones may not have subprojects or milestones.', $parent_phid), $xaction); break; @@ -427,9 +495,9 @@ final class PhabricatorProjectTransactionEditor $type, pht('Invalid'), pht( - 'You can not create a subproject under this parent because '. - 'it would nest projects too deeply. The maximum nesting '. - 'depth of projects is %s.', + 'You can not create a subproject or mielstone under this parent '. + 'because it would nest projects too deeply. The maximum '. + 'nesting depth of projects is %s.', new PhutilNumber($limit)), $xaction); break; @@ -509,12 +577,13 @@ final class PhabricatorProjectTransactionEditor } protected function willPublish(PhabricatorLiskDAO $object, array $xactions) { - $member_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( - $object->getPHID(), - PhabricatorProjectProjectHasMemberEdgeType::EDGECONST); - $object->attachMemberPHIDs($member_phids); - - return $object; + // NOTE: We're using the omnipotent user here because the original actor + // may no longer have permission to view the object. + return id(new PhabricatorProjectQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs(array($object->getPHID())) + ->needMembers(true) + ->executeOne(); } protected function shouldSendMail( @@ -528,12 +597,9 @@ final class PhabricatorProjectTransactionEditor } protected function getMailTo(PhabricatorLiskDAO $object) { - return $object->getMemberPHIDs(); - } - - protected function getMailCC(PhabricatorLiskDAO $object) { - $all = parent::getMailCC($object); - return array_diff($all, $object->getMemberPHIDs()); + return array( + $this->getActingAsPHID(), + ); } public function getMailTagsMap() { @@ -610,6 +676,7 @@ final class PhabricatorProjectTransactionEditor array $xactions) { $materialize = false; + $new_parent = null; foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorTransactions::TYPE_EDGE: @@ -621,10 +688,34 @@ final class PhabricatorProjectTransactionEditor break; case PhabricatorProjectTransaction::TYPE_PARENT: $materialize = true; + $new_parent = $object->getParentProject(); break; } } + if ($new_parent) { + // If we just created the first subproject of this parent, we want to + // copy all of the real members to the subproject. + if (!$new_parent->getHasSubprojects()) { + $member_type = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; + + $project_members = PhabricatorEdgeQuery::loadDestinationPHIDs( + $new_parent->getPHID(), + $member_type); + + if ($project_members) { + $editor = id(new PhabricatorEdgeEditor()); + foreach ($project_members as $phid) { + $editor->addEdge($object->getPHID(), $member_type, $phid); + } + $editor->save(); + } + } + } + + // TODO: We should dump an informational transaction onto the parent + // project to show that we created the sub-thing. + if ($materialize) { id(new PhabricatorProjectsMembershipIndexEngineExtension()) ->rematerialize($object); @@ -719,9 +810,12 @@ final class PhabricatorProjectTransactionEditor $object_phid = $object->getPHID(); if ($object_phid) { - $members = PhabricatorEdgeQuery::loadDestinationPHIDs( - $object_phid, - PhabricatorProjectProjectHasMemberEdgeType::EDGECONST); + $project = id(new PhabricatorProjectQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($object_phid)) + ->needMembers(true) + ->executeOne(); + $members = $project->getMemberPHIDs(); } else { $members = array(); } @@ -741,4 +835,51 @@ final class PhabricatorProjectTransactionEditor return $copy; } + protected function expandTransactions( + PhabricatorLiskDAO $object, + array $xactions) { + + $actor = $this->getActor(); + $actor_phid = $actor->getPHID(); + + $results = parent::expandTransactions($object, $xactions); + + // Automatically add the author as a member when they create a project + // if they're using the web interface. + + $content_source = $this->getContentSource(); + $source_web = PhabricatorContentSource::SOURCE_WEB; + $is_web = ($content_source->getSource() === $source_web); + + if ($this->getIsNewObject() && $is_web) { + if ($actor_phid) { + $type_member = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; + + $results[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $type_member) + ->setNewValue( + array( + '+' => array($actor_phid => $actor_phid), + )); + } + } + + $is_milestone = $object->isMilestone(); + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case PhabricatorProjectTransaction::TYPE_MILESTONE: + if ($xaction->getNewValue() !== null) { + $is_milestone = true; + } + break; + } + } + + $this->setIsMilestone($is_milestone); + + return $results; + } + + } diff --git a/src/applications/project/engine/PhabricatorProjectEditEngine.php b/src/applications/project/engine/PhabricatorProjectEditEngine.php new file mode 100644 index 0000000000..389c585fce --- /dev/null +++ b/src/applications/project/engine/PhabricatorProjectEditEngine.php @@ -0,0 +1,244 @@ +parentProject = $parent_project; + return $this; + } + + public function getParentProject() { + return $this->parentProject; + } + + public function setMilestoneProject(PhabricatorProject $milestone_project) { + $this->milestoneProject = $milestone_project; + return $this; + } + + public function getMilestoneProject() { + return $this->milestoneProject; + } + + public function getEngineName() { + return pht('Projects'); + } + + public function getSummaryHeader() { + return pht('Configure Project Forms'); + } + + public function getSummaryText() { + return pht('Configure forms for creating projects.'); + } + + public function getEngineApplicationClass() { + return 'PhabricatorProjectApplication'; + } + + protected function newEditableObject() { + $project = PhabricatorProject::initializeNewProject($this->getViewer()); + + $milestone = $this->getMilestoneProject(); + if ($milestone) { + $default_name = pht( + 'Milestone %s', + new PhutilNumber($milestone->loadNextMilestoneNumber())); + $project->setName($default_name); + } + + return $project; + } + + protected function newObjectQuery() { + return id(new PhabricatorProjectQuery()) + ->needSlugs(true); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Project'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return $object->getName(); + } + + protected function getObjectCreateShortText() { + return pht('Create Project'); + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function getObjectCreateCancelURI($object) { + $parent = $this->getParentProject(); + if ($parent) { + $id = $parent->getID(); + return "/project/subprojects/{$id}/"; + } + + $milestone = $this->getMilestoneProject(); + if ($milestone) { + $id = $milestone->getID(); + return "/project/milestones/{$id}/"; + } + + return parent::getObjectCreateCancelURI($object); + } + + protected function getCreateNewObjectPolicy() { + return $this->getApplication()->getPolicy( + ProjectCreateProjectsCapability::CAPABILITY); + } + + protected function willConfigureFields($object, array $fields) { + $is_milestone = ($this->getMilestoneProject() || $object->isMilestone()); + + $unavailable = array( + PhabricatorTransactions::TYPE_VIEW_POLICY, + PhabricatorTransactions::TYPE_EDIT_POLICY, + PhabricatorTransactions::TYPE_JOIN_POLICY, + PhabricatorProjectTransaction::TYPE_ICON, + PhabricatorProjectTransaction::TYPE_COLOR, + ); + $unavailable = array_fuse($unavailable); + + if ($is_milestone) { + foreach ($fields as $key => $field) { + $xaction_type = $field->getTransactionType(); + if (isset($unavailable[$xaction_type])) { + unset($fields[$key]); + } + } + } + + return $fields; + } + + protected function newBuiltinEngineConfigurations() { + $configuration = head(parent::newBuiltinEngineConfigurations()); + + // TODO: This whole method is clumsy, and the ordering for the custom + // field is especially clumsy. Maybe try to make this more natural to + // express. + + $configuration + ->setFieldOrder( + array( + 'parent', + 'milestone', + 'name', + 'std:project:internal:description', + 'icon', + 'color', + 'slugs', + 'subscriberPHIDs', + )); + + return array( + $configuration, + ); + } + + protected function buildCustomEditFields($object) { + $slugs = mpull($object->getSlugs(), 'getSlug'); + $slugs = array_fuse($slugs); + unset($slugs[$object->getPrimarySlug()]); + $slugs = array_values($slugs); + + $milestone = $this->getMilestoneProject(); + $parent = $this->getParentProject(); + + if ($parent) { + $parent_phid = $parent->getPHID(); + } else { + $parent_phid = null; + } + + if ($milestone) { + $milestone_phid = $milestone->getPHID(); + } else { + $milestone_phid = null; + } + + return array( + id(new PhabricatorHandlesEditField()) + ->setKey('parent') + ->setLabel(pht('Parent')) + ->setDescription(pht('Create a subproject of an existing project.')) + ->setConduitDescription( + pht('Choose a parent project to create a subproject beneath.')) + ->setConduitTypeDescription(pht('PHID of the parent project.')) + ->setAliases(array('parentPHID')) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_PARENT) + ->setHandleParameterType(new AphrontPHIDHTTPParameterType()) + ->setSingleValue($parent_phid) + ->setIsReorderable(false) + ->setIsDefaultable(false) + ->setIsLockable(false) + ->setIsLocked(true), + id(new PhabricatorHandlesEditField()) + ->setKey('milestone') + ->setLabel(pht('Milestone Of')) + ->setDescription(pht('Parent project to create a milestone for.')) + ->setConduitDescription( + pht('Choose a parent project to create a new milestone for.')) + ->setConduitTypeDescription(pht('PHID of the parent project.')) + ->setAliases(array('milestonePHID')) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_MILESTONE) + ->setHandleParameterType(new AphrontPHIDHTTPParameterType()) + ->setSingleValue($milestone_phid) + ->setIsReorderable(false) + ->setIsDefaultable(false) + ->setIsLockable(false) + ->setIsLocked(true), + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setIsRequired(true) + ->setDescription(pht('Project name.')) + ->setConduitDescription(pht('Rename the project')) + ->setConduitTypeDescription(pht('New project name.')) + ->setValue($object->getName()), + id(new PhabricatorIconSetEditField()) + ->setKey('icon') + ->setLabel(pht('Icon')) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_ICON) + ->setIconSet(new PhabricatorProjectIconSet()) + ->setDescription(pht('Project icon.')) + ->setConduitDescription(pht('Change the project icon.')) + ->setConduitTypeDescription(pht('New project icon.')) + ->setValue($object->getIcon()), + id(new PhabricatorSelectEditField()) + ->setKey('color') + ->setLabel(pht('Color')) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_COLOR) + ->setOptions(PhabricatorProjectIconSet::getColorMap()) + ->setDescription(pht('Project tag color.')) + ->setConduitDescription(pht('Change the project tag color.')) + ->setConduitTypeDescription(pht('New project tag color.')) + ->setValue($object->getColor()), + id(new PhabricatorStringListEditField()) + ->setKey('slugs') + ->setLabel(pht('Additional Hashtags')) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setDescription(pht('Additional project slugs.')) + ->setConduitDescription(pht('Change project slugs.')) + ->setConduitTypeDescription(pht('New list of slugs.')) + ->setValue($slugs), + ); + } + +} diff --git a/src/applications/project/icon/PhabricatorProjectIconSet.php b/src/applications/project/icon/PhabricatorProjectIconSet.php index 470e871096..82592918b1 100644 --- a/src/applications/project/icon/PhabricatorProjectIconSet.php +++ b/src/applications/project/icon/PhabricatorProjectIconSet.php @@ -5,38 +5,121 @@ final class PhabricatorProjectIconSet const ICONSETKEY = 'projects'; + const SPECIAL_MILESTONE = 'milestone'; + public function getSelectIconTitleText() { return pht('Choose Project Icon'); } - protected function newIcons() { - $map = array( - 'fa-briefcase' => pht('Briefcase'), - 'fa-tags' => pht('Tag'), - 'fa-folder' => pht('Folder'), - 'fa-users' => pht('Team'), - - 'fa-bug' => pht('Bug'), - 'fa-trash-o' => pht('Garbage'), - 'fa-calendar' => pht('Deadline'), - 'fa-flag-checkered' => pht('Goal'), - - 'fa-envelope' => pht('Communication'), - 'fa-truck' => pht('Release'), - 'fa-lock' => pht('Policy'), - 'fa-umbrella' => pht('An Umbrella'), - - 'fa-cloud' => pht('The Cloud'), - 'fa-building' => pht('Company'), - 'fa-credit-card' => pht('Accounting'), - 'fa-flask' => pht('Experimental'), + public static function getDefaultConfiguration() { + return array( + array( + 'key' => 'project', + 'icon' => 'fa-briefcase', + 'name' => pht('Project'), + 'default' => true, + ), + array( + 'key' => 'tag', + 'icon' => 'fa-tags', + 'name' => pht('Tag'), + ), + array( + 'key' => 'policy', + 'icon' => 'fa-lock', + 'name' => pht('Policy'), + ), + array( + 'key' => 'group', + 'icon' => 'fa-users', + 'name' => pht('Group'), + ), + array( + 'key' => 'folder', + 'icon' => 'fa-folder', + 'name' => pht('Folder'), + ), + array( + 'key' => 'timeline', + 'icon' => 'fa-calendar', + 'name' => pht('Timeline'), + ), + array( + 'key' => 'goal', + 'icon' => 'fa-flag-checkered', + 'name' => pht('Goal'), + ), + array( + 'key' => 'release', + 'icon' => 'fa-truck', + 'name' => pht('Release'), + ), + array( + 'key' => 'bugs', + 'icon' => 'fa-bug', + 'name' => pht('Bugs'), + ), + array( + 'key' => 'cleanup', + 'icon' => 'fa-trash-o', + 'name' => pht('Cleanup'), + ), + array( + 'key' => 'umbrella', + 'icon' => 'fa-umbrella', + 'name' => pht('Umbrella'), + ), + array( + 'key' => 'communication', + 'icon' => 'fa-envelope', + 'name' => pht('Communication'), + ), + array( + 'key' => 'organization', + 'icon' => 'fa-building', + 'name' => pht('Organization'), + ), + array( + 'key' => 'infrastructure', + 'icon' => 'fa-cloud', + 'name' => pht('Infrastructure'), + ), + array( + 'key' => 'account', + 'icon' => 'fa-credit-card', + 'name' => pht('Account'), + ), + array( + 'key' => 'experimental', + 'icon' => 'fa-flask', + 'name' => pht('Experimental'), + ), + array( + 'key' => 'milestone', + 'icon' => 'fa-map-marker', + 'name' => pht('Milestone'), + 'special' => self::SPECIAL_MILESTONE, + ), ); + } + + + protected function newIcons() { + $map = self::getIconSpecifications(); $icons = array(); - foreach ($map as $key => $label) { + foreach ($map as $spec) { + $special = idx($spec, 'special'); + + if ($special === self::SPECIAL_MILESTONE) { + continue; + } + $icons[] = id(new PhabricatorIconSetIcon()) - ->setKey($key) - ->setLabel($label); + ->setKey($spec['key']) + ->setIsDisabled(idx($spec, 'disabled')) + ->setIcon($spec['icon']) + ->setLabel($spec['name']); } return $icons; @@ -52,4 +135,183 @@ final class PhabricatorProjectIconSet return $shades; } + private static function getIconSpecifications() { + return PhabricatorEnv::getEnvConfig('projects.icons'); + } + + public static function getDefaultIconKey() { + $icons = self::getIconSpecifications(); + foreach ($icons as $icon) { + if (idx($icon, 'default')) { + return $icon['key']; + } + } + return null; + } + + public static function getIconIcon($key) { + $spec = self::getIconSpec($key); + return idx($spec, 'icon', null); + } + + public static function getIconName($key) { + $spec = self::getIconSpec($key); + return idx($spec, 'name', null); + } + + private static function getIconSpec($key) { + $icons = self::getIconSpecifications(); + foreach ($icons as $icon) { + if (idx($icon, 'key') === $key) { + return $icon; + } + } + + return array(); + } + + public static function getMilestoneIconKey() { + $icons = self::getIconSpecifications(); + foreach ($icons as $icon) { + if (idx($icon, 'special') === self::SPECIAL_MILESTONE) { + return idx($icon, 'key'); + } + } + return null; + } + + public static function validateConfiguration($config) { + if (!is_array($config)) { + throw new Exception( + pht('Configuration must be a list of project icon specifications.')); + } + + foreach ($config as $idx => $value) { + if (!is_array($value)) { + throw new Exception( + pht( + 'Value for index "%s" should be a dictionary.', + $idx)); + } + + PhutilTypeSpec::checkMap( + $value, + array( + 'key' => 'string', + 'name' => 'string', + 'icon' => 'string', + 'special' => 'optional string', + 'disabled' => 'optional bool', + 'default' => 'optional bool', + )); + + if (!preg_match('/^[a-z]{1,32}\z/', $value['key'])) { + throw new Exception( + pht( + 'Icon key "%s" is not a valid icon key. Icon keys must be 1-32 '. + 'characters long and contain only lowercase letters. For example, '. + '"%s" and "%s" are reasonable keys.', + 'tag', + 'group')); + } + + $special = idx($value, 'special'); + $valid = array( + self::SPECIAL_MILESTONE => true, + ); + + if ($special !== null) { + if (empty($valid[$special])) { + throw new Exception( + pht( + 'Icon special attribute "%s" is not valid. Recognized special '. + 'attributes are: %s.', + $special, + implode(', ', array_keys($valid)))); + } + } + } + + $default = null; + $milestone = null; + $keys = array(); + foreach ($config as $idx => $value) { + $key = $value['key']; + if (isset($keys[$key])) { + throw new Exception( + pht( + 'Project icons must have unique keys, but two icons share the '. + 'same key ("%s").', + $key)); + } else { + $keys[$key] = true; + } + + $is_disabled = idx($value, 'disabled'); + + if (idx($value, 'default')) { + if ($default === null) { + if ($is_disabled) { + throw new Exception( + pht( + 'The project icon marked as the default icon ("%s") must not '. + 'be disabled.', + $key)); + } + $default = $value; + } else { + $original_key = $default['key']; + throw new Exception( + pht( + 'Two different icons ("%s", "%s") are marked as the default '. + 'icon. Only one icon may be marked as the default.', + $key, + $original_key)); + } + } + + $special = idx($value, 'special'); + if ($special === self::SPECIAL_MILESTONE) { + if ($milestone === null) { + if ($is_disabled) { + throw new Exception( + pht( + 'The project icon ("%s") with special attribute "%s" must '. + 'not be disabled', + $key, + self::SPECIAL_MIILESTONE)); + } + $milestone = $value; + } else { + $original_key = $milestone['key']; + throw new Exception( + pht( + 'Two different icons ("%s", "%s") are marked with special '. + 'attribute "%s". Only one icon may be marked with this '. + 'attribute.', + $key, + $original_key, + self::SPECIAL_MILESTONE)); + } + } + } + + if ($default === null) { + throw new Exception( + pht( + 'Project icons must include one icon marked as the "%s" icon, '. + 'but no such icon exists.', + 'default')); + } + + if ($milestone === null) { + throw new Exception( + pht( + 'Project icons must include one icon marked with special attribute '. + '"%s", but no such icon exists.', + self::SPECIAL_MILESTONE)); + } + + } + } diff --git a/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php b/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php index c3d9bdd3fb..b9fdefc6b3 100644 --- a/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php +++ b/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php @@ -42,11 +42,17 @@ final class PhabricatorProjectProjectPHIDType extends PhabricatorPHIDType { $slug = $project->getPrimarySlug(); $handle->setName($name); - $handle->setObjectName('#'.$slug); - $handle->setURI("/tag/{$slug}/"); + + if (strlen($slug)) { + $handle->setObjectName('#'.$slug); + $handle->setURI("/tag/{$slug}/"); + } else { + $handle->setURI("/project/view/{$id}/"); + } + $handle->setImageURI($project->getProfileImageURI()); - $handle->setIcon($project->getIcon()); - $handle->setTagColor($project->getColor()); + $handle->setIcon($project->getDisplayIconIcon()); + $handle->setTagColor($project->getDisplayColor()); if ($project->isArchived()) { $handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED); diff --git a/src/applications/project/query/PhabricatorProjectQuery.php b/src/applications/project/query/PhabricatorProjectQuery.php index c975150fbc..bc14e93a88 100644 --- a/src/applications/project/query/PhabricatorProjectQuery.php +++ b/src/applications/project/query/PhabricatorProjectQuery.php @@ -7,6 +7,8 @@ final class PhabricatorProjectQuery private $phids; private $memberPHIDs; private $slugs; + private $slugNormals; + private $slugMap; private $names; private $nameTokens; private $icons; @@ -157,6 +159,24 @@ final class PhabricatorProjectQuery ); } + public function getSlugMap() { + if ($this->slugMap === null) { + throw new PhutilInvalidStateException('execute'); + } + return $this->slugMap; + } + + protected function willExecute() { + $this->slugMap = array(); + $this->slugNormals = array(); + if ($this->slugs) { + foreach ($this->slugs as $slug) { + $normal = PhabricatorSlug::normalizeProjectSlug($slug); + $this->slugNormals[$slug] = $normal; + } + } + } + protected function loadPage() { return $this->loadStandardPage($this->newResultObject()); } @@ -181,11 +201,11 @@ final class PhabricatorProjectQuery $viewer_phid = $this->getViewer()->getPHID(); - $member_type = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; + $material_type = PhabricatorProjectMaterializedMemberEdgeType::EDGECONST; $watcher_type = PhabricatorObjectHasWatcherEdgeType::EDGECONST; $types = array(); - $types[] = $member_type; + $types[] = $material_type; if ($this->needWatchers) { $types[] = $watcher_type; } @@ -206,11 +226,21 @@ final class PhabricatorProjectQuery // If we only need to know if the viewer is a member, we can restrict // the query to just their PHID. + $any_edges = true; if (!$this->needMembers && !$this->needWatchers) { - $edge_query->withDestinationPHIDs(array($viewer_phid)); + if ($viewer_phid) { + $edge_query->withDestinationPHIDs(array($viewer_phid)); + } else { + // If we don't need members or watchers and don't have a viewer PHID + // (viewer is logged-out or omnipotent), they'll never be a member + // so we don't need to issue this query at all. + $any_edges = false; + } } - $edge_query->execute(); + if ($any_edges) { + $edge_query->execute(); + } $membership_projects = array(); foreach ($projects as $project) { @@ -222,9 +252,13 @@ final class PhabricatorProjectQuery $source_phids = array($project_phid); } - $member_phids = $edge_query->getDestinationPHIDs( - $source_phids, - array($member_type)); + if ($any_edges) { + $member_phids = $edge_query->getDestinationPHIDs( + $source_phids, + array($material_type)); + } else { + $member_phids = array(); + } if (in_array($viewer_phid, $member_phids)) { $membership_projects[$project_phid] = $project; @@ -287,17 +321,7 @@ final class PhabricatorProjectQuery } } - if ($this->needSlugs) { - $slugs = id(new PhabricatorProjectSlug()) - ->loadAllWhere( - 'projectPHID IN (%Ls)', - mpull($projects, 'getPHID')); - $slugs = mgroup($slugs, 'getProjectPHID'); - foreach ($projects as $project) { - $project_slugs = idx($slugs, $project->getPHID(), array()); - $project->attachSlugs($project_slugs); - } - } + $this->loadSlugs($projects); return $projects; } @@ -356,7 +380,7 @@ final class PhabricatorProjectQuery $where[] = qsprintf( $conn, 'slug.slug IN (%Ls)', - $this->slugs); + $this->slugNormals); } if ($this->names !== null) { @@ -464,7 +488,7 @@ final class PhabricatorProjectQuery $conn, 'JOIN %T e ON e.src = p.phid AND e.type = %d', PhabricatorEdgeConfig::TABLE_NAME_EDGE, - PhabricatorProjectProjectHasMemberEdgeType::EDGECONST); + PhabricatorProjectMaterializedMemberEdgeType::EDGECONST); } if ($this->slugs !== null) { @@ -583,4 +607,76 @@ final class PhabricatorProjectQuery return $ancestors; } + private function loadSlugs(array $projects) { + // Build a map from primary slugs to projects. + $primary_map = array(); + foreach ($projects as $project) { + $primary_slug = $project->getPrimarySlug(); + if ($primary_slug === null) { + continue; + } + + $primary_map[$primary_slug] = $project; + } + + // Link up all of the queried slugs which correspond to primary + // slugs. If we can link up everything from this (no slugs were queried, + // or only primary slugs were queried) we don't need to load anything + // else. + $unknown = $this->slugNormals; + foreach ($unknown as $input => $normal) { + if (!isset($primary_map[$normal])) { + continue; + } + + $this->slugMap[$input] = array( + 'slug' => $normal, + 'projectPHID' => $primary_map[$normal]->getPHID(), + ); + + unset($unknown[$input]); + } + + // If we need slugs, we have to load everything. + // If we still have some queried slugs which we haven't mapped, we only + // need to look for them. + // If we've mapped everything, we don't have to do any work. + $project_phids = mpull($projects, 'getPHID'); + if ($this->needSlugs) { + $slugs = id(new PhabricatorProjectSlug())->loadAllWhere( + 'projectPHID IN (%Ls)', + $project_phids); + } else if ($unknown) { + $slugs = id(new PhabricatorProjectSlug())->loadAllWhere( + 'projectPHID IN (%Ls) AND slug IN (%Ls)', + $project_phids, + $unknown); + } else { + $slugs = array(); + } + + // Link up any slugs we were not able to link up earlier. + $extra_map = mpull($slugs, 'getProjectPHID', 'getSlug'); + foreach ($unknown as $input => $normal) { + if (!isset($extra_map[$normal])) { + continue; + } + + $this->slugMap[$input] = array( + 'slug' => $normal, + 'projectPHID' => $extra_map[$normal], + ); + + unset($unknown[$input]); + } + + if ($this->needSlugs) { + $slug_groups = mgroup($slugs, 'getProjectPHID'); + foreach ($projects as $project) { + $project_slugs = idx($slug_groups, $project->getPHID(), array()); + $project->attachSlugs($project_slugs); + } + } + } + } diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php index f68d4e67f8..eca90853cc 100644 --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -13,7 +13,8 @@ final class PhabricatorProjectSearchEngine public function newQuery() { return id(new PhabricatorProjectQuery()) - ->needImages(true); + ->needImages(true) + ->withIsMilestone(false); } protected function buildCustomSearchFields() { @@ -130,6 +131,10 @@ protected function buildQueryFromParameters(array $map) { $set = new PhabricatorProjectIconSet(); foreach ($set->getIcons() as $icon) { + if ($icon->getIsDisabled()) { + continue; + } + $options[$icon->getKey()] = array( id(new PHUIIconView()) ->setIconFont($icon->getIcon()), @@ -164,49 +169,22 @@ protected function buildQueryFromParameters(array $map) { array $handles) { assert_instances_of($projects, 'PhabricatorProject'); $viewer = $this->requireViewer(); - $handles = $viewer->loadHandles(mpull($projects, 'getPHID')); - $list = new PHUIObjectItemListView(); - $list->setUser($viewer); - $can_edit_projects = id(new PhabricatorPolicyFilter()) - ->setViewer($viewer) - ->requireCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT)) - ->apply($projects); - - foreach ($projects as $key => $project) { - $id = $project->getID(); - - $tag_list = id(new PHUIHandleTagListView()) - ->setSlim(true) - ->setHandles(array($handles[$project->getPHID()])); - - $item = id(new PHUIObjectItemView()) - ->setHeader($project->getName()) - ->setHref($this->getApplicationURI("view/{$id}/")) - ->setImageURI($project->getProfileImageURI()) - ->addAttribute($tag_list); - - if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ARCHIVED) { - $item->addIcon('delete-grey', pht('Archived')); - $item->setDisabled(true); - } - - $list->addItem($item); - } - - $result = new PhabricatorApplicationSearchResultView(); - $result->setObjectList($list); - $result->setNoDataString(pht('No projects found.')); - - return $result; + $list = id(new PhabricatorProjectListView()) + ->setUser($viewer) + ->setProjects($projects) + ->renderList(); + return id(new PhabricatorApplicationSearchResultView()) + ->setObjectList($list) + ->setNoDataString(pht('No projects found.')); } protected function getNewUserBody() { $create_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Create a Project')) - ->setHref('/project/create/') + ->setHref('/project/edit/') ->setColor(PHUIButtonView::GREEN); $icon = $this->getApplication()->getFontIcon(); diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php index 223333809f..92c8ac68f8 100644 --- a/src/applications/project/storage/PhabricatorProject.php +++ b/src/applications/project/storage/PhabricatorProject.php @@ -9,7 +9,8 @@ final class PhabricatorProject extends PhabricatorProjectDAO PhabricatorSubscribableInterface, PhabricatorCustomFieldInterface, PhabricatorDestructibleInterface, - PhabricatorFulltextInterface { + PhabricatorFulltextInterface, + PhabricatorConduitResultInterface { protected $name; protected $status = PhabricatorProjectStatus::STATUS_ACTIVE; @@ -44,7 +45,6 @@ final class PhabricatorProject extends PhabricatorProjectDAO private $slugs = self::ATTACHABLE; private $parentProject = self::ATTACHABLE; - const DEFAULT_ICON = 'fa-briefcase'; const DEFAULT_COLOR = 'blue'; const TABLE_DATASOURCE_TOKEN = 'project_datasourcetoken'; @@ -62,9 +62,11 @@ final class PhabricatorProject extends PhabricatorProjectDAO $join_policy = $app->getPolicy( ProjectDefaultJoinCapability::CAPABILITY); + $default_icon = PhabricatorProjectIconSet::getDefaultIconKey(); + return id(new PhabricatorProject()) ->setAuthorPHID($actor->getPHID()) - ->setIcon(self::DEFAULT_ICON) + ->setIcon($default_icon) ->setColor(self::DEFAULT_COLOR) ->setViewPolicy($view_policy) ->setEditPolicy($edit_policy) @@ -87,6 +89,10 @@ final class PhabricatorProject extends PhabricatorProjectDAO } public function getPolicy($capability) { + if ($this->isMilestone()) { + return $this->getParentProject()->getPolicy($capability); + } + switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); @@ -98,6 +104,12 @@ final class PhabricatorProject extends PhabricatorProjectDAO } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + if ($this->isMilestone()) { + return $this->getParentProject()->hasAutomaticCapability( + $capability, + $viewer); + } + $can_edit = PhabricatorPolicyCapability::CAN_EDIT; switch ($capability) { @@ -200,21 +212,12 @@ final class PhabricatorProject extends PhabricatorProjectDAO 'projectPathKey' => 'bytes4', ), self::CONFIG_KEY_SCHEMA => array( - 'key_phid' => null, - 'phid' => array( - 'columns' => array('phid'), - 'unique' => true, - ), 'key_icon' => array( 'columns' => array('icon'), ), 'key_color' => array( 'columns' => array('color'), ), - 'name' => array( - 'columns' => array('name'), - 'unique' => true, - ), 'key_milestone' => array( 'columns' => array('parentProjectPHID', 'milestoneNumber'), 'unique' => true, @@ -307,6 +310,11 @@ final class PhabricatorProject extends PhabricatorProjectDAO return $this->color; } + public function getURI() { + $id = $this->getID(); + return "/project/view/{$id}/"; + } + public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20)); @@ -431,6 +439,80 @@ final class PhabricatorProject extends PhabricatorProjectDAO return $ancestors; } + public function supportsEditMembers() { + if ($this->isMilestone()) { + return false; + } + + if ($this->getHasSubprojects()) { + return false; + } + + return true; + } + + public function supportsMilestones() { + if ($this->isMilestone()) { + return false; + } + + return true; + } + + public function supportsSubprojects() { + if ($this->isMilestone()) { + return false; + } + + return true; + } + + public function loadNextMilestoneNumber() { + $current = queryfx_one( + $this->establishConnection('w'), + 'SELECT MAX(milestoneNumber) n + FROM %T + WHERE parentProjectPHID = %s', + $this->getTableName(), + $this->getPHID()); + + if (!$current) { + $number = 1; + } else { + $number = (int)$current['n'] + 1; + } + + return $number; + } + + public function getDisplayIconKey() { + if ($this->isMilestone()) { + $key = PhabricatorProjectIconSet::getMilestoneIconKey(); + } else { + $key = $this->getIcon(); + } + + return $key; + } + + public function getDisplayIconIcon() { + $key = $this->getDisplayIconKey(); + return PhabricatorProjectIconSet::getIconIcon($key); + } + + public function getDisplayIconName() { + $key = $this->getDisplayIconKey(); + return PhabricatorProjectIconSet::getIconName($key); + } + + public function getDisplayColor() { + if ($this->isMilestone()) { + return self::DEFAULT_COLOR; + } + + return $this->getColor(); + } + /* -( PhabricatorSubscribableInterface )----------------------------------- */ @@ -525,4 +607,41 @@ final class PhabricatorProject extends PhabricatorProjectDAO return new PhabricatorProjectFulltextEngine(); } + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('name') + ->setType('string') + ->setDescription(pht('The name of the project.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('slug') + ->setType('string') + ->setDescription(pht('Primary slug/hashtag.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('icon') + ->setType('map') + ->setDescription(pht('Information about the project icon.')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'name' => $this->getName(), + 'slug' => $this->getPrimarySlug(), + 'icon' => array( + 'key' => $this->getDisplayIconKey(), + 'name' => $this->getDisplayIconName(), + 'icon' => $this->getDisplayIconIcon(), + ), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + } diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php index b928d43a91..b16a4e30ca 100644 --- a/src/applications/project/storage/PhabricatorProjectTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectTransaction.php @@ -99,9 +99,15 @@ final class PhabricatorProjectTransaction public function getTitle() { $old = $this->getOldValue(); $new = $this->getNewValue(); - $author_handle = $this->renderHandleLink($this->getAuthorPHID()); + $author_phid = $this->getAuthorPHID(); + $author_handle = $this->renderHandleLink($author_phid); switch ($this->getTransactionType()) { + case PhabricatorTransactions::TYPE_CREATE: + return pht( + '%s created this project.', + $this->renderHandleLink($author_phid)); + case self::TYPE_NAME: if ($old === null) { return pht( diff --git a/src/applications/project/typeahead/PhabricatorProjectDatasource.php b/src/applications/project/typeahead/PhabricatorProjectDatasource.php index bb8f02cfd4..64f0604b3f 100644 --- a/src/applications/project/typeahead/PhabricatorProjectDatasource.php +++ b/src/applications/project/typeahead/PhabricatorProjectDatasource.php @@ -67,7 +67,7 @@ final class PhabricatorProjectDatasource ->setDisplayType(pht('Project')) ->setURI('/tag/'.$proj->getPrimarySlug().'/') ->setPHID($proj->getPHID()) - ->setIcon($proj->getIcon()) + ->setIcon($proj->getDisplayIconIcon()) ->setColor($proj->getColor()) ->setPriorityType('proj') ->setClosed($closed); diff --git a/src/applications/project/typeahead/PhabricatorProjectLogicalAncestorDatasource.php b/src/applications/project/typeahead/PhabricatorProjectLogicalAncestorDatasource.php new file mode 100644 index 0000000000..070cb88485 --- /dev/null +++ b/src/applications/project/typeahead/PhabricatorProjectLogicalAncestorDatasource.php @@ -0,0 +1,95 @@ +getViewer(); + + $all_projects = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withAncestorProjectPHIDs($phids) + ->execute(); + + foreach ($phids as $phid) { + $map[$phid][] = $phid; + } + + foreach ($all_projects as $project) { + $project_phid = $project->getPHID(); + $map[$project_phid][] = $project_phid; + foreach ($project->getAncestorProjects() as $ancestor) { + $ancestor_phid = $ancestor->getPHID(); + + if (isset($phids[$project_phid]) && isset($phids[$ancestor_phid])) { + // This is a descendant of some other project in the query, so + // we don't need to query for that project. This happens if a user + // runs a query for both "Engineering" and "Engineering > Warp + // Drive". We can only ever match the "Warp Drive" results, so + // we do not need to add the weaker "Engineering" constraint. + $skip[$ancestor_phid] = true; + } + + $map[$ancestor_phid][] = $project_phid; + } + } + } + + foreach ($results as $key => $result) { + if (!is_string($result)) { + continue; + } + + if (empty($map[$result])) { + continue; + } + + // This constraint is implied by another, stronger constraint. + if (isset($skip[$result])) { + unset($results[$key]); + continue; + } + + // If we have duplicates, don't apply the second constraint. + $skip[$result] = true; + + $results[$key] = new PhabricatorQueryConstraint( + PhabricatorQueryConstraint::OPERATOR_ANCESTOR, + $map[$result]); + } + + return $results; + } + +} diff --git a/src/applications/project/typeahead/PhabricatorProjectLogicalAndDatasource.php b/src/applications/project/typeahead/PhabricatorProjectLogicalAndDatasource.php deleted file mode 100644 index a3016820a0..0000000000 --- a/src/applications/project/typeahead/PhabricatorProjectLogicalAndDatasource.php +++ /dev/null @@ -1,36 +0,0 @@ - $result) { - if (is_string($result)) { - $results[$key] = new PhabricatorQueryConstraint( - PhabricatorQueryConstraint::OPERATOR_AND, - $result); - } - } - - return $results; - } - -} diff --git a/src/applications/project/typeahead/PhabricatorProjectLogicalDatasource.php b/src/applications/project/typeahead/PhabricatorProjectLogicalDatasource.php index 1bce988339..b724e9f8ca 100644 --- a/src/applications/project/typeahead/PhabricatorProjectLogicalDatasource.php +++ b/src/applications/project/typeahead/PhabricatorProjectLogicalDatasource.php @@ -18,7 +18,7 @@ final class PhabricatorProjectLogicalDatasource public function getComponentDatasources() { return array( new PhabricatorProjectNoProjectsDatasource(), - new PhabricatorProjectLogicalAndDatasource(), + new PhabricatorProjectLogicalAncestorDatasource(), new PhabricatorProjectLogicalOrNotDatasource(), new PhabricatorProjectLogicalViewerDatasource(), new PhabricatorProjectLogicalUserDatasource(), diff --git a/src/applications/project/view/PhabricatorProjectListView.php b/src/applications/project/view/PhabricatorProjectListView.php new file mode 100644 index 0000000000..87874e3594 --- /dev/null +++ b/src/applications/project/view/PhabricatorProjectListView.php @@ -0,0 +1,62 @@ +projects = $projects; + return $this; + } + + public function getProjects() { + return $this->projects; + } + + public function renderList() { + $viewer = $this->getUser(); + $projects = $this->getProjects(); + + $handles = $viewer->loadHandles(mpull($projects, 'getPHID')); + + $list = id(new PHUIObjectItemListView()) + ->setUser($viewer); + + foreach ($projects as $key => $project) { + $id = $project->getID(); + + $icon = $project->getDisplayIconIcon(); + $color = $project->getColor(); + + $icon_icon = id(new PHUIIconView()) + ->setIconFont("{$icon} {$color}"); + + $icon_name = $project->getDisplayIconName(); + + $item = id(new PHUIObjectItemView()) + ->setHeader($project->getName()) + ->setHref("/project/view/{$id}/") + ->setImageURI($project->getProfileImageURI()) + ->addAttribute( + array( + $icon_icon, + ' ', + $icon_name, + )); + + if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ARCHIVED) { + $item->addIcon('delete-grey', pht('Archived')); + $item->setDisabled(true); + } + + $list->addItem($item); + } + + return $list; + } + + public function render() { + return $this->renderList(); + } + +} diff --git a/src/applications/releeph/controller/product/ReleephProductCreateController.php b/src/applications/releeph/controller/product/ReleephProductCreateController.php index 9772b293fe..2aedf8cdf1 100644 --- a/src/applications/releeph/controller/product/ReleephProductCreateController.php +++ b/src/applications/releeph/controller/product/ReleephProductCreateController.php @@ -121,11 +121,11 @@ final class ReleephProductCreateController extends ReleephProductController { foreach ($repos as $repo_id => $repo) { $repo_name = $repo->getName(); - $callsign = $repo->getCallsign(); - $choices[$repo->getPHID()] = "r{$callsign} ({$repo_name})"; + $display = $repo->getDisplayName(); + $choices[$repo->getPHID()] = "{$display} ({$repo_name})"; } - ksort($choices); + asort($choices); return $choices; } diff --git a/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php b/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php index 2f06f50f13..26e708a00c 100644 --- a/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php +++ b/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php @@ -372,7 +372,7 @@ final class DifferentialReleephRequestFieldSpecification extends Phobject { 'part of a Releeph branch, but also has %d path change(s) not '. 'part of a Releeph branch!', $commit->getCommitIdentifier(), - $repo->getCallsign(), + $repo->getDisplayName(), count($in_branch), count($ex_branch)); phlog($error); diff --git a/src/applications/releeph/query/ReleephProductSearchEngine.php b/src/applications/releeph/query/ReleephProductSearchEngine.php index ca92068b6f..f56125a687 100644 --- a/src/applications/releeph/query/ReleephProductSearchEngine.php +++ b/src/applications/releeph/query/ReleephProductSearchEngine.php @@ -114,9 +114,9 @@ final class ReleephProductSearchEngine phutil_tag( 'a', array( - 'href' => '/diffusion/'.$repo->getCallsign().'/', + 'href' => $repo->getURI(), ), - 'r'.$repo->getCallsign())); + $repo->getMonogram())); $list->addItem($item); } diff --git a/src/applications/repository/customfield/PhabricatorCommitBranchesField.php b/src/applications/repository/customfield/PhabricatorCommitBranchesField.php index 63a807ea55..b018e32e34 100644 --- a/src/applications/repository/customfield/PhabricatorCommitBranchesField.php +++ b/src/applications/repository/customfield/PhabricatorCommitBranchesField.php @@ -26,7 +26,7 @@ final class PhabricatorCommitBranchesField $params = array( 'contains' => $this->getObject()->getCommitIdentifier(), - 'callsign' => $this->getObject()->getRepository()->getCallsign(), + 'repository' => $this->getObject()->getRepository()->getPHID(), ); try { diff --git a/src/applications/repository/customfield/PhabricatorCommitMergedCommitsField.php b/src/applications/repository/customfield/PhabricatorCommitMergedCommitsField.php index 267b440dce..c8a53b387d 100644 --- a/src/applications/repository/customfield/PhabricatorCommitMergedCommitsField.php +++ b/src/applications/repository/customfield/PhabricatorCommitMergedCommitsField.php @@ -39,7 +39,7 @@ final class PhabricatorCommitMergedCommitsField id(new ConduitCall('diffusion.mergedcommitsquery', array( 'commit' => $commit->getCommitIdentifier(), 'limit' => $limit + 1, - 'callsign' => $commit->getRepository()->getCallsign(), + 'repository' => $commit->getRepository()->getPHID(), ))) ->setUser($this->getViewer()) ->execute()); diff --git a/src/applications/repository/customfield/PhabricatorCommitTagsField.php b/src/applications/repository/customfield/PhabricatorCommitTagsField.php index 001d81e960..5f8e1448c2 100644 --- a/src/applications/repository/customfield/PhabricatorCommitTagsField.php +++ b/src/applications/repository/customfield/PhabricatorCommitTagsField.php @@ -26,7 +26,7 @@ final class PhabricatorCommitTagsField $params = array( 'commit' => $this->getObject()->getCommitIdentifier(), - 'callsign' => $this->getObject()->getRepository()->getCallsign(), + 'repository' => $this->getObject()->getRepository()->getPHID(), ); try { diff --git a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php index bccb58e35a..21b0e13413 100644 --- a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php +++ b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php @@ -6,7 +6,7 @@ * * By default, the daemon pulls **every** repository. If you want it to be * responsible for only some repositories, you can launch it with a list of - * PHIDs or callsigns: + * repositories: * * ./phd launch repositorypulllocal -- X Q Z * @@ -228,9 +228,8 @@ final class PhabricatorRepositoryPullLocalDaemon $flags[] = '--no-discovery'; } - $callsign = $repository->getCallsign(); - - $future = new ExecFuture('%s update %Ls -- %s', $bin, $flags, $callsign); + $monogram = $repository->getMonogram(); + $future = new ExecFuture('%s update %Ls -- %s', $bin, $flags, $monogram); // Sometimes, the underlying VCS commands will hang indefinitely. We've // observed this occasionally with GitHub, and other users have observed @@ -303,30 +302,44 @@ final class PhabricatorRepositoryPullLocalDaemon ->setViewer($this->getViewer()); if ($include) { - $query->withCallsigns($include); + $query->withIdentifiers($include); } $repositories = $query->execute(); + $repositories = mpull($repositories, null, 'getPHID'); if ($include) { - $by_callsign = mpull($repositories, null, 'getCallsign'); - foreach ($include as $name) { - if (empty($by_callsign[$name])) { + $map = $query->getIdentifierMap(); + foreach ($include as $identifier) { + if (empty($map[$identifier])) { throw new Exception( pht( - "No repository exists with callsign '%s'!", - $name)); + 'No repository "%s" exists!', + $identifier)); } } } if ($exclude) { - $exclude = array_fuse($exclude); - foreach ($repositories as $key => $repository) { - if (isset($exclude[$repository->getCallsign()])) { - unset($repositories[$key]); + $xquery = id(new PhabricatorRepositoryQuery()) + ->setViewer($this->getViewer()) + ->withIdentifiers($exclude); + + $excluded_repos = $xquery->execute(); + $xmap = $xquery->getIdentifierMap(); + + foreach ($exclude as $identifier) { + if (empty($xmap[$identifier])) { + throw new Exception( + pht( + 'No repository "%s" exists!', + $identifier)); } } + + foreach ($excluded_repos as $excluded_repo) { + unset($repositories[$excluded_repo->getPHID()]); + } } foreach ($repositories as $key => $repository) { diff --git a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php index 407940e474..3c3a0526be 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php @@ -100,8 +100,8 @@ final class PhabricatorRepositoryDiscoveryEngine $this->log( pht( - 'Discovering commits in repository %s.', - $repository->getCallsign())); + 'Discovering commits in repository "%s".', + $repository->getDisplayName())); $this->fillCommitCache(array_values($branches)); @@ -244,7 +244,7 @@ final class PhabricatorRepositoryDiscoveryEngine 'configured URI is "%s". To resolve this error, set the remote URI '. 'to point at the repository root. If you want to import only part '. 'of a Subversion repository, use the "Import Only" option.', - $repository->getCallsign(), + $repository->getDisplayName(), $remote_root, $expect_root)); } diff --git a/src/applications/repository/engine/PhabricatorRepositoryEngine.php b/src/applications/repository/engine/PhabricatorRepositoryEngine.php index 0bfe94a0a6..42ad650a38 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryEngine.php @@ -62,8 +62,21 @@ abstract class PhabricatorRepositoryEngine extends Phobject { * @return void */ protected function verifyGitOrigin(PhabricatorRepository $repository) { - list($remotes) = $repository->execxLocalCommand( - 'remote show -n origin'); + try { + list($remotes) = $repository->execxLocalCommand( + 'remote show -n origin'); + } catch (CommandException $ex) { + throw new PhutilProxyException( + pht( + 'Expected to find a Git working copy at path "%s", but the '. + 'path exists and is not a valid working copy. If you remove '. + 'this directory, the daemons will automatically recreate it '. + 'correctly. Phabricator will not destroy the directory for you '. + 'because it can not be sure that it does not contain important '. + 'data.', + $repository->getLocalPath()), + $ex); + } $matches = null; if (!preg_match('/^\s*Fetch URL:\s*(.*?)\s*$/m', $remotes, $matches)) { diff --git a/src/applications/repository/engine/PhabricatorRepositoryMirrorEngine.php b/src/applications/repository/engine/PhabricatorRepositoryMirrorEngine.php index 8d3dd9f8f9..e4e65aab6f 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryMirrorEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryMirrorEngine.php @@ -37,7 +37,7 @@ final class PhabricatorRepositoryMirrorEngine throw new PhutilAggregateException( pht( 'Exceptions occurred while mirroring the "%s" repository.', - $repository->getCallsign()), + $repository->getDisplayName()), $exceptions); } } diff --git a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php index 299310be27..30ab234027 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php @@ -29,7 +29,6 @@ final class PhabricatorRepositoryPullEngine $is_svn = false; $vcs = $repository->getVersionControlSystem(); - $callsign = $repository->getCallsign(); switch ($vcs) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: @@ -37,9 +36,9 @@ final class PhabricatorRepositoryPullEngine if (!$repository->isHosted()) { $this->skipPull( pht( - "Repository '%s' is a non-hosted Subversion repository, which ". - "does not require a local working copy to be pulled.", - $callsign)); + 'Repository "%s" is a non-hosted Subversion repository, which '. + 'does not require a local working copy to be pulled.', + $repository->getDisplayName())); return; } $is_svn = true; @@ -55,13 +54,12 @@ final class PhabricatorRepositoryPullEngine break; } - $callsign = $repository->getCallsign(); $local_path = $repository->getLocalPath(); if ($local_path === null) { $this->abortPull( pht( - "No local path is configured for repository '%s'.", - $callsign)); + 'No local path is configured for repository "%s".', + $repository->getDisplayName())); } try { @@ -73,8 +71,8 @@ final class PhabricatorRepositoryPullEngine if (!Filesystem::pathExists($local_path)) { $this->logPull( pht( - "Creating a new working copy for repository '%s'.", - $callsign)); + 'Creating a new working copy for repository "%s".', + $repository->getDisplayName())); if ($is_git) { $this->executeGitCreate(); } else if ($is_hg) { @@ -86,8 +84,8 @@ final class PhabricatorRepositoryPullEngine if (!$repository->isHosted()) { $this->logPull( pht( - "Updating the working copy for repository '%s'.", - $callsign)); + 'Updating the working copy for repository "%s".', + $repository->getDisplayName())); if ($is_git) { $this->verifyGitOrigin($repository); $this->executeGitUpdate(); @@ -113,7 +111,10 @@ final class PhabricatorRepositoryPullEngine } catch (Exception $ex) { $this->abortPull( - pht('Pull of "%s" failed: %s', $callsign, $ex->getMessage()), + pht( + "Pull of '%s' failed: %s", + $repository->getDisplayName(), + $ex->getMessage()), $ex); } diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php index 526348d150..03c4745841 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php @@ -7,7 +7,7 @@ final class PhabricatorRepositoryManagementDiscoverWorkflow $this ->setName('discover') ->setExamples('**discover** [__options__] __repository__ ...') - ->setSynopsis(pht('Discover __repository__, named by callsign.')) + ->setSynopsis(pht('Discover __repository__.')) ->setArguments( array( array( @@ -31,14 +31,16 @@ final class PhabricatorRepositoryManagementDiscoverWorkflow if (!$repos) { throw new PhutilArgumentUsageException( - pht('Specify one or more repositories to discover, by callsign.')); + pht('Specify one or more repositories to discover.')); } $console = PhutilConsole::getConsole(); foreach ($repos as $repo) { $console->writeOut( "%s\n", - pht("Discovering '%s'...", $repo->getCallsign())); + pht( + 'Discovering "%s"...', + $repo->getDisplayName())); id(new PhabricatorRepositoryDiscoveryEngine()) ->setRepository($repo) diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php index adee507c51..83447fcc18 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php @@ -9,8 +9,7 @@ final class PhabricatorRepositoryManagementEditWorkflow ->setExamples('**edit** --as __username__ __repository__ ...') ->setSynopsis( pht( - 'Edit __repository__, named by callsign '. - '(will eventually be deprecated by Conduit).')) + 'Edit __repository__ (will eventually be deprecated by Conduit).')) ->setArguments( array( array( @@ -45,7 +44,7 @@ final class PhabricatorRepositoryManagementEditWorkflow if (!$repos) { throw new PhutilArgumentUsageException( - pht('Specify one or more repositories to edit, by callsign.')); + pht('Specify one or more repositories to edit.')); } $console = PhutilConsole::getConsole(); @@ -76,7 +75,11 @@ final class PhabricatorRepositoryManagementEditWorkflow } foreach ($repos as $repo) { - $console->writeOut("%s\n", pht("Editing '%s'...", $repo->getCallsign())); + $console->writeOut( + "%s\n", + pht( + 'Editing "%s"...', + $repo->getDisplayName())); $xactions = array(); diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php index 4da1e03c9b..acb533999b 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php @@ -9,8 +9,7 @@ final class PhabricatorRepositoryManagementImportingWorkflow ->setExamples('**importing** __repository__ ...') ->setSynopsis( pht( - 'Show commits in __repository__, named by callsign, which are '. - 'still importing.')) + 'Show commits in __repository__ which are still importing.')) ->setArguments( array( array( @@ -30,8 +29,7 @@ final class PhabricatorRepositoryManagementImportingWorkflow if (!$repos) { throw new PhutilArgumentUsageException( pht( - 'Specify one or more repositories to find importing commits for, '. - 'by callsign.')); + 'Specify one or more repositories to find importing commits for.')); } $repos = mpull($repos, null, 'getID'); @@ -54,7 +52,7 @@ final class PhabricatorRepositoryManagementImportingWorkflow $repo = $repos[$row['repositoryID']]; $identifier = $row['commitIdentifier']; - $console->writeOut('%s', 'r'.$repo->getCallsign().$identifier); + $console->writeOut('%s', $repo->formatCommitName($identifier)); if (!$args->getArg('simple')) { $status = $row['importStatus']; diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php index a663142987..24f4c68b9b 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php @@ -18,7 +18,7 @@ final class PhabricatorRepositoryManagementListWorkflow ->execute(); if ($repos) { foreach ($repos as $repo) { - $console->writeOut("%s\n", $repo->getCallsign()); + $console->writeOut("%s\n", $repo->getMonogram()); } } else { $console->writeErr("%s\n", pht('There are no repositories.')); diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementMarkImportedWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementMarkImportedWorkflow.php index fb14261df0..034617deb2 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementMarkImportedWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementMarkImportedWorkflow.php @@ -7,7 +7,7 @@ final class PhabricatorRepositoryManagementMarkImportedWorkflow $this ->setName('mark-imported') ->setExamples('**mark-imported** __repository__ ...') - ->setSynopsis(pht('Mark __repository__, named by callsign, as imported.')) + ->setSynopsis(pht('Mark __repository__ as imported.')) ->setArguments( array( array( @@ -26,32 +26,40 @@ final class PhabricatorRepositoryManagementMarkImportedWorkflow if (!$repos) { throw new PhutilArgumentUsageException( - pht('Specify one or more repositories to mark imported, by callsign.')); + pht('Specify one or more repositories to mark imported.')); } $new_importing_value = (bool)$args->getArg('mark-not-imported'); $console = PhutilConsole::getConsole(); foreach ($repos as $repo) { - $callsign = $repo->getCallsign(); + $name = $repo->getDisplayName(); if ($repo->isImporting() && $new_importing_value) { $console->writeOut( "%s\n", - pht("Repository '%s' is already importing.", $callsign)); + pht( + 'Repository "%s" is already importing.', + $name)); } else if (!$repo->isImporting() && !$new_importing_value) { $console->writeOut( "%s\n", - pht("Repository '%s' is already imported.", $callsign)); + pht( + 'Repository "%s" is already imported.', + $name)); } else { if ($new_importing_value) { $console->writeOut( "%s\n", - pht("Marking repository '%s' as importing.", $callsign)); + pht( + 'Marking repository "%s" as importing.', + $name)); } else { $console->writeOut( "%s\n", - pht("Marking repository '%s' as imported.", $callsign)); + pht( + 'Marking repository "%s" as imported.', + $name)); } $repo->setDetail('importing', $new_importing_value); diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php index 4a4c732558..99a2b08c8b 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php @@ -8,7 +8,7 @@ final class PhabricatorRepositoryManagementMirrorWorkflow ->setName('mirror') ->setExamples('**mirror** [__options__] __repository__ ...') ->setSynopsis( - pht('Push __repository__, named by callsign, to mirrors.')) + pht('Push __repository__ to mirrors.')) ->setArguments( array( array( @@ -28,14 +28,16 @@ final class PhabricatorRepositoryManagementMirrorWorkflow if (!$repos) { throw new PhutilArgumentUsageException( pht( - 'Specify one or more repositories to push to mirrors, by callsign.')); + 'Specify one or more repositories to push to mirrors.')); } $console = PhutilConsole::getConsole(); foreach ($repos as $repo) { $console->writeOut( "%s\n", - pht('Pushing "%s" to mirrors...', $repo->getCallsign())); + pht( + "Pushing '%s' to mirrors...", + $repo->getDisplayName())); $engine = id(new PhabricatorRepositoryMirrorEngine()) ->setRepository($repo) @@ -43,7 +45,7 @@ final class PhabricatorRepositoryManagementMirrorWorkflow ->pushToMirrors(); } - $console->writeOut('%s\b', pht('Done.')); + $console->writeOut("%s\n", pht('Done.')); return 0; } diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementPullWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementPullWorkflow.php index b58c56d46b..f41f8b95a1 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementPullWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementPullWorkflow.php @@ -7,7 +7,7 @@ final class PhabricatorRepositoryManagementPullWorkflow $this ->setName('pull') ->setExamples('**pull** __repository__ ...') - ->setSynopsis(pht('Pull __repository__, named by callsign.')) + ->setSynopsis(pht('Pull __repository__.')) ->setArguments( array( array( @@ -26,12 +26,16 @@ final class PhabricatorRepositoryManagementPullWorkflow if (!$repos) { throw new PhutilArgumentUsageException( - pht('Specify one or more repositories to pull, by callsign.')); + pht('Specify one or more repositories to pull.')); } $console = PhutilConsole::getConsole(); foreach ($repos as $repo) { - $console->writeOut("%s\n", pht("Pulling '%s'...", $repo->getCallsign())); + $console->writeOut( + "%s\n", + pht( + 'Pulling "%s"...', + $repo->getDisplayName())); id(new PhabricatorRepositoryPullEngine()) ->setRepository($repo) diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementRefsWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementRefsWorkflow.php index 5d5dd169e8..ad9bbbd0db 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementRefsWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementRefsWorkflow.php @@ -7,7 +7,7 @@ final class PhabricatorRepositoryManagementRefsWorkflow $this ->setName('refs') ->setExamples('**refs** [__options__] __repository__ ...') - ->setSynopsis(pht('Update refs in __repository__, named by callsign.')) + ->setSynopsis(pht('Update refs in __repository__.')) ->setArguments( array( array( @@ -27,15 +27,16 @@ final class PhabricatorRepositoryManagementRefsWorkflow if (!$repos) { throw new PhutilArgumentUsageException( pht( - 'Specify one or more repositories to update refs for, '. - 'by callsign.')); + 'Specify one or more repositories to update refs for.')); } $console = PhutilConsole::getConsole(); foreach ($repos as $repo) { $console->writeOut( "%s\n", - pht("Updating refs in '%s'...", $repo->getCallsign())); + pht( + 'Updating refs in "%s"...', + $repo->getDisplayName())); $engine = id(new PhabricatorRepositoryRefEngine()) ->setRepository($repo) diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php index 524cc606aa..2067fdbd11 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php @@ -28,7 +28,7 @@ final class PhabricatorRepositoryManagementReparseWorkflow ), array( 'name' => 'all', - 'param' => 'callsign or phid', + 'param' => 'repository', 'help' => pht( 'Reparse all commits in the specified repository. This mode '. 'queues parsers into the task queue; you must run taskmasters '. @@ -192,16 +192,16 @@ final class PhabricatorRepositoryManagementReparseWorkflow $commits = array(); if ($all_from_repo) { - $repository = id(new PhabricatorRepository())->loadOneWhere( - 'callsign = %s OR phid = %s', - $all_from_repo, - $all_from_repo); + $repository = id(new PhabricatorRepositoryQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withIdentifiers(array($all_from_repo)) + ->executeOne(); + if (!$repository) { throw new PhutilArgumentUsageException( - pht('Unknown repository %s!', $all_from_repo)); + pht('Unknown repository "%s"!', $all_from_repo)); } - $query = id(new DiffusionCommitQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withRepository($repository); @@ -216,46 +216,14 @@ final class PhabricatorRepositoryManagementReparseWorkflow $commits = $query->execute(); - $callsign = $repository->getCallsign(); if (!$commits) { throw new PhutilArgumentUsageException( pht( - 'No commits have been discovered in %s repository!', - $callsign)); + 'No commits have been discovered in the "%s" repository!', + $repository->getDisplayName())); } } else { - $commits = array(); - foreach ($reparse_what as $identifier) { - $matches = null; - if (!preg_match('/r([A-Z]+)([a-z0-9]+)/', $identifier, $matches)) { - throw new PhutilArgumentUsageException(pht( - "Can't parse commit identifier: %s", - $identifier)); - } - $callsign = $matches[1]; - $commit_identifier = $matches[2]; - $repository = id(new PhabricatorRepository())->loadOneWhere( - 'callsign = %s', - $callsign); - if (!$repository) { - throw new PhutilArgumentUsageException(pht( - "No repository with callsign '%s'!", - $callsign)); - } - $commit = id(new PhabricatorRepositoryCommit())->loadOneWhere( - 'repositoryID = %d AND commitIdentifier = %s', - $repository->getID(), - $commit_identifier); - if (!$commit) { - throw new PhutilArgumentUsageException(pht( - "No matching commit '%s' in repository '%s'. ". - "(For git and mercurial repositories, you must specify the entire ". - "commit hash.)", - $commit_identifier, - $callsign)); - } - $commits[] = $commit; - } + $commits = $this->loadNamedCommits($reparse_what); } if ($all_from_repo && !$force_local) { @@ -273,6 +241,8 @@ final class PhabricatorRepositoryManagementReparseWorkflow $tasks = array(); foreach ($commits as $commit) { + $repository = $commit->getRepository(); + if ($importing) { $status = $commit->getImportStatus(); // Find the first missing import step and queue that up. diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php index 99b4a3adc6..a754f3c3d4 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php @@ -20,9 +20,9 @@ final class PhabricatorRepositoryManagementUpdateWorkflow ->setExamples('**update** [options] __repository__') ->setSynopsis( pht( - 'Update __repository__, named by callsign. '. - 'This performs the __pull__, __discover__, __ref__ and __mirror__ '. - 'operations and is primarily an internal workflow.')) + 'Update __repository__. This performs the __pull__, __discover__, '. + '__ref__ and __mirror__ operations and is primarily an internal '. + 'workflow.')) ->setArguments( array( array( @@ -47,7 +47,7 @@ final class PhabricatorRepositoryManagementUpdateWorkflow $repos = $this->loadRepositories($args, 'repos'); if (count($repos) !== 1) { throw new PhutilArgumentUsageException( - pht('Specify exactly one repository to update, by callsign.')); + pht('Specify exactly one repository to update.')); } $repository = head($repos); diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementWorkflow.php index 26ab7c315e..300f8ec50f 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementWorkflow.php @@ -4,26 +4,32 @@ abstract class PhabricatorRepositoryManagementWorkflow extends PhabricatorManagementWorkflow { protected function loadRepositories(PhutilArgumentParser $args, $param) { - $callsigns = $args->getArg($param); + $identifiers = $args->getArg($param); - if (!$callsigns) { + if (!$identifiers) { return null; } - $repos = id(new PhabricatorRepositoryQuery()) + $query = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getViewer()) - ->withCallsigns($callsigns) - ->execute(); + ->withIdentifiers($identifiers); - $repos = mpull($repos, null, 'getCallsign'); - foreach ($callsigns as $callsign) { - if (empty($repos[$callsign])) { + $query->execute(); + + $map = $query->getIdentifierMap(); + foreach ($identifiers as $identifier) { + if (empty($map[$identifier])) { throw new PhutilArgumentUsageException( - pht("No repository with callsign '%s' exists!", $callsign)); + pht( + 'Repository "%s" does not exist!', + $identifier)); } } - return $repos; + // Reorder repositories according to argument order. + $repositories = array_select_keys($map, $identifiers); + + return array_values($repositories); } protected function loadCommits(PhutilArgumentParser $args, $param) { diff --git a/src/applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php b/src/applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php index 5a3bca339d..cf812366e5 100644 --- a/src/applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php +++ b/src/applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php @@ -38,17 +38,17 @@ final class PhabricatorRepositoryRepositoryPHIDType $repository = $objects[$phid]; $monogram = $repository->getMonogram(); - $callsign = $repository->getCallsign(); $name = $repository->getName(); + $uri = $repository->getURI(); $handle->setName($monogram); $handle->setFullName("{$monogram} {$name}"); - $handle->setURI("/diffusion/{$callsign}/"); + $handle->setURI($uri); } } public function canLoadNamedObject($name) { - return preg_match('/^r[A-Z]+|R[0-9]+$/', $name); + return preg_match('/^r[A-Z]+|R[1-9]\d*\z/', $name); } public function loadNamedObjects( diff --git a/src/applications/repository/query/PhabricatorRepositoryQuery.php b/src/applications/repository/query/PhabricatorRepositoryQuery.php index 7595cd0c8e..bd9765130a 100644 --- a/src/applications/repository/query/PhabricatorRepositoryQuery.php +++ b/src/applications/repository/query/PhabricatorRepositoryQuery.php @@ -15,6 +15,7 @@ final class PhabricatorRepositoryQuery private $numericIdentifiers; private $callsignIdentifiers; private $phidIdentifiers; + private $monogramIdentifiers; private $identifierMap; @@ -48,10 +49,18 @@ final class PhabricatorRepositoryQuery } public function withIdentifiers(array $identifiers) { - $ids = array(); $callsigns = array(); $phids = array(); + $identifiers = array_fuse($identifiers); + + $ids = array(); + $callsigns = array(); + $phids = array(); + $monograms = array(); + foreach ($identifiers as $identifier) { - if (ctype_digit($identifier)) { + if (ctype_digit((string)$identifier)) { $ids[$identifier] = $identifier; + } else if (preg_match('/^(r[A-Z]+)|(R[1-9]\d*)\z/', $identifier)) { + $monograms[$identifier] = $identifier; } else { $repository_type = PhabricatorRepositoryRepositoryPHIDType::TYPECONST; if (phid_get_type($identifier) === $repository_type) { @@ -65,6 +74,8 @@ final class PhabricatorRepositoryQuery $this->numericIdentifiers = $ids; $this->callsignIdentifiers = $callsigns; $this->phidIdentifiers = $phids; + $this->monogramIdentifiers = $monograms; + return $this; } @@ -273,6 +284,21 @@ final class PhabricatorRepositoryQuery } } + if ($this->monogramIdentifiers) { + $monogram_map = array(); + foreach ($repositories as $repository) { + foreach ($repository->getAllMonograms() as $monogram) { + $monogram_map[$monogram] = $repository; + } + } + + foreach ($this->monogramIdentifiers as $monogram) { + if (isset($monogram_map[$monogram])) { + $this->identifierMap[$monogram] = $monogram_map[$monogram]; + } + } + } + return $repositories; } @@ -447,7 +473,8 @@ final class PhabricatorRepositoryQuery if ($this->numericIdentifiers || $this->callsignIdentifiers || - $this->phidIdentifiers) { + $this->phidIdentifiers || + $this->monogramIdentifiers) { $identifier_clause = array(); if ($this->numericIdentifiers) { @@ -471,6 +498,33 @@ final class PhabricatorRepositoryQuery $this->phidIdentifiers); } + if ($this->monogramIdentifiers) { + $monogram_callsigns = array(); + $monogram_ids = array(); + + foreach ($this->monogramIdentifiers as $identifier) { + if ($identifier[0] == 'r') { + $monogram_callsigns[] = substr($identifier, 1); + } else { + $monogram_ids[] = substr($identifier, 1); + } + } + + if ($monogram_ids) { + $identifier_clause[] = qsprintf( + $conn, + 'r.id IN (%Ld)', + $monogram_ids); + } + + if ($monogram_callsigns) { + $identifier_clause[] = qsprintf( + $conn, + 'r.callsign IN (%Ls)', + $monogram_callsigns); + } + } + $where = array('('.implode(' OR ', $identifier_clause).')'); } diff --git a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php index 04333384f9..ad3b61c8df 100644 --- a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php +++ b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php @@ -155,15 +155,15 @@ final class PhabricatorRepositorySearchEngine ->setUser($viewer) ->setObject($repository) ->setHeader($repository->getName()) - ->setObjectName('r'.$repository->getCallsign()) - ->setHref($this->getApplicationURI($repository->getCallsign().'/')); + ->setObjectName($repository->getMonogram()) + ->setHref($repository->getURI()); $commit = $repository->getMostRecentCommit(); if ($commit) { $commit_link = DiffusionView::linkCommit( - $repository, - $commit->getCommitIdentifier(), - $commit->getSummary()); + $repository, + $commit->getCommitIdentifier(), + $commit->getSummary()); $item->setSubhead($commit_link); $item->setEpoch($commit->getEpoch()); } @@ -175,9 +175,8 @@ final class PhabricatorRepositorySearchEngine $size = $repository->getCommitCount(); if ($size) { - $history_uri = DiffusionRequest::generateDiffusionURI( + $history_uri = $repository->generateURI( array( - 'callsign' => $repository->getCallsign(), 'action' => 'history', )); @@ -205,6 +204,8 @@ final class PhabricatorRepositorySearchEngine if (!$repository->isTracked()) { $item->setDisabled(true); $item->addIcon('disable-grey', pht('Inactive')); + } else if ($repository->isImporting()) { + $item->addIcon('fa-clock-o indigo', pht('Importing...')); } $list->addItem($item); diff --git a/src/applications/repository/search/DiffusionCommitFulltextEngine.php b/src/applications/repository/search/DiffusionCommitFulltextEngine.php index 51d2ead5fa..dd87a8bda5 100644 --- a/src/applications/repository/search/DiffusionCommitFulltextEngine.php +++ b/src/applications/repository/search/DiffusionCommitFulltextEngine.php @@ -20,8 +20,10 @@ final class DiffusionCommitFulltextEngine $commit_message = $commit_data->getCommitMessage(); $author_phid = $commit_data->getCommitDetail('authorPHID'); - $title = 'r'.$repository->getCallsign().$commit->getCommitIdentifier(). - ' '.$commit_data->getSummary(); + $monogram = $commit->getMonogram(); + $summary = $commit_data->getSummary(); + + $title = "{$monogram} {$summary}"; $document ->setDocumentCreated($date_created) diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index c7ef71e9b3..e34c4980c0 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -151,6 +151,26 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return 'r'.$this->getCallsign(); } + public function getDisplayName() { + // TODO: This is intended to produce a human-readable name that is not + // necessarily a global, unique identifier. Eventually, it may just return + // a string like "skynet" instead of "rSKYNET". + return $this->getMonogram(); + } + + public function getAllMonograms() { + $monograms = array(); + + $monograms[] = 'R'.$this->getID(); + + $callsign = $this->getCallsign(); + if (strlen($callsign)) { + $monograms[] = 'r'.$callsign; + } + + return $monograms; + } + public function getDetail($key, $default = null) { return idx($this->details, $key, $default); } @@ -581,6 +601,151 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return '/diffusion/'.$this->getCallsign().'/'; } + public function getPathURI($path) { + return $this->getURI().$path; + } + + public function getCommitURI($identifier) { + $callsign = $this->getCallsign(); + return "/r{$callsign}{$identifier}"; + } + + public function generateURI(array $params) { + $req_branch = false; + $req_commit = false; + + $action = idx($params, 'action'); + switch ($action) { + case 'history': + case 'browse': + case 'change': + case 'lastmodified': + case 'tags': + case 'branches': + case 'lint': + case 'pathtree': + case 'refs': + break; + case 'branch': + // NOTE: This does not actually require a branch, and won't have one + // in Subversion. Possibly this should be more clear. + break; + case 'commit': + case 'rendering-ref': + $req_commit = true; + break; + default: + throw new Exception( + pht( + 'Action "%s" is not a valid repository URI action.', + $action)); + } + + $path = idx($params, 'path'); + $branch = idx($params, 'branch'); + $commit = idx($params, 'commit'); + $line = idx($params, 'line'); + + if ($req_commit && !strlen($commit)) { + throw new Exception( + pht( + 'Diffusion URI action "%s" requires commit!', + $action)); + } + + if ($req_branch && !strlen($branch)) { + throw new Exception( + pht( + 'Diffusion URI action "%s" requires branch!', + $action)); + } + + if ($action === 'commit') { + return $this->getCommitURI($commit); + } + + + $identifier = $this->getID(); + + $callsign = $this->getCallsign(); + if ($callsign !== null) { + $identifier = $callsign; + } + + if (strlen($identifier)) { + $identifier = phutil_escape_uri_path_component($identifier); + } + + if (strlen($path)) { + $path = ltrim($path, '/'); + $path = str_replace(array(';', '$'), array(';;', '$$'), $path); + $path = phutil_escape_uri($path); + } + + if (strlen($branch)) { + $branch = phutil_escape_uri_path_component($branch); + $path = "{$branch}/{$path}"; + } + + if (strlen($commit)) { + $commit = str_replace('$', '$$', $commit); + $commit = ';'.phutil_escape_uri($commit); + } + + if (strlen($line)) { + $line = '$'.phutil_escape_uri($line); + } + + switch ($action) { + case 'change': + case 'history': + case 'browse': + case 'lastmodified': + case 'tags': + case 'branches': + case 'lint': + case 'pathtree': + case 'refs': + $uri = "/diffusion/{$identifier}/{$action}/{$path}{$commit}{$line}"; + break; + case 'branch': + if (strlen($path)) { + $uri = "/diffusion/{$identifier}/repository/{$path}"; + } else { + $uri = "/diffusion/{$identifier}/"; + } + break; + case 'external': + $commit = ltrim($commit, ';'); + $uri = "/diffusion/external/{$commit}/"; + break; + case 'rendering-ref': + // This isn't a real URI per se, it's passed as a query parameter to + // the ajax changeset stuff but then we parse it back out as though + // it came from a URI. + $uri = rawurldecode("{$path}{$commit}"); + break; + } + + if ($action == 'rendering-ref') { + return $uri; + } + + $uri = new PhutilURI($uri); + + if (isset($params['lint'])) { + $params['params'] = idx($params, 'params', array()) + array( + 'lint' => $params['lint'], + ); + } + + if (idx($params, 'params')) { + $uri->setQueryParams($params['params']); + } + + return $uri; + } + public function getNormalizedPath() { $uri = (string)$this->getCloneURIObject(); @@ -703,6 +868,47 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return (bool)$this->getDetail('importing', false); } + public function loadImportProgress() { + $progress = queryfx_all( + $this->establishConnection('r'), + 'SELECT importStatus, count(*) N FROM %T WHERE repositoryID = %d + GROUP BY importStatus', + id(new PhabricatorRepositoryCommit())->getTableName(), + $this->getID()); + + $done = 0; + $total = 0; + foreach ($progress as $row) { + $total += $row['N'] * 4; + $status = $row['importStatus']; + if ($status & PhabricatorRepositoryCommit::IMPORTED_MESSAGE) { + $done += $row['N']; + } + if ($status & PhabricatorRepositoryCommit::IMPORTED_CHANGE) { + $done += $row['N']; + } + if ($status & PhabricatorRepositoryCommit::IMPORTED_OWNERS) { + $done += $row['N']; + } + if ($status & PhabricatorRepositoryCommit::IMPORTED_HERALD) { + $done += $row['N']; + } + } + + if ($total) { + $ratio = ($done / $total); + } else { + $ratio = 0; + } + + // Cap this at "99.99%", because it's confusing to users when the actual + // fraction is "99.996%" and it rounds up to "100.00%". + if ($ratio > 0.9999) { + $ratio = 0.9999; + } + + return $ratio; + } /** * Should this repository publish feed, notifications, audits, and email? diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index da6d56b915..d7a2e111e2 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -203,10 +203,7 @@ final class PhabricatorRepositoryCommit } public function getURI() { - $repository = $this->getRepository(); - $callsign = $repository->getCallsign(); - $commit_identifier = $this->getCommitIdentifier(); - return '/r'.$callsign.$commit_identifier; + return '/'.$this->getMonogram(); } /** @@ -251,6 +248,50 @@ final class PhabricatorRepositoryCommit return $this->setAuditStatus($status); } + public function getMonogram() { + $repository = $this->getRepository(); + $callsign = $repository->getCallsign(); + $identifier = $this->getCommitIdentifier(); + if ($callsign !== null) { + return "r{$callsign}{$identifier}"; + } else { + $id = $repository->getID(); + return "R{$id}:{$identifier}"; + } + } + + public function getDisplayName() { + $repository = $this->getRepository(); + $identifier = $this->getCommitIdentifier(); + return $repository->formatCommitName($identifier); + } + + public function getShortName() { + $identifier = $this->getCommitIdentifier(); + return substr($identifier, 0, 9); + } + + public function renderAuthorLink($handles) { + $author_phid = $this->getAuthorPHID(); + if ($author_phid && isset($handles[$author_phid])) { + return $handles[$author_phid]->renderLink(); + } + + return $this->renderAuthorShortName($handles); + } + + public function renderAuthorShortName($handles) { + $author_phid = $this->getAuthorPHID(); + if ($author_phid && isset($handles[$author_phid])) { + return $handles[$author_phid]->getName(); + } + + $data = $this->getCommitData(); + $name = $data->getAuthorName(); + + $parsed = new PhutilEmailAddress($name); + return nonempty($parsed->getDisplayName(), $parsed->getAddress()); + } /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php b/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php index cafb28d7f6..aa2aaa270b 100644 --- a/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php +++ b/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php @@ -17,30 +17,26 @@ abstract class PhabricatorRepositoryCommitParserWorker pht('No "%s" in task data.', 'commitID')); } - $commit = id(new PhabricatorRepositoryCommit())->load($commit_id); - + $commit = id(new DiffusionCommitQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withIDs(array($commit_id)) + ->executeOne(); if (!$commit) { throw new PhabricatorWorkerPermanentFailureException( pht('Commit "%s" does not exist.', $commit_id)); } - return $this->commit = $commit; + $this->commit = $commit; + + return $commit; } final protected function doWork() { - if (!$this->loadCommit()) { - return; - } - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withIDs(array($this->commit->getRepositoryID())) - ->executeOne(); - if (!$repository) { - return; - } + $commit = $this->loadCommit(); + $repository = $commit->getRepository(); $this->repository = $repository; + $this->parseCommit($repository, $this->commit); } @@ -52,14 +48,14 @@ abstract class PhabricatorRepositoryCommitParserWorker PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit); - protected function isBadCommit($full_commit_name) { + protected function isBadCommit(PhabricatorRepositoryCommit $commit) { $repository = new PhabricatorRepository(); $bad_commit = queryfx_one( $repository->establishConnection('w'), 'SELECT * FROM %T WHERE fullCommitName = %s', PhabricatorRepository::TABLE_BADCOMMIT, - $full_commit_name); + $commit->getMonogram()); return (bool)$bad_commit; } diff --git a/src/applications/repository/worker/__tests__/PhabricatorChangeParserTestCase.php b/src/applications/repository/worker/__tests__/PhabricatorChangeParserTestCase.php index 4925c9dbea..452d02ae40 100644 --- a/src/applications/repository/worker/__tests__/PhabricatorChangeParserTestCase.php +++ b/src/applications/repository/worker/__tests__/PhabricatorChangeParserTestCase.php @@ -1186,7 +1186,7 @@ final class PhabricatorChangeParserTestCase pht( 'No test entry for commit "%s" in repository "%s"!', $commit_identifier, - $repository->getCallsign())); + $repository->getDisplayName())); } $changes = $this->parseCommit($repository, $commit); diff --git a/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php b/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php index 6563c4442c..6a0161fd06 100644 --- a/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php +++ b/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php @@ -17,12 +17,8 @@ abstract class PhabricatorRepositoryCommitChangeParserWorker PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit) { - $identifier = $commit->getCommitIdentifier(); - $callsign = $repository->getCallsign(); - $full_name = 'r'.$callsign.$identifier; - - $this->log("%s\n", pht('Parsing %s...', $full_name)); - if ($this->isBadCommit($full_name)) { + $this->log("%s\n", pht('Parsing "%s"...', $commit->getMonogram())); + if ($this->isBadCommit($commit)) { $this->log(pht('This commit is marked bad!')); return; } diff --git a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php index c93810cb0c..17a2f3a3f2 100644 --- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php +++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php @@ -216,47 +216,24 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $low_level_query->getRevisionMatchData()); } - $diff = $this->generateFinalDiff($revision, $acting_as_phid); - - $vs_diff = $this->loadChangedByCommit($revision, $diff); - $changed_uri = null; - if ($vs_diff) { - $data->setCommitDetail('vsDiff', $vs_diff->getID()); - - $changed_uri = PhabricatorEnv::getProductionURI( - '/D'.$revision->getID(). - '?vs='.$vs_diff->getID(). - '&id='.$diff->getID(). - '#toc'); - } - - $xactions = array(); - $xactions[] = id(new DifferentialTransaction()) - ->setTransactionType(DifferentialTransaction::TYPE_UPDATE) - ->setIgnoreOnNoEffect(true) - ->setNewValue($diff->getPHID()) - ->setMetadataValue('isCommitUpdate', true); - $xactions[] = $commit_close_xaction; + $extraction_engine = id(new DifferentialDiffExtractionEngine()) + ->setViewer($actor) + ->setAuthorPHID($acting_as_phid); $content_source = PhabricatorContentSource::newForSource( PhabricatorContentSource::SOURCE_DAEMON, array()); - $editor = id(new DifferentialTransactionEditor()) - ->setActor($actor) - ->setActingAsPHID($acting_as_phid) - ->setContinueOnMissingFields(true) - ->setContentSource($content_source) - ->setChangedPriorToCommitURI($changed_uri) - ->setIsCloseByCommit(true); + $update_data = $extraction_engine->updateRevisionWithCommit( + $revision, + $commit, + array( + $commit_close_xaction, + ), + $content_source); - try { - $editor->applyTransactions($revision, $xactions); - } catch (PhabricatorApplicationTransactionNoEffectException $ex) { - // NOTE: We've marked transactions other than the CLOSE transaction - // as ignored when they don't have an effect, so this means that we - // lost a race to close the revision. That's perfectly fine, we can - // just continue normally. + foreach ($update_data as $key => $value) { + $data->setCommitDetail($key, $value); } } } @@ -277,172 +254,6 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker PhabricatorRepositoryCommit::IMPORTED_MESSAGE); } - private function generateFinalDiff( - DifferentialRevision $revision, - $actor_phid) { - - $viewer = PhabricatorUser::getOmnipotentUser(); - - $drequest = DiffusionRequest::newFromDictionary(array( - 'user' => $viewer, - 'repository' => $this->repository, - )); - - $raw_diff = DiffusionQuery::callConduitWithDiffusionRequest( - $viewer, - $drequest, - 'diffusion.rawdiffquery', - array( - 'commit' => $this->commit->getCommitIdentifier(), - )); - - // TODO: Support adds, deletes and moves under SVN. - if (strlen($raw_diff)) { - $changes = id(new ArcanistDiffParser())->parseDiff($raw_diff); - } else { - // This is an empty diff, maybe made with `git commit --allow-empty`. - // NOTE: These diffs have the same tree hash as their ancestors, so - // they may attach to revisions in an unexpected way. Just let this - // happen for now, although it might make sense to special case it - // eventually. - $changes = array(); - } - - $diff = DifferentialDiff::newFromRawChanges($viewer, $changes) - ->setRepositoryPHID($this->repository->getPHID()) - ->setAuthorPHID($actor_phid) - ->setCreationMethod('commit') - ->setSourceControlSystem($this->repository->getVersionControlSystem()) - ->setLintStatus(DifferentialLintStatus::LINT_AUTO_SKIP) - ->setUnitStatus(DifferentialUnitStatus::UNIT_AUTO_SKIP) - ->setDateCreated($this->commit->getEpoch()) - ->setDescription( - pht( - 'Commit %s', - 'r'.$this->repository->getCallsign(). - $this->commit->getCommitIdentifier())); - - $parents = DiffusionQuery::callConduitWithDiffusionRequest( - $viewer, - $drequest, - 'diffusion.commitparentsquery', - array( - 'commit' => $this->commit->getCommitIdentifier(), - )); - if ($parents) { - $diff->setSourceControlBaseRevision(head($parents)); - } - - // TODO: Attach binary files. - - return $diff->save(); - } - - private function loadChangedByCommit( - DifferentialRevision $revision, - DifferentialDiff $diff) { - - $repository = $this->repository; - - $vs_diff = id(new DifferentialDiffQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withRevisionIDs(array($revision->getID())) - ->needChangesets(true) - ->setLimit(1) - ->executeOne(); - if (!$vs_diff) { - return null; - } - - if ($vs_diff->getCreationMethod() == 'commit') { - return null; - } - - $vs_changesets = array(); - foreach ($vs_diff->getChangesets() as $changeset) { - $path = $changeset->getAbsoluteRepositoryPath($repository, $vs_diff); - $path = ltrim($path, '/'); - $vs_changesets[$path] = $changeset; - } - - $changesets = array(); - foreach ($diff->getChangesets() as $changeset) { - $path = $changeset->getAbsoluteRepositoryPath($repository, $diff); - $path = ltrim($path, '/'); - $changesets[$path] = $changeset; - } - - if (array_fill_keys(array_keys($changesets), true) != - array_fill_keys(array_keys($vs_changesets), true)) { - return $vs_diff; - } - - $file_phids = array(); - foreach ($vs_changesets as $changeset) { - $metadata = $changeset->getMetadata(); - $file_phid = idx($metadata, 'new:binary-phid'); - if ($file_phid) { - $file_phids[$file_phid] = $file_phid; - } - } - - $files = array(); - if ($file_phids) { - $files = id(new PhabricatorFileQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs($file_phids) - ->execute(); - $files = mpull($files, null, 'getPHID'); - } - - foreach ($changesets as $path => $changeset) { - $vs_changeset = $vs_changesets[$path]; - - $file_phid = idx($vs_changeset->getMetadata(), 'new:binary-phid'); - if ($file_phid) { - if (!isset($files[$file_phid])) { - return $vs_diff; - } - $drequest = DiffusionRequest::newFromDictionary(array( - 'user' => PhabricatorUser::getOmnipotentUser(), - 'repository' => $this->repository, - 'commit' => $this->commit->getCommitIdentifier(), - 'path' => $path, - )); - $corpus = DiffusionFileContentQuery::newFromDiffusionRequest($drequest) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->loadFileContent() - ->getCorpus(); - if ($files[$file_phid]->loadFileData() != $corpus) { - return $vs_diff; - } - } else { - $context = implode("\n", $changeset->makeChangesWithContext()); - $vs_context = implode("\n", $vs_changeset->makeChangesWithContext()); - - // We couldn't just compare $context and $vs_context because following - // diffs will be considered different: - // - // -(empty line) - // -echo 'test'; - // (empty line) - // - // (empty line) - // -echo "test"; - // -(empty line) - - $hunk = id(new DifferentialModernHunk())->setChanges($context); - $vs_hunk = id(new DifferentialModernHunk())->setChanges($vs_context); - if ($hunk->makeOldFile() != $vs_hunk->makeOldFile() || - $hunk->makeNewFile() != $vs_hunk->makeNewFile()) { - return $vs_diff; - } - } - } - - return null; - } - private function resolveUserPHID( PhabricatorRepositoryCommit $commit, $user_name) { diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index df79a4e0a5..702af44c40 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -1081,7 +1081,9 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { return $fields; } - public function buildConduitResponse(ConduitAPIRequest $request) { + public function buildConduitResponse( + ConduitAPIRequest $request, + ConduitAPIMethod $method) { $viewer = $this->requireViewer(); $query_key = $request->getValue('queryKey'); @@ -1172,6 +1174,12 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { $attachment_specs[$key]); } + // If this is empty, we still want to emit a JSON object, not a + // JSON list. + if (!$attachment_map) { + $attachment_map = (object)$attachment_map; + } + $id = (int)$object->getID(); $phid = $object->getPHID(); @@ -1187,6 +1195,7 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { return array( 'data' => $data, + 'maps' => $method->getQueryMaps($query), 'query' => array( 'queryKey' => $saved_query->getQueryKey(), ), diff --git a/src/applications/search/engine/PhabricatorJumpNavHandler.php b/src/applications/search/engine/PhabricatorJumpNavHandler.php index 9e810e3977..6c30af98dd 100644 --- a/src/applications/search/engine/PhabricatorJumpNavHandler.php +++ b/src/applications/search/engine/PhabricatorJumpNavHandler.php @@ -57,7 +57,7 @@ final class PhabricatorJumpNavHandler extends Phobject { ->execute(); if (count($repositories) == 1) { // Just one match, jump to repository. - $uri = '/diffusion/'.head($repositories)->getCallsign().'/'; + $uri = head($repositories)->getURI(); } else { // More than one match, jump to search. $uri = urisprintf('/diffusion/?order=name&name=%s', $name); diff --git a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php index a299d90018..f411c208bf 100644 --- a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php +++ b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php @@ -5,6 +5,27 @@ abstract class PhabricatorSearchEngineAPIMethod abstract public function newSearchEngine(); + final public function getQueryMaps($query) { + $maps = $this->getCustomQueryMaps($query); + + // Make sure we emit empty maps as objects, not lists. + foreach ($maps as $key => $map) { + if (!$map) { + $maps[$key] = (object)$map; + } + } + + if (!$maps) { + $maps = (object)$maps; + } + + return $maps; + } + + protected function getCustomQueryMaps($query) { + return array(); + } + public function getApplication() { $engine = $this->newSearchEngine(); $class = $engine->getApplicationClassName(); @@ -36,7 +57,7 @@ abstract class PhabricatorSearchEngineAPIMethod $engine = $this->newSearchEngine() ->setViewer($request->getUser()); - return $engine->buildConduitResponse($request); + return $engine->buildConduitResponse($request, $this); } final public function getMethodDescription() { diff --git a/src/applications/settings/application/PhabricatorSettingsApplication.php b/src/applications/settings/application/PhabricatorSettingsApplication.php index da0daf65d2..2608cabd88 100644 --- a/src/applications/settings/application/PhabricatorSettingsApplication.php +++ b/src/applications/settings/application/PhabricatorSettingsApplication.php @@ -40,26 +40,4 @@ final class PhabricatorSettingsApplication extends PhabricatorApplication { return self::GROUP_UTILITIES; } - public function buildMainMenuItems( - PhabricatorUser $user, - PhabricatorController $controller = null) { - - $items = array(); - - if ($user->isLoggedIn() && $user->isUserActivated()) { - $selected = ($controller instanceof PhabricatorSettingsMainController); - $item = id(new PHUIListItemView()) - ->setName(pht('Settings')) - ->setIcon('fa-wrench') - ->addClass('core-menu-item') - ->setSelected($selected) - ->setHref('/settings/') - ->setAural(pht('Settings')) - ->setOrder(400); - $items[] = $item; - } - - return $items; - } - } diff --git a/src/applications/settings/extension/PhabricatorSettingsMainMenuBarExtension.php b/src/applications/settings/extension/PhabricatorSettingsMainMenuBarExtension.php new file mode 100644 index 0000000000..21534c4237 --- /dev/null +++ b/src/applications/settings/extension/PhabricatorSettingsMainMenuBarExtension.php @@ -0,0 +1,29 @@ +getController(); + $is_selected = ($controller instanceof PhabricatorSettingsMainController); + + $bar_item = id(new PHUIListItemView()) + ->setName(pht('Settings')) + ->setIcon('fa-wrench') + ->addClass('core-menu-item') + ->setSelected($is_selected) + ->setHref('/settings/') + ->setAural(pht('Settings')); + + $settings_menu = id(new PHUIMainMenuView()) + ->setMenuBarItem($bar_item) + ->setOrder(400); + + return array( + $settings_menu, + ); + } + +} diff --git a/src/applications/tokens/query/PhabricatorTokenGivenQuery.php b/src/applications/tokens/query/PhabricatorTokenGivenQuery.php index c16eaa2168..224efff198 100644 --- a/src/applications/tokens/query/PhabricatorTokenGivenQuery.php +++ b/src/applications/tokens/query/PhabricatorTokenGivenQuery.php @@ -22,68 +22,62 @@ final class PhabricatorTokenGivenQuery return $this; } - protected function loadPage() { - $table = new PhabricatorTokenGiven(); - $conn_r = $table->establishConnection('r'); - - $rows = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($rows); + public function newResultObject() { + return new PhabricatorTokenGiven(); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } - if ($this->authorPHIDs) { + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->authorPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'authorPHID IN (%Ls)', $this->authorPHIDs); } - if ($this->objectPHIDs) { + if ($this->objectPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'objectPHID IN (%Ls)', $this->objectPHIDs); } - if ($this->tokenPHIDs) { + if ($this->tokenPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'tokenPHID IN (%Ls)', $this->tokenPHIDs); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } protected function willFilterPage(array $results) { - $object_phids = array_filter(mpull($results, 'getObjectPHID')); - if (!$object_phids) { - return array(); - } + $object_phids = mpull($results, 'getObjectPHID'); $objects = id(new PhabricatorObjectQuery()) ->setViewer($this->getViewer()) ->withPHIDs($object_phids) ->execute(); + $objects = mpull($objects, null, 'getPHID'); foreach ($results as $key => $result) { - $phid = $result->getObjectPHID(); - if (empty($objects[$phid])) { - unset($results[$key]); - } else { - $result->attachObject($objects[$phid]); + $object = idx($objects, $result->getObjectPHID()); + + if ($object) { + if ($object instanceof PhabricatorTokenReceiverInterface) { + $result->attachObject($object); + continue; + } } + + $this->didRejectResult($result); + unset($results[$key]); } return $results; diff --git a/src/applications/transactions/controller/PhabricatorApplicationTransactionRemarkupPreviewController.php b/src/applications/transactions/controller/PhabricatorApplicationTransactionRemarkupPreviewController.php index 4ba8345d5c..2714be02c4 100644 --- a/src/applications/transactions/controller/PhabricatorApplicationTransactionRemarkupPreviewController.php +++ b/src/applications/transactions/controller/PhabricatorApplicationTransactionRemarkupPreviewController.php @@ -10,9 +10,9 @@ final class PhabricatorApplicationTransactionRemarkupPreviewController public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); - $corpus = $request->getStr('corpus'); + $text = $request->getStr('text'); - $remarkup = new PHUIRemarkupView($viewer, $corpus); + $remarkup = new PHUIRemarkupView($viewer, $text); $content = array( 'content' => hsprintf('%s', $remarkup), diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 2e96e60dd0..e89fea3604 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -64,10 +64,6 @@ abstract class PhabricatorEditEngine abstract public function getEngineApplicationClass(); abstract protected function buildCustomEditFields($object); - protected function didBuildCustomEditFields($object, array $fields) { - return; - } - public function getFieldsForConfig( PhabricatorEditEngineConfiguration $config) { @@ -93,7 +89,6 @@ abstract class PhabricatorEditEngine } $fields = mpull($fields, null, 'getKey'); - $this->didBuildCustomEditFields($object, $fields); $extensions = PhabricatorEditEngineExtension::getAllEnabledExtensions(); foreach ($extensions as $extension) { @@ -115,7 +110,6 @@ abstract class PhabricatorEditEngine } $extension_fields = mpull($extension_fields, null, 'getKey'); - $extension->didBuildCustomEditFields($this, $object, $extension_fields); foreach ($extension_fields as $key => $field) { $fields[$key] = $field; @@ -123,11 +117,16 @@ abstract class PhabricatorEditEngine } $config = $this->getEditEngineConfiguration(); + $fields = $this->willConfigureFields($object, $fields); $fields = $config->applyConfigurationToFields($this, $object, $fields); return $fields; } + protected function willConfigureFields($object, array $fields) { + return $fields; + } + /* -( Display Text )------------------------------------------------------- */ @@ -962,6 +961,28 @@ abstract class PhabricatorEditEngine $header_text = $this->getObjectEditTitleText($object); } + $show_preview = !$request->isAjax(); + + if ($show_preview) { + $previews = array(); + foreach ($fields as $field) { + $preview = $field->getPreviewPanel(); + if (!$preview) { + continue; + } + + $control_id = $field->getControlID(); + + $preview + ->setControlID($control_id) + ->setPreviewURI('/transactions/remarkuppreview/'); + + $previews[] = $preview; + } + } else { + $previews = array(); + } + $form = $this->buildEditForm($object, $fields); if ($request->isAjax()) { @@ -998,7 +1019,8 @@ abstract class PhabricatorEditEngine return $controller->newPage() ->setTitle($header_text) ->setCrumbs($crumbs) - ->appendChild($box); + ->appendChild($box) + ->appendChild($previews); } protected function newEditResponse( @@ -1665,7 +1687,16 @@ abstract class PhabricatorEditEngine // Let the parameter type interpret the value. This allows you to // use usernames in list fields, for example. $parameter_type = $type->getConduitParameterType(); - $xaction['value'] = $parameter_type->getValue($xaction, 'value'); + + try { + $xaction['value'] = $parameter_type->getValue($xaction, 'value'); + } catch (Exception $ex) { + throw new PhutilProxyException( + pht( + 'Exception when processing transaction of type "%s".', + $xaction['type']), + $ex); + } $type_xactions = $type->generateTransactions( clone $template, diff --git a/src/applications/transactions/editfield/PhabricatorEditField.php b/src/applications/transactions/editfield/PhabricatorEditField.php index 9d1b976299..1216370819 100644 --- a/src/applications/transactions/editfield/PhabricatorEditField.php +++ b/src/applications/transactions/editfield/PhabricatorEditField.php @@ -14,6 +14,8 @@ abstract class PhabricatorEditField extends Phobject { private $metadata = array(); private $editTypeKey; private $isRequired; + private $previewPanel; + private $controlID; private $description; private $conduitDescription; @@ -251,6 +253,15 @@ abstract class PhabricatorEditField extends Phobject { return $this->commentActionValue; } + public function setPreviewPanel(PHUIRemarkupPreviewPanel $preview_panel) { + $this->previewPanel = $preview_panel; + return $this; + } + + public function getPreviewPanel() { + return $this->previewPanel; + } + protected function newControl() { throw new PhutilMethodNotImplementedException(); } @@ -285,6 +296,13 @@ abstract class PhabricatorEditField extends Phobject { return $control; } + public function getControlID() { + if (!$this->controlID) { + $this->controlID = celerity_generate_unique_node_id(); + } + return $this->controlID; + } + protected function renderControl() { $control = $this->buildControl(); if ($control === null) { @@ -308,6 +326,10 @@ abstract class PhabricatorEditField extends Phobject { $control->setDisabled($disabled); + if ($this->controlID) { + $control->setID($this->controlID); + } + return $control; } diff --git a/src/applications/transactions/editfield/PhabricatorIconSetEditField.php b/src/applications/transactions/editfield/PhabricatorIconSetEditField.php new file mode 100644 index 0000000000..a500da5430 --- /dev/null +++ b/src/applications/transactions/editfield/PhabricatorIconSetEditField.php @@ -0,0 +1,30 @@ +iconSet = $icon_set; + return $this; + } + + public function getIconSet() { + return $this->iconSet; + } + + protected function newControl() { + return id(new PHUIFormIconSetControl()) + ->setIconSet($this->getIconSet()); + } + + protected function newConduitParameterType() { + return new ConduitStringParameterType(); + } + + protected function newHTTPParameterType() { + return new AphrontStringHTTPParameterType(); + } + +} diff --git a/src/applications/transactions/editfield/PhabricatorPHIDListEditField.php b/src/applications/transactions/editfield/PhabricatorPHIDListEditField.php index b0eee2d465..4968aef5b2 100644 --- a/src/applications/transactions/editfield/PhabricatorPHIDListEditField.php +++ b/src/applications/transactions/editfield/PhabricatorPHIDListEditField.php @@ -35,7 +35,11 @@ abstract class PhabricatorPHIDListEditField } protected function newConduitParameterType() { - return new ConduitPHIDListParameterType(); + if ($this->getIsSingleValue()) { + return new ConduitPHIDParameterType(); + } else { + return new ConduitPHIDListParameterType(); + } } protected function getValueFromRequest(AphrontRequest $request, $key) { diff --git a/src/applications/transactions/editfield/PhabricatorStringListEditField.php b/src/applications/transactions/editfield/PhabricatorStringListEditField.php new file mode 100644 index 0000000000..64cb1fe08c --- /dev/null +++ b/src/applications/transactions/editfield/PhabricatorStringListEditField.php @@ -0,0 +1,18 @@ +setAncestorClass(__CLASS__) diff --git a/src/docs/user/configuration/configuring_backups.diviner b/src/docs/user/configuration/configuring_backups.diviner index b96531d9ff..1f13e0e17f 100644 --- a/src/docs/user/configuration/configuring_backups.diviner +++ b/src/docs/user/configuration/configuring_backups.diviner @@ -3,7 +3,9 @@ Advice for backing up Phabricator, or migrating from one machine to another. -= Overview = + +Overview +======== Phabricator does not currently have a comprehensive backup system, but creating backups is not particularly difficult and Phabricator does have a few basic @@ -11,6 +13,7 @@ tools which can help you set up a reasonable process. In particular, the things which needs to be backed up are: - the MySQL databases; + - hosted repositories; - uploaded files; and - your Phabricator configuration files. @@ -21,7 +24,9 @@ same steps you would if you were creating a backup and then restoring it, you will just backup the old machine and then restore the data onto the new machine. -= Backup: MySQL Databases = + +Backup: MySQL Databases +======================= Most of Phabricator's data is stored in MySQL, and it's the most important thing to back up. You can run `bin/storage dump` to get a dump of all the MySQL @@ -36,14 +41,35 @@ gzip prior to storage. For example: Then store the backup somewhere safe, like in a box buried under an old tree stump. No one will ever think to look for it there. -= Restore: MySQL = +Restore: MySQL +============== To restore a MySQL dump, just pipe it to `mysql` on a clean host. (You may need to uncompress it first, if you compressed it prior to storage.) $ gunzip -c backup.sql.gz | mysql -= Backup: Uploaded Files = + +Backup: Hosted Repositories +=========================== + +If you host repositories in Phabricator, you should back them up. You can use +`bin/repository list-paths` to show the local paths on disk for each +repository. To back them up, copy them elsewhere. + +You can also just clone them and keep the clones up to date, or use +{nav Add Mirror} to have the mirror somewhere automatically. + + +Restore: Hosted Repositories +============================ + +To restore hosted repositories, copy them back into the correct locations +as shown by `bin/repository list-paths`. + + +Backup: Uploaded Files +====================== Uploaded files may be stored in several different locations. The backup procedure depends on where files are stored: @@ -65,11 +91,15 @@ setting). For more information about configuring how files are stored, see @{article:Configuring File Storage}. -= Restore: Uploaded Files = + +Restore: Uploaded Files +======================= To restore a backup of local disk storage, just copy the backup into place. -= Backup: Configuration Files = + +Backup: Configuration Files +=========================== You should also backup your configuration files, and any scripts you use to deploy or administrate Phabricator (like a customized upgrade script). The best @@ -85,12 +115,15 @@ creates: This file contains all of the configuration settings that have been adjusted by using `bin/config set `. -= Restore: Configuration Files = + +Restore: Configuration Files +============================ To restore configuration files, just copy them into the right locations. Copy your backup of `local.json` to `phabricator/conf/local/local.json`. -= Security = +Security +======== MySQL dumps have no builtin encryption and most data in Phabricator is stored in a raw, accessible form, so giving a user access to backups is a lot like giving @@ -105,7 +138,9 @@ Some of this information is durable, so disclosure of even a very old backup may present a risk. If you restrict access to the Phabricator host or database, you should also restrict access to the backups. -= Next Steps = + +Next Steps +========== Continue by: diff --git a/src/docs/user/userguide/conduit.diviner b/src/docs/user/userguide/conduit.diviner index 5292736f30..5784d8cd01 100644 --- a/src/docs/user/userguide/conduit.diviner +++ b/src/docs/user/userguide/conduit.diviner @@ -56,7 +56,7 @@ Creating and Editing Objects ============================ For information on creating, editing and updating objects, see -@{article:Conduit API: Using Search Endpoints}. +@{article:Conduit API: Using Edit Endpoints}. Next Steps diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php index 89a4722d85..f617d3a849 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php @@ -17,12 +17,14 @@ abstract class PhabricatorStandardCustomField private $default; private $isCopyable; private $hasStorageValue; + private $isBuiltin; abstract public function getFieldType(); public static function buildStandardFields( PhabricatorCustomField $template, - array $config) { + array $config, + $builtin = false) { $types = id(new PhutilClassMapQuery()) ->setAncestorClass(__CLASS__) @@ -48,6 +50,10 @@ abstract class PhabricatorStandardCustomField ->setFieldConfig($value) ->setApplicationField($template); + if ($builtin) { + $standard->setIsBuiltin(true); + } + $field = $template->setProxy($standard); $fields[] = $field; } @@ -93,6 +99,15 @@ abstract class PhabricatorStandardCustomField return $this; } + public function setIsBuiltin($is_builtin) { + $this->isBuiltin = $is_builtin; + return $this; + } + + public function getIsBuiltin() { + return $this->isBuiltin; + } + public function setFieldConfig(array $config) { foreach ($config as $key => $value) { switch ($key) { @@ -470,7 +485,11 @@ abstract class PhabricatorStandardCustomField } public function getModernFieldKey() { - return 'custom.'.$this->getRawStandardFieldKey(); + if ($this->getIsBuiltin()) { + return $this->getRawStandardFieldKey(); + } else { + return 'custom.'.$this->getRawStandardFieldKey(); + } } public function getConduitDictionaryValue() { diff --git a/src/infrastructure/query/constraint/PhabricatorQueryConstraint.php b/src/infrastructure/query/constraint/PhabricatorQueryConstraint.php index 7a04d9d7db..5b5702673b 100644 --- a/src/infrastructure/query/constraint/PhabricatorQueryConstraint.php +++ b/src/infrastructure/query/constraint/PhabricatorQueryConstraint.php @@ -6,6 +6,7 @@ final class PhabricatorQueryConstraint extends Phobject { const OPERATOR_OR = 'or'; const OPERATOR_NOT = 'not'; const OPERATOR_NULL = 'null'; + const OPERATOR_ANCESTOR = 'ancestor'; private $operator; private $value; diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index 58df7aea33..9996f03ae0 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -1516,8 +1516,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery $constraints = mgroup($constraints, 'getOperator'); foreach ($constraints as $operator => $list) { foreach ($list as $item) { - $value = $item->getValue(); - $this->edgeLogicConstraints[$edge_type][$operator][$value] = $item; + $this->edgeLogicConstraints[$edge_type][$operator][] = $item; } } @@ -1548,6 +1547,47 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery $this->buildEdgeLogicTableAliasCount($alias)); } break; + case PhabricatorQueryConstraint::OPERATOR_ANCESTOR: + // This is tricky. We have a query which specifies multiple + // projects, each of which may have an arbitrarily large number + // of descendants. + + // Suppose the projects are "Engineering" and "Operations", and + // "Engineering" has subprojects X, Y and Z. + + // We first use `FIELD(dst, X, Y, Z)` to produce a 0 if a row + // is not part of Engineering at all, or some number other than + // 0 if it is. + + // Then we use `IF(..., idx, NULL)` to convert the 0 to a NULL and + // any other value to an index (say, 1) for the ancestor. + + // We build these up for every ancestor, then use `COALESCE(...)` + // to select the non-null one, giving us an ancestor which this + // row is a member of. + + // From there, we use `COUNT(DISTINCT(...))` to make sure that + // each result row is a member of all ancestors. + if (count($list) > 1) { + $idx = 1; + $parts = array(); + foreach ($list as $constraint) { + $parts[] = qsprintf( + $conn, + 'IF(FIELD(%T.dst, %Ls) != 0, %d, NULL)', + $alias, + (array)$constraint->getValue(), + $idx++); + } + $parts = implode(', ', $parts); + + $select[] = qsprintf( + $conn, + 'COUNT(DISTINCT(COALESCE(%Q))) %T', + $parts, + $this->buildEdgeLogicTableAliasAncestor($alias)); + } + break; default: break; } @@ -1573,6 +1613,16 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery foreach ($constraints as $operator => $list) { $alias = $this->getEdgeLogicTableAlias($operator, $type); + + $phids = array(); + foreach ($list as $constraint) { + $value = (array)$constraint->getValue(); + foreach ($value as $v) { + $phids[$v] = $v; + } + } + $phids = array_keys($phids); + switch ($operator) { case PhabricatorQueryConstraint::OPERATOR_NOT: $joins[] = qsprintf( @@ -1586,8 +1636,9 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery $alias, $type, $alias, - mpull($list, 'getValue')); + $phids); break; + case PhabricatorQueryConstraint::OPERATOR_ANCESTOR: case PhabricatorQueryConstraint::OPERATOR_AND: case PhabricatorQueryConstraint::OPERATOR_OR: // If we're including results with no matches, we have to degrade @@ -1611,7 +1662,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery $alias, $type, $alias, - mpull($list, 'getValue')); + $phids); break; case PhabricatorQueryConstraint::OPERATOR_NULL: $joins[] = qsprintf( @@ -1711,6 +1762,15 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery count($list)); } break; + case PhabricatorQueryConstraint::OPERATOR_ANCESTOR: + if (count($list) > 1) { + $having[] = qsprintf( + $conn, + '%T = %d', + $this->buildEdgeLogicTableAliasAncestor($alias), + count($list)); + } + break; } } } @@ -1729,6 +1789,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery case PhabricatorQueryConstraint::OPERATOR_NOT: case PhabricatorQueryConstraint::OPERATOR_AND: case PhabricatorQueryConstraint::OPERATOR_OR: + case PhabricatorQueryConstraint::OPERATOR_ANCESTOR: if (count($list) > 1) { return true; } @@ -1758,6 +1819,13 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery return $alias.'_count'; } + /** + * @task edgelogic + */ + private function buildEdgeLogicTableAliasAncestor($alias) { + return $alias.'_ancestor'; + } + /** * Select certain edge logic constraint values. @@ -1781,7 +1849,10 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery } foreach ($constraints as $operator => $list) { foreach ($list as $constraint) { - $values[] = $constraint->getValue(); + $value = (array)$constraint->getValue(); + foreach ($value as $v) { + $values[] = $v; + } } } } @@ -1812,6 +1883,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery PhabricatorQueryConstraint::OPERATOR_AND, PhabricatorQueryConstraint::OPERATOR_OR, PhabricatorQueryConstraint::OPERATOR_NOT, + PhabricatorQueryConstraint::OPERATOR_ANCESTOR, )); if ($project_phids) { $projects = id(new PhabricatorProjectQuery()) diff --git a/src/view/form/PHUIInfoView.php b/src/view/form/PHUIInfoView.php index 3a8f259f52..0da471574c 100644 --- a/src/view/form/PHUIInfoView.php +++ b/src/view/form/PHUIInfoView.php @@ -41,7 +41,6 @@ final class PHUIInfoView extends AphrontView { } public function addButton(PHUIButtonView $button) { - $this->buttons[] = $button; return $this; } diff --git a/src/view/form/control/PHUIFormIconSetControl.php b/src/view/form/control/PHUIFormIconSetControl.php index 913402d8ea..459bff08fa 100644 --- a/src/view/form/control/PHUIFormIconSetControl.php +++ b/src/view/form/control/PHUIFormIconSetControl.php @@ -26,11 +26,21 @@ final class PHUIFormIconSetControl $input_id = celerity_generate_unique_node_id(); $display_id = celerity_generate_unique_node_id(); + $is_disabled = $this->getDisabled(); + + $classes = array(); + $classes[] = 'button'; + $classes[] = 'grey'; + + if ($is_disabled) { + $classes[] = 'disabled'; + } + $button = javelin_tag( 'a', array( 'href' => '#', - 'class' => 'button grey', + 'class' => implode(' ', $classes), 'sigil' => 'phui-form-iconset-button', ), $set->getChooseButtonText()); @@ -79,6 +89,7 @@ final class PHUIFormIconSetControl 'input', array( 'type' => 'hidden', + 'disabled' => ($is_disabled ? 'disabled' : null), 'name' => $this->getName(), 'value' => $this->getValue(), 'id' => $input_id, diff --git a/src/view/page/menu/PhabricatorMainMenuBarExtension.php b/src/view/page/menu/PhabricatorMainMenuBarExtension.php new file mode 100644 index 0000000000..8b863c835c --- /dev/null +++ b/src/view/page/menu/PhabricatorMainMenuBarExtension.php @@ -0,0 +1,88 @@ +viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + + public function setApplication(PhabricatorApplication $application) { + $this->application = $application; + return $this; + } + + public function getApplication() { + return $this->application; + } + + public function setController(PhabricatorController $controller) { + $this->controller = $controller; + return $this; + } + + public function getController() { + return $this->controller; + } + + final public function getExtensionKey() { + return $this->getPhobjectClassConstant('MAINMENUBARKEY'); + } + + public function isExtensionEnabled() { + return true; + } + + public function isExtensionEnabledForViewer(PhabricatorUser $viewer) { + if (!$viewer->isLoggedIn()) { + return false; + } + + if (!$viewer->isUserActivated()) { + return false; + } + + // Don't show menus for users with partial sessions. This usually means + // they have logged in but have not made it through MFA, so we don't want + // to show notification counts, saved queries, etc. + if (!$viewer->hasSession()) { + return false; + } + + if ($viewer->getSession()->getIsPartial()) { + return false; + } + + return true; + } + + abstract public function buildMainMenus(); + + final public static function getAllExtensions() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getExtensionKey') + ->execute(); + } + + final public static function getAllEnabledExtensions() { + $extensions = self::getAllExtensions(); + + foreach ($extensions as $key => $extension) { + if (!$extension->isExtensionEnabled()) { + unset($extensions[$key]); + } + } + + return $extensions; + } + +} diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index 1dc8466df3..54ba622f80 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -30,7 +30,7 @@ final class PhabricatorMainMenuView extends AphrontView { require_celerity_resource('sprite-main-header-css'); $header_id = celerity_generate_unique_node_id(); - $menus = array(); + $menu_bar = array(); $alerts = array(); $search_button = ''; $app_button = ''; @@ -41,7 +41,7 @@ final class PhabricatorMainMenuView extends AphrontView { if (array_filter($menu)) { $alerts[] = $menu; } - $menus = array_merge($menus, $dropdowns); + $menu_bar = array_merge($menu_bar, $dropdowns); $app_button = $this->renderApplicationMenuButton($header_id); $search_button = $this->renderSearchMenuButton($header_id); } else { @@ -73,13 +73,69 @@ final class PhabricatorMainMenuView extends AphrontView { } $applications = PhabricatorApplication::getAllInstalledApplications(); + + $menus = array(); + $controller = $this->getController(); foreach ($applications as $application) { - $menus[] = $application->buildMainMenuExtraNodes( + $app_actions = $application->buildMainMenuItems( $user, - $this->getController()); + $controller); + $app_extra = $application->buildMainMenuExtraNodes( + $user, + $controller); + + foreach ($app_actions as $action) { + $menus[] = id(new PHUIMainMenuView()) + ->setMenuBarItem($action) + ->setOrder($action->getOrder()); + } + + if ($app_extra !== null) { + $menus[] = id(new PHUIMainMenuView()) + ->appendChild($app_extra); + } } - $application_menu = $this->renderApplicationMenu(); + $extensions = PhabricatorMainMenuBarExtension::getAllEnabledExtensions(); + foreach ($extensions as $extension) { + $extension->setViewer($user); + + $controller = $this->getController(); + if ($controller) { + $extension->setController($controller); + $application = $controller->getCurrentApplication(); + if ($application) { + $extension->setApplication($application); + } + } + } + + foreach ($extensions as $key => $extension) { + if (!$extension->isExtensionEnabledForViewer($extension->getViewer())) { + unset($extensions[$key]); + } + } + + foreach ($extensions as $extension) { + foreach ($extension->buildMainMenus() as $menu) { + $menus[] = $menu; + } + } + + $menus = msort($menus, 'getOrder'); + $bar_items = array(); + foreach ($menus as $menu) { + $menu_bar[] = $menu; + + $item = $menu->getMenuBarItem(); + if ($item === null) { + continue; + } + + $bar_items[] = $item; + } + + $application_menu = $this->renderApplicationMenu($bar_items); $classes = array(); $classes[] = 'phabricator-main-menu sprite-main-header'; $classes[] = 'phabricator-main-menu-background'; @@ -98,7 +154,7 @@ final class PhabricatorMainMenuView extends AphrontView { $aural, $application_menu, $search_menu, - $menus, + $menu_bar, )); } @@ -174,21 +230,8 @@ final class PhabricatorMainMenuView extends AphrontView { '')); } - public function renderApplicationMenu() { + private function renderApplicationMenu(array $bar_items) { $user = $this->getUser(); - $controller = $this->getController(); - - $applications = PhabricatorApplication::getAllInstalledApplications(); - - $actions = array(); - foreach ($applications as $application) { - $app_actions = $application->buildMainMenuItems($user, $controller); - foreach ($app_actions as $action) { - $actions[] = $action; - } - } - - $actions = msort($actions, 'getOrder'); $view = $this->getApplicationMenu(); @@ -199,13 +242,13 @@ final class PhabricatorMainMenuView extends AphrontView { $view->addClass('phabricator-dark-menu'); $view->addClass('phabricator-application-menu'); - if ($actions) { + if ($bar_items) { $view->addMenuItem( id(new PHUIListItemView()) ->setType(PHUIListItemView::TYPE_LABEL) ->setName(pht('Actions'))); - foreach ($actions as $action) { - $view->addMenuItem($action); + foreach ($bar_items as $bar_item) { + $view->addMenuItem($bar_item); } } diff --git a/src/view/phui/PHUIDocumentViewPro.php b/src/view/phui/PHUIDocumentViewPro.php index c18732e2c6..dbab3ab54c 100644 --- a/src/view/phui/PHUIDocumentViewPro.php +++ b/src/view/phui/PHUIDocumentViewPro.php @@ -7,6 +7,7 @@ final class PHUIDocumentViewPro extends AphrontTagView { private $bookdescription; private $fluid; private $toc; + private $foot; public function setHeader(PHUIHeaderView $header) { $header->setTall(true); @@ -30,6 +31,11 @@ final class PHUIDocumentViewPro extends AphrontTagView { return $this; } + public function setFoot($foot) { + $this->foot = $foot; + return $this; + } + protected function getTagAttributes() { $classes = array(); @@ -37,6 +43,9 @@ final class PHUIDocumentViewPro extends AphrontTagView { if ($this->fluid) { $classes[] = 'phui-document-fluid'; } + if ($this->foot) { + $classes[] = 'document-has-foot'; + } return array( 'class' => implode(' ', $classes), @@ -95,6 +104,16 @@ final class PHUIDocumentViewPro extends AphrontTagView { $toc); } + $foot_content = null; + if ($this->foot) { + $foot_content = phutil_tag( + 'div', + array( + 'class' => 'phui-document-foot-content', + ), + $this->foot); + } + $content_inner = phutil_tag( 'div', array( @@ -104,6 +123,7 @@ final class PHUIDocumentViewPro extends AphrontTagView { $table_of_contents, $this->header, $main_content, + $foot_content, )); $content = phutil_tag( diff --git a/src/view/phui/PHUIMainMenuView.php b/src/view/phui/PHUIMainMenuView.php new file mode 100644 index 0000000000..7d5910dd7b --- /dev/null +++ b/src/view/phui/PHUIMainMenuView.php @@ -0,0 +1,31 @@ +menuItem = $menu_item; + return $this; + } + + public function getMenuBarItem() { + return $this->menuItem; + } + + public function setOrder($order) { + $this->order = $order; + return $this; + } + + public function getOrder() { + return $this->order; + } + + public function render() { + return $this->renderChildren(); + } + +} diff --git a/src/view/phui/PHUIPagerView.php b/src/view/phui/PHUIPagerView.php index 16d1f96a69..1e14522ca7 100644 --- a/src/view/phui/PHUIPagerView.php +++ b/src/view/phui/PHUIPagerView.php @@ -58,6 +58,10 @@ final class PHUIPagerView extends AphrontView { return $this->hasMorePages; } + public function getHasMorePages() { + return $this->hasMorePages; + } + public function setSurroundingPages($pages) { $this->surroundingPages = max(0, $pages); return $this; diff --git a/src/view/widget/AphrontStackTraceView.php b/src/view/widget/AphrontStackTraceView.php index 11a85dae4b..1d0616df3d 100644 --- a/src/view/widget/AphrontStackTraceView.php +++ b/src/view/widget/AphrontStackTraceView.php @@ -50,14 +50,6 @@ final class AphrontStackTraceView extends AphrontView { if ($file) { if (isset($callsigns[$lib])) { $attrs = array('title' => $file); - try { - $attrs['href'] = $user->loadEditorLink( - '/src/'.$relative, - $part['line'], - $callsigns[$lib]); - } catch (Exception $ex) { - // The database can be inaccessible. - } if (empty($attrs['href'])) { $attrs['href'] = sprintf($path, $callsigns[$lib]). str_replace(DIRECTORY_SEPARATOR, '/', $relative). diff --git a/webroot/rsrc/css/application/calendar/calendar-icon.css b/webroot/rsrc/css/application/calendar/calendar-icon.css deleted file mode 100644 index 35757de9e2..0000000000 --- a/webroot/rsrc/css/application/calendar/calendar-icon.css +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @provides calendar-icon-css - */ - -button.icon-button { - background: {$lightgreybackground}; - border: 1px solid {$lightblueborder}; - position: relative; - width: 16px; - height: 16px; - padding: 12px; - margin: 4px; - text-shadow: none; - box-shadow: none; - box-sizing: content-box; -} - -.icon-grid { - text-align: center; -} - -.icon-icon + .icon-icon { - margin-left: 4px; -} - -button.icon-button.selected { - background: {$bluebackground}; -} diff --git a/webroot/rsrc/css/application/phame/phame.css b/webroot/rsrc/css/application/phame/phame.css index 02d8761a6b..65604a6dd6 100644 --- a/webroot/rsrc/css/application/phame/phame.css +++ b/webroot/rsrc/css/application/phame/phame.css @@ -134,3 +134,106 @@ width: 14px; color: {$lightbluetext}; } + +.phame-next-post-view { + margin: 0 auto; + padding: 12px 0; + font-family: 'Aleo', {$fontfamily}; +} + +.phame-next { + width: 360px; + float: right; + text-align: right; + color: #000; + position: relative; +} + +.phame-next-arrow, +.phame-previous-arrow { + border: 1px solid {$lightblueborder}; + border-radius: 36px; + height: 36px; + width: 36px; + text-align: center; +} + +.phame-next-arrow .phui-icon-view, +.phame-previous-arrow .phui-icon-view { + height: 36px; + width: 34px; + font-size: 24px; + line-height: 35px; + color: {$lightblueborder}; +} + +.phame-previous-arrow { + float: left; +} + +.phame-next-arrow { + float: right; +} + +.phame-previous { + width: 360px; + float: left; + text-align: left; + color: #000; + position: relative; +} + +.device .phame-previous, +.device .phame-next { + width: 100px; +} + +.device .phame-previous .phame-previous-title, +.device .phame-next .phame-next-title { + display: none; +} + +.phame-next:hover, +.phame-previous:hover { + text-decoration: none; +} + +.phame-next:hover .phame-next-arrow, +.phame-previous:hover .phame-previous-arrow { + border-color: {$indigo}; +} + +.phame-next:hover .phui-icon-view, +.phame-previous:hover .phui-icon-view { + color: {$indigo}; +} + +.phame-next-header, +.phame-previous-header { + font-weight: bold; + font-size: {$biggerfontsize}; +} + +.phame-next-header { + top: 2px; + right: 50px; + position: absolute; +} + +.phame-next-title { + top: 22px; + right: 50px; + position: absolute; +} + +.phame-previous-header { + top: 2px; + left: 50px; + position: absolute; +} + +.phame-previous-title { + top: 22px; + left: 50px; + position: absolute; +} diff --git a/webroot/rsrc/css/layout/phabricator-side-menu-view.css b/webroot/rsrc/css/layout/phabricator-side-menu-view.css index 5a3521b17e..7f0d2bc720 100644 --- a/webroot/rsrc/css/layout/phabricator-side-menu-view.css +++ b/webroot/rsrc/css/layout/phabricator-side-menu-view.css @@ -89,6 +89,10 @@ color: {$blue}; } +.phabricator-icon-nav .phabricator-side-menu .phui-list-item-icon.grey { + color: {$lightgreyborder}; +} + .phabricator-icon-nav .phabricator-side-menu .phui-list-item-selected { border: none; } diff --git a/webroot/rsrc/css/phui/phui-document-pro.css b/webroot/rsrc/css/phui/phui-document-pro.css index ab3a22656c..90f268cf66 100644 --- a/webroot/rsrc/css/phui/phui-document-pro.css +++ b/webroot/rsrc/css/phui/phui-document-pro.css @@ -179,11 +179,6 @@ a.button.phui-document-toc { display: none; } -.phui-document-view-pro-box .phui-timeline-major-event .phui-timeline-content - .phui-timeline-core-content { - -} - .phui-document-view-pro-box .phui-object-box { margin: 0; } @@ -195,3 +190,11 @@ a.button.phui-document-toc { .phui-document-view-pro-box .phui-object-box .remarkup-assist-textarea { height: 9em; } + +.document-has-foot .phui-document-view-pro { + padding-bottom: 0; +} + +.phui-document-foot-content { + margin: 64px 0 32px; +} diff --git a/webroot/rsrc/css/application/projects/project-icon.css b/webroot/rsrc/css/phui/phui-icon-set-selector.css similarity index 84% rename from webroot/rsrc/css/application/projects/project-icon.css rename to webroot/rsrc/css/phui/phui-icon-set-selector.css index 1af85b30d6..09a0362246 100644 --- a/webroot/rsrc/css/application/projects/project-icon.css +++ b/webroot/rsrc/css/phui/phui-icon-set-selector.css @@ -1,5 +1,5 @@ /** - * @provides project-icon-css + * @provides phui-icon-set-selector-css */ button.icon-button { @@ -25,4 +25,5 @@ button.icon-button { button.icon-button.selected { background: {$bluebackground}; + border: 1px solid {$blueborder}; } diff --git a/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js b/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js index 97f1de568d..a3a7c7bdf8 100644 --- a/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js +++ b/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js @@ -37,6 +37,7 @@ JX.behavior('diffusion-commit-graph', function(config) { // Stroke with fill (for commit circles). function fstroke(c) { + cxt.lineWidth = 1; cxt.fillStyle = color(c); cxt.strokeStyle = '#ffffff'; cxt.fill(); @@ -52,7 +53,7 @@ JX.behavior('diffusion-commit-graph', function(config) { return (col * cell) + (cell / 2); }; - var h = 30; + var h = 32; var w = cell * config.count; var canvas = JX.$N('canvas', {width: w, height: h}); @@ -117,16 +118,17 @@ JX.behavior('diffusion-commit-graph', function(config) { case 'o': case '^': case '|': - if (c == 'o' || c == '^') { - origin = xpos(jj); + case 'x': + case 'X': + + if (c !== 'X') { + cxt.beginPath(); + cxt.moveTo(xpos(jj), (c == '^' ? h/2 : 0)); + cxt.lineTo(xpos(jj), (c == 'x' ? h/2 : h)); + lstroke(jj); } - cxt.beginPath(); - cxt.moveTo(xpos(jj), (c == '^' ? h/2 : 0)); - cxt.lineTo(xpos(jj), h); - lstroke(jj); - - if (c == 'o' || c == '^') { + if (c == 'o' || c == '^' || c == 'x' || c == 'X') { cxt.beginPath(); cxt.arc(xpos(jj), h/2, 3, 0, 2 * Math.PI, true); fstroke(jj); diff --git a/webroot/rsrc/js/core/behavior-choose-control.js b/webroot/rsrc/js/core/behavior-choose-control.js index 8a33ac4eb3..68fbbbc8e0 100644 --- a/webroot/rsrc/js/core/behavior-choose-control.js +++ b/webroot/rsrc/js/core/behavior-choose-control.js @@ -15,14 +15,19 @@ JX.behavior('choose-control', function() { e.kill(); var data = e.getNodeData('phui-form-iconset'); + var input = JX.$(data.inputID); + + if (input.disabled) { + return; + } var params = { - value: JX.$(data.inputID).value + icon: input.value }; new JX.Workflow(data.uri, params) .setHandler(function(r) { - JX.$(data.inputID).value = r.value; + input.value = r.value; JX.DOM.setContent(JX.$(data.displayID), JX.$H(r.display)); }) .start(); diff --git a/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js b/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js index 293c44ed9b..9dabffeb10 100644 --- a/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js +++ b/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js @@ -254,7 +254,7 @@ JX.behavior('phabricator-remarkup-assist', function(config) { var value = area.value; var data = { - corpus: value + text: value }; var onupdate = function(r) { diff --git a/webroot/rsrc/js/core/behavior-remarkup-preview.js b/webroot/rsrc/js/core/behavior-remarkup-preview.js index f703e56266..4e79c48b59 100644 --- a/webroot/rsrc/js/core/behavior-remarkup-preview.js +++ b/webroot/rsrc/js/core/behavior-remarkup-preview.js @@ -8,16 +8,26 @@ JX.behavior('remarkup-preview', function(config) { + // Don't bother with any of this on mobile. + if (JX.Device.getDevice() !== 'desktop') { + return; + } + var preview = JX.$(config.previewID); var control = JX.$(config.controlID); var callback = function(r) { - JX.DOM.setContent(preview, JX.$H(r)); + // This currently accepts responses from two controllers: + // Old: PhabricatorMarkupPreviewController + // New: PhabricatorApplicationTransactionRemarkupPreviewController + // TODO: Swap everything to just the new controller. + + JX.DOM.setContent(preview, JX.$H(r.content || r)); }; var getdata = function() { return { - text : control.value + text: control.value }; };